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
@@ -533,6 +533,7 @@ type AppIface interface {
|
||||
CreatePost(c request.CTX, post *model.Post, channel *model.Channel, triggerWebhooks, setOnline bool) (savedPost *model.Post, err *model.AppError)
|
||||
CreatePostAsUser(c request.CTX, post *model.Post, currentSessionId string, setOnline bool) (*model.Post, *model.AppError)
|
||||
CreatePostMissingChannel(c request.CTX, post *model.Post, triggerWebhooks bool, setOnline bool) (*model.Post, *model.AppError)
|
||||
CreateRemoteClusterInvite(remoteId, siteURL, token, password string) (string, *model.AppError)
|
||||
CreateRetentionPolicy(policy *model.RetentionPolicyWithTeamAndChannelIDs) (*model.RetentionPolicyWithTeamAndChannelCounts, *model.AppError)
|
||||
CreateRole(role *model.Role) (*model.Role, *model.AppError)
|
||||
CreateSamlRelayToken(extra string) (*model.Token, *model.AppError)
|
||||
@@ -555,6 +556,7 @@ type AppIface interface {
|
||||
DeactivateGuests(c request.CTX) *model.AppError
|
||||
DeactivateMfa(userID string) *model.AppError
|
||||
DeauthorizeOAuthAppForUser(c request.CTX, userID, appID string) *model.AppError
|
||||
DecryptRemoteClusterInvite(inviteCode, password string) (*model.RemoteClusterInvite, *model.AppError)
|
||||
DeleteAcknowledgementForPost(c request.CTX, postID, userID string) *model.AppError
|
||||
DeleteAllExpiredPluginKeys() *model.AppError
|
||||
DeleteAllKeysForPlugin(pluginID string) *model.AppError
|
||||
@@ -626,7 +628,7 @@ type AppIface interface {
|
||||
GetAllChannelsCount(c request.CTX, opts model.ChannelSearchOpts) (int64, *model.AppError)
|
||||
GetAllPrivateTeams() ([]*model.Team, *model.AppError)
|
||||
GetAllPublicTeams() ([]*model.Team, *model.AppError)
|
||||
GetAllRemoteClusters(filter model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, *model.AppError)
|
||||
GetAllRemoteClusters(page, perPage int, filter model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, *model.AppError)
|
||||
GetAllRoles() ([]*model.Role, *model.AppError)
|
||||
GetAllTeams() ([]*model.Team, *model.AppError)
|
||||
GetAllTeamsPage(offset int, limit int, opts *model.TeamSearch) ([]*model.Team, *model.AppError)
|
||||
@@ -973,6 +975,7 @@ type AppIface interface {
|
||||
PatchChannel(c request.CTX, channel *model.Channel, patch *model.ChannelPatch, userID string) (*model.Channel, *model.AppError)
|
||||
PatchChannelMembersNotifyProps(c request.CTX, members []*model.ChannelMemberIdentifier, notifyProps map[string]string) ([]*model.ChannelMember, *model.AppError)
|
||||
PatchPost(c request.CTX, postID string, patch *model.PostPatch) (*model.Post, *model.AppError)
|
||||
PatchRemoteCluster(rcId string, patch *model.RemoteClusterPatch) (*model.RemoteCluster, *model.AppError)
|
||||
PatchRetentionPolicy(patch *model.RetentionPolicyWithTeamAndChannelIDs) (*model.RetentionPolicyWithTeamAndChannelCounts, *model.AppError)
|
||||
PatchRole(role *model.Role, patch *model.RolePatch) (*model.Role, *model.AppError)
|
||||
PatchScheme(scheme *model.Scheme, patch *model.SchemePatch) (*model.Scheme, *model.AppError)
|
||||
|
||||
@@ -2544,6 +2544,28 @@ func (a *OpenTracingAppLayer) CreatePostMissingChannel(c request.CTX, post *mode
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) CreateRemoteClusterInvite(remoteId string, siteURL string, token string, password string) (string, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.CreateRemoteClusterInvite")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store().SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store().SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1 := a.app.CreateRemoteClusterInvite(remoteId, siteURL, token, password)
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) CreateRetentionPolicy(policy *model.RetentionPolicyWithTeamAndChannelIDs) (*model.RetentionPolicyWithTeamAndChannelCounts, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.CreateRetentionPolicy")
|
||||
@@ -3050,6 +3072,28 @@ func (a *OpenTracingAppLayer) DeauthorizeOAuthAppForUser(c request.CTX, userID s
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) DecryptRemoteClusterInvite(inviteCode string, password string) (*model.RemoteClusterInvite, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.DecryptRemoteClusterInvite")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store().SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store().SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1 := a.app.DecryptRemoteClusterInvite(inviteCode, password)
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) DefaultChannelNames(c request.CTX) []string {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.DefaultChannelNames")
|
||||
@@ -5023,7 +5067,7 @@ func (a *OpenTracingAppLayer) GetAllPublicTeams() ([]*model.Team, *model.AppErro
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) GetAllRemoteClusters(filter model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, *model.AppError) {
|
||||
func (a *OpenTracingAppLayer) GetAllRemoteClusters(page int, perPage int, filter model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetAllRemoteClusters")
|
||||
|
||||
@@ -5035,7 +5079,7 @@ func (a *OpenTracingAppLayer) GetAllRemoteClusters(filter model.RemoteClusterQue
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1 := a.app.GetAllRemoteClusters(filter)
|
||||
resultVar0, resultVar1 := a.app.GetAllRemoteClusters(page, perPage, filter)
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
@@ -13376,6 +13420,28 @@ func (a *OpenTracingAppLayer) PatchPost(c request.CTX, postID string, patch *mod
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) PatchRemoteCluster(rcId string, patch *model.RemoteClusterPatch) (*model.RemoteCluster, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.PatchRemoteCluster")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store().SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store().SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1 := a.app.PatchRemoteCluster(rcId, patch)
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) PatchRetentionPolicy(patch *model.RetentionPolicyWithTeamAndChannelIDs) (*model.RetentionPolicyWithTeamAndChannelCounts, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.PatchRetentionPolicy")
|
||||
|
||||
@@ -88,7 +88,7 @@ func handleContentSync(ps *PlatformService, syncService SharedChannelServiceIFac
|
||||
OnlyConfirmed: true,
|
||||
RequireOptions: model.BitflagOptionAutoShareDMs,
|
||||
}
|
||||
remotes, err := ps.Store.RemoteCluster().GetAll(filter) // empty list returned if none found, no error
|
||||
remotes, err := ps.Store.RemoteCluster().GetAll(0, 999999, filter) // empty list returned if none found, no error
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot fetch remote clusters: %w", err)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ package app
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@@ -100,6 +101,22 @@ func (a *App) AddRemoteCluster(rc *model.RemoteCluster) (*model.RemoteCluster, *
|
||||
return rc, nil
|
||||
}
|
||||
|
||||
func (a *App) PatchRemoteCluster(rcId string, patch *model.RemoteClusterPatch) (*model.RemoteCluster, *model.AppError) {
|
||||
rc, err := a.GetRemoteCluster(rcId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rc.Patch(patch)
|
||||
|
||||
updatedRC, err := a.UpdateRemoteCluster(rc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return updatedRC, nil
|
||||
}
|
||||
|
||||
func (a *App) UpdateRemoteCluster(rc *model.RemoteCluster) (*model.RemoteCluster, *model.AppError) {
|
||||
rc, err := a.Srv().Store().RemoteCluster().Update(rc)
|
||||
if err != nil {
|
||||
@@ -123,13 +140,18 @@ func (a *App) DeleteRemoteCluster(remoteClusterId string) (bool, *model.AppError
|
||||
func (a *App) GetRemoteCluster(remoteClusterId string) (*model.RemoteCluster, *model.AppError) {
|
||||
rc, err := a.Srv().Store().RemoteCluster().Get(remoteClusterId)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("GetRemoteCluster", "api.remote_cluster.get.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
switch {
|
||||
case errors.Is(err, sql.ErrNoRows):
|
||||
return nil, model.NewAppError("GetRemoteCluster", "api.remote_cluster.get.not_found", nil, "", http.StatusNotFound).Wrap(err)
|
||||
default:
|
||||
return nil, model.NewAppError("GetRemoteCluster", "api.remote_cluster.get.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
}
|
||||
return rc, nil
|
||||
}
|
||||
|
||||
func (a *App) GetAllRemoteClusters(filter model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, *model.AppError) {
|
||||
list, err := a.Srv().Store().RemoteCluster().GetAll(filter)
|
||||
func (a *App) GetAllRemoteClusters(page, perPage int, filter model.RemoteClusterQueryFilter) ([]*model.RemoteCluster, *model.AppError) {
|
||||
list, err := a.Srv().Store().RemoteCluster().GetAll(page*perPage, perPage, filter)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("GetAllRemoteClusters", "api.remote_cluster.get.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
@@ -159,3 +181,32 @@ func (a *App) GetRemoteClusterService() (remotecluster.RemoteClusterServiceIFace
|
||||
}
|
||||
return service, nil
|
||||
}
|
||||
|
||||
func (a *App) CreateRemoteClusterInvite(remoteId, siteURL, token, password string) (string, *model.AppError) {
|
||||
invite := &model.RemoteClusterInvite{
|
||||
RemoteId: remoteId,
|
||||
SiteURL: siteURL,
|
||||
Token: token,
|
||||
}
|
||||
|
||||
encrypted, err := invite.Encrypt(password)
|
||||
if err != nil {
|
||||
return "", model.NewAppError("CreateRemoteClusterInvite", "api.remote_cluster.encrypt_invite_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
return base64.URLEncoding.EncodeToString(encrypted), nil
|
||||
}
|
||||
|
||||
func (a *App) DecryptRemoteClusterInvite(inviteCode, password string) (*model.RemoteClusterInvite, *model.AppError) {
|
||||
decoded, err := base64.URLEncoding.DecodeString(inviteCode)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("DecryptRemoteClusterInvite", "api.remote_cluster.base64_decode_error", nil, "", http.StatusBadRequest).Wrap(err)
|
||||
}
|
||||
|
||||
invite := &model.RemoteClusterInvite{}
|
||||
if dErr := invite.Decrypt(decoded, password); dErr != nil {
|
||||
return nil, model.NewAppError("DecryptRemoteClusterInvite", "api.remote_cluster.invite_decrypt_error", nil, "", http.StatusBadRequest).Wrap(dErr)
|
||||
}
|
||||
|
||||
return invite, nil
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
package slashcommands
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
@@ -144,19 +143,13 @@ func (rp *RemoteProvider) doCreate(a *app.App, args *model.CommandArgs, margs ma
|
||||
}
|
||||
|
||||
// Display the encrypted invitation
|
||||
invite := &model.RemoteClusterInvite{
|
||||
RemoteId: rcSaved.RemoteId,
|
||||
SiteURL: url,
|
||||
Token: rcSaved.Token,
|
||||
}
|
||||
encrypted, err := invite.Encrypt(password)
|
||||
inviteCode, err := a.CreateRemoteClusterInvite(rcSaved.RemoteId, url, rcSaved.Token, password)
|
||||
if err != nil {
|
||||
return responsef(args.T("api.command_remote.encrypt_invitation.error", map[string]any{"Error": err.Error()}))
|
||||
}
|
||||
encoded := base64.URLEncoding.EncodeToString(encrypted)
|
||||
|
||||
return responsef("##### " + args.T("api.command_remote.invitation_created") + "\n" +
|
||||
args.T("api.command_remote.invite_summary", map[string]any{"Command": "/secure-connection accept", "Invitation": encoded, "SiteURL": invite.SiteURL}))
|
||||
args.T("api.command_remote.invite_summary", map[string]any{"Command": "/secure-connection accept", "Invitation": inviteCode, "SiteURL": url}))
|
||||
}
|
||||
|
||||
// doAccept accepts an invitation generated by a remote site.
|
||||
@@ -182,14 +175,9 @@ func (rp *RemoteProvider) doAccept(a *app.App, args *model.CommandArgs, margs ma
|
||||
}
|
||||
|
||||
// invite is encoded as base64 and encrypted
|
||||
decoded, err := base64.URLEncoding.DecodeString(blob)
|
||||
if err != nil {
|
||||
return responsef(args.T("api.command_remote.decode_invitation.error", map[string]any{"Error": err.Error()}))
|
||||
}
|
||||
invite := &model.RemoteClusterInvite{}
|
||||
err = invite.Decrypt(decoded, password)
|
||||
if err != nil {
|
||||
return responsef(args.T("api.command_remote.incorrect_password.error", map[string]any{"Error": err.Error()}))
|
||||
invite, dErr := a.DecryptRemoteClusterInvite(blob, password)
|
||||
if dErr != nil {
|
||||
return responsef(args.T("api.command_remote.decode_invitation.error", map[string]any{"Error": dErr.Error()}))
|
||||
}
|
||||
|
||||
rcs, _ := a.GetRemoteClusterService()
|
||||
@@ -202,7 +190,7 @@ func (rp *RemoteProvider) doAccept(a *app.App, args *model.CommandArgs, margs ma
|
||||
return responsef(args.T("api.command_remote.site_url_not_set"))
|
||||
}
|
||||
|
||||
rc, err := rcs.AcceptInvitation(invite, name, displayname, args.UserId, args.TeamId, url)
|
||||
rc, err := rcs.AcceptInvitation(invite, name, displayname, args.UserId, url)
|
||||
if err != nil {
|
||||
return responsef(args.T("api.command_remote.accept_invitation.error", map[string]any{"Error": err.Error()}))
|
||||
}
|
||||
@@ -231,7 +219,7 @@ func (rp *RemoteProvider) doRemove(a *app.App, args *model.CommandArgs, margs ma
|
||||
|
||||
// doStatus displays connection status for all remote clusters.
|
||||
func (rp *RemoteProvider) doStatus(a *app.App, args *model.CommandArgs, _ map[string]string) *model.CommandResponse {
|
||||
list, err := a.GetAllRemoteClusters(model.RemoteClusterQueryFilter{})
|
||||
list, err := a.GetAllRemoteClusters(0, 999999, model.RemoteClusterQueryFilter{})
|
||||
if err != nil {
|
||||
responsef(args.T("api.command_remote.fetch_status.error", map[string]any{"Error": err.Error()}))
|
||||
}
|
||||
@@ -263,7 +251,7 @@ func getRemoteClusterAutocompleteListItems(a *app.App, includeOffline bool) ([]m
|
||||
filter := model.RemoteClusterQueryFilter{
|
||||
ExcludeOffline: !includeOffline,
|
||||
}
|
||||
clusters, err := a.GetAllRemoteClusters(filter)
|
||||
clusters, err := a.GetAllRemoteClusters(0, 999999, filter)
|
||||
if err != nil || len(clusters) == 0 {
|
||||
return []model.AutocompleteListItem{}, nil
|
||||
}
|
||||
@@ -284,7 +272,7 @@ func getRemoteClusterAutocompleteListItemsNotInChannel(a *app.App, channelID str
|
||||
ExcludeOffline: !includeOffline,
|
||||
NotInChannel: channelID,
|
||||
}
|
||||
all, err := a.GetAllRemoteClusters(filter)
|
||||
all, err := a.GetAllRemoteClusters(0, 999999, filter)
|
||||
if err != nil || len(all) == 0 {
|
||||
return []model.AutocompleteListItem{}, nil
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user