new configuration version
This commit is contained in:
parent
fdd705eed5
commit
834005e157
|
@ -46,7 +46,7 @@ spec:
|
|||
podDisruptionBudget:
|
||||
maxUnavailable: "50%"
|
||||
git:
|
||||
tag: "r-9849dba282e5e9421988bf7092f242ff73d83ce5"
|
||||
tag: "r-04ad6fd7455702c2a591f4a7b8d6c94222de911e"
|
||||
dir: "DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth"
|
||||
credentials: "git-credentials"
|
||||
database:
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/bash
|
||||
echo 'password'
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,32 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
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
|
||||
4g4p3ZpFGmoq4lM=
|
||||
-----END CERTIFICATE-----
|
|
@ -134,6 +134,15 @@
|
|||
<!-- source: pattern://8dbec5bb024707d73fca93ef -->
|
||||
<KeyObject name="https://trustbroker-idp.agov-w.azure.adnovum.net" certificate="/var/opt/keys/trust/idp-pem-atb/truststore.jks"/>
|
||||
</KeyStore>
|
||||
<!-- source: pattern://122a5450f8611066c0a8e6bf -->
|
||||
<KeyStore name="Store_IDP_AGOV_SEC">
|
||||
<!-- source: pattern://122a5450f8611066c0a8e6bf -->
|
||||
<KeyObject name="Signer_IDP_AGOV_SEC" certificate="/var/opt/keys/own/idp-pem-signer/cert.pem" privateKey="/var/opt/keys/own/idp-pem-signer/keystore.jks" passPhrase="pipe:///var/opt/keys/own/idp-pem-signer/keypass"/>
|
||||
<!-- source: pattern://122a5450f8611066c0a8e6bf -->
|
||||
<KeyObject name="https://trustbroker.agov-d.azure.adnovum.net" certificate="/var/opt/keys/trust/idp-pem-atb/truststore.jks"/>
|
||||
<!-- source: pattern://122a5450f8611066c0a8e6bf -->
|
||||
<KeyObject name="Encrypt_https://trustbroker.agov-d.azure.adnovum.net" certificate="/var/opt/keys/trust/idp-pem-atb-enc/truststore.pem"/>
|
||||
</KeyStore>
|
||||
<!-- source: pattern://cb8c63274fe346280de0ffd5 -->
|
||||
<KeyStore name="Auth_Realm_Mobile_FIDO_UAFKeyStore">
|
||||
<!-- source: pattern://cb8c63274fe346280de0ffd5 -->
|
||||
|
@ -159,10 +168,13 @@
|
|||
<!-- source: pattern://4fcfadb4a5c946ead7e6e995 -->
|
||||
<Domain name="Auth_Realm_Main_IDP" default="false" inactiveInterval="1800" reauthInterval="0" resetAuthenticationCondition="#{ (inargs.containsKey('SAMLRequest') and session.containsKey('ch.nevis.auth.saml.request.id')) ? 'restart' : '' }">
|
||||
<Entry method="authenticate" state="Auth_Realm_Main_IDP_IDP_Status_Check"/>
|
||||
<Entry method="authenticate" state="Auth_Realm_Main_IDP_IDP_Status_Check" selector="${request:currentResource:^http[s]?\u003A//[^/]+/SAML2/SECSSO/.*$:true}"/>
|
||||
<Entry method="authenticate" state="Auth_Realm_Main_IDP_IDP_Status_Check" selector="${request:currentResource:^http[s]?\u003A//[^/]+/SAML2/SSO/.*$:true}"/>
|
||||
<Entry method="logout" state="Auth_Realm_Main_IDP_IDP_Status_Check"/>
|
||||
<Entry method="logout" state="Auth_Realm_Main_IDP_IDP_Status_Check" selector="${request:currentResource:^http[s]?\u003A//[^/]+/SAML2/SECSSO/.*$:true}"/>
|
||||
<Entry method="logout" state="Auth_Realm_Main_IDP_IDP_Status_Check" selector="${request:currentResource:^http[s]?\u003A//[^/]+/SAML2/SSO/.*$:true}"/>
|
||||
<Entry method="stepup" state="Auth_Realm_Main_IDP_Selector"/>
|
||||
<Entry method="stepup" state="Auth_Realm_Main_IDP_IDP_Status_Check" selector="${request:currentResource:^http[s]?\u003A//[^/]+/SAML2/SECSSO/.*$:true}"/>
|
||||
<Entry method="stepup" state="Auth_Realm_Main_IDP_IDP_Status_Check" selector="${request:currentResource:^http[s]?\u003A//[^/]+/SAML2/SSO/.*$:true}"/>
|
||||
</Domain>
|
||||
<!-- source: pattern://cb8c63274fe346280de0ffd5 -->
|
||||
|
@ -1398,7 +1410,7 @@
|
|||
</AuthState>
|
||||
<AuthState name="Auth_Realm_Main_IDP_Prepare_Done" class="ch.nevis.esauth.auth.states.scripting.ScriptState" final="false">
|
||||
<!-- source: pattern://6061abea33a234fad73897b7, pattern://359792ce61c28c723ab7d354, pattern://4fcfadb4a5c946ead7e6e995 -->
|
||||
<ResultCond name="default" next="Auth_Realm_Main_IDP_Auth_Done"/>
|
||||
<ResultCond name="default" next="Auth_Realm_Main_IDP_IDP_AGOV_SEC_Authorization"/>
|
||||
<!-- source: pattern://6061abea33a234fad73897b7, pattern://359792ce61c28c723ab7d354, pattern://4fcfadb4a5c946ead7e6e995 -->
|
||||
<Response value="AUTH_DONE">
|
||||
<!-- source: pattern://6061abea33a234fad73897b7, pattern://359792ce61c28c723ab7d354, pattern://4fcfadb4a5c946ead7e6e995 -->
|
||||
|
@ -1546,12 +1558,21 @@
|
|||
<!-- source: pattern://306ce091fd87bad6174d9e8b -->
|
||||
<property name="parameter.idm.httpclient.tls.trustStoreRef" value="EId_Compare_And_Update_IDM_Attributes"/>
|
||||
</AuthState>
|
||||
<AuthState name="Auth_Realm_Main_IDP_Auth_Done" class="ch.nevis.esauth.auth.states.standard.AuthDone" final="false">
|
||||
<!-- source: pattern://6061abea33a234fad73897b7, pattern://359792ce61c28c723ab7d354, pattern://4fcfadb4a5c946ead7e6e995 -->
|
||||
<Response value="AUTH_DONE">
|
||||
<!-- source: pattern://6061abea33a234fad73897b7, pattern://359792ce61c28c723ab7d354, pattern://4fcfadb4a5c946ead7e6e995 -->
|
||||
<Gui name="ContinueResponse"/>
|
||||
<AuthState name="Auth_Realm_Main_IDP_IDP_AGOV_SEC_Authorization" class="ch.nevis.esauth.auth.states.scripting.ScriptState" final="false">
|
||||
<ResultCond name="ok" next="Auth_Realm_Main_IDP_Auth_Done"/>
|
||||
<!-- source: pattern://76b897ee0646a882016810be -->
|
||||
<ResultCond name="forbidden_0" next="Auth_Realm_Main_IDP_IDP_AGOV_SEC_Authorization"/>
|
||||
<!-- source: pattern://76b897ee0646a882016810be -->
|
||||
<ResultCond name="stepup" next="Auth_Realm_Main_IDP_Selector"/>
|
||||
<!-- source: pattern://76b897ee0646a882016810be -->
|
||||
<Response value="AUTH_ERROR">
|
||||
<!-- source: pattern://76b897ee0646a882016810be -->
|
||||
<Arg name="ch.nevis.isiweb4.response.status" value="403"/>
|
||||
</Response>
|
||||
<!-- source: pattern://76b897ee0646a882016810be -->
|
||||
<property name="parameter.paths" value="^http[s]?\u003A//[^/]+/SAML2/SECSSO/.*$"/>
|
||||
<!-- source: pattern://76b897ee0646a882016810be -->
|
||||
<property name="script" value="file:///var/opt/nevisauth/default/conf/saml_idp_agov_sec_authorization.groovy"/>
|
||||
</AuthState>
|
||||
<AuthState name="Auth_Realm_Main_IDP_Auth_Realm_Main_IDP_Custom_Auth_Done_GUI" class="ch.nevis.esauth.auth.states.standard.AuthDone" final="false" resumeState="true">
|
||||
<!-- source: pattern://cf0e8f8de1c8ac7345c5a6bb -->
|
||||
|
@ -1690,6 +1711,22 @@
|
|||
<!-- source: pattern://306ce091fd87bad6174d9e8b -->
|
||||
<property name="parameter.idm.httpclient.tls.trustStoreRef" value="EId_Compare_And_Update_IDM_Attributes"/>
|
||||
</AuthState>
|
||||
<AuthState name="Auth_Realm_Main_IDP_Auth_Done" class="ch.nevis.esauth.auth.states.standard.AuthDone" final="false">
|
||||
<!-- source: pattern://6061abea33a234fad73897b7, pattern://359792ce61c28c723ab7d354, pattern://4fcfadb4a5c946ead7e6e995 -->
|
||||
<Response value="AUTH_DONE">
|
||||
<!-- source: pattern://6061abea33a234fad73897b7, pattern://359792ce61c28c723ab7d354, pattern://4fcfadb4a5c946ead7e6e995 -->
|
||||
<Gui name="ContinueResponse"/>
|
||||
</Response>
|
||||
</AuthState>
|
||||
<AuthState name="Auth_Realm_Main_IDP_Selector" class="ch.nevis.esauth.auth.states.standard.ConditionalDispatcherState" final="false">
|
||||
<!-- source: pattern://4fcfadb4a5c946ead7e6e995 -->
|
||||
<ResultCond name="nomatch" next="Auth_Realm_Main_IDP_Prepare_Done"/>
|
||||
<!-- source: pattern://4fcfadb4a5c946ead7e6e995 -->
|
||||
<Response value="AUTH_ERROR">
|
||||
<!-- source: pattern://4fcfadb4a5c946ead7e6e995 -->
|
||||
<Arg name="ch.nevis.isiweb4.response.status" value="403"/>
|
||||
</Response>
|
||||
</AuthState>
|
||||
<AuthState name="Auth_Realm_Main_IDP_OnCancel_Dispatch" class="ch.nevis.esauth.auth.states.standard.ConditionalDispatcherState" final="false">
|
||||
<!-- source: pattern://af4ec934e8efbef422f03926 -->
|
||||
<ResultCond name="AccessApp" next="Auth_Realm_Main_IDP_Mobile_NLess_Auth"/>
|
||||
|
@ -2231,15 +2268,6 @@
|
|||
<!-- source: pattern://9a8294b080ea769d22924af0 -->
|
||||
<property name="script" value="file:///var/opt/nevisauth/default/conf/checkInsufficientLoa.groovy"/>
|
||||
</AuthState>
|
||||
<AuthState name="Auth_Realm_Main_IDP_Selector" class="ch.nevis.esauth.auth.states.standard.ConditionalDispatcherState" final="false">
|
||||
<!-- source: pattern://4fcfadb4a5c946ead7e6e995 -->
|
||||
<ResultCond name="nomatch" next="Auth_Realm_Main_IDP_Prepare_Done"/>
|
||||
<!-- source: pattern://4fcfadb4a5c946ead7e6e995 -->
|
||||
<Response value="AUTH_ERROR">
|
||||
<!-- source: pattern://4fcfadb4a5c946ead7e6e995 -->
|
||||
<Arg name="ch.nevis.isiweb4.response.status" value="403"/>
|
||||
</Response>
|
||||
</AuthState>
|
||||
<AuthState name="Auth_Realm_Mobile_FIDO_UAF_DirectFidoAuthRequired" class="ch.nevis.esauth.auth.states.directResponse.DirectResponseState" final="true" resumeState="false">
|
||||
<!-- source: pattern://cb8c63274fe346280de0ffd5 -->
|
||||
<Response value="AUTH_ERROR">
|
||||
|
@ -3452,6 +3480,15 @@
|
|||
<!-- source: pattern://ab5a82719993921822e95751 -->
|
||||
<property name="out.keyobjectref" value="Signer_IDP_AGOV"/>
|
||||
</WebService>
|
||||
<!-- source: pattern://14efdcb489f3f295fcbdf811 -->
|
||||
<WebService name="IDP_AGOV_SEC_ARS" class="ch.nevis.esauth.auth.adapter.saml.ArtifactResolutionService" uri="/nevisauth/services/ars/sec" SSODomain="Auth_Realm_Main_IDP">
|
||||
<!-- source: pattern://14efdcb489f3f295fcbdf811 -->
|
||||
<property name="issuer" value="https://auth.agov-w.azure.adnovum.net/SAML2SEC/"/>
|
||||
<!-- source: pattern://14efdcb489f3f295fcbdf811 -->
|
||||
<property name="out.keystoreref" value="Store_IDP_AGOV"/>
|
||||
<!-- source: pattern://14efdcb489f3f295fcbdf811 -->
|
||||
<property name="out.keyobjectref" value="Signer_IDP_AGOV"/>
|
||||
</WebService>
|
||||
<!-- source: pattern://7022472ae407577ae604bbb8 -->
|
||||
<RESTService name="ManagementService" class="ch.nevis.esauth.rest.service.session.ManagementService"/>
|
||||
</esauth-server>
|
||||
|
|
|
@ -0,0 +1,179 @@
|
|||
boolean isEnabled() {
|
||||
def paths = parameters.get("paths")
|
||||
if (paths && !paths.isEmpty()) {
|
||||
for (path in paths.split(',')) {
|
||||
String url = request.currentResource
|
||||
if (url.matches(path)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
boolean isLevel(String role) {
|
||||
if (role != null && role.isNumber()) {
|
||||
def number = Integer.parseInt(role)
|
||||
if (number > 0 && number <= 9) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
int getCurrentLevel() {
|
||||
int level = 1 // level 1 is reached by definition on successful authentication
|
||||
// levels are stored as roles once the authentication is done
|
||||
for (String role : response.getActualRoles()) {
|
||||
if (isLevel(role)) {
|
||||
Integer number = Integer.parseInt(role)
|
||||
if (number > level) {
|
||||
level = number
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG.debug("current level: $level")
|
||||
return level
|
||||
}
|
||||
|
||||
Integer getRequestedLevel() {
|
||||
// try to determine required level based on SAML request (SP-initiated)
|
||||
def context = session['ch.nevis.auth.saml.request.authnContextClassRef']
|
||||
if (context == null) {
|
||||
// this is expected for non-Nevis SAML partners
|
||||
LOG.debug("unable to determine required authentication level: no AuthnContext")
|
||||
return null
|
||||
}
|
||||
String prefix = 'urn:nevis:level:'
|
||||
Integer level = null
|
||||
if (context.contains(prefix)) {
|
||||
def start = context.indexOf(prefix) // the prefix can appear anywhere in the context but only once
|
||||
def remainder = context.substring(start + prefix.length())
|
||||
for (String candidate : remainder.split(',')) {
|
||||
if (!candidate.isNumber()) {
|
||||
continue // must be an actual role
|
||||
}
|
||||
def number = Integer.parseInt(candidate)
|
||||
if (level == null || number < level) {
|
||||
level = number
|
||||
}
|
||||
}
|
||||
}
|
||||
if (level == null) {
|
||||
// an AuthnContext has been sent but it does not contain the required authentication level
|
||||
LOG.debug("unable to determine required authentication level from request: $context")
|
||||
}
|
||||
else {
|
||||
LOG.info("extracted required authentication level from request: $context -> $level")
|
||||
}
|
||||
return level
|
||||
}
|
||||
|
||||
Integer getRequiredLevel(levels, String issuer) {
|
||||
// try to determine required level based on request
|
||||
def level = getRequestedLevel()
|
||||
if (level != null) {
|
||||
LOG.info("required authentication level from request: $level")
|
||||
return level
|
||||
}
|
||||
// else determine required level based on configuration (IDP-initiated or no authnContextClassRef sent)
|
||||
if (issuer != null && levels.containsKey(issuer)) {
|
||||
level = levels[issuer]
|
||||
LOG.debug("required authentication level for issuer $issuer defined as $level")
|
||||
return level
|
||||
}
|
||||
// else return null
|
||||
LOG.debug("required authentication level for issuer $issuer is not defined")
|
||||
return null
|
||||
}
|
||||
|
||||
void setAuthnContext() {
|
||||
def parts = [] as Set
|
||||
def authLevel = response.getAuthLevel()
|
||||
if (authLevel != null) {
|
||||
if (isLevel(authLevel)) {
|
||||
parts.add("urn:nevis:level:$authLevel")
|
||||
}
|
||||
else { // might be legacy auth.weak / auth.strong
|
||||
parts.add(authLevel)
|
||||
}
|
||||
}
|
||||
for (String role : response.getActualRoles()) {
|
||||
if (isLevel(role)) { // previous authLevels might have been added to the roles already
|
||||
parts.add("urn:nevis:level:$role")
|
||||
}
|
||||
// levels can also be normal roles so we add them always
|
||||
parts.add(role)
|
||||
}
|
||||
def value = parts.sort().join(",")
|
||||
LOG.debug("calculated AuthnContextClassRef for SAML Response: $value")
|
||||
session['saml.idp.response.authncontext'] = value
|
||||
}
|
||||
|
||||
boolean stepupRequired(levels, String issuer) {
|
||||
|
||||
Integer requiredLevel = getRequiredLevel(levels, issuer)
|
||||
if (requiredLevel == null) {
|
||||
LOG.info("unable to determine required authentication level for request from issuer $issuer")
|
||||
setAuthnContext()
|
||||
return false
|
||||
}
|
||||
|
||||
Integer currentLevel = getCurrentLevel()
|
||||
if (currentLevel >= requiredLevel) {
|
||||
LOG.info("required authentication level $requiredLevel has been reached (current level $currentLevel)")
|
||||
setAuthnContext()
|
||||
return false
|
||||
}
|
||||
|
||||
LOG.info("required authentication level $requiredLevel has not been reached (current level $currentLevel) - session upgrade needed")
|
||||
request.setRequiredRoles("$requiredLevel")
|
||||
return true
|
||||
}
|
||||
|
||||
boolean hasAnyRequiredRole(i2r, issuer) {
|
||||
if (issuer != null && i2r.containsKey(issuer)) {
|
||||
def roles = i2r[issuer]
|
||||
for (role in response.getActualRoles()) {
|
||||
if (roles.contains(role)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isEnabled()) {
|
||||
LOG.info("skipping SAML authorization checks.")
|
||||
response.setResult('ok') // skip execution
|
||||
return
|
||||
}
|
||||
|
||||
// issuer set by IdentityProviderState (SP-initiated)
|
||||
def issuer = session['ch.nevis.auth.saml.request.issuer']
|
||||
|
||||
// issuer to minimum required authentication level
|
||||
def i2l = [:]
|
||||
|
||||
|
||||
if (stepupRequired(i2l, issuer)) {
|
||||
LOG.info("authentication level stepup required.")
|
||||
response.setResult("stepup")
|
||||
return // we are done for now
|
||||
}
|
||||
|
||||
// issuer to list of required roles
|
||||
def i2r = [:]
|
||||
|
||||
|
||||
// issuer to ResultCond name
|
||||
def i2e = [:]
|
||||
i2e.put('https://trustbroker.agov-d.azure.adnovum.net', 'forbidden_0')
|
||||
|
||||
|
||||
if (!i2r.isEmpty() && !hasAnyRequiredRole(i2r, issuer)) {
|
||||
LOG.info("required roles check failed.")
|
||||
response.setResult(i2e[issuer])
|
||||
return // we are done
|
||||
}
|
||||
|
||||
response.setResult('ok')
|
|
@ -0,0 +1,174 @@
|
|||
import java.util.zip.Inflater
|
||||
import java.util.zip.InflaterInputStream
|
||||
|
||||
import groovy.xml.XmlSlurper
|
||||
import groovy.xml.slurpersupport.GPathResult
|
||||
|
||||
/**
|
||||
* Gets the value of the Referer header.
|
||||
* If the header is missing the fallback is returned.
|
||||
*
|
||||
* Do NOT remove this method.
|
||||
* This method is used when SAML IDP / Dispatch Error Redirect is not set.
|
||||
* A call to this method will be generated into this script (~line 157).
|
||||
*
|
||||
* @param fallback - value to return if the Referer header is missing
|
||||
* @return value of header or fallback
|
||||
*/
|
||||
def getReferer(String fallback) {
|
||||
return request.getHttpHeader('Referer') ?: fallback
|
||||
}
|
||||
|
||||
def redirect(String url) {
|
||||
outargs.put('nevis.transfer.type', 'redirect')
|
||||
outargs.put('nevis.transfer.destination', url)
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the content of the Issuer element from a parsed SAML message.
|
||||
* The Issuer is optional according to SAML specification but we need it for dispatching.
|
||||
*
|
||||
* @param xml - as parsed by Groovy XmlSlurper
|
||||
* @return text content of Issuer element converted or null
|
||||
*/
|
||||
static String getIssuer(GPathResult xml) {
|
||||
return xml.depthFirst().find { GPathResult node -> {
|
||||
node.name().endsWith(":Issuer") || node.name().equalsIgnoreCase("Issuer")
|
||||
}
|
||||
}?.text()
|
||||
}
|
||||
|
||||
String getIssuer(String value) {
|
||||
if (value == null) {
|
||||
return
|
||||
}
|
||||
String text
|
||||
byte[] decoded
|
||||
def parser = new XmlSlurper()
|
||||
// if value is raw xml then continue otherwise try to parse the base64 encoding
|
||||
if (value.startsWith("<")) {
|
||||
text = new String(value)
|
||||
}
|
||||
else {
|
||||
decoded = value.decodeBase64()
|
||||
text = new String(decoded)
|
||||
LOG.info("received SAML request $value")
|
||||
}
|
||||
|
||||
// after decoded, if redirect binding, we need to parse string to xml
|
||||
if (text.startsWith("<")) {
|
||||
LOG.debug("assuming POST/SOAP binding")
|
||||
// plain String (POST/SOAP parameter)
|
||||
def xml = parser.parseText(text)
|
||||
return getIssuer(xml)
|
||||
}
|
||||
else {
|
||||
LOG.debug("assuming redirect binding")
|
||||
// should be deflate encoded (query parameter)
|
||||
def is = new InflaterInputStream(new ByteArrayInputStream(decoded), new Inflater(true))
|
||||
def xml = parser.parse(is)
|
||||
return getIssuer(xml)
|
||||
}
|
||||
}
|
||||
|
||||
def dispatchIssuer(i2s, String issuer) {
|
||||
def result = i2s.get(issuer)
|
||||
if (result == null) {
|
||||
throw new RuntimeException("No SP found for issuer '$issuer'. Hint: check SAML SP Connector patterns.")
|
||||
}
|
||||
response.setResult(result)
|
||||
session.put("saml.inbound.issuer", issuer)
|
||||
session.put('saml.idp.result', result) // remember decision for sub-sequent requests without a SAML message
|
||||
}
|
||||
|
||||
def dispatchMessage(i2s, String message) {
|
||||
def issuer = getIssuer(message)
|
||||
if (issuer == null) {
|
||||
throw new RuntimeException("No issuer found in incoming SAML message. Giving up.")
|
||||
}
|
||||
session.put("saml.inbound.issuer", issuer)
|
||||
dispatchIssuer(i2s, issuer)
|
||||
}
|
||||
|
||||
if (parameters.get('logoutConfirmation') == 'true' && "stepup" == request.getMethod()) {
|
||||
String url = request.currentResource
|
||||
def path = new URL(url).getPath()
|
||||
if (path.endsWith("/logout")) {
|
||||
// next AuthState will show a logout confirmation GUI
|
||||
response.setResult('confirm')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// ensure session exists
|
||||
if (request.getSession(false) == null) {
|
||||
session = request.getSession(true).getData()
|
||||
}
|
||||
|
||||
// issuer (any case) -> ResultCond name
|
||||
def i2s = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER)
|
||||
|
||||
|
||||
i2s.put('https://trustbroker.agov-d.azure.adnovum.net', 'state0')
|
||||
|
||||
def spInitiatedAllowed = parameters.get('spInitiated') == 'true'
|
||||
def idpInitiatedAllowed = parameters.get('idpInitiated') == 'true'
|
||||
|
||||
try {
|
||||
if (spInitiatedAllowed && inargs.containsKey('SAMLRequest')) { // SP-initiated authentication
|
||||
LOG.debug("found SAMLRequest parameter for SP-initiated authentication")
|
||||
String message = inargs.get('SAMLRequest')
|
||||
dispatchMessage(i2s, message)
|
||||
return
|
||||
}
|
||||
|
||||
if (inargs.containsKey('SAMLResponse')) { // response to IDP-initiated SAML Logout
|
||||
LOG.debug("found SAMLResponse parameter")
|
||||
String message = inargs.get('SAMLResponse')
|
||||
dispatchMessage(i2s, message)
|
||||
return
|
||||
}
|
||||
|
||||
if (spInitiatedAllowed && inargs.containsKey('soapheader')) { // SP-initiated SOAP with soapheader
|
||||
LOG.debug("found soapheader parameter for SP-initiated")
|
||||
String message = inargs.get('soapheader')
|
||||
dispatchMessage(i2s, message)
|
||||
return
|
||||
}
|
||||
|
||||
if (spInitiatedAllowed && inargs.containsKey('')) { // SP-initiated SOAP with empty
|
||||
LOG.debug("found empty parameter for SP-initiated SOAP message")
|
||||
String message = inargs.get('')
|
||||
dispatchMessage(i2s, message)
|
||||
return
|
||||
}
|
||||
|
||||
String issuer = inargs['Issuer'] ?: inargs['issuer']
|
||||
if (idpInitiatedAllowed && issuer != null) { // IDP-initiated authentication
|
||||
LOG.debug("found Issuer parameter for IDP-initiated authentication")
|
||||
dispatchIssuer(i2s, issuer)
|
||||
return
|
||||
}
|
||||
|
||||
// used as fallback in case of ?logout (we need an IdentityProviderState)
|
||||
if (inargs.containsKey("logout") && session.containsKey('saml.idp.result')) {
|
||||
def result = session.get('saml.idp.result')
|
||||
LOG.debug("dispatching to last used ResultCond: $result")
|
||||
response.setResult(result)
|
||||
return
|
||||
}
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
LOG.error("Error while dispatching SAML message: ${e.message}")
|
||||
}
|
||||
|
||||
def redirectEnabled = parameters.get('errorHandling') == 'redirect'
|
||||
if (redirectEnabled) {
|
||||
def location = getReferer('/')
|
||||
LOG.info("Unable to dispatch request. Giving up and redirecting (back) to $location")
|
||||
redirect(location)
|
||||
}
|
||||
else {
|
||||
LOG.info("Unable to dispatch request. Giving up and showing error GUI.")
|
||||
response.setResult('default')
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
def redirect(location) {
|
||||
outargs.put('nevis.transfer.type', 'redirect')
|
||||
outargs.put('nevis.transfer.destination', location)
|
||||
}
|
||||
|
||||
def getReturnURL() {
|
||||
if (inargs.containsKey('return')) {
|
||||
return inargs.get('return')
|
||||
}
|
||||
// determine returnURL based on Referer header (if present and not pointing to this page)
|
||||
def referer = request.getHttpHeader('Referer')
|
||||
if (referer == null) {
|
||||
LOG.debug('no Referer header found')
|
||||
return null
|
||||
}
|
||||
// strip query String for comparison
|
||||
String previous = referer.contains('?') ? referer.substring(0, referer.indexOf("?")) : referer
|
||||
def current = request.getCurrentResource()
|
||||
if (current.startsWith(previous)) {
|
||||
LOG.debug("Referer header $referer cannot be used as return URL - cyclic redirect")
|
||||
return null
|
||||
}
|
||||
return referer
|
||||
}
|
||||
|
||||
if (inargs.containsKey('logout-confirm')) {
|
||||
def current = request.getCurrentResource()
|
||||
// user has confirmed logout -> replace /logout with /?logout
|
||||
String location
|
||||
if (current.contains('?')) {
|
||||
location = current.replace("/logout?", "/?logout&")
|
||||
}
|
||||
else {
|
||||
location = current.replace("/logout", "/?logout")
|
||||
}
|
||||
redirect(location)
|
||||
return
|
||||
}
|
||||
|
||||
if (inargs.containsKey('logout-abort')) {
|
||||
// user has aborted logout -> redirect to stored return URL
|
||||
def location = session.get('logout-abort-url')
|
||||
redirect(location)
|
||||
return
|
||||
}
|
||||
|
||||
// user has not clicked any button -> render GUI
|
||||
response.setGuiName('saml_logout_confirm')
|
||||
response.setGuiLabel('title.logout.confirmation')
|
||||
// not setting a target as the API has been removed
|
||||
response.addInfoGuiField('info', 'info.logout.confirmation', null)
|
||||
response.addButtonGuiField('logout-confirm', 'continue.button.label', 'true')
|
||||
|
||||
def returnURL = getReturnURL()
|
||||
|
||||
if (returnURL != null) {
|
||||
// store return URL in session
|
||||
session.put('logout-abort-url', returnURL)
|
||||
}
|
||||
|
||||
if (session.containsKey('logout-abort-url')) {
|
||||
// add cancel button to go back
|
||||
response.addButtonGuiField('logout-abort', 'cancel.button.label', 'true')
|
||||
}
|
|
@ -47,7 +47,7 @@ spec:
|
|||
podDisruptionBudget:
|
||||
maxUnavailable: "50%"
|
||||
git:
|
||||
tag: "r-ca58de85fdf7a911b85ea6cd56b4c1a3d7f94fd6"
|
||||
tag: "r-04ad6fd7455702c2a591f4a7b8d6c94222de911e"
|
||||
dir: "DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/proxy-idp"
|
||||
credentials: "git-credentials"
|
||||
database:
|
||||
|
|
|
@ -1112,6 +1112,11 @@
|
|||
<url-pattern>/pwreset/*</url-pattern>
|
||||
</filter-mapping>
|
||||
<!-- source: pattern://4fcfadb4a5c946ead7e6e995 -->
|
||||
<filter-mapping>
|
||||
<filter-name>SessionHandler_Auth_Realm_Main_IDP</filter-name>
|
||||
<url-pattern>/SAML2/SECSSO/*</url-pattern>
|
||||
</filter-mapping>
|
||||
<!-- source: pattern://4fcfadb4a5c946ead7e6e995 -->
|
||||
<filter-mapping>
|
||||
<filter-name>SessionHandler_Auth_Realm_Main_IDP</filter-name>
|
||||
<url-pattern>/SAML2/SSO/*</url-pattern>
|
||||
|
@ -1203,6 +1208,11 @@
|
|||
<url-pattern>/pwreset/*</url-pattern>
|
||||
</filter-mapping>
|
||||
<!-- source: pattern://4fcfadb4a5c946ead7e6e995 -->
|
||||
<filter-mapping>
|
||||
<filter-name>AuthenticationService_Auth_Realm_Main_IDP</filter-name>
|
||||
<url-pattern>/SAML2/SECSSO/*</url-pattern>
|
||||
</filter-mapping>
|
||||
<!-- source: pattern://4fcfadb4a5c946ead7e6e995 -->
|
||||
<filter-mapping>
|
||||
<filter-name>AuthenticationService_Auth_Realm_Main_IDP</filter-name>
|
||||
<url-pattern>/SAML2/SSO/*</url-pattern>
|
||||
|
@ -1635,10 +1645,10 @@
|
|||
<param-value>true</param-value>
|
||||
</init-param>
|
||||
</servlet>
|
||||
<!-- source: pattern://e0fda9336be9c69dafc9b69e, pattern://a6f6dc6affdc7c692ff857b9, pattern://decb9b3f88d430fb5c95f466 -->
|
||||
<!-- source: pattern://e0fda9336be9c69dafc9b69e, pattern://76b897ee0646a882016810be, pattern://a6f6dc6affdc7c692ff857b9, pattern://decb9b3f88d430fb5c95f466 -->
|
||||
<servlet>
|
||||
<servlet-name>Hosting_Default</servlet-name>
|
||||
<!-- source: pattern://e0fda9336be9c69dafc9b69e, pattern://a6f6dc6affdc7c692ff857b9, pattern://decb9b3f88d430fb5c95f466 -->
|
||||
<!-- source: pattern://e0fda9336be9c69dafc9b69e, pattern://76b897ee0646a882016810be, pattern://a6f6dc6affdc7c692ff857b9, pattern://decb9b3f88d430fb5c95f466 -->
|
||||
<servlet-class>ch::nevis::isiweb4::servlet::defaults::DefaultServlet</servlet-class>
|
||||
</servlet>
|
||||
<!-- source: pattern://097929211988398a87bcbb0c -->
|
||||
|
@ -1754,6 +1764,11 @@
|
|||
<servlet-name>Hosting_Default</servlet-name>
|
||||
<url-pattern>/AUTH/RECOVERY/*</url-pattern>
|
||||
</servlet-mapping>
|
||||
<!-- source: pattern://76b897ee0646a882016810be -->
|
||||
<servlet-mapping>
|
||||
<servlet-name>Hosting_Default</servlet-name>
|
||||
<url-pattern>/SAML2/SECSSO/*</url-pattern>
|
||||
</servlet-mapping>
|
||||
<!-- source: pattern://a6f6dc6affdc7c692ff857b9 -->
|
||||
<servlet-mapping>
|
||||
<servlet-name>Hosting_Default</servlet-name>
|
||||
|
|
Loading…
Reference in New Issue