wai-saml2-0.4: src/Network/Wai/SAML2/Request.hs
-------------------------------------------------------------------------------
-- SAML2 Middleware for WAI --
-------------------------------------------------------------------------------
-- This source code is licensed under the MIT license found in the LICENSE --
-- file in the root directory of this source tree. --
-------------------------------------------------------------------------------
-- | Defines types and functions for SP-initiated SSO. Use `issueAuthnRequest`
-- to initialise an `AuthnRequest` value which stores the parameters for the
-- authentication request you wish to issue to the IdP. You can update this
-- value as required.
--
-- Use `renderBase64` to render the request for use with a HTTP POST binding [1], or
-- `renderUrlEncodingDeflate` for HTTP redirect binding[2] respectively.
-- You may wish to read
-- the [SAML2 overview for this process](http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0-cd-02.html#5.1.2.SP-Initiated%20SSO:%20%20Redirect/POST%20Bindings|outline).
--
-- * [1] https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf#page=21
-- Section 3.5 HTTP POST Binding
-- * [2] https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf#page=15
-- Section 3.4 HTTP Redirect Binding
--
-- @since 0.4
module Network.Wai.SAML2.Request (
AuthnRequest(..),
issueAuthnRequest,
renderBase64,
renderUrlEncodingDeflate,
renderXML,
) where
-------------------------------------------------------------------------------
import Crypto.Random
import Data.Time.Clock
import Network.Wai.SAML2.NameIDFormat
import Network.Wai.SAML2.XML
import Text.XML
import qualified Codec.Compression.Zlib.Raw as Deflate
import qualified Data.ByteString as B
import qualified Data.ByteString.Base16 as Base16
import qualified Data.ByteString.Base64 as Base64
import qualified Data.ByteString.Lazy as BL
import qualified Data.Map.Strict as Map
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import Network.HTTP.Types (urlEncode)
-------------------------------------------------------------------------------
-- | Parameters for SP-initiated SSO
data AuthnRequest
-- Reference [AuthnRequest]
= AuthnRequest {
-- | The time at which 'AuthnRequest' was created.
authnRequestTimestamp :: !UTCTime
-- | Unique identifier for 'AuthnRequest' which should be preserved
-- by the IdP in its response.
, authnRequestID :: !T.Text
-- | SP Entity ID
, authnRequestIssuer :: !T.Text
-- | Allow IdP to generate a new identifier
, authnRequestAllowCreate :: !Bool
-- | The URI reference corresponding to a name identifier format
, authnRequestNameIDFormat :: !NameIDFormat
}
deriving (Eq, Show)
-- | Creates a default 'AuthnRequest' with the current timestamp and a
-- randomly-generated ID.
issueAuthnRequest
:: T.Text -- ^ SP Entity ID
-> IO AuthnRequest
issueAuthnRequest authnRequestIssuer = do
authnRequestTimestamp <- getCurrentTime
-- Digits are not allowed in initial position
-- Reference [ID Values]
authnRequestID <- ("id" <>) . T.decodeUtf8 . Base16.encode <$> getRandomBytes 16
pure AuthnRequest{
authnRequestAllowCreate = True
, authnRequestNameIDFormat = Transient
, ..
}
-- | Renders an `AuthnRequest` for SP initiated SSO according to
-- @urn:oasis:names:tc:SAML:2.0:bindings:URL-Encoding:DEFLATE@ and suitable for
-- use with HTTP Redirect binding
--
-- The value should be sent as a query parameter named @SAMLRequest@
renderUrlEncodingDeflate :: AuthnRequest -> B.ByteString
renderUrlEncodingDeflate request =
urlEncode True $ Base64.encode $ BL.toStrict $ Deflate.compress $ renderXML request
-- | Renders and base64-encodes an `AuthnRequest` for SP initiated SSO suitable
-- for use with HTTP POST binding
--
-- If used in an HTTP POST binding, the value should be sent as an invisible
-- form control named @SAMLRequest@
renderBase64 :: AuthnRequest -> B.ByteString
renderBase64 request = Base64.encode $ BL.toStrict $ renderXML request
-- | Render an `AuthnRequest` as XML
renderXML :: AuthnRequest -> BL.ByteString
renderXML AuthnRequest{..} =
renderLBS def $
-- Reference [HTTP redirect binding]
Document{
documentPrologue = Prologue [] Nothing []
, documentRoot = root
, documentEpilogue = []
}
where
timestamp = showUTCTime authnRequestTimestamp
root = Element
(saml2pName "AuthnRequest")
(Map.fromList
[ ("xmlns:samlp", "urn:oasis:names:tc:SAML:2.0:protocol")
, ("xmlns:saml", "urn:oasis:names:tc:SAML:2.0:assertion")
, ("ID", authnRequestID) -- Reference [RequestAbstractType] and see [ID Values]
, ("Version", "2.0") -- [RequestAbstractType]
, ("IssueInstant", timestamp) -- [RequestAbstractType]
, ("AssertionConsumerServiceIndex", "1") -- [AuthnRequest]
])
[NodeElement issuer, NodeElement nameIdPolicy]
-- Reference [RequestAbstractType]
issuer = Element
(saml2Name "Issuer")
mempty
[NodeContent authnRequestIssuer]
-- Reference [AuthnRequest]
nameIdPolicy = Element
(saml2pName "NameIDPolicy")
(Map.fromList
[ ("allowCreate"
, if authnRequestAllowCreate then "true" else "false")
, ("Format", showNameIDFormat authnRequestNameIDFormat)
])
[]
-------------------------------------------------------------------------------
-- Reference [RequestAbstractType]
-- Source: https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf#page=36
-- Section: 3.2.1 Complex Type RequestAbstractType
-- Reference [AuthnRequest]
-- Source: https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf#page=48
-- Section: 3.4.1 Element <AuthnRequest>
-- Reference [HTTP redirect binding]
-- Source:
-- https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf#page=15
-- Section: 3.4 HTTP Redirect Binding
-- Reference [ID Values]
-- Source: https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf#page=9
-- Section: 1.3.4 ID and ID Reference Values
-- Note: ID Values must conform to "xs:ID", which in turn has a restriction of "xs:NCName" (non-colonized name).
-- In practice that means they are a string consisting of
-- first 1 of: Letter or '_'
-- then 0 or more of: Letter, Digit, '.', '-', '_', CombiningChar, Extender
--
-- Definitions of character classes: https://www.w3.org/TR/2000/WD-xml-2e-20000814#CharClasses
-- Compare e.g. https://stackoverflow.com/questions/1631396/what-is-an-xsncname-type-and-when-should-it-be-used
--and https://www.w3.org/TR/xmlschema-2/#dt-ccesN (see \i and \c, bute not that colons are excluded)