mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
Adds Remote Cluster related API endpoints (#27432)
* Adds Remote Cluster related API endpoints
New endpoints for the following routes are added:
- Get Remote Clusters at `GET /api/v4/remotecluster`
- Create Remote Cluster at `POST /api/v4/remotecluster`
- Accept Remote Cluster invite at `POST
/api/v4/remotecluster/accept_invite`
- Generate Remote Cluster invite at `POST
/api/v4/remotecluster/{remote_id}/generate_invite`
- Get Remote Cluster at `GET /api/v4/remotecluster/{remote_id}`
- Patch Remote Cluster at `PATCH /api/v4/remotecluster/{remote_id}`
- Delete Remote Cluster at `DELETE /api/v4/remotecluster/{remote_id}`
These endpoints are planned to be used from the system console, and
gated through the `manage_secure_connections` permission.
* Update server/channels/api4/remote_cluster_test.go
Co-authored-by: Doug Lauder <wiggin77@warpmail.net>
* Fix AppError names
---------
Co-authored-by: Doug Lauder <wiggin77@warpmail.net>
Co-authored-by: Mattermost Build <build@mattermost.com>
This commit is contained in:
committed by
GitHub
parent
cc5e87ae24
commit
809ad4f76d
@@ -40,6 +40,7 @@ build-v4: node_modules playbooks
|
||||
@cat $(V4_SRC)/roles.yaml >> $(V4_YAML)
|
||||
@cat $(V4_SRC)/schemes.yaml >> $(V4_YAML)
|
||||
@cat $(V4_SRC)/service_terms.yaml >> $(V4_YAML)
|
||||
@cat $(V4_SRC)/remoteclusters.yaml >> $(V4_YAML)
|
||||
@cat $(V4_SRC)/sharedchannels.yaml >> $(V4_YAML)
|
||||
@cat $(V4_SRC)/reactions.yaml >> $(V4_YAML)
|
||||
@cat $(V4_SRC)/actions.yaml >> $(V4_YAML)
|
||||
|
||||
@@ -3485,6 +3485,39 @@ components:
|
||||
remote_id:
|
||||
description: Id of the remote cluster where the shared channel is homed
|
||||
type: string
|
||||
RemoteCluster:
|
||||
type: object
|
||||
properties:
|
||||
remote_id:
|
||||
type: string
|
||||
remote_team_id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
display_name:
|
||||
type: string
|
||||
site_url:
|
||||
description: URL of the remote cluster
|
||||
type: string
|
||||
create_at:
|
||||
description: Time in milliseconds that the remote cluster was created
|
||||
type: integer
|
||||
last_ping_at:
|
||||
description: Time in milliseconds when the last ping to the remote cluster was run
|
||||
type: integer
|
||||
token:
|
||||
type: string
|
||||
remote_token:
|
||||
type: string
|
||||
topics:
|
||||
type: string
|
||||
creator_id:
|
||||
type: string
|
||||
plugin_id:
|
||||
type: string
|
||||
options:
|
||||
description: A bitmask with a set of option flags
|
||||
type: integer
|
||||
RemoteClusterInfo:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
@@ -598,6 +598,7 @@ x-tagGroups:
|
||||
- roles
|
||||
- schemes
|
||||
- integration_actions
|
||||
- remote clusters
|
||||
- shared channels
|
||||
- terms of service
|
||||
- imports
|
||||
|
||||
282
api/v4/source/remoteclusters.yaml
Normal file
282
api/v4/source/remoteclusters.yaml
Normal file
@@ -0,0 +1,282 @@
|
||||
"/api/v4/remotecluster":
|
||||
get:
|
||||
tags:
|
||||
- remote clusters
|
||||
summary: Get a list of remote clusters.
|
||||
description: |
|
||||
Get a list of remote clusters.
|
||||
|
||||
##### Permissions
|
||||
`manage_secure_connections`
|
||||
operationId: GetRemoteClusters
|
||||
parameters:
|
||||
- name: page
|
||||
in: query
|
||||
description: The page to select
|
||||
schema:
|
||||
type: integer
|
||||
- name: per_page
|
||||
in: query
|
||||
description: The number of remote clusters per page
|
||||
schema:
|
||||
type: integer
|
||||
- name: exclude_offline
|
||||
in: query
|
||||
description: Exclude offline remote clusters
|
||||
schema:
|
||||
type: boolean
|
||||
- name: in_channel
|
||||
in: query
|
||||
description: Select remote clusters in channel
|
||||
schema:
|
||||
type: string
|
||||
- name: not_in_channel
|
||||
in: query
|
||||
description: Select remote clusters not in this channel
|
||||
schema:
|
||||
type: string
|
||||
- name: only_confirmed
|
||||
in: query
|
||||
description: Select only remote clusters already confirmed
|
||||
schema:
|
||||
type: boolean
|
||||
- name: only_plugins
|
||||
in: query
|
||||
description: Select only remote clusters that belong to a plugin
|
||||
schema:
|
||||
type: boolean
|
||||
- name: exclude_plugins
|
||||
in: query
|
||||
description: Select only remote clusters that don't belong to a plugin
|
||||
schema:
|
||||
type: boolean
|
||||
responses:
|
||||
"200":
|
||||
description: Remote clusters fetch successful. Result might be empty.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/RemoteCluster"
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
|
||||
post:
|
||||
tags:
|
||||
- remote clusters
|
||||
summary: Create a new remote cluster.
|
||||
description: |
|
||||
Create a new remote cluster and generate an invite code.
|
||||
|
||||
##### Permissions
|
||||
`manage_secure_connections`
|
||||
operationId: CreateRemoteCluster
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
- password
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
display_name:
|
||||
type: string
|
||||
password:
|
||||
type: string
|
||||
description: The password to use in the invite code.
|
||||
responses:
|
||||
"201":
|
||||
description: Remote cluster creation successful
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
remote_cluster:
|
||||
$ref: "#/components/schemas/RemoteCluster"
|
||||
invite:
|
||||
type: string
|
||||
description: The encrypted invite for the newly created remote cluster
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
|
||||
"/api/v4/remotecluster/{remote_id}":
|
||||
get:
|
||||
tags:
|
||||
- remote clusters
|
||||
summary: Get a remote cluster.
|
||||
description: |
|
||||
Get the Remote Cluster details from the provided id string.
|
||||
|
||||
##### Permissions
|
||||
`manage_secure_connections`
|
||||
operationId: GetRemoteCluster
|
||||
parameters:
|
||||
- name: remote_id
|
||||
in: path
|
||||
description: Remote Cluster GUID
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: Remote Cluster retrieval successful
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/RemoteCluster"
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFound"
|
||||
patch:
|
||||
tags:
|
||||
- remote clusters
|
||||
summary: Patch a remote cluster.
|
||||
description: |
|
||||
Partially update a Remote Cluster by providing only the fields you want to update. Ommited fields will not be updated.
|
||||
|
||||
##### Permissions
|
||||
`manage_secure_connections`
|
||||
operationId: PatchRemoteCluster
|
||||
parameters:
|
||||
- name: remote_id
|
||||
in: path
|
||||
description: Remote Cluster GUID
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
display_name:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: Remote Cluster patch successful
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/RemoteCluster"
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFound"
|
||||
delete:
|
||||
tags:
|
||||
- remote clusters
|
||||
summary: Delete a remote cluster.
|
||||
description: |
|
||||
Deletes a Remote Cluster.
|
||||
|
||||
##### Permissions
|
||||
`manage_secure_connections`
|
||||
operationId: DeleteRemoteCluster
|
||||
parameters:
|
||||
- name: remote_id
|
||||
in: path
|
||||
description: Remote Cluster GUID
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"204":
|
||||
description: Remote Cluster deletion successful
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFound"
|
||||
|
||||
"/api/v4/remotecluster/{remote_id}/generate_invite":
|
||||
post:
|
||||
tags:
|
||||
- remote clusters
|
||||
summary: Generate invite code.
|
||||
description: |
|
||||
Generates an invite code for a given remote cluster.
|
||||
|
||||
##### Permissions
|
||||
`manage_secure_connections`
|
||||
operationId: GenerateRemoteClusterInvite
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- password
|
||||
properties:
|
||||
password:
|
||||
type: string
|
||||
description: The password to encrypt the invite code with.
|
||||
responses:
|
||||
"201":
|
||||
description: Invite code generated
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
|
||||
"/api/v4/remotecluster/accept_invite":
|
||||
post:
|
||||
tags:
|
||||
- remote clusters
|
||||
summary: Accept a remote cluster invite code.
|
||||
description: |
|
||||
Accepts a remote cluster invite code.
|
||||
|
||||
##### Permissions
|
||||
`manage_secure_connections`
|
||||
operationId: AcceptRemoteClusterInvite
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- invite
|
||||
- name
|
||||
- password
|
||||
properties:
|
||||
invite:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
display_name:
|
||||
type: string
|
||||
password:
|
||||
type: string
|
||||
description: The password to decrypt the invite code.
|
||||
responses:
|
||||
"201":
|
||||
description: Invite successfully accepted
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
$ref: "#/components/schemas/RemoteCluster"
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
@@ -1086,6 +1086,11 @@ func CheckCreatedStatus(tb testing.TB, resp *model.Response) {
|
||||
checkHTTPStatus(tb, resp, http.StatusCreated)
|
||||
}
|
||||
|
||||
func CheckNoContentStatus(tb testing.TB, resp *model.Response) {
|
||||
tb.Helper()
|
||||
checkHTTPStatus(tb, resp, http.StatusNoContent)
|
||||
}
|
||||
|
||||
func CheckForbiddenStatus(tb testing.TB, resp *model.Response) {
|
||||
tb.Helper()
|
||||
checkHTTPStatus(tb, resp, http.StatusForbidden)
|
||||
@@ -1106,6 +1111,11 @@ func CheckBadRequestStatus(tb testing.TB, resp *model.Response) {
|
||||
checkHTTPStatus(tb, resp, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
func CheckUnprocessableEntityStatus(tb testing.TB, resp *model.Response) {
|
||||
tb.Helper()
|
||||
checkHTTPStatus(tb, resp, http.StatusUnprocessableEntity)
|
||||
}
|
||||
|
||||
func CheckNotImplementedStatus(tb testing.TB, resp *model.Response) {
|
||||
tb.Helper()
|
||||
checkHTTPStatus(tb, resp, http.StatusNotImplemented)
|
||||
|
||||
@@ -22,6 +22,14 @@ func (api *API) InitRemoteCluster() {
|
||||
api.BaseRoutes.RemoteCluster.Handle("/confirm_invite", api.RemoteClusterTokenRequired(remoteClusterConfirmInvite)).Methods("POST")
|
||||
api.BaseRoutes.RemoteCluster.Handle("/upload/{upload_id:[A-Za-z0-9]+}", api.RemoteClusterTokenRequired(uploadRemoteData, handlerParamFileAPI)).Methods("POST")
|
||||
api.BaseRoutes.RemoteCluster.Handle("/{user_id:[A-Za-z0-9]+}/image", api.RemoteClusterTokenRequired(remoteSetProfileImage, handlerParamFileAPI)).Methods("POST")
|
||||
|
||||
api.BaseRoutes.RemoteCluster.Handle("", api.APISessionRequired(getRemoteClusters)).Methods("GET")
|
||||
api.BaseRoutes.RemoteCluster.Handle("", api.APISessionRequired(createRemoteCluster)).Methods("POST")
|
||||
api.BaseRoutes.RemoteCluster.Handle("/accept_invite", api.APISessionRequired(remoteClusterAcceptInvite)).Methods("POST")
|
||||
api.BaseRoutes.RemoteCluster.Handle("/{remote_id:[A-Za-z0-9]+}/generate_invite", api.APISessionRequired(generateRemoteClusterInvite)).Methods("POST")
|
||||
api.BaseRoutes.RemoteCluster.Handle("/{remote_id:[A-Za-z0-9]+}", api.APISessionRequired(getRemoteCluster)).Methods("GET")
|
||||
api.BaseRoutes.RemoteCluster.Handle("/{remote_id:[A-Za-z0-9]+}", api.APISessionRequired(patchRemoteCluster)).Methods("PATCH")
|
||||
api.BaseRoutes.RemoteCluster.Handle("/{remote_id:[A-Za-z0-9]+}", api.APISessionRequired(deleteRemoteCluster)).Methods("DELETE")
|
||||
}
|
||||
|
||||
func remoteClusterPing(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
@@ -293,3 +301,359 @@ func remoteSetProfileImage(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
ReturnStatusOK(w)
|
||||
}
|
||||
|
||||
func getRemoteClusters(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSecureConnections) {
|
||||
c.SetPermissionError(model.PermissionManageSecureConnections)
|
||||
return
|
||||
}
|
||||
|
||||
// make sure remote cluster service is enabled.
|
||||
if _, appErr := c.App.GetRemoteClusterService(); appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
filter := model.RemoteClusterQueryFilter{
|
||||
ExcludeOffline: c.Params.ExcludeOffline,
|
||||
InChannel: c.Params.InChannel,
|
||||
NotInChannel: c.Params.NotInChannel,
|
||||
Topic: c.Params.Topic,
|
||||
CreatorId: c.Params.CreatorId,
|
||||
OnlyConfirmed: c.Params.OnlyConfirmed,
|
||||
PluginID: c.Params.PluginId,
|
||||
OnlyPlugins: c.Params.OnlyPlugins,
|
||||
ExcludePlugins: c.Params.ExcludePlugins,
|
||||
}
|
||||
|
||||
rcs, appErr := c.App.GetAllRemoteClusters(c.Params.Page, c.Params.PerPage, filter)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
for _, rc := range rcs {
|
||||
rc.Sanitize()
|
||||
}
|
||||
|
||||
b, err := json.Marshal(rcs)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("getRemoteClusters", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(b)
|
||||
}
|
||||
|
||||
func createRemoteCluster(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSecureConnections) {
|
||||
c.SetPermissionError(model.PermissionManageSecureConnections)
|
||||
return
|
||||
}
|
||||
|
||||
// make sure remote cluster service is enabled.
|
||||
if _, appErr := c.App.GetRemoteClusterService(); appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
auditRec := c.MakeAuditRecord("createRemoteCluster", audit.Fail)
|
||||
defer c.LogAuditRec(auditRec)
|
||||
|
||||
var rcWithTeamAndPassword model.RemoteClusterWithPassword
|
||||
if jsonErr := json.NewDecoder(r.Body).Decode(&rcWithTeamAndPassword); jsonErr != nil {
|
||||
c.SetInvalidParamWithErr("remoteCluster", jsonErr)
|
||||
return
|
||||
}
|
||||
|
||||
if rcWithTeamAndPassword.Password == "" {
|
||||
c.SetInvalidParam("password")
|
||||
return
|
||||
}
|
||||
|
||||
url := c.App.GetSiteURL()
|
||||
if url == "" {
|
||||
c.Err = model.NewAppError("createRemoteCluster", "api.get_site_url_error", nil, "", http.StatusUnprocessableEntity)
|
||||
return
|
||||
}
|
||||
|
||||
if rcWithTeamAndPassword.DisplayName == "" {
|
||||
rcWithTeamAndPassword.DisplayName = rcWithTeamAndPassword.Name
|
||||
}
|
||||
|
||||
rc := &model.RemoteCluster{
|
||||
Name: rcWithTeamAndPassword.Name,
|
||||
DisplayName: rcWithTeamAndPassword.DisplayName,
|
||||
SiteURL: model.SiteURLPending + model.NewId(),
|
||||
Token: model.NewId(),
|
||||
CreatorId: c.AppContext.Session().UserId,
|
||||
}
|
||||
|
||||
audit.AddEventParameterAuditable(auditRec, "remotecluster", rc)
|
||||
|
||||
rcSaved, appErr := c.App.AddRemoteCluster(rc)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
rcSaved.Sanitize()
|
||||
|
||||
inviteCode, iErr := c.App.CreateRemoteClusterInvite(rcSaved.RemoteId, url, rcSaved.Token, rcWithTeamAndPassword.Password)
|
||||
if iErr != nil {
|
||||
c.Err = iErr
|
||||
return
|
||||
}
|
||||
|
||||
auditRec.Success()
|
||||
auditRec.AddEventResultState(rcSaved)
|
||||
auditRec.AddEventObjectType("remotecluster")
|
||||
|
||||
b, err := json.Marshal(model.RemoteClusterWithInvite{RemoteCluster: rcSaved, Invite: inviteCode})
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("createRemoteCluster", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
w.Write(b)
|
||||
}
|
||||
|
||||
func remoteClusterAcceptInvite(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSecureConnections) {
|
||||
c.SetPermissionError(model.PermissionManageSecureConnections)
|
||||
return
|
||||
}
|
||||
|
||||
// make sure remote cluster service is enabled.
|
||||
rcs, appErr := c.App.GetRemoteClusterService()
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
auditRec := c.MakeAuditRecord("remoteClusterAcceptInvite", audit.Fail)
|
||||
defer c.LogAuditRec(auditRec)
|
||||
|
||||
var rcAcceptInvite model.RemoteClusterAcceptInvite
|
||||
if jsonErr := json.NewDecoder(r.Body).Decode(&rcAcceptInvite); jsonErr != nil {
|
||||
c.SetInvalidParamWithErr("remoteCluster", jsonErr)
|
||||
return
|
||||
}
|
||||
|
||||
audit.AddEventParameter(auditRec, "name", rcAcceptInvite.Name)
|
||||
audit.AddEventParameter(auditRec, "display_name", rcAcceptInvite.DisplayName)
|
||||
|
||||
if rcAcceptInvite.DisplayName == "" {
|
||||
rcAcceptInvite.DisplayName = rcAcceptInvite.Name
|
||||
}
|
||||
|
||||
invite, dErr := c.App.DecryptRemoteClusterInvite(rcAcceptInvite.Invite, rcAcceptInvite.Password)
|
||||
if dErr != nil {
|
||||
c.Err = dErr
|
||||
return
|
||||
}
|
||||
|
||||
audit.AddEventParameter(auditRec, "site_url", invite.SiteURL)
|
||||
|
||||
url := c.App.GetSiteURL()
|
||||
if url == "" {
|
||||
c.Err = model.NewAppError("remoteClusterAcceptInvite", "api.get_site_url_error", nil, "", http.StatusUnprocessableEntity)
|
||||
return
|
||||
}
|
||||
|
||||
rc, aErr := rcs.AcceptInvitation(invite, rcAcceptInvite.Name, rcAcceptInvite.DisplayName, c.AppContext.Session().UserId, url)
|
||||
if aErr != nil {
|
||||
c.Err = model.NewAppError("remoteClusterAcceptInvite", "api.remote_cluster.accept_invitation_error", nil, "", http.StatusInternalServerError).Wrap(aErr)
|
||||
if appErr, ok := aErr.(*model.AppError); ok {
|
||||
c.Err = appErr
|
||||
}
|
||||
return
|
||||
}
|
||||
rc.Sanitize()
|
||||
|
||||
auditRec.Success()
|
||||
auditRec.AddEventResultState(rc)
|
||||
auditRec.AddEventObjectType("remotecluster")
|
||||
|
||||
b, err := json.Marshal(rc)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("remoteClusterAcceptInvite", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
w.Write(b)
|
||||
}
|
||||
|
||||
func generateRemoteClusterInvite(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.RequireRemoteId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSecureConnections) {
|
||||
c.SetPermissionError(model.PermissionManageSecureConnections)
|
||||
return
|
||||
}
|
||||
|
||||
// make sure remote cluster service is enabled.
|
||||
if _, appErr := c.App.GetRemoteClusterService(); appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
auditRec := c.MakeAuditRecord("generateRemoteClusterInvite", audit.Fail)
|
||||
defer c.LogAuditRec(auditRec)
|
||||
audit.AddEventParameter(auditRec, "remote_id", c.Params.RemoteId)
|
||||
|
||||
props := model.MapFromJSON(r.Body)
|
||||
password := props["password"]
|
||||
if password == "" {
|
||||
c.SetInvalidParam("password")
|
||||
return
|
||||
}
|
||||
|
||||
url := c.App.GetSiteURL()
|
||||
if url == "" {
|
||||
c.Err = model.NewAppError("generateRemoteClusterInvite", "api.get_site_url_error", nil, "", http.StatusUnprocessableEntity)
|
||||
return
|
||||
}
|
||||
|
||||
rc, appErr := c.App.GetRemoteCluster(c.Params.RemoteId)
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
inviteCode, invErr := c.App.CreateRemoteClusterInvite(rc.RemoteId, url, rc.Token, password)
|
||||
if invErr != nil {
|
||||
c.Err = invErr
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
w.Write([]byte(inviteCode))
|
||||
}
|
||||
|
||||
func getRemoteCluster(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSecureConnections) {
|
||||
c.SetPermissionError(model.PermissionManageSecureConnections)
|
||||
return
|
||||
}
|
||||
|
||||
c.RequireRemoteId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// make sure remote cluster service is enabled.
|
||||
if _, appErr := c.App.GetRemoteClusterService(); appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
rc, err := c.App.GetRemoteCluster(c.Params.RemoteId)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
rc.Sanitize()
|
||||
|
||||
if err := json.NewEncoder(w).Encode(rc); err != nil {
|
||||
c.Logger.Warn("Error while writing response", mlog.Err(err))
|
||||
}
|
||||
}
|
||||
|
||||
func patchRemoteCluster(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSecureConnections) {
|
||||
c.SetPermissionError(model.PermissionManageSecureConnections)
|
||||
return
|
||||
}
|
||||
|
||||
c.RequireRemoteId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// make sure remote cluster service is enabled.
|
||||
if _, appErr := c.App.GetRemoteClusterService(); appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
var patch model.RemoteClusterPatch
|
||||
if jsonErr := json.NewDecoder(r.Body).Decode(&patch); jsonErr != nil {
|
||||
c.SetInvalidParamWithErr("remotecluster", jsonErr)
|
||||
return
|
||||
}
|
||||
|
||||
auditRec := c.MakeAuditRecord("patchRemoteCluster", audit.Fail)
|
||||
audit.AddEventParameter(auditRec, "remote_id", c.Params.RemoteId)
|
||||
audit.AddEventParameterAuditable(auditRec, "remotecluster_patch", &patch)
|
||||
defer c.LogAuditRec(auditRec)
|
||||
|
||||
orc, err := c.App.GetRemoteCluster(c.Params.RemoteId)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
auditRec.AddEventPriorState(orc)
|
||||
auditRec.AddEventObjectType("remotecluster")
|
||||
|
||||
updatedRC, err := c.App.PatchRemoteCluster(c.Params.RemoteId, &patch)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
auditRec.Success()
|
||||
auditRec.AddEventResultState(updatedRC)
|
||||
|
||||
if err := json.NewEncoder(w).Encode(updatedRC); err != nil {
|
||||
c.Logger.Warn("Error while writing response", mlog.Err(err))
|
||||
}
|
||||
}
|
||||
|
||||
func deleteRemoteCluster(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.RequireRemoteId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSecureConnections) {
|
||||
c.SetPermissionError(model.PermissionManageSecureConnections)
|
||||
return
|
||||
}
|
||||
|
||||
// make sure remote cluster service is enabled.
|
||||
if _, appErr := c.App.GetRemoteClusterService(); appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
auditRec := c.MakeAuditRecord("deleteRemoteCluster", audit.Fail)
|
||||
audit.AddEventParameter(auditRec, "remote_id", c.Params.RemoteId)
|
||||
defer c.LogAuditRec(auditRec)
|
||||
|
||||
orc, err := c.App.GetRemoteCluster(c.Params.RemoteId)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
auditRec.AddEventPriorState(orc)
|
||||
auditRec.AddEventObjectType("remotecluster")
|
||||
|
||||
deleted, err := c.App.DeleteRemoteCluster(c.Params.RemoteId)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
if !deleted {
|
||||
c.Err = model.NewAppError("deleteRemoteCluster", "api.remote_cluster.cluster_not_deleted", nil, "", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
auditRec.Success()
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
559
server/channels/api4/remote_cluster_test.go
Normal file
559
server/channels/api4/remote_cluster_test.go
Normal file
@@ -0,0 +1,559 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package api4
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/base64"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGetRemoteClusters(t *testing.T) {
|
||||
t.Run("Should not work if the remote cluster service is not enabled", func(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
rcs, resp, err := th.SystemAdminClient.GetRemoteClusters(context.Background(), 0, 999999, model.RemoteClusterQueryFilter{})
|
||||
CheckNotImplementedStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
require.Empty(t, rcs)
|
||||
})
|
||||
|
||||
th := setupForSharedChannels(t)
|
||||
defer th.TearDown()
|
||||
|
||||
newRCs := []*model.RemoteCluster{
|
||||
{
|
||||
RemoteId: model.NewId(),
|
||||
Name: "remote1",
|
||||
SiteURL: "http://example1.com",
|
||||
CreatorId: th.SystemAdminUser.Id,
|
||||
Token: model.NewId(),
|
||||
RemoteToken: model.NewId(),
|
||||
},
|
||||
{
|
||||
RemoteId: model.NewId(),
|
||||
Name: "remote2",
|
||||
SiteURL: "http://example2.com",
|
||||
CreatorId: th.SystemAdminUser.Id,
|
||||
},
|
||||
{
|
||||
RemoteId: model.NewId(),
|
||||
Name: "remote3",
|
||||
SiteURL: "http://example3.com",
|
||||
CreatorId: th.SystemAdminUser.Id,
|
||||
PluginID: model.NewId(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, rc := range newRCs {
|
||||
_, appErr := th.App.AddRemoteCluster(rc)
|
||||
require.Nil(t, appErr)
|
||||
}
|
||||
|
||||
t.Run("The returned data should be sanitized", func(t *testing.T) {
|
||||
rcs, resp, err := th.SystemAdminClient.GetRemoteClusters(context.Background(), 0, 999999, model.RemoteClusterQueryFilter{})
|
||||
CheckOKStatus(t, resp)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, rcs[0].Name, "remote")
|
||||
require.Zero(t, rcs[0].Token)
|
||||
require.Zero(t, rcs[0].RemoteToken)
|
||||
})
|
||||
|
||||
testCases := []struct {
|
||||
Name string
|
||||
Client *model.Client4
|
||||
Page int
|
||||
PerPage int
|
||||
Filter model.RemoteClusterQueryFilter
|
||||
ExpectedStatusCode int
|
||||
ExpectedError bool
|
||||
ExpectedNames []string
|
||||
}{
|
||||
{
|
||||
Name: "Should reject if the user has not sufficient permissions",
|
||||
Client: th.Client,
|
||||
Page: 0,
|
||||
PerPage: 999999,
|
||||
Filter: model.RemoteClusterQueryFilter{},
|
||||
ExpectedStatusCode: 403,
|
||||
ExpectedError: true,
|
||||
ExpectedNames: []string{},
|
||||
},
|
||||
{
|
||||
Name: "Should return all remote clusters",
|
||||
Client: th.SystemAdminClient,
|
||||
Page: 0,
|
||||
PerPage: 999999,
|
||||
Filter: model.RemoteClusterQueryFilter{},
|
||||
ExpectedStatusCode: 200,
|
||||
ExpectedError: false,
|
||||
ExpectedNames: []string{"remote1", "remote2", "remote3"},
|
||||
},
|
||||
{
|
||||
Name: "Should return all remote clusters but those belonging to plugins",
|
||||
Client: th.SystemAdminClient,
|
||||
Page: 0,
|
||||
PerPage: 999999,
|
||||
Filter: model.RemoteClusterQueryFilter{ExcludePlugins: true},
|
||||
ExpectedStatusCode: 200,
|
||||
ExpectedError: false,
|
||||
ExpectedNames: []string{"remote1", "remote2"},
|
||||
},
|
||||
{
|
||||
Name: "Should return only remote clusters belonging to plugins",
|
||||
Client: th.SystemAdminClient,
|
||||
Page: 0,
|
||||
PerPage: 999999,
|
||||
Filter: model.RemoteClusterQueryFilter{OnlyPlugins: true},
|
||||
ExpectedStatusCode: 200,
|
||||
ExpectedError: false,
|
||||
ExpectedNames: []string{"remote3"},
|
||||
},
|
||||
{
|
||||
Name: "Should work as a paginated endpoint",
|
||||
Client: th.SystemAdminClient,
|
||||
Page: 1,
|
||||
PerPage: 1,
|
||||
Filter: model.RemoteClusterQueryFilter{},
|
||||
ExpectedStatusCode: 200,
|
||||
ExpectedError: false,
|
||||
ExpectedNames: []string{"remote2"},
|
||||
},
|
||||
{
|
||||
Name: "Should return an empty set with a successful status",
|
||||
Client: th.SystemAdminClient,
|
||||
Page: 0,
|
||||
PerPage: 999999,
|
||||
Filter: model.RemoteClusterQueryFilter{InChannel: model.NewId()},
|
||||
ExpectedStatusCode: 200,
|
||||
ExpectedError: false,
|
||||
ExpectedNames: []string{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
rcs, resp, err := tc.Client.GetRemoteClusters(context.Background(), tc.Page, tc.PerPage, tc.Filter)
|
||||
checkHTTPStatus(t, resp, tc.ExpectedStatusCode)
|
||||
if tc.ExpectedError {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.Len(t, rcs, len(tc.ExpectedNames))
|
||||
names := []string{}
|
||||
for _, rc := range rcs {
|
||||
names = append(names, rc.Name)
|
||||
}
|
||||
require.ElementsMatch(t, tc.ExpectedNames, names)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateRemoteCluster(t *testing.T) {
|
||||
rcWithTeamAndPassword := &model.RemoteClusterWithPassword{
|
||||
RemoteCluster: &model.RemoteCluster{
|
||||
Name: "remotecluster",
|
||||
SiteURL: "http://example.com",
|
||||
Token: model.NewId(),
|
||||
},
|
||||
Password: "mysupersecret",
|
||||
}
|
||||
|
||||
t.Run("Should not work if the remote cluster service is not enabled", func(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
rcWithInvite, resp, err := th.SystemAdminClient.CreateRemoteCluster(context.Background(), rcWithTeamAndPassword)
|
||||
CheckNotImplementedStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
require.Empty(t, rcWithInvite)
|
||||
})
|
||||
|
||||
th := setupForSharedChannels(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
t.Run("Should not work if the user doesn't have the right permissions", func(t *testing.T) {
|
||||
rcWithInvite, resp, err := th.Client.CreateRemoteCluster(context.Background(), rcWithTeamAndPassword)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
require.Empty(t, rcWithInvite)
|
||||
})
|
||||
|
||||
t.Run("Should not work if the siteURL is not set in the configuration", func(t *testing.T) {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.SiteURL = "" })
|
||||
rcWithInvite, resp, err := th.SystemAdminClient.CreateRemoteCluster(context.Background(), rcWithTeamAndPassword)
|
||||
CheckUnprocessableEntityStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
require.Empty(t, rcWithInvite)
|
||||
})
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.SiteURL = "http://localhost:8065" })
|
||||
|
||||
t.Run("Should enforce the presence of the password", func(t *testing.T) {
|
||||
// clean the password and check the response
|
||||
rcWithTeamAndPassword.Password = ""
|
||||
|
||||
rcWithInvite, resp, err := th.SystemAdminClient.CreateRemoteCluster(context.Background(), rcWithTeamAndPassword)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
require.Empty(t, rcWithInvite)
|
||||
|
||||
// reset password for the next tests
|
||||
rcWithTeamAndPassword.Password = "mysupersecret"
|
||||
})
|
||||
|
||||
t.Run("Should return a sanitized remote cluster and its invite", func(t *testing.T) {
|
||||
rcWithInvite, resp, err := th.SystemAdminClient.CreateRemoteCluster(context.Background(), rcWithTeamAndPassword)
|
||||
CheckCreatedStatus(t, resp)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, rcWithTeamAndPassword.Name, rcWithInvite.RemoteCluster.Name)
|
||||
require.NotZero(t, rcWithInvite.Invite)
|
||||
require.Zero(t, rcWithInvite.RemoteCluster.Token)
|
||||
require.Zero(t, rcWithInvite.RemoteCluster.RemoteToken)
|
||||
|
||||
rc, appErr := th.App.GetRemoteCluster(rcWithInvite.RemoteCluster.RemoteId)
|
||||
require.Nil(t, appErr)
|
||||
require.Equal(t, rcWithTeamAndPassword.Name, rc.Name)
|
||||
|
||||
rci, appErr := th.App.DecryptRemoteClusterInvite(rcWithInvite.Invite, rcWithTeamAndPassword.Password)
|
||||
require.Nil(t, appErr)
|
||||
require.Equal(t, rc.RemoteId, rci.RemoteId)
|
||||
require.Equal(t, rc.RemoteToken, rci.Token)
|
||||
require.Equal(t, th.App.GetSiteURL(), rci.SiteURL)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRemoteClusterAcceptinvite(t *testing.T) {
|
||||
rcAcceptInvite := &model.RemoteClusterAcceptInvite{
|
||||
Name: "remotecluster",
|
||||
Invite: "myinvitecode",
|
||||
Password: "mysupersecret",
|
||||
}
|
||||
|
||||
t.Run("Should not work if the remote cluster service is not enabled", func(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
rc, resp, err := th.SystemAdminClient.RemoteClusterAcceptInvite(context.Background(), rcAcceptInvite)
|
||||
CheckNotImplementedStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
require.Empty(t, rc)
|
||||
})
|
||||
|
||||
th := setupForSharedChannels(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
remoteId := model.NewId()
|
||||
invite := &model.RemoteClusterInvite{
|
||||
RemoteId: remoteId,
|
||||
SiteURL: "http://localhost:8065",
|
||||
Token: "token",
|
||||
}
|
||||
password := "mysupersecret"
|
||||
encrypted, err := invite.Encrypt(password)
|
||||
require.NoError(t, err)
|
||||
encoded := base64.URLEncoding.EncodeToString(encrypted)
|
||||
rcAcceptInvite.Invite = encoded
|
||||
|
||||
t.Run("Should not work if the siteURL is not set in the configuration", func(t *testing.T) {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.SiteURL = "" })
|
||||
rc, resp, err := th.SystemAdminClient.RemoteClusterAcceptInvite(context.Background(), rcAcceptInvite)
|
||||
CheckUnprocessableEntityStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
require.Empty(t, rc)
|
||||
})
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.SiteURL = "http://localhost:8065" })
|
||||
|
||||
t.Run("should fail if the parameters are not valid", func(t *testing.T) {
|
||||
rcAcceptInvite.Name = ""
|
||||
defer func() { rcAcceptInvite.Name = "remotecluster" }()
|
||||
|
||||
rc, resp, err := th.SystemAdminClient.RemoteClusterAcceptInvite(context.Background(), rcAcceptInvite)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
require.Empty(t, rc)
|
||||
})
|
||||
|
||||
t.Run("should fail with the correct status code if the invite returns an app error", func(t *testing.T) {
|
||||
rcAcceptInvite.Invite = "malformedinvite"
|
||||
// reset the invite after
|
||||
defer func() { rcAcceptInvite.Invite = encoded }()
|
||||
|
||||
rc, resp, err := th.SystemAdminClient.RemoteClusterAcceptInvite(context.Background(), rcAcceptInvite)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
require.Empty(t, rc)
|
||||
})
|
||||
|
||||
t.Run("should not work if the user doesn't have the right permissions", func(t *testing.T) {
|
||||
rc, resp, err := th.Client.RemoteClusterAcceptInvite(context.Background(), rcAcceptInvite)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
require.Empty(t, rc)
|
||||
})
|
||||
|
||||
t.Run("should return a sanitized remote cluster if the action succeeds", func(t *testing.T) {
|
||||
t.Skip("Requires server2server communication: ToBeImplemented")
|
||||
})
|
||||
}
|
||||
|
||||
func TestGenerateRemoteClusterInvite(t *testing.T) {
|
||||
password := "mysupersecret"
|
||||
|
||||
newRC := &model.RemoteCluster{
|
||||
Name: "remotecluster",
|
||||
SiteURL: "http://example.com",
|
||||
Token: model.NewId(),
|
||||
}
|
||||
|
||||
t.Run("Should not work if the remote cluster service is not enabled", func(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
newRC.CreatorId = th.SystemAdminUser.Id
|
||||
|
||||
rc, appErr := th.App.AddRemoteCluster(newRC)
|
||||
require.Nil(t, appErr)
|
||||
require.NotZero(t, rc.RemoteId)
|
||||
|
||||
inviteCode, resp, err := th.SystemAdminClient.GenerateRemoteClusterInvite(context.Background(), rc.RemoteId, password)
|
||||
CheckNotImplementedStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
require.Zero(t, inviteCode)
|
||||
})
|
||||
|
||||
th := setupForSharedChannels(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
newRC.CreatorId = th.SystemAdminUser.Id
|
||||
|
||||
rc, appErr := th.App.AddRemoteCluster(newRC)
|
||||
require.Nil(t, appErr)
|
||||
require.NotZero(t, rc.RemoteId)
|
||||
|
||||
t.Run("Should not work if the siteURL is not set in the configuration", func(t *testing.T) {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.SiteURL = "" })
|
||||
inviteCode, resp, err := th.SystemAdminClient.GenerateRemoteClusterInvite(context.Background(), rc.RemoteId, password)
|
||||
CheckUnprocessableEntityStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
require.Empty(t, inviteCode)
|
||||
})
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.SiteURL = "http://localhost:8065" })
|
||||
|
||||
t.Run("Should not work if the user doesn't have the right permissions", func(t *testing.T) {
|
||||
inviteCode, resp, err := th.Client.GenerateRemoteClusterInvite(context.Background(), rc.RemoteId, password)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
require.Empty(t, inviteCode)
|
||||
})
|
||||
|
||||
t.Run("should not work if the remote cluster doesn't exist", func(t *testing.T) {
|
||||
inviteCode, resp, err := th.SystemAdminClient.GenerateRemoteClusterInvite(context.Background(), model.NewId(), password)
|
||||
CheckNotFoundStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
require.Empty(t, inviteCode)
|
||||
})
|
||||
|
||||
t.Run("should not work if the password has been provided", func(t *testing.T) {
|
||||
inviteCode, resp, err := th.SystemAdminClient.GenerateRemoteClusterInvite(context.Background(), rc.RemoteId, "")
|
||||
CheckBadRequestStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
require.Empty(t, inviteCode)
|
||||
})
|
||||
|
||||
t.Run("should generate a valid invite code", func(t *testing.T) {
|
||||
inviteCode, resp, err := th.SystemAdminClient.GenerateRemoteClusterInvite(context.Background(), rc.RemoteId, password)
|
||||
CheckCreatedStatus(t, resp)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, inviteCode)
|
||||
|
||||
invite, appErr := th.App.DecryptRemoteClusterInvite(inviteCode, password)
|
||||
require.Nil(t, appErr)
|
||||
require.Equal(t, rc.RemoteId, invite.RemoteId)
|
||||
require.Equal(t, rc.Token, invite.Token)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetRemoteCluster(t *testing.T) {
|
||||
newRC := &model.RemoteCluster{
|
||||
Name: "remotecluster",
|
||||
SiteURL: "http://example.com",
|
||||
Token: model.NewId(),
|
||||
}
|
||||
|
||||
t.Run("Should not work if the remote cluster service is not enabled", func(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
newRC.CreatorId = th.SystemAdminUser.Id
|
||||
|
||||
rc, appErr := th.App.AddRemoteCluster(newRC)
|
||||
require.Nil(t, appErr)
|
||||
require.NotZero(t, rc.RemoteId)
|
||||
require.NotZero(t, rc.Token)
|
||||
|
||||
fetchedRC, resp, err := th.SystemAdminClient.GetRemoteCluster(context.Background(), rc.RemoteId)
|
||||
CheckNotImplementedStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
require.Empty(t, fetchedRC)
|
||||
})
|
||||
|
||||
th := setupForSharedChannels(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
newRC.CreatorId = th.SystemAdminUser.Id
|
||||
|
||||
rc, appErr := th.App.AddRemoteCluster(newRC)
|
||||
require.Nil(t, appErr)
|
||||
require.NotZero(t, rc.RemoteId)
|
||||
|
||||
t.Run("Should not work if the user doesn't have the right permissions", func(t *testing.T) {
|
||||
fetchedRC, resp, err := th.Client.GetRemoteCluster(context.Background(), rc.RemoteId)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
require.Empty(t, fetchedRC)
|
||||
})
|
||||
|
||||
t.Run("should return not found if the id doesn't exist", func(t *testing.T) {
|
||||
fetchedRC, resp, err := th.SystemAdminClient.GetRemoteCluster(context.Background(), model.NewId())
|
||||
CheckNotFoundStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
require.Empty(t, fetchedRC)
|
||||
})
|
||||
|
||||
t.Run("should return a sanitized remote cluster", func(t *testing.T) {
|
||||
fetchedRC, resp, err := th.SystemAdminClient.GetRemoteCluster(context.Background(), rc.RemoteId)
|
||||
CheckOKStatus(t, resp)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, rc.RemoteId, fetchedRC.RemoteId)
|
||||
require.Empty(t, fetchedRC.Token)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPatchRemoteCluster(t *testing.T) {
|
||||
newRC := &model.RemoteCluster{
|
||||
Name: "remotecluster",
|
||||
DisplayName: "initialvalue",
|
||||
SiteURL: "http://example.com",
|
||||
Token: model.NewId(),
|
||||
}
|
||||
|
||||
rcp := &model.RemoteClusterPatch{DisplayName: model.NewString("different value")}
|
||||
|
||||
t.Run("Should not work if the remote cluster service is not enabled", func(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
newRC.CreatorId = th.SystemAdminUser.Id
|
||||
|
||||
rc, appErr := th.App.AddRemoteCluster(newRC)
|
||||
require.Nil(t, appErr)
|
||||
require.NotZero(t, rc.RemoteId)
|
||||
|
||||
patchedRC, resp, err := th.SystemAdminClient.PatchRemoteCluster(context.Background(), rc.RemoteId, rcp)
|
||||
CheckNotImplementedStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
require.Empty(t, patchedRC)
|
||||
})
|
||||
|
||||
th := setupForSharedChannels(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
newRC.CreatorId = th.SystemAdminUser.Id
|
||||
|
||||
rc, appErr := th.App.AddRemoteCluster(newRC)
|
||||
require.Nil(t, appErr)
|
||||
require.NotZero(t, rc.RemoteId)
|
||||
|
||||
t.Run("Should not work if the user doesn't have the right permissions", func(t *testing.T) {
|
||||
patchedRC, resp, err := th.Client.PatchRemoteCluster(context.Background(), rc.RemoteId, rcp)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
require.Empty(t, patchedRC)
|
||||
})
|
||||
|
||||
t.Run("should not work if the remote cluster is nonexistent", func(t *testing.T) {
|
||||
patchedRC, resp, err := th.SystemAdminClient.PatchRemoteCluster(context.Background(), model.NewId(), rcp)
|
||||
CheckNotFoundStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
require.Empty(t, patchedRC)
|
||||
})
|
||||
|
||||
t.Run("should correctly patch the remote cluster", func(t *testing.T) {
|
||||
rcp := &model.RemoteClusterPatch{DisplayName: model.NewString("patched!")}
|
||||
|
||||
patchedRC, resp, err := th.SystemAdminClient.PatchRemoteCluster(context.Background(), rc.RemoteId, rcp)
|
||||
CheckOKStatus(t, resp)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "patched!", patchedRC.DisplayName)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeleteRemoteCluster(t *testing.T) {
|
||||
newRC := &model.RemoteCluster{
|
||||
Name: "remotecluster",
|
||||
DisplayName: "initialvalue",
|
||||
SiteURL: "http://example.com",
|
||||
Token: model.NewId(),
|
||||
}
|
||||
|
||||
t.Run("Should not work if the remote cluster service is not enabled", func(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
newRC.CreatorId = th.SystemAdminUser.Id
|
||||
|
||||
rc, appErr := th.App.AddRemoteCluster(newRC)
|
||||
require.Nil(t, appErr)
|
||||
require.NotZero(t, rc.RemoteId)
|
||||
|
||||
resp, err := th.SystemAdminClient.DeleteRemoteCluster(context.Background(), rc.RemoteId)
|
||||
CheckNotImplementedStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
th := setupForSharedChannels(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
newRC.CreatorId = th.SystemAdminUser.Id
|
||||
|
||||
rc, appErr := th.App.AddRemoteCluster(newRC)
|
||||
require.Nil(t, appErr)
|
||||
require.NotZero(t, rc.RemoteId)
|
||||
|
||||
t.Run("Should not work if the user doesn't have the right permissions", func(t *testing.T) {
|
||||
resp, err := th.Client.DeleteRemoteCluster(context.Background(), rc.RemoteId)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("should not work if the remote cluster is nonexistent", func(t *testing.T) {
|
||||
resp, err := th.SystemAdminClient.DeleteRemoteCluster(context.Background(), model.NewId())
|
||||
CheckNotFoundStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("should correctly delete the remote cluster", func(t *testing.T) {
|
||||
resp, err := th.SystemAdminClient.DeleteRemoteCluster(context.Background(), rc.RemoteId)
|
||||
CheckNoContentStatus(t, resp)
|
||||
require.NoError(t, err)
|
||||
|
||||
deletedRC, err := th.App.GetRemoteCluster(rc.RemoteId)
|
||||
require.ErrorIs(t, err, sql.ErrNoRows)
|
||||
require.Empty(t, deletedRC)
|
||||
})
|
||||
|
||||
t.Run("should return not found if the remote cluster is already deleted", func(t *testing.T) {
|
||||
resp, err := th.SystemAdminClient.DeleteRemoteCluster(context.Background(), rc.RemoteId)
|
||||
CheckNotFoundStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
@@ -533,6 +533,7 @@ type AppIface interface {
|
||||
CreatePost(c request.CTX, post *model.Post, channel *model.Channel, triggerWebhooks, setOnline bool) (savedPost *model.Post, err *model.AppError)
|
||||
CreatePostAsUser(c request.CTX, post *model.Post, currentSessionId string, setOnline bool) (*model.Post, *model.AppError)
|
||||
CreatePostMissingChannel(c request.CTX, post *model.Post, triggerWebhooks bool, setOnline bool) (*model.Post, *model.AppError)
|
||||
CreateRemoteClusterInvite(remoteId, siteURL, token, password string) (string, *model.AppError)
|
||||
CreateRetentionPolicy(policy *model.RetentionPolicyWithTeamAndChannelIDs) (*model.RetentionPolicyWithTeamAndChannelCounts, *model.AppError)
|
||||
CreateRole(role *model.Role) (*model.Role, *model.AppError)
|
||||
CreateSamlRelayToken(extra string) (*model.Token, *model.AppError)
|
||||
@@ -555,6 +556,7 @@ type AppIface interface {
|
||||
DeactivateGuests(c request.CTX) *model.AppError
|
||||
DeactivateMfa(userID string) *model.AppError
|
||||
DeauthorizeOAuthAppForUser(c request.CTX, userID, appID string) *model.AppError
|
||||
DecryptRemoteClusterInvite(inviteCode, password string) (*model.RemoteClusterInvite, *model.AppError)
|
||||
DeleteAcknowledgementForPost(c request.CTX, postID, userID string) *model.AppError
|
||||
DeleteAllExpiredPluginKeys() *model.AppError
|
||||
DeleteAllKeysForPlugin(pluginID string) *model.AppError
|
||||
@@ -626,7 +628,7 @@ type AppIface interface {
|
||||
GetAllChannelsCount(c request.CTX, opts model.ChannelSearchOpts) (int64, *model.AppError)
|
||||
GetAllPrivateTeams() ([]*model.Team, *model.AppError)
|
||||
GetAllPublicTeams() ([]*model.Team, *model.AppError)
|
||||
GetAllRemoteClusters(filter model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, *model.AppError)
|
||||
GetAllRemoteClusters(page, perPage int, filter model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, *model.AppError)
|
||||
GetAllRoles() ([]*model.Role, *model.AppError)
|
||||
GetAllTeams() ([]*model.Team, *model.AppError)
|
||||
GetAllTeamsPage(offset int, limit int, opts *model.TeamSearch) ([]*model.Team, *model.AppError)
|
||||
@@ -973,6 +975,7 @@ type AppIface interface {
|
||||
PatchChannel(c request.CTX, channel *model.Channel, patch *model.ChannelPatch, userID string) (*model.Channel, *model.AppError)
|
||||
PatchChannelMembersNotifyProps(c request.CTX, members []*model.ChannelMemberIdentifier, notifyProps map[string]string) ([]*model.ChannelMember, *model.AppError)
|
||||
PatchPost(c request.CTX, postID string, patch *model.PostPatch) (*model.Post, *model.AppError)
|
||||
PatchRemoteCluster(rcId string, patch *model.RemoteClusterPatch) (*model.RemoteCluster, *model.AppError)
|
||||
PatchRetentionPolicy(patch *model.RetentionPolicyWithTeamAndChannelIDs) (*model.RetentionPolicyWithTeamAndChannelCounts, *model.AppError)
|
||||
PatchRole(role *model.Role, patch *model.RolePatch) (*model.Role, *model.AppError)
|
||||
PatchScheme(scheme *model.Scheme, patch *model.SchemePatch) (*model.Scheme, *model.AppError)
|
||||
|
||||
@@ -2544,6 +2544,28 @@ func (a *OpenTracingAppLayer) CreatePostMissingChannel(c request.CTX, post *mode
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) CreateRemoteClusterInvite(remoteId string, siteURL string, token string, password string) (string, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.CreateRemoteClusterInvite")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store().SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store().SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1 := a.app.CreateRemoteClusterInvite(remoteId, siteURL, token, password)
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) CreateRetentionPolicy(policy *model.RetentionPolicyWithTeamAndChannelIDs) (*model.RetentionPolicyWithTeamAndChannelCounts, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.CreateRetentionPolicy")
|
||||
@@ -3050,6 +3072,28 @@ func (a *OpenTracingAppLayer) DeauthorizeOAuthAppForUser(c request.CTX, userID s
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) DecryptRemoteClusterInvite(inviteCode string, password string) (*model.RemoteClusterInvite, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.DecryptRemoteClusterInvite")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store().SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store().SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1 := a.app.DecryptRemoteClusterInvite(inviteCode, password)
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) DefaultChannelNames(c request.CTX) []string {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.DefaultChannelNames")
|
||||
@@ -5023,7 +5067,7 @@ func (a *OpenTracingAppLayer) GetAllPublicTeams() ([]*model.Team, *model.AppErro
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) GetAllRemoteClusters(filter model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, *model.AppError) {
|
||||
func (a *OpenTracingAppLayer) GetAllRemoteClusters(page int, perPage int, filter model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetAllRemoteClusters")
|
||||
|
||||
@@ -5035,7 +5079,7 @@ func (a *OpenTracingAppLayer) GetAllRemoteClusters(filter model.RemoteClusterQue
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1 := a.app.GetAllRemoteClusters(filter)
|
||||
resultVar0, resultVar1 := a.app.GetAllRemoteClusters(page, perPage, filter)
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
@@ -13376,6 +13420,28 @@ func (a *OpenTracingAppLayer) PatchPost(c request.CTX, postID string, patch *mod
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) PatchRemoteCluster(rcId string, patch *model.RemoteClusterPatch) (*model.RemoteCluster, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.PatchRemoteCluster")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store().SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store().SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1 := a.app.PatchRemoteCluster(rcId, patch)
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) PatchRetentionPolicy(patch *model.RetentionPolicyWithTeamAndChannelIDs) (*model.RetentionPolicyWithTeamAndChannelCounts, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.PatchRetentionPolicy")
|
||||
|
||||
@@ -88,7 +88,7 @@ func handleContentSync(ps *PlatformService, syncService SharedChannelServiceIFac
|
||||
OnlyConfirmed: true,
|
||||
RequireOptions: model.BitflagOptionAutoShareDMs,
|
||||
}
|
||||
remotes, err := ps.Store.RemoteCluster().GetAll(filter) // empty list returned if none found, no error
|
||||
remotes, err := ps.Store.RemoteCluster().GetAll(0, 999999, filter) // empty list returned if none found, no error
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot fetch remote clusters: %w", err)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ package app
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@@ -100,6 +101,22 @@ func (a *App) AddRemoteCluster(rc *model.RemoteCluster) (*model.RemoteCluster, *
|
||||
return rc, nil
|
||||
}
|
||||
|
||||
func (a *App) PatchRemoteCluster(rcId string, patch *model.RemoteClusterPatch) (*model.RemoteCluster, *model.AppError) {
|
||||
rc, err := a.GetRemoteCluster(rcId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rc.Patch(patch)
|
||||
|
||||
updatedRC, err := a.UpdateRemoteCluster(rc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return updatedRC, nil
|
||||
}
|
||||
|
||||
func (a *App) UpdateRemoteCluster(rc *model.RemoteCluster) (*model.RemoteCluster, *model.AppError) {
|
||||
rc, err := a.Srv().Store().RemoteCluster().Update(rc)
|
||||
if err != nil {
|
||||
@@ -123,13 +140,18 @@ func (a *App) DeleteRemoteCluster(remoteClusterId string) (bool, *model.AppError
|
||||
func (a *App) GetRemoteCluster(remoteClusterId string) (*model.RemoteCluster, *model.AppError) {
|
||||
rc, err := a.Srv().Store().RemoteCluster().Get(remoteClusterId)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("GetRemoteCluster", "api.remote_cluster.get.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
switch {
|
||||
case errors.Is(err, sql.ErrNoRows):
|
||||
return nil, model.NewAppError("GetRemoteCluster", "api.remote_cluster.get.not_found", nil, "", http.StatusNotFound).Wrap(err)
|
||||
default:
|
||||
return nil, model.NewAppError("GetRemoteCluster", "api.remote_cluster.get.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
}
|
||||
return rc, nil
|
||||
}
|
||||
|
||||
func (a *App) GetAllRemoteClusters(filter model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, *model.AppError) {
|
||||
list, err := a.Srv().Store().RemoteCluster().GetAll(filter)
|
||||
func (a *App) GetAllRemoteClusters(page, perPage int, filter model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, *model.AppError) {
|
||||
list, err := a.Srv().Store().RemoteCluster().GetAll(page*perPage, perPage, filter)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("GetAllRemoteClusters", "api.remote_cluster.get.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
@@ -159,3 +181,32 @@ func (a *App) GetRemoteClusterService() (remotecluster.RemoteClusterServiceIFace
|
||||
}
|
||||
return service, nil
|
||||
}
|
||||
|
||||
func (a *App) CreateRemoteClusterInvite(remoteId, siteURL, token, password string) (string, *model.AppError) {
|
||||
invite := &model.RemoteClusterInvite{
|
||||
RemoteId: remoteId,
|
||||
SiteURL: siteURL,
|
||||
Token: token,
|
||||
}
|
||||
|
||||
encrypted, err := invite.Encrypt(password)
|
||||
if err != nil {
|
||||
return "", model.NewAppError("CreateRemoteClusterInvite", "api.remote_cluster.encrypt_invite_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
return base64.URLEncoding.EncodeToString(encrypted), nil
|
||||
}
|
||||
|
||||
func (a *App) DecryptRemoteClusterInvite(inviteCode, password string) (*model.RemoteClusterInvite, *model.AppError) {
|
||||
decoded, err := base64.URLEncoding.DecodeString(inviteCode)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("DecryptRemoteClusterInvite", "api.remote_cluster.base64_decode_error", nil, "", http.StatusBadRequest).Wrap(err)
|
||||
}
|
||||
|
||||
invite := &model.RemoteClusterInvite{}
|
||||
if dErr := invite.Decrypt(decoded, password); dErr != nil {
|
||||
return nil, model.NewAppError("DecryptRemoteClusterInvite", "api.remote_cluster.invite_decrypt_error", nil, "", http.StatusBadRequest).Wrap(dErr)
|
||||
}
|
||||
|
||||
return invite, nil
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
package slashcommands
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
@@ -144,19 +143,13 @@ func (rp *RemoteProvider) doCreate(a *app.App, args *model.CommandArgs, margs ma
|
||||
}
|
||||
|
||||
// Display the encrypted invitation
|
||||
invite := &model.RemoteClusterInvite{
|
||||
RemoteId: rcSaved.RemoteId,
|
||||
SiteURL: url,
|
||||
Token: rcSaved.Token,
|
||||
}
|
||||
encrypted, err := invite.Encrypt(password)
|
||||
inviteCode, err := a.CreateRemoteClusterInvite(rcSaved.RemoteId, url, rcSaved.Token, password)
|
||||
if err != nil {
|
||||
return responsef(args.T("api.command_remote.encrypt_invitation.error", map[string]any{"Error": err.Error()}))
|
||||
}
|
||||
encoded := base64.URLEncoding.EncodeToString(encrypted)
|
||||
|
||||
return responsef("##### " + args.T("api.command_remote.invitation_created") + "\n" +
|
||||
args.T("api.command_remote.invite_summary", map[string]any{"Command": "/secure-connection accept", "Invitation": encoded, "SiteURL": invite.SiteURL}))
|
||||
args.T("api.command_remote.invite_summary", map[string]any{"Command": "/secure-connection accept", "Invitation": inviteCode, "SiteURL": url}))
|
||||
}
|
||||
|
||||
// doAccept accepts an invitation generated by a remote site.
|
||||
@@ -182,14 +175,9 @@ func (rp *RemoteProvider) doAccept(a *app.App, args *model.CommandArgs, margs ma
|
||||
}
|
||||
|
||||
// invite is encoded as base64 and encrypted
|
||||
decoded, err := base64.URLEncoding.DecodeString(blob)
|
||||
if err != nil {
|
||||
return responsef(args.T("api.command_remote.decode_invitation.error", map[string]any{"Error": err.Error()}))
|
||||
}
|
||||
invite := &model.RemoteClusterInvite{}
|
||||
err = invite.Decrypt(decoded, password)
|
||||
if err != nil {
|
||||
return responsef(args.T("api.command_remote.incorrect_password.error", map[string]any{"Error": err.Error()}))
|
||||
invite, dErr := a.DecryptRemoteClusterInvite(blob, password)
|
||||
if dErr != nil {
|
||||
return responsef(args.T("api.command_remote.decode_invitation.error", map[string]any{"Error": dErr.Error()}))
|
||||
}
|
||||
|
||||
rcs, _ := a.GetRemoteClusterService()
|
||||
@@ -202,7 +190,7 @@ func (rp *RemoteProvider) doAccept(a *app.App, args *model.CommandArgs, margs ma
|
||||
return responsef(args.T("api.command_remote.site_url_not_set"))
|
||||
}
|
||||
|
||||
rc, err := rcs.AcceptInvitation(invite, name, displayname, args.UserId, args.TeamId, url)
|
||||
rc, err := rcs.AcceptInvitation(invite, name, displayname, args.UserId, url)
|
||||
if err != nil {
|
||||
return responsef(args.T("api.command_remote.accept_invitation.error", map[string]any{"Error": err.Error()}))
|
||||
}
|
||||
@@ -231,7 +219,7 @@ func (rp *RemoteProvider) doRemove(a *app.App, args *model.CommandArgs, margs ma
|
||||
|
||||
// doStatus displays connection status for all remote clusters.
|
||||
func (rp *RemoteProvider) doStatus(a *app.App, args *model.CommandArgs, _ map[string]string) *model.CommandResponse {
|
||||
list, err := a.GetAllRemoteClusters(model.RemoteClusterQueryFilter{})
|
||||
list, err := a.GetAllRemoteClusters(0, 999999, model.RemoteClusterQueryFilter{})
|
||||
if err != nil {
|
||||
responsef(args.T("api.command_remote.fetch_status.error", map[string]any{"Error": err.Error()}))
|
||||
}
|
||||
@@ -263,7 +251,7 @@ func getRemoteClusterAutocompleteListItems(a *app.App, includeOffline bool) ([]m
|
||||
filter := model.RemoteClusterQueryFilter{
|
||||
ExcludeOffline: !includeOffline,
|
||||
}
|
||||
clusters, err := a.GetAllRemoteClusters(filter)
|
||||
clusters, err := a.GetAllRemoteClusters(0, 999999, filter)
|
||||
if err != nil || len(clusters) == 0 {
|
||||
return []model.AutocompleteListItem{}, nil
|
||||
}
|
||||
@@ -284,7 +272,7 @@ func getRemoteClusterAutocompleteListItemsNotInChannel(a *app.App, channelID str
|
||||
ExcludeOffline: !includeOffline,
|
||||
NotInChannel: channelID,
|
||||
}
|
||||
all, err := a.GetAllRemoteClusters(filter)
|
||||
all, err := a.GetAllRemoteClusters(0, 999999, filter)
|
||||
if err != nil || len(all) == 0 {
|
||||
return []model.AutocompleteListItem{}, nil
|
||||
}
|
||||
|
||||
@@ -7828,7 +7828,7 @@ func (s *OpenTracingLayerRemoteClusterStore) Get(remoteClusterId string) (*model
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerRemoteClusterStore) GetAll(filter model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, error) {
|
||||
func (s *OpenTracingLayerRemoteClusterStore) GetAll(offset int, limit int, filter model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "RemoteClusterStore.GetAll")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
@@ -7837,7 +7837,7 @@ func (s *OpenTracingLayerRemoteClusterStore) GetAll(filter model.RemoteClusterQu
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
result, err := s.RemoteClusterStore.GetAll(filter)
|
||||
result, err := s.RemoteClusterStore.GetAll(offset, limit, filter)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
|
||||
@@ -8909,11 +8909,11 @@ func (s *RetryLayerRemoteClusterStore) Get(remoteClusterId string) (*model.Remot
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerRemoteClusterStore) GetAll(filter model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, error) {
|
||||
func (s *RetryLayerRemoteClusterStore) GetAll(offset int, limit int, filter model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.RemoteClusterStore.GetAll(filter)
|
||||
result, err := s.RemoteClusterStore.GetAll(offset, limit, filter)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -162,7 +162,14 @@ func (s sqlRemoteClusterStore) GetByPluginID(pluginID string) (*model.RemoteClus
|
||||
return &rc, nil
|
||||
}
|
||||
|
||||
func (s sqlRemoteClusterStore) GetAll(filter model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, error) {
|
||||
func (s sqlRemoteClusterStore) GetAll(offset, limit int, filter model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, error) {
|
||||
if offset < 0 {
|
||||
return nil, errors.New("offset must be a positive integer")
|
||||
}
|
||||
if limit < 0 {
|
||||
return nil, errors.New("limit must be a positive integer")
|
||||
}
|
||||
|
||||
query := s.getQueryBuilder().
|
||||
Select(remoteClusterFields("rc")...).
|
||||
From("RemoteClusters rc")
|
||||
@@ -195,6 +202,10 @@ func (s sqlRemoteClusterStore) GetAll(filter model.RemoteClusterQueryFilter) ([]
|
||||
query = query.Where(sq.NotEq{"rc.PluginID": ""})
|
||||
}
|
||||
|
||||
if filter.ExcludePlugins {
|
||||
query = query.Where(sq.Eq{"rc.PluginID": ""})
|
||||
}
|
||||
|
||||
if filter.RequireOptions != 0 {
|
||||
query = query.Where(sq.NotEq{fmt.Sprintf("(rc.Options & %d)", filter.RequireOptions): 0})
|
||||
}
|
||||
@@ -208,6 +219,8 @@ func (s sqlRemoteClusterStore) GetAll(filter model.RemoteClusterQueryFilter) ([]
|
||||
query = query.Where(sq.Or{sq.Like{"rc.Topics": queryTopic}, sq.Eq{"rc.Topics": "*"}})
|
||||
}
|
||||
|
||||
query = query.Offset(uint64(offset)).Limit(uint64(limit))
|
||||
|
||||
queryString, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "remote_cluster_getall_tosql")
|
||||
|
||||
@@ -533,7 +533,7 @@ type RemoteClusterStore interface {
|
||||
Delete(remoteClusterId string) (bool, error)
|
||||
Get(remoteClusterId string) (*model.RemoteCluster, error)
|
||||
GetByPluginID(pluginID string) (*model.RemoteCluster, error)
|
||||
GetAll(filter model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, error)
|
||||
GetAll(offset, limit int, filter model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, error)
|
||||
UpdateTopics(remoteClusterId string, topics string) (*model.RemoteCluster, error)
|
||||
SetLastPingAt(remoteClusterId string) error
|
||||
}
|
||||
|
||||
@@ -72,9 +72,9 @@ func (_m *RemoteClusterStore) Get(remoteClusterId string) (*model.RemoteCluster,
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetAll provides a mock function with given fields: filter
|
||||
func (_m *RemoteClusterStore) GetAll(filter model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, error) {
|
||||
ret := _m.Called(filter)
|
||||
// GetAll provides a mock function with given fields: offset, limit, filter
|
||||
func (_m *RemoteClusterStore) GetAll(offset int, limit int, filter model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, error) {
|
||||
ret := _m.Called(offset, limit, filter)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetAll")
|
||||
@@ -82,19 +82,19 @@ func (_m *RemoteClusterStore) GetAll(filter model.RemoteClusterQueryFilter) ([]*
|
||||
|
||||
var r0 []*model.RemoteCluster
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, error)); ok {
|
||||
return rf(filter)
|
||||
if rf, ok := ret.Get(0).(func(int, int, model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, error)); ok {
|
||||
return rf(offset, limit, filter)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(model.RemoteClusterQueryFilter) []*model.RemoteCluster); ok {
|
||||
r0 = rf(filter)
|
||||
if rf, ok := ret.Get(0).(func(int, int, model.RemoteClusterQueryFilter) []*model.RemoteCluster); ok {
|
||||
r0 = rf(offset, limit, filter)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.RemoteCluster)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(model.RemoteClusterQueryFilter) error); ok {
|
||||
r1 = rf(filter)
|
||||
if rf, ok := ret.Get(1).(func(int, int, model.RemoteClusterQueryFilter) error); ok {
|
||||
r1 = rf(offset, limit, filter)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
@@ -238,6 +238,7 @@ func testRemoteClusterGetAll(t *testing.T, _ request.CTX, ss store.Store) {
|
||||
idsOnline := make([]string, 0)
|
||||
idsShareTopic := make([]string, 0)
|
||||
idsPlugin := make([]string, 0)
|
||||
idsNotPlugin := make([]string, 0)
|
||||
idsConfirmed := make([]string, 0)
|
||||
|
||||
for _, item := range data {
|
||||
@@ -253,6 +254,8 @@ func testRemoteClusterGetAll(t *testing.T, _ request.CTX, ss store.Store) {
|
||||
}
|
||||
if item.PluginID != "" {
|
||||
idsPlugin = append(idsPlugin, saved.RemoteId)
|
||||
} else {
|
||||
idsNotPlugin = append(idsNotPlugin, saved.RemoteId)
|
||||
}
|
||||
if item.SiteURL != "" {
|
||||
idsConfirmed = append(idsConfirmed, saved.RemoteId)
|
||||
@@ -261,7 +264,7 @@ func testRemoteClusterGetAll(t *testing.T, _ request.CTX, ss store.Store) {
|
||||
|
||||
t.Run("GetAll", func(t *testing.T) {
|
||||
filter := model.RemoteClusterQueryFilter{}
|
||||
remotes, err := ss.RemoteCluster().GetAll(filter)
|
||||
remotes, err := ss.RemoteCluster().GetAll(0, 999999, filter)
|
||||
require.NoError(t, err)
|
||||
// make sure all the test data remotes were returned.
|
||||
ids := getIds(remotes)
|
||||
@@ -272,7 +275,7 @@ func testRemoteClusterGetAll(t *testing.T, _ request.CTX, ss store.Store) {
|
||||
filter := model.RemoteClusterQueryFilter{
|
||||
ExcludeOffline: true,
|
||||
}
|
||||
remotes, err := ss.RemoteCluster().GetAll(filter)
|
||||
remotes, err := ss.RemoteCluster().GetAll(0, 999999, filter)
|
||||
require.NoError(t, err)
|
||||
// make sure all the online remotes were returned.
|
||||
ids := getIds(remotes)
|
||||
@@ -283,7 +286,7 @@ func testRemoteClusterGetAll(t *testing.T, _ request.CTX, ss store.Store) {
|
||||
filter := model.RemoteClusterQueryFilter{
|
||||
Topic: "shared",
|
||||
}
|
||||
remotes, err := ss.RemoteCluster().GetAll(filter)
|
||||
remotes, err := ss.RemoteCluster().GetAll(0, 999999, filter)
|
||||
require.NoError(t, err)
|
||||
// make sure only correct topic returned
|
||||
ids := getIds(remotes)
|
||||
@@ -295,7 +298,7 @@ func testRemoteClusterGetAll(t *testing.T, _ request.CTX, ss store.Store) {
|
||||
ExcludeOffline: true,
|
||||
Topic: "shared",
|
||||
}
|
||||
remotes, err := ss.RemoteCluster().GetAll(filter)
|
||||
remotes, err := ss.RemoteCluster().GetAll(0, 999999, filter)
|
||||
require.NoError(t, err)
|
||||
// make sure only online remotes were returned.
|
||||
ids := getIds(remotes)
|
||||
@@ -309,7 +312,7 @@ func testRemoteClusterGetAll(t *testing.T, _ request.CTX, ss store.Store) {
|
||||
filter := model.RemoteClusterQueryFilter{
|
||||
CreatorId: userId,
|
||||
}
|
||||
remotes, err := ss.RemoteCluster().GetAll(filter)
|
||||
remotes, err := ss.RemoteCluster().GetAll(0, 999999, filter)
|
||||
require.NoError(t, err)
|
||||
// make sure only correct creator returned
|
||||
assert.Len(t, remotes, 3)
|
||||
@@ -322,7 +325,7 @@ func testRemoteClusterGetAll(t *testing.T, _ request.CTX, ss store.Store) {
|
||||
filter := model.RemoteClusterQueryFilter{
|
||||
OnlyConfirmed: true,
|
||||
}
|
||||
remotes, err := ss.RemoteCluster().GetAll(filter)
|
||||
remotes, err := ss.RemoteCluster().GetAll(0, 999999, filter)
|
||||
require.NoError(t, err)
|
||||
// make sure only confirmed returned
|
||||
for _, rc := range remotes {
|
||||
@@ -337,7 +340,7 @@ func testRemoteClusterGetAll(t *testing.T, _ request.CTX, ss store.Store) {
|
||||
filter := model.RemoteClusterQueryFilter{
|
||||
OnlyPlugins: true,
|
||||
}
|
||||
remotes, err := ss.RemoteCluster().GetAll(filter)
|
||||
remotes, err := ss.RemoteCluster().GetAll(0, 999999, filter)
|
||||
require.NoError(t, err)
|
||||
// make sure only plugin remotes returned
|
||||
for _, rc := range remotes {
|
||||
@@ -348,6 +351,22 @@ func testRemoteClusterGetAll(t *testing.T, _ request.CTX, ss store.Store) {
|
||||
ids := getIds(remotes)
|
||||
assert.ElementsMatch(t, ids, idsPlugin)
|
||||
})
|
||||
|
||||
t.Run("GetAll excluding plugins", func(t *testing.T) {
|
||||
filter := model.RemoteClusterQueryFilter{
|
||||
ExcludePlugins: true,
|
||||
}
|
||||
remotes, err := ss.RemoteCluster().GetAll(0, 999999, filter)
|
||||
require.NoError(t, err)
|
||||
// make sure only non plugin remotes returned
|
||||
for _, rc := range remotes {
|
||||
assert.Empty(t, rc.PluginID)
|
||||
assert.False(t, rc.IsPlugin())
|
||||
}
|
||||
// make sure all of the non plugin remotes were returned.
|
||||
ids := getIds(remotes)
|
||||
assert.ElementsMatch(t, ids, idsNotPlugin)
|
||||
})
|
||||
}
|
||||
|
||||
func testRemoteClusterGetAllInChannel(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
@@ -411,7 +430,7 @@ func testRemoteClusterGetAllInChannel(t *testing.T, rctx request.CTX, ss store.S
|
||||
filter := model.RemoteClusterQueryFilter{
|
||||
InChannel: channel1.Id,
|
||||
}
|
||||
list, err := ss.RemoteCluster().GetAll(filter)
|
||||
list, err := ss.RemoteCluster().GetAll(0, 999999, filter)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, list, 2, "channel 1 should have 2 remote clusters")
|
||||
ids := getIds(list)
|
||||
@@ -425,7 +444,7 @@ func testRemoteClusterGetAllInChannel(t *testing.T, rctx request.CTX, ss store.S
|
||||
ExcludeOffline: true,
|
||||
InChannel: channel1.Id,
|
||||
}
|
||||
list, err := ss.RemoteCluster().GetAll(filter)
|
||||
list, err := ss.RemoteCluster().GetAll(0, 999999, filter)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, list, 1, "channel 1 should have 1 online remote clusters")
|
||||
ids := getIds(list)
|
||||
@@ -436,7 +455,7 @@ func testRemoteClusterGetAllInChannel(t *testing.T, rctx request.CTX, ss store.S
|
||||
filter := model.RemoteClusterQueryFilter{
|
||||
InChannel: channel2.Id,
|
||||
}
|
||||
list, err := ss.RemoteCluster().GetAll(filter)
|
||||
list, err := ss.RemoteCluster().GetAll(0, 999999, filter)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, list, 3, "channel 2 should have 3 remote clusters")
|
||||
ids := getIds(list)
|
||||
@@ -448,7 +467,7 @@ func testRemoteClusterGetAllInChannel(t *testing.T, rctx request.CTX, ss store.S
|
||||
ExcludeOffline: true,
|
||||
InChannel: channel2.Id,
|
||||
}
|
||||
list, err := ss.RemoteCluster().GetAll(filter)
|
||||
list, err := ss.RemoteCluster().GetAll(0, 999999, filter)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, list, 2, "channel 2 should have 2 online remote clusters")
|
||||
ids := getIds(list)
|
||||
@@ -459,7 +478,7 @@ func testRemoteClusterGetAllInChannel(t *testing.T, rctx request.CTX, ss store.S
|
||||
filter := model.RemoteClusterQueryFilter{
|
||||
InChannel: channel3.Id,
|
||||
}
|
||||
list, err := ss.RemoteCluster().GetAll(filter)
|
||||
list, err := ss.RemoteCluster().GetAll(0, 999999, filter)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, list, "channel 3 should have 0 remote clusters")
|
||||
})
|
||||
@@ -520,7 +539,7 @@ func testRemoteClusterGetAllNotInChannel(t *testing.T, rctx request.CTX, ss stor
|
||||
filter := model.RemoteClusterQueryFilter{
|
||||
NotInChannel: channel1.Id,
|
||||
}
|
||||
list, err := ss.RemoteCluster().GetAll(filter)
|
||||
list, err := ss.RemoteCluster().GetAll(0, 999999, filter)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, list, 3, "channel 1 should have 3 remote clusters that are not already members")
|
||||
ids := getIds(list)
|
||||
@@ -531,7 +550,7 @@ func testRemoteClusterGetAllNotInChannel(t *testing.T, rctx request.CTX, ss stor
|
||||
filter := model.RemoteClusterQueryFilter{
|
||||
NotInChannel: channel2.Id,
|
||||
}
|
||||
list, err := ss.RemoteCluster().GetAll(filter)
|
||||
list, err := ss.RemoteCluster().GetAll(0, 999999, filter)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, list, 3, "channel 2 should have 3 remote clusters that are not already members")
|
||||
ids := getIds(list)
|
||||
@@ -542,7 +561,7 @@ func testRemoteClusterGetAllNotInChannel(t *testing.T, rctx request.CTX, ss stor
|
||||
filter := model.RemoteClusterQueryFilter{
|
||||
NotInChannel: channel3.Id,
|
||||
}
|
||||
list, err := ss.RemoteCluster().GetAll(filter)
|
||||
list, err := ss.RemoteCluster().GetAll(0, 999999, filter)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, list, 4, "channel 3 should have 4 remote clusters that are not already members")
|
||||
ids := getIds(list)
|
||||
@@ -553,7 +572,7 @@ func testRemoteClusterGetAllNotInChannel(t *testing.T, rctx request.CTX, ss stor
|
||||
filter := model.RemoteClusterQueryFilter{
|
||||
NotInChannel: model.NewId(),
|
||||
}
|
||||
list, err := ss.RemoteCluster().GetAll(filter)
|
||||
list, err := ss.RemoteCluster().GetAll(0, 999999, filter)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, list, 5, "should have 5 remote clusters that are not already members")
|
||||
ids := getIds(list)
|
||||
@@ -605,7 +624,7 @@ func testRemoteClusterGetByTopic(t *testing.T, _ request.CTX, ss store.Store) {
|
||||
filter := model.RemoteClusterQueryFilter{
|
||||
Topic: tt.topic,
|
||||
}
|
||||
list, err := ss.RemoteCluster().GetAll(filter)
|
||||
list, err := ss.RemoteCluster().GetAll(0, 999999, filter)
|
||||
if tt.expectError {
|
||||
assert.Errorf(t, err, "expected error for topic=%s", tt.topic)
|
||||
} else {
|
||||
@@ -653,7 +672,7 @@ func testRemoteClusterUpdateTopics(t *testing.T, _ request.CTX, ss store.Store)
|
||||
}
|
||||
|
||||
func clearRemoteClusters(ss store.Store) error {
|
||||
list, err := ss.RemoteCluster().GetAll(model.RemoteClusterQueryFilter{})
|
||||
list, err := ss.RemoteCluster().GetAll(0, 999999, model.RemoteClusterQueryFilter{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -7065,10 +7065,10 @@ func (s *TimerLayerRemoteClusterStore) Get(remoteClusterId string) (*model.Remot
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerRemoteClusterStore) GetAll(filter model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, error) {
|
||||
func (s *TimerLayerRemoteClusterStore) GetAll(offset int, limit int, filter model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, err := s.RemoteClusterStore.GetAll(filter)
|
||||
result, err := s.RemoteClusterStore.GetAll(offset, limit, filter)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
|
||||
@@ -93,6 +93,14 @@ type Params struct {
|
||||
FilterHasMember string
|
||||
IncludeChannelMemberCount string
|
||||
OutgoingOAuthConnectionID string
|
||||
ExcludeOffline bool
|
||||
InChannel string
|
||||
NotInChannel string
|
||||
Topic string
|
||||
CreatorId string
|
||||
OnlyConfirmed bool
|
||||
OnlyPlugins bool
|
||||
ExcludePlugins bool
|
||||
|
||||
//Bookmarks
|
||||
ChannelBookmarkId string
|
||||
@@ -126,7 +134,11 @@ func ParamsFromRequest(r *http.Request) *Params {
|
||||
params.FileId = props["file_id"]
|
||||
params.Filename = query.Get("filename")
|
||||
params.UploadId = props["upload_id"]
|
||||
params.PluginId = props["plugin_id"]
|
||||
if val, ok := props["plugin_id"]; ok {
|
||||
params.PluginId = val
|
||||
} else {
|
||||
params.PluginId = query.Get("plugin_id")
|
||||
}
|
||||
params.CommandId = props["command_id"]
|
||||
params.HookId = props["hook_id"]
|
||||
params.ReportId = props["report_id"]
|
||||
@@ -150,6 +162,14 @@ func ParamsFromRequest(r *http.Request) *Params {
|
||||
params.RemoteId = props["remote_id"]
|
||||
params.InvoiceId = props["invoice_id"]
|
||||
params.OutgoingOAuthConnectionID = props["outgoing_oauth_connection_id"]
|
||||
params.ExcludeOffline, _ = strconv.ParseBool(query.Get("exclude_offline"))
|
||||
params.InChannel = query.Get("in_channel")
|
||||
params.NotInChannel = query.Get("not_in_channel")
|
||||
params.Topic = query.Get("topic")
|
||||
params.CreatorId = query.Get("creator_id")
|
||||
params.OnlyConfirmed, _ = strconv.ParseBool(query.Get("only_confirmed"))
|
||||
params.OnlyPlugins, _ = strconv.ParseBool(query.Get("only_plugins"))
|
||||
params.ExcludePlugins, _ = strconv.ParseBool(query.Get("exclude_plugins"))
|
||||
params.ChannelBookmarkId = props["bookmark_id"]
|
||||
params.Scope = query.Get("scope")
|
||||
|
||||
|
||||
@@ -1281,10 +1281,6 @@
|
||||
"id": "api.command_remote.hint",
|
||||
"translation": "[action]"
|
||||
},
|
||||
{
|
||||
"id": "api.command_remote.incorrect_password.error",
|
||||
"translation": "Could not decrypt invitation. Incorrect password or corrupt invitation: {{.Error}}"
|
||||
},
|
||||
{
|
||||
"id": "api.command_remote.invitation.help",
|
||||
"translation": "Invitation from secure connection"
|
||||
@@ -2144,6 +2140,10 @@
|
||||
"id": "api.getUsersForReporting.invalid_team_filter",
|
||||
"translation": "Invalid team id provided."
|
||||
},
|
||||
{
|
||||
"id": "api.get_site_url_error",
|
||||
"translation": "Could not get the instance site url"
|
||||
},
|
||||
{
|
||||
"id": "api.image.get.app_error",
|
||||
"translation": "Requested image url cannot be parsed."
|
||||
@@ -2726,14 +2726,34 @@
|
||||
"id": "api.reaction.save_reaction.user_id.app_error",
|
||||
"translation": "You cannot save reaction for the other user."
|
||||
},
|
||||
{
|
||||
"id": "api.remote_cluster.accept_invitation_error",
|
||||
"translation": "Could not accept the remote cluster invitation"
|
||||
},
|
||||
{
|
||||
"id": "api.remote_cluster.base64_decode_error",
|
||||
"translation": "Could not decode the base64 string"
|
||||
},
|
||||
{
|
||||
"id": "api.remote_cluster.cluster_not_deleted",
|
||||
"translation": "Remote cluster has not been deleted"
|
||||
},
|
||||
{
|
||||
"id": "api.remote_cluster.delete.app_error",
|
||||
"translation": "We encountered an error deleting the secure connection."
|
||||
},
|
||||
{
|
||||
"id": "api.remote_cluster.encrypt_invite_error",
|
||||
"translation": "Could not encrypt the remote cluster invite using the provided password"
|
||||
},
|
||||
{
|
||||
"id": "api.remote_cluster.get.app_error",
|
||||
"translation": "We encountered an error retrieving a secure connection."
|
||||
},
|
||||
{
|
||||
"id": "api.remote_cluster.get.not_found",
|
||||
"translation": "Remote Cluster not found"
|
||||
},
|
||||
{
|
||||
"id": "api.remote_cluster.invalid_id.app_error",
|
||||
"translation": "Invalid id."
|
||||
@@ -2742,6 +2762,10 @@
|
||||
"id": "api.remote_cluster.invalid_topic.app_error",
|
||||
"translation": "Invalid topic."
|
||||
},
|
||||
{
|
||||
"id": "api.remote_cluster.invite_decrypt_error",
|
||||
"translation": "Could not decrypt the remote cluster invite using the provided password"
|
||||
},
|
||||
{
|
||||
"id": "api.remote_cluster.save.app_error",
|
||||
"translation": "We encountered an error saving the secure connection."
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
// AcceptInvitation is called when accepting an invitation to connect with a remote cluster.
|
||||
func (rcs *Service) AcceptInvitation(invite *model.RemoteClusterInvite, name string, displayName, creatorId string, teamId string, siteURL string) (*model.RemoteCluster, error) {
|
||||
func (rcs *Service) AcceptInvitation(invite *model.RemoteClusterInvite, name string, displayName, creatorId string, siteURL string) (*model.RemoteCluster, error) {
|
||||
rc := &model.RemoteCluster{
|
||||
RemoteId: invite.RemoteId,
|
||||
Name: name,
|
||||
@@ -29,7 +29,7 @@ func (rcs *Service) AcceptInvitation(invite *model.RemoteClusterInvite, name str
|
||||
}
|
||||
|
||||
// confirm the invitation with the originating site
|
||||
frame, err := makeConfirmFrame(rcSaved, teamId, siteURL)
|
||||
frame, err := makeConfirmFrame(rcSaved, siteURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -60,7 +60,7 @@ func (rcs *Service) AcceptInvitation(invite *model.RemoteClusterInvite, name str
|
||||
return rcSaved, nil
|
||||
}
|
||||
|
||||
func makeConfirmFrame(rc *model.RemoteCluster, teamId string, siteURL string) (*model.RemoteClusterFrame, error) {
|
||||
func makeConfirmFrame(rc *model.RemoteCluster, siteURL string) (*model.RemoteClusterFrame, error) {
|
||||
confirm := model.RemoteClusterInvite{
|
||||
RemoteId: rc.RemoteId,
|
||||
SiteURL: siteURL,
|
||||
|
||||
@@ -52,7 +52,7 @@ func (ms *mockServer) GetStore() store.Store {
|
||||
|
||||
remoteClusterStoreMock := &mocks.RemoteClusterStore{}
|
||||
remoteClusterStoreMock.On("GetByTopic", "share").Return(ms.remotes, nil)
|
||||
remoteClusterStoreMock.On("GetAll", anyQueryFilter).Return(ms.remotes, nil)
|
||||
remoteClusterStoreMock.On("GetAll", 0, 999999, anyQueryFilter).Return(ms.remotes, nil)
|
||||
remoteClusterStoreMock.On("SetLastPingAt", anyId).Return(nil)
|
||||
|
||||
userStoreMock := &mocks.UserStore{}
|
||||
|
||||
@@ -37,7 +37,7 @@ func (rcs *Service) PingNow(rc *model.RemoteCluster) {
|
||||
// pingAllNow emits a ping to all remotes immediately without waiting for next ping loop.
|
||||
func (rcs *Service) pingAllNow(filter model.RemoteClusterQueryFilter) {
|
||||
// get all remotes, including any previously offline.
|
||||
remotes, err := rcs.server.GetStore().RemoteCluster().GetAll(filter)
|
||||
remotes, err := rcs.server.GetStore().RemoteCluster().GetAll(0, 999999, filter)
|
||||
if err != nil {
|
||||
rcs.server.Log().Log(mlog.LvlRemoteClusterServiceError, "Ping all remote clusters failed (could not get list of remotes)", mlog.Err(err))
|
||||
return
|
||||
|
||||
@@ -41,7 +41,7 @@ func (rcs *Service) BroadcastMsg(ctx context.Context, msg model.RemoteClusterMsg
|
||||
filter := model.RemoteClusterQueryFilter{
|
||||
Topic: msg.Topic,
|
||||
}
|
||||
list, err := rcs.server.GetStore().RemoteCluster().GetAll(filter)
|
||||
list, err := rcs.server.GetStore().RemoteCluster().GetAll(0, 999999, filter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ type RemoteClusterServiceIFace interface {
|
||||
SendMsg(ctx context.Context, msg model.RemoteClusterMsg, rc *model.RemoteCluster, f SendMsgResultFunc) error
|
||||
SendFile(ctx context.Context, us *model.UploadSession, fi *model.FileInfo, rc *model.RemoteCluster, rp ReaderProvider, f SendFileResultFunc) error
|
||||
SendProfileImage(ctx context.Context, userID string, rc *model.RemoteCluster, provider ProfileImageProvider, f SendProfileImageResultFunc) error
|
||||
AcceptInvitation(invite *model.RemoteClusterInvite, name string, displayName string, creatorId string, teamId string, siteURL string) (*model.RemoteCluster, error)
|
||||
AcceptInvitation(invite *model.RemoteClusterInvite, name string, displayName string, creatorId string, siteURL string) (*model.RemoteCluster, error)
|
||||
ReceiveIncomingMsg(rc *model.RemoteCluster, msg model.RemoteClusterMsg) Response
|
||||
ReceiveInviteConfirmation(invite model.RemoteClusterInvite) (*model.RemoteCluster, error)
|
||||
PingNow(rc *model.RemoteCluster)
|
||||
|
||||
@@ -258,7 +258,7 @@ func (scs *Service) processTask(task syncTask) error {
|
||||
InChannel: task.channelID,
|
||||
OnlyConfirmed: true,
|
||||
}
|
||||
remotes, err := scs.server.GetStore().RemoteCluster().GetAll(filter)
|
||||
remotes, err := scs.server.GetStore().RemoteCluster().GetAll(0, 999999, filter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -270,7 +270,7 @@ func (scs *Service) processTask(task syncTask) error {
|
||||
filter = model.RemoteClusterQueryFilter{
|
||||
RequireOptions: model.BitflagOptionAutoInvited,
|
||||
}
|
||||
remotesAutoInvited, err := scs.server.GetStore().RemoteCluster().GetAll(filter)
|
||||
remotesAutoInvited, err := scs.server.GetStore().RemoteCluster().GetAll(0, 999999, filter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -568,6 +568,10 @@ func (c *Client4) exportRoute(name string) string {
|
||||
return fmt.Sprintf(c.exportsRoute()+"/%v", name)
|
||||
}
|
||||
|
||||
func (c *Client4) remoteClusterRoute() string {
|
||||
return "/remotecluster"
|
||||
}
|
||||
|
||||
func (c *Client4) sharedChannelsRoute() string {
|
||||
return "/sharedchannels"
|
||||
}
|
||||
@@ -8698,6 +8702,154 @@ func (c *Client4) GetRemoteClusterInfo(ctx context.Context, remoteID string) (Re
|
||||
return rci, BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) GetRemoteClusters(ctx context.Context, page, perPage int, filter RemoteClusterQueryFilter) ([]*RemoteCluster, *Response, error) {
|
||||
v := url.Values{}
|
||||
if page != 0 {
|
||||
v.Set("page", fmt.Sprintf("%d", page))
|
||||
}
|
||||
if perPage != 0 {
|
||||
v.Set("per_page", fmt.Sprintf("%d", perPage))
|
||||
}
|
||||
if filter.ExcludeOffline {
|
||||
v.Set("exclude_offline", "true")
|
||||
}
|
||||
if filter.InChannel != "" {
|
||||
v.Set("in_channel", filter.InChannel)
|
||||
}
|
||||
if filter.NotInChannel != "" {
|
||||
v.Set("not_in_channel", filter.NotInChannel)
|
||||
}
|
||||
if filter.Topic != "" {
|
||||
v.Set("topic", filter.Topic)
|
||||
}
|
||||
if filter.CreatorId != "" {
|
||||
v.Set("creator_id", filter.CreatorId)
|
||||
}
|
||||
if filter.OnlyConfirmed {
|
||||
v.Set("only_confirmed", "true")
|
||||
}
|
||||
if filter.PluginID != "" {
|
||||
v.Set("plugin_id", filter.PluginID)
|
||||
}
|
||||
if filter.OnlyPlugins {
|
||||
v.Set("only_plugins", "true")
|
||||
}
|
||||
if filter.ExcludePlugins {
|
||||
v.Set("exclude_plugins", "true")
|
||||
}
|
||||
url := c.remoteClusterRoute()
|
||||
if len(v) > 0 {
|
||||
url += "?" + v.Encode()
|
||||
}
|
||||
|
||||
r, err := c.DoAPIGet(ctx, url, "")
|
||||
if err != nil {
|
||||
return nil, BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
var rcs []*RemoteCluster
|
||||
json.NewDecoder(r.Body).Decode(&rcs)
|
||||
|
||||
return rcs, BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) CreateRemoteCluster(ctx context.Context, rcWithPassword *RemoteClusterWithPassword) (*RemoteClusterWithInvite, *Response, error) {
|
||||
rcJSON, err := json.Marshal(rcWithPassword)
|
||||
if err != nil {
|
||||
return nil, nil, NewAppError("CreateRemoteCluster", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
r, err := c.DoAPIPost(ctx, c.remoteClusterRoute(), string(rcJSON))
|
||||
if err != nil {
|
||||
return nil, BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
var rcWithInvite RemoteClusterWithInvite
|
||||
if err := json.NewDecoder(r.Body).Decode(&rcWithInvite); err != nil {
|
||||
return nil, nil, NewAppError("CreateRemoteCluster", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
return &rcWithInvite, BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) RemoteClusterAcceptInvite(ctx context.Context, rcAcceptInvite *RemoteClusterAcceptInvite) (*RemoteCluster, *Response, error) {
|
||||
rcAcceptInviteJSON, err := json.Marshal(rcAcceptInvite)
|
||||
if err != nil {
|
||||
return nil, nil, NewAppError("RemoteClusterAcceptInvite", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/accept_invite", c.remoteClusterRoute())
|
||||
r, err := c.DoAPIPost(ctx, url, string(rcAcceptInviteJSON))
|
||||
if err != nil {
|
||||
return nil, BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
var rc RemoteCluster
|
||||
if err := json.NewDecoder(r.Body).Decode(&rc); err != nil {
|
||||
return nil, nil, NewAppError("RemoteClusterAcceptInvite", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
return &rc, BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) GenerateRemoteClusterInvite(ctx context.Context, remoteClusterId, password string) (string, *Response, error) {
|
||||
url := fmt.Sprintf("%s/%s/generate_invite", c.remoteClusterRoute(), remoteClusterId)
|
||||
r, err := c.DoAPIPost(ctx, url, MapToJSON(map[string]string{"password": password}))
|
||||
if err != nil {
|
||||
return "", BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
b, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return "", nil, NewAppError("GenerateRemoteClusterInvite", "api.read_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
return string(b), BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) GetRemoteCluster(ctx context.Context, remoteClusterId string) (*RemoteCluster, *Response, error) {
|
||||
r, err := c.DoAPIGet(ctx, fmt.Sprintf("%s/%s", c.remoteClusterRoute(), remoteClusterId), "")
|
||||
if err != nil {
|
||||
return nil, BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
var rc *RemoteCluster
|
||||
json.NewDecoder(r.Body).Decode(&rc)
|
||||
|
||||
return rc, BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) PatchRemoteCluster(ctx context.Context, remoteClusterId string, patch *RemoteClusterPatch) (*RemoteCluster, *Response, error) {
|
||||
patchJSON, err := json.Marshal(patch)
|
||||
if err != nil {
|
||||
return nil, nil, NewAppError("PatchRemoteCluster", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/%s", c.remoteClusterRoute(), remoteClusterId)
|
||||
r, err := c.DoAPIPatchBytes(ctx, url, patchJSON)
|
||||
if err != nil {
|
||||
return nil, BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
var rc RemoteCluster
|
||||
if err := json.NewDecoder(r.Body).Decode(&rc); err != nil {
|
||||
return nil, nil, NewAppError("PatchRemoteCluster", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
return &rc, BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) DeleteRemoteCluster(ctx context.Context, remoteClusterId string) (*Response, error) {
|
||||
r, err := c.DoAPIDelete(ctx, fmt.Sprintf("%s/%s", c.remoteClusterRoute(), remoteClusterId))
|
||||
if err != nil {
|
||||
return BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
return BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) GetAncillaryPermissions(ctx context.Context, subsectionPermissions []string) ([]string, *Response, error) {
|
||||
var returnedPermissions []string
|
||||
url := fmt.Sprintf("%s/ancillary?subsection_permissions=%s", c.permissionsRoute(), strings.Join(subsectionPermissions, ","))
|
||||
|
||||
@@ -126,6 +126,37 @@ func (rc *RemoteCluster) IsValid() *AppError {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rc *RemoteCluster) Sanitize() {
|
||||
rc.Token = ""
|
||||
rc.RemoteToken = ""
|
||||
}
|
||||
|
||||
type RemoteClusterPatch struct {
|
||||
DisplayName *string `json:"display_name"`
|
||||
}
|
||||
|
||||
func (rcp *RemoteClusterPatch) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"display_name": rcp.DisplayName,
|
||||
}
|
||||
}
|
||||
|
||||
func (rc *RemoteCluster) Patch(patch *RemoteClusterPatch) {
|
||||
if patch.DisplayName != nil {
|
||||
rc.DisplayName = *patch.DisplayName
|
||||
}
|
||||
}
|
||||
|
||||
type RemoteClusterWithPassword struct {
|
||||
*RemoteCluster
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type RemoteClusterWithInvite struct {
|
||||
RemoteCluster *RemoteCluster `json:"remote_cluster"`
|
||||
Invite string `json:"invite"`
|
||||
}
|
||||
|
||||
func newIDFromBytes(b []byte) string {
|
||||
hash := md5.New()
|
||||
_, _ = hash.Write(b)
|
||||
@@ -393,6 +424,13 @@ func (rci *RemoteClusterInvite) Decrypt(encrypted []byte, password string) error
|
||||
return json.Unmarshal(plain, &rci)
|
||||
}
|
||||
|
||||
type RemoteClusterAcceptInvite struct {
|
||||
Name string `json:"name"`
|
||||
DisplayName string `json:"display_name"`
|
||||
Invite string `json:"invite"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
// RemoteClusterQueryFilter provides filter criteria for RemoteClusterStore.GetAll
|
||||
type RemoteClusterQueryFilter struct {
|
||||
ExcludeOffline bool
|
||||
@@ -403,5 +441,6 @@ type RemoteClusterQueryFilter struct {
|
||||
OnlyConfirmed bool
|
||||
PluginID string
|
||||
OnlyPlugins bool
|
||||
ExcludePlugins bool
|
||||
RequireOptions Bitmask
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user