adn-agov-iam-project/patterns/7a913eec7f78ce674cd87854_re.../idp_status_check.groovy

146 lines
5.5 KiB
Groovy

import groovy.json.JsonBuilder
import java.security.MessageDigest
import java.util.HashSet
import ch.nevis.esauth.auth.engine.AuthResponse
def getHeader(String name) {
def inctx = request.getLoginContext()
// case-insensitive lookup of HTTP headers
def map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER)
map.putAll(inctx)
return map['connection.HttpHeader.' + name]
}
def sha256(String input) {
// we do not catch NoSuchAlgorithmException, as every implementation of the Java platform is required to support SHA-256
def digestBytes = MessageDigest.getInstance('SHA-256').digest(input.getBytes())
return digestBytes.encodeBase64().toString()
}
def clearCurrentAuthenticationSession() {
// clean up session attributes
def s = request.getAuthSession(true)
def requestId = session['ch.nevis.auth.saml.request.id'] ?: 'unknown'
// we backup the replaced requestId
if (requestId != 'unknown') {
s.setAttribute('agov.replacedRequestId', '' + requestId)
}
// fido
s.removeAttribute('ch.nevis.auth.fido.uaf.fidouafsessionid')
// SAML
s.removeAttribute('finisherState-DeferredResponse')
s.removeAttribute('saml.idp.result')
s.removeAttribute('saml.inbound.issuer')
def sessionKeySet = new HashSet(session.keySet())
sessionKeySet.each { key ->
if ( key ==~ /ch.nevis.auth.saml.request.*/ ) {
s.removeAttribute(key)
}
}
// agov
s.removeAttribute('agov.requestedRoleLevel')
}
// context: script is executed, thus we are in the initial dispatching of the state engine
// due to the resetAuthenticationCondition it will be called for sure after each SAMLRequest received
if (inargs['SAMLRequest'] != null) {
if (session['ch.nevis.auth.saml.request.id'] != null) {
// Accounting
def requester = session['ch.nevis.auth.saml.request.scoping.requesterId'] ?: 'unknown'
def requestId = session['ch.nevis.auth.saml.request.id'] ?: 'unknown'
def requestedAq = session['agov.requestedRoleLevel'] ?: 'unknown'
def user = session['ch.adnovum.nevisidm.user.extId'] ?: 'unknown'
def credentialType = session['authenticatedWith'] ?: 'unknown'
def sourceIp = request.getLoginContext()['connection.HttpHeader.X-Real-IP'] ?: 'unknown'
def userAgent = request.getLoginContext()['connection.HttpHeader.user-agent'] ?: request.getLoginContext()['connection.HttpHeader.User-Agent'] ?: 'unknown'
// check if we receive a repost of the ongoing request
if (session['agov.currentSamlRequestHash'] != null && session['agov.currentSamlRequestHash'] == sha256(inargs['SAMLRequest'])) {
LOG.info("Event='AUTHCONTINUE', Requester='${requester}', RequestId='${requestId}', RequestedAq=${requestedAq}, User=${user}, CredentialType='${credentialType}', SourceIp=${sourceIp}, UserAgent='${userAgent}'")
request.getInArgs().remove('SAMLRequest')
request.getInArgs().remove('RelayState')
// restore the finisher again (was removed by resetAuthenticationCondition)
def s = request.getAuthSession(true)
s.setAttribute('ch.nevis.session.finishers', '' + session['agov.backup.finishers'])
// process it the same way, as if frontend triggered a reload
request.getInArgs().setProperty('onReload', 'now')
response.setResult('continueAfterRepost')
return
}
// else, the new replaces the on-going one
LOG.info("Event='AUTHREPL', Requester='${requester}', RequestId='${requestId}', RequestedAq=${requestedAq}, User=${user}, CredentialType='${credentialType}', SourceIp=${sourceIp}, UserAgent='${userAgent}'")
clearCurrentAuthenticationSession()
}
// we track the SAML Request we received
def s = request.getAuthSession(true)
s.setAttribute('agov.currentSamlRequestHash', '' + sha256(inargs['SAMLRequest']))
// we set/update a login Cookie
def agovLoginCookie = "agovLogin=${System.currentTimeMillis()}; Domain=${parameters.get('cookie.domain')}; Path=/; SameSite=Strict; Secure; HttpOnly"
response.setHeader('Set-Cookie', agovLoginCookie)
response.setResult('ok')
return
}
// from here on, corner cases //
// =============================
def json = new JsonBuilder()
if (inargs.containsKey('o.fidoUafSessionId.v')) {
// timeout, and script in login page is still polling -> send fake response
LOG.debug('authentication timeout reached, login script is still polling access app status')
json {
"status" "unknown"
"timestamp" org.joda.time.DateTime.now().toString()
}
String body = json.toString()
response.setContent(body)
response.setContentType('application/json')
response.setHttpStatusCode(200)
response.setIsDirectResponse(true)
response.setStatus(AuthResponse.AUTH_CONTINUE)
return
}
else {
// authentication timeout reached, or SSO-Endpoint bookmarked -> return a 404
def agovLoginCookie = 'missing'
if (getHeader('cookie') != null) {
def cookies = getHeader('cookie')
if (cookies.matches('^.*agovLogin=([^;]+).*$')) {
agovLoginCookie = cookies.replaceAll('^.*agovLogin=([^;]+).*$', '$1')
}
}
LOG.debug("agovLoginCookie: ${agovLoginCookie}")
if (agovLoginCookie == 'missing' || agovLoginCookie == 'deleted') {
LOG.debug('SSO-Endpoint bookmarked -> return a 404')
response.setHttpStatusCode(404)
response.setIsDirectResponse(true)
response.setStatus(AuthResponse.AUTH_ERROR)
}
else {
LOG.debug('authentication timeout reached -> return a 408')
response.setHttpStatusCode(408)
response.setIsDirectResponse(true)
response.setStatus(AuthResponse.AUTH_ERROR)
}
return
}