Skip to content

Polysemy: Separate more Cassandra effects from Spar #1792

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 50 commits into from
Sep 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
fdf4c61
Make sure to actually wrap the action in 'wrapMonadClientSem'
isovector Sep 17, 2021
b239949
Implement wrapMonadClient in terms of wrapMonadClientSem
isovector Sep 17, 2021
c66961d
Pull out IdP effect
isovector Sep 17, 2021
2943335
Push Member IdP constraints throughout
isovector Sep 17, 2021
18bebc6
Pull application logic out of Data and into App
isovector Sep 17, 2021
d58dc66
Use application-level functions instead
isovector Sep 17, 2021
92401dd
Remove deleteTeam from Data too
isovector Sep 17, 2021
6564df4
Get rid of wrapMonadClientWithEnvSem
isovector Sep 17, 2021
ed760c3
Implement wrapSpar
isovector Sep 17, 2021
016d39c
Undo accidental formatting
isovector Sep 17, 2021
c93a8c5
Update cabal
isovector Sep 17, 2021
3c5fb71
make format
isovector Sep 17, 2021
4173291
Update changelog
isovector Sep 17, 2021
dc159fa
Merge branch 'wrap-monad-client-sem' into idp-effect
isovector Sep 17, 2021
55f9ba8
Get rid of the untouchable variable in liftSem
isovector Sep 17, 2021
4e9ebc5
Be very careful about wrapping in the same places
isovector Sep 17, 2021
c585b91
Resort exports
isovector Sep 17, 2021
c79130d
Changelog
isovector Sep 17, 2021
e4a761c
Merge branch 'develop' into idp-effect
isovector Sep 17, 2021
6c7ff76
DefaultSsoCode effect
isovector Sep 18, 2021
2e0319c
ScimTokenStore effect
isovector Sep 18, 2021
d1f9158
wip BindCookie effect
isovector Sep 18, 2021
a62a7eb
Forgot some callsites
isovector Sep 18, 2021
c4d33b7
Merge branch 'idp-effect' of github.com:wireapp/wire-server into idp-…
isovector Sep 18, 2021
29befdb
Get tests compiling again
isovector Sep 18, 2021
dc14528
Get everything compiling
isovector Sep 18, 2021
2de4c63
remove runSparCassSem
isovector Sep 18, 2021
1d800f5
Merge branch 'idp-effect' into misc-effects
isovector Sep 18, 2021
1a708f4
Change the tests to use IdP
isovector Sep 19, 2021
971a581
Finish all SAMLUser and IdP effects refs in tests
isovector Sep 19, 2021
0b47ac2
Excise all references to IdP and SAMLUser effects
isovector Sep 19, 2021
5292607
Merge branch 'idp-effect' into misc-effects
isovector Sep 19, 2021
d76dbf7
make format
isovector Sep 19, 2021
4494e1d
Merge branch 'idp-effect' into misc-effects
isovector Sep 19, 2021
4abee7b
make format
isovector Sep 19, 2021
4c61c14
Remove all references to new effects
isovector Sep 19, 2021
246bb83
make format
isovector Sep 19, 2021
f5bf9af
Add ScimUserTimesStore effect
isovector Sep 20, 2021
c459aa9
ScimExternalIdStore effect
isovector Sep 20, 2021
ae815cb
make format
isovector Sep 20, 2021
15a70a7
Implement scimExternalIdStoreToCassandra
isovector Sep 20, 2021
92ea468
Merge branch 'develop' into misc-effects
isovector Sep 20, 2021
26824fc
Use Members when appropriate
isovector Sep 20, 2021
6eb9f0b
make format
isovector Sep 20, 2021
6e45f86
Fixes.
fisx Sep 21, 2021
88ab682
Merge branch 'develop' into misc-effects
isovector Sep 21, 2021
18ca728
Remove unwritten BindCookie effect modules
isovector Sep 22, 2021
7b751d7
SAMLUser -> SAMLUserStore
isovector Sep 22, 2021
08c6b16
Don't do extraneous lifting
isovector Sep 22, 2021
ddb96be
Changelog.d
isovector Sep 22, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/5-internal/misc-effects
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Pull more polysemy effects out of Spar.
14 changes: 11 additions & 3 deletions services/spar/spar.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ cabal-version: 1.12
--
-- see: https://github.com/sol/hpack
--
-- hash: 920daea26e271c6f0d5688476b71beb67c388d49d00e6f57723aaf825eb3df0d
-- hash: f135fc7f6d1e85694286faaeb615c1808dedee43fd207e59f50864481c2a9cca

