From 8bff71c19e079930a1ee8bca09fd47cd42fd0ea7 Mon Sep 17 00:00:00 2001 From: Gautier DI FOLCO Date: Fri, 10 Jul 2026 17:41:42 +0200 Subject: [PATCH 1/4] WPB-21744: invalidate pending email update when a user is put under SCIM control When a user's managed_by transitioned from Wire to SCIM, a pending email-address update was left dangling: team settings kept offering a "resend verification" action that failed with 403 managed-by-scim, and a stale activation link could still change a SCIM-managed user's email outside of SCIM. - Add internal brig endpoint DELETE /i/users/:uid/pending-email-update that removes the user's unvalidated email and its activation token. - Call it from spar's two Wire->SCIM transition sites (lazy SCIM adoption via GET /Users/:id and getUserById). - Add defense-in-depth: brig's email-activation path now rejects SCIM-managed users (InvalidActivationManagedByScim -> 403 managed-by-scim). - Add ActivationCodeStore.deleteActivationCode (effect + Cassandra + in-memory impls). --- changelog.d/3-bug-fixes/WPB-21744 | 1 + .../src/Wire/API/Routes/Internal/Brig.hs | 13 ++++++++++ libs/wire-api/src/Wire/API/User.hs | 4 +++- .../src/Wire/ActivationCodeStore.hs | 6 +++++ .../src/Wire/ActivationCodeStore/Cassandra.hs | 13 ++++++++++ .../wire-subsystems/src/Wire/BrigAPIAccess.hs | 1 + .../src/Wire/BrigAPIAccess/Rpc.hs | 14 +++++++++++ libs/wire-subsystems/src/Wire/StoredUser.hs | 4 ++++ .../MockInterpreters/ActivationCodeStore.hs | 24 ++++++++++--------- services/brig/src/Brig/API/Error.hs | 1 + services/brig/src/Brig/API/Internal.hs | 17 +++++++++++++ services/brig/src/Brig/Data/Activation.hs | 8 +++++++ services/spar/src/Spar/Scim/User.hs | 8 ++++++- 13 files changed, 101 insertions(+), 13 deletions(-) create mode 100644 changelog.d/3-bug-fixes/WPB-21744 diff --git a/changelog.d/3-bug-fixes/WPB-21744 b/changelog.d/3-bug-fixes/WPB-21744 new file mode 100644 index 00000000000..3825ca54830 --- /dev/null +++ b/changelog.d/3-bug-fixes/WPB-21744 @@ -0,0 +1 @@ +When a user is put under SCIM control, any pending email-address update is now invalidated (the unvalidated email and its activation token are removed). Previously, team settings kept offering a "resend verification" action that could not succeed (failing with `403 managed-by-scim`), and a stale activation link could still change a SCIM-managed user's email outside of SCIM. diff --git a/libs/wire-api/src/Wire/API/Routes/Internal/Brig.hs b/libs/wire-api/src/Wire/API/Routes/Internal/Brig.hs index 4a0e907c808..0bf24d834c0 100644 --- a/libs/wire-api/src/Wire/API/Routes/Internal/Brig.hs +++ b/libs/wire-api/src/Wire/API/Routes/Internal/Brig.hs @@ -535,6 +535,19 @@ type AccountAPI = :> ReqBody '[Servant.JSON] ManagedByUpdate :> Put '[Servant.JSON] NoContent ) + :<|> Named + "iDeletePendingEmailUpdate" + ( Summary + "Invalidate a pending email-address update for a user. \ + \Used by spar when a user is put under SCIM control, so that team \ + \settings no longer offers a 'resend verification' action that cannot \ + \succeed, and stale activation links cannot change a SCIM-managed \ + \user's email." + :> "users" + :> Capture "uid" UserId + :> "pending-email-update" + :> Delete '[Servant.JSON] NoContent + ) :<|> Named "iPutRichInfo" ( "users" diff --git a/libs/wire-api/src/Wire/API/User.hs b/libs/wire-api/src/Wire/API/User.hs index 6e7bd0ba119..afaf2b37a65 100644 --- a/libs/wire-api/src/Wire/API/User.hs +++ b/libs/wire-api/src/Wire/API/User.hs @@ -905,6 +905,7 @@ data RegisterError | RegisterErrorUserCreationRestricted | RegisterErrorEphemeralUserCreationDisabled | RegisterErrorScimDisplayNameMismatch + | RegisterErrorInvalidActivationManagedByScim deriving (Show, Generic) deriving (AsUnion RegisterErrorResponses) via GenericAsUnion RegisterErrorResponses RegisterError @@ -923,7 +924,8 @@ type RegisterErrorResponses = ErrorResponse 'TooManyTeamMembers, ErrorResponse 'UserCreationRestricted, ErrorResponse 'EphemeralUserCreationDisabled, - ErrorResponse 'NameManagedByScim + ErrorResponse 'NameManagedByScim, + ErrorResponse 'EmailManagedByScim ] type RegisterResponses = diff --git a/libs/wire-subsystems/src/Wire/ActivationCodeStore.hs b/libs/wire-subsystems/src/Wire/ActivationCodeStore.hs index 06b331cebb1..0b6f656e363 100644 --- a/libs/wire-subsystems/src/Wire/ActivationCodeStore.hs +++ b/libs/wire-subsystems/src/Wire/ActivationCodeStore.hs @@ -54,5 +54,11 @@ data ActivationCodeStore :: Effect where -- | The user with whom to associate the activation code. Maybe UserId -> ActivationCodeStore m Activation + -- | Delete a pending activation code for a given 'EmailKey', if any. + -- This is used to invalidate a pending email-address update (e.g. when a + -- user is put under SCIM control). + DeleteActivationCode :: + EmailKey -> + ActivationCodeStore m () makeSem ''ActivationCodeStore diff --git a/libs/wire-subsystems/src/Wire/ActivationCodeStore/Cassandra.hs b/libs/wire-subsystems/src/Wire/ActivationCodeStore/Cassandra.hs index 56ff6f247a5..24d2f3c2737 100644 --- a/libs/wire-subsystems/src/Wire/ActivationCodeStore/Cassandra.hs +++ b/libs/wire-subsystems/src/Wire/ActivationCodeStore/Cassandra.hs @@ -42,6 +42,7 @@ interpretActivationCodeStoreToCassandra casClient = liftIO (mkActivationKey ek) >>= retry x1 . query1 cql . params LocalQuorum . Identity NewActivationCode ek timeout uid -> newActivationCodeImpl ek timeout uid + DeleteActivationCode ek -> deleteActivationCodeImpl ek where cql :: PrepQuery R (Identity ActivationKey) (Maybe UserId, ActivationCode) cql = @@ -72,6 +73,15 @@ newActivationCodeImpl uk timeout u = do ActivationCode . Ascii.unsafeFromText . pack . printf "%06d" <$> randIntegerZeroToNMinusOne 1000000 +-- | Delete a pending activation code for a given 'EmailKey', if any. +deleteActivationCodeImpl :: + (MonadClient m) => + EmailKey -> + m () +deleteActivationCodeImpl uk = do + key <- liftIO $ mkActivationKey uk + retry x5 . write keyDelete $ params LocalQuorum (Identity key) + -------------------------------------------------------------------------------- -- Utilities @@ -91,6 +101,9 @@ keyInsert = \(key, key_type, key_text, code, user, retries) VALUES \ \(? , ? , ? , ? , ? , ? ) USING TTL ?" +keyDelete :: PrepQuery W (Identity ActivationKey) () +keyDelete = "DELETE FROM activation_keys WHERE key = ?" + -- | Max. number of activation attempts per 'ActivationKey'. maxAttempts :: Int32 maxAttempts = 3 diff --git a/libs/wire-subsystems/src/Wire/BrigAPIAccess.hs b/libs/wire-subsystems/src/Wire/BrigAPIAccess.hs index ceec1624944..972df7782cf 100644 --- a/libs/wire-subsystems/src/Wire/BrigAPIAccess.hs +++ b/libs/wire-subsystems/src/Wire/BrigAPIAccess.hs @@ -151,6 +151,7 @@ data BrigAPIAccess m a where SetName :: UserId -> Name -> BrigAPIAccess m () SetHandle :: UserId -> Handle -> BrigAPIAccess m () SetManagedBy :: UserId -> ManagedBy -> BrigAPIAccess m () + DeletePendingEmailUpdate :: UserId -> BrigAPIAccess m () SetSSOId :: UserId -> UserSSOId -> BrigAPIAccess m () SetRichInfo :: UserId -> RichInfo -> BrigAPIAccess m () SetLocale :: UserId -> Maybe Locale -> BrigAPIAccess m () diff --git a/libs/wire-subsystems/src/Wire/BrigAPIAccess/Rpc.hs b/libs/wire-subsystems/src/Wire/BrigAPIAccess/Rpc.hs index f4bd3d9ea47..0c754c43f5a 100644 --- a/libs/wire-subsystems/src/Wire/BrigAPIAccess/Rpc.hs +++ b/libs/wire-subsystems/src/Wire/BrigAPIAccess/Rpc.hs @@ -167,6 +167,8 @@ interpretBrigAccess brigEndpoint = setHandle uid handle SetManagedBy uid managedBy -> setManagedBy uid managedBy + DeletePendingEmailUpdate uid -> + deletePendingEmailUpdate uid SetSSOId uid ssoId -> setSSOId uid ssoId SetRichInfo uid richInfo -> @@ -996,6 +998,18 @@ setManagedBy buid managedBy = do unless (statusCode resp == 200) $ rethrow "brig" resp +deletePendingEmailUpdate :: + (Member Rpc r, Member (Input Endpoint) r, Member (Error RpcException) r) => + UserId -> + Sem r () +deletePendingEmailUpdate buid = do + resp <- + brigRequest $ + method DELETE + . paths ["i", "users", toByteString' buid, "pending-email-update"] + unless (statusCode resp == 200) $ + rethrow "brig" resp + setSSOId :: (Member Rpc r, Member (Input Endpoint) r, Member (Error RpcException) r) => UserId -> diff --git a/libs/wire-subsystems/src/Wire/StoredUser.hs b/libs/wire-subsystems/src/Wire/StoredUser.hs index 414598883f3..a4321ee1407 100644 --- a/libs/wire-subsystems/src/Wire/StoredUser.hs +++ b/libs/wire-subsystems/src/Wire/StoredUser.hs @@ -108,6 +108,10 @@ setStoredUserHandle newHandle user = user {handle = Just newHandle} hasPendingInvitation :: StoredUser -> Bool hasPendingInvitation u = u.status == Just PendingInvitation +-- | The pending (unvalidated) email address of a stored user, if any. +storedUserEmailUnvalidated :: StoredUser -> Maybe EmailAddress +storedUserEmailUnvalidated u = u.emailUnvalidated + mkUserFromStored :: Domain -> Locale -> StoredUser -> User mkUserFromStored domain defaultLocale storedUser = let expiration = if storedUser.status == Just Ephemeral then storedUser.expires else Nothing diff --git a/libs/wire-subsystems/test/unit/Wire/MockInterpreters/ActivationCodeStore.hs b/libs/wire-subsystems/test/unit/Wire/MockInterpreters/ActivationCodeStore.hs index dfb21478e8c..7250d3047fa 100644 --- a/libs/wire-subsystems/test/unit/Wire/MockInterpreters/ActivationCodeStore.hs +++ b/libs/wire-subsystems/test/unit/Wire/MockInterpreters/ActivationCodeStore.hs @@ -42,14 +42,16 @@ emailKeyToCode = inMemoryActivationCodeStoreInterpreter :: (Member (State (Map EmailKey (Maybe UserId, ActivationCode))) r) => InterpreterFor ActivationCodeStore r -inMemoryActivationCodeStoreInterpreter = interpret \case - LookupActivationCode ek -> gets (!? ek) - NewActivationCode ek _ uid -> do - let key = - ActivationKey - . Ascii.encodeBase64Url - . T.encodeUtf8 - . emailKeyUniq - $ ek - c = emailKeyToCode ek - modify (insert ek (uid, c)) $> Activation key c +inMemoryActivationCodeStoreInterpreter = + interpret \case + LookupActivationCode ek -> gets (!? ek) + NewActivationCode ek _ uid -> do + let key = + ActivationKey + . Ascii.encodeBase64Url + . T.encodeUtf8 + . emailKeyUniq + $ ek + c = emailKeyToCode ek + modify (insert ek (uid, c)) $> Activation key c + DeleteActivationCode ek -> modify (delete ek) diff --git a/services/brig/src/Brig/API/Error.hs b/services/brig/src/Brig/API/Error.hs index 733550000c8..5a34f5fa532 100644 --- a/services/brig/src/Brig/API/Error.hs +++ b/services/brig/src/Brig/API/Error.hs @@ -57,6 +57,7 @@ actError InvalidActivationCodeWrongUser = StdError (errorToWai @'E.InvalidActiva actError InvalidActivationCodeWrongCode = StdError (errorToWai @'E.InvalidActivationCodeWrongCode) actError (InvalidActivationEmail _ _) = StdError (errorToWai @'E.InvalidEmail) actError (InvalidActivationPhone _) = StdError (errorToWai @'E.InvalidPhone) +actError InvalidActivationManagedByScim = StdError (errorToWai @'E.EmailManagedByScim) pwResetError :: PasswordResetError -> HttpError pwResetError InvalidPasswordResetKey = StdError (errorToWai @'E.InvalidPasswordResetKey) diff --git a/services/brig/src/Brig/API/Internal.hs b/services/brig/src/Brig/API/Internal.hs index 6a6f17dbb80..80d98038cde 100644 --- a/services/brig/src/Brig/API/Internal.hs +++ b/services/brig/src/Brig/API/Internal.hs @@ -90,6 +90,7 @@ import Wire.API.UserGroup (UserGroup) import Wire.API.UserGroup.Pagination import Wire.API.UserMap import Wire.ActivationCodeStore (ActivationCodeStore) +import Wire.ActivationCodeStore qualified as ActivationCode import Wire.AppStore (AppStore) import Wire.AppStore qualified as AppStore import Wire.AppSubsystem (AppSubsystem) @@ -128,6 +129,7 @@ import Wire.Sem.Concurrency import Wire.Sem.Now (Now) import Wire.Sem.Random (Random) import Wire.SparAPIAccess (SparAPIAccess) +import Wire.StoredUser (storedUserEmailUnvalidated) import Wire.TeamInvitationSubsystem import Wire.TeamSubsystem (TeamSubsystem) import Wire.UserGroupSubsystem @@ -282,6 +284,7 @@ accountAPI = :<|> Named @"iPutUserSsoId" updateSSOIdH :<|> Named @"iDeleteUserSsoId" deleteSSOIdH :<|> Named @"iPutManagedBy" updateManagedByH + :<|> Named @"iDeletePendingEmailUpdate" deletePendingEmailUpdateH :<|> Named @"iPutRichInfo" updateRichInfoH :<|> Named @"iPutHandle" updateHandleH :<|> Named @"iPutUserName" updateUserNameH @@ -898,6 +901,20 @@ updateManagedByH :: (Member UserStore r) => UserId -> ManagedByUpdate -> (Handle updateManagedByH uid (ManagedByUpdate managedBy) = do NoContent <$ lift (liftSem $ UserStore.updateManagedBy uid managedBy) +deletePendingEmailUpdateH :: + ( Member UserStore r, + Member ActivationCodeStore r + ) => + UserId -> + (Handler r) NoContent +deletePendingEmailUpdateH uid = do + mUser <- lift . liftSem $ UserStore.getUser uid + for_ (storedUserEmailUnvalidated =<< mUser) $ \email -> + lift . liftSem $ do + ActivationCode.deleteActivationCode (mkEmailKey email) + UserStore.deleteEmailUnvalidated uid + pure NoContent + updateRichInfoH :: (Member UserStore r) => UserId -> RichInfoUpdate -> (Handler r) NoContent updateRichInfoH uid rup = NoContent <$ do diff --git a/services/brig/src/Brig/Data/Activation.hs b/services/brig/src/Brig/Data/Activation.hs index 9c9eb1446e1..e1f947e9c93 100644 --- a/services/brig/src/Brig/Data/Activation.hs +++ b/services/brig/src/Brig/Data/Activation.hs @@ -53,6 +53,7 @@ data ActivationError | InvalidActivationCodeWrongCode | InvalidActivationEmail !EmailAddress !String | InvalidActivationPhone !Phone + | InvalidActivationManagedByScim activationErrorToRegisterError :: ActivationError -> RegisterError activationErrorToRegisterError = \case @@ -61,6 +62,7 @@ activationErrorToRegisterError = \case InvalidActivationCodeWrongCode -> RegisterErrorInvalidActivationCodeWrongCode InvalidActivationEmail _ _ -> RegisterErrorInvalidEmail InvalidActivationPhone _ -> RegisterErrorInvalidPhone + InvalidActivationManagedByScim -> RegisterErrorInvalidActivationManagedByScim data ActivationEvent = AccountActivated !User @@ -100,6 +102,12 @@ activateKey k c u = do let a' = a {userIdentity = Just ident} pure . Just $ AccountActivated a' Just _ -> do + -- A SCIM-managed user's email can only be changed through SCIM. Reject any + -- activation that would change a SCIM-managed user's email (defense in depth; + -- pending email-update tokens are also invalidated when a user is put under SCIM + -- control). + when (userManagedBy a == ManagedByScim) $ + throwE InvalidActivationManagedByScim let profileNeedsUpdate = Just (emailKeyOrig key) /= userEmail a oldKey :: Maybe EmailKey = mkEmailKey <$> userEmail a in handleExistingIdentity uid profileNeedsUpdate oldKey key diff --git a/services/spar/src/Spar/Scim/User.hs b/services/spar/src/Spar/Scim/User.hs index 118e00d2ed2..ad530d8412c 100644 --- a/services/spar/src/Spar/Scim/User.hs +++ b/services/spar/src/Spar/Scim/User.hs @@ -1000,8 +1000,12 @@ synthesizeStoredUser acc veid = writeState oldAccessTimes oldManagedBy oldRichInfo storedUser = do when (isNothing oldAccessTimes) $ ScimUserTimesStore.write storedUser - when (oldManagedBy /= ManagedByScim) $ + when (oldManagedBy /= ManagedByScim) $ do BrigAPIAccess.setManagedBy uid ManagedByScim + -- Invalidate any pending email-address update: a SCIM-managed user's + -- email can only be changed through SCIM, so the pending update token + -- and the unvalidated email must be removed. + BrigAPIAccess.deletePendingEmailUpdate uid let newRichInfo = view ST.sueRichInfo . Scim.extra . Scim.value . Scim.thing $ storedUser when (oldRichInfo /= newRichInfo) $ BrigAPIAccess.setRichInfo uid newRichInfo @@ -1130,6 +1134,8 @@ getUserById midp stiTeam uid = do -- set managed_by when (userManagedBy brigUser /= ManagedByScim) do lift $ BrigAPIAccess.setManagedBy uid ManagedByScim + -- Invalidate any pending email-address update (see comment above). + lift $ BrigAPIAccess.deletePendingEmailUpdate uid -- remove dangling entry from spar.user_v2 table (cassandra) case mbOldVeid of Just oldVeid | ST.veidUref newVeid /= ST.veidUref oldVeid -> do From b817c02d25481b5eef17b6ffb84f09043290591c Mon Sep 17 00:00:00 2001 From: Gautier DI FOLCO Date: Sun, 12 Jul 2026 12:46:49 +0200 Subject: [PATCH 2/4] Hello CI From c85b33a75bdd3cb2830dae708055d584b33d2868 Mon Sep 17 00:00:00 2001 From: Gautier DI FOLCO Date: Mon, 13 Jul 2026 18:00:42 +0200 Subject: [PATCH 3/4] fix(sven): add tests --- integration/test/Test/Spar.hs | 44 +++++++++++++++++++++ libs/wire-subsystems/src/Wire/StoredUser.hs | 4 -- services/brig/src/Brig/API/Internal.hs | 4 +- 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/integration/test/Test/Spar.hs b/integration/test/Test/Spar.hs index 26cd2f43207..fff111393d5 100644 --- a/integration/test/Test/Spar.hs +++ b/integration/test/Test/Spar.hs @@ -1564,3 +1564,47 @@ testScimUserChangeNameOnRegisteringIgnoredV16 = do registerUserWithVersioned (ExplicitVersion 16) OwnDomain email code newProfilename `bindResponse` \resp -> do resp.status `shouldMatchInt` 201 resp.json %. "name" `shouldMatch` scimUserDisplayName + +-- | A pending email update (emailUnvalidated + activation code) must be +-- invalidated when the user transitions to SCIM control. Getting a Wire-managed +-- user via the SCIM API triggers 'getUserById' -> 'synthesizeStoredUser' -> +-- 'ManagedByScim' -> 'deletePendingEmailUpdate' in spar, which calls the brig +-- internal endpoint that deletes both the activation code and the pending +-- email entry. See WPB-21744 / PR #5333. +testSparScimInvalidatesPendingEmail :: (HasCallStack) => App () +testSparScimInvalidatesPendingEmail = do + -- 1. Create a team with an owner and one Wire-managed member. + (owner, _tid, [mem]) <- createTeam OwnDomain 2 + memberId <- mem %. "id" >>= asString + memberEmail <- mem %. "email" >>= asString + + -- 2. Login as the member and initiate an email update. This creates a + -- pending emailUnvalidated entry plus an activation code for the new email. + (cookie, token) <- + login mem memberEmail defPassword `bindResponse` \resp -> do + resp.status `shouldMatchInt` 200 + tok <- resp.json %. "access_token" & asString + let c = fromJust $ getCookie "zuid" resp + pure ("zuid=" <> c, tok) + newEmail <- randomEmail + updateEmail mem newEmail cookie token >>= assertSuccess + + -- 3. Verify the activation code exists for the pending (unvalidated) email. + getActivationCode OwnDomain newEmail >>= assertStatus 200 + + -- 4. Transition the member to SCIM control. Reading the user through the + -- SCIM API triggers getUserById -> synthesizeStoredUser -> (since the user + -- is email-only and not yet SCIM-managed) setManagedBy ManagedByScim and + -- deletePendingEmailUpdate. + tok <- createScimTokenV6 owner def >>= getJSON 200 >>= (%. "token") >>= asString + getScimUser OwnDomain tok memberId >>= assertStatus 200 + + -- 5. The activation code for the pending email has been invalidated. + getActivationCode OwnDomain newEmail >>= assertStatus 404 + + -- 6. The member's email is unchanged: the SCIM transition must not promote + -- the unvalidated email, only drop the pending update. + bindResponse (getUsersId OwnDomain [memberId]) $ \resp -> do + resp.status `shouldMatchInt` 200 + u <- resp.json & asList >>= assertOne + u %. "email" `shouldMatch` memberEmail diff --git a/libs/wire-subsystems/src/Wire/StoredUser.hs b/libs/wire-subsystems/src/Wire/StoredUser.hs index a4321ee1407..414598883f3 100644 --- a/libs/wire-subsystems/src/Wire/StoredUser.hs +++ b/libs/wire-subsystems/src/Wire/StoredUser.hs @@ -108,10 +108,6 @@ setStoredUserHandle newHandle user = user {handle = Just newHandle} hasPendingInvitation :: StoredUser -> Bool hasPendingInvitation u = u.status == Just PendingInvitation --- | The pending (unvalidated) email address of a stored user, if any. -storedUserEmailUnvalidated :: StoredUser -> Maybe EmailAddress -storedUserEmailUnvalidated u = u.emailUnvalidated - mkUserFromStored :: Domain -> Locale -> StoredUser -> User mkUserFromStored domain defaultLocale storedUser = let expiration = if storedUser.status == Just Ephemeral then storedUser.expires else Nothing diff --git a/services/brig/src/Brig/API/Internal.hs b/services/brig/src/Brig/API/Internal.hs index 80d98038cde..f175554db71 100644 --- a/services/brig/src/Brig/API/Internal.hs +++ b/services/brig/src/Brig/API/Internal.hs @@ -129,7 +129,7 @@ import Wire.Sem.Concurrency import Wire.Sem.Now (Now) import Wire.Sem.Random (Random) import Wire.SparAPIAccess (SparAPIAccess) -import Wire.StoredUser (storedUserEmailUnvalidated) +import Wire.StoredUser (StoredUser (emailUnvalidated)) import Wire.TeamInvitationSubsystem import Wire.TeamSubsystem (TeamSubsystem) import Wire.UserGroupSubsystem @@ -909,7 +909,7 @@ deletePendingEmailUpdateH :: (Handler r) NoContent deletePendingEmailUpdateH uid = do mUser <- lift . liftSem $ UserStore.getUser uid - for_ (storedUserEmailUnvalidated =<< mUser) $ \email -> + for_ (emailUnvalidated =<< mUser) $ \email -> lift . liftSem $ do ActivationCode.deleteActivationCode (mkEmailKey email) UserStore.deleteEmailUnvalidated uid From 40b2b80b6bd89273f98016a2dcd40c49ec297e55 Mon Sep 17 00:00:00 2001 From: Gautier DI FOLCO Date: Mon, 13 Jul 2026 19:55:17 +0200 Subject: [PATCH 4/4] Hello CI --- libs/wire-api/src/Wire/API/User.hs | 4 +--- services/brig/src/Brig/API/Error.hs | 1 - services/brig/src/Brig/Data/Activation.hs | 8 -------- 3 files changed, 1 insertion(+), 12 deletions(-) diff --git a/libs/wire-api/src/Wire/API/User.hs b/libs/wire-api/src/Wire/API/User.hs index afaf2b37a65..6e7bd0ba119 100644 --- a/libs/wire-api/src/Wire/API/User.hs +++ b/libs/wire-api/src/Wire/API/User.hs @@ -905,7 +905,6 @@ data RegisterError | RegisterErrorUserCreationRestricted | RegisterErrorEphemeralUserCreationDisabled | RegisterErrorScimDisplayNameMismatch - | RegisterErrorInvalidActivationManagedByScim deriving (Show, Generic) deriving (AsUnion RegisterErrorResponses) via GenericAsUnion RegisterErrorResponses RegisterError @@ -924,8 +923,7 @@ type RegisterErrorResponses = ErrorResponse 'TooManyTeamMembers, ErrorResponse 'UserCreationRestricted, ErrorResponse 'EphemeralUserCreationDisabled, - ErrorResponse 'NameManagedByScim, - ErrorResponse 'EmailManagedByScim + ErrorResponse 'NameManagedByScim ] type RegisterResponses = diff --git a/services/brig/src/Brig/API/Error.hs b/services/brig/src/Brig/API/Error.hs index 5a34f5fa532..733550000c8 100644 --- a/services/brig/src/Brig/API/Error.hs +++ b/services/brig/src/Brig/API/Error.hs @@ -57,7 +57,6 @@ actError InvalidActivationCodeWrongUser = StdError (errorToWai @'E.InvalidActiva actError InvalidActivationCodeWrongCode = StdError (errorToWai @'E.InvalidActivationCodeWrongCode) actError (InvalidActivationEmail _ _) = StdError (errorToWai @'E.InvalidEmail) actError (InvalidActivationPhone _) = StdError (errorToWai @'E.InvalidPhone) -actError InvalidActivationManagedByScim = StdError (errorToWai @'E.EmailManagedByScim) pwResetError :: PasswordResetError -> HttpError pwResetError InvalidPasswordResetKey = StdError (errorToWai @'E.InvalidPasswordResetKey) diff --git a/services/brig/src/Brig/Data/Activation.hs b/services/brig/src/Brig/Data/Activation.hs index e1f947e9c93..9c9eb1446e1 100644 --- a/services/brig/src/Brig/Data/Activation.hs +++ b/services/brig/src/Brig/Data/Activation.hs @@ -53,7 +53,6 @@ data ActivationError | InvalidActivationCodeWrongCode | InvalidActivationEmail !EmailAddress !String | InvalidActivationPhone !Phone - | InvalidActivationManagedByScim activationErrorToRegisterError :: ActivationError -> RegisterError activationErrorToRegisterError = \case @@ -62,7 +61,6 @@ activationErrorToRegisterError = \case InvalidActivationCodeWrongCode -> RegisterErrorInvalidActivationCodeWrongCode InvalidActivationEmail _ _ -> RegisterErrorInvalidEmail InvalidActivationPhone _ -> RegisterErrorInvalidPhone - InvalidActivationManagedByScim -> RegisterErrorInvalidActivationManagedByScim data ActivationEvent = AccountActivated !User @@ -102,12 +100,6 @@ activateKey k c u = do let a' = a {userIdentity = Just ident} pure . Just $ AccountActivated a' Just _ -> do - -- A SCIM-managed user's email can only be changed through SCIM. Reject any - -- activation that would change a SCIM-managed user's email (defense in depth; - -- pending email-update tokens are also invalidated when a user is put under SCIM - -- control). - when (userManagedBy a == ManagedByScim) $ - throwE InvalidActivationManagedByScim let profileNeedsUpdate = Just (emailKeyOrig key) /= userEmail a oldKey :: Maybe EmailKey = mkEmailKey <$> userEmail a in handleExistingIdentity uid profileNeedsUpdate oldKey key