MM-14753: Verifies that user can join teams and channels in spite of group constraints. (#10529)
* MM-147753: Verifies that users are allowed to be members of a team or a channel, based on group constraints, prior to allowing the API to add them. * MM-14753: Allow methods to return meaningful results for deleted teams or channels. * MM-14753: Renames methods to differentiate from permissions and other team and channel restrictions. * MM-14753: Only check if users are team/channel members if team/channel is group constrained. * MM-14753: Updates test function names. * MM-14753: Changes a few method signatures. * MM-14753: Small refactor and adds missing returns. * MM-14753: Changes method names from Get* to Filter* name prefixes. * MM-14753: Renames error variables. * MM-14753: Updates method names for consistency with join table names. * MM-14753: Adds case for non AppError return. * Update i18n/en.json
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
43fa7e0548
Коммит
7bde0378cd
@@ -44,6 +44,7 @@ type TestHelper struct {
|
||||
BasicDeletedChannel *model.Channel
|
||||
BasicChannel2 *model.Channel
|
||||
BasicPost *model.Post
|
||||
Group *model.Group
|
||||
|
||||
SystemAdminClient *model.Client4
|
||||
SystemAdminUser *model.User
|
||||
@@ -210,6 +211,7 @@ func (me *TestHelper) InitBasic() *TestHelper {
|
||||
me.App.UpdateUserRoles(me.BasicUser.Id, model.SYSTEM_USER_ROLE_ID, false)
|
||||
me.Client.DeleteChannel(me.BasicDeletedChannel.Id)
|
||||
me.LoginBasic()
|
||||
me.Group = me.CreateGroup()
|
||||
|
||||
return me
|
||||
}
|
||||
@@ -509,6 +511,24 @@ func (me *TestHelper) GenerateTestEmail() string {
|
||||
return strings.ToLower(model.NewId() + "@dockerhost")
|
||||
}
|
||||
|
||||
func (me *TestHelper) CreateGroup() *model.Group {
|
||||
id := model.NewId()
|
||||
group := &model.Group{
|
||||
Name: "n-" + id,
|
||||
DisplayName: "dn_" + id,
|
||||
Source: model.GroupSourceLdap,
|
||||
RemoteId: "ri_" + id,
|
||||
}
|
||||
|
||||
utils.DisableDebugLogForTest()
|
||||
group, err := me.App.CreateGroup(group)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
utils.EnableDebugLogForTest()
|
||||
return group
|
||||
}
|
||||
|
||||
func GenerateTestUsername() string {
|
||||
return "fakeuser" + model.NewRandomString(10)
|
||||
}
|
||||
|
||||
@@ -1174,6 +1174,22 @@ func addChannelMember(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
if channel.GroupConstrained != nil && *channel.GroupConstrained {
|
||||
nonMembers, err := c.App.FilterNonGroupChannelMembers([]string{member.UserId}, channel)
|
||||
if err != nil {
|
||||
if v, ok := err.(*model.AppError); ok {
|
||||
c.Err = v
|
||||
} else {
|
||||
c.Err = model.NewAppError("addChannelMember", "api.channel.add_members.error", nil, err.Error(), http.StatusBadRequest)
|
||||
}
|
||||
return
|
||||
}
|
||||
if len(nonMembers) > 0 {
|
||||
c.Err = model.NewAppError("addChannelMember", "api.channel.add_members.user_denied", map[string]interface{}{"UserIDs": nonMembers}, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
cm, err := c.App.AddChannelMember(member.UserId, channel, c.App.Session.UserId, postRootId, c.App.Session.Id)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
|
||||
@@ -2021,6 +2021,30 @@ func TestAddChannelMember(t *testing.T) {
|
||||
_, resp = Client.AddChannelMember(privateChannel.Id, user3.Id)
|
||||
CheckNoError(t, resp)
|
||||
Client.Logout()
|
||||
|
||||
// Set a channel to group-constrained
|
||||
privateChannel.GroupConstrained = model.NewBool(true)
|
||||
_, appErr := th.App.UpdateChannel(privateChannel)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// User is not in associated groups so shouldn't be allowed
|
||||
_, resp = th.SystemAdminClient.AddChannelMember(privateChannel.Id, user.Id)
|
||||
CheckErrorMessage(t, resp, "api.channel.add_members.user_denied")
|
||||
|
||||
// Associate group to team
|
||||
_, appErr = th.App.CreateGroupSyncable(&model.GroupSyncable{
|
||||
GroupId: th.Group.Id,
|
||||
SyncableId: privateChannel.Id,
|
||||
Type: model.GroupSyncableTypeChannel,
|
||||
})
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// Add user to group
|
||||
_, appErr = th.App.CreateOrRestoreGroupMember(th.Group.Id, user.Id)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
_, resp = th.SystemAdminClient.AddChannelMember(privateChannel.Id, user.Id)
|
||||
CheckNoError(t, resp)
|
||||
}
|
||||
|
||||
func TestAddChannelMemberAddMyself(t *testing.T) {
|
||||
|
||||
56
api4/team.go
56
api4/team.go
@@ -388,6 +388,28 @@ func addTeamMember(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
team, err := c.App.GetTeam(member.TeamId)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
if team.GroupConstrained != nil && *team.GroupConstrained {
|
||||
nonMembers, err := c.App.FilterNonGroupTeamMembers([]string{member.UserId}, team)
|
||||
if err != nil {
|
||||
if v, ok := err.(*model.AppError); ok {
|
||||
c.Err = v
|
||||
} else {
|
||||
c.Err = model.NewAppError("addTeamMember", "api.team.add_members.error", nil, err.Error(), http.StatusBadRequest)
|
||||
}
|
||||
return
|
||||
}
|
||||
if len(nonMembers) > 0 {
|
||||
c.Err = model.NewAppError("addTeamMember", "api.team.add_members.user_denied", map[string]interface{}{"UserIDs": nonMembers}, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
member, err = c.App.AddTeamMember(member.TeamId, member.UserId)
|
||||
|
||||
if err != nil {
|
||||
@@ -432,11 +454,43 @@ func addTeamMembers(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
var err *model.AppError
|
||||
members := model.TeamMembersFromJson(r.Body)
|
||||
|
||||
if len(members) > MAX_ADD_MEMBERS_BATCH || len(members) == 0 {
|
||||
if len(members) > MAX_ADD_MEMBERS_BATCH {
|
||||
c.SetInvalidParam("too many members in batch")
|
||||
return
|
||||
}
|
||||
|
||||
if len(members) == 0 {
|
||||
c.SetInvalidParam("no members in batch")
|
||||
return
|
||||
}
|
||||
|
||||
var memberIDs []string
|
||||
for _, member := range members {
|
||||
memberIDs = append(memberIDs, member.UserId)
|
||||
}
|
||||
|
||||
team, err := c.App.GetTeam(c.Params.TeamId)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
if team.GroupConstrained != nil && *team.GroupConstrained {
|
||||
nonMembers, err := c.App.FilterNonGroupTeamMembers(memberIDs, team)
|
||||
if err != nil {
|
||||
if v, ok := err.(*model.AppError); ok {
|
||||
c.Err = v
|
||||
} else {
|
||||
c.Err = model.NewAppError("addTeamMembers", "api.team.add_members.error", nil, err.Error(), http.StatusBadRequest)
|
||||
}
|
||||
return
|
||||
}
|
||||
if len(nonMembers) > 0 {
|
||||
c.Err = model.NewAppError("addTeamMembers", "api.team.add_members.user_denied", map[string]interface{}{"UserIDs": nonMembers}, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var userIds []string
|
||||
for _, member := range members {
|
||||
if member.TeamId != c.Params.TeamId {
|
||||
|
||||
@@ -1442,6 +1442,30 @@ func TestAddTeamMember(t *testing.T) {
|
||||
if tm != nil {
|
||||
t.Fatal("should have not returned team member")
|
||||
}
|
||||
|
||||
// Set a team to group-constrained
|
||||
team.GroupConstrained = model.NewBool(true)
|
||||
_, err := th.App.UpdateTeam(team)
|
||||
require.Nil(t, err)
|
||||
|
||||
// User is not in associated groups so shouldn't be allowed
|
||||
_, resp = th.SystemAdminClient.AddTeamMember(team.Id, otherUser.Id)
|
||||
CheckErrorMessage(t, resp, "api.team.add_members.user_denied")
|
||||
|
||||
// Associate group to team
|
||||
_, err = th.App.CreateGroupSyncable(&model.GroupSyncable{
|
||||
GroupId: th.Group.Id,
|
||||
SyncableId: team.Id,
|
||||
Type: model.GroupSyncableTypeTeam,
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
// Add user to group
|
||||
_, err = th.App.CreateOrRestoreGroupMember(th.Group.Id, otherUser.Id)
|
||||
require.Nil(t, err)
|
||||
|
||||
_, resp = th.SystemAdminClient.AddTeamMember(team.Id, otherUser.Id)
|
||||
CheckNoError(t, resp)
|
||||
}
|
||||
|
||||
func TestAddTeamMemberMyself(t *testing.T) {
|
||||
@@ -1578,7 +1602,7 @@ func TestAddTeamMembers(t *testing.T) {
|
||||
CheckBadRequestStatus(t, resp)
|
||||
|
||||
_, resp = Client.AddTeamMembers(GenerateTestId(), userList)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
CheckNotFoundStatus(t, resp)
|
||||
|
||||
testUserList := append(userList, GenerateTestId())
|
||||
_, resp = Client.AddTeamMembers(team.Id, testUserList)
|
||||
@@ -1633,6 +1657,30 @@ func TestAddTeamMembers(t *testing.T) {
|
||||
// Should work as a regular user.
|
||||
_, resp = Client.AddTeamMembers(team.Id, userList)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
// Set a team to group-constrained
|
||||
team.GroupConstrained = model.NewBool(true)
|
||||
_, err := th.App.UpdateTeam(team)
|
||||
require.Nil(t, err)
|
||||
|
||||
// User is not in associated groups so shouldn't be allowed
|
||||
_, resp = Client.AddTeamMembers(team.Id, userList)
|
||||
CheckErrorMessage(t, resp, "api.team.add_members.user_denied")
|
||||
|
||||
// Associate group to team
|
||||
_, err = th.App.CreateGroupSyncable(&model.GroupSyncable{
|
||||
GroupId: th.Group.Id,
|
||||
SyncableId: team.Id,
|
||||
Type: model.GroupSyncableTypeTeam,
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
// Add user to group
|
||||
_, err = th.App.CreateOrRestoreGroupMember(th.Group.Id, userList[0])
|
||||
require.Nil(t, err)
|
||||
|
||||
_, resp = Client.AddTeamMembers(team.Id, userList)
|
||||
CheckNoError(t, resp)
|
||||
}
|
||||
|
||||
func TestRemoveTeamMember(t *testing.T) {
|
||||
|
||||
Ссылка в новой задаче
Block a user