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>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
06d8c857ea
Коммит
eec9a4742a
@@ -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 {
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user