name: spar
version: 0.1
Expand Down Expand Up @@ -34,11 +34,19 @@ library
Spar.Scim.Auth
Spar.Scim.Types
Spar.Scim.User
Spar.Sem.DefaultSsoCode
Spar.Sem.DefaultSsoCode.Cassandra
Spar.Sem.IdP
Spar.Sem.IdP.Cassandra
Spar.Sem.IdP.Mem
Spar.Sem.SAMLUser
Spar.Sem.SAMLUser.Cassandra
Spar.Sem.SAMLUserStore
Spar.Sem.SAMLUserStore.Cassandra
Spar.Sem.ScimExternalIdStore
Spar.Sem.ScimExternalIdStore.Cassandra
Spar.Sem.ScimTokenStore
Spar.Sem.ScimTokenStore.Cassandra
Spar.Sem.ScimUserTimesStore
Spar.Sem.ScimUserTimesStore.Cassandra
other-modules:
Paths_spar
hs-source-dirs:
Expand Down
52 changes: 29 additions & 23 deletions services/spar/src/Spar/API.hs
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,15 @@ import qualified Spar.Intra.Brig as Brig
import qualified Spar.Intra.Galley as Galley
import Spar.Orphans ()
import Spar.Scim
import Spar.Sem.DefaultSsoCode (DefaultSsoCode)
import qualified Spar.Sem.DefaultSsoCode as DefaultSsoCode
import qualified Spar.Sem.IdP as IdPEffect
import Spar.Sem.SAMLUser (SAMLUser)
import qualified Spar.Sem.SAMLUser as SAMLUser
import Spar.Sem.SAMLUserStore (SAMLUserStore)
import qualified Spar.Sem.SAMLUserStore as SAMLUserStore
import Spar.Sem.ScimExternalIdStore (ScimExternalIdStore)
import Spar.Sem.ScimTokenStore (ScimTokenStore)
import qualified Spar.Sem.ScimTokenStore as ScimTokenStore
import Spar.Sem.ScimUserTimesStore (ScimUserTimesStore)
import qualified URI.ByteString as URI
import Wire.API.Cookie
import Wire.API.Routes.Public.Spar
Expand All @@ -78,7 +84,7 @@ app ctx =
SAML.setHttpCachePolicy $
serve (Proxy @API) (hoistServer (Proxy @API) (SAML.nt @SparError @(Spar _) ctx) (api $ sparCtxOpts ctx) :: Server API)

api :: Members [IdPEffect.IdP, SAMLUser] r => Opts -> ServerT API (Spar r)
api :: Members '[ScimExternalIdStore, ScimUserTimesStore, ScimTokenStore, DefaultSsoCode, IdPEffect.IdP, SAMLUserStore] r => Opts -> ServerT API (Spar r)
api opts =
apiSSO opts
:<|> authreqPrecheck
Expand All @@ -87,7 +93,7 @@ api opts =
:<|> apiScim
:<|> apiINTERNAL

apiSSO :: Members [IdPEffect.IdP, SAMLUser] r => Opts -> ServerT APISSO (Spar r)
apiSSO :: Members '[ScimTokenStore, DefaultSsoCode, IdPEffect.IdP, SAMLUserStore] r => Opts -> ServerT APISSO (Spar r)
apiSSO opts =
SAML.meta appName (sparSPIssuer Nothing) (sparResponseURI Nothing)
:<|> (\tid -> SAML.meta appName (sparSPIssuer (Just tid)) (sparResponseURI (Just tid)))
Expand All @@ -97,7 +103,7 @@ apiSSO opts =
:<|> authresp . Just
:<|> ssoSettings

apiIDP :: Members [IdPEffect.IdP, SAMLUser] r => ServerT APIIDP (Spar r)
apiIDP :: Members '[ScimTokenStore, IdPEffect.IdP, SAMLUserStore] r => ServerT APIIDP (Spar r)
apiIDP =
idpGet
:<|> idpGetRaw
Expand All @@ -106,7 +112,7 @@ apiIDP =
:<|> idpUpdate
:<|> idpDelete

