146 lines
5.5 KiB
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
|
|
}
|