MM-9779: Incorporate a Token into the invitations system (#8604)
* Incorporate a Token into the invitations system * Adding unit tests * Fixing some api4 client tests * Removing unnecesary hash validation * Change the Hash concept on invitations with tokenId * Not send invitation if it wasn't able to create the Token * Fixing some naming problems * Changing the hash query params received from the client side * Removed unneded data param in the token usage
Этот коммит содержится в:
коммит произвёл
Christopher Speller
родитель
b13a228b04
Коммит
0910eae31d
@@ -376,15 +376,14 @@ func addTeamMember(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func addUserToTeamFromInvite(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
hash := r.URL.Query().Get("hash")
|
||||
data := r.URL.Query().Get("data")
|
||||
tokenId := r.URL.Query().Get("token")
|
||||
inviteId := r.URL.Query().Get("invite_id")
|
||||
|
||||
var member *model.TeamMember
|
||||
var err *model.AppError
|
||||
|
||||
if len(hash) > 0 && len(data) > 0 {
|
||||
member, err = c.App.AddTeamMemberByHash(c.Session.UserId, hash, data)
|
||||
if len(tokenId) > 0 {
|
||||
member, err = c.App.AddTeamMemberByToken(c.Session.UserId, tokenId)
|
||||
} else if len(inviteId) > 0 {
|
||||
member, err = c.App.AddTeamMemberByInviteId(inviteId, c.Session.UserId)
|
||||
} else {
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
"encoding/base64"
|
||||
|
||||
"github.com/mattermost/mattermost-server/app"
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -1361,17 +1362,16 @@ func TestAddTeamMember(t *testing.T) {
|
||||
_, resp = Client.AddTeamMember(team.Id, otherUser.Id)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
// by hash and data
|
||||
// by token
|
||||
Client.Login(otherUser.Email, otherUser.Password)
|
||||
|
||||
dataObject := make(map[string]string)
|
||||
dataObject["time"] = fmt.Sprintf("%v", model.GetMillis())
|
||||
dataObject["id"] = team.Id
|
||||
token := model.NewToken(
|
||||
app.TOKEN_TYPE_TEAM_INVITATION,
|
||||
model.MapToJson(map[string]string{"teamId": team.Id}),
|
||||
)
|
||||
<-th.App.Srv.Store.Token().Save(token)
|
||||
|
||||
data := model.MapToJson(dataObject)
|
||||
hashed := utils.HashSha256(fmt.Sprintf("%v:%v", data, th.App.Config().EmailSettings.InviteSalt))
|
||||
|
||||
tm, resp = Client.AddTeamMemberFromInvite(hashed, data, "")
|
||||
tm, resp = Client.AddTeamMemberFromInvite(token.Token, "")
|
||||
CheckNoError(t, resp)
|
||||
|
||||
if tm == nil {
|
||||
@@ -1386,36 +1386,42 @@ func TestAddTeamMember(t *testing.T) {
|
||||
t.Fatal("team ids should have matched")
|
||||
}
|
||||
|
||||
tm, resp = Client.AddTeamMemberFromInvite("junk", data, "")
|
||||
if result := <-th.App.Srv.Store.Token().GetByToken(token.Token); result.Err == nil {
|
||||
t.Fatal("The token must be deleted after be used")
|
||||
}
|
||||
|
||||
tm, resp = Client.AddTeamMemberFromInvite("junk", "")
|
||||
CheckBadRequestStatus(t, resp)
|
||||
|
||||
if tm != nil {
|
||||
t.Fatal("should have not returned team member")
|
||||
}
|
||||
|
||||
_, resp = Client.AddTeamMemberFromInvite(hashed, "junk", "")
|
||||
CheckBadRequestStatus(t, resp)
|
||||
// expired token of more than 50 hours
|
||||
token = model.NewToken(app.TOKEN_TYPE_TEAM_INVITATION, "")
|
||||
token.CreateAt = model.GetMillis() - 1000*60*60*50
|
||||
<-th.App.Srv.Store.Token().Save(token)
|
||||
|
||||
// expired data of more than 50 hours
|
||||
dataObject["time"] = fmt.Sprintf("%v", model.GetMillis()-1000*60*60*50)
|
||||
data = model.MapToJson(dataObject)
|
||||
hashed = utils.HashSha256(fmt.Sprintf("%v:%v", data, th.App.Config().EmailSettings.InviteSalt))
|
||||
|
||||
tm, resp = Client.AddTeamMemberFromInvite(hashed, data, "")
|
||||
tm, resp = Client.AddTeamMemberFromInvite(token.Token, "")
|
||||
CheckBadRequestStatus(t, resp)
|
||||
th.App.DeleteToken(token)
|
||||
|
||||
// invalid team id
|
||||
dataObject["id"] = GenerateTestId()
|
||||
data = model.MapToJson(dataObject)
|
||||
hashed = utils.HashSha256(fmt.Sprintf("%v:%v", data, th.App.Config().EmailSettings.InviteSalt))
|
||||
testId := GenerateTestId()
|
||||
token = model.NewToken(
|
||||
app.TOKEN_TYPE_TEAM_INVITATION,
|
||||
model.MapToJson(map[string]string{"teamId": testId}),
|
||||
)
|
||||
<-th.App.Srv.Store.Token().Save(token)
|
||||
|
||||
tm, resp = Client.AddTeamMemberFromInvite(hashed, data, "")
|
||||
CheckBadRequestStatus(t, resp)
|
||||
tm, resp = Client.AddTeamMemberFromInvite(token.Token, "")
|
||||
CheckNotFoundStatus(t, resp)
|
||||
th.App.DeleteToken(token)
|
||||
|
||||
// by invite_id
|
||||
Client.Login(otherUser.Email, otherUser.Password)
|
||||
|
||||
tm, resp = Client.AddTeamMemberFromInvite("", "", team.InviteId)
|
||||
tm, resp = Client.AddTeamMemberFromInvite("", team.InviteId)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
if tm == nil {
|
||||
@@ -1430,7 +1436,7 @@ func TestAddTeamMember(t *testing.T) {
|
||||
t.Fatal("team ids should have matched")
|
||||
}
|
||||
|
||||
tm, resp = Client.AddTeamMemberFromInvite("", "", "junk")
|
||||
tm, resp = Client.AddTeamMemberFromInvite("", "junk")
|
||||
CheckNotFoundStatus(t, resp)
|
||||
|
||||
if tm != nil {
|
||||
|
||||
@@ -73,15 +73,15 @@ func createUser(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
hash := r.URL.Query().Get("h")
|
||||
tokenId := r.URL.Query().Get("t")
|
||||
inviteId := r.URL.Query().Get("iid")
|
||||
|
||||
// No permission check required
|
||||
|
||||
var ruser *model.User
|
||||
var err *model.AppError
|
||||
if len(hash) > 0 {
|
||||
ruser, err = c.App.CreateUserWithHash(user, hash, r.URL.Query().Get("d"))
|
||||
if len(tokenId) > 0 {
|
||||
ruser, err = c.App.CreateUserWithToken(user, tokenId)
|
||||
} else if len(inviteId) > 0 {
|
||||
ruser, err = c.App.CreateUserWithInviteId(user, inviteId)
|
||||
} else if c.IsSystemAdmin() {
|
||||
|
||||
@@ -4,15 +4,14 @@
|
||||
package api4
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/mattermost-server/app"
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
"github.com/mattermost/mattermost-server/utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -81,23 +80,20 @@ func TestCreateUser(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateUserWithHash(t *testing.T) {
|
||||
func TestCreateUserWithToken(t *testing.T) {
|
||||
th := Setup().InitBasic().InitSystemAdmin()
|
||||
defer th.TearDown()
|
||||
Client := th.Client
|
||||
|
||||
t.Run("CreateWithHashHappyPath", func(t *testing.T) {
|
||||
t.Run("CreateWithTokenHappyPath", func(t *testing.T) {
|
||||
user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: "hello1", Username: GenerateTestUsername(), Roles: model.SYSTEM_ADMIN_ROLE_ID + " " + model.SYSTEM_USER_ROLE_ID}
|
||||
props := make(map[string]string)
|
||||
props["email"] = user.Email
|
||||
props["id"] = th.BasicTeam.Id
|
||||
props["display_name"] = th.BasicTeam.DisplayName
|
||||
props["name"] = th.BasicTeam.Name
|
||||
props["time"] = fmt.Sprintf("%v", model.GetMillis())
|
||||
data := model.MapToJson(props)
|
||||
hash := utils.HashSha256(fmt.Sprintf("%v:%v", data, th.App.Config().EmailSettings.InviteSalt))
|
||||
token := model.NewToken(
|
||||
app.TOKEN_TYPE_TEAM_INVITATION,
|
||||
model.MapToJson(map[string]string{"teamId": th.BasicTeam.Id, "email": user.Email}),
|
||||
)
|
||||
<-th.App.Srv.Store.Token().Save(token)
|
||||
|
||||
ruser, resp := Client.CreateUserWithHash(&user, hash, data)
|
||||
ruser, resp := Client.CreateUserWithToken(&user, token.Token)
|
||||
CheckNoError(t, resp)
|
||||
CheckCreatedStatus(t, resp)
|
||||
|
||||
@@ -110,78 +106,73 @@ func TestCreateUserWithHash(t *testing.T) {
|
||||
t.Fatal("did not clear roles")
|
||||
}
|
||||
CheckUserSanitization(t, ruser)
|
||||
if result := <-th.App.Srv.Store.Token().GetByToken(token.Token); result.Err == nil {
|
||||
t.Fatal("The token must be deleted after be used")
|
||||
}
|
||||
|
||||
if result := <-th.App.Srv.Store.Token().GetByToken(token.Token); result.Err == nil {
|
||||
t.Fatal("The token must be deleted after be used")
|
||||
}
|
||||
|
||||
if teams, err := th.App.GetTeamsForUser(ruser.Id); err != nil || len(teams) == 0 {
|
||||
t.Fatal("The user must have teams")
|
||||
} else if teams[0].Id != th.BasicTeam.Id {
|
||||
t.Fatal("The user joined team must be the team provided.")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("NoHashAndNoData", func(t *testing.T) {
|
||||
t.Run("NoToken", func(t *testing.T) {
|
||||
user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: "hello1", Username: GenerateTestUsername(), Roles: model.SYSTEM_ADMIN_ROLE_ID + " " + model.SYSTEM_USER_ROLE_ID}
|
||||
props := make(map[string]string)
|
||||
props["email"] = user.Email
|
||||
props["id"] = th.BasicTeam.Id
|
||||
props["display_name"] = th.BasicTeam.DisplayName
|
||||
props["name"] = th.BasicTeam.Name
|
||||
props["time"] = fmt.Sprintf("%v", model.GetMillis())
|
||||
data := model.MapToJson(props)
|
||||
hash := utils.HashSha256(fmt.Sprintf("%v:%v", data, th.App.Config().EmailSettings.InviteSalt))
|
||||
token := model.NewToken(
|
||||
app.TOKEN_TYPE_TEAM_INVITATION,
|
||||
model.MapToJson(map[string]string{"teamId": th.BasicTeam.Id, "email": user.Email}),
|
||||
)
|
||||
<-th.App.Srv.Store.Token().Save(token)
|
||||
defer th.App.DeleteToken(token)
|
||||
|
||||
_, resp := Client.CreateUserWithHash(&user, "", data)
|
||||
_, resp := Client.CreateUserWithToken(&user, "")
|
||||
CheckBadRequestStatus(t, resp)
|
||||
CheckErrorMessage(t, resp, "api.user.create_user.missing_hash_or_data.app_error")
|
||||
|
||||
_, resp = Client.CreateUserWithHash(&user, hash, "")
|
||||
CheckBadRequestStatus(t, resp)
|
||||
CheckErrorMessage(t, resp, "api.user.create_user.missing_hash_or_data.app_error")
|
||||
CheckErrorMessage(t, resp, "api.user.create_user.missing_token.app_error")
|
||||
})
|
||||
|
||||
t.Run("HashExpired", func(t *testing.T) {
|
||||
t.Run("TokenExpired", func(t *testing.T) {
|
||||
user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: "hello1", Username: GenerateTestUsername(), Roles: model.SYSTEM_ADMIN_ROLE_ID + " " + model.SYSTEM_USER_ROLE_ID}
|
||||
timeNow := time.Now()
|
||||
past49Hours := timeNow.Add(-49*time.Hour).UnixNano() / int64(time.Millisecond)
|
||||
token := model.NewToken(
|
||||
app.TOKEN_TYPE_TEAM_INVITATION,
|
||||
model.MapToJson(map[string]string{"teamId": th.BasicTeam.Id, "email": user.Email}),
|
||||
)
|
||||
token.CreateAt = past49Hours
|
||||
<-th.App.Srv.Store.Token().Save(token)
|
||||
defer th.App.DeleteToken(token)
|
||||
|
||||
props := make(map[string]string)
|
||||
props["email"] = user.Email
|
||||
props["id"] = th.BasicTeam.Id
|
||||
props["display_name"] = th.BasicTeam.DisplayName
|
||||
props["name"] = th.BasicTeam.Name
|
||||
props["time"] = fmt.Sprintf("%v", past49Hours)
|
||||
data := model.MapToJson(props)
|
||||
hash := utils.HashSha256(fmt.Sprintf("%v:%v", data, th.App.Config().EmailSettings.InviteSalt))
|
||||
|
||||
_, resp := Client.CreateUserWithHash(&user, hash, data)
|
||||
CheckInternalErrorStatus(t, resp)
|
||||
_, resp := Client.CreateUserWithToken(&user, token.Token)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
CheckErrorMessage(t, resp, "api.user.create_user.signup_link_expired.app_error")
|
||||
})
|
||||
|
||||
t.Run("WrongHash", func(t *testing.T) {
|
||||
t.Run("WrongToken", func(t *testing.T) {
|
||||
user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: "hello1", Username: GenerateTestUsername(), Roles: model.SYSTEM_ADMIN_ROLE_ID + " " + model.SYSTEM_USER_ROLE_ID}
|
||||
props := make(map[string]string)
|
||||
props["email"] = user.Email
|
||||
props["id"] = th.BasicTeam.Id
|
||||
props["display_name"] = th.BasicTeam.DisplayName
|
||||
props["name"] = th.BasicTeam.Name
|
||||
props["time"] = fmt.Sprintf("%v", model.GetMillis())
|
||||
data := model.MapToJson(props)
|
||||
hash := utils.HashSha256(fmt.Sprintf("%v:%v", data, "WrongHash"))
|
||||
|
||||
_, resp := Client.CreateUserWithHash(&user, hash, data)
|
||||
CheckInternalErrorStatus(t, resp)
|
||||
_, resp := Client.CreateUserWithToken(&user, "wrong")
|
||||
CheckBadRequestStatus(t, resp)
|
||||
CheckErrorMessage(t, resp, "api.user.create_user.signup_link_invalid.app_error")
|
||||
})
|
||||
|
||||
t.Run("EnableUserCreationDisable", func(t *testing.T) {
|
||||
user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: "hello1", Username: GenerateTestUsername(), Roles: model.SYSTEM_ADMIN_ROLE_ID + " " + model.SYSTEM_USER_ROLE_ID}
|
||||
|
||||
props := make(map[string]string)
|
||||
props["email"] = user.Email
|
||||
props["id"] = th.BasicTeam.Id
|
||||
props["display_name"] = th.BasicTeam.DisplayName
|
||||
props["name"] = th.BasicTeam.Name
|
||||
props["time"] = fmt.Sprintf("%v", model.GetMillis())
|
||||
data := model.MapToJson(props)
|
||||
hash := utils.HashSha256(fmt.Sprintf("%v:%v", data, th.App.Config().EmailSettings.InviteSalt))
|
||||
token := model.NewToken(
|
||||
app.TOKEN_TYPE_TEAM_INVITATION,
|
||||
model.MapToJson(map[string]string{"teamId": th.BasicTeam.Id, "email": user.Email}),
|
||||
)
|
||||
<-th.App.Srv.Store.Token().Save(token)
|
||||
defer th.App.DeleteToken(token)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.EnableUserCreation = false })
|
||||
|
||||
_, resp := Client.CreateUserWithHash(&user, hash, data)
|
||||
_, resp := Client.CreateUserWithToken(&user, token.Token)
|
||||
CheckNotImplementedStatus(t, resp)
|
||||
CheckErrorMessage(t, resp, "api.user.create_user.signup_email_disabled.app_error")
|
||||
|
||||
@@ -191,18 +182,15 @@ func TestCreateUserWithHash(t *testing.T) {
|
||||
t.Run("EnableOpenServerDisable", func(t *testing.T) {
|
||||
user := model.User{Email: th.GenerateTestEmail(), Nickname: "Corey Hulen", Password: "hello1", Username: GenerateTestUsername(), Roles: model.SYSTEM_ADMIN_ROLE_ID + " " + model.SYSTEM_USER_ROLE_ID}
|
||||
|
||||
props := make(map[string]string)
|
||||
props["email"] = user.Email
|
||||
props["id"] = th.BasicTeam.Id
|
||||
props["display_name"] = th.BasicTeam.DisplayName
|
||||
props["name"] = th.BasicTeam.Name
|
||||
props["time"] = fmt.Sprintf("%v", model.GetMillis())
|
||||
data := model.MapToJson(props)
|
||||
hash := utils.HashSha256(fmt.Sprintf("%v:%v", data, th.App.Config().EmailSettings.InviteSalt))
|
||||
token := model.NewToken(
|
||||
app.TOKEN_TYPE_TEAM_INVITATION,
|
||||
model.MapToJson(map[string]string{"teamId": th.BasicTeam.Id, "email": user.Email}),
|
||||
)
|
||||
<-th.App.Srv.Store.Token().Save(token)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.EnableOpenServer = false })
|
||||
|
||||
ruser, resp := Client.CreateUserWithHash(&user, hash, data)
|
||||
ruser, resp := Client.CreateUserWithToken(&user, token.Token)
|
||||
CheckNoError(t, resp)
|
||||
CheckCreatedStatus(t, resp)
|
||||
|
||||
@@ -215,6 +203,9 @@ func TestCreateUserWithHash(t *testing.T) {
|
||||
t.Fatal("did not clear roles")
|
||||
}
|
||||
CheckUserSanitization(t, ruser)
|
||||
if result := <-th.App.Srv.Store.Token().GetByToken(token.Token); result.Err == nil {
|
||||
t.Fatal("The token must be deleted after be used")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Ссылка в новой задаче
Block a user