Files
mostlymatter/app/slashcommands/command_remote.go
Ibrahim Serdar Acikgoz 717a4d04a9 Use any instead of interface{} (#20577)
* replace interface{} with any
2022-07-05 09:46:50 +03:00

300 строки
11 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package slashcommands
import (
"encoding/base64"
"errors"
"fmt"
"strings"
"github.com/mattermost/mattermost-server/v6/app"
"github.com/mattermost/mattermost-server/v6/app/request"
"github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/mattermost-server/v6/shared/i18n"
)
const (
AvailableRemoteActions = "create, accept, remove, status"
)
type RemoteProvider struct {
}
const (
CommandTriggerRemote = "secure-connection"
)
func init() {
app.RegisterCommandProvider(&RemoteProvider{})
}
func (rp *RemoteProvider) GetTrigger() string {
return CommandTriggerRemote
}
func (rp *RemoteProvider) GetCommand(a *app.App, T i18n.TranslateFunc) *model.Command {
remote := model.NewAutocompleteData(rp.GetTrigger(), "[action]", T("api.command_remote.remote_add_remove.help", map[string]any{"Actions": AvailableRemoteActions}))
create := model.NewAutocompleteData("create", "", T("api.command_remote.invite.help"))
create.AddNamedTextArgument("name", T("api.command_remote.name.help"), T("api.command_remote.name.hint"), "", true)
create.AddNamedTextArgument("displayname", T("api.command_remote.displayname.help"), T("api.command_remote.displayname.hint"), "", false)
create.AddNamedTextArgument("password", T("api.command_remote.invite_password.help"), T("api.command_remote.invite_password.hint"), "", true)
accept := model.NewAutocompleteData("accept", "", T("api.command_remote.accept.help"))
accept.AddNamedTextArgument("name", T("api.command_remote.name.help"), T("api.command_remote.name.hint"), "", true)
accept.AddNamedTextArgument("displayname", T("api.command_remote.displayname.help"), T("api.command_remote.displayname.hint"), "", false)
accept.AddNamedTextArgument("password", T("api.command_remote.invite_password.help"), T("api.command_remote.invite_password.hint"), "", true)
accept.AddNamedTextArgument("invite", T("api.command_remote.invitation.help"), T("api.command_remote.invitation.hint"), "", true)
remove := model.NewAutocompleteData("remove", "", T("api.command_remote.remove.help"))
remove.AddNamedDynamicListArgument("connectionID", T("api.command_remote.remove_remote_id.help"), "builtin:"+CommandTriggerRemote, true)
status := model.NewAutocompleteData("status", "", T("api.command_remote.status.help"))
remote.AddCommand(create)
remote.AddCommand(accept)
remote.AddCommand(remove)
remote.AddCommand(status)
return &model.Command{
Trigger: rp.GetTrigger(),
AutoComplete: true,
AutoCompleteDesc: T("api.command_remote.desc"),
AutoCompleteHint: T("api.command_remote.hint"),
DisplayName: T("api.command_remote.name"),
AutocompleteData: remote,
}
}
func (rp *RemoteProvider) DoCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) *model.CommandResponse {
if !a.HasPermissionTo(args.UserId, model.PermissionManageSecureConnections) {
return responsef(args.T("api.command_remote.permission_required", map[string]any{"Permission": "manage_secure_connections"}))
}
margs := parseNamedArgs(args.Command)
action, ok := margs[ActionKey]
if !ok {
return responsef(args.T("api.command_remote.missing_command", map[string]any{"Actions": AvailableRemoteActions}))
}
switch action {
case "create":
return rp.doCreate(a, args, margs)
case "accept":
return rp.doAccept(a, args, margs)
case "remove":
return rp.doRemove(a, args, margs)
case "status":
return rp.doStatus(a, args, margs)
}
return responsef(args.T("api.command_remote.unknown_action", map[string]any{"Action": action}))
}
func (rp *RemoteProvider) GetAutoCompleteListItems(a *app.App, commandArgs *model.CommandArgs, arg *model.AutocompleteArg, parsed, toBeParsed string) ([]model.AutocompleteListItem, error) {
if !a.HasPermissionTo(commandArgs.UserId, model.PermissionManageSecureConnections) {
return nil, errors.New("You require `manage_secure_connections` permission to manage secure connections.")
}
if arg.Name == "connectionID" && strings.Contains(parsed, " remove ") {
return getRemoteClusterAutocompleteListItems(a, true)
}
return nil, fmt.Errorf("`%s` is not a dynamic argument", arg.Name)
}
// doCreate creates and displays an encrypted invite that can be used by a remote site to establish a simple trust.
func (rp *RemoteProvider) doCreate(a *app.App, args *model.CommandArgs, margs map[string]string) *model.CommandResponse {
password := margs["password"]
if password == "" {
return responsef(args.T("api.command_remote.missing_empty", map[string]any{"Arg": "password"}))
}
name := margs["name"]
if name == "" {
return responsef(args.T("api.command_remote.missing_empty", map[string]any{"Arg": "name"}))
}
displayname := margs["displayname"]
if displayname == "" {
displayname = name
}
url := a.GetSiteURL()
if url == "" {
return responsef(args.T("api.command_remote.site_url_not_set"))
}
rc := &model.RemoteCluster{
Name: name,
DisplayName: displayname,
Token: model.NewId(),
CreatorId: args.UserId,
}
rcSaved, appErr := a.AddRemoteCluster(rc)
if appErr != nil {
return responsef(args.T("api.command_remote.add_remote.error", map[string]any{"Error": appErr.Error()}))
}
// Display the encrypted invitation
invite := &model.RemoteClusterInvite{
RemoteId: rcSaved.RemoteId,
RemoteTeamId: args.TeamId,
SiteURL: url,
Token: rcSaved.Token,
}
encrypted, err := invite.Encrypt(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}))
}
// doAccept accepts an invitation generated by a remote site.
func (rp *RemoteProvider) doAccept(a *app.App, args *model.CommandArgs, margs map[string]string) *model.CommandResponse {
password := margs["password"]
if password == "" {
return responsef(args.T("api.command_remote.missing_empty", map[string]any{"Arg": "password"}))
}
name := margs["name"]
if name == "" {
return responsef(args.T("api.command_remote.missing_empty", map[string]any{"Arg": "name"}))
}
displayname := margs["displayname"]
if displayname == "" {
displayname = name
}
blob := margs["invite"]
if blob == "" {
return responsef(args.T("api.command_remote.missing_empty", map[string]any{"Arg": "invite"}))
}
// 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()}))
}
rcs, _ := a.GetRemoteClusterService()
if rcs == nil {
return responsef(args.T("api.command_remote.service_not_enabled"))
}
url := a.GetSiteURL()
if url == "" {
return responsef(args.T("api.command_remote.site_url_not_set"))
}
rc, err := rcs.AcceptInvitation(invite, name, displayname, args.UserId, args.TeamId, url)
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", map[string]any{"SiteURL": rc.SiteURL}))
}
// doRemove removes a remote cluster from the database, effectively revoking the trust relationship.
func (rp *RemoteProvider) doRemove(a *app.App, args *model.CommandArgs, margs map[string]string) *model.CommandResponse {
id, ok := margs["connectionID"]
if !ok {
return responsef(args.T("api.command_remote.missing_empty", map[string]any{"Arg": "remoteId"}))
}
deleted, err := a.DeleteRemoteCluster(id)
if err != nil {
responsef(args.T("api.command_remote.remove_remote.error", map[string]any{"Error": err.Error()}))
}
result := "removed"
if !deleted {
result = "**NOT FOUND**"
}
return responsef("##### " + args.T("api.command_remote.cluster_removed", map[string]any{"RemoteId": id, "Result": result}))
}
// 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{})
if err != nil {
responsef(args.T("api.command_remote.fetch_status.error", map[string]any{"Error": err.Error()}))
}
if len(list) == 0 {
return responsef("** " + args.T("api.command_remote.remotes_not_found") + " **")
}
var sb strings.Builder
fmt.Fprintf(&sb, args.T("api.command_remote.remote_table_header")+" \n")
// | Secure Connection | Display name | ConnectionID | Site URL | Invite accepted | Online | Last ping |
fmt.Fprintf(&sb, "| :---- | :---- | :---- | :---- | :---- | :---- | :---- | \n")
for _, rc := range list {
accepted := formatBool(args.T, rc.SiteURL != "")
online := formatBool(args.T, isOnline(rc.LastPingAt))
lastPing := formatTimestamp(rc.LastPingAt)
fmt.Fprintf(&sb, "| %s | %s | %s | %s | %s | %s | %s |\n", rc.Name, rc.DisplayName, rc.RemoteId, rc.SiteURL, accepted, online, lastPing)
}
return responsef(sb.String())
}
func isOnline(lastPing int64) bool {
return lastPing > model.GetMillis()-model.RemoteOfflineAfterMillis
}
func getRemoteClusterAutocompleteListItems(a *app.App, includeOffline bool) ([]model.AutocompleteListItem, error) {
filter := model.RemoteClusterQueryFilter{
ExcludeOffline: !includeOffline,
}
clusters, err := a.GetAllRemoteClusters(filter)
if err != nil || len(clusters) == 0 {
return []model.AutocompleteListItem{}, nil
}
list := make([]model.AutocompleteListItem, 0, len(clusters))
for _, rc := range clusters {
item := model.AutocompleteListItem{
Item: rc.RemoteId,
HelpText: fmt.Sprintf("%s (%s)", rc.DisplayName, rc.SiteURL)}
list = append(list, item)
}
return list, nil
}
func getRemoteClusterAutocompleteListItemsNotInChannel(a *app.App, channelId string, includeOffline bool) ([]model.AutocompleteListItem, error) {
filter := model.RemoteClusterQueryFilter{
ExcludeOffline: !includeOffline,
NotInChannel: channelId,
}
all, err := a.GetAllRemoteClusters(filter)
if err != nil || len(all) == 0 {
return []model.AutocompleteListItem{}, nil
}
list := make([]model.AutocompleteListItem, 0, len(all))
for _, rc := range all {
item := model.AutocompleteListItem{
Item: rc.RemoteId,
HelpText: fmt.Sprintf("%s (%s)", rc.DisplayName, rc.SiteURL)}
list = append(list, item)
}
return list, nil
}