saml2-web-sso-0.20: Library and example web app for the SAML Web-based SSO profile.
Safe HaskellSafe-Inferred
LanguageHaskell2010

SAML2.WebSSO.SP

Synopsis

Documentation

type SP m = (HasConfig m, HasLogger m, HasCreateUUID m, HasNow m) Source #

Application logic of the service provider.

class HasLogger m where Source #

Minimal complete definition

Nothing

Methods

logger :: Level -> String -> m () Source #

default logger :: (HasConfig m, MonadIO m) => Level -> String -> m () Source #

Instances

Instances details
HasLogger SimpleSP Source # 
Instance details

Defined in SAML2.WebSSO.API.Example

Methods

logger :: Level -> String -> SimpleSP () Source #

HasLogger TestSP Source # 
Instance details

Defined in SAML2.WebSSO.Test.Util.TestSP

Methods

logger :: Level -> String -> TestSP () Source #

(Monad m, HasLogger m) => HasLogger (JudgeT m) Source # 
Instance details

Defined in SAML2.WebSSO.SP

Methods

logger :: Level -> String -> JudgeT m () Source #

class HasCreateUUID m where Source #

Minimal complete definition

Nothing

Methods

createUUID :: m UUID Source #

default createUUID :: MonadIO m => m UUID Source #

Instances

Instances details
HasCreateUUID SimpleSP Source # 
Instance details

Defined in SAML2.WebSSO.API.Example

HasCreateUUID TestSP Source # 
Instance details

Defined in SAML2.WebSSO.Test.Util.TestSP

(Monad m, HasCreateUUID m) => HasCreateUUID (JudgeT m) Source # 
Instance details

Defined in SAML2.WebSSO.SP

class HasNow m where Source #

Minimal complete definition

Nothing

Methods

getNow :: m Time Source #

default getNow :: MonadIO m => m Time Source #

Instances

Instances details
HasNow SimpleSP Source # 
Instance details

Defined in SAML2.WebSSO.API.Example

HasNow TestSP Source # 
Instance details

Defined in SAML2.WebSSO.Test.Util.TestSP

(Monad m, HasNow m) => HasNow (JudgeT m) Source # 
Instance details

Defined in SAML2.WebSSO.SP

Methods

getNow :: JudgeT m Time Source #

class SPStoreID i m where Source #

Methods

storeID :: ID i -> Time -> m () Source #

unStoreID :: ID i -> m () Source #

isAliveID Source #

Arguments

:: ID i 
-> m Bool

stored and not timed out.

class MonadError err m => SPStoreIdP err m where Source #

Associated Types

type IdPConfigExtra m :: Type Source #

type IdPConfigSPId m :: Type Source #

class (SP m, SPStore m, SPStoreIdP err m, MonadError err m) => SPHandler err m where Source #

HTTP handling of the service provider.

Associated Types

type NTCTX m :: Type Source #

Methods

nt :: forall x. NTCTX m -> m x -> Handler x Source #

Instances

Instances details
SPHandler SimpleError SimpleSP Source #

If you read the Config initially in IO and then pass it into the monad via Reader, you safe disk load and redundant debug logs.

Instance details

Defined in SAML2.WebSSO.API.Example

Associated Types

type NTCTX SimpleSP Source #

Methods

nt :: NTCTX SimpleSP -> SimpleSP x -> Handler x Source #

SPHandler SimpleError TestSP Source # 
Instance details

Defined in SAML2.WebSSO.Test.Util.TestSP

Associated Types

type NTCTX TestSP Source #

Methods

nt :: NTCTX TestSP -> TestSP x -> Handler x Source #

storeAssertion :: (Monad m, SPStore m) => ID Assertion -> Time -> m Bool Source #

Store Assertions to prevent replay attack. Time argument is end of life (IDs may be garbage collected after that time). Iff assertion has already been stored and is still alive, return False.

loggerIO :: MonadIO m => Level -> Level -> String -> m () Source #

createID :: (Functor m, SP m) => m (ID a) Source #

(Microsoft Active Directory likes IDs to be of the form idhex digits: ID . cs . ("id" <>) . filter (/= -) . cs . UUID.toText $ createUUID. Hopefully the more common form produced by this function is also ok.)

createAuthnRequest :: (Monad m, SP m, SPStore m) => NominalDiffTime -> m Issuer -> m AuthnRequest Source #

