180 lines
5.8 KiB
Groovy
180 lines
5.8 KiB
Groovy
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') |