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')