module Test.Spar.GetByEmail where
import API.BrigInternal (CreateUser (..))
import API.GalleyInternal
import API.Spar
import GHC.Stack
import qualified SAML2.WebSSO.Test.Util as SAML
import SetupHelpers
import Testlib.Certs (fingerprintHex)
import Testlib.Prelude
import qualified Text.XML.DSig as XMLDSig
testGetSsoCodeByEmailWithMultiIngress ::
(HasCallStack) =>
TaggedBool "requireExternalEmailVerification" ->
TaggedBool "idpScimToken" ->
App ()
testGetSsoCodeByEmailWithMultiIngress :: HasCallStack =>
TaggedBool "requireExternalEmailVerification"
-> TaggedBool "idpScimToken" -> App ()
testGetSsoCodeByEmailWithMultiIngress (TaggedBool Bool
requireExternalEmailVerification) (TaggedBool Bool
isIdPScimToken) = do
let ernieZHost :: [Char]
ernieZHost = [Char]
"nginz-https.ernie.example.com"
bertZHost :: [Char]
bertZHost = [Char]
"nginz-https.bert.example.com"
credsWithCertErnie@(_, _, signedCertErnie) <- Maybe DateTime
-> Int -> App (SignPrivCreds, SignCreds, SignedCertificate)
forall (m :: * -> *).
(MonadRandom m, MonadIO m) =>
Maybe DateTime
-> Int -> m (SignPrivCreds, SignCreds, SignedCertificate)
XMLDSig.mkSignCredsWithCert Maybe DateTime
forall a. Maybe a
Nothing Int
96
credsWithCertBert@(_, _, signedCertBert) <- XMLDSig.mkSignCredsWithCert Nothing 96
withModifiedBackend
def
{ sparCfg =
setField "enableIdPByEmailDiscovery" True
>=> removeField "saml.spSsoUri"
>=> removeField "saml.spAppUri"
>=> removeField "saml.contacts"
>=> setField
"saml.spDomainConfigs"
( object
[ ernieZHost
.= object
[ "spAppUri" .= ("https://webapp." ++ ernieZHost),
"spSsoUri" .= ("https://" ++ ernieZHost ++ "/sso"),
"contacts" .= [object ["type" .= ("ContactTechnical" :: String)]]
],
bertZHost
.= object
[ "spAppUri" .= ("https://webapp." ++ bertZHost),
"spSsoUri" .= ("https://" ++ bertZHost ++ "/sso"),
"contacts" .= [object ["type" .= ("ContactTechnical" :: String)]]
]
]
)
>=> setField
"idpCertFingerprintAllowlist"
(fingerprintHex <$> [signedCertErnie, signedCertBert])
}
$ \[Char]
domain -> do
(owner, tid, _) <- [Char] -> Int -> App (Value, [Char], [Value])
forall domain.
(HasCallStack, MakesValue domain) =>
domain -> Int -> App (Value, [Char], [Value])
createTeam [Char]
domain Int
1
assertSuccess =<< setTeamFeatureStatus domain tid "sso" "enabled"
let status = if Bool
requireExternalEmailVerification then [Char]
"enabled" else [Char]
"disabled"
assertSuccess =<< setTeamFeatureStatus owner tid "validateSAMLemails" status
SAML.SampleIdP idpmetaErnie _ _ _ <- SAML.makeSampleIdPMetadataWithCert credsWithCertErnie
idpIdErnie <-
createIdpWithZHostV2 owner (Just ernieZHost) idpmetaErnie `bindResponse` \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
201
Response
resp.json Maybe Value -> [Char] -> App Value
forall a. (HasCallStack, MakesValue a) => a -> [Char] -> App Value
%. [Char]
"extraInfo.domain" App Value -> [Char] -> App ()
forall a b.
(MakesValue a, MakesValue b, HasCallStack) =>
a -> b -> App ()
`shouldMatch` [Char]
ernieZHost
Response
resp.json Maybe Value -> [Char] -> App Value
forall a. (HasCallStack, MakesValue a) => a -> [Char] -> App Value
%. [Char]
"id" App Value -> (Value -> App [Char]) -> App [Char]
forall a b. App a -> (a -> App b) -> App b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Value -> App [Char]
forall a. (HasCallStack, MakesValue a) => a -> App [Char]
asString
SAML.SampleIdP idpmetaBert _ _ _ <- SAML.makeSampleIdPMetadataWithCert credsWithCertBert
idpIdBert <-
createIdpWithZHostV2 owner (Just bertZHost) idpmetaBert `bindResponse` \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
201
Response
resp.json Maybe Value -> [Char] -> App Value
forall a. (HasCallStack, MakesValue a) => a -> [Char] -> App Value
%. [Char]
"extraInfo.domain" App Value -> [Char] -> App ()
forall a b.
(MakesValue a, MakesValue b, HasCallStack) =>
a -> b -> App ()
`shouldMatch` [Char]
bertZHost
Response
resp.json Maybe Value -> [Char] -> App Value
forall a. (HasCallStack, MakesValue a) => a -> [Char] -> App Value
%. [Char]
"id" App Value -> (Value -> App [Char]) -> App [Char]
forall a b. App a -> (a -> App b) -> App b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Value -> App [Char]
forall a. (HasCallStack, MakesValue a) => a -> App [Char]
asString
scimUser <- randomScimUser
userEmail <-
scimUser %. "emails" >>= asList >>= \case
(Value
e : [Value]
_) -> Value
e Value -> [Char] -> App Value
forall a. (HasCallStack, MakesValue a) => a -> [Char] -> App Value
%. [Char]
"value" App Value -> (Value -> App [Char]) -> App [Char]
forall a b. App a -> (a -> App b) -> App b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Value -> App [Char]
forall a. (HasCallStack, MakesValue a) => a -> App [Char]
asString
[] -> [Char] -> App [Char]
forall a. HasCallStack => [Char] -> App a
assertFailure [Char]
"Expected at least one email"
let idpTokenConfig = if Bool
isIdPScimToken then (CreateScimToken
forall a. Default a => a
def {idp = Just idpIdErnie}) else CreateScimToken
forall a. Default a => a
def
scimTok <- createScimToken owner idpTokenConfig
scimToken <- scimTok.json %. "token" & asString
createScimUser domain scimToken scimUser >>= assertSuccess
if isIdPScimToken
then when requireExternalEmailVerification $ do
activateEmail domain userEmail
else
registerInvitedUser domain tid userEmail
getSsoCodeByEmailWithZHost domain (Just ernieZHost) userEmail `bindResponse` \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
ssoCodeStr <- Response
resp.json Maybe Value -> [Char] -> App Value
forall a. (HasCallStack, MakesValue a) => a -> [Char] -> App Value
%. [Char]
"sso_code" App Value -> (Value -> App [Char]) -> App [Char]
forall a b. App a -> (a -> App b) -> App b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Value -> App [Char]
forall a. (HasCallStack, MakesValue a) => a -> App [Char]
asString
ssoCodeStr `shouldMatch` idpIdErnie
getSsoCodeByEmail domain userEmail `bindResponse` \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
404
mbSsoCode <- Maybe Value -> [Char] -> App (Maybe Value)
forall a.
(HasCallStack, MakesValue a) =>
a -> [Char] -> App (Maybe Value)
lookupField Response
resp.json [Char]
"sso_code"
mbSsoCode `shouldMatch` (Nothing :: Maybe Value)
getSsoCodeByEmailWithZHost domain (Just bertZHost) userEmail `bindResponse` \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
ssoCodeStr <- Response
resp.json Maybe Value -> [Char] -> App Value
forall a. (HasCallStack, MakesValue a) => a -> [Char] -> App Value
%. [Char]
"sso_code" App Value -> (Value -> App [Char]) -> App [Char]
forall a b. App a -> (a -> App b) -> App b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Value -> App [Char]
forall a. (HasCallStack, MakesValue a) => a -> App [Char]
asString
ssoCodeStr `shouldMatch` idpIdBert
testGetSsoCodeByEmailRegular :: (HasCallStack) => (TaggedBool "requireExternalEmailVerification") -> (TaggedBool "idpScimToken") -> App ()
testGetSsoCodeByEmailRegular :: HasCallStack =>
TaggedBool "requireExternalEmailVerification"
-> TaggedBool "idpScimToken" -> App ()
testGetSsoCodeByEmailRegular (TaggedBool Bool
requireExternalEmailVerification) (TaggedBool Bool
isIdPScimToken) =
ServiceOverrides -> (HasCallStack => [Char] -> App ()) -> App ()
forall a.
HasCallStack =>
ServiceOverrides -> (HasCallStack => [Char] -> App a) -> App a
withModifiedBackend ServiceOverrides
forall a. Default a => a
def {sparCfg = setField "enableIdPByEmailDiscovery" True}
((HasCallStack => [Char] -> App ()) -> App ())
-> (HasCallStack => [Char] -> App ()) -> App ()
forall a b. (a -> b) -> a -> b
$ \[Char]
domain -> do
(owner, tid, _) <- [Char] -> Int -> App (Value, [Char], [Value])
forall domain.
(HasCallStack, MakesValue domain) =>
domain -> Int -> App (Value, [Char], [Value])
createTeam [Char]
domain Int
1
void $ setTeamFeatureStatus owner tid "sso" "enabled"
let status = if Bool
requireExternalEmailVerification then [Char]
"enabled" else [Char]
"disabled"
assertSuccess =<< setTeamFeatureStatus owner tid "validateSAMLemails" status
SAML.SampleIdP idpmeta _ _ _ <- SAML.makeSampleIdPMetadata
idpId <-
createIdp owner idpmeta `bindResponse` \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
201
Response
resp.json Maybe Value -> [Char] -> App Value
forall a. (HasCallStack, MakesValue a) => a -> [Char] -> App Value
%. [Char]
"id" App Value -> (Value -> App [Char]) -> App [Char]
forall a b. App a -> (a -> App b) -> App b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Value -> App [Char]
forall a. (HasCallStack, MakesValue a) => a -> App [Char]
asString
scimUser <- randomScimUser
userEmail <-
scimUser %. "emails" >>= asList >>= \case
(Value
e : [Value]
_) -> Value
e Value -> [Char] -> App Value
forall a. (HasCallStack, MakesValue a) => a -> [Char] -> App Value
%. [Char]
"value" App Value -> (Value -> App [Char]) -> App [Char]
forall a b. App a -> (a -> App b) -> App b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Value -> App [Char]
forall a. (HasCallStack, MakesValue a) => a -> App [Char]
asString
[] -> [Char] -> App [Char]
forall a. HasCallStack => [Char] -> App a
assertFailure [Char]
"Expected at least one email"
let idpTokenConfig = if Bool
isIdPScimToken then (CreateScimToken
forall a. Default a => a
def {idp = Just idpId}) else CreateScimToken
forall a. Default a => a
def
scimTok <- createScimToken owner idpTokenConfig
scimToken <- scimTok.json %. "token" & asString
createScimUser domain scimToken scimUser >>= assertSuccess
if isIdPScimToken
then when requireExternalEmailVerification $ do
activateEmail domain userEmail
else
registerInvitedUser domain tid userEmail
getSsoCodeByEmail domain userEmail `bindResponse` \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
ssoCodeStr <- Response
resp.json Maybe Value -> [Char] -> App Value
forall a. (HasCallStack, MakesValue a) => a -> [Char] -> App Value
%. [Char]
"sso_code" App Value -> (Value -> App [Char]) -> App [Char]
forall a b. App a -> (a -> App b) -> App b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Value -> App [Char]
forall a. (HasCallStack, MakesValue a) => a -> App [Char]
asString
ssoCodeStr `shouldMatch` idpId
testGetSsoCodeByEmailNonSSOUser :: (HasCallStack) => App ()
testGetSsoCodeByEmailNonSSOUser :: HasCallStack => App ()
testGetSsoCodeByEmailNonSSOUser = do
ServiceOverrides -> (HasCallStack => [Char] -> App ()) -> App ()
forall a.
HasCallStack =>
ServiceOverrides -> (HasCallStack => [Char] -> App a) -> App a
withModifiedBackend
ServiceOverrides
forall a. Default a => a
def {sparCfg = setField "enableIdPByEmailDiscovery" True}
((HasCallStack => [Char] -> App ()) -> App ())
-> (HasCallStack => [Char] -> App ()) -> App ()
forall a b. (a -> b) -> a -> b
$ \[Char]
domain -> do
(owner, tid, _) <- [Char] -> Int -> App (Value, [Char], [Value])
forall domain.
(HasCallStack, MakesValue domain) =>
domain -> Int -> App (Value, [Char], [Value])
createTeam [Char]
domain Int
1
void $ setTeamFeatureStatus owner tid "sso" "enabled"
SAML.SampleIdP idpmeta _ _ _ <- SAML.makeSampleIdPMetadata
createIdp owner idpmeta >>= assertSuccess
usr <- randomUser domain def {activate = True}
userEmail <- usr %. "email" & asString
getSsoCodeByEmail domain userEmail `bindResponse` \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
404
mbSsoCode <- Maybe Value -> [Char] -> App (Maybe Value)
forall a.
(HasCallStack, MakesValue a) =>
a -> [Char] -> App (Maybe Value)
lookupField Response
resp.json [Char]
"sso_code"
mbSsoCode `shouldMatch` (Nothing :: Maybe Value)
testGetSsoCodeByEmailDisabledRegular :: (HasCallStack) => App ()
testGetSsoCodeByEmailDisabledRegular :: HasCallStack => App ()
testGetSsoCodeByEmailDisabledRegular = do
ServiceOverrides -> (HasCallStack => [Char] -> App ()) -> App ()
forall a.
HasCallStack =>
ServiceOverrides -> (HasCallStack => [Char] -> App a) -> App a
withModifiedBackend
ServiceOverrides
forall a. Default a => a
def {sparCfg = setField "enableIdPByEmailDiscovery" False}
((HasCallStack => [Char] -> App ()) -> App ())
-> (HasCallStack => [Char] -> App ()) -> App ()
forall a b. (a -> b) -> a -> b
$ \[Char]
domain -> do
(owner, tid, _) <- [Char] -> Int -> App (Value, [Char], [Value])
forall domain.
(HasCallStack, MakesValue domain) =>
domain -> Int -> App (Value, [Char], [Value])
createTeam [Char]
domain Int
1
void $ setTeamFeatureStatus owner tid "sso" "enabled"
SAML.SampleIdP idpmeta _ _ _ <- SAML.makeSampleIdPMetadata
idpId <-
createIdp owner idpmeta `bindResponse` \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
201
Response
resp.json Maybe Value -> [Char] -> App Value
forall a. (HasCallStack, MakesValue a) => a -> [Char] -> App Value
%. [Char]
"id" App Value -> (Value -> App [Char]) -> App [Char]
forall a b. App a -> (a -> App b) -> App b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Value -> App [Char]
forall a. (HasCallStack, MakesValue a) => a -> App [Char]
asString
scimUser <- randomScimUser
userEmail <-
scimUser %. "emails" >>= asList >>= \case
(Value
e : [Value]
_) -> Value
e Value -> [Char] -> App Value
forall a. (HasCallStack, MakesValue a) => a -> [Char] -> App Value
%. [Char]
"value" App Value -> (Value -> App [Char]) -> App [Char]
forall a b. App a -> (a -> App b) -> App b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Value -> App [Char]
forall a. (HasCallStack, MakesValue a) => a -> App [Char]
asString
[] -> [Char] -> App [Char]
forall a. HasCallStack => [Char] -> App a
assertFailure [Char]
"Expected at least one email"
scimTok <- createScimToken owner def {idp = Just idpId}
scimToken <- scimTok.json %. "token" & asString
createScimUser domain scimToken scimUser >>= assertSuccess
activateEmail domain userEmail
getSsoCodeByEmail domain userEmail `bindResponse` \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
404
mbSsoCode <- Maybe Value -> [Char] -> App (Maybe Value)
forall a.
(HasCallStack, MakesValue a) =>
a -> [Char] -> App (Maybe Value)
lookupField Response
resp.json [Char]
"sso_code"
mbSsoCode `shouldMatch` (Nothing :: Maybe Value)
testGetSsoCodeByEmailDisabledMultiIngress :: (HasCallStack) => App ()
testGetSsoCodeByEmailDisabledMultiIngress :: HasCallStack => App ()
testGetSsoCodeByEmailDisabledMultiIngress = do
let ernieZHost :: [Char]
ernieZHost = [Char]
"nginz-https.ernie.example.com"
bertZHost :: [Char]
bertZHost = [Char]
"nginz-https.bert.example.com"
credsWithCertErnie@(_, _, signedCertErnie) <- Maybe DateTime
-> Int -> App (SignPrivCreds, SignCreds, SignedCertificate)
forall (m :: * -> *).
(MonadRandom m, MonadIO m) =>
Maybe DateTime
-> Int -> m (SignPrivCreds, SignCreds, SignedCertificate)
XMLDSig.mkSignCredsWithCert Maybe DateTime
forall a. Maybe a
Nothing Int
96
withModifiedBackend
def
{ sparCfg =
setField "enableIdPByEmailDiscovery" False
>=> removeField "saml.spSsoUri"
>=> removeField "saml.spAppUri"
>=> removeField "saml.contacts"
>=> setField
"saml.spDomainConfigs"
( object
[ ernieZHost
.= object
[ "spAppUri" .= ("https://webapp." ++ ernieZHost),
"spSsoUri" .= ("https://" ++ ernieZHost ++ "/sso"),
"contacts" .= [object ["type" .= ("ContactTechnical" :: String)]]
],
bertZHost
.= object
[ "spAppUri" .= ("https://webapp." ++ bertZHost),
"spSsoUri" .= ("https://" ++ bertZHost ++ "/sso"),
"contacts" .= [object ["type" .= ("ContactTechnical" :: String)]]
]
]
)
>=> setField "idpCertFingerprintAllowlist" [fingerprintHex signedCertErnie]
}
$ \[Char]
domain -> do
(owner, tid, _) <- [Char] -> Int -> App (Value, [Char], [Value])
forall domain.
(HasCallStack, MakesValue domain) =>
domain -> Int -> App (Value, [Char], [Value])
createTeam [Char]
domain Int
1
void $ setTeamFeatureStatus owner tid "sso" "enabled"
SAML.SampleIdP idpmetaErnie _ _ _ <- SAML.makeSampleIdPMetadataWithCert credsWithCertErnie
idpIdErnie <-
createIdpWithZHostV2 owner (Just ernieZHost) idpmetaErnie `bindResponse` \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
201
Response
resp.json Maybe Value -> [Char] -> App Value
forall a. (HasCallStack, MakesValue a) => a -> [Char] -> App Value
%. [Char]
"extraInfo.domain" App Value -> [Char] -> App ()
forall a b.
(MakesValue a, MakesValue b, HasCallStack) =>
a -> b -> App ()
`shouldMatch` [Char]
ernieZHost
Response
resp.json Maybe Value -> [Char] -> App Value
forall a. (HasCallStack, MakesValue a) => a -> [Char] -> App Value
%. [Char]
"id" App Value -> (Value -> App [Char]) -> App [Char]
forall a b. App a -> (a -> App b) -> App b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Value -> App [Char]
forall a. (HasCallStack, MakesValue a) => a -> App [Char]
asString
scimUser <- randomScimUser
userEmail <-
scimUser %. "emails" >>= asList >>= \case
(Value
e : [Value]
_) -> Value
e Value -> [Char] -> App Value
forall a. (HasCallStack, MakesValue a) => a -> [Char] -> App Value
%. [Char]
"value" App Value -> (Value -> App [Char]) -> App [Char]
forall a b. App a -> (a -> App b) -> App b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Value -> App [Char]
forall a. (HasCallStack, MakesValue a) => a -> App [Char]
asString
[] -> [Char] -> App [Char]
forall a. HasCallStack => [Char] -> App a
assertFailure [Char]
"Expected at least one email"
scimTok <- createScimToken owner def {idp = Just idpIdErnie}
scimToken <- scimTok.json %. "token" & asString
createScimUser domain scimToken scimUser >>= assertSuccess
activateEmail domain userEmail
bindResponse (getSsoCodeByEmailWithZHost domain (Just ernieZHost) userEmail) $ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
404
mbSsoCode <- Maybe Value -> [Char] -> App (Maybe Value)
forall a.
(HasCallStack, MakesValue a) =>
a -> [Char] -> App (Maybe Value)
lookupField Response
resp.json [Char]
"sso_code"
mbSsoCode `shouldMatch` (Nothing :: Maybe Value)