Generate an AuthnRequest value for the initiate-login response. The NameIdPolicy is NameIDFUnspecified.

We do not use XML encryption (which appears to have been dead for years). We do not sign AuthnRequest values. Both encryption and signatures are optional, and rarely found in the wild. Security: (1) the AuthnReq ID is stored, and Assertions need to refer to a valid one in order to get a positive AccessVerdict by judge. AuthnResponses answering requests not originating from us will be ignored. (2) the request Issuer is there to help the IdP construct an AuthnResponse that we will accept. If it is changed by an attacker (either in the browser or on the wire, which is weakly procted by TLS) and passed on to the legitimate IdP, there will either be no access-granting AuthnResponse, or it will contain the wrong audience and be rejected by us. (3) The nameID policy is expected to be configured on the IdP side to not support any set of name spaces that overlap (e.g. because user A has an email that is the account name of user B).

tolerance :: NominalDiffTime Source #

The clock drift between IdP and us that we allow for.

FUTUREWORK: make this configurable

earlier :: Time -> Time -> Bool Source #

First arg must be comfortably earlier than the latter: if tolerance is one minute, then ("4:32:14" earlier "4:31:15") == True.

If this returns False you should be *more* inclined to throw an error, so a good calling pattern is unless (a earlier b) throwSomething, which is very different from when (b earlier a) throwSomething.

noLater :: Time -> Time -> Bool Source #

Even though this makes little sense, the standard requires to distinguish between "less" and "less or equal". For all practical purposes, you may consider noLater == earlier.

getSsoURI :: forall m endpoint api. (HasCallStack, Functor m, HasConfig m, IsElem endpoint api, HasLink endpoint, ToHttpApiData (MkLink endpoint)) => Proxy api -> Proxy endpoint -> m URI Source #

getSsoURI' :: forall endpoint api a (f :: Type -> Type) t. (Functor f, HasConfig f, MkLink endpoint ~ (t -> a), HasLink endpoint, ToHttpApiData a, IsElem endpoint api) => Proxy api -> Proxy endpoint -> t -> f URI Source #

getSsoURI for links that have one variable path segment.

FUTUREWORK: this is only sometimes what we need. it would be nice to have a type class with a method getSsoURI for arbitrary path arities.

newtype JudgeT m a Source #

This monad collects errors in a writer, so that the reasons for access denial are as helpful as possible. It is a little like an exception monad, except you can throw several exceptions without interrupting the flow, and will get a list of them at the end.

NOTE: -XGeneralizedNewtypeDeriving does not help with the boilerplate instances below, since this is a transformer stack and not a concrete Monad.

Instances

Instances details
(Monad m, SPStoreID i m) => SPStoreID (i :: k) (JudgeT m) Source # 
Instance details

Defined in SAML2.WebSSO.SP

Methods

storeID :: ID i -> Time -> JudgeT m () Source #

unStoreID :: ID i -> JudgeT m () Source #

isAliveID :: ID i -> JudgeT m Bool Source #

(Functor m, Applicative m, Monad m) => Applicative (JudgeT m) Source # 
Instance details

Defined in SAML2.WebSSO.SP

Methods

pure :: a -> JudgeT m a #

(<*>) :: JudgeT m (a -> b) -> JudgeT m a -> JudgeT m b #

liftA2 :: (a -> b -> c) -> JudgeT m a -> JudgeT m b -> JudgeT m c #

(*>) :: JudgeT m a -> JudgeT m b -> JudgeT m b #

(<*) :: JudgeT m a -> JudgeT m b -> JudgeT m a #

(Functor m, Applicative m, Monad m) => Functor (JudgeT m) Source # 
Instance details

Defined in SAML2.WebSSO.SP

Methods

fmap :: (a -> b) -> JudgeT m a -> JudgeT m b #

(<$) :: a -> JudgeT m b -> JudgeT m a #

(Functor m, Applicative m, Monad m) => Monad (JudgeT m) Source # 
Instance details

Defined in SAML2.WebSSO.SP

Methods

(>>=) :: JudgeT m a -> (a -> JudgeT m b) -> JudgeT m b #

(>>) :: JudgeT m a -> JudgeT m b -> JudgeT m b #

return :: a -> JudgeT m a #

