diff --git a/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/etc/nevis/k8s-nevisauth-7022472ae407577ae604bbb8.yaml b/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/etc/nevis/k8s-nevisauth-7022472ae407577ae604bbb8.yaml index 5e66bb1..a28f573 100644 --- a/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/etc/nevis/k8s-nevisauth-7022472ae407577ae604bbb8.yaml +++ b/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/etc/nevis/k8s-nevisauth-7022472ae407577ae604bbb8.yaml @@ -46,7 +46,7 @@ spec: podDisruptionBudget: maxUnavailable: "50%" git: - tag: "r-04ad6fd7455702c2a591f4a7b8d6c94222de911e" + tag: "r-0574c5a2098562d6585435194234bdb2b0cf0858" dir: "DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth" credentials: "git-credentials" database: diff --git a/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/var/opt/nevisauth/default/conf/esauth4.xml b/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/var/opt/nevisauth/default/conf/esauth4.xml index 7e482b0..a67760c 100644 --- a/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/var/opt/nevisauth/default/conf/esauth4.xml +++ b/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/var/opt/nevisauth/default/conf/esauth4.xml @@ -134,14 +134,10 @@ - - - - - - - - + + + + @@ -168,13 +164,10 @@ - - - @@ -438,6 +431,8 @@ + + @@ -951,6 +946,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1410,7 +1450,7 @@ - + @@ -1558,21 +1598,12 @@ - - - - - - - - - - + + + + + - - - - @@ -1711,22 +1742,6 @@ - - - - - - - - - - - - - - - - @@ -2268,6 +2283,15 @@ + + + + + + + + + diff --git a/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/var/opt/nevisauth/default/conf/idp_dispatcher.groovy b/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/var/opt/nevisauth/default/conf/idp_dispatcher.groovy index 20a9e7e..2289c32 100644 --- a/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/var/opt/nevisauth/default/conf/idp_dispatcher.groovy +++ b/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/var/opt/nevisauth/default/conf/idp_dispatcher.groovy @@ -30,47 +30,52 @@ def redirect(String url) { * @param xml - as parsed by Groovy XmlSlurper * @return text content of Issuer element converted or null */ -String getIssuer(GPathResult xml) { +String getNodeText(GPathResult xml, String nodeName) { return xml.depthFirst().find { GPathResult node -> { - node.name().endsWith(":Issuer") || node.name().equalsIgnoreCase("Issuer") + node.name().endsWith(":${nodeName}") || node.name().equalsIgnoreCase(nodeName) } - }?.text() + }?.text()?.trim() } -String getIssuer(String value) { - if (value == null) { +String getNodeText(String samlMessage, String nodeName) { + if (samlMessage == 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) + // if samlMessage is raw xml then continue otherwise try to parse the base64 encoding + if (samlMessage.startsWith("<")) { + text = new String(samlMessage) } else { - decoded = value.decodeBase64() + decoded = samlMessage.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) + return getNodeText(xml, nodeName) } 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) + return getNodeText(xml, nodeName) } } -def dispatchIssuer(i2s, String issuer) { +String getIssuer(String value) { + return getNodeText(value, 'Issuer') +} + +String getRequesterID(String value) { + return getNodeText(value, 'RequesterID') +} + +def dispatchIssuer(i2s, String issuer, String requester) { def result = i2s.get(issuer) if (result == null) { LOG.info("No SP found for issuer '$issuer'. Hint: check SAML SP Connector patterns.") @@ -80,22 +85,30 @@ def dispatchIssuer(i2s, String issuer) { if(parameters.get('epdMode') == 'artifact' && result == 'epd'){ LOG.debug("EPD: Artifact mode") result = result + "_artifact" - }else{ - LOG.debug("EPD: POST mode") - } + } else if (result == 'main') { + if ('https://op.agov-w.azure.adnovum.net/SAML2/ACS/' == requester) { + result = result + "_secure" + } + } 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 dispatchIssuer(i2s, String issuer) { + dispatchIssuer(i2s, issuer, 'unknown') +} + def dispatchMessage(i2s, String message) { def issuer = getIssuer(message) + def requester = getRequesterID(message) + if (issuer == null) { LOG.info("No issuer found in incoming SAML message. Giving up.") } session.put("saml.inbound.issuer", issuer) - dispatchIssuer(i2s, issuer) + dispatchIssuer(i2s, issuer, requester) } if (parameters.get('logoutConfirmation') == 'true' && "stepup" == request.getMethod()) { diff --git a/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/var/opt/nevisauth/default/conf/saml_idp_agov_sec_authorization.groovy b/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/var/opt/nevisauth/default/conf/saml_idp_agov_sec_authorization.groovy deleted file mode 100644 index d89725f..0000000 --- a/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/var/opt/nevisauth/default/conf/saml_idp_agov_sec_authorization.groovy +++ /dev/null @@ -1,179 +0,0 @@ -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') diff --git a/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/var/opt/nevisauth/default/conf/saml_idp_agov_sec_dispatcher.groovy b/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/var/opt/nevisauth/default/conf/saml_idp_agov_sec_dispatcher.groovy deleted file mode 100644 index 1aebdbb..0000000 --- a/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/var/opt/nevisauth/default/conf/saml_idp_agov_sec_dispatcher.groovy +++ /dev/null @@ -1,174 +0,0 @@ -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.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') -} diff --git a/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/var/opt/nevisauth/default/conf/saml_idp_logout_confirm.groovy b/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/var/opt/nevisauth/default/conf/saml_idp_logout_confirm.groovy deleted file mode 100644 index 8f7202b..0000000 --- a/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/var/opt/nevisauth/default/conf/saml_idp_logout_confirm.groovy +++ /dev/null @@ -1,64 +0,0 @@ -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') -} \ No newline at end of file diff --git a/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/proxy-idp/etc/nevis/k8s-nevisproxy-idp-0ceb05c56644a59d648c13b9.yaml b/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/proxy-idp/etc/nevis/k8s-nevisproxy-idp-0ceb05c56644a59d648c13b9.yaml index a896221..7e012d1 100644 --- a/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/proxy-idp/etc/nevis/k8s-nevisproxy-idp-0ceb05c56644a59d648c13b9.yaml +++ b/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/proxy-idp/etc/nevis/k8s-nevisproxy-idp-0ceb05c56644a59d648c13b9.yaml @@ -47,7 +47,7 @@ spec: podDisruptionBudget: maxUnavailable: "50%" git: - tag: "r-04ad6fd7455702c2a591f4a7b8d6c94222de911e" + tag: "r-0574c5a2098562d6585435194234bdb2b0cf0858" dir: "DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/proxy-idp" credentials: "git-credentials" database: diff --git a/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/proxy-idp/var/opt/nevisproxy/default/host-auth.agov-w.azure.adnovum.net/WEB-INF/web.xml b/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/proxy-idp/var/opt/nevisproxy/default/host-auth.agov-w.azure.adnovum.net/WEB-INF/web.xml index 7df3f11..d0dae25 100644 --- a/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/proxy-idp/var/opt/nevisproxy/default/host-auth.agov-w.azure.adnovum.net/WEB-INF/web.xml +++ b/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/proxy-idp/var/opt/nevisproxy/default/host-auth.agov-w.azure.adnovum.net/WEB-INF/web.xml @@ -1112,11 +1112,6 @@ /pwreset/* - - SessionHandler_Auth_Realm_Main_IDP - /SAML2/SECSSO/* - - SessionHandler_Auth_Realm_Main_IDP /SAML2/SSO/* @@ -1208,11 +1203,6 @@ /pwreset/* - - AuthenticationService_Auth_Realm_Main_IDP - /SAML2/SECSSO/* - - AuthenticationService_Auth_Realm_Main_IDP /SAML2/SSO/* @@ -1645,10 +1635,10 @@ true - + Hosting_Default - + ch::nevis::isiweb4::servlet::defaults::DefaultServlet @@ -1764,11 +1754,6 @@ Hosting_Default /AUTH/RECOVERY/* - - - Hosting_Default - /SAML2/SECSSO/* - Hosting_Default