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 } // 1b) 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 } // 1c) 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 } // 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)