apiINTERNAL :: Members [IdPEffect.IdP, SAMLUser] r => ServerT APIINTERNAL (Spar r)
apiINTERNAL :: Members '[ScimTokenStore, DefaultSsoCode, IdPEffect.IdP, SAMLUserStore] r => ServerT APIINTERNAL (Spar r)
apiINTERNAL =
internalStatus
:<|> internalDeleteTeam
Expand Down Expand Up @@ -181,7 +187,7 @@ validateRedirectURL uri = do
unless ((SBS.length $ URI.serializeURIRef' uri) <= redirectURLMaxLength) $ do
throwSpar $ SparBadInitiateLoginQueryParams "url-too-long"

authresp :: forall r. Members [IdPEffect.IdP, SAMLUser] r => Maybe TeamId -> Maybe ST -> SAML.AuthnResponseBody -> Spar r Void
authresp :: forall r. Members '[ScimTokenStore, IdPEffect.IdP, SAMLUserStore] r => Maybe TeamId -> Maybe ST -> SAML.AuthnResponseBody -> Spar r Void
authresp mbtid ckyraw arbody = logErrors $ SAML.authresp mbtid (sparSPIssuer mbtid) (sparResponseURI mbtid) go arbody
where
cky :: Maybe BindCookie
Expand All @@ -202,9 +208,9 @@ authresp mbtid ckyraw arbody = logErrors $ SAML.authresp mbtid (sparSPIssuer mbt
(Multipart.inputs (SAML.authnResponseBodyRaw arbody))
ckyraw

ssoSettings :: Spar r SsoSettings
ssoSettings :: Member DefaultSsoCode r => Spar r SsoSettings
ssoSettings = do
SsoSettings <$> wrapMonadClient Data.getDefaultSsoCode
SsoSettings <$> wrapMonadClientSem DefaultSsoCode.get

----------------------------------------------------------------------------
-- IdP API
Expand Down Expand Up @@ -237,20 +243,20 @@ idpGetAll zusr = withDebugLog "idpGetAll" (const Nothing) $ do
-- matter what the team size, it shouldn't choke any servers, just the client (which is
-- probably curl running locally on one of the spar instances).
-- https://github.com/zinfra/backend-issues/issues/1314
idpDelete :: forall r. Members [SAMLUser, IdPEffect.IdP] r => Maybe UserId -> SAML.IdPId -> Maybe Bool -> Spar r NoContent
idpDelete :: forall r. Members '[ScimTokenStore, SAMLUserStore, IdPEffect.IdP] r => Maybe UserId -> SAML.IdPId -> Maybe Bool -> Spar r NoContent
idpDelete zusr idpid (fromMaybe False -> purge) = withDebugLog "idpDelete" (const Nothing) $ do
idp <- SAML.getIdPConfig idpid
_ <- authorizeIdP zusr idp
let issuer = idp ^. SAML.idpMetadata . SAML.edIssuer
team = idp ^. SAML.idpExtraInfo . wiTeam
-- if idp is not empty: fail or purge
idpIsEmpty <- wrapMonadClientSem $ isNothing <$> SAMLUser.getAnyByIssuer issuer
idpIsEmpty <- wrapMonadClientSem $ isNothing <$> SAMLUserStore.getAnyByIssuer issuer
let doPurge :: Spar r ()
doPurge = do
some <- wrapMonadClientSem (SAMLUser.getSomeByIssuer issuer)
some <- wrapMonadClientSem (SAMLUserStore.getSomeByIssuer issuer)
forM_ some $ \(uref, uid) -> do
Brig.deleteBrigUser uid
wrapMonadClientSem (SAMLUser.delete uid uref)
wrapMonadClientSem (SAMLUserStore.delete uid uref)
unless (null some) doPurge
when (not idpIsEmpty) $ do
if purge
Expand All @@ -262,9 +268,9 @@ idpDelete zusr idpid (fromMaybe False -> purge) = withDebugLog "idpDelete" (cons
-- Delete tokens associated with given IdP (we rely on the fact that
-- each IdP has exactly one team so we can look up all tokens
-- associated with the team and then filter them)
tokens <- liftMonadClient $ Data.getScimTokens team
tokens <- liftSem $ ScimTokenStore.getByTeam team
for_ tokens $ \ScimTokenInfo {..} ->
when (stiIdP == Just idpid) $ liftMonadClient $ Data.deleteScimToken team stiId
when (stiIdP == Just idpid) $ liftSem $ ScimTokenStore.delete team stiId
-- Delete IdP config
liftSem $ do
IdPEffect.deleteConfig idpid issuer team
Expand Down Expand Up @@ -292,11 +298,11 @@ idpDelete zusr idpid (fromMaybe False -> purge) = withDebugLog "idpDelete" (cons

-- | This handler only does the json parsing, and leaves all authorization checks and
-- application logic to 'idpCreateXML'.
idpCreate :: Member IdPEffect.IdP r => Maybe UserId -> IdPMetadataInfo -> Maybe SAML.IdPId -> Maybe WireIdPAPIVersion -> Spar r IdP
idpCreate :: Members '[ScimTokenStore, IdPEffect.IdP] r => Maybe UserId -> IdPMetadataInfo -> Maybe SAML.IdPId -> Maybe WireIdPAPIVersion -> Spar r IdP
idpCreate zusr (IdPMetadataValue raw xml) midpid apiversion = idpCreateXML zusr raw xml midpid apiversion

-- | We generate a new UUID for each IdP used as IdPConfig's path, thereby ensuring uniqueness.
idpCreateXML :: Member IdPEffect.IdP r => Maybe UserId -> Text -> SAML.IdPMetadata -> Maybe SAML.IdPId -> Maybe WireIdPAPIVersion -> Spar r IdP
idpCreateXML :: Members '[ScimTokenStore, IdPEffect.IdP] r => Maybe UserId -> Text -> SAML.IdPMetadata -> Maybe SAML.IdPId -> Maybe WireIdPAPIVersion -> Spar r IdP
idpCreateXML zusr raw idpmeta mReplaces (fromMaybe defWireIdPAPIVersion -> apiversion) = withDebugLog "idpCreate" (Just . show . (^. SAML.idpId)) $ do
teamid <- Brig.getZUsrCheckPerm zusr CreateUpdateDeleteIdp
Galley.assertSSOEnabled teamid
Expand All @@ -312,9 +318,9 @@ idpCreateXML zusr raw idpmeta mReplaces (fromMaybe defWireIdPAPIVersion -> apive
-- data contains no information about the idp issuer, only the user name, so no valid saml
-- credentials can be created. To fix this, we need to implement a way to associate scim
-- tokens with IdPs. https://wearezeta.atlassian.net/browse/SQSERVICES-165
assertNoScimOrNoIdP :: Member IdPEffect.IdP r => TeamId -> Spar r ()
assertNoScimOrNoIdP :: Members '[ScimTokenStore, IdPEffect.IdP] r => TeamId -> Spar r ()
assertNoScimOrNoIdP teamid = do
numTokens <- length <$> wrapMonadClient (Data.getScimTokens teamid)
numTokens <- length <$> wrapMonadClientSem (ScimTokenStore.getByTeam teamid)
numIdps <- length <$> wrapMonadClientSem (IdPEffect.getConfigsByTeam teamid)
when (numTokens > 0 && numIdps > 0) $ do
throwSpar $
Expand Down Expand Up @@ -482,14 +488,14 @@ internalStatus = pure NoContent

-- | Cleanup handler that is called by Galley whenever a team is about to
-- get deleted.
internalDeleteTeam :: Members [IdPEffect.IdP, SAMLUser] r => TeamId -> Spar r NoContent
internalDeleteTeam :: Members '[ScimTokenStore, IdPEffect.IdP, SAMLUserStore] r => TeamId -> Spar r NoContent
internalDeleteTeam team = do
wrapSpar $ deleteTeam team
pure NoContent

internalPutSsoSettings :: Member IdPEffect.IdP r => SsoSettings -> Spar r NoContent
internalPutSsoSettings :: Members '[DefaultSsoCode, IdPEffect.IdP] r => SsoSettings -> Spar r NoContent
internalPutSsoSettings SsoSettings {defaultSsoCode = Nothing} = do
wrapMonadClient $ Data.deleteDefaultSsoCode
wrapMonadClientSem $ DefaultSsoCode.delete
pure NoContent
internalPutSsoSettings SsoSettings {defaultSsoCode = Just code} = do
wrapMonadClientSem (IdPEffect.getConfig code) >>= \case
Expand All @@ -499,5 +505,5 @@ internalPutSsoSettings SsoSettings {defaultSsoCode = Just code} = do
-- "Could not find IdP".
throwSpar $ SparIdPNotFound mempty
Just _ -> do
wrapMonadClient $ Data.storeDefaultSsoCode code
wrapMonadClientSem $ DefaultSsoCode.store code
pure NoContent
Loading