From 01ee93fc89b8ec4e10c256fcb32044ea94922799 Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Thu, 26 Oct 2023 15:24:37 +0000 Subject: [PATCH 1/7] check for SearchContacts permissions on /team/:id/members --- integration/test/API/Galley.hs | 6 +++++ integration/test/SetupHelpers.hs | 12 +++++++-- integration/test/Test/Brig.hs | 25 +++++++++++++++++++ .../API/Routes/Public/Galley/TeamMember.hs | 1 + services/galley/src/Galley/API/Teams.hs | 2 ++ 5 files changed, 44 insertions(+), 2 deletions(-) diff --git a/integration/test/API/Galley.hs b/integration/test/API/Galley.hs index 66755cc8e7..dc319af17e 100644 --- a/integration/test/API/Galley.hs +++ b/integration/test/API/Galley.hs @@ -485,3 +485,9 @@ updateMessageTimer user qcnv update = do let path = joinHttpPath ["conversations", cnvDomain, cnvId, "message-timer"] req <- baseRequest user Galley Versioned path submit "PUT" (addJSONObject ["message_timer" .= updateReq] req) + +getTeamMembers :: (HasCallStack, MakesValue user, MakesValue tid) => user -> tid -> App Response +getTeamMembers user tid = do + tidStr <- asString tid + req <- baseRequest user Galley Versioned (joinHttpPath ["teams", tidStr, "members"]) + submit "GET" req diff --git a/integration/test/SetupHelpers.hs b/integration/test/SetupHelpers.hs index 95694cfaf9..270bed8bf7 100644 --- a/integration/test/SetupHelpers.hs +++ b/integration/test/SetupHelpers.hs @@ -40,9 +40,17 @@ createTeamMember :: inviter -> String -> App Value -createTeamMember inviter tid = do +createTeamMember inviter tid = createTeamMemberWithRole inviter tid "member" + +createTeamMemberWithRole :: + (HasCallStack, MakesValue inviter) => + inviter -> + String -> + String -> + App Value +createTeamMemberWithRole inviter tid role = do newUserEmail <- randomEmail - let invitationJSON = ["role" .= "member", "email" .= newUserEmail] + let invitationJSON = ["role" .= role, "email" .= newUserEmail] invitationReq <- baseRequest inviter Brig Versioned $ joinHttpPath ["teams", tid, "invitations"] diff --git a/integration/test/Test/Brig.hs b/integration/test/Test/Brig.hs index f35fbd1856..be84af32f4 100644 --- a/integration/test/Test/Brig.hs +++ b/integration/test/Test/Brig.hs @@ -3,6 +3,7 @@ module Test.Brig where import API.Brig qualified as BrigP import API.BrigInternal qualified as BrigI import API.Common (randomName) +import API.Galley qualified as Galley import Data.Aeson.Types hiding ((.=)) import Data.Set qualified as Set import Data.String.Conversions @@ -13,6 +14,30 @@ import SetupHelpers import Testlib.Assertions import Testlib.Prelude +testSearchContactForExternalUsers :: HasCallStack => App () +testSearchContactForExternalUsers = do + owner <- randomUser OwnDomain def {BrigI.team = True} + tid <- owner %. "team" & asString + + partner <- createTeamMemberWithRole owner tid "partner" + teamMember <- createTeamMember owner tid + + bindResponse (BrigP.searchContacts teamMember (owner %. "name") OwnDomain) $ \resp -> + resp.status `shouldMatchInt` 200 + + bindResponse (BrigP.searchContacts partner (owner %. "name") OwnDomain) $ \resp -> + resp.status `shouldMatchInt` 403 + + bindResponse (Galley.getTeamMembers teamMember tid) $ \resp -> do + members <- resp.json %. "members" & asList + userIds <- for members (\m -> m %. "user") + expected <- for [owner, teamMember, partner] objId + userIds `shouldMatchSet` expected + resp.status `shouldMatchInt` 200 + + bindResponse (Galley.getTeamMembers partner tid) $ \resp -> do + resp.status `shouldMatchInt` 403 + testCrudFederationRemotes :: HasCallStack => App () testCrudFederationRemotes = do otherDomain <- asString OtherDomain diff --git a/libs/wire-api/src/Wire/API/Routes/Public/Galley/TeamMember.hs b/libs/wire-api/src/Wire/API/Routes/Public/Galley/TeamMember.hs index 4c71df03e4..c66eb7d7b9 100644 --- a/libs/wire-api/src/Wire/API/Routes/Public/Galley/TeamMember.hs +++ b/libs/wire-api/src/Wire/API/Routes/Public/Galley/TeamMember.hs @@ -40,6 +40,7 @@ type TeamMemberAPI = "get-team-members" ( Summary "Get team members" :> CanThrow 'NotATeamMember + :> CanThrow 'InvalidPermissions :> ZLocalUser :> "teams" :> Capture "tid" TeamId diff --git a/services/galley/src/Galley/API/Teams.hs b/services/galley/src/Galley/API/Teams.hs index 98573b9ef8..f271276a56 100644 --- a/services/galley/src/Galley/API/Teams.hs +++ b/services/galley/src/Galley/API/Teams.hs @@ -484,6 +484,7 @@ getTeamConversationRoles zusr tid = do getTeamMembers :: ( Member (ErrorS 'NotATeamMember) r, + Member (ErrorS 'InvalidPermissions) r, Member TeamStore r, Member (TeamMemberStore CassandraPaging) r ) => @@ -494,6 +495,7 @@ getTeamMembers :: Sem r TeamMembersPage getTeamMembers lzusr tid mbMaxResults mbPagingState = do member <- E.getTeamMember tid (tUnqualified lzusr) >>= noteS @'NotATeamMember + unless (member `hasPermission` SearchContacts) $ throwS @'InvalidPermissions let mState = C.PagingState . LBS.fromStrict <$> (mbPagingState >>= mtpsState) let mLimit = fromMaybe (unsafeRange Public.hardTruncationLimit) mbMaxResults E.listTeamMembers @CassandraPaging tid mState mLimit <&> toTeamMembersPage member From ec5c22e54be31d714c245e131e755657e40dc2a6 Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Thu, 26 Oct 2023 15:30:23 +0000 Subject: [PATCH 2/7] changelog --- changelog.d/3-bug-fixes/WPB-5133 | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/3-bug-fixes/WPB-5133 diff --git a/changelog.d/3-bug-fixes/WPB-5133 b/changelog.d/3-bug-fixes/WPB-5133 new file mode 100644 index 0000000000..d1c499e30c --- /dev/null +++ b/changelog.d/3-bug-fixes/WPB-5133 @@ -0,0 +1 @@ +Only allow team member search with SearchContacts permission From 7328f20d5c1d2ae15051fc3ecb066ba13216268e Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Fri, 10 Nov 2023 15:27:06 +0000 Subject: [PATCH 3/7] filter team members according to external role permissions --- integration/test/Test/Brig.hs | 35 ++++++++++++++----- .../API/Routes/Public/Galley/TeamMember.hs | 1 - services/galley/src/Galley/API/Teams.hs | 20 ++++++++--- services/galley/src/Galley/Cassandra/Team.hs | 13 +++++++ .../galley/src/Galley/Effects/TeamStore.hs | 8 +++++ 5 files changed, 64 insertions(+), 13 deletions(-) diff --git a/integration/test/Test/Brig.hs b/integration/test/Test/Brig.hs index be84af32f4..a7816a7af9 100644 --- a/integration/test/Test/Brig.hs +++ b/integration/test/Test/Brig.hs @@ -3,6 +3,7 @@ module Test.Brig where import API.Brig qualified as BrigP import API.BrigInternal qualified as BrigI import API.Common (randomName) +import API.Galley import API.Galley qualified as Galley import Data.Aeson.Types hiding ((.=)) import Data.Set qualified as Set @@ -20,23 +21,41 @@ testSearchContactForExternalUsers = do tid <- owner %. "team" & asString partner <- createTeamMemberWithRole owner tid "partner" - teamMember <- createTeamMember owner tid + tm1 <- createTeamMember owner tid + tm2 <- createTeamMember owner tid - bindResponse (BrigP.searchContacts teamMember (owner %. "name") OwnDomain) $ \resp -> + -- a team member can search for contacts + bindResponse (BrigP.searchContacts tm1 (owner %. "name") OwnDomain) $ \resp -> resp.status `shouldMatchInt` 200 + -- a partner is not allowed to search for contacts bindResponse (BrigP.searchContacts partner (owner %. "name") OwnDomain) $ \resp -> resp.status `shouldMatchInt` 403 - bindResponse (Galley.getTeamMembers teamMember tid) $ \resp -> do - members <- resp.json %. "members" & asList - userIds <- for members (\m -> m %. "user") - expected <- for [owner, teamMember, partner] objId - userIds `shouldMatchSet` expected + -- a team member can see all other team members + bindResponse (Galley.getTeamMembers tm1 tid) $ \resp -> do resp.status `shouldMatchInt` 200 + assertContainsUserIds resp [owner, tm1, tm2, partner] + -- an external partner should see the person who invited them bindResponse (Galley.getTeamMembers partner tid) $ \resp -> do - resp.status `shouldMatchInt` 403 + resp.status `shouldMatchInt` 200 + assertContainsUserIds resp [owner, partner] + + -- the team owner creates a conversation with the partner and another team member + void $ postConversation owner (defProteus {qualifiedUsers = [tm1, partner], team = Just tid}) >>= getJSON 201 + + -- now the external partner should also see the other team member + bindResponse (Galley.getTeamMembers partner tid) $ \resp -> do + resp.status `shouldMatchInt` 200 + assertContainsUserIds resp [owner, tm1, partner] + where + assertContainsUserIds :: Response -> [Value] -> App () + assertContainsUserIds resp users = do + members <- resp.json %. "members" & asList + userIds <- for members (\m -> m %. "user") + expected <- for users objId + userIds `shouldMatchSet` expected testCrudFederationRemotes :: HasCallStack => App () testCrudFederationRemotes = do diff --git a/libs/wire-api/src/Wire/API/Routes/Public/Galley/TeamMember.hs b/libs/wire-api/src/Wire/API/Routes/Public/Galley/TeamMember.hs index c66eb7d7b9..4c71df03e4 100644 --- a/libs/wire-api/src/Wire/API/Routes/Public/Galley/TeamMember.hs +++ b/libs/wire-api/src/Wire/API/Routes/Public/Galley/TeamMember.hs @@ -40,7 +40,6 @@ type TeamMemberAPI = "get-team-members" ( Summary "Get team members" :> CanThrow 'NotATeamMember - :> CanThrow 'InvalidPermissions :> ZLocalUser :> "teams" :> Capture "tid" TeamId diff --git a/services/galley/src/Galley/API/Teams.hs b/services/galley/src/Galley/API/Teams.hs index f271276a56..921edc2036 100644 --- a/services/galley/src/Galley/API/Teams.hs +++ b/services/galley/src/Galley/API/Teams.hs @@ -89,6 +89,7 @@ import Galley.API.Update qualified as API import Galley.API.Util import Galley.App import Galley.Data.Conversation qualified as Data +import Galley.Data.Conversation.Types import Galley.Data.Services (BotMember) import Galley.Effects import Galley.Effects.BrigAccess qualified as E @@ -484,8 +485,8 @@ getTeamConversationRoles zusr tid = do getTeamMembers :: ( Member (ErrorS 'NotATeamMember) r, - Member (ErrorS 'InvalidPermissions) r, Member TeamStore r, + Member ConversationStore r, Member (TeamMemberStore CassandraPaging) r ) => Local UserId -> @@ -494,11 +495,22 @@ getTeamMembers :: Maybe TeamMembersPagingState -> Sem r TeamMembersPage getTeamMembers lzusr tid mbMaxResults mbPagingState = do - member <- E.getTeamMember tid (tUnqualified lzusr) >>= noteS @'NotATeamMember - unless (member `hasPermission` SearchContacts) $ throwS @'InvalidPermissions + let uid = tUnqualified lzusr + member <- E.getTeamMember tid uid >>= noteS @'NotATeamMember let mState = C.PagingState . LBS.fromStrict <$> (mbPagingState >>= mtpsState) let mLimit = fromMaybe (unsafeRange Public.hardTruncationLimit) mbMaxResults - E.listTeamMembers @CassandraPaging tid mState mLimit <&> toTeamMembersPage member + if member `hasPermission` SearchContacts + then E.listTeamMembers @CassandraPaging tid mState mLimit <&> toTeamMembersPage member + else do + -- If the user does not have the SearchContacts permission (e.g. the external partner), + -- we only return the team members they are in a conversation with plus the invitee + teamCids <- fmap (\tConv -> tConv ^. conversationId) <$> E.getTeamConversations tid + userTeamCids <- E.selectConversations uid teamCids + convs <- E.getConversations userTeamCids + let lConvMems = Conv.lmId <$> (convs >>= convLocalMembers) + let invitee = member ^. invitation <&> fst + let uids = nub (maybe (uid : lConvMems) (: uid : lConvMems) invitee) + E.selectTeamMembersPaginated tid uids mState mLimit <&> toTeamMembersPage member where toTeamMembersPage :: TeamMember -> C.PageWithState TeamMember -> TeamMembersPage toTeamMembersPage member p = diff --git a/services/galley/src/Galley/Cassandra/Team.hs b/services/galley/src/Galley/Cassandra/Team.hs index 06560c01ba..f6322b1015 100644 --- a/services/galley/src/Galley/Cassandra/Team.hs +++ b/services/galley/src/Galley/Cassandra/Team.hs @@ -104,6 +104,7 @@ interpretTeamStoreToCassandra lh = interpret $ \case menv <- inputs (view aEnv) for_ menv $ \env -> embed @IO $ Aws.execute env (Aws.enqueue e) + SelectTeamMembersPaginated tid uids mps lim -> embedClient $ selectSomeTeamMembersPaginated lh tid uids mps lim interpretTeamListToCassandra :: ( Member (Embed IO) r, @@ -488,3 +489,15 @@ teamMembersPageFrom lh tid pagingState (fromRange -> max) = do page <- paginateWithState Cql.selectTeamMembers (paramsPagingState LocalQuorum (Identity tid) max pagingState) members <- mapM (newTeamMember' lh tid) (pwsResults page) pure $ PageWithState members (pwsState page) + +selectSomeTeamMembersPaginated :: + FeatureLegalHold -> + TeamId -> + [UserId] -> + Maybe PagingState -> + Range 1 HardTruncationLimit Int32 -> + Client (PageWithState TeamMember) +selectSomeTeamMembersPaginated lh tid uids pagingState (fromRange -> max) = do + page <- paginateWithState Cql.selectTeamMembers' (paramsPagingState LocalQuorum (tid, uids) max pagingState) + members <- mapM (newTeamMember' lh tid) (pwsResults page) + pure $ PageWithState members (pwsState page) diff --git a/services/galley/src/Galley/Effects/TeamStore.hs b/services/galley/src/Galley/Effects/TeamStore.hs index 00db468b51..cf0a225715 100644 --- a/services/galley/src/Galley/Effects/TeamStore.hs +++ b/services/galley/src/Galley/Effects/TeamStore.hs @@ -61,6 +61,7 @@ module Galley.Effects.TeamStore getBillingTeamMembers, getTeamAdmins, selectTeamMembers, + selectTeamMembersPaginated, -- ** Update team members setTeamMemberPermissions, @@ -92,6 +93,7 @@ import Wire.API.Team.Conversation import Wire.API.Team.Member (HardTruncationLimit, TeamMember, TeamMemberList) import Wire.API.Team.Permission import Wire.Sem.Paging +import Wire.Sem.Paging.Cassandra (CassandraPaging) data TeamStore m a where CreateTeamMember :: TeamId -> TeamMember -> TeamStore m () @@ -116,6 +118,12 @@ data TeamStore m a where GetTeamMembersWithLimit :: TeamId -> Range 1 HardTruncationLimit Int32 -> TeamStore m TeamMemberList GetTeamMembers :: TeamId -> TeamStore m [TeamMember] SelectTeamMembers :: TeamId -> [UserId] -> TeamStore m [TeamMember] + SelectTeamMembersPaginated :: + TeamId -> + [UserId] -> + Maybe (PagingState CassandraPaging TeamMember) -> + PagingBounds CassandraPaging TeamMember -> + TeamStore m (Page CassandraPaging TeamMember) GetUserTeams :: UserId -> TeamStore m [TeamId] GetUsersTeams :: [UserId] -> TeamStore m (Map UserId TeamId) GetOneUserTeam :: UserId -> TeamStore m (Maybe TeamId) From 26dda8b0fc1a5a763a684b62b74e7ead9426aca5 Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Fri, 10 Nov 2023 15:31:36 +0000 Subject: [PATCH 4/7] changelog --- changelog.d/3-bug-fixes/WPB-5133 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.d/3-bug-fixes/WPB-5133 b/changelog.d/3-bug-fixes/WPB-5133 index d1c499e30c..23077a2129 100644 --- a/changelog.d/3-bug-fixes/WPB-5133 +++ b/changelog.d/3-bug-fixes/WPB-5133 @@ -1 +1 @@ -Only allow team member search with SearchContacts permission +External partners search restriction enforced by backend From 47f5b293e48b6514588e5c74b93bf3f3920c7d7e Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Wed, 29 Nov 2023 15:17:54 +0000 Subject: [PATCH 5/7] members endpoint return only invitee --- integration/test/Test/Brig.hs | 44 ------------------------- integration/test/Test/Search.hs | 37 +++++++++++++++++++-- services/galley/src/Galley/API/Teams.hs | 10 ++---- 3 files changed, 37 insertions(+), 54 deletions(-) diff --git a/integration/test/Test/Brig.hs b/integration/test/Test/Brig.hs index a7816a7af9..f35fbd1856 100644 --- a/integration/test/Test/Brig.hs +++ b/integration/test/Test/Brig.hs @@ -3,8 +3,6 @@ module Test.Brig where import API.Brig qualified as BrigP import API.BrigInternal qualified as BrigI import API.Common (randomName) -import API.Galley -import API.Galley qualified as Galley import Data.Aeson.Types hiding ((.=)) import Data.Set qualified as Set import Data.String.Conversions @@ -15,48 +13,6 @@ import SetupHelpers import Testlib.Assertions import Testlib.Prelude -testSearchContactForExternalUsers :: HasCallStack => App () -testSearchContactForExternalUsers = do - owner <- randomUser OwnDomain def {BrigI.team = True} - tid <- owner %. "team" & asString - - partner <- createTeamMemberWithRole owner tid "partner" - tm1 <- createTeamMember owner tid - tm2 <- createTeamMember owner tid - - -- a team member can search for contacts - bindResponse (BrigP.searchContacts tm1 (owner %. "name") OwnDomain) $ \resp -> - resp.status `shouldMatchInt` 200 - - -- a partner is not allowed to search for contacts - bindResponse (BrigP.searchContacts partner (owner %. "name") OwnDomain) $ \resp -> - resp.status `shouldMatchInt` 403 - - -- a team member can see all other team members - bindResponse (Galley.getTeamMembers tm1 tid) $ \resp -> do - resp.status `shouldMatchInt` 200 - assertContainsUserIds resp [owner, tm1, tm2, partner] - - -- an external partner should see the person who invited them - bindResponse (Galley.getTeamMembers partner tid) $ \resp -> do - resp.status `shouldMatchInt` 200 - assertContainsUserIds resp [owner, partner] - - -- the team owner creates a conversation with the partner and another team member - void $ postConversation owner (defProteus {qualifiedUsers = [tm1, partner], team = Just tid}) >>= getJSON 201 - - -- now the external partner should also see the other team member - bindResponse (Galley.getTeamMembers partner tid) $ \resp -> do - resp.status `shouldMatchInt` 200 - assertContainsUserIds resp [owner, tm1, partner] - where - assertContainsUserIds :: Response -> [Value] -> App () - assertContainsUserIds resp users = do - members <- resp.json %. "members" & asList - userIds <- for members (\m -> m %. "user") - expected <- for users objId - userIds `shouldMatchSet` expected - testCrudFederationRemotes :: HasCallStack => App () testCrudFederationRemotes = do otherDomain <- asString OtherDomain diff --git a/integration/test/Test/Search.hs b/integration/test/Test/Search.hs index 99ffc44061..e8d6b2955d 100644 --- a/integration/test/Test/Search.hs +++ b/integration/test/Test/Search.hs @@ -3,6 +3,8 @@ module Test.Search where import API.Brig qualified as BrigP import API.BrigInternal qualified as BrigI import API.Common qualified as API +import API.Galley +import API.Galley qualified as Galley import API.GalleyInternal qualified as GalleyI import GHC.Stack import SetupHelpers @@ -15,14 +17,45 @@ import Testlib.Prelude testSearchContactForExternalUsers :: HasCallStack => App () testSearchContactForExternalUsers = do owner <- randomUser OwnDomain def {BrigI.team = True} - partner <- randomUser OwnDomain def {BrigI.team = True} + tid <- owner %. "team" & asString - bindResponse (GalleyI.putTeamMember partner (partner %. "team") (API.teamRole "partner")) $ \resp -> + partner <- createTeamMemberWithRole owner tid "partner" + tm1 <- createTeamMember owner tid + tm2 <- createTeamMember owner tid + + -- a team member can search for contacts + bindResponse (BrigP.searchContacts tm1 (owner %. "name") OwnDomain) $ \resp -> resp.status `shouldMatchInt` 200 + -- a partner is not allowed to search for contacts bindResponse (BrigP.searchContacts partner (owner %. "name") OwnDomain) $ \resp -> resp.status `shouldMatchInt` 403 + -- a team member can see all other team members + bindResponse (Galley.getTeamMembers tm1 tid) $ \resp -> do + resp.status `shouldMatchInt` 200 + assertContainsUserIds resp [owner, tm1, tm2, partner] + + -- an external partner should see the person who invited them + bindResponse (Galley.getTeamMembers partner tid) $ \resp -> do + resp.status `shouldMatchInt` 200 + assertContainsUserIds resp [owner, partner] + + -- the team owner creates a conversation with the partner and another team member + void $ postConversation owner (defProteus {qualifiedUsers = [tm1, partner], team = Just tid}) >>= getJSON 201 + + -- now the external partner should still only the person who invited them + bindResponse (Galley.getTeamMembers partner tid) $ \resp -> do + resp.status `shouldMatchInt` 200 + assertContainsUserIds resp [owner, partner] + where + assertContainsUserIds :: Response -> [Value] -> App () + assertContainsUserIds resp users = do + members <- resp.json %. "members" & asList + userIds <- for members (\m -> m %. "user") + expected <- for users objId + userIds `shouldMatchSet` expected + -------------------------------------------------------------------------------- -- FEDERATION SEARCH diff --git a/services/galley/src/Galley/API/Teams.hs b/services/galley/src/Galley/API/Teams.hs index 921edc2036..9dca92b0ec 100644 --- a/services/galley/src/Galley/API/Teams.hs +++ b/services/galley/src/Galley/API/Teams.hs @@ -89,7 +89,6 @@ import Galley.API.Update qualified as API import Galley.API.Util import Galley.App import Galley.Data.Conversation qualified as Data -import Galley.Data.Conversation.Types import Galley.Data.Services (BotMember) import Galley.Effects import Galley.Effects.BrigAccess qualified as E @@ -486,7 +485,6 @@ getTeamConversationRoles zusr tid = do getTeamMembers :: ( Member (ErrorS 'NotATeamMember) r, Member TeamStore r, - Member ConversationStore r, Member (TeamMemberStore CassandraPaging) r ) => Local UserId -> @@ -503,13 +501,9 @@ getTeamMembers lzusr tid mbMaxResults mbPagingState = do then E.listTeamMembers @CassandraPaging tid mState mLimit <&> toTeamMembersPage member else do -- If the user does not have the SearchContacts permission (e.g. the external partner), - -- we only return the team members they are in a conversation with plus the invitee - teamCids <- fmap (\tConv -> tConv ^. conversationId) <$> E.getTeamConversations tid - userTeamCids <- E.selectConversations uid teamCids - convs <- E.getConversations userTeamCids - let lConvMems = Conv.lmId <$> (convs >>= convLocalMembers) + -- we only return the person who invited them and the self user. let invitee = member ^. invitation <&> fst - let uids = nub (maybe (uid : lConvMems) (: uid : lConvMems) invitee) + let uids = maybe [uid] (: [uid]) invitee E.selectTeamMembersPaginated tid uids mState mLimit <&> toTeamMembersPage member where toTeamMembersPage :: TeamMember -> C.PageWithState TeamMember -> TeamMembersPage From 14adb0f5a1a0847493f4040c7a0f20db5f36a345 Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Thu, 30 Nov 2023 17:08:40 +0100 Subject: [PATCH 6/7] Update services/galley/src/Galley/API/Teams.hs Co-authored-by: Akshay Mankar --- services/galley/src/Galley/API/Teams.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/galley/src/Galley/API/Teams.hs b/services/galley/src/Galley/API/Teams.hs index 9dca92b0ec..6302467c8d 100644 --- a/services/galley/src/Galley/API/Teams.hs +++ b/services/galley/src/Galley/API/Teams.hs @@ -503,7 +503,7 @@ getTeamMembers lzusr tid mbMaxResults mbPagingState = do -- If the user does not have the SearchContacts permission (e.g. the external partner), -- we only return the person who invited them and the self user. let invitee = member ^. invitation <&> fst - let uids = maybe [uid] (: [uid]) invitee + let uids = uid : maybeToList invitee E.selectTeamMembersPaginated tid uids mState mLimit <&> toTeamMembersPage member where toTeamMembersPage :: TeamMember -> C.PageWithState TeamMember -> TeamMembersPage From ed0d2d7c5ead5f92c97c0dab83340022e28d9f67 Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Thu, 30 Nov 2023 16:12:37 +0000 Subject: [PATCH 7/7] typo --- integration/test/Test/Search.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration/test/Test/Search.hs b/integration/test/Test/Search.hs index e8d6b2955d..955afd744f 100644 --- a/integration/test/Test/Search.hs +++ b/integration/test/Test/Search.hs @@ -44,7 +44,7 @@ testSearchContactForExternalUsers = do -- the team owner creates a conversation with the partner and another team member void $ postConversation owner (defProteus {qualifiedUsers = [tm1, partner], team = Just tid}) >>= getJSON 201 - -- now the external partner should still only the person who invited them + -- now the external partner should still only see the person who invited them bindResponse (Galley.getTeamMembers partner tid) $ \resp -> do resp.status `shouldMatchInt` 200 assertContainsUserIds resp [owner, partner]