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-epr-lab.azure.adnovum.net', 'forbidden_0') i2e.put('https://trustbroker-idp.agov-epr-lab.azure.adnovum.net', 'forbidden_1') if (!i2r.isEmpty() && !hasAnyRequiredRole(i2r, issuer)) { LOG.info("required roles check failed.") response.setResult(i2e[issuer]) return // we are done } response.setResult('ok')