diff --git a/README.md b/README.md index 8876d1a..633f605 100644 --- a/README.md +++ b/README.md @@ -133,13 +133,14 @@ import module namespace exsaml="http://exist-db.org/xquery/exsaml" at 'xmldb:/// that gets auto-submitted by the user's browser, back to the SP (eXist) :) declare option exist:serialize "method=html media-type=text/html indent=no"; +declare variable $exsaml-config := exsaml:parse-xml-config(doc("/db/apps/your-app/config-exsaml.xml")); declare variable $cid := exsaml:generate-correlation-id(); (: handle SP endpoint to process SAML response in HTTP POST :) if ($exist:path = "/SAML2SP") then let $log := exsaml:log('info', $cid, "SAML2SP: processing SAML response") - let $status := exsaml:process-saml-response-post() + let $status := exsaml:process-saml-response-post($exsaml-config) let $log := exsaml:log('debug', $cid, "endpoint SAML2SP; status: " || $status/@code) return if ($status/@code >= 0) then @@ -157,21 +158,21 @@ then else if ($exist:path = '/logout') then let $_ := - if (exsaml:is-enabled($cid)) + if (exsaml:is-enabled($cid, $exsaml-config)) then - exsaml:invalidate-saml-token($cid) + exsaml:invalidate-saml-token($cid, $exsaml-config) else () return ... (: if no valid token, redirect to SAML auth :) -else if (exsaml:is-enabled($cid) and not(exsaml:check-valid-saml-token($cid))) +else if (exsaml:is-enabled($cid, $exsaml-config) and not(exsaml:check-valid-saml-token($cid, $exsaml-config))) then let $debug := exsaml:log('info', $cid, "controller: no valid token, redirect to SAML auth") let $return-path := "/exist/apps" || $exist:controller || $exist:path return - + diff --git a/content/exsaml.xqm b/content/exsaml.xqm index 160b7d5..5411570 100644 --- a/content/exsaml.xqm +++ b/content/exsaml.xqm @@ -24,38 +24,33 @@ import module namespace xmldb = "http://exist-db.org/xquery/xmldb"; declare variable $exsaml:version := doc("../expath-pkg.xml")/*:package/@version/string(); -(: pull config from config-exsaml.xml :) -(: NEED TO CHECK IF CONFIG EXISTS :) -declare %private variable $exsaml:config := doc("config-exsaml.xml")/config; - -declare %private variable $exsaml:sp-ent := data($exsaml:config/sp/@entity); -declare %private variable $exsaml:sp-uri := data($exsaml:config/sp/@endpoint); -declare %private variable $exsaml:sp-assertion-consumer-service-index := $exsaml:config/sp/@assertion-consumer-service-index ! xs:integer(.); -declare %private variable $exsaml:sp-fallback-rs := data($exsaml:config/sp/@fallback-relaystate); -declare %private variable $exsaml:idp-ent := data($exsaml:config/idp/@entity); -declare %private variable $exsaml:idp-uri := data($exsaml:config/idp/@endpoint); -declare %private variable $exsaml:idp-certfile as xs:string? := $exsaml:config/idp/@certfile; -declare %private variable $exsaml:idp-unsolicited := data($exsaml:config/idp/@accept-unsolicited); -declare %private variable $exsaml:idp-force-rs as xs:boolean := $exsaml:config/idp/@force-relaystate eq "true"; -declare %private variable $exsaml:idp-verify-issuer := data($exsaml:config/idp/@verify-issuer); - -declare %private variable $exsaml:hmac-key := data($exsaml:config/crypto/@hmac-key); -declare %private variable $exsaml:hmac-alg := data($exsaml:config/crypto/@hmac-alg); -declare %private variable $exsaml:group-attr := $exsaml:config/group-attribute/text(); -declare %private variable $exsaml:token-minutes := data($exsaml:config/token/@valid-mins); -declare %private variable $exsaml:token-name := data($exsaml:config/token/@name); -declare %private variable $exsaml:token-separator := "="; -(: needed for priv escalation :) -declare %private variable $exsaml:exsaml-user := data($exsaml:config/exsaml-creds/@username); -declare %private variable $exsaml:exsaml-pass := data($exsaml:config/exsaml-creds/@pass); -(: needed if user accounts should be created on the fly :) -declare %private variable $exsaml:create-user := data($exsaml:config/dynamic-users/@create); -declare %private variable $exsaml:user-group := data($exsaml:config/dynamic-users/@group); -(: only used for fake IDP response testing :) -declare %private variable $exsaml:minutes-valid := data($exsaml:config/fake-idp/@minutes-valid); -declare %private variable $exsaml:fake-result := data($exsaml:config/fake-idp/@result); -declare %private variable $exsaml:fake-user := data($exsaml:config/fake-idp/@user); -declare %private variable $exsaml:fake-group := data($exsaml:config/fake-idp/@group); +(: START of configuration Map keys :) +declare %private variable $exsaml:key-enabled := "enabled"; +declare %private variable $exsaml:key-sp-ent := "sp-ent"; +declare %private variable $exsaml:key-sp-uri := "sp-uri"; +declare %private variable $exsaml:key-sp-assertion-consumer-service-index := "sp-assertion-consumer-service-index"; +declare %private variable $exsaml:key-sp-fallback-rs := "sp-fallback-rs"; +declare %private variable $exsaml:key-idp-ent := "idp-ent"; +declare %private variable $exsaml:key-idp-uri := "idp-uri"; +declare %private variable $exsaml:key-idp-certfile := "idp-certfile"; +declare %private variable $exsaml:key-idp-unsolicited := "idp-unsolicited"; +declare %private variable $exsaml:key-idp-force-rs := "idp-force-rs"; +declare %private variable $exsaml:key-idp-verify-issuer := "idp-verify-issuer"; +declare %private variable $exsaml:key-hmac-key := "hmac-key"; +declare %private variable $exsaml:key-hmac-alg := "hmac-alg"; +declare %private variable $exsaml:key-group-attr := "group-attr"; +declare %private variable $exsaml:key-token-minutes := "token-minutes"; +declare %private variable $exsaml:key-token-name := "token-name"; +declare %private variable $exsaml:key-token-separator := "token-separator"; +declare %private variable $exsaml:key-exsaml-user := "exsaml-user"; +declare %private variable $exsaml:key-exsaml-pass := "exsaml-pass"; +declare %private variable $exsaml:key-create-user := "create-user"; +declare %private variable $exsaml:key-user-group := "user-group"; +declare %private variable $exsaml:key-minutes-valid := "minutes-valid"; +declare %private variable $exsaml:key-fake-result := "fake-result"; +declare %private variable $exsaml:key-fake-user := "fake-user"; +declare %private variable $exsaml:key-fake-group := "fake-group"; +(: END of configuration Map keys :) (: SAML specific constants and non-configurable vars :) declare %private variable $exsaml:saml-coll := "/db/system/repo/existdb-saml-1.7.0-SNAPSHOT"; @@ -66,6 +61,44 @@ declare %private variable $exsaml:status-success := "urn:oasis:names:tc:SAML:2.0 declare %private variable $exsaml:status-badauth := "urn:oasis:names:tc:SAML:2.0:status:AuthnFailed"; declare variable $exsaml:ERROR := xs:QName("saml:error"); +(:~ + : Parse an XML configuration for the exsaml module into a Map. + : + : @param $doc the XML document to parse the configuration from. + : + : @return an XDM map containing the configuration for the exsaml module. + :) +declare function exsaml:parse-xml-config($doc as document-node(element(config))) as map(xs:string, item()*) { + let $config := $doc/config + return + map { + $exsaml:key-enabled: $config/@enabled eq "true", + $exsaml:key-sp-ent: data($config/sp/@entity), + $exsaml:key-sp-uri: data($config/sp/@endpoint), + $exsaml:key-sp-assertion-consumer-service-index: $config/sp/@assertion-consumer-service-index ! xs:integer(.), + $exsaml:key-sp-fallback-rs: data($config/sp/@fallback-relaystate), + $exsaml:key-idp-ent: data($config/idp/@entity), + $exsaml:key-idp-uri: data($config/idp/@endpoint), + $exsaml:key-idp-certfile: data($config/idp/@certfile), + $exsaml:key-idp-unsolicited: data($config/idp/@accept-unsolicited), + $exsaml:key-idp-force-rs: $config/idp/@force-relaystate eq "true", + $exsaml:key-idp-verify-issuer: data($config/idp/@verify-issuer), + $exsaml:key-hmac-key: data($config/crypto/@hmac-key), + $exsaml:key-hmac-alg: data($config/crypto/@hmac-alg), + $exsaml:key-group-attr: $config/group-attribute/text(), + $exsaml:key-token-minutes: data($config/token/@valid-mins), + $exsaml:key-token-name: data($config/token/@name), + $exsaml:key-token-separator: "=", + $exsaml:key-exsaml-user: data($config/exsaml-creds/@username), + $exsaml:key-exsaml-pass: data($config/exsaml-creds/@pass), + $exsaml:key-create-user: data($config/dynamic-users/@create), + $exsaml:key-user-group: data($config/dynamic-users/@group), + $exsaml:key-minutes-valid: data($config/fake-idp/@minutes-valid), + $exsaml:key-fake-result: data($config/fake-idp/@result), + $exsaml:key-fake-user: data($config/fake-idp/@user), + $exsaml:key-fake-group: data($config/fake-idp/@group) + } +}; (:~ : Generate a correlation ID. @@ -78,16 +111,16 @@ declare function exsaml:generate-correlation-id() as xs:string { util:uuid() }; - (:~ : May be used to check if SAML is enabled at all : : @param $cid An id used for correlating log messages. + : @param $config the exsaml module configuration. : : @return true if SAML is enabled, false otherwise. :) -declare function exsaml:is-enabled($cid as xs:string) { - let $result := $exsaml:config/@enabled eq "true" +declare function exsaml:is-enabled($cid as xs:string, $config as map(xs:string, item()*)) { + let $result := $config($exsaml:key-enabled) let $_ := exsaml:log("debug", $cid, "saml is-enabled: " || $result) return $result @@ -97,15 +130,12 @@ declare function exsaml:is-enabled($cid as xs:string) { : Dump current config data. : : @param $cid An id used for correlating log messages. + : @param $config the exsaml module configuration. :) -declare function exsaml:info($cid as xs:string) { +declare function exsaml:info($cid as xs:string, $config as map(xs:string, item()*)) as map(xs:string, item()*) { map { - 'enabled': exsaml:is-enabled($cid), 'version': $exsaml:version, - 'hmacAlgorithm': $exsaml:hmac-alg, - 'idpEntity': $exsaml:idp-ent, - 'idpUri': $exsaml:idp-uri, - 'tokenLifetime': $exsaml:token-minutes + 'config': $config } }; @@ -120,10 +150,11 @@ declare function exsaml:info($cid as xs:string) { : @param $cid a correlation id. : @param $relaystate this is the path component of the resource that the user : initially requested, so that she gets sent there after SAML auth. + : @param $config the exsaml module configuration. :) -declare function exsaml:build-authnreq-redir-url($cid as xs:string, $relaystate as xs:string) { +declare function exsaml:build-authnreq-redir-url($cid as xs:string, $config as map(xs:string, item()*)) { let $log := exsaml:log("info", $cid, "building SAML auth request redir-url; relaystate: " || $relaystate) - let $req := exsaml:build-saml-authnreq($cid) + let $req := exsaml:build-saml-authnreq($cid, $config) let $log := exsaml:log("debug", $cid, "build-authnreq-redir-url; req: " || $req) (: deflate and base64 encode request :) @@ -139,28 +170,29 @@ declare function exsaml:build-authnreq-redir-url($cid as xs:string, $relaystate let $log := exsaml:log("debug", $cid, "build-authnreq-redir-url; urlenc: " || $urlenc) return - $exsaml:idp-uri || "?SAMLRequest=" || $urlenc || "&RelayState=" || xmldb:encode($relaystate) + $config($exsaml:key-idp-uri) || "?SAMLRequest=" || $urlenc || "&RelayState=" || xmldb:encode($relaystate) }; (:~ : Build and return SAML AuthnRequest node. : : @param $cid An id used for correlating log messages. + : @param $config the exsaml module configuration. :) -declare %private function exsaml:build-saml-authnreq($cid as xs:string) as element(samlp:AuthnRequest) { - let $reqid := exsaml:gen-id($cid) +declare %private function exsaml:build-saml-authnreq($cid as xs:string, $config as map(xs:string, item()*)) as element(samlp:AuthnRequest) { + let $reqid := exsaml:gen-id($cid, $config) let $instant := fn:current-dateTime() - let $store := exsaml:store-authnreqid($cid, $reqid, $instant) + let $store := exsaml:store-authnreqid($cid, $reqid, $instant, $config) return { - if (fn:exists($exsaml:sp-assertion-consumer-service-index)) + if (fn:exists($config($exsaml:key-sp-assertion-consumer-service-index))) then - attribute AssertionConsumerServiceIndex { $exsaml:sp-assertion-consumer-service-index } + attribute AssertionConsumerServiceIndex { $config($exsaml:key-sp-assertion-consumer-service-index) } else - attribute AssertionConsumerServiceURL { $exsaml:sp-uri } + attribute AssertionConsumerServiceURL { $config($exsaml:key-sp-uri) } } - {$exsaml:sp-ent} + {$config($exsaml:key-sp-ent)} }; @@ -189,13 +221,14 @@ declare %private function exsaml:store-authnreqid-as-exsol-user($cid as xs:strin : @param $cid An id used for correlating log messages. : @param $reqid The SAML Request ID. : @param $instant the instant. + : @param $config the exsaml module configuration. :) -declare %private function exsaml:store-authnreqid($cid as xs:string, $reqid as xs:string, $instant as xs:dateTime) { +declare %private function exsaml:store-authnreqid($cid as xs:string, $reqid as xs:string, $instant as xs:dateTime, $config as map(xs:string, item()*)) { let $log := exsaml:log("info", $cid, "storing SAML request id: " || $reqid || ", date: " || $instant) return system:as-user( - $exsaml:exsaml-user, - $exsaml:exsaml-pass, + $config($exsaml:key-exsaml-user), + $config($exsaml:key-exsaml-pass), exsaml:store-authnreqid-as-exsol-user($cid, $reqid, $instant) ) }; @@ -210,8 +243,9 @@ declare %private function exsaml:store-authnreqid($cid as xs:string, $reqid as x : so the user can be redirected to the requested resource. : : @param $cid An id used for correlating log messages. + : @param $config the exsaml module configuration. :) -declare function exsaml:process-saml-response-post($cid as xs:string) { +declare function exsaml:process-saml-response-post($cid as xs:string, $config as map(xs:string, item()*)) { let $log := exsaml:log("info", $cid, "process-saml-response-post") let $saml-resp := request:get-parameter("SAMLResponse", "error") @@ -230,7 +264,6 @@ declare function exsaml:process-saml-response-post($cid as xs:string) { let $debug := exsaml:log("debug", $cid, "END SAML RESPONSE") return - if ($resp = "error") then error($exsaml:ERROR, $cid || ": Empty SAML Response: ", "No SAML response data has been provided") @@ -238,7 +271,7 @@ declare function exsaml:process-saml-response-post($cid as xs:string) { try { - let $res := exsaml:validate-saml-response($cid, $resp) + let $res := exsaml:validate-saml-response($cid, $resp, $config) return if (xs:integer($res/@res) lt 0) then @@ -250,11 +283,11 @@ declare function exsaml:process-saml-response-post($cid as xs:string) { let $rsin := request:get-parameter("RelayState", ()) let $rsout := (: if we accept IDP-initiated SAML *and* use a forced landing page :) - if ($exsaml:idp-unsolicited and $exsaml:idp-force-rs) + if ($config($exsaml:key-idp-unsolicited) and $config($exsaml:key-idp-force-rs)) then - let $debug := exsaml:log("debug", $cid, "evaluated to: $exsaml:idp-unsolicited and $exsaml:idp-force-rs = 'true'") + let $debug := exsaml:log("debug", $cid, "evaluated to: $config($exsaml:key-idp-unsolicited) and $config($exsaml:key-idp-force-rs) = true()") return - $exsaml:idp-force-rs + $config($exsaml:key-idp-force-rs) (: otherwise accept relaystate from the SAML response :) else if (exists($rsin)) then @@ -262,34 +295,35 @@ declare function exsaml:process-saml-response-post($cid as xs:string) { return $rsin else - let $debug := exsaml:log("info", $cid, "no Relay State provided by SSO, switching to SP fallback relaystate: " || $exsaml:sp-fallback-rs) + let $debug := exsaml:log("info", $cid, "no Relay State provided by SSO, switching to SP fallback relaystate: " || $config($exsaml:key-sp-fallback-rs)) return - $exsaml:sp-fallback-rs + $config($exsaml:key-sp-fallback-rs) (: Return an element with all SAML validation data to the controller. If SAML success, this is basically username and group membership. IF SAML fail, pass enough info to allow meaningful error messages. :) let $auth := - {exsaml:fetch-saml-attribute-values($cid, $exsaml:group-attr, $resp/saml:Assertion) ! {.}} + {exsaml:fetch-saml-attribute-values($cid, $config($exsaml:key-group-attr), $resp/saml:Assertion) ! {.}} (: create SAML user if not exists yet :) let $u := - if ($exsaml:create-user = "true" and xs:integer($auth/@code) ge 0) + if ($config($exsaml:key-create-user) = "true" and xs:integer($auth/@code) ge 0) then - exsaml:ensure-saml-user($cid, $auth/@nameid) + exsaml:ensure-saml-user($cid, $auth/@nameid, $config) else "" - let $pass := exsaml:create-user-password($auth/@nameid) + let $pass := exsaml:create-user-password($auth/@nameid, $config) let $log-in := xmldb:login("/db", $auth/@nameid, $pass, true()) let $log := exsaml:log("info", $cid, "login result: " || $log-in || ", " || fn:serialize(sm:id())) + (: put SAML token into browser session :) let $sesstok := if ($log-in and xs:integer($auth/@code) ge 0) then - exsaml:set-saml-token($cid, $auth/@nameid, $auth/@authndate) + exsaml:set-saml-token($cid, $auth/@nameid, $auth/@authndate, $config) else () let $debug := exsaml:log("info", $cid, "finished exsaml:process-saml-response-post. auth: ") @@ -307,10 +341,11 @@ declare function exsaml:process-saml-response-post($cid as xs:string) { : : @param $cid An id used for correlating log messages. : @param $resp the XML document containing the SAML Response. + : @param $config the exsaml module configuration. : : @return an element indicating the result of the validation. :) -declare %private function exsaml:validate-saml-response($cid as xs:string, $resp as node()) as element(exsaml:funcret) { +declare %private function exsaml:validate-saml-response($cid as xs:string, $resp as node(), $config as map(xs:string, item()*)) as element(exsaml:funcret) { let $log := exsaml:log("info", $cid, "validate-saml-response") let $as := $resp/saml:Assertion @@ -327,15 +362,14 @@ declare %private function exsaml:validate-saml-response($cid as xs:string, $resp (: check that "Issuer" is the expected IDP. Not stricty required by : SAML specs, but adds extra protection against forged SAML responses. :) - else if ($exsaml:idp-verify-issuer = "true" and boolean($resp/saml:Issuer) and not($resp/saml:Issuer = $exsaml:idp-ent)) - then + else if ($config($exsaml:key-idp-verify-issuer) = "true" and boolean($resp/saml:Issuer) and not($resp/saml:Issuer = $config($exsaml:key-idp-ent))) let $msg := "SAML response from unexpected IDP: " || $resp/saml:Issuer return (: verify response signature if present :) (: COMMENTED OUT until crypto-lib issues resolved :) - (: else if (boolean($sig) and not(exsaml:verify-response-signature($cid, $sig))) then :) + (: else if (boolean($sig) and not(exsaml:verify-response-signature($cid, $sig, $config))) then :) (: :) (: must contain at least one assertion :) @@ -345,7 +379,7 @@ declare %private function exsaml:validate-saml-response($cid as xs:string, $resp (: validate all assertions - only first by now :) else - exsaml:validate-saml-assertion($cid, $as) + exsaml:validate-saml-assertion($cid, $as, $config) }; (:~ @@ -353,10 +387,11 @@ declare %private function exsaml:validate-saml-response($cid as xs:string, $resp : : @param $cid An id used for correlating log messages. : @param $assertion The SAML assertion to validate. + : @param $config the exsaml module configuration. : : @return an element indicating the result of the validation. :) -declare %private function exsaml:validate-saml-assertion($cid as xs:string, $assertion as item()) as element(exsaml:funcret) { +declare %private function exsaml:validate-saml-assertion($cid as xs:string, $assertion as item(), $config as map(xs:string, item()*)) as element(exsaml:funcret) { if (empty($assertion)) then let $log := exsaml:log("info", $cid, "Error: Empty Assertion") @@ -374,7 +409,7 @@ declare %private function exsaml:validate-saml-assertion($cid as xs:string, $ass (: check that "Issuer" is the expected IDP. Not stricty required by SAML specs, but adds extra protection against forged SAML responses. :) - if ($exsaml:idp-verify-issuer = "true" and boolean($assertion/saml:Issuer) and not($assertion/saml:Issuer = $exsaml:idp-ent)) + if ($config($exsaml:key-idp-verify-issuer) = "true" and boolean($assertion/saml:Issuer) and not($assertion/saml:Issuer = $config($exsaml:key-idp-ent))) then let $msg := "SAML assertion from unexpected IDP: " || $assertion/saml:Issuer return @@ -382,13 +417,13 @@ declare %private function exsaml:validate-saml-assertion($cid as xs:string, $ass (: verify assertion signature if present :) (: COMMENTED OUT until crypto-lib issues resolved :) -(: else if (boolean($sig) and not(exsaml:verify-assertion-signature($cid, $assertion))) then :) +(: else if (boolean($sig) and not(exsaml:verify-assertion-signature($cid, $assertion, $config))) then :) (: :) (: maybe verify SubjectConfirmation/@Method :) (: verify SubjectConfirmationData/@Recipient is SP URL ($sp-uri) :) - else if (not($subj-confirm-data/@Recipient = $exsaml:sp-uri)) + else if (not($subj-confirm-data/@Recipient = $config($exsaml:key-sp-uri))) then @@ -400,14 +435,14 @@ declare %private function exsaml:validate-saml-assertion($cid as xs:string, $ass (: verify SubjectConfirmationData/@InResponseTo is present in the SAML response :) else if (not($reqid)) then - if ($exsaml:idp-unsolicited) + if ($config($exsaml:key-idp-unsolicited)) then else (: else verify SubjectConfirmationData/@InResponseTo equal to orig AuthnRequest ID :) - else if (not(exsaml:check-authnreqid($cid, $reqid))) + else if (not(exsaml:check-authnreqid($cid, $reqid, $config))) then @@ -424,7 +459,7 @@ declare %private function exsaml:validate-saml-assertion($cid as xs:string, $ass (: verify Conditions/AudienceRestriction/Audience is myself ($sp-ent) :) - else if (not($conds/saml:AudienceRestriction/saml:Audience = $exsaml:sp-ent)) + else if (not($conds/saml:AudienceRestriction/saml:Audience = $config($exsaml:key-sp-ent))) then @@ -437,13 +472,14 @@ declare %private function exsaml:validate-saml-assertion($cid as xs:string, $ass : : @param $cid An id used for correlating log messages. : @param $reqid the SAML Request ID. + : @param $config the exsaml module configuration. : : @param true if the SAML Request ID is valid, false otherwise. :) -declare %private function exsaml:check-authnreqid($cid as xs:string, $reqid as xs:string) as xs:boolean { +declare %private function exsaml:check-authnreqid($cid as xs:string, $reqid as xs:string, $config as map(xs:string, item()*)) as xs:boolean { let $log := exsaml:log("info", $cid, "verifying SAML request: reqid: " || $reqid) return - system:as-user($exsaml:exsaml-user, $exsaml:exsaml-pass, + system:as-user($config($exsaml:key-exsaml-user), $config($exsaml:key-exsaml-pass), exists(doc($exsaml:saml-coll-reqid || "/" || $reqid)) and empty(xmldb:remove($exsaml:saml-coll-reqid, $reqid))) }; @@ -452,14 +488,15 @@ declare %private function exsaml:check-authnreqid($cid as xs:string, $reqid as x : : @param $cid An id used for correlating log messages. : @param $resp the SAML response. + : @param $config the exsaml module configuration. :) -declare %private function exsaml:verify-response-signature($cid as xs:string, $resp as item()) as xs:boolean { +declare %private function exsaml:verify-response-signature($cid as xs:string, $resp as item(), $config as map(xs:string, item()*)) as xs:boolean { let $log := exsaml:log("debug", $cid, "verify-response-signature: " || $resp) return (: if $idp-certfile is configured, use that to validate XML signature :) - if (exists($exsaml:idp-certfile)) + if (exists($config($exsaml:key-idp-certfile)) then -(: crypto:validate-signature-by-certfile($resp, $exsaml:idp-certfile):) +(: crypto:validate-signature-by-certfile($resp, $config($exsaml:key-idp-certfile)):) true() else let $log := exsaml:log("info", $cid, "cert to verify response signature is missing - could not verify signature! ") @@ -472,16 +509,17 @@ declare %private function exsaml:verify-response-signature($cid as xs:string, $r : : @param $cid An id used for correlating log messages. : @param $assertion The SAML assertion to validate the signature of. + : @param $config the exsaml module configuration. : : @return true of the signature is valid, false otherwise. :) -declare %private function exsaml:verify-assertion-signature($cid as xs:string, $assertion as item()) as xs:boolean { +declare %private function exsaml:verify-assertion-signature($cid as xs:string, $assertion as item(), $config as map(xs:string, item()*)) as xs:boolean { let $log := exsaml:log("debug", $cid, "verify-assertion-signature: " || $assertion) return (: if $idp-certfile is configured, use that to validate XML signature :) - if (exists($exsaml:idp-certfile)) + if (exists($config($exsaml:key-idp-certfile))) then -(: crypto:validate-signature-by-certfile($assertion, $exsaml:idp-certfile):) +(: crypto:validate-signature-by-certfile($assertion, $config($exsaml:key-idp-certfile)):) true() else let $log := exsaml:log("info", $cid, "cert to verify assertion signature is missing - could not verify signature") @@ -520,24 +558,25 @@ declare %private function exsaml:fetch-saml-attribute-values($cid as xs:string, : : @param $cid An id used for correlating log messages. : @param $nameid the name for the user account. + : @param $config the exsaml module configuration. :) -declare %private function exsaml:ensure-saml-user($cid as xs:string, $nameid as xs:string) { - let $pass := exsaml:create-user-password($nameid) +declare %private function exsaml:ensure-saml-user($cid as xs:string, $nameid as xs:string, $config as map(xs:string, item()*)) { + let $pass := exsaml:create-user-password($nameid, $config) return (: run as user exsaml, group dba :) - system:as-user($exsaml:exsaml-user, $exsaml:exsaml-pass, + system:as-user($config($exsaml:key-exsaml-user), $config($exsaml:key-exsaml-pass), if (not(sm:user-exists($nameid)) - and exsaml:log("info", $cid, "create new user account " || $nameid || ", group " || $exsaml:user-group)) + and exsaml:log("info", $cid, "create new user account " || $nameid || ", group " || $config($exsaml:key-user-group))) then - sm:create-account($nameid, $pass, $exsaml:user-group, ()) + sm:create-account($nameid, $pass, $config($exsaml:key-user-group), ()) else () ) }; (: create user password as HMAC of username :) -declare %private function exsaml:create-user-password($nameid as xs:string) { - let $key := $exsaml:hmac-key || "" - let $alg := $exsaml:hmac-alg || "" +declare %private function exsaml:create-user-password($nameid as xs:string, $config as map(xs:string, item()*)) { + let $key := $config($exsaml:key-hmac-key) || "" + let $alg := $config($exsaml:key-hmac-alg) || "" return crypto:hmac($nameid, $key, $alg, "hex") }; @@ -550,17 +589,18 @@ declare %private function exsaml:create-user-password($nameid as xs:string) { : This is called from the controller(s) to check if access should be granted. : : @param $cid An id used for correlating log messages. + : @param $config the exsaml module configuration. :) -declare function exsaml:check-valid-saml-token($cid as xs:string) as xs:boolean { - let $raw := session:get-attribute($exsaml:token-name) - let $log := exsaml:log("debug", $cid, "checking saml token, name: " || $exsaml:token-name || ", value: " || $raw) +declare function exsaml:check-valid-saml-token($cid as xs:string, $config as map(xs:string, item()*)) as xs:boolean { + let $raw := session:get-attribute($config($exsaml:key-token-name)) + let $log := exsaml:log("debug", $cid, "checking saml token, name: " || $config($exsaml:key-token-name) || ", value: " || $raw) - let $tokdata := fn:tokenize($raw, $exsaml:token-separator) + let $tokdata := fn:tokenize($raw, $config($exsaml:key-token-separator)) return if (empty($raw) and exsaml:log("info", $cid, "no token found")) then false() - else if (not($tokdata[3] eq exsaml:hmac-tokval($cid, $tokdata[1] || $exsaml:token-separator || $tokdata[2])) and exsaml:log("info", $cid, "token is invalid")) + else if (not($tokdata[3] eq exsaml:hmac-tokval($cid, $tokdata[1] || $config($exsaml:key-token-separator) || $tokdata[2], $config)) and exsaml:log("info", $cid, "token is invalid")) then false() else if (xs:dateTime(fn:current-dateTime()) gt xs:dateTime($tokdata[2]) and exsaml:log("info", $cid, "token has expired")) @@ -576,44 +616,47 @@ declare function exsaml:check-valid-saml-token($cid as xs:string) as xs:boolean : This is called from the controller(s) upon user logout. : : @param $cid An id used for correlating log messages. + : @param $config the exsaml module configuration. :) -declare function exsaml:invalidate-saml-token($cid as xs:string) as empty-sequence() { +declare function exsaml:invalidate-saml-token($cid as xs:string, $config as map(xs:string, item()*)) as empty-sequence() { let $user := sm:id()/sm:id/sm:real/sm:username - let $tok := exsaml:build-string-token($cid, $user, xs:dateTime("1970-01-01T00:00:00")) - let $hmac := exsaml:hmac-tokval($cid, $tok) + let $tok := exsaml:build-string-token($cid, $user, xs:dateTime("1970-01-01T00:00:00"), $config) + let $hmac := exsaml:hmac-tokval($cid, $tok, $config) let $log := exsaml:log("info", $cid, "invalidate saml token for: " || $user || ", hmac: " || $hmac) return - session:set-attribute($exsaml:token-name, $tok || $exsaml:token-separator || $hmac) + session:set-attribute($config($exsaml:key-token-name), $tok || $config($exsaml:key-token-separator) || $hmac) }; (:~ Return the HMAC of the string token passed in. : : @param $cid An id used for correlating log messages. : @param $tokval the token. + : @param $config the exsaml module configuration. : : @return the HMAC. :) -declare %private function exsaml:hmac-tokval($cid as xs:string, $tokval as xs:string) as xs:string { - let $log := exsaml:log("debug", $cid, "hmac-tokval; t: " || $tokval || ", key: " || $exsaml:hmac-key) - let $key := $exsaml:hmac-key || "" - let $alg := $exsaml:hmac-alg || "" +declare %private function exsaml:hmac-tokval($cid as xs:string, $tokval as xs:string, $config as map(xs:string, item()*)) as xs:string { + let $log := exsaml:log("debug", $cid, "hmac-tokval; t: " || $tokval || ", key: " || $config($exsaml:key-hmac-key)) + let $key := $config($exsaml:key-hmac-key) || "" + let $alg := $config($exsaml:key-hmac-alg) || "" return crypto:hmac($tokval, $key, $alg, "hex") }; (:~ - : Build string token: join nameid and validto by $exsaml:token-separator. + : Build string token: join nameid and validto by $config($exsaml:key-token-separator). : : @param $cid An id used for correlating log messages. : @param $nameid the user name. : @param $validto the expiry date in ISO format. + : @param $config the exsaml module configuration. : : @return the token. :) -declare %private function exsaml:build-string-token($cid as xs:string, $nameid as xs:string, $validto as xs:dateTime) as xs:string { +declare %private function exsaml:build-string-token($cid as xs:string, $nameid as xs:string, $validto as xs:dateTime, $config as map(xs:string, item()*)) as xs:string { let $log := exsaml:log("debug", $cid, "build-string-token; n: " || $nameid || ", v: " || $validto) return - $nameid || $exsaml:token-separator || $validto + $nameid || $config($exsaml:key-token-separator) || $validto }; (:~ @@ -622,22 +665,23 @@ declare %private function exsaml:build-string-token($cid as xs:string, $nameid a : @param $cid An id used for correlating log messages. : @param $nameid the user name. : @param $authndate the auth date. + : @param $config the exsaml module configuration. :) -declare %private function exsaml:set-saml-token($cid as xs:string, $nameid as xs:string, $authndate as xs:string) as empty-sequence() { - let $validto := xs:dateTime($authndate) + xs:dayTimeDuration("PT" || $exsaml:token-minutes || "M") +declare %private function exsaml:set-saml-token($cid as xs:string, $nameid as xs:string, $authndate as xs:string, $config as map(xs:string, item()*)) as empty-sequence() { + let $validto := xs:dateTime($authndate) + xs:dayTimeDuration("PT" || $config($exsaml:key-token-minutes) || "M") - let $tok := exsaml:build-string-token($cid, $nameid, $validto) - let $hmac := exsaml:hmac-tokval($cid, $tok) + let $tok := exsaml:build-string-token($cid, $nameid, $validto, $config) + let $hmac := exsaml:hmac-tokval($cid, $tok, $config) let $log := exsaml:log("info", $cid, "set saml token for: " || $nameid || ", authndate: " || $authndate || ", valid until: " || $validto || ", hmac: " || $hmac) return - session:set-attribute($exsaml:token-name, $tok || $exsaml:token-separator || $hmac) + session:set-attribute($config($exsaml:key-token-name), $tok || $config($exsaml:key-token-separator) || $hmac) }; (: ==== FUNCTIONS TO FAKE A SAML IDP (testing only) ==== :) (: process SAML AuthnRequest, return SAML Response via POST :) -declare function exsaml:process-saml-request($cid as xs:string) as element(html) { +declare function exsaml:process-saml-request($cid as xs:string, $config as map(xs:string, item()*)) as element(html) { let $log := exsaml:log("debug", $cid, "process-saml-request") let $raw := request:get-parameter("SAMLRequest", "") let $log := exsaml:log("debug", $cid, "process-saml-request; raw: " || $raw) @@ -649,21 +693,21 @@ declare function exsaml:process-saml-request($cid as xs:string) as element(html) let $log := exsaml:log("debug", $cid, "process-saml-request; req: " || $req) let $rs := request:get-parameter("RelayState", false()) - let $resp := exsaml:fake-idp-response($cid, $req, $rs) + let $resp := exsaml:fake-idp-response($cid, $req, $rs, $config) return $resp }; (: fake SAML IDP response: build response and return via XHTML autosubmit form :) -declare %private function exsaml:fake-idp-response($cid as xs:string, $req as node(), $rs as xs:string) as element(html) { +declare %private function exsaml:fake-idp-response($cid as xs:string, $req as node(), $rs as xs:string, $config as map(xs:string, item()*)) as element(html) { let $log := exsaml:log("debug", $cid, "fake-idp-response") - let $resp := exsaml:build-saml-fakeresp($cid, $req) + let $resp := exsaml:build-saml-fakeresp($cid, $req, $config) let $b64resp := util:base64-encode(fn:serialize($resp)) return -
+ @@ -674,36 +718,36 @@ declare %private function exsaml:fake-idp-response($cid as xs:string, $req as no }; (: return a fake SAML response node :) -declare %private function exsaml:build-saml-fakeresp($cid as xs:string, $req as node()) as element(samlp:Response) { +declare %private function exsaml:build-saml-fakeresp($cid as xs:string, $req as node(), $config as map(xs:string, item()*)) as element(samlp:Response) { let $reqid := $req/@ID let $status := - if ($exsaml:fake-result = "true") + if ($config($exsaml:key-fake-result) = "true") then $exsaml:status-success else $exsaml:status-badauth let $fakesig := "ABCDEF" let $now := fn:current-dateTime() - let $validto := $now + xs:dayTimeDuration("PT" || $exsaml:minutes-valid || "M") + let $validto := $now + xs:dayTimeDuration("PT" || $config($exsaml:key-minutes-valid) || "M") return - - {$exsaml:idp-ent} + + {$config($exsaml:key-idp-ent)} - {$exsaml:idp-ent} + {$config(exsaml:key-idp-ent)} {$fakesig} - {$exsaml:fake-user} + {$config($exsaml:key-fake-user)} - + - {$exsaml:sp-ent} + {$config($exsaml:key-sp-ent)} @@ -712,8 +756,8 @@ declare %private function exsaml:build-saml-fakeresp($cid as xs:string, $req as - - {$exsaml:fake-group} + + {$config($exsaml:key-fake-group)} diff --git a/content/config-exsaml.xml b/examples/config-exsaml.xml similarity index 100% rename from content/config-exsaml.xml rename to examples/config-exsaml.xml