Safe Haskell | Safe-Inferred |
---|---|
Language | Haskell2010 |
This is a partial implementation of Web SSO using the HTTP Post Binding [2/3.5].
The default API offers 3 end-points: one for retrieving the AuthnRequest
in a redirect to the
IdP; one for delivering the AuthnResponse
that will re-direct to some fixed landing page; and
one for retrieving the SP's metadata.
There are other scenarios, e.g. all resources on the page could be guarded with an authentication check and redirect the client to the IdP, and make sure that the client lands on the initally requested resource after successful authentication. With the building blocks provided by this module, it should be straight-forward to implemented all of these scenarios.
This module works best if imported qualified.
FUTUREWORK: servant-server is quite heavy. we should have a cabal flag to exclude it.
Synopsis
- type API = APIMeta' :<|> (APIAuthReq' :<|> APIAuthResp')
- type Cky = SimpleSetCookie CookieName
- type APIMeta = Get '[XML] SPMetadata
- type APIAuthReq = Capture "idp" IdPId :> Get '[HTML] (FormRedirect AuthnRequest)
- data FormRedirect xml = FormRedirect URI xml
- type APIAuthResp = MultipartForm Mem AuthnResponseBody :> PostRedir '[HTML] (WithCookieAndLocation ST)
- data AuthnResponseBody = AuthnResponseBody {
- authnResponseBodyAction :: forall m err spid extra. (SPStoreIdP (Error err) m, spid ~ IdPConfigSPId m, extra ~ IdPConfigExtra m) => Maybe spid -> m (AuthnResponse, IdPConfig extra)
- authnResponseBodyRaw :: MultipartData Mem
- type WithCookieAndLocation = Headers '[Header "Set-Cookie" Cky, Header "Location" URI]
- type APIMeta' = "meta" :> APIMeta
- type APIAuthReq' = "authreq" :> APIAuthReq
- type APIAuthResp' = "authresp" :> APIAuthResp
- data HandleVerdict m
- class HasXML xml => HasFormRedirect xml where
- formRedirectFieldName :: xml -> ST
- type OnSuccessRedirect m = UserRef -> m (Cky, URI)
- type CookieName = "saml2-web-sso"
- data SubjectFoldCase
- type ResponseVerdict = ServerError
- meta :: forall m err. (SPStore m, MonadError err m, HasConfig m) => ST -> m Issuer -> m URI -> m SPMetadata
- api :: forall err m. SPHandler (Error err) m => ST -> HandleVerdict m -> ServerT API m
- defSPIssuer :: (Functor m, HasConfig m) => m Issuer
- defResponseURI :: (Functor m, HasConfig m) => m URI
- authreq' :: (SPStore m, SPStoreIdP err m, MonadError err m) => m Issuer -> IdPId -> m (FormRedirect AuthnRequest)
- authresp' :: (SP m, SPStoreIdP (Error err) m, SPStoreID Assertion m, SPStoreID AuthnRequest m) => Maybe (IdPConfigSPId m) -> m Issuer -> m URI -> HandleVerdict m -> AuthnResponseBody -> m (WithCookieAndLocation ST)
- renderAuthnResponseBody :: AuthnResponse -> LBS
- parseAuthnResponseBody :: forall m err spid extra. (SPStoreIdP (Error err) m, spid ~ IdPConfigSPId m, extra ~ IdPConfigExtra m) => Maybe spid -> ST -> m (AuthnResponse, IdPConfig extra)
- idpToCreds :: forall m err extra. (SPStoreIdP (Error err) m, extra ~ IdPConfigExtra m) => Issuer -> IdPConfig extra -> m (NonEmpty SignCreds)
- simpleVerifyAuthnResponse :: forall m err. MonadError (Error err) m => NonEmpty SignCreds -> LBS -> m ()
- authnResponseBodyToMultipart :: AuthnResponse -> MultipartData tag
- issuerToCreds :: forall m err. SPStoreIdP (Error err) m => Maybe Issuer -> Maybe (IdPConfigSPId m) -> m (NonEmpty SignCreds)
- allVerifies :: forall m err. MonadError (Error err) m => NonEmpty SignCreds -> LBS -> [String] -> m ()
- verifyADFS :: MonadError String m => NonEmpty SignCreds -> LBS -> [String] -> m ()
- enterH :: SP m => String -> m ()
- authreq :: (SPStore m, SPStoreIdP err m, MonadError err m) => NominalDiffTime -> m Issuer -> IdPId -> m (FormRedirect AuthnRequest)
- leaveH :: (Monad m, Show a, SP m) => a -> m a
- defReqTTL :: NominalDiffTime
- authresp :: (SP m, SPStoreIdP (Error err) m, extra ~ IdPConfigExtra m, SPStoreID Assertion m, SPStoreID AuthnRequest m) => Maybe (IdPConfigSPId m) -> m Issuer -> m URI -> (AuthnResponse -> IdPConfig extra -> AccessVerdict -> m resp) -> AuthnResponseBody -> m resp
- simpleHandleVerdict :: (SP m, MonadError (Error err) m) => OnSuccessRedirect m -> AccessVerdict -> m (WithCookieAndLocation ST)
- simpleOnSuccess :: (Monad m, SP m) => SubjectFoldCase -> OnSuccessRedirect m
- crash :: (SP m, MonadError (Error err) m) => String -> m a
- module SAML2.WebSSO.Servant
Documentation
type API = APIMeta' :<|> (APIAuthReq' :<|> APIAuthResp') Source #
Consider rate-limiting these end-points to mitigate DOS attacks. APIAuthReq
uses database
space, and APIAuthResp
uses both database space and CPU.
type Cky = SimpleSetCookie CookieName Source #
type APIAuthReq = Capture "idp" IdPId :> Get '[HTML] (FormRedirect AuthnRequest) Source #
data FormRedirect xml Source #
- 2/3.5.4
FormRedirect URI xml |
Instances
type APIAuthResp = MultipartForm Mem AuthnResponseBody :> PostRedir '[HTML] (WithCookieAndLocation ST) Source #
data AuthnResponseBody Source #
An AuthnResponseBody
contains a AuthnResponse
, but you need to give it an SPId for
IdP lookup and trust base for signature verification first, plus you may get an error when
you're looking at it.
AuthnResponseBody | |
|
Instances
type APIAuthReq' = "authreq" :> APIAuthReq Source #
type APIAuthResp' = "authresp" :> APIAuthResp Source #
data HandleVerdict m Source #
We support two cases: redirect with a cookie, and a generic response with arbitrary status,
headers, and body. The latter case fits the ServerError
type well, but we give it a more
suitable name here.
class HasXML xml => HasFormRedirect xml where Source #
formRedirectFieldName :: xml -> ST Source #
Instances
HasFormRedirect SomeSAMLRequest Source # | |
Defined in SAML2.WebSSO.Test.Util.Misc | |
HasFormRedirect AuthnRequest Source # | |
Defined in SAML2.WebSSO.API |
type CookieName = "saml2-web-sso" Source #
type ResponseVerdict = ServerError Source #
meta :: forall m err. (SPStore m, MonadError err m, HasConfig m) => ST -> m Issuer -> m URI -> m SPMetadata Source #
defSPIssuer :: (Functor m, HasConfig m) => m Issuer Source #
The Issuer
is an identifier of a SAML participant. In this case, it's the SP, ie.,
ourselves. For simplicity, we re-use the response URI here.
defResponseURI :: (Functor m, HasConfig m) => m URI Source #
The URI that AuthnResponse
values are delivered to (APIAuthResp
).
authreq' :: (SPStore m, SPStoreIdP err m, MonadError err m) => m Issuer -> IdPId -> m (FormRedirect AuthnRequest) Source #
authreq
with request life expectancy defaulting to 8 hours.
authresp' :: (SP m, SPStoreIdP (Error err) m, SPStoreID Assertion m, SPStoreID AuthnRequest m) => Maybe (IdPConfigSPId m) -> m Issuer -> m URI -> HandleVerdict m -> AuthnResponseBody -> m (WithCookieAndLocation ST) Source #
a variant of authresp
with a less general verdict handler.
parseAuthnResponseBody :: forall m err spid extra. (SPStoreIdP (Error err) m, spid ~ IdPConfigSPId m, extra ~ IdPConfigExtra m) => Maybe spid -> ST -> m (AuthnResponse, IdPConfig extra) Source #
Implies verification, hence the constraints.
idpToCreds :: forall m err extra. (SPStoreIdP (Error err) m, extra ~ IdPConfigExtra m) => Issuer -> IdPConfig extra -> m (NonEmpty SignCreds) Source #
simpleVerifyAuthnResponse :: forall m err. MonadError (Error err) m => NonEmpty SignCreds -> LBS -> m () Source #
Pull assertions sub-forest and pass unparsed xml input to verify
with a reference to
each assertion individually. The input must be a valid AuthnResponse
. All assertions
need to be signed by the issuer given in the arguments using the same key.
issuerToCreds :: forall m err. SPStoreIdP (Error err) m => Maybe Issuer -> Maybe (IdPConfigSPId m) -> m (NonEmpty SignCreds) Source #
allVerifies :: forall m err. MonadError (Error err) m => NonEmpty SignCreds -> LBS -> [String] -> m () Source #
Call verify and, if that fails, any work-arounds we have. Discard all errors from work-arounds, and throw the error from the regular verification.
verifyADFS :: MonadError String m => NonEmpty SignCreds -> LBS -> [String] -> m () Source #
ADFS illegally breaks whitespace after signing documents; here we try to fix that. https://github.com/wireapp/wire-server/issues/656 (This may also have been a copy&paste issue in customer support, but let's just leave it in just in case.)
authreq :: (SPStore m, SPStoreIdP err m, MonadError err m) => NominalDiffTime -> m Issuer -> IdPId -> m (FormRedirect AuthnRequest) Source #
Create authnreq, store it for comparison against assertions later, and return it in an HTTP redirect together with the IdP's URI.
authresp :: (SP m, SPStoreIdP (Error err) m, extra ~ IdPConfigExtra m, SPStoreID Assertion m, SPStoreID AuthnRequest m) => Maybe (IdPConfigSPId m) -> m Issuer -> m URI -> (AuthnResponse -> IdPConfig extra -> AccessVerdict -> m resp) -> AuthnResponseBody -> m resp Source #
parse and validate response, and pass the verdict to a user-provided verdict handler. the
handler takes a response and a verdict (provided by this package), and can cause any effects in
m
and return anything it likes.
simpleHandleVerdict :: (SP m, MonadError (Error err) m) => OnSuccessRedirect m -> AccessVerdict -> m (WithCookieAndLocation ST) Source #
simpleOnSuccess :: (Monad m, SP m) => SubjectFoldCase -> OnSuccessRedirect m Source #
crash :: (SP m, MonadError (Error err) m) => String -> m a Source #
Write error info to log, and apologise to client.
module SAML2.WebSSO.Servant