126 lines
5.3 KiB
Groovy
126 lines
5.3 KiB
Groovy
import ch.nevis.esauth.auth.engine.AuthResponse
|
|
import ch.nevis.esauth.util.httpclient.api.HttpClient
|
|
|
|
import io.opentelemetry.api.trace.Span
|
|
|
|
import groovy.json.JsonSlurper
|
|
import groovy.xml.XmlSlurper
|
|
|
|
// 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'
|
|
|
|
|
|
|
|
|
|
|
|
HttpClient httpClient = HttpClients.create(parameters)
|
|
def spanCtxt = Span.current().getSpanContext()
|
|
def traceparent = "00-${spanCtxt.getTraceId()}-${spanCtxt.getSpanId()}-${spanCtxt.getTraceFlags().asHex()}"
|
|
|
|
String clientExtId = session.get('ch.adnovum.nevisidm.user.clientExtId')
|
|
String userExtId = session.get('ch.adnovum.nevisidm.user.extId')
|
|
String sessionId = session.get('ch.nevis.session.conversationId')
|
|
|
|
String endPoint = "${parameters.get('utility-service.baseUrl')}/api/v1/recovery/code"
|
|
|
|
def userDto = new XmlSlurper().parseText(session.get('ch.adnovum.nevisidm.userDto'))
|
|
def recoveryCredential = userDto.'**'.find {node -> node.name() == 'credentials' && node.type.text() == 'CONTEXT_PASSWORD' && node.context.text() == 'RECOVERY'}
|
|
|
|
// Only for aq 100, skip for the rest
|
|
if (Arrays.stream(response.getActualRoles()).filter( r -> r.matches('^.*AGOV-Loi\\.level[2345]00.*$')).findAny().isPresent()) {
|
|
LOG.debug("Account '${user}' has a higher AQ-level than 100, no need to check code")
|
|
response.setResult('done')
|
|
return
|
|
}
|
|
|
|
// 1a) check if user has a credential
|
|
if ( recoveryCredential != null ) {
|
|
LOG.debug("Account '${user}' has an active recovery code, no need to create new code")
|
|
response.setResult('done')
|
|
return
|
|
}
|
|
|
|
// 1b) check if a recovery is ongoing (nothing to do)
|
|
if (Arrays.stream(response.getActualRoles()).filter( r -> r.contains('AGOV-AccountStatus.recovery')).findAny().isPresent()) {
|
|
LOG.debug("Account '${user}' is in recovery, no need to create new code")
|
|
response.setResult('done')
|
|
return
|
|
}
|
|
|
|
// 1c) don't do it for mobile phones (BUNDBITBK-4445)
|
|
if (userAgent =~ /(iPhone|Android)/ ) {
|
|
LOG.debug("User '${user}' used a mobile phone, recovery code creation skipped")
|
|
response.setResult('done')
|
|
return
|
|
}
|
|
|
|
// 2) set cookie for recoveryCode
|
|
if (outargs.containsKey('out.JWTToken')) {
|
|
def token = outargs.getProperty('out.JWTToken').bytes.encodeBase64().toString()
|
|
def agovRecoveryCodeCookie = "agovRecoveryCode=${token }; Domain=${parameters.get('cookie.domain')}; Path=/; SameSite=Strict; Secure; HttpOnly"
|
|
response.setHeader('Set-Cookie', agovRecoveryCodeCookie)
|
|
outargs.remove('out.JWTToken')
|
|
}
|
|
|
|
// 3) generate code if not yet done
|
|
if (!session['agov.new.recovery.code.generated']) {
|
|
inargs.remove('submit')
|
|
try {
|
|
def httpResponse = Http.post()
|
|
.url(endPoint)
|
|
.header("Accept", "application/json")
|
|
.header("traceparent", traceparent)
|
|
.entity(Http.entity()
|
|
.content("{\"userExtId\":\"$userExtId\",\"userSessionId\": \"$sessionId\"}")
|
|
.contentType("application/json")
|
|
.build())
|
|
.build()
|
|
.send(httpClient)
|
|
|
|
|
|
if (httpResponse.code() != 200) {
|
|
LOG.debug("Result: ${httpResponse}")
|
|
LOG.warn("Event='RCVRY-CODE', Requester='${requester}', RequestId='${requestId}', RequestedAq=${requestedAq}, User=${user}, CredentialType='${credentialType}', SourceIp=${sourceIp}, UserAgent='${userAgent}', reason='Failed to create code (http status code ${httpResponse.code()})")
|
|
response.setResult('failed')
|
|
return
|
|
}
|
|
|
|
def json = new JsonSlurper().parseText(httpResponse.bodyAsString())
|
|
|
|
notes.setProperty('agov.new.recovery.code', json['recoveryCode']['code'].replaceAll('^(....)(....)(.*)$', '$1-$2-$3'))
|
|
LOG.debug("agov.new.recovery.code: ${notes['agov.new.recovery.code']}")
|
|
|
|
response.setSessionAttribute('agov.new.recovery.code.generated', 'true')
|
|
def validTil = "${json['recoveryCode']['validUntil'][2]}.${json['recoveryCode']['validUntil'][1]}.${json['recoveryCode']['validUntil'][0]}"
|
|
response.setSessionAttribute('agov.new.recovery.code.validTil', validTil)
|
|
response.setSessionAttribute('agov.new.recovery.code.pdfAuthToken', json['authToken'])
|
|
|
|
LOG.info("Event='RCVRY-CODE', Requester='${requester}', RequestId='${requestId}', RequestedAq=${requestedAq}, User=${user}, CredentialType='${credentialType}', SourceIp=${sourceIp}, UserAgent='${userAgent}'")
|
|
|
|
} catch(Exception e) {
|
|
LOG.warn("Event='RCVRY-CODE', Requester='${requester}', RequestId='${requestId}', RequestedAq=${requestedAq}, User=${user}, CredentialType='${credentialType}', SourceIp=${sourceIp}, UserAgent='${userAgent}', reason='Failed to create code (http status code ${e.getMessage()})")
|
|
LOG.error("Recoverycode processing failed: $e")
|
|
response.setResult('failed')
|
|
return
|
|
}
|
|
|
|
response.setResult('encryptCode')
|
|
return
|
|
}
|
|
|
|
if (inargs['submit']) {
|
|
def agovRecoveryCodeCookie = "agovRecoveryCode=deleted; Domain=${parameters.get('cookie.domain')}; Path=/; Max-Age=0; SameSite=Strict; Secure; HttpOnly"
|
|
response.setHeader('Set-Cookie', agovRecoveryCodeCookie)
|
|
response.setResult('done')
|
|
return
|
|
}
|
|
|
|
// show the GUI
|
|
response.setStatus(AuthResponse.AUTH_CONTINUE)
|