Allows creating new remote clusters without providing a password (#27864)

* Allows creating new remote clusters without providing a password

If the endpoint receives a request with no password, it will generate
one internally and return it in the response, so the frotend can show
it to the user.

* Use a random string instead of a UUID for the generated password

* Update function name to avoid CString reference and adds assertion

* Update server/channels/utils/textgeneration.go

Co-authored-by: Eva Sarafianou <eva.sarafianou@gmail.com>

* Extends the charset

---------

Co-authored-by: Eva Sarafianou <eva.sarafianou@gmail.com>
Этот коммит содержится в:
Miguel de la Cruz
2024-08-08 12:18:21 +02:00
коммит произвёл GitHub
родитель 06d8c857ea
Коммит eec9a4742a
5 изменённых файлов: 69 добавлений и 20 удалений

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

@@ -81,7 +81,6 @@
type: object type: object
required: required:
- name - name
- password
properties: properties:
name: name:
type: string type: string
@@ -89,7 +88,10 @@
type: string type: string
password: password:
type: string type: string
description: The password to use in the invite code. description: |
The password to use in the invite code. If empty,
the server will generate one and it will be part
of the response
responses: responses:
"201": "201":
description: Remote cluster creation successful description: Remote cluster creation successful
@@ -103,6 +105,11 @@
invite: invite:
type: string type: string
description: The encrypted invite for the newly created remote cluster description: The encrypted invite for the newly created remote cluster
password:
type: string
description: |
The password generated by the server if none was
sent on the create request
"401": "401":
$ref: "#/components/responses/Unauthorized" $ref: "#/components/responses/Unauthorized"
"403": "403":

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

@@ -13,6 +13,7 @@ import (
"github.com/mattermost/mattermost/server/public/shared/mlog" "github.com/mattermost/mattermost/server/public/shared/mlog"
"github.com/mattermost/mattermost/server/v8/channels/app" "github.com/mattermost/mattermost/server/v8/channels/app"
"github.com/mattermost/mattermost/server/v8/channels/audit" "github.com/mattermost/mattermost/server/v8/channels/audit"
"github.com/mattermost/mattermost/server/v8/channels/utils"
"github.com/mattermost/mattermost/server/v8/platform/services/remotecluster" "github.com/mattermost/mattermost/server/v8/platform/services/remotecluster"
) )
@@ -366,11 +367,6 @@ func createRemoteCluster(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
if rcWithTeamAndPassword.Password == "" {
c.SetInvalidParam("password")
return
}
url := c.App.GetSiteURL() url := c.App.GetSiteURL()
if url == "" { if url == "" {
c.Err = model.NewAppError("createRemoteCluster", "api.get_site_url_error", nil, "", http.StatusUnprocessableEntity) c.Err = model.NewAppError("createRemoteCluster", "api.get_site_url_error", nil, "", http.StatusUnprocessableEntity)
@@ -398,7 +394,12 @@ func createRemoteCluster(c *Context, w http.ResponseWriter, r *http.Request) {
} }
rcSaved.Sanitize() rcSaved.Sanitize()
inviteCode, iErr := c.App.CreateRemoteClusterInvite(rcSaved.RemoteId, url, rcSaved.Token, rcWithTeamAndPassword.Password) password := rcWithTeamAndPassword.Password
if password == "" {
password = utils.SecureRandString(16)
}
inviteCode, iErr := c.App.CreateRemoteClusterInvite(rcSaved.RemoteId, url, rcSaved.Token, password)
if iErr != nil { if iErr != nil {
c.Err = iErr c.Err = iErr
return return
@@ -408,7 +409,12 @@ func createRemoteCluster(c *Context, w http.ResponseWriter, r *http.Request) {
auditRec.AddEventResultState(rcSaved) auditRec.AddEventResultState(rcSaved)
auditRec.AddEventObjectType("remotecluster") auditRec.AddEventObjectType("remotecluster")
b, err := json.Marshal(model.RemoteClusterWithInvite{RemoteCluster: rcSaved, Invite: inviteCode}) resp := model.RemoteClusterWithInvite{RemoteCluster: rcSaved, Invite: inviteCode}
if rcWithTeamAndPassword.Password == "" {
resp.Password = password
}
b, err := json.Marshal(resp)
if err != nil { if err != nil {
c.Err = model.NewAppError("createRemoteCluster", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) c.Err = model.NewAppError("createRemoteCluster", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
return return

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

@@ -195,17 +195,35 @@ func TestCreateRemoteCluster(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.SiteURL = "http://localhost:8065" }) 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) { t.Run("Should generate a password if none is given", func(t *testing.T) {
// clean the password and check the response // clean the password and check the response
rcWithTeamAndPassword.Password = "" rcWithTeamNoPassword := &model.RemoteClusterWithPassword{
RemoteCluster: &model.RemoteCluster{
Name: "remotecluster-nopasswd",
SiteURL: "http://no-passwd.example.com",
Token: model.NewId(),
},
Password: "",
}
rcWithInvite, resp, err := th.SystemAdminClient.CreateRemoteCluster(context.Background(), rcWithTeamAndPassword) rcWithInvite, resp, err := th.SystemAdminClient.CreateRemoteCluster(context.Background(), rcWithTeamNoPassword)
CheckBadRequestStatus(t, resp) CheckCreatedStatus(t, resp)
require.Error(t, err) require.NoError(t, err)
require.Empty(t, rcWithInvite) require.NotZero(t, rcWithInvite.Invite)
// when the password is not provided, it is returned as part
// of the response
require.NotZero(t, rcWithInvite.Password)
require.Len(t, rcWithInvite.Password, 16)
// reset password for the next tests rc, appErr := th.App.GetRemoteCluster(rcWithInvite.RemoteCluster.RemoteId)
rcWithTeamAndPassword.Password = "mysupersecret" require.Nil(t, appErr)
require.Equal(t, rcWithTeamNoPassword.Name, rc.Name)
rci, appErr := th.App.DecryptRemoteClusterInvite(rcWithInvite.Invite, rcWithInvite.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)
}) })
t.Run("Should return a sanitized remote cluster and its invite", func(t *testing.T) { t.Run("Should return a sanitized remote cluster and its invite", func(t *testing.T) {
@@ -216,6 +234,9 @@ func TestCreateRemoteCluster(t *testing.T) {
require.NotZero(t, rcWithInvite.Invite) require.NotZero(t, rcWithInvite.Invite)
require.Zero(t, rcWithInvite.RemoteCluster.Token) require.Zero(t, rcWithInvite.RemoteCluster.Token)
require.Zero(t, rcWithInvite.RemoteCluster.RemoteToken) require.Zero(t, rcWithInvite.RemoteCluster.RemoteToken)
// when the password is provided as an input, is not returned
// by the endpoint
require.Zero(t, rcWithInvite.Password)
rc, appErr := th.App.GetRemoteCluster(rcWithInvite.RemoteCluster.RemoteId) rc, appErr := th.App.GetRemoteCluster(rcWithInvite.RemoteCluster.RemoteId)
require.Nil(t, appErr) require.Nil(t, appErr)

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

@@ -4,7 +4,9 @@
package utils package utils
import ( import (
"math/rand" crand "crypto/rand"
"math/big"
mrand "math/rand"
"strings" "strings"
) )
@@ -468,11 +470,23 @@ Up to and hey without pill that this squid alas brusque on inventoried and sprea
func RandString(l int, charset string) string { func RandString(l int, charset string) string {
ret := make([]byte, l) ret := make([]byte, l)
for i := 0; i < l; i++ { for i := 0; i < l; i++ {
ret[i] = charset[rand.Intn(len(charset))] ret[i] = charset[mrand.Intn(len(charset))]
} }
return string(ret) return string(ret)
} }
func SecureRandString(n int) string {
charset := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%&*0123456789"
var str strings.Builder
for i := 0; i < n; i++ {
num, _ := crand.Int(crand.Reader, big.NewInt(int64(len(charset))))
str.WriteString(string(charset[num.Int64()]))
}
return str.String()
}
// func RandomEmail(length Range, charset string) string { // func RandomEmail(length Range, charset string) string {
// emaillen := RandIntFromRange(length) // emaillen := RandIntFromRange(length)
// username := RandString(emaillen, charset) // username := RandString(emaillen, charset)
@@ -517,7 +531,7 @@ func RandomText(length Range, hashtags Range, mentions Range, users []string) st
// Shuffle the words // Shuffle the words
for i := range words { for i := range words {
j := rand.Intn(i + 1) j := mrand.Intn(i + 1)
words[i], words[j] = words[j], words[i] words[i], words[j] = words[j], words[i]
} }

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

@@ -155,6 +155,7 @@ type RemoteClusterWithPassword struct {
type RemoteClusterWithInvite struct { type RemoteClusterWithInvite struct {
RemoteCluster *RemoteCluster `json:"remote_cluster"` RemoteCluster *RemoteCluster `json:"remote_cluster"`
Invite string `json:"invite"` Invite string `json:"invite"`
Password string `json:"password,omitempty"`
} }
func newIDFromBytes(b []byte) string { func newIDFromBytes(b []byte) string {