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>
Этот коммит содержится в:
Miguel de la Cruz
2024-07-04 10:35:26 +02:00
коммит произвёл GitHub
родитель cc5e87ae24
Коммит 809ad4f76d
29 изменённых файлов: 1702 добавлений и 77 удалений

Просмотреть файл

@@ -568,6 +568,10 @@ func (c *Client4) exportRoute(name string) string {
return fmt.Sprintf(c.exportsRoute()+"/%v", name)
}
func (c *Client4) remoteClusterRoute() string {
return "/remotecluster"
}
func (c *Client4) sharedChannelsRoute() string {
return "/sharedchannels"
}
@@ -8698,6 +8702,154 @@ func (c *Client4) GetRemoteClusterInfo(ctx context.Context, remoteID string) (Re
return rci, BuildResponse(r), nil
}
func (c *Client4) GetRemoteClusters(ctx context.Context, page, perPage int, filter RemoteClusterQueryFilter) ([]*RemoteCluster, *Response, error) {
v := url.Values{}
if page != 0 {
v.Set("page", fmt.Sprintf("%d", page))
}
if perPage != 0 {
v.Set("per_page", fmt.Sprintf("%d", perPage))
}
if filter.ExcludeOffline {
v.Set("exclude_offline", "true")
}
if filter.InChannel != "" {
v.Set("in_channel", filter.InChannel)
}
if filter.NotInChannel != "" {
v.Set("not_in_channel", filter.NotInChannel)
}
if filter.Topic != "" {
v.Set("topic", filter.Topic)
}
if filter.CreatorId != "" {
v.Set("creator_id", filter.CreatorId)
}
if filter.OnlyConfirmed {
v.Set("only_confirmed", "true")
}
if filter.PluginID != "" {
v.Set("plugin_id", filter.PluginID)
}
if filter.OnlyPlugins {
v.Set("only_plugins", "true")
}
if filter.ExcludePlugins {
v.Set("exclude_plugins", "true")
}
url := c.remoteClusterRoute()
if len(v) > 0 {
url += "?" + v.Encode()
}
r, err := c.DoAPIGet(ctx, url, "")
if err != nil {
return nil, BuildResponse(r), err
}
defer closeBody(r)
var rcs []*RemoteCluster
json.NewDecoder(r.Body).Decode(&rcs)
return rcs, BuildResponse(r), nil
}
func (c *Client4) CreateRemoteCluster(ctx context.Context, rcWithPassword *RemoteClusterWithPassword) (*RemoteClusterWithInvite, *Response, error) {
rcJSON, err := json.Marshal(rcWithPassword)
if err != nil {
return nil, nil, NewAppError("CreateRemoteCluster", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
r, err := c.DoAPIPost(ctx, c.remoteClusterRoute(), string(rcJSON))
if err != nil {
return nil, BuildResponse(r), err
}
defer closeBody(r)
var rcWithInvite RemoteClusterWithInvite
if err := json.NewDecoder(r.Body).Decode(&rcWithInvite); err != nil {
return nil, nil, NewAppError("CreateRemoteCluster", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
return &rcWithInvite, BuildResponse(r), nil
}
func (c *Client4) RemoteClusterAcceptInvite(ctx context.Context, rcAcceptInvite *RemoteClusterAcceptInvite) (*RemoteCluster, *Response, error) {
rcAcceptInviteJSON, err := json.Marshal(rcAcceptInvite)
if err != nil {
return nil, nil, NewAppError("RemoteClusterAcceptInvite", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
url := fmt.Sprintf("%s/accept_invite", c.remoteClusterRoute())
r, err := c.DoAPIPost(ctx, url, string(rcAcceptInviteJSON))
if err != nil {
return nil, BuildResponse(r), err
}
defer closeBody(r)
var rc RemoteCluster
if err := json.NewDecoder(r.Body).Decode(&rc); err != nil {
return nil, nil, NewAppError("RemoteClusterAcceptInvite", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
return &rc, BuildResponse(r), nil
}
func (c *Client4) GenerateRemoteClusterInvite(ctx context.Context, remoteClusterId, password string) (string, *Response, error) {
url := fmt.Sprintf("%s/%s/generate_invite", c.remoteClusterRoute(), remoteClusterId)
r, err := c.DoAPIPost(ctx, url, MapToJSON(map[string]string{"password": password}))
if err != nil {
return "", BuildResponse(r), err
}
defer closeBody(r)
b, err := io.ReadAll(r.Body)
if err != nil {
return "", nil, NewAppError("GenerateRemoteClusterInvite", "api.read_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
return string(b), BuildResponse(r), nil
}
func (c *Client4) GetRemoteCluster(ctx context.Context, remoteClusterId string) (*RemoteCluster, *Response, error) {
r, err := c.DoAPIGet(ctx, fmt.Sprintf("%s/%s", c.remoteClusterRoute(), remoteClusterId), "")
if err != nil {
return nil, BuildResponse(r), err
}
defer closeBody(r)
var rc *RemoteCluster
json.NewDecoder(r.Body).Decode(&rc)
return rc, BuildResponse(r), nil
}
func (c *Client4) PatchRemoteCluster(ctx context.Context, remoteClusterId string, patch *RemoteClusterPatch) (*RemoteCluster, *Response, error) {
patchJSON, err := json.Marshal(patch)
if err != nil {
return nil, nil, NewAppError("PatchRemoteCluster", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
url := fmt.Sprintf("%s/%s", c.remoteClusterRoute(), remoteClusterId)
r, err := c.DoAPIPatchBytes(ctx, url, patchJSON)
if err != nil {
return nil, BuildResponse(r), err
}
defer closeBody(r)
var rc RemoteCluster
if err := json.NewDecoder(r.Body).Decode(&rc); err != nil {
return nil, nil, NewAppError("PatchRemoteCluster", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
return &rc, BuildResponse(r), nil
}
func (c *Client4) DeleteRemoteCluster(ctx context.Context, remoteClusterId string) (*Response, error) {
r, err := c.DoAPIDelete(ctx, fmt.Sprintf("%s/%s", c.remoteClusterRoute(), remoteClusterId))
if err != nil {
return BuildResponse(r), err
}
defer closeBody(r)
return BuildResponse(r), nil
}
func (c *Client4) GetAncillaryPermissions(ctx context.Context, subsectionPermissions []string) ([]string, *Response, error) {
var returnedPermissions []string
url := fmt.Sprintf("%s/ancillary?subsection_permissions=%s", c.permissionsRoute(), strings.Join(subsectionPermissions, ","))

Просмотреть файл

@@ -126,6 +126,37 @@ func (rc *RemoteCluster) IsValid() *AppError {
return nil
}
func (rc *RemoteCluster) Sanitize() {
rc.Token = ""
rc.RemoteToken = ""
}
type RemoteClusterPatch struct {
DisplayName *string `json:"display_name"`
}
func (rcp *RemoteClusterPatch) Auditable() map[string]interface{} {
return map[string]interface{}{
"display_name": rcp.DisplayName,
}
}
func (rc *RemoteCluster) Patch(patch *RemoteClusterPatch) {
if patch.DisplayName != nil {
rc.DisplayName = *patch.DisplayName
}
}
type RemoteClusterWithPassword struct {
*RemoteCluster
Password string `json:"password"`
}
type RemoteClusterWithInvite struct {
RemoteCluster *RemoteCluster `json:"remote_cluster"`
Invite string `json:"invite"`
}
func newIDFromBytes(b []byte) string {
hash := md5.New()
_, _ = hash.Write(b)
@@ -393,6 +424,13 @@ func (rci *RemoteClusterInvite) Decrypt(encrypted []byte, password string) error
return json.Unmarshal(plain, &rci)
}
type RemoteClusterAcceptInvite struct {
Name string `json:"name"`
DisplayName string `json:"display_name"`
Invite string `json:"invite"`
Password string `json:"password"`
}
// RemoteClusterQueryFilter provides filter criteria for RemoteClusterStore.GetAll
type RemoteClusterQueryFilter struct {
ExcludeOffline bool
@@ -403,5 +441,6 @@ type RemoteClusterQueryFilter struct {
OnlyConfirmed bool
PluginID string
OnlyPlugins bool
ExcludePlugins bool
RequireOptions Bitmask
}