(Monad m, HasConfig m) => HasConfig (JudgeT m) Source # 
Instance details

Defined in SAML2.WebSSO.SP

(Monad m, HasCreateUUID m) => HasCreateUUID (JudgeT m) Source # 
Instance details

Defined in SAML2.WebSSO.SP

(Monad m, HasLogger m) => HasLogger (JudgeT m) Source # 
Instance details

Defined in SAML2.WebSSO.SP

Methods

logger :: Level -> String -> JudgeT m () Source #

(Monad m, HasNow m) => HasNow (JudgeT m) Source # 
Instance details

Defined in SAML2.WebSSO.SP

Methods

getNow :: JudgeT m Time Source #

(Functor m, Applicative m, Monad m) => MonadJudge (JudgeT m) Source # 
Instance details

Defined in SAML2.WebSSO.SP

data JudgeCtx Source #

Note on security: we assume that the SP has only one audience, which is defined here. If you have different sub-services running on your SP, associate a dedicated IdP with each sub-service. (To be more specific, construct AuthnReqs to different IdPs for each sub-service.) Secure association with the service can then be guaranteed via the Issuer in the signed Assertion.

Instances

Instances details
Show JudgeCtx Source # 
Instance details

Defined in SAML2.WebSSO.SP

Eq JudgeCtx Source # 
Instance details

Defined in SAML2.WebSSO.SP

class (Functor m, Applicative m, Monad m) => MonadJudge m where Source #

Instances

Instances details
(Functor m, Applicative m, Monad m) => MonadJudge (JudgeT m) Source # 
Instance details

Defined in SAML2.WebSSO.SP

judge :: (Monad m, SP m, SPStore m) => AuthnResponse -> JudgeCtx -> m AccessVerdict Source #

3/4.1.4.2
, [3/4.1.4.3]; specific to active-directory: https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-single-sign-on-protocol-reference

judge does not consider the following parts of the AuthnResponse. * subjectID * scdAddress ("If any bearer SubjectConfirmationData includes an Address attribute, the service provider MAY check the user agent's client address against it." [3/4.1.4.3]) * astSessionIndex * astSubjectLocality ("This element is entirely advisory, since both of these fields are quite easily “spoofed,” but may be useful information in some applications." [1/2.7.2.1])

judge does check the rspStatus field, even though that makes no sense: most IdPs encrypt the Assertions, but not the entire response, and we make taht an assumption when validating signatures. So the status info is not signed, and could easily be changed by an attacker attempting to authenticate.

checkInResponseTo :: (SPStore m, MonadJudge m) => String -> ID AuthnRequest -> m () Source #

If this fails, we could continue (deny), but we stop processing (giveup) to make DOS attacks harder.

checkIsInPast :: (SP m, MonadJudge m) => (Time -> Time -> DeniedReason) -> Time -> m () Source #

checkDestination :: (HasConfig m, MonadJudge m) => (String -> String -> DeniedReason) -> URI -> m () Source #

Check that the response is intended for us (based on config's finalize-login uri stored in JudgeCtx).

checkSubjectConfirmations :: (SP m, SPStore m, MonadJudge m) => [Assertion] -> m () Source #

Check all SubjectConfirmations and Subjects in all Assertion. Deny if not at least one confirmation has method "bearer".

data HasBearerConfirmation Source #

Instances

Instances details
Monoid HasBearerConfirmation Source # 
Instance details

Defined in SAML2.WebSSO.SP

Semigroup HasBearerConfirmation Source # 
Instance details

Defined in SAML2.WebSSO.SP

Bounded HasBearerConfirmation Source # 
Instance details

Defined in SAML2.WebSSO.SP

Enum HasBearerConfirmation Source # 
Instance details

Defined in SAML2.WebSSO.SP

Eq HasBearerConfirmation Source # 
Instance details

Defined in SAML2.WebSSO.SP

Ord HasBearerConfirmation Source # 
Instance details

Defined in SAML2.WebSSO.SP

checkSubjectConfirmation :: (SPStore m, MonadJudge m) => Assertion -> SubjectConfirmation -> m HasBearerConfirmation Source #

Locally check one SubjectConfirmation and deny if there is a problem. If this is a confirmation of method "bearer", return HasBearerConfirmation.

checkConditions :: forall m. (HasCallStack, MonadJudge m, SP m) => Conditions -> m () Source #