Skip to content

Commit 5179609

Browse files
battermannfisx
authored andcommitted
Update user groups with scim [WIP]
1 parent d2df1ec commit 5179609

File tree

5 files changed

+109
-5
lines changed

5 files changed

+109
-5
lines changed

integration/test/API/Spar.hs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,12 @@ createScimUserGroup domain token scimUserGroup = do
9797
body <- make scimUserGroup
9898
submit "POST" $ req & addJSON body . addHeader "Authorization" ("Bearer " <> token)
9999

100+
updateScimUserGroup :: (HasCallStack, MakesValue domain, MakesValue scimUserGroup) => domain -> String -> String -> scimUserGroup -> App Response
101+
updateScimUserGroup domain token groupId scimUserGroup = do
102+
req <- baseRequest domain Spar Versioned $ joinHttpPath ["scim", "v2", "Groups", groupId]
103+
body <- make scimUserGroup
104+
submit "PUT" $ req & addJSON body . addHeader "Authorization" ("Bearer " <> token)
105+
100106
-- | https://staging-nginz-https.zinfra.io/v12/api/swagger-ui/#/default/idp-create
101107
createIdp :: (HasCallStack, MakesValue user) => user -> SAML.IdPMetadata -> App Response
102108
createIdp user metadata = do

integration/test/Test/Spar.hs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,50 @@ testSparScimCreateUserGroup = do
420420
]
421421
createScimUserGroup OwnDomain tok scimUserGroup >>= assertSuccess
422422

423+
testSparScimUpdateUserGroup :: (HasCallStack) => App ()
424+
testSparScimUpdateUserGroup = do
425+
(owner, _, u1 : u2 : u3 : _) <- createTeam OwnDomain 4
426+
u1Id <- u1 %. "id" >>= asString
427+
u2Id <- u2 %. "id" >>= asString
428+
u3Id <- u3 %. "id" >>= asString
429+
tok <- createScimToken owner def >>= getJSON 200 >>= (%. "token") >>= asString
430+
let scimUserGroup =
431+
object
432+
[ "schemas" .= ["urn:ietf:params:scim:schemas:core:2.0:Group"],
433+
"displayName" .= "My funky group",
434+
"members"
435+
.= [ object
436+
[ "value" .= u1Id,
437+
"type" .= "User",
438+
"$ref" .= ("http://example.com:8088/scim/v2/Users/" <> u1Id)
439+
],
440+
object
441+
[ "value" .= u2Id,
442+
"type" .= "User",
443+
"$ref" .= ("http://example.com:8088/scim/v2/Users/" <> u2Id)
444+
]
445+
]
446+
]
447+
gid <- createScimUserGroup OwnDomain tok scimUserGroup >>= getJSON 200 >>= (%. "id") >>= asString
448+
let scimUserGroupUpdated =
449+
object
450+
[ "schemas" .= ["urn:ietf:params:scim:schemas:core:2.0:Group"],
451+
"displayName" .= "My even funkier group",
452+
"members"
453+
.= [ object
454+
[ "value" .= u2Id,
455+
"type" .= "User",
456+
"$ref" .= ("http://example.com:8088/scim/v2/Users/" <> u2Id)
457+
],
458+
object
459+
[ "value" .= u3Id,
460+
"type" .= "User",
461+
"$ref" .= ("http://example.com:8088/scim/v2/Users/" <> u3Id)
462+
]
463+
]
464+
]
465+
updateScimUserGroup OwnDomain tok gid scimUserGroupUpdated >>= assertSuccess
466+
423467
----------------------------------------------------------------------
424468
-- saml stuff
425469

libs/wire-subsystems/src/Wire/ScimSubsystem.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@ import Wire.API.User.Scim (SparTag)
99

1010
data ScimSubsystem m a where
1111
ScimCreateUserGroup :: TeamId -> SCG.Group -> ScimSubsystem m (SCG.StoredGroup SparTag)
12+
ScimUpdateUserGroup :: TeamId -> UserGroupId -> SCG.Group -> ScimSubsystem m (SCG.StoredGroup SparTag)
1213

1314
makeSem ''ScimSubsystem

libs/wire-subsystems/src/Wire/ScimSubsystem/Interpreter.hs

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import Wire.API.UserGroup
2121
import Wire.BrigAPIAccess (BrigAPIAccess)
2222
import Wire.BrigAPIAccess qualified as BrigAPI
2323
import Wire.ScimSubsystem
24+
import Wire.UserGroupStore qualified as UGStore
2425
import Wire.UserSubsystem
2526

2627
data ScimSubsystemConfig = ScimSubsystemConfig
@@ -35,6 +36,7 @@ interpretScimSubsystem ::
3536
InterpreterFor ScimSubsystem r
3637
interpretScimSubsystem = interpret $ \case
3738
ScimCreateUserGroup teamId scimGroup -> createScimGroupImpl teamId scimGroup
39+
ScimUpdateUserGroup teamId userGroupId scimGroup -> scimUpdateUserGroupImpl teamId userGroupId scimGroup
3840

3941
data ScimSubsystemError
4042
= ScimSubsystemError ScimError
@@ -57,9 +59,7 @@ createScimGroupImpl ::
5759
createScimGroupImpl teamId grp = do
5860
membersNotManagedByScim <- do
5961
let uidsAsText = (.value) <$> grp.members
60-
uids :: [UserId] <-
61-
let thrw = throw . ScimSubsystemInvalidGroupMemberId
62-
in forM uidsAsText $ either (thrw . Text.pack) pure . parseIdFromText
62+
uids :: [UserId] <- uidsAsText `mapM` parseMember
6363
users <- BrigAPI.getAccountsBy def {getByUserId = uids}
6464
pure $
6565
users
@@ -83,6 +83,53 @@ createScimGroupImpl teamId grp = do
8383
ScimSubsystemConfig scimBaseUri <- input
8484
pure $ toStoredGroup scimBaseUri ug
8585

86+
scimUpdateUserGroupImpl ::
87+
forall r.
88+
( Member UGStore.UserGroupStore r,
89+
Member (Input ScimSubsystemConfig) r,
90+
Member (Error ScimSubsystemError) r,
91+
Member UserSubsystem r,
92+
Member (Input (Local ())) r
93+
) =>
94+
TeamId ->
95+
UserGroupId ->
96+
SCG.Group ->
97+
Sem r (SCG.StoredGroup SparTag)
98+
scimUpdateUserGroupImpl teamId gid grp = do
99+
let includeChannels = False
100+
mExisting <- UGStore.getUserGroup teamId gid includeChannels
101+
existing <- maybe (scimThrow $ notFound "Group" (UUID.toText $ gid.toUUID)) pure mExisting
102+
when (existing.managedBy /= ManagedByScim) do
103+
scimThrow $ notFound "Group" (UUID.toText $ gid.toUUID)
104+
105+
ugName <- either (scimThrow . badRequest InvalidValue . Just) pure $ userGroupNameFromText grp.displayName
106+
107+
reqMemberIds <- for grp.members parseMember
108+
109+
let currentSet = Set.fromList (toList (runIdentity existing.members))
110+
requestedSet = Set.fromList reqMemberIds
111+
toAdd = requestedSet `Set.difference` currentSet
112+
113+
unless (null toAdd) do
114+
accounts <- inputQualifyLocal def {getByUserId = Set.toList toAdd} >>= getAccountsBy
115+
let nonScim = [userId u | u <- accounts, u.userManagedBy /= ManagedByScim]
116+
found = Set.fromList (userId <$> accounts)
117+
missing = Set.toList (toAdd `Set.difference` found)
118+
unless (null nonScim) do
119+
throw (ScimSubsystemScimGroupWithNonScimMembers nonScim)
120+
case missing of
121+
[] -> pure ()
122+
(u : _) -> scimThrow $ notFound "User" (idToText u)
123+
124+
when (existing.name /= ugName) do
125+
_ <- UGStore.updateUserGroup teamId gid UserGroupUpdate {name = ugName}
126+
pure ()
127+
128+
UGStore.updateUsers gid (V.fromList reqMemberIds)
129+
130+
ScimSubsystemConfig scimBaseUri <- input
131+
maybe (scimThrow $ notFound "Group" (UUID.toText $ gid.toUUID)) (pure . toStoredGroup scimBaseUri) =<< UGStore.getUserGroup teamId gid includeChannels
132+
86133
toStoredGroup :: Common.URI -> UserGroup -> SCG.StoredGroup SparTag
87134
toStoredGroup scimBaseUri ug = Meta.WithMeta meta (Common.WithId ug.id_ sg)
88135
where
@@ -113,3 +160,9 @@ toStoredGroup scimBaseUri ug = Meta.WithMeta meta (Common.WithId ug.id_ sg)
113160
| uid <- toList (runIdentity ug.members)
114161
]
115162
}
163+
164+
parseMember ::
165+
(Member (Error ScimSubsystemError) r) =>
166+
SCG.Member ->
167+
Sem r UserId
168+
parseMember m = parseIdFromText m.value & either (throw . ScimSubsystemInvalidGroupMemberId) pure

services/spar/src/Spar/Scim/Group.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ instance (AuthDB SparTag (Sem r), Member ScimSubsystem r) => SCG.GroupDB SparTag
6868
AuthInfo SparTag ->
6969
SCG.GroupId SparTag ->
7070
SCG.Group ->
71-
ScimHandler m (SCG.StoredGroup SparTag)
72-
putGroup = undefined
71+
ScimHandler (Sem r) (SCG.StoredGroup SparTag)
72+
putGroup ((.stiTeam) -> tid) gid grp = lift $ scimUpdateUserGroup tid gid grp
7373

7474
-- \| Modify an existing group.
7575
--

0 commit comments

Comments
 (0)