saml2-web-sso-0.20: Library and example web app for the SAML Web-based SSO profile.
Safe HaskellNone
LanguageGHC2021

SAML2.WebSSO.API

Description

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

Documentation

meta :: (SPStore m, MonadError err m, HasConfig m) => ST -> m Issuer -> m URI -> m [ContactPerson] -> m SPMetadata Source #

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.

data FormRedirect xml Source #

2/3.5.4

Constructors

FormRedirect URI xml 

Instances

Instances details
HasXMLRoot xml => MimeRender HTML (FormRedirect xml) Source # 
Instance details

Defined in SAML2.WebSSO.API

HasXMLRoot xml => MimeUnrender HTML (FormRedirect xml) Source # 
Instance details

Defined in SAML2.WebSSO.API

Arbitrary a => Arbitrary (FormRedirect a) Source # 
Instance details

Defined in SAML2.WebSSO.Test.Arbitrary

Generic (FormRedirect xml) Source # 
Instance details

Defined in SAML2.WebSSO.API

Associated Types

type Rep (FormRedirect xml) 
Instance details

Defined in SAML2.WebSSO.API

type Rep (FormRedirect xml) = D1 ('MetaData "FormRedirect" "SAML2.WebSSO.API" "saml2-web-sso-0.20-1GwqfFH5XrPLS7nLjYu78J" 'False) (C1 ('MetaCons "FormRedirect" 'PrefixI 'False) (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 URI) :*: S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 xml)))

Methods

from :: FormRedirect xml -> Rep (FormRedirect xml) x #

to :: Rep (FormRedirect xml) x -> FormRedirect xml #

Show xml => Show (FormRedirect xml) Source # 
Instance details

Defined in SAML2.WebSSO.API

Eq xml => Eq (FormRedirect xml) Source # 
Instance details

Defined in SAML2.WebSSO.API

Methods

(==) :: FormRedirect xml -> FormRedirect xml -> Bool #

(/=) :: FormRedirect xml -> FormRedirect xml -> Bool #

type Rep (FormRedirect xml) Source # 
Instance details

Defined in SAML2.WebSSO.API

type Rep (FormRedirect xml) = D1 ('MetaData "FormRedirect" "SAML2.WebSSO.API" "saml2-web-sso-0.20-1GwqfFH5XrPLS7nLjYu78J" 'False) (C1 ('MetaCons "FormRedirect" 'PrefixI 'False) (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 URI) :*: S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 xml)))

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. As a consequence, you will get the signature validation error when looking at it, but the type still guarantees that the signature verification cannot be circumvented.

Constructors

AuthnResponseBody 

Fields

Instances

Instances details
FromMultipart Mem AuthnResponseBody Source # 
Instance details

Defined in SAML2.WebSSO.API

type WithCookieAndLocation = Headers '[Header "Set-Cookie" Cky, Header "Location" URI] Source #

type APIMeta' = "meta" :> APIMeta Source #

type APIAuthReq' = "authreq" :> APIAuthReq Source #

type APIAuthResp' = "authresp" :> APIAuthResp 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).

parseAuthnResponseBody :: forall m err spid extra. (SPStoreIdP (Error err) m, spid ~ IdPConfigSPId m, extra ~ IdPConfigExtra m) => Maybe spid -> ST -> m (NonEmpty Assertion, IdPConfig extra, UnvalidatedSAMLStatus) Source #

Implies verification, hence the constraints and the optional service provider ID (needed for IdP lookup).

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 (NonEmpty Assertion) 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.

The assertions are returned.

NEVER PROCESS AN ASSERTION NOT RETURNED BY A SIGNATURE VERifICATION FUNCTION.

simpleVerifyAuthnResponse ensures that the assertion list is non-empty, and (more importantly) that the IDs are unique accross the entire document we pass to the signature validation function. REASON: since we use xml-conduit as a parser here, and signature validation uses a different one, we can't guarantee that the assertions we return here are the ones of which we validated the signatures. this problem can get very real very quickly: https://github.blog/security/sign-in-as-anyone-bypassing-saml-sso-authentication-with-parser-differentials

allVerifies :: forall m err. MonadError (Error err) m => NonEmpty SignCreds -> LBS -> NonEmpty String -> m (NonEmpty Assertion) 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 (Error err) m => NonEmpty SignCreds -> LBS -> NonEmpty String -> m (NonEmpty XmlTree) 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.)

class HasXML xml => HasFormRedirect xml where Source #

Methods

formRedirectFieldName :: xml -> ST Source #

enterH :: SP m => String -> m () Source #

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.

leaveH :: (Monad m, Show a, SP m) => a -> m a Source #

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 :: (SPStoreIdP (Error err) m, SPStoreRequest AuthnRequest m, extra ~ IdPConfigExtra m, SPStore m) => Maybe (IdPConfigSPId m) -> m Issuer -> m URI -> (NonEmpty Assertion -> 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.

authresp' :: forall m err extra. (SPStoreIdP (Error err) m, SP m, MonadError (Error err) m, SPStore m, extra ~ IdPConfigExtra 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.

data HandleVerdict (m :: Type -> Type) 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.

type OnSuccessRedirect (m :: Type -> Type) = UserRef -> m (Cky, URI) Source #

type CookieName = "saml2-web-sso" Source #