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

173 lines
6.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 getCookie(String name){
cookies = getHeader('cookie')
if (cookies != null) {
if (cookies.matches('^.*'+"${name}"+'=([^;]+).*$')) {
return cookies.replaceAll('^.*'+"${name}"+'=([^;]+).*$', '$1')
}
}
return null
}
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')
def eidEnabled = parameters.get('eidPassthroughEnabled') == "true" || parameters.get('eidFullEnabled') == "true"
eidEnabled
LOG.error("EID?: " + eidEnabled)
LOG.error("Full?: " + parameters.get('eidFullEnabled'))
LOG.error("Pass?: " + parameters.get('eidPassthroughEnabled'))
def requestedLoa = s.getAttribute("agov.requestedRoleLevel")
// TODO: use a different flag to check if this is a eid request since eid can now also be used for lower aq
if( eidEnabled && ( requestedLoa == "600" || requestedLoa == "500") ){
// EID request -> goto correct state
response.setResult('continueEidAfterRepost')
}else{
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)
// we check if a login method cookie has been set, if so save it to the session
def lastLoginMethod = getCookie('LOGINMETHOD')
if(lastLoginMethod != null){
s.setAttribute('agov.lastLoginMethod', lastLoginMethod)
}
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 = getCookie('agovLogin')
if (agovLoginCookie == null) {
agovLoginCookie = 'missing'
}
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
}