Skip to content

Commit f147ee3

Browse files
authored
WPB-24006 rename validate sam lemails to require external email verification and deprecate legacy feature flag endpoint (#5118)
1 parent 14b3310 commit f147ee3

File tree

22 files changed

+89
-78
lines changed

22 files changed

+89
-78
lines changed

changelog.d/4-docs/WPB-24006

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Updated docs for the team feature `validateSAMLemails`

docs/src/developer/reference/config-options.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -288,15 +288,17 @@ The lock status for individual teams can be changed via the internal API (`PUT /
288288

289289
The feature status for individual teams can be changed via the public API (if the feature is unlocked).
290290

291-
### Validate SAML Emails
291+
### Require External Email Verification
292292

293-
The feature only affects email address changes originating from SCIM or SAML. Personal users and team users provisioned through the team management app will *always* be validated.
293+
The external feature name `validateSAMLemails` is kept for backward compatibility, but it is misleading: the feature applies to email addresses originating from both SCIM and SAML, and it controls ownership verification rather than generic email validation.
294294

295-
`enabled` means "user has authority over email address": if a new user account with an email address is created, the user behind the account will receive a validation email. If they follow the validation procedure, they will be able to receive emails about their account, eg., if a new device is associated with the account. If the user does not validate their email address, they can still use it to login.
295+
The feature only affects email address changes originating from SCIM or SAML. Personal users and team users provisioned through the team management app will *always* go through email verification.
296296

297-
`disabled` means "team admin has authority over email address, and by extension over all member accounts": if a user account with an email address is created, the address is considered valid immediately, without any emails being sent out, and without confirmation from the recipient.
297+
`enabled` means "user has authority over email address": if a new user account with an email address is created, the user behind the account will receive a verification email. If they complete the verification flow, they will be able to receive emails about their account, eg., if a new device is associated with the account. If they do not verify their email address, they can still use it to log in.
298298

299-
Validate SAML emails is enabled by default. To disable, use the following syntax:
299+
`disabled` means "team admin has authority over email address, and by extension over all member accounts": if a user account with an email address is created, the address is auto-activated immediately, without any verification email being sent and without confirmation from the recipient. The user can still receive later account notifications on that address, eg., if a new device is associated with the account.
300+
301+
This feature is enabled by default. To disable it, use the following syntax:
300302

301303
```yaml
302304
# galley.yaml

integration/integration.cabal

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ library
158158
Test.FeatureFlags.MlsE2EId
159159
Test.FeatureFlags.MlsMigration
160160
Test.FeatureFlags.OutlookCalIntegration
161+
Test.FeatureFlags.RequireExternalEmailVerification
161162
Test.FeatureFlags.SearchVisibilityAvailable
162163
Test.FeatureFlags.SearchVisibilityInbound
163164
Test.FeatureFlags.SelfDeletingMessages
@@ -167,7 +168,6 @@ library
167168
Test.FeatureFlags.StealthUsers
168169
Test.FeatureFlags.User
169170
Test.FeatureFlags.Util
170-
Test.FeatureFlags.ValidateSAMLEmails
171171
Test.Federation
172172
Test.Federator
173173
Test.LegalHold

integration/test/Test/FeatureFlags/ValidateSAMLEmails.hs renamed to integration/test/Test/FeatureFlags/RequireExternalEmailVerification.hs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,19 @@
1515
-- You should have received a copy of the GNU Affero General Public License along
1616
-- with this program. If not, see <https://www.gnu.org/licenses/>.
1717

18-
module Test.FeatureFlags.ValidateSAMLEmails where
18+
module Test.FeatureFlags.RequireExternalEmailVerification where
1919

2020
import SetupHelpers
2121
import Test.FeatureFlags.Util
2222
import Testlib.Prelude
2323

24-
testPatchValidateSAMLEmails :: (HasCallStack) => App ()
25-
testPatchValidateSAMLEmails =
24+
testPatchRequireExternalEmailVerification :: (HasCallStack) => App ()
25+
testPatchRequireExternalEmailVerification =
2626
checkPatch OwnDomain "validateSAMLemails"
2727
$ object ["status" .= "disabled"]
2828

29-
testValidateSAMLEmailsInternal :: (HasCallStack) => App ()
30-
testValidateSAMLEmailsInternal = do
29+
testRequireExternalEmailVerification :: (HasCallStack) => App ()
30+
testRequireExternalEmailVerification = do
3131
(alice, tid, _) <- createTeam OwnDomain 0
3232
withWebSocket alice $ \ws -> do
3333
setFlag InternalAPI ws tid "validateSAMLemails" disabled

integration/test/Test/Spar.hs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -887,12 +887,12 @@ testSsoLoginAndEmailVerification = do
887887
user %. "email" `shouldMatch` email
888888

889889
-- | This test may be covered by `testScimUpdateEmailAddress` and maybe can be removed.
890-
testSsoLoginNoSamlEmailValidation :: (HasCallStack) => TaggedBool "validateSAMLEmails" -> App ()
891-
testSsoLoginNoSamlEmailValidation (TaggedBool validateSAMLEmails) = do
890+
testSsoLoginNoSamlEmailValidation :: (HasCallStack) => TaggedBool "requireExternalEmailVerification" -> App ()
891+
testSsoLoginNoSamlEmailValidation (TaggedBool requireExternalEmailVerification) = do
892892
(owner, tid, _) <- createTeam OwnDomain 1
893893
emailDomain <- randomDomain
894894

895-
let status = if validateSAMLEmails then "enabled" else "disabled"
895+
let status = if requireExternalEmailVerification then "enabled" else "disabled"
896896
assertSuccess =<< setTeamFeatureStatus owner tid "validateSAMLemails" status
897897

898898
void $ setTeamFeatureStatus owner tid "sso" "enabled"
@@ -910,7 +910,7 @@ testSsoLoginNoSamlEmailValidation (TaggedBool validateSAMLEmails) = do
910910
eid = CI.original $ uref ^. SAML.uidSubject . to SAML.unsafeShowNameID
911911
eid `shouldMatch` email
912912

913-
when validateSAMLEmails $ do
913+
when requireExternalEmailVerification $ do
914914
getUsersId OwnDomain [uid] `bindResponse` \res -> do
915915
res.status `shouldMatchInt` 200
916916
user <- res.json & asList >>= assertOne
@@ -936,11 +936,11 @@ testSsoLoginNoSamlEmailValidation (TaggedBool validateSAMLEmails) = do
936936
user %. "email" `shouldMatch` email
937937

938938
-- | create user with non-email externalId. then use put to add an email address.
939-
testScimUpdateEmailAddress :: (HasCallStack) => TaggedBool "extIdIsEmail" -> TaggedBool "validateSAMLEmails" -> App ()
940-
testScimUpdateEmailAddress (TaggedBool extIdIsEmail) (TaggedBool validateSAMLEmails) = do
939+
testScimUpdateEmailAddress :: (HasCallStack) => TaggedBool "extIdIsEmail" -> TaggedBool "requireExternalEmailVerification" -> App ()
940+
testScimUpdateEmailAddress (TaggedBool extIdIsEmail) (TaggedBool requireExternalEmailVerification) = do
941941
(owner, tid, _) <- createTeam OwnDomain 1
942942

943-
let status = if validateSAMLEmails then "enabled" else "disabled"
943+
let status = if requireExternalEmailVerification then "enabled" else "disabled"
944944
assertSuccess =<< setTeamFeatureStatus owner tid "validateSAMLemails" status
945945

946946
void $ setTeamFeatureStatus owner tid "sso" "enabled"
@@ -991,7 +991,7 @@ testScimUpdateEmailAddress (TaggedBool extIdIsEmail) (TaggedBool validateSAMLEma
991991
res.status `shouldMatchInt` 200
992992
res.json %. "emails" `shouldMatch` [object ["value" .= newEmail]]
993993

994-
when validateSAMLEmails $ do
994+
when requireExternalEmailVerification $ do
995995
getUsersId OwnDomain [uid] `bindResponse` \res -> do
996996
res.status `shouldMatchInt` 200
997997
user <- res.json & asList >>= assertOne
@@ -1164,11 +1164,11 @@ testScimUpdateEmailAddressAndExternalId = do
11641164
user %. "status" `shouldMatch` "active"
11651165
user %. "email" `shouldMatch` newEmail1
11661166

1167-
testScimLoginNoSamlEmailValidation :: (HasCallStack) => TaggedBool "validateSAMLEmails" -> App ()
1168-
testScimLoginNoSamlEmailValidation (TaggedBool validateSAMLEmails) = do
1167+
testScimLoginNoSamlEmailValidation :: (HasCallStack) => TaggedBool "requireExternalEmailVerification" -> App ()
1168+
testScimLoginNoSamlEmailValidation (TaggedBool requireExternalEmailVerification) = do
11691169
(owner, tid, _) <- createTeam OwnDomain 1
11701170

1171-
let status = if validateSAMLEmails then "enabled" else "disabled"
1171+
let status = if requireExternalEmailVerification then "enabled" else "disabled"
11721172
assertSuccess =<< setTeamFeatureStatus owner tid "validateSAMLemails" status
11731173

11741174
void $ setTeamFeatureStatus owner tid "sso" "enabled"
@@ -1187,7 +1187,7 @@ testScimLoginNoSamlEmailValidation (TaggedBool validateSAMLEmails) = do
11871187
res.status `shouldMatchInt` 200
11881188
res.json %. "id" `shouldMatch` uid
11891189

1190-
when validateSAMLEmails $ do
1190+
when requireExternalEmailVerification $ do
11911191
getUsersId OwnDomain [uid] `bindResponse` \res -> do
11921192
res.status `shouldMatchInt` 200
11931193
user <- res.json & asList >>= assertOne

integration/test/Test/Spar/GetByEmail.hs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@ import Testlib.Prelude
2828
-- | Test the /sso/get-by-email endpoint with multi-ingress setup
2929
testGetSsoCodeByEmailWithMultiIngress ::
3030
(HasCallStack) =>
31-
TaggedBool "validateSAMLemails" ->
31+
TaggedBool "requireExternalEmailVerification" ->
3232
TaggedBool "idpScimToken" ->
3333
App ()
34-
testGetSsoCodeByEmailWithMultiIngress (TaggedBool validateSAMLemails) (TaggedBool isIdPScimToken) = do
34+
testGetSsoCodeByEmailWithMultiIngress (TaggedBool requireExternalEmailVerification) (TaggedBool isIdPScimToken) = do
3535
let ernieZHost = "nginz-https.ernie.example.com"
3636
bertZHost = "nginz-https.bert.example.com"
3737

@@ -65,7 +65,7 @@ testGetSsoCodeByEmailWithMultiIngress (TaggedBool validateSAMLemails) (TaggedBoo
6565
assertSuccess =<< setTeamFeatureStatus domain tid "sso" "enabled"
6666

6767
-- The test should work for both: SCIM user with and without email confirmation
68-
let status = if validateSAMLemails then "enabled" else "disabled"
68+
let status = if requireExternalEmailVerification then "enabled" else "disabled"
6969
assertSuccess =<< setTeamFeatureStatus owner tid "validateSAMLemails" status
7070

7171
-- Create IdP for ernie domain
@@ -98,7 +98,7 @@ testGetSsoCodeByEmailWithMultiIngress (TaggedBool validateSAMLemails) (TaggedBoo
9898
createScimUser domain scimToken scimUser >>= assertSuccess
9999

100100
if isIdPScimToken
101-
then when validateSAMLemails $ do
101+
then when requireExternalEmailVerification $ do
102102
-- Activate the email so the user can be found by email
103103
activateEmail domain userEmail
104104
else
@@ -124,15 +124,15 @@ testGetSsoCodeByEmailWithMultiIngress (TaggedBool validateSAMLemails) (TaggedBoo
124124
ssoCodeStr `shouldMatch` idpIdBert
125125

126126
-- | Test the /sso/get-by-email endpoint with regular (non-multi-ingress) setup
127-
testGetSsoCodeByEmailRegular :: (HasCallStack) => (TaggedBool "validateSAMLemails") -> (TaggedBool "idpScimToken") -> App ()
128-
testGetSsoCodeByEmailRegular (TaggedBool validateSAMLemails) (TaggedBool isIdPScimToken) =
127+
testGetSsoCodeByEmailRegular :: (HasCallStack) => (TaggedBool "requireExternalEmailVerification") -> (TaggedBool "idpScimToken") -> App ()
128+
testGetSsoCodeByEmailRegular (TaggedBool requireExternalEmailVerification) (TaggedBool isIdPScimToken) =
129129
withModifiedBackend def {sparCfg = setField "enableIdPByEmailDiscovery" True}
130130
$ \domain -> do
131131
(owner, tid, _) <- createTeam domain 1
132132
void $ setTeamFeatureStatus owner tid "sso" "enabled"
133133

134134
-- The test should work for both: SCIM user with and without email confirmation
135-
let status = if validateSAMLemails then "enabled" else "disabled"
135+
let status = if requireExternalEmailVerification then "enabled" else "disabled"
136136
assertSuccess =<< setTeamFeatureStatus owner tid "validateSAMLemails" status
137137

138138
-- Create IdP without domain binding
@@ -156,7 +156,7 @@ testGetSsoCodeByEmailRegular (TaggedBool validateSAMLemails) (TaggedBool isIdPSc
156156
createScimUser domain scimToken scimUser >>= assertSuccess
157157

158158
if isIdPScimToken
159-
then when validateSAMLemails $ do
159+
then when requireExternalEmailVerification $ do
160160
-- Activate the email so the user can be found by email
161161
activateEmail domain userEmail
162162
else

libs/wire-api/src/Wire/API/Routes/Features.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,6 @@ type family FeatureErrors cfg where
3636
type family FeatureAPIDesc cfg where
3737
FeatureAPIDesc EnforceFileDownloadLocationConfig =
3838
"<p><b>Custom feature: only supported on some dedicated on-prem systems.</b></p>"
39+
FeatureAPIDesc RequireExternalEmailVerificationConfig =
40+
"<p>Controls whether externally managed email addresses (from SAML or SCIM) must be verified by the user, or are auto-activated.</p><p>The external feature name is kept as <code>validateSAMLemails</code> for backward compatibility. That name is misleading because the feature also applies to SCIM-managed users, and it controls email ownership verification rather than generic email validation.</p>"
3941
FeatureAPIDesc _ = ""

libs/wire-api/src/Wire/API/Routes/Public/Galley/Feature.hs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ type FeatureAPI =
4242
:<|> FeatureAPIGetPut SearchVisibilityAvailableConfig
4343
:<|> SearchVisibilityGet
4444
:<|> SearchVisibilitySet
45-
:<|> FeatureAPIGet ValidateSAMLEmailsConfig
45+
:<|> FeatureAPIGet RequireExternalEmailVerificationConfig
4646
:<|> FeatureAPIGet DigitalSignaturesConfig
4747
:<|> FeatureAPIGetPut AppLockConfig
4848
:<|> FeatureAPIGetPut FileSharingConfig
@@ -108,7 +108,7 @@ type DeprecatedFeatureConfigs =
108108
[ LegalholdConfig,
109109
SSOConfig,
110110
SearchVisibilityAvailableConfig,
111-
ValidateSAMLEmailsConfig,
111+
RequireExternalEmailVerificationConfig,
112112
DigitalSignaturesConfig,
113113
AppLockConfig,
114114
FileSharingConfig,
@@ -129,7 +129,7 @@ type family AllDeprecatedFeatureConfigAPI cfgs where
129129
type DeprecatedFeatureAPI =
130130
FeatureStatusDeprecatedGet DeprecationNotice1 SearchVisibilityAvailableConfig V2
131131
:<|> FeatureStatusDeprecatedPut DeprecationNotice1 SearchVisibilityAvailableConfig V2
132-
:<|> FeatureStatusDeprecatedGet DeprecationNotice1 ValidateSAMLEmailsConfig V2
132+
:<|> FeatureStatusDeprecatedGet DeprecationNotice1 RequireExternalEmailVerificationConfig V2
133133
:<|> FeatureStatusDeprecatedGet DeprecationNotice2 DigitalSignaturesConfig V2
134134

135135
type FeatureAPIGet cfg =

libs/wire-api/src/Wire/API/Team/Feature.hs

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ module Wire.API.Team.Feature
6161
SearchVisibilityAvailableConfig (..),
6262
SelfDeletingMessagesConfigB (..),
6363
SelfDeletingMessagesConfig,
64-
ValidateSAMLEmailsConfig (..),
64+
RequireExternalEmailVerificationConfig (..),
6565
DigitalSignaturesConfig (..),
6666
ConferenceCallingConfigB (..),
6767
ConferenceCallingConfig,
@@ -256,7 +256,7 @@ data FeatureSingleton cfg where
256256
FeatureSingletonLegalholdConfig :: FeatureSingleton LegalholdConfig
257257
FeatureSingletonSSOConfig :: FeatureSingleton SSOConfig
258258
FeatureSingletonSearchVisibilityAvailableConfig :: FeatureSingleton SearchVisibilityAvailableConfig
259-
FeatureSingletonValidateSAMLEmailsConfig :: FeatureSingleton ValidateSAMLEmailsConfig
259+
FeatureSingletonRequireExternalEmailVerificationConfig :: FeatureSingleton RequireExternalEmailVerificationConfig
260260
FeatureSingletonDigitalSignaturesConfig :: FeatureSingleton DigitalSignaturesConfig
261261
FeatureSingletonConferenceCallingConfig :: FeatureSingleton ConferenceCallingConfig
262262
FeatureSingletonSndFactorPasswordChallengeConfig :: FeatureSingleton SndFactorPasswordChallengeConfig
@@ -753,29 +753,35 @@ instance ToSchema SearchVisibilityAvailableConfig where
753753
type instance DeprecatedFeatureName V2 SearchVisibilityAvailableConfig = "search-visibility"
754754

755755
--------------------------------------------------------------------------------
756-
-- ValidateSAMLEmails feature
756+
-- RequireExternalEmailVerification feature
757757

758-
-- | This feature does not have a PUT endpoint. See Note [unsettable features].
759-
data ValidateSAMLEmailsConfig = ValidateSAMLEmailsConfig
758+
-- | Controls whether externally managed email addresses (from SAML or SCIM)
759+
-- must be verified by the user, or are auto-activated. When disabled, no
760+
-- verification email is sent, but the address is still activated immediately
761+
-- and can receive later account notifications such as new-device emails.
762+
-- The external feature name is kept for backward compatibility.
763+
--
764+
-- (This feature does not have a PUT endpoint. See Note [unsettable features].)
765+
data RequireExternalEmailVerificationConfig = RequireExternalEmailVerificationConfig
760766
deriving (Eq, Show, Generic, GSOP.Generic)
761-
deriving (Arbitrary) via (GenericUniform ValidateSAMLEmailsConfig)
762-
deriving (RenderableSymbol) via (RenderableTypeName ValidateSAMLEmailsConfig)
763-
deriving (ParseDbFeature, Default) via (TrivialFeature ValidateSAMLEmailsConfig)
767+
deriving (Arbitrary) via (GenericUniform RequireExternalEmailVerificationConfig)
768+
deriving (RenderableSymbol) via (RenderableTypeName RequireExternalEmailVerificationConfig)
769+
deriving (ParseDbFeature, Default) via (TrivialFeature RequireExternalEmailVerificationConfig)
764770

765-
instance ToSchema ValidateSAMLEmailsConfig where
766-
schema = object "ValidateSAMLEmailsConfig" objectSchema
771+
instance ToSchema RequireExternalEmailVerificationConfig where
772+
schema = object "RequireExternalEmailVerificationConfig" objectSchema
767773

768-
instance Default (LockableFeature ValidateSAMLEmailsConfig) where
774+
instance Default (LockableFeature RequireExternalEmailVerificationConfig) where
769775
def = defUnlockedFeature
770776

771-
instance ToObjectSchema ValidateSAMLEmailsConfig where
772-
objectSchema = pure ValidateSAMLEmailsConfig
777+
instance ToObjectSchema RequireExternalEmailVerificationConfig where
778+
objectSchema = pure RequireExternalEmailVerificationConfig
773779

774-
instance IsFeatureConfig ValidateSAMLEmailsConfig where
775-
type FeatureSymbol ValidateSAMLEmailsConfig = "validateSAMLemails"
776-
featureSingleton = FeatureSingletonValidateSAMLEmailsConfig
780+
instance IsFeatureConfig RequireExternalEmailVerificationConfig where
781+
type FeatureSymbol RequireExternalEmailVerificationConfig = "validateSAMLemails"
782+
featureSingleton = FeatureSingletonRequireExternalEmailVerificationConfig
777783

778-
type instance DeprecatedFeatureName V2 ValidateSAMLEmailsConfig = "validate-saml-emails"
784+
type instance DeprecatedFeatureName V2 RequireExternalEmailVerificationConfig = "validate-saml-emails"
779785

780786
--------------------------------------------------------------------------------
781787
-- DigitalSignatures feature
@@ -2207,7 +2213,7 @@ type Features =
22072213
SSOConfig,
22082214
SearchVisibilityAvailableConfig,
22092215
SearchVisibilityInboundConfig,
2210-
ValidateSAMLEmailsConfig,
2216+
RequireExternalEmailVerificationConfig,
22112217
DigitalSignaturesConfig,
22122218
AppLockConfig,
22132219
FileSharingConfig,

libs/wire-api/src/Wire/API/Team/FeatureFlags.hs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -182,19 +182,19 @@ newtype instance FeatureDefaults SearchVisibilityInboundConfig
182182
deriving (FromJSON, ToJSON) via Defaults (Feature SearchVisibilityInboundConfig)
183183
deriving (ParseFeatureDefaults) via OptionalField SearchVisibilityInboundConfig
184184

185-
newtype instance FeatureDefaults ValidateSAMLEmailsConfig
186-
= ValidateSAMLEmailsDefaults (Feature ValidateSAMLEmailsConfig)
185+
newtype instance FeatureDefaults RequireExternalEmailVerificationConfig
186+
= RequireExternalEmailVerificationDefaults (Feature RequireExternalEmailVerificationConfig)
187187
deriving stock (Eq, Show)
188188
deriving newtype (Default, GetFeatureDefaults)
189-
deriving (FromJSON, ToJSON) via Defaults (Feature ValidateSAMLEmailsConfig)
189+
deriving (FromJSON, ToJSON) via Defaults (Feature RequireExternalEmailVerificationConfig)
190190

191-
instance ParseFeatureDefaults (FeatureDefaults ValidateSAMLEmailsConfig) where
191+
instance ParseFeatureDefaults (FeatureDefaults RequireExternalEmailVerificationConfig) where
192192
parseFeatureDefaults obj =
193193
do
194194
-- Accept the legacy typo in config input for backward compatibility,
195195
-- but prefer the canonical feature key when both are present.
196-
mCanonical :: Maybe (FeatureDefaults ValidateSAMLEmailsConfig) <- obj .:? featureKey @ValidateSAMLEmailsConfig
197-
mLegacy :: Maybe (FeatureDefaults ValidateSAMLEmailsConfig) <- obj .:? "validateSAMLEmails"
196+
mCanonical :: Maybe (FeatureDefaults RequireExternalEmailVerificationConfig) <- obj .:? featureKey @RequireExternalEmailVerificationConfig
197+
mLegacy :: Maybe (FeatureDefaults RequireExternalEmailVerificationConfig) <- obj .:? "validateSAMLEmails"
198198
pure $ fromMaybe def (mCanonical <|> mLegacy)
199199

200200
data instance FeatureDefaults DigitalSignaturesConfig = DigitalSignaturesDefaults

0 commit comments

Comments
 (0)