* adds team member data sanitizing (#35562)

* adds team member data sanitizing

* assert using require

* adds data sanitizing to team members for user endpoint

* team admin data visibility now tests with different user

(cherry picked from commit 2be57a7ec0c67004b77c76386f20a630920196e3)

* removes wrong argument in test helper calls

* fix: add explicit permission grant in team members test (#36007)

* fix: add explicit permission grant in team members test

TestGetTeamMembersForUserRoleDataSanitization was relying on a permission
side-effect leaked from concurrent tests. Under fullyparallel, another test
temporarily adds PermissionReadOtherUsersTeams to system_user role, which
the team admin subtest accidentally benefits from. Under sequential execution
(binary parameters mode), no concurrent test leaks this permission, so the
team admin correctly gets 403.

Fix by explicitly granting ReadOtherUsersTeams in the subtest setup, matching
the pattern used in adjacent subtests.

Release Note
NONE

Co-authored-by: Claude <claude@anthropic.com>

* fix: remove explanatory comment per review feedback

---------

Co-authored-by: Claude <claude@anthropic.com>

* removes extra arg from test helper call

---------

Co-authored-by: Carlos Garcia <carlos.garcia@mattermost.com>
Co-authored-by: Pavel Zeman <pavel.zeman@mattermost.com>
Co-authored-by: Claude <claude@anthropic.com>
Этот коммит содержится в:
Mattermost Build
2026-04-16 02:58:14 +02:00
коммит произвёл GitHub
родитель 787fca6a08
Коммит 610a28e9fa
3 изменённых файлов: 297 добавлений и 0 удалений

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

@@ -604,6 +604,10 @@ func getTeamMember(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), c.Params.TeamId, model.PermissionManageTeamRoles) {
team.SanitizeRoleData(c.AppContext.Session().UserId)
}
if err := json.NewEncoder(w).Encode(team); err != nil {
c.Logger.Warn("Error while writing response", mlog.Err(err))
}
@@ -642,6 +646,13 @@ func getTeamMembers(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
currentUserId := c.AppContext.Session().UserId
if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), c.Params.TeamId, model.PermissionManageTeamRoles) {
for _, m := range members {
m.SanitizeRoleData(currentUserId)
}
}
js, err := json.Marshal(members)
if err != nil {
c.Err = model.NewAppError("getTeamMembers", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
@@ -681,6 +692,13 @@ func getTeamMembersForUser(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
currentUserId := c.AppContext.Session().UserId
for _, m := range members {
if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), m.TeamId, model.PermissionManageTeamRoles) {
m.SanitizeRoleData(currentUserId)
}
}
js, err := json.Marshal(members)
if err != nil {
c.Err = model.NewAppError("getTeamMembersForUser", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
@@ -724,6 +742,13 @@ func getTeamMembersByIds(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
currentUserId := c.AppContext.Session().UserId
if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), c.Params.TeamId, model.PermissionManageTeamRoles) {
for _, m := range members {
m.SanitizeRoleData(currentUserId)
}
}
js, err := json.Marshal(members)
if err != nil {
c.Err = model.NewAppError("getTeamMembersByIds", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
@@ -830,6 +855,10 @@ func addTeamMember(c *Context, w http.ResponseWriter, r *http.Request) {
auditRec.AddEventObjectType("team_member") // TODO verify this is the final state. should it be the team instead?
auditRec.Success()
if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), c.Params.TeamId, model.PermissionManageTeamRoles) {
tm.SanitizeRoleData(c.AppContext.Session().UserId)
}
w.WriteHeader(http.StatusCreated)
if err := json.NewEncoder(w).Encode(tm); err != nil {
c.Logger.Warn("Error while writing response", mlog.Err(err))
@@ -984,6 +1013,15 @@ func addTeamMembers(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
currentUserId := c.AppContext.Session().UserId
if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), c.Params.TeamId, model.PermissionManageTeamRoles) {
for _, m := range membersWithErrors {
if m.Member != nil {
m.Member.SanitizeRoleData(currentUserId)
}
}
}
var (
js []byte
err error

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

@@ -4574,3 +4574,251 @@ func TestInvalidateAllEmailInvites(t *testing.T) {
CheckOKStatus(t, res)
})
}
func setupTeamWithAdminAndMember(t *testing.T, th *TestHelper) *model.Client4 {
t.Helper()
th.UpdateUserToTeamAdmin(th.BasicUser2, th.BasicTeam)
require.Nil(t, th.App.Srv().InvalidateAllCaches())
teamAdminClient := th.CreateClient()
_, _, err := teamAdminClient.Login(context.Background(), th.BasicUser2.Email, th.BasicUser2.Password)
require.NoError(t, err)
return teamAdminClient
}
func assertRoleDataSanitized(t *testing.T, m *model.TeamMember) {
t.Helper()
assert.Empty(t, m.Roles)
assert.Empty(t, m.ExplicitRoles)
assert.False(t, m.SchemeAdmin)
assert.False(t, m.SchemeGuest)
assert.False(t, m.SchemeUser)
assert.Equal(t, int64(-1), m.DeleteAt)
}
func TestGetTeamMembersRoleDataSanitization(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic()
teamAdminClient := setupTeamWithAdminAndMember(t, th)
t.Run("non-admin cannot see role data of others", func(t *testing.T) {
members, _, err := th.Client.GetTeamMembers(context.Background(), th.BasicTeam.Id, 0, 100, "")
require.NoError(t, err)
for _, m := range members {
if m.UserId != th.BasicUser.Id {
assertRoleDataSanitized(t, m)
}
}
})
t.Run("non-admin sees own role data", func(t *testing.T) {
members, _, err := th.Client.GetTeamMembers(context.Background(), th.BasicTeam.Id, 0, 100, "")
require.NoError(t, err)
for _, m := range members {
if m.UserId == th.BasicUser.Id {
assert.True(t, m.SchemeUser)
return
}
}
require.Fail(t, "current user not found in members")
})
t.Run("team admin sees full role data for other user", func(t *testing.T) {
members, _, err := teamAdminClient.GetTeamMembers(context.Background(), th.BasicTeam.Id, 0, 100, "")
require.NoError(t, err)
for _, m := range members {
if m.UserId == th.BasicUser.Id {
assert.True(t, m.SchemeUser)
return
}
}
require.Fail(t, "target user not found in members")
})
t.Run("system admin sees full role data", func(t *testing.T) {
members, _, err := th.SystemAdminClient.GetTeamMembers(context.Background(), th.BasicTeam.Id, 0, 100, "")
require.NoError(t, err)
for _, m := range members {
if m.UserId == th.BasicUser2.Id {
assert.True(t, m.SchemeAdmin)
return
}
}
require.Fail(t, "team admin not found in members")
})
}
func TestGetTeamMemberRoleDataSanitization(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic()
teamAdminClient := setupTeamWithAdminAndMember(t, th)
t.Run("non-admin cannot see role data of others", func(t *testing.T) {
member, _, err := th.Client.GetTeamMember(context.Background(), th.BasicTeam.Id, th.BasicUser2.Id, "")
require.NoError(t, err)
assertRoleDataSanitized(t, member)
})
t.Run("non-admin sees own role data", func(t *testing.T) {
member, _, err := th.Client.GetTeamMember(context.Background(), th.BasicTeam.Id, th.BasicUser.Id, "")
require.NoError(t, err)
assert.True(t, member.SchemeUser)
})
t.Run("team admin sees full role data for other user", func(t *testing.T) {
member, _, err := teamAdminClient.GetTeamMember(context.Background(), th.BasicTeam.Id, th.BasicUser.Id, "")
require.NoError(t, err)
assert.True(t, member.SchemeUser)
})
t.Run("system admin sees full role data", func(t *testing.T) {
member, _, err := th.SystemAdminClient.GetTeamMember(context.Background(), th.BasicTeam.Id, th.BasicUser2.Id, "")
require.NoError(t, err)
assert.True(t, member.SchemeAdmin)
})
}
func TestGetTeamMembersByIdsRoleDataSanitization(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic()
teamAdminClient := setupTeamWithAdminAndMember(t, th)
t.Run("non-admin cannot see role data of others", func(t *testing.T) {
members, _, err := th.Client.GetTeamMembersByIds(context.Background(), th.BasicTeam.Id, []string{th.BasicUser2.Id})
require.NoError(t, err)
require.Len(t, members, 1)
assertRoleDataSanitized(t, members[0])
})
t.Run("non-admin sees own role data", func(t *testing.T) {
members, _, err := th.Client.GetTeamMembersByIds(context.Background(), th.BasicTeam.Id, []string{th.BasicUser.Id})
require.NoError(t, err)
require.Len(t, members, 1)
assert.True(t, members[0].SchemeUser)
})
t.Run("team admin sees full role data for other user", func(t *testing.T) {
members, _, err := teamAdminClient.GetTeamMembersByIds(context.Background(), th.BasicTeam.Id, []string{th.BasicUser.Id})
require.NoError(t, err)
require.Len(t, members, 1)
assert.True(t, members[0].SchemeUser)
})
t.Run("system admin sees full role data", func(t *testing.T) {
members, _, err := th.SystemAdminClient.GetTeamMembersByIds(context.Background(), th.BasicTeam.Id, []string{th.BasicUser2.Id})
require.NoError(t, err)
require.Len(t, members, 1)
assert.True(t, members[0].SchemeAdmin)
})
}
func TestAddTeamMemberRoleDataSanitization(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic()
teamAdminClient := setupTeamWithAdminAndMember(t, th)
t.Run("team admin adding user sees full role data in response", func(t *testing.T) {
newUser := th.CreateUser()
tm, _, err := teamAdminClient.AddTeamMember(context.Background(), th.BasicTeam.Id, newUser.Id)
require.NoError(t, err)
assert.True(t, tm.SchemeUser)
})
t.Run("non-admin adding user sees sanitized role data in response", func(t *testing.T) {
defaultRolePermissions := th.SaveDefaultRolePermissions()
defer th.RestoreDefaultRolePermissions(defaultRolePermissions)
th.AddPermissionToRole(model.PermissionAddUserToTeam.Id, model.TeamUserRoleId)
newUser := th.CreateUser()
tm, _, err := th.Client.AddTeamMember(context.Background(), th.BasicTeam.Id, newUser.Id)
require.NoError(t, err)
assertRoleDataSanitized(t, tm)
})
}
func TestAddTeamMembersRoleDataSanitization(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic()
teamAdminClient := setupTeamWithAdminAndMember(t, th)
t.Run("team admin adding users sees full role data in response", func(t *testing.T) {
newUser := th.CreateUser()
members, _, err := teamAdminClient.AddTeamMembers(context.Background(), th.BasicTeam.Id, []string{newUser.Id})
require.NoError(t, err)
require.Len(t, members, 1)
assert.True(t, members[0].SchemeUser)
})
t.Run("non-admin adding users sees sanitized role data in response", func(t *testing.T) {
defaultRolePermissions := th.SaveDefaultRolePermissions()
defer th.RestoreDefaultRolePermissions(defaultRolePermissions)
th.AddPermissionToRole(model.PermissionAddUserToTeam.Id, model.TeamUserRoleId)
newUser := th.CreateUser()
members, _, err := th.Client.AddTeamMembers(context.Background(), th.BasicTeam.Id, []string{newUser.Id})
require.NoError(t, err)
require.Len(t, members, 1)
assertRoleDataSanitized(t, members[0])
})
}
func TestGetTeamMembersForUserRoleDataSanitization(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic()
teamAdminClient := setupTeamWithAdminAndMember(t, th)
t.Run("user sees own role data", func(t *testing.T) {
members, _, err := th.Client.GetTeamMembersForUser(context.Background(), th.BasicUser.Id, "")
require.NoError(t, err)
require.NotEmpty(t, members)
for _, m := range members {
assert.True(t, m.SchemeUser)
}
})
t.Run("non-admin cannot see role data of another user", func(t *testing.T) {
defaultRolePermissions := th.SaveDefaultRolePermissions()
defer th.RestoreDefaultRolePermissions(defaultRolePermissions)
th.AddPermissionToRole(model.PermissionReadOtherUsersTeams.Id, model.SystemUserRoleId)
members, _, err := th.Client.GetTeamMembersForUser(context.Background(), th.BasicUser2.Id, "")
require.NoError(t, err)
require.NotEmpty(t, members)
for _, m := range members {
assertRoleDataSanitized(t, m)
}
})
t.Run("team admin sees full role data for other user in managed team", func(t *testing.T) {
defaultRolePermissions := th.SaveDefaultRolePermissions()
defer th.RestoreDefaultRolePermissions(defaultRolePermissions)
th.AddPermissionToRole(model.PermissionReadOtherUsersTeams.Id, model.SystemUserRoleId)
members, _, err := teamAdminClient.GetTeamMembersForUser(context.Background(), th.BasicUser.Id, "")
require.NoError(t, err)
require.NotEmpty(t, members)
for _, m := range members {
if m.TeamId == th.BasicTeam.Id {
assert.True(t, m.SchemeUser)
return
}
}
require.Fail(t, "basic team membership not found")
})
t.Run("system admin sees full role data", func(t *testing.T) {
members, _, err := th.SystemAdminClient.GetTeamMembersForUser(context.Background(), th.BasicUser2.Id, "")
require.NoError(t, err)
require.NotEmpty(t, members)
for _, m := range members {
if m.TeamId == th.BasicTeam.Id {
assert.True(t, m.SchemeAdmin)
return
}
}
require.Fail(t, "basic team membership not found")
})
}

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

@@ -142,3 +142,14 @@ func (o *TeamMember) PreUpdate() {
func (o *TeamMember) GetRoles() []string {
return strings.Fields(o.Roles)
}
func (o *TeamMember) SanitizeRoleData(currentUserId string) {
if o.UserId != currentUserId {
o.Roles = ""
o.ExplicitRoles = ""
o.SchemeAdmin = false
o.SchemeGuest = false
o.SchemeUser = false
o.DeleteAt = -1
}
}