Skip to content

Commit 90fb90e

Browse files
smattingfisx
authored andcommitted
Forbid manual updates to email, handle, display for SCIM-managed users (#1320)
Co-authored-by: fisx <[email protected]>
1 parent c20958e commit 90fb90e

File tree

8 files changed

+163
-28
lines changed

8 files changed

+163
-28
lines changed

services/brig/src/Brig/API/Error.hs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ changeEmailError :: ChangeEmailError -> Error
116116
changeEmailError (InvalidNewEmail _ _) = StdError invalidEmail
117117
changeEmailError (EmailExists _) = StdError userKeyExists
118118
changeEmailError (ChangeBlacklistedEmail _) = StdError blacklistedEmail
119+
changeEmailError EmailManagedByScim = StdError $ propertyManagedByScim "email"
119120

120121
changePhoneError :: ChangePhoneError -> Error
121122
changePhoneError (InvalidNewPhone _) = StdError invalidPhone
@@ -130,6 +131,7 @@ changeHandleError :: ChangeHandleError -> Error
130131
changeHandleError ChangeHandleNoIdentity = StdError (noIdentity 2)
131132
changeHandleError ChangeHandleExists = StdError handleExists
132133
changeHandleError ChangeHandleInvalid = StdError invalidHandle
134+
changeHandleError ChangeHandleManagedByScim = StdError $ propertyManagedByScim "handle"
133135

134136
legalHoldLoginError :: LegalHoldLoginError -> Error
135137
legalHoldLoginError LegalHoldLoginNoBindingTeam = StdError noBindingTeam
@@ -207,6 +209,10 @@ phoneError PhoneNumberUnreachable = StdError invalidPhone
207209
phoneError PhoneNumberBarred = StdError blacklistedPhone
208210
phoneError (PhoneBudgetExhausted t) = RichError phoneBudgetExhausted (PhoneBudgetTimeout t) []
209211

212+
updateProfileError :: UpdateProfileError -> Error
213+
updateProfileError DisplayNameManagedByScim = StdError (propertyManagedByScim "name")
214+
updateProfileError (ProfileNotFound _) = StdError userNotFound
215+
210216
-- WAI Errors -----------------------------------------------------------------
211217

212218
tooManyProperties :: Wai.Error
@@ -427,6 +433,9 @@ insufficientTeamPermissions = Wai.Error status403 "insufficient-permissions" "In
427433
noBindingTeam :: Wai.Error
428434
noBindingTeam = Wai.Error status403 "no-binding-team" "Operation allowed only on binding teams"
429435

436+
propertyManagedByScim :: LText -> Wai.Error
437+
propertyManagedByScim prop = Wai.Error status403 "managed-by-scim" $ "Updating \"" <> prop <> "\" is not allowed, because it is managed by SCIM"
438+
430439
sameBindingTeamUsers :: Wai.Error
431440
sameBindingTeamUsers = Wai.Error status403 "same-binding-team-users" "Operation not allowed to binding team users."
432441

services/brig/src/Brig/API/Internal.hs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -322,17 +322,17 @@ deleteUserNoVerify uid = do
322322
changeSelfEmailMaybeSendH :: UserId ::: Bool ::: JsonRequest EmailUpdate -> Handler Response
323323
changeSelfEmailMaybeSendH (u ::: validate ::: req) = do
324324
email <- euEmail <$> parseJsonBody req
325-
changeSelfEmailMaybeSend u (if validate then ActuallySendEmail else DoNotSendEmail) email >>= \case
325+
changeSelfEmailMaybeSend u (if validate then ActuallySendEmail else DoNotSendEmail) email API.AllowSCIMUpdates >>= \case
326326
ChangeEmailResponseIdempotent -> pure (setStatus status204 empty)
327327
ChangeEmailResponseNeedsActivation -> pure (setStatus status202 empty)
328328

329329
data MaybeSendEmail = ActuallySendEmail | DoNotSendEmail
330330

331-
changeSelfEmailMaybeSend :: UserId -> MaybeSendEmail -> Email -> Handler ChangeEmailResponse
332-
changeSelfEmailMaybeSend u ActuallySendEmail email = do
333-
API.changeSelfEmail u email
334-
changeSelfEmailMaybeSend u DoNotSendEmail email = do
335-
API.changeEmail u email !>> changeEmailError >>= \case
331+
changeSelfEmailMaybeSend :: UserId -> MaybeSendEmail -> Email -> API.AllowSCIMUpdates -> Handler ChangeEmailResponse
332+
changeSelfEmailMaybeSend u ActuallySendEmail email allowScim = do
333+
API.changeSelfEmail u email allowScim
334+
changeSelfEmailMaybeSend u DoNotSendEmail email allowScim = do
335+
API.changeEmail u email allowScim !>> changeEmailError >>= \case
336336
ChangeEmailIdempotent -> pure ChangeEmailResponseIdempotent
337337
ChangeEmailNeedsActivation _ -> pure ChangeEmailResponseNeedsActivation
338338

@@ -518,7 +518,7 @@ updateHandleH (uid ::: _ ::: body) = empty <$ (updateHandle uid =<< parseJsonBod
518518
updateHandle :: UserId -> HandleUpdate -> Handler ()
519519
updateHandle uid (HandleUpdate handleUpd) = do
520520
handle <- validateHandle handleUpd
521-
API.changeHandle uid Nothing handle !>> changeHandleError
521+
API.changeHandle uid Nothing handle API.AllowSCIMUpdates !>> changeHandleError
522522

523523
updateUserNameH :: UserId ::: JSON ::: JsonRequest NameUpdate -> Handler Response
524524
updateUserNameH (uid ::: _ ::: body) = empty <$ (updateUserName uid =<< parseJsonBody body)
@@ -534,7 +534,7 @@ updateUserName uid (NameUpdate nameUpd) = do
534534
uupAccentId = Nothing
535535
}
536536
lift (Data.lookupUser WithPendingInvitations uid) >>= \case
537-
Just _ -> lift $ API.updateUser uid Nothing uu
537+
Just _ -> API.updateUser uid Nothing uu API.AllowSCIMUpdates !>> updateProfileError
538538
Nothing -> throwStd invalidUser
539539

540540
checkHandleInternalH :: Text -> Handler Response
@@ -547,7 +547,7 @@ checkHandleInternalH =
547547
getContactListH :: JSON ::: UserId -> Handler Response
548548
getContactListH (_ ::: uid) = do
549549
contacts <- lift $ API.lookupContactList uid
550-
return $ json $ (UserIds contacts)
550+
return $ json $ UserIds contacts
551551

552552
-- Deprecated
553553

services/brig/src/Brig/API/Public.hs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1290,7 +1290,7 @@ instance ToJSON GetActivationCodeResp where
12901290
updateUserH :: UserId ::: ConnId ::: JsonRequest Public.UserUpdate -> Handler Response
12911291
updateUserH (uid ::: conn ::: req) = do
12921292
uu <- parseJsonBody req
1293-
lift $ API.updateUser uid (Just conn) uu
1293+
API.updateUser uid (Just conn) uu API.ForbidSCIMUpdates !>> updateProfileError
12941294
return empty
12951295

12961296
changePhoneH :: UserId ::: ConnId ::: JsonRequest Public.PhoneUpdate -> Handler Response
@@ -1383,7 +1383,8 @@ changeHandleH (u ::: conn ::: req) = do
13831383
changeHandle :: UserId -> ConnId -> Public.HandleUpdate -> Handler ()
13841384
changeHandle u conn (Public.HandleUpdate h) = do
13851385
handle <- API.validateHandle h
1386-
API.changeHandle u (Just conn) handle !>> changeHandleError
1386+
-- TODO check here
1387+
API.changeHandle u (Just conn) handle API.ForbidSCIMUpdates !>> changeHandleError
13871388

13881389
beginPasswordResetH :: JSON ::: JsonRequest Public.NewPasswordReset -> Handler Response
13891390
beginPasswordResetH (_ ::: req) = do
@@ -1434,7 +1435,7 @@ customerExtensionCheckBlockedDomains email = do
14341435
changeSelfEmailH :: UserId ::: ConnId ::: JsonRequest Public.EmailUpdate -> Handler Response
14351436
changeSelfEmailH (u ::: _ ::: req) = do
14361437
email <- Public.euEmail <$> parseJsonBody req
1437-
API.changeSelfEmail u email >>= \case
1438+
API.changeSelfEmail u email API.ForbidSCIMUpdates >>= \case
14381439
ChangeEmailResponseIdempotent -> pure (setStatus status204 empty)
14391440
ChangeEmailResponseNeedsActivation -> pure (setStatus status202 empty)
14401441

services/brig/src/Brig/API/Types.hs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ data CreateUserError
103103
| -- | Some precondition on another Wire service failed. We propagate this error.
104104
ExternalPreconditionFailed Wai.Error
105105

106+
data UpdateProfileError
107+
= DisplayNameManagedByScim
108+
| ProfileNotFound UserId
109+
106110
data InvitationError
107111
= InviteeEmailExists UserId
108112
| InviteInvalidEmail Email
@@ -163,11 +167,13 @@ data ChangeEmailError
163167
= InvalidNewEmail !Email !String
164168
| EmailExists !Email
165169
| ChangeBlacklistedEmail !Email
170+
| EmailManagedByScim
166171

167172
data ChangeHandleError
168173
= ChangeHandleNoIdentity
169174
| ChangeHandleExists
170175
| ChangeHandleInvalid
176+
| ChangeHandleManagedByScim
171177

172178
data SendActivationCodeError
173179
= InvalidRecipient UserKey

services/brig/src/Brig/API/User.hs

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ module Brig.API.User
5656
checkHandles,
5757
isBlacklistedHandle,
5858
Data.reauthenticate,
59+
AllowSCIMUpdates (..),
5960

6061
-- * Activation
6162
sendActivationCode,
@@ -147,6 +148,11 @@ import Network.Wai.Utilities
147148
import qualified System.Logger.Class as Log
148149
import System.Logger.Message
149150

151+
data AllowSCIMUpdates
152+
= AllowSCIMUpdates
153+
| ForbidSCIMUpdates
154+
deriving (Show, Eq, Ord)
155+
150156
-------------------------------------------------------------------------------
151157
-- Create User
152158

@@ -394,13 +400,20 @@ checkRestrictedUserCreation new = do
394400
-------------------------------------------------------------------------------
395401
-- Update Profile
396402

397-
-- FUTUREWORK: this and other functions should refuse to modify a ManagedByScim user. See
398-
-- {#SparBrainDump} https://github.com/zinfra/backend-issues/issues/1632
399-
400-
updateUser :: UserId -> Maybe ConnId -> UserUpdate -> AppIO ()
401-
updateUser uid mconn uu = do
402-
Data.updateUser uid uu
403-
Intra.onUserEvent uid mconn (profileUpdated uid uu)
403+
updateUser :: UserId -> Maybe ConnId -> UserUpdate -> AllowSCIMUpdates -> ExceptT UpdateProfileError AppIO ()
404+
updateUser uid mconn uu allowScim = do
405+
for_ (uupName uu) $ \newName -> do
406+
mbUser <- lift $ Data.lookupUser WithPendingInvitations uid
407+
user <- maybe (throwE (ProfileNotFound uid)) pure mbUser
408+
unless
409+
( userManagedBy user /= ManagedByScim
410+
|| userDisplayName user == newName
411+
|| allowScim == AllowSCIMUpdates
412+
)
413+
$ throwE DisplayNameManagedByScim
414+
lift $ do
415+
Data.updateUser uid uu
416+
Intra.onUserEvent uid mconn (profileUpdated uid uu)
404417

405418
-------------------------------------------------------------------------------
406419
-- Update Locale
@@ -421,14 +434,21 @@ changeManagedBy uid conn (ManagedByUpdate mb) = do
421434
--------------------------------------------------------------------------------
422435
-- Change Handle
423436

424-
changeHandle :: UserId -> Maybe ConnId -> Handle -> ExceptT ChangeHandleError AppIO ()
425-
changeHandle uid mconn hdl = do
437+
changeHandle :: UserId -> Maybe ConnId -> Handle -> AllowSCIMUpdates -> ExceptT ChangeHandleError AppIO ()
438+
changeHandle uid mconn hdl allowScim = do
426439
when (isBlacklistedHandle hdl) $
427440
throwE ChangeHandleInvalid
428441
usr <- lift $ Data.lookupUser WithPendingInvitations uid
429442
case usr of
430443
Nothing -> throwE ChangeHandleNoIdentity
431-
Just u -> claim u
444+
Just u -> do
445+
unless
446+
( userManagedBy u /= ManagedByScim
447+
|| Just hdl == userHandle u
448+
|| allowScim == AllowSCIMUpdates
449+
)
450+
$ throwE ChangeHandleManagedByScim
451+
claim u
432452
where
433453
claim u = do
434454
unless (isJust (userIdentity u)) $
@@ -487,9 +507,9 @@ checkHandles check num = reverse <$> collectFree [] check num
487507

488508
-- | Call 'changeEmail' and process result: if email changes to itself, succeed, if not, send
489509
-- validation email.
490-
changeSelfEmail :: UserId -> Email -> ExceptT Error.Error AppIO ChangeEmailResponse
491-
changeSelfEmail u email = do
492-
changeEmail u email !>> Error.changeEmailError >>= \case
510+
changeSelfEmail :: UserId -> Email -> AllowSCIMUpdates -> ExceptT Error.Error AppIO ChangeEmailResponse
511+
changeSelfEmail u email allowScim = do
512+
changeEmail u email allowScim !>> Error.changeEmailError >>= \case
493513
ChangeEmailIdempotent ->
494514
pure ChangeEmailResponseIdempotent
495515
ChangeEmailNeedsActivation (usr, adata, en) -> do
@@ -505,8 +525,8 @@ changeSelfEmail u email = do
505525
(userIdentity usr)
506526

507527
-- | Prepare changing the email (checking a number of invariants).
508-
changeEmail :: UserId -> Email -> ExceptT ChangeEmailError AppIO ChangeEmailResult
509-
changeEmail u email = do
528+
changeEmail :: UserId -> Email -> AllowSCIMUpdates -> ExceptT ChangeEmailError AppIO ChangeEmailResult
529+
changeEmail u email allowScim = do
510530
em <-
511531
either
512532
(throwE . InvalidNewEmail email)
@@ -525,6 +545,11 @@ changeEmail u email = do
525545
-- The user already has an email address and the new one is exactly the same
526546
Just current | current == em -> return ChangeEmailIdempotent
527547
_ -> do
548+
unless
549+
( userManagedBy usr /= ManagedByScim
550+
|| allowScim == AllowSCIMUpdates
551+
)
552+
$ throwE EmailManagedByScim
528553
timeout <- setActivationTimeout <$> view settings
529554
act <- lift $ Data.newActivation ek timeout (Just u)
530555
return $ ChangeEmailNeedsActivation (usr, act, em)

services/spar/test-integration/Test/Spar/Scim/UserSpec.hs

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import Brig.Types.Intra (AccountStatus (Active, PendingInvitation, Suspended), a
3434
import Brig.Types.User as Brig
3535
import qualified Control.Exception
3636
import Control.Lens
37+
import Control.Monad.Random (Random (randomRIO))
3738
import Control.Monad.Trans.Except
3839
import Control.Monad.Trans.Maybe
3940
import Control.Retry (exponentialBackoff, limitRetries, recovering)
@@ -47,6 +48,7 @@ import Data.Ix (inRange)
4748
import Data.String.Conversions (cs)
4849
import Data.Text.Encoding (encodeUtf8)
4950
import Imports
51+
import qualified Network.Wai.Utilities.Error as Wai
5052
import qualified SAML2.WebSSO.Test.MockResponse as SAML
5153
import qualified SAML2.WebSSO.Types as SAML
5254
import qualified Spar.Data as Data
@@ -81,6 +83,7 @@ spec = do
8183
specAzureQuirks
8284
specEmailValidation
8385
specSuspend
86+
specSCIMManaged
8487
describe "CRUD operations maintain invariants in mapScimToBrig, mapBrigToScim." $ do
8588
it "..." $ do
8689
pendingWith "this is a job for quickcheck-state-machine"
@@ -193,7 +196,7 @@ specCreateUser = describe "POST /Users" $ do
193196
context "team has one SAML IdP" $ do
194197
it "creates a user in an existing team" $ do
195198
testCreateUserWithSamlIdP
196-
it "adds a Wire scheme to the user record" $ testSchemaIsAdded
199+
it "adds a Wire scheme to the user record" $ testSchemaIsAdded
197200
it "requires externalId to be present" $ testExternalIdIsRequired
198201
it "rejects invalid handle" $ testCreateRejectsInvalidHandle
199202
it "rejects occupied handle" $ testCreateRejectsTakenHandle
@@ -1594,3 +1597,42 @@ specEmailValidation = do
15941597
context "not enabled in team" . it "does not give user email" $ do
15951598
(uid, _) <- setup False
15961599
eventually $ checkEmail uid Nothing
1600+
1601+
specSCIMManaged :: SpecWith TestEnv
1602+
specSCIMManaged = do
1603+
describe "SCIM-managed users" $ do
1604+
it "cannot manually update their email, handle or name" $ do
1605+
env <- ask
1606+
let brig = env ^. teBrig
1607+
1608+
(tok, _) <- registerIdPAndScimToken
1609+
user <- randomScimUser
1610+
storedUser <- createUser tok user
1611+
let uid = Scim.id . Scim.thing $ storedUser
1612+
1613+
do
1614+
email <- randomEmail
1615+
call $
1616+
changeEmailBrig brig uid email !!! do
1617+
(fmap Wai.label . responseJsonEither @Wai.Error) === const (Right "managed-by-scim")
1618+
statusCode === const 403
1619+
1620+
do
1621+
handleTxt <- randomAlphaNum
1622+
call $
1623+
changeHandleBrig brig uid handleTxt !!! do
1624+
(fmap Wai.label . responseJsonEither @Wai.Error) === const (Right "managed-by-scim")
1625+
statusCode === const 403
1626+
1627+
do
1628+
displayName <- Name <$> randomAlphaNum
1629+
let uupd = UserUpdate (Just displayName) Nothing Nothing Nothing
1630+
call $
1631+
updateProfileBrig brig uid uupd !!! do
1632+
(fmap Wai.label . responseJsonEither @Wai.Error) === const (Right "managed-by-scim")
1633+
statusCode === const 403
1634+
where
1635+
randomAlphaNum :: MonadIO m => m Text
1636+
randomAlphaNum = liftIO $ do
1637+
nrs <- replicateM 21 (randomRIO (97, 122)) -- a-z
1638+
return (cs (map chr nrs))

services/spar/test-integration/Util/Core.hs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ module Util.Core
5252
randomEmail,
5353
defPassword,
5454
getUserBrig,
55+
changeHandleBrig,
56+
updateProfileBrig,
5557
createUserWithTeam,
5658
createUserWithTeamDisableSSO,
5759
getSSOEnabledInternal,
@@ -71,6 +73,7 @@ module Util.Core
7173
nextUserRef,
7274
createRandomPhoneUser,
7375
zUser,
76+
zConn,
7477
ping,
7578
makeTestIdP,
7679
getTestSPMetadata,
@@ -192,6 +195,7 @@ import qualified Web.Cookie as Web
192195
import Wire.API.Team.Feature (TeamFeatureStatusValue (..))
193196
import qualified Wire.API.Team.Feature as Public
194197
import qualified Wire.API.Team.Invitation as TeamInvitation
198+
import Wire.API.User (HandleUpdate (HandleUpdate), UserUpdate)
195199
import qualified Wire.API.User as User
196200

197201
-- | Call 'mkEnv' with options from config files.
@@ -1203,3 +1207,35 @@ stdInvitationRequest = stdInvitationRequest' Nothing Nothing
12031207
stdInvitationRequest' :: Maybe User.Locale -> Maybe Galley.Role -> User.Email -> TeamInvitation.InvitationRequest
12041208
stdInvitationRequest' loc role email =
12051209
TeamInvitation.InvitationRequest loc role Nothing email Nothing
1210+
1211+
changeHandleBrig ::
1212+
(MonadCatch m, MonadIO m, MonadHttp m, HasCallStack) =>
1213+
BrigReq ->
1214+
UserId ->
1215+
Text ->
1216+
m ResponseLBS
1217+
changeHandleBrig brig uid handlTxt = do
1218+
put
1219+
( brig
1220+
. path "/self/handle"
1221+
. zUser uid
1222+
. zConn "user"
1223+
. contentJson
1224+
. json (HandleUpdate handlTxt)
1225+
)
1226+
1227+
updateProfileBrig ::
1228+
(MonadCatch m, MonadIO m, MonadHttp m, HasCallStack) =>
1229+
BrigReq ->
1230+
UserId ->
1231+
UserUpdate ->
1232+
m ResponseLBS
1233+
updateProfileBrig brig uid uupd =
1234+
put
1235+
( brig
1236+
. path "/self"
1237+
. zUser uid
1238+
. zConn "user"
1239+
. contentJson
1240+
. json uupd
1241+
)

0 commit comments

Comments
 (0)