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>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
cc5e87ae24
Коммит
809ad4f76d
@@ -40,6 +40,7 @@ build-v4: node_modules playbooks
|
|||||||
@cat $(V4_SRC)/roles.yaml >> $(V4_YAML)
|
@cat $(V4_SRC)/roles.yaml >> $(V4_YAML)
|
||||||
@cat $(V4_SRC)/schemes.yaml >> $(V4_YAML)
|
@cat $(V4_SRC)/schemes.yaml >> $(V4_YAML)
|
||||||
@cat $(V4_SRC)/service_terms.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)/sharedchannels.yaml >> $(V4_YAML)
|
||||||
@cat $(V4_SRC)/reactions.yaml >> $(V4_YAML)
|
@cat $(V4_SRC)/reactions.yaml >> $(V4_YAML)
|
||||||
@cat $(V4_SRC)/actions.yaml >> $(V4_YAML)
|
@cat $(V4_SRC)/actions.yaml >> $(V4_YAML)
|
||||||
|
|||||||
@@ -3485,6 +3485,39 @@ components:
|
|||||||
remote_id:
|
remote_id:
|
||||||
description: Id of the remote cluster where the shared channel is homed
|
description: Id of the remote cluster where the shared channel is homed
|
||||||
type: string
|
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:
|
RemoteClusterInfo:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
|||||||
@@ -598,6 +598,7 @@ x-tagGroups:
|
|||||||
- roles
|
- roles
|
||||||
- schemes
|
- schemes
|
||||||
- integration_actions
|
- integration_actions
|
||||||
|
- remote clusters
|
||||||
- shared channels
|
- shared channels
|
||||||
- terms of service
|
- terms of service
|
||||||
- imports
|
- imports
|
||||||
|
|||||||
282
api/v4/source/remoteclusters.yaml
Обычный файл
282
api/v4/source/remoteclusters.yaml
Обычный файл
@@ -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)
|
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) {
|
func CheckForbiddenStatus(tb testing.TB, resp *model.Response) {
|
||||||
tb.Helper()
|
tb.Helper()
|
||||||
checkHTTPStatus(tb, resp, http.StatusForbidden)
|
checkHTTPStatus(tb, resp, http.StatusForbidden)
|
||||||
@@ -1106,6 +1111,11 @@ func CheckBadRequestStatus(tb testing.TB, resp *model.Response) {
|
|||||||
checkHTTPStatus(tb, resp, http.StatusBadRequest)
|
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) {
|
func CheckNotImplementedStatus(tb testing.TB, resp *model.Response) {
|
||||||
tb.Helper()
|
tb.Helper()
|
||||||
checkHTTPStatus(tb, resp, http.StatusNotImplemented)
|
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("/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("/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("/{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) {
|
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)
|
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
Обычный файл
559
server/channels/api4/remote_cluster_test.go
Обычный файл
@@ -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)
|
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)
|
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)
|
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)
|
CreateRetentionPolicy(policy *model.RetentionPolicyWithTeamAndChannelIDs) (*model.RetentionPolicyWithTeamAndChannelCounts, *model.AppError)
|
||||||
CreateRole(role *model.Role) (*model.Role, *model.AppError)
|
CreateRole(role *model.Role) (*model.Role, *model.AppError)
|
||||||
CreateSamlRelayToken(extra string) (*model.Token, *model.AppError)
|
CreateSamlRelayToken(extra string) (*model.Token, *model.AppError)
|
||||||
@@ -555,6 +556,7 @@ type AppIface interface {
|
|||||||
DeactivateGuests(c request.CTX) *model.AppError
|
DeactivateGuests(c request.CTX) *model.AppError
|
||||||
DeactivateMfa(userID string) *model.AppError
|
DeactivateMfa(userID string) *model.AppError
|
||||||
DeauthorizeOAuthAppForUser(c request.CTX, userID, appID 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
|
DeleteAcknowledgementForPost(c request.CTX, postID, userID string) *model.AppError
|
||||||
DeleteAllExpiredPluginKeys() *model.AppError
|
DeleteAllExpiredPluginKeys() *model.AppError
|
||||||
DeleteAllKeysForPlugin(pluginID string) *model.AppError
|
DeleteAllKeysForPlugin(pluginID string) *model.AppError
|
||||||
@@ -626,7 +628,7 @@ type AppIface interface {
|
|||||||
GetAllChannelsCount(c request.CTX, opts model.ChannelSearchOpts) (int64, *model.AppError)
|
GetAllChannelsCount(c request.CTX, opts model.ChannelSearchOpts) (int64, *model.AppError)
|
||||||
GetAllPrivateTeams() ([]*model.Team, *model.AppError)
|
GetAllPrivateTeams() ([]*model.Team, *model.AppError)
|
||||||
GetAllPublicTeams() ([]*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)
|
GetAllRoles() ([]*model.Role, *model.AppError)
|
||||||
GetAllTeams() ([]*model.Team, *model.AppError)
|
GetAllTeams() ([]*model.Team, *model.AppError)
|
||||||
GetAllTeamsPage(offset int, limit int, opts *model.TeamSearch) ([]*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)
|
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)
|
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)
|
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)
|
PatchRetentionPolicy(patch *model.RetentionPolicyWithTeamAndChannelIDs) (*model.RetentionPolicyWithTeamAndChannelCounts, *model.AppError)
|
||||||
PatchRole(role *model.Role, patch *model.RolePatch) (*model.Role, *model.AppError)
|
PatchRole(role *model.Role, patch *model.RolePatch) (*model.Role, *model.AppError)
|
||||||
PatchScheme(scheme *model.Scheme, patch *model.SchemePatch) (*model.Scheme, *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
|
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) {
|
func (a *OpenTracingAppLayer) CreateRetentionPolicy(policy *model.RetentionPolicyWithTeamAndChannelIDs) (*model.RetentionPolicyWithTeamAndChannelCounts, *model.AppError) {
|
||||||
origCtx := a.ctx
|
origCtx := a.ctx
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.CreateRetentionPolicy")
|
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.CreateRetentionPolicy")
|
||||||
@@ -3050,6 +3072,28 @@ func (a *OpenTracingAppLayer) DeauthorizeOAuthAppForUser(c request.CTX, userID s
|
|||||||
return resultVar0
|
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 {
|
func (a *OpenTracingAppLayer) DefaultChannelNames(c request.CTX) []string {
|
||||||
origCtx := a.ctx
|
origCtx := a.ctx
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.DefaultChannelNames")
|
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.DefaultChannelNames")
|
||||||
@@ -5023,7 +5067,7 @@ func (a *OpenTracingAppLayer) GetAllPublicTeams() ([]*model.Team, *model.AppErro
|
|||||||
return resultVar0, resultVar1
|
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
|
origCtx := a.ctx
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetAllRemoteClusters")
|
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetAllRemoteClusters")
|
||||||
|
|
||||||
@@ -5035,7 +5079,7 @@ func (a *OpenTracingAppLayer) GetAllRemoteClusters(filter model.RemoteClusterQue
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
defer span.Finish()
|
defer span.Finish()
|
||||||
resultVar0, resultVar1 := a.app.GetAllRemoteClusters(filter)
|
resultVar0, resultVar1 := a.app.GetAllRemoteClusters(page, perPage, filter)
|
||||||
|
|
||||||
if resultVar1 != nil {
|
if resultVar1 != nil {
|
||||||
span.LogFields(spanlog.Error(resultVar1))
|
span.LogFields(spanlog.Error(resultVar1))
|
||||||
@@ -13376,6 +13420,28 @@ func (a *OpenTracingAppLayer) PatchPost(c request.CTX, postID string, patch *mod
|
|||||||
return resultVar0, resultVar1
|
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) {
|
func (a *OpenTracingAppLayer) PatchRetentionPolicy(patch *model.RetentionPolicyWithTeamAndChannelIDs) (*model.RetentionPolicyWithTeamAndChannelCounts, *model.AppError) {
|
||||||
origCtx := a.ctx
|
origCtx := a.ctx
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.PatchRetentionPolicy")
|
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.PatchRetentionPolicy")
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ func handleContentSync(ps *PlatformService, syncService SharedChannelServiceIFac
|
|||||||
OnlyConfirmed: true,
|
OnlyConfirmed: true,
|
||||||
RequireOptions: model.BitflagOptionAutoShareDMs,
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot fetch remote clusters: %w", err)
|
return fmt.Errorf("cannot fetch remote clusters: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"encoding/base64"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@@ -100,6 +101,22 @@ func (a *App) AddRemoteCluster(rc *model.RemoteCluster) (*model.RemoteCluster, *
|
|||||||
return rc, nil
|
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) {
|
func (a *App) UpdateRemoteCluster(rc *model.RemoteCluster) (*model.RemoteCluster, *model.AppError) {
|
||||||
rc, err := a.Srv().Store().RemoteCluster().Update(rc)
|
rc, err := a.Srv().Store().RemoteCluster().Update(rc)
|
||||||
if err != nil {
|
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) {
|
func (a *App) GetRemoteCluster(remoteClusterId string) (*model.RemoteCluster, *model.AppError) {
|
||||||
rc, err := a.Srv().Store().RemoteCluster().Get(remoteClusterId)
|
rc, err := a.Srv().Store().RemoteCluster().Get(remoteClusterId)
|
||||||
if err != nil {
|
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
|
return rc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) GetAllRemoteClusters(filter model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, *model.AppError) {
|
func (a *App) GetAllRemoteClusters(page, perPage int, filter model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, *model.AppError) {
|
||||||
list, err := a.Srv().Store().RemoteCluster().GetAll(filter)
|
list, err := a.Srv().Store().RemoteCluster().GetAll(page*perPage, perPage, filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, model.NewAppError("GetAllRemoteClusters", "api.remote_cluster.get.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
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
|
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
|
package slashcommands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -144,19 +143,13 @@ func (rp *RemoteProvider) doCreate(a *app.App, args *model.CommandArgs, margs ma
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Display the encrypted invitation
|
// Display the encrypted invitation
|
||||||
invite := &model.RemoteClusterInvite{
|
inviteCode, err := a.CreateRemoteClusterInvite(rcSaved.RemoteId, url, rcSaved.Token, password)
|
||||||
RemoteId: rcSaved.RemoteId,
|
|
||||||
SiteURL: url,
|
|
||||||
Token: rcSaved.Token,
|
|
||||||
}
|
|
||||||
encrypted, err := invite.Encrypt(password)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return responsef(args.T("api.command_remote.encrypt_invitation.error", map[string]any{"Error": err.Error()}))
|
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" +
|
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.
|
// 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
|
// invite is encoded as base64 and encrypted
|
||||||
decoded, err := base64.URLEncoding.DecodeString(blob)
|
invite, dErr := a.DecryptRemoteClusterInvite(blob, password)
|
||||||
if err != nil {
|
if dErr != nil {
|
||||||
return responsef(args.T("api.command_remote.decode_invitation.error", map[string]any{"Error": err.Error()}))
|
return responsef(args.T("api.command_remote.decode_invitation.error", map[string]any{"Error": dErr.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()}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rcs, _ := a.GetRemoteClusterService()
|
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"))
|
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 {
|
if err != nil {
|
||||||
return responsef(args.T("api.command_remote.accept_invitation.error", map[string]any{"Error": err.Error()}))
|
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.
|
// doStatus displays connection status for all remote clusters.
|
||||||
func (rp *RemoteProvider) doStatus(a *app.App, args *model.CommandArgs, _ map[string]string) *model.CommandResponse {
|
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 {
|
if err != nil {
|
||||||
responsef(args.T("api.command_remote.fetch_status.error", map[string]any{"Error": err.Error()}))
|
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{
|
filter := model.RemoteClusterQueryFilter{
|
||||||
ExcludeOffline: !includeOffline,
|
ExcludeOffline: !includeOffline,
|
||||||
}
|
}
|
||||||
clusters, err := a.GetAllRemoteClusters(filter)
|
clusters, err := a.GetAllRemoteClusters(0, 999999, filter)
|
||||||
if err != nil || len(clusters) == 0 {
|
if err != nil || len(clusters) == 0 {
|
||||||
return []model.AutocompleteListItem{}, nil
|
return []model.AutocompleteListItem{}, nil
|
||||||
}
|
}
|
||||||
@@ -284,7 +272,7 @@ func getRemoteClusterAutocompleteListItemsNotInChannel(a *app.App, channelID str
|
|||||||
ExcludeOffline: !includeOffline,
|
ExcludeOffline: !includeOffline,
|
||||||
NotInChannel: channelID,
|
NotInChannel: channelID,
|
||||||
}
|
}
|
||||||
all, err := a.GetAllRemoteClusters(filter)
|
all, err := a.GetAllRemoteClusters(0, 999999, filter)
|
||||||
if err != nil || len(all) == 0 {
|
if err != nil || len(all) == 0 {
|
||||||
return []model.AutocompleteListItem{}, nil
|
return []model.AutocompleteListItem{}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7828,7 +7828,7 @@ func (s *OpenTracingLayerRemoteClusterStore) Get(remoteClusterId string) (*model
|
|||||||
return result, err
|
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()
|
origCtx := s.Root.Store.Context()
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "RemoteClusterStore.GetAll")
|
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "RemoteClusterStore.GetAll")
|
||||||
s.Root.Store.SetContext(newCtx)
|
s.Root.Store.SetContext(newCtx)
|
||||||
@@ -7837,7 +7837,7 @@ func (s *OpenTracingLayerRemoteClusterStore) GetAll(filter model.RemoteClusterQu
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
defer span.Finish()
|
defer span.Finish()
|
||||||
result, err := s.RemoteClusterStore.GetAll(filter)
|
result, err := s.RemoteClusterStore.GetAll(offset, limit, filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
span.LogFields(spanlog.Error(err))
|
span.LogFields(spanlog.Error(err))
|
||||||
ext.Error.Set(span, true)
|
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
|
tries := 0
|
||||||
for {
|
for {
|
||||||
result, err := s.RemoteClusterStore.GetAll(filter)
|
result, err := s.RemoteClusterStore.GetAll(offset, limit, filter)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -162,7 +162,14 @@ func (s sqlRemoteClusterStore) GetByPluginID(pluginID string) (*model.RemoteClus
|
|||||||
return &rc, nil
|
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().
|
query := s.getQueryBuilder().
|
||||||
Select(remoteClusterFields("rc")...).
|
Select(remoteClusterFields("rc")...).
|
||||||
From("RemoteClusters rc")
|
From("RemoteClusters rc")
|
||||||
@@ -195,6 +202,10 @@ func (s sqlRemoteClusterStore) GetAll(filter model.RemoteClusterQueryFilter) ([]
|
|||||||
query = query.Where(sq.NotEq{"rc.PluginID": ""})
|
query = query.Where(sq.NotEq{"rc.PluginID": ""})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if filter.ExcludePlugins {
|
||||||
|
query = query.Where(sq.Eq{"rc.PluginID": ""})
|
||||||
|
}
|
||||||
|
|
||||||
if filter.RequireOptions != 0 {
|
if filter.RequireOptions != 0 {
|
||||||
query = query.Where(sq.NotEq{fmt.Sprintf("(rc.Options & %d)", 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.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()
|
queryString, args, err := query.ToSql()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "remote_cluster_getall_tosql")
|
return nil, errors.Wrap(err, "remote_cluster_getall_tosql")
|
||||||
|
|||||||
@@ -533,7 +533,7 @@ type RemoteClusterStore interface {
|
|||||||
Delete(remoteClusterId string) (bool, error)
|
Delete(remoteClusterId string) (bool, error)
|
||||||
Get(remoteClusterId string) (*model.RemoteCluster, error)
|
Get(remoteClusterId string) (*model.RemoteCluster, error)
|
||||||
GetByPluginID(pluginID 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)
|
UpdateTopics(remoteClusterId string, topics string) (*model.RemoteCluster, error)
|
||||||
SetLastPingAt(remoteClusterId string) error
|
SetLastPingAt(remoteClusterId string) error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,9 +72,9 @@ func (_m *RemoteClusterStore) Get(remoteClusterId string) (*model.RemoteCluster,
|
|||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAll provides a mock function with given fields: filter
|
// GetAll provides a mock function with given fields: offset, limit, filter
|
||||||
func (_m *RemoteClusterStore) GetAll(filter model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, error) {
|
func (_m *RemoteClusterStore) GetAll(offset int, limit int, filter model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, error) {
|
||||||
ret := _m.Called(filter)
|
ret := _m.Called(offset, limit, filter)
|
||||||
|
|
||||||
if len(ret) == 0 {
|
if len(ret) == 0 {
|
||||||
panic("no return value specified for GetAll")
|
panic("no return value specified for GetAll")
|
||||||
@@ -82,19 +82,19 @@ func (_m *RemoteClusterStore) GetAll(filter model.RemoteClusterQueryFilter) ([]*
|
|||||||
|
|
||||||
var r0 []*model.RemoteCluster
|
var r0 []*model.RemoteCluster
|
||||||
var r1 error
|
var r1 error
|
||||||
if rf, ok := ret.Get(0).(func(model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, error)); ok {
|
if rf, ok := ret.Get(0).(func(int, int, model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, error)); ok {
|
||||||
return rf(filter)
|
return rf(offset, limit, filter)
|
||||||
}
|
}
|
||||||
if rf, ok := ret.Get(0).(func(model.RemoteClusterQueryFilter) []*model.RemoteCluster); ok {
|
if rf, ok := ret.Get(0).(func(int, int, model.RemoteClusterQueryFilter) []*model.RemoteCluster); ok {
|
||||||
r0 = rf(filter)
|
r0 = rf(offset, limit, filter)
|
||||||
} else {
|
} else {
|
||||||
if ret.Get(0) != nil {
|
if ret.Get(0) != nil {
|
||||||
r0 = ret.Get(0).([]*model.RemoteCluster)
|
r0 = ret.Get(0).([]*model.RemoteCluster)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if rf, ok := ret.Get(1).(func(model.RemoteClusterQueryFilter) error); ok {
|
if rf, ok := ret.Get(1).(func(int, int, model.RemoteClusterQueryFilter) error); ok {
|
||||||
r1 = rf(filter)
|
r1 = rf(offset, limit, filter)
|
||||||
} else {
|
} else {
|
||||||
r1 = ret.Error(1)
|
r1 = ret.Error(1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -238,6 +238,7 @@ func testRemoteClusterGetAll(t *testing.T, _ request.CTX, ss store.Store) {
|
|||||||
idsOnline := make([]string, 0)
|
idsOnline := make([]string, 0)
|
||||||
idsShareTopic := make([]string, 0)
|
idsShareTopic := make([]string, 0)
|
||||||
idsPlugin := make([]string, 0)
|
idsPlugin := make([]string, 0)
|
||||||
|
idsNotPlugin := make([]string, 0)
|
||||||
idsConfirmed := make([]string, 0)
|
idsConfirmed := make([]string, 0)
|
||||||
|
|
||||||
for _, item := range data {
|
for _, item := range data {
|
||||||
@@ -253,6 +254,8 @@ func testRemoteClusterGetAll(t *testing.T, _ request.CTX, ss store.Store) {
|
|||||||
}
|
}
|
||||||
if item.PluginID != "" {
|
if item.PluginID != "" {
|
||||||
idsPlugin = append(idsPlugin, saved.RemoteId)
|
idsPlugin = append(idsPlugin, saved.RemoteId)
|
||||||
|
} else {
|
||||||
|
idsNotPlugin = append(idsNotPlugin, saved.RemoteId)
|
||||||
}
|
}
|
||||||
if item.SiteURL != "" {
|
if item.SiteURL != "" {
|
||||||
idsConfirmed = append(idsConfirmed, saved.RemoteId)
|
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) {
|
t.Run("GetAll", func(t *testing.T) {
|
||||||
filter := model.RemoteClusterQueryFilter{}
|
filter := model.RemoteClusterQueryFilter{}
|
||||||
remotes, err := ss.RemoteCluster().GetAll(filter)
|
remotes, err := ss.RemoteCluster().GetAll(0, 999999, filter)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
// make sure all the test data remotes were returned.
|
// make sure all the test data remotes were returned.
|
||||||
ids := getIds(remotes)
|
ids := getIds(remotes)
|
||||||
@@ -272,7 +275,7 @@ func testRemoteClusterGetAll(t *testing.T, _ request.CTX, ss store.Store) {
|
|||||||
filter := model.RemoteClusterQueryFilter{
|
filter := model.RemoteClusterQueryFilter{
|
||||||
ExcludeOffline: true,
|
ExcludeOffline: true,
|
||||||
}
|
}
|
||||||
remotes, err := ss.RemoteCluster().GetAll(filter)
|
remotes, err := ss.RemoteCluster().GetAll(0, 999999, filter)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
// make sure all the online remotes were returned.
|
// make sure all the online remotes were returned.
|
||||||
ids := getIds(remotes)
|
ids := getIds(remotes)
|
||||||
@@ -283,7 +286,7 @@ func testRemoteClusterGetAll(t *testing.T, _ request.CTX, ss store.Store) {
|
|||||||
filter := model.RemoteClusterQueryFilter{
|
filter := model.RemoteClusterQueryFilter{
|
||||||
Topic: "shared",
|
Topic: "shared",
|
||||||
}
|
}
|
||||||
remotes, err := ss.RemoteCluster().GetAll(filter)
|
remotes, err := ss.RemoteCluster().GetAll(0, 999999, filter)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
// make sure only correct topic returned
|
// make sure only correct topic returned
|
||||||
ids := getIds(remotes)
|
ids := getIds(remotes)
|
||||||
@@ -295,7 +298,7 @@ func testRemoteClusterGetAll(t *testing.T, _ request.CTX, ss store.Store) {
|
|||||||
ExcludeOffline: true,
|
ExcludeOffline: true,
|
||||||
Topic: "shared",
|
Topic: "shared",
|
||||||
}
|
}
|
||||||
remotes, err := ss.RemoteCluster().GetAll(filter)
|
remotes, err := ss.RemoteCluster().GetAll(0, 999999, filter)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
// make sure only online remotes were returned.
|
// make sure only online remotes were returned.
|
||||||
ids := getIds(remotes)
|
ids := getIds(remotes)
|
||||||
@@ -309,7 +312,7 @@ func testRemoteClusterGetAll(t *testing.T, _ request.CTX, ss store.Store) {
|
|||||||
filter := model.RemoteClusterQueryFilter{
|
filter := model.RemoteClusterQueryFilter{
|
||||||
CreatorId: userId,
|
CreatorId: userId,
|
||||||
}
|
}
|
||||||
remotes, err := ss.RemoteCluster().GetAll(filter)
|
remotes, err := ss.RemoteCluster().GetAll(0, 999999, filter)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
// make sure only correct creator returned
|
// make sure only correct creator returned
|
||||||
assert.Len(t, remotes, 3)
|
assert.Len(t, remotes, 3)
|
||||||
@@ -322,7 +325,7 @@ func testRemoteClusterGetAll(t *testing.T, _ request.CTX, ss store.Store) {
|
|||||||
filter := model.RemoteClusterQueryFilter{
|
filter := model.RemoteClusterQueryFilter{
|
||||||
OnlyConfirmed: true,
|
OnlyConfirmed: true,
|
||||||
}
|
}
|
||||||
remotes, err := ss.RemoteCluster().GetAll(filter)
|
remotes, err := ss.RemoteCluster().GetAll(0, 999999, filter)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
// make sure only confirmed returned
|
// make sure only confirmed returned
|
||||||
for _, rc := range remotes {
|
for _, rc := range remotes {
|
||||||
@@ -337,7 +340,7 @@ func testRemoteClusterGetAll(t *testing.T, _ request.CTX, ss store.Store) {
|
|||||||
filter := model.RemoteClusterQueryFilter{
|
filter := model.RemoteClusterQueryFilter{
|
||||||
OnlyPlugins: true,
|
OnlyPlugins: true,
|
||||||
}
|
}
|
||||||
remotes, err := ss.RemoteCluster().GetAll(filter)
|
remotes, err := ss.RemoteCluster().GetAll(0, 999999, filter)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
// make sure only plugin remotes returned
|
// make sure only plugin remotes returned
|
||||||
for _, rc := range remotes {
|
for _, rc := range remotes {
|
||||||
@@ -348,6 +351,22 @@ func testRemoteClusterGetAll(t *testing.T, _ request.CTX, ss store.Store) {
|
|||||||
ids := getIds(remotes)
|
ids := getIds(remotes)
|
||||||
assert.ElementsMatch(t, ids, idsPlugin)
|
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) {
|
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{
|
filter := model.RemoteClusterQueryFilter{
|
||||||
InChannel: channel1.Id,
|
InChannel: channel1.Id,
|
||||||
}
|
}
|
||||||
list, err := ss.RemoteCluster().GetAll(filter)
|
list, err := ss.RemoteCluster().GetAll(0, 999999, filter)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, list, 2, "channel 1 should have 2 remote clusters")
|
require.Len(t, list, 2, "channel 1 should have 2 remote clusters")
|
||||||
ids := getIds(list)
|
ids := getIds(list)
|
||||||
@@ -425,7 +444,7 @@ func testRemoteClusterGetAllInChannel(t *testing.T, rctx request.CTX, ss store.S
|
|||||||
ExcludeOffline: true,
|
ExcludeOffline: true,
|
||||||
InChannel: channel1.Id,
|
InChannel: channel1.Id,
|
||||||
}
|
}
|
||||||
list, err := ss.RemoteCluster().GetAll(filter)
|
list, err := ss.RemoteCluster().GetAll(0, 999999, filter)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, list, 1, "channel 1 should have 1 online remote clusters")
|
require.Len(t, list, 1, "channel 1 should have 1 online remote clusters")
|
||||||
ids := getIds(list)
|
ids := getIds(list)
|
||||||
@@ -436,7 +455,7 @@ func testRemoteClusterGetAllInChannel(t *testing.T, rctx request.CTX, ss store.S
|
|||||||
filter := model.RemoteClusterQueryFilter{
|
filter := model.RemoteClusterQueryFilter{
|
||||||
InChannel: channel2.Id,
|
InChannel: channel2.Id,
|
||||||
}
|
}
|
||||||
list, err := ss.RemoteCluster().GetAll(filter)
|
list, err := ss.RemoteCluster().GetAll(0, 999999, filter)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, list, 3, "channel 2 should have 3 remote clusters")
|
require.Len(t, list, 3, "channel 2 should have 3 remote clusters")
|
||||||
ids := getIds(list)
|
ids := getIds(list)
|
||||||
@@ -448,7 +467,7 @@ func testRemoteClusterGetAllInChannel(t *testing.T, rctx request.CTX, ss store.S
|
|||||||
ExcludeOffline: true,
|
ExcludeOffline: true,
|
||||||
InChannel: channel2.Id,
|
InChannel: channel2.Id,
|
||||||
}
|
}
|
||||||
list, err := ss.RemoteCluster().GetAll(filter)
|
list, err := ss.RemoteCluster().GetAll(0, 999999, filter)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, list, 2, "channel 2 should have 2 online remote clusters")
|
require.Len(t, list, 2, "channel 2 should have 2 online remote clusters")
|
||||||
ids := getIds(list)
|
ids := getIds(list)
|
||||||
@@ -459,7 +478,7 @@ func testRemoteClusterGetAllInChannel(t *testing.T, rctx request.CTX, ss store.S
|
|||||||
filter := model.RemoteClusterQueryFilter{
|
filter := model.RemoteClusterQueryFilter{
|
||||||
InChannel: channel3.Id,
|
InChannel: channel3.Id,
|
||||||
}
|
}
|
||||||
list, err := ss.RemoteCluster().GetAll(filter)
|
list, err := ss.RemoteCluster().GetAll(0, 999999, filter)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Empty(t, list, "channel 3 should have 0 remote clusters")
|
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{
|
filter := model.RemoteClusterQueryFilter{
|
||||||
NotInChannel: channel1.Id,
|
NotInChannel: channel1.Id,
|
||||||
}
|
}
|
||||||
list, err := ss.RemoteCluster().GetAll(filter)
|
list, err := ss.RemoteCluster().GetAll(0, 999999, filter)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, list, 3, "channel 1 should have 3 remote clusters that are not already members")
|
require.Len(t, list, 3, "channel 1 should have 3 remote clusters that are not already members")
|
||||||
ids := getIds(list)
|
ids := getIds(list)
|
||||||
@@ -531,7 +550,7 @@ func testRemoteClusterGetAllNotInChannel(t *testing.T, rctx request.CTX, ss stor
|
|||||||
filter := model.RemoteClusterQueryFilter{
|
filter := model.RemoteClusterQueryFilter{
|
||||||
NotInChannel: channel2.Id,
|
NotInChannel: channel2.Id,
|
||||||
}
|
}
|
||||||
list, err := ss.RemoteCluster().GetAll(filter)
|
list, err := ss.RemoteCluster().GetAll(0, 999999, filter)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, list, 3, "channel 2 should have 3 remote clusters that are not already members")
|
require.Len(t, list, 3, "channel 2 should have 3 remote clusters that are not already members")
|
||||||
ids := getIds(list)
|
ids := getIds(list)
|
||||||
@@ -542,7 +561,7 @@ func testRemoteClusterGetAllNotInChannel(t *testing.T, rctx request.CTX, ss stor
|
|||||||
filter := model.RemoteClusterQueryFilter{
|
filter := model.RemoteClusterQueryFilter{
|
||||||
NotInChannel: channel3.Id,
|
NotInChannel: channel3.Id,
|
||||||
}
|
}
|
||||||
list, err := ss.RemoteCluster().GetAll(filter)
|
list, err := ss.RemoteCluster().GetAll(0, 999999, filter)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, list, 4, "channel 3 should have 4 remote clusters that are not already members")
|
require.Len(t, list, 4, "channel 3 should have 4 remote clusters that are not already members")
|
||||||
ids := getIds(list)
|
ids := getIds(list)
|
||||||
@@ -553,7 +572,7 @@ func testRemoteClusterGetAllNotInChannel(t *testing.T, rctx request.CTX, ss stor
|
|||||||
filter := model.RemoteClusterQueryFilter{
|
filter := model.RemoteClusterQueryFilter{
|
||||||
NotInChannel: model.NewId(),
|
NotInChannel: model.NewId(),
|
||||||
}
|
}
|
||||||
list, err := ss.RemoteCluster().GetAll(filter)
|
list, err := ss.RemoteCluster().GetAll(0, 999999, filter)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, list, 5, "should have 5 remote clusters that are not already members")
|
require.Len(t, list, 5, "should have 5 remote clusters that are not already members")
|
||||||
ids := getIds(list)
|
ids := getIds(list)
|
||||||
@@ -605,7 +624,7 @@ func testRemoteClusterGetByTopic(t *testing.T, _ request.CTX, ss store.Store) {
|
|||||||
filter := model.RemoteClusterQueryFilter{
|
filter := model.RemoteClusterQueryFilter{
|
||||||
Topic: tt.topic,
|
Topic: tt.topic,
|
||||||
}
|
}
|
||||||
list, err := ss.RemoteCluster().GetAll(filter)
|
list, err := ss.RemoteCluster().GetAll(0, 999999, filter)
|
||||||
if tt.expectError {
|
if tt.expectError {
|
||||||
assert.Errorf(t, err, "expected error for topic=%s", tt.topic)
|
assert.Errorf(t, err, "expected error for topic=%s", tt.topic)
|
||||||
} else {
|
} else {
|
||||||
@@ -653,7 +672,7 @@ func testRemoteClusterUpdateTopics(t *testing.T, _ request.CTX, ss store.Store)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func clearRemoteClusters(ss store.Store) error {
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7065,10 +7065,10 @@ func (s *TimerLayerRemoteClusterStore) Get(remoteClusterId string) (*model.Remot
|
|||||||
return result, err
|
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()
|
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)
|
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||||
if s.Root.Metrics != nil {
|
if s.Root.Metrics != nil {
|
||||||
|
|||||||
@@ -93,6 +93,14 @@ type Params struct {
|
|||||||
FilterHasMember string
|
FilterHasMember string
|
||||||
IncludeChannelMemberCount string
|
IncludeChannelMemberCount string
|
||||||
OutgoingOAuthConnectionID string
|
OutgoingOAuthConnectionID string
|
||||||
|
ExcludeOffline bool
|
||||||
|
InChannel string
|
||||||
|
NotInChannel string
|
||||||
|
Topic string
|
||||||
|
CreatorId string
|
||||||
|
OnlyConfirmed bool
|
||||||
|
OnlyPlugins bool
|
||||||
|
ExcludePlugins bool
|
||||||
|
|
||||||
//Bookmarks
|
//Bookmarks
|
||||||
ChannelBookmarkId string
|
ChannelBookmarkId string
|
||||||
@@ -126,7 +134,11 @@ func ParamsFromRequest(r *http.Request) *Params {
|
|||||||
params.FileId = props["file_id"]
|
params.FileId = props["file_id"]
|
||||||
params.Filename = query.Get("filename")
|
params.Filename = query.Get("filename")
|
||||||
params.UploadId = props["upload_id"]
|
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.CommandId = props["command_id"]
|
||||||
params.HookId = props["hook_id"]
|
params.HookId = props["hook_id"]
|
||||||
params.ReportId = props["report_id"]
|
params.ReportId = props["report_id"]
|
||||||
@@ -150,6 +162,14 @@ func ParamsFromRequest(r *http.Request) *Params {
|
|||||||
params.RemoteId = props["remote_id"]
|
params.RemoteId = props["remote_id"]
|
||||||
params.InvoiceId = props["invoice_id"]
|
params.InvoiceId = props["invoice_id"]
|
||||||
params.OutgoingOAuthConnectionID = props["outgoing_oauth_connection_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.ChannelBookmarkId = props["bookmark_id"]
|
||||||
params.Scope = query.Get("scope")
|
params.Scope = query.Get("scope")
|
||||||
|
|
||||||
|
|||||||
@@ -1281,10 +1281,6 @@
|
|||||||
"id": "api.command_remote.hint",
|
"id": "api.command_remote.hint",
|
||||||
"translation": "[action]"
|
"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",
|
"id": "api.command_remote.invitation.help",
|
||||||
"translation": "Invitation from secure connection"
|
"translation": "Invitation from secure connection"
|
||||||
@@ -2144,6 +2140,10 @@
|
|||||||
"id": "api.getUsersForReporting.invalid_team_filter",
|
"id": "api.getUsersForReporting.invalid_team_filter",
|
||||||
"translation": "Invalid team id provided."
|
"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",
|
"id": "api.image.get.app_error",
|
||||||
"translation": "Requested image url cannot be parsed."
|
"translation": "Requested image url cannot be parsed."
|
||||||
@@ -2726,14 +2726,34 @@
|
|||||||
"id": "api.reaction.save_reaction.user_id.app_error",
|
"id": "api.reaction.save_reaction.user_id.app_error",
|
||||||
"translation": "You cannot save reaction for the other user."
|
"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",
|
"id": "api.remote_cluster.delete.app_error",
|
||||||
"translation": "We encountered an error deleting the secure connection."
|
"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",
|
"id": "api.remote_cluster.get.app_error",
|
||||||
"translation": "We encountered an error retrieving a secure connection."
|
"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",
|
"id": "api.remote_cluster.invalid_id.app_error",
|
||||||
"translation": "Invalid id."
|
"translation": "Invalid id."
|
||||||
@@ -2742,6 +2762,10 @@
|
|||||||
"id": "api.remote_cluster.invalid_topic.app_error",
|
"id": "api.remote_cluster.invalid_topic.app_error",
|
||||||
"translation": "Invalid topic."
|
"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",
|
"id": "api.remote_cluster.save.app_error",
|
||||||
"translation": "We encountered an error saving the secure connection."
|
"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.
|
// 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{
|
rc := &model.RemoteCluster{
|
||||||
RemoteId: invite.RemoteId,
|
RemoteId: invite.RemoteId,
|
||||||
Name: name,
|
Name: name,
|
||||||
@@ -29,7 +29,7 @@ func (rcs *Service) AcceptInvitation(invite *model.RemoteClusterInvite, name str
|
|||||||
}
|
}
|
||||||
|
|
||||||
// confirm the invitation with the originating site
|
// confirm the invitation with the originating site
|
||||||
frame, err := makeConfirmFrame(rcSaved, teamId, siteURL)
|
frame, err := makeConfirmFrame(rcSaved, siteURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -60,7 +60,7 @@ func (rcs *Service) AcceptInvitation(invite *model.RemoteClusterInvite, name str
|
|||||||
return rcSaved, nil
|
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{
|
confirm := model.RemoteClusterInvite{
|
||||||
RemoteId: rc.RemoteId,
|
RemoteId: rc.RemoteId,
|
||||||
SiteURL: siteURL,
|
SiteURL: siteURL,
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ func (ms *mockServer) GetStore() store.Store {
|
|||||||
|
|
||||||
remoteClusterStoreMock := &mocks.RemoteClusterStore{}
|
remoteClusterStoreMock := &mocks.RemoteClusterStore{}
|
||||||
remoteClusterStoreMock.On("GetByTopic", "share").Return(ms.remotes, nil)
|
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)
|
remoteClusterStoreMock.On("SetLastPingAt", anyId).Return(nil)
|
||||||
|
|
||||||
userStoreMock := &mocks.UserStore{}
|
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.
|
// pingAllNow emits a ping to all remotes immediately without waiting for next ping loop.
|
||||||
func (rcs *Service) pingAllNow(filter model.RemoteClusterQueryFilter) {
|
func (rcs *Service) pingAllNow(filter model.RemoteClusterQueryFilter) {
|
||||||
// get all remotes, including any previously offline.
|
// 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 {
|
if err != nil {
|
||||||
rcs.server.Log().Log(mlog.LvlRemoteClusterServiceError, "Ping all remote clusters failed (could not get list of remotes)", mlog.Err(err))
|
rcs.server.Log().Log(mlog.LvlRemoteClusterServiceError, "Ping all remote clusters failed (could not get list of remotes)", mlog.Err(err))
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ func (rcs *Service) BroadcastMsg(ctx context.Context, msg model.RemoteClusterMsg
|
|||||||
filter := model.RemoteClusterQueryFilter{
|
filter := model.RemoteClusterQueryFilter{
|
||||||
Topic: msg.Topic,
|
Topic: msg.Topic,
|
||||||
}
|
}
|
||||||
list, err := rcs.server.GetStore().RemoteCluster().GetAll(filter)
|
list, err := rcs.server.GetStore().RemoteCluster().GetAll(0, 999999, filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ type RemoteClusterServiceIFace interface {
|
|||||||
SendMsg(ctx context.Context, msg model.RemoteClusterMsg, rc *model.RemoteCluster, f SendMsgResultFunc) error
|
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
|
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
|
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
|
ReceiveIncomingMsg(rc *model.RemoteCluster, msg model.RemoteClusterMsg) Response
|
||||||
ReceiveInviteConfirmation(invite model.RemoteClusterInvite) (*model.RemoteCluster, error)
|
ReceiveInviteConfirmation(invite model.RemoteClusterInvite) (*model.RemoteCluster, error)
|
||||||
PingNow(rc *model.RemoteCluster)
|
PingNow(rc *model.RemoteCluster)
|
||||||
|
|||||||
@@ -258,7 +258,7 @@ func (scs *Service) processTask(task syncTask) error {
|
|||||||
InChannel: task.channelID,
|
InChannel: task.channelID,
|
||||||
OnlyConfirmed: true,
|
OnlyConfirmed: true,
|
||||||
}
|
}
|
||||||
remotes, err := scs.server.GetStore().RemoteCluster().GetAll(filter)
|
remotes, err := scs.server.GetStore().RemoteCluster().GetAll(0, 999999, filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -270,7 +270,7 @@ func (scs *Service) processTask(task syncTask) error {
|
|||||||
filter = model.RemoteClusterQueryFilter{
|
filter = model.RemoteClusterQueryFilter{
|
||||||
RequireOptions: model.BitflagOptionAutoInvited,
|
RequireOptions: model.BitflagOptionAutoInvited,
|
||||||
}
|
}
|
||||||
remotesAutoInvited, err := scs.server.GetStore().RemoteCluster().GetAll(filter)
|
remotesAutoInvited, err := scs.server.GetStore().RemoteCluster().GetAll(0, 999999, filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -568,6 +568,10 @@ func (c *Client4) exportRoute(name string) string {
|
|||||||
return fmt.Sprintf(c.exportsRoute()+"/%v", name)
|
return fmt.Sprintf(c.exportsRoute()+"/%v", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client4) remoteClusterRoute() string {
|
||||||
|
return "/remotecluster"
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client4) sharedChannelsRoute() string {
|
func (c *Client4) sharedChannelsRoute() string {
|
||||||
return "/sharedchannels"
|
return "/sharedchannels"
|
||||||
}
|
}
|
||||||
@@ -8698,6 +8702,154 @@ func (c *Client4) GetRemoteClusterInfo(ctx context.Context, remoteID string) (Re
|
|||||||
return rci, BuildResponse(r), nil
|
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) {
|
func (c *Client4) GetAncillaryPermissions(ctx context.Context, subsectionPermissions []string) ([]string, *Response, error) {
|
||||||
var returnedPermissions []string
|
var returnedPermissions []string
|
||||||
url := fmt.Sprintf("%s/ancillary?subsection_permissions=%s", c.permissionsRoute(), strings.Join(subsectionPermissions, ","))
|
url := fmt.Sprintf("%s/ancillary?subsection_permissions=%s", c.permissionsRoute(), strings.Join(subsectionPermissions, ","))
|
||||||
|
|||||||
@@ -126,6 +126,37 @@ func (rc *RemoteCluster) IsValid() *AppError {
|
|||||||
return nil
|
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 {
|
func newIDFromBytes(b []byte) string {
|
||||||
hash := md5.New()
|
hash := md5.New()
|
||||||
_, _ = hash.Write(b)
|
_, _ = hash.Write(b)
|
||||||
@@ -393,6 +424,13 @@ func (rci *RemoteClusterInvite) Decrypt(encrypted []byte, password string) error
|
|||||||
return json.Unmarshal(plain, &rci)
|
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
|
// RemoteClusterQueryFilter provides filter criteria for RemoteClusterStore.GetAll
|
||||||
type RemoteClusterQueryFilter struct {
|
type RemoteClusterQueryFilter struct {
|
||||||
ExcludeOffline bool
|
ExcludeOffline bool
|
||||||
@@ -403,5 +441,6 @@ type RemoteClusterQueryFilter struct {
|
|||||||
OnlyConfirmed bool
|
OnlyConfirmed bool
|
||||||
PluginID string
|
PluginID string
|
||||||
OnlyPlugins bool
|
OnlyPlugins bool
|
||||||
|
ExcludePlugins bool
|
||||||
RequireOptions Bitmask
|
RequireOptions Bitmask
|
||||||
}
|
}
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user