diff --git a/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/etc/nevis/k8s-nevisauth-7022472ae407577ae604bbb8.yaml b/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/etc/nevis/k8s-nevisauth-7022472ae407577ae604bbb8.yaml index 2a9c6c0..c04c000 100644 --- a/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/etc/nevis/k8s-nevisauth-7022472ae407577ae604bbb8.yaml +++ b/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/etc/nevis/k8s-nevisauth-7022472ae407577ae604bbb8.yaml @@ -45,7 +45,7 @@ spec: podDisruptionBudget: maxUnavailable: "50%" git: - tag: "r-68680b2182672bd8a81d786c163e95b91fb89a64" + tag: "r-1663a8d1d9ae71e0fb7c5af2e10bfc2536ee973b" dir: "DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth" credentials: "git-credentials" keystores: diff --git a/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/var/opt/nevisauth/default/conf/SendSamlResponseWithAssertion.groovy b/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/var/opt/nevisauth/default/conf/SendSamlResponseWithAssertion.groovy index 680791c..1269ed1 100644 --- a/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/var/opt/nevisauth/default/conf/SendSamlResponseWithAssertion.groovy +++ b/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/var/opt/nevisauth/default/conf/SendSamlResponseWithAssertion.groovy @@ -10,6 +10,20 @@ def tAuth = System.currentTimeMillis() - (request.getSession(true).getCreationTi LOG.info("Event='AUTHENTICATION', Requester='${requester}', RequestId='${requestId}', RequestedAq=${requestedAq}, User=${user}, CredentialType='${credentialType}', tAuth=${tAuth}ms, SourceIp=${sourceIp}, UserAgent='${userAgent}'") +// BUNDBITBK-4824: Address was missing after bmid verification +def session = request.getAuthSession(true) +int loa = session.get('agov.actualRoleLevel') as int + +// Best Token Available only if account's AQlevel is high enough +if ((session.getAttribute('agov.appAddressRequired') == 'true') && (loa < 200)) { + LOG.debug("Best Token: Address requested but account has to low AQ (${loa})") + session.setAttribute('agov.appAddressRequired', 'false') +} +if ((session.getAttribute('agov.appSvnrAllowed') == 'true') && (loa < 400)) { + LOG.debug("Best Token: SVNr requested but account has to low AQ (${loa})") + session.setAttribute('agov.appSvnrAllowed', 'false') +} +// BUNDBITBK-4824 END // delete the login cookie def agovLoginCookie = "agovLogin=deleted; Domain=${parameters.get('cookie.domain')}; Path=/; Max-Age=0; SameSite=Strict; Secure; HttpOnly" diff --git a/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/var/opt/nevisauth/default/conf/eid_verification_auth.groovy b/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/var/opt/nevisauth/default/conf/eid_verification_auth.groovy index 05ea3c6..ccfb5bd 100644 --- a/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/var/opt/nevisauth/default/conf/eid_verification_auth.groovy +++ b/DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth/var/opt/nevisauth/default/conf/eid_verification_auth.groovy @@ -1,26 +1,14 @@ -import groovy.json.JsonSlurper - import ch.nevis.esauth.auth.engine.AuthResponse import ch.nevis.esauth.util.httpclient.api.HttpClient - +import groovy.json.JsonSlurper import io.opentelemetry.api.trace.Span 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 clearIdmSessionAttributes() { - def s = request.getAuthSession(true) - def sessionKeySet = new HashSet(session.keySet()) - sessionKeySet.each { key -> - if ( key ==~ /ch.nevis.idm.*/ || key ==~ /ch.adnovum.nevisidm.*/ ) { - s.removeAttribute(key) - } - } + 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 verification_request_template = ''' @@ -129,47 +117,47 @@ def verification_request_template = ''' ''' def ERROR_CODE_TO_STATUS_MAPPER = [ - 'CREDENTIAL_INVALID': 'FAILED', - 'JWT_EXPIRED': 'ERROR', - 'INVALID_FORMAT': 'ERROR', - 'CREDENTIAL_EXPIRED': 'FAILED', - 'MISSING_NONCE': 'ERROR', - 'UNSUPPORTED_FORMAT': 'ERROR', - 'CREDENTIAL_REVOKED': 'FAILED', - 'CREDENTIAL_SUSPENDED': 'FAILED', - 'HOLDER_BINDING_MISMATCH': 'ERROR', - 'CREDENTIAL_MISSING_DATA': 'FAILED', - 'UNRESOLVABLE_STATUS_LIST': 'ERROR', - 'PUBLIC_KEY_OF_ISSUER_UNRESOLVABLE': 'ERROR', - 'CLIENT_REJECTED': 'CANCELED', - 'ISSUER_NOT_ACCEPTED' : 'ERROR' + 'CREDENTIAL_INVALID' : 'FAILED', + 'JWT_EXPIRED' : 'ERROR', + 'INVALID_FORMAT' : 'ERROR', + 'CREDENTIAL_EXPIRED' : 'FAILED', + 'MISSING_NONCE' : 'ERROR', + 'UNSUPPORTED_FORMAT' : 'ERROR', + 'CREDENTIAL_REVOKED' : 'FAILED', + 'CREDENTIAL_SUSPENDED' : 'FAILED', + 'HOLDER_BINDING_MISMATCH' : 'ERROR', + 'CREDENTIAL_MISSING_DATA' : 'FAILED', + 'UNRESOLVABLE_STATUS_LIST' : 'ERROR', + 'PUBLIC_KEY_OF_ISSUER_UNRESOLVABLE': 'ERROR', + 'CLIENT_REJECTED' : 'CANCELED', + 'ISSUER_NOT_ACCEPTED' : 'ERROR' ] // --------------- // check, whether we are still processing the correct AuthnRequest if (inargs.containsKey('authRequestId') && (inargs['authRequestId'] != session['ch.nevis.auth.saml.request.id'])) { - // wrong request, "force" a timeout - LOG.debug('authentication timeout enforced, due to concurrent requests -> return a 408') + // wrong request, "force" a timeout + LOG.debug('authentication timeout enforced, due to concurrent requests -> return a 408') - response.setIsDirectResponse(true) - response.setContentType('text/html; charset=UTF-8') - response.setContent('Timeout') - response.setHttpStatusCode(205) - response.setHeader('IDP-AUTH', 'Timeout') + response.setIsDirectResponse(true) + response.setContentType('text/html; charset=UTF-8') + response.setContent('Timeout') + response.setHttpStatusCode(205) + response.setHeader('IDP-AUTH', 'Timeout') - // CONTINUE to keep the other request beeing processed - response.setStatus(AuthResponse.AUTH_CONTINUE) - return + // CONTINUE to keep the other request beeing processed + response.setStatus(AuthResponse.AUTH_CONTINUE) + return } if (inargs['oid4vp'] == 'ERROR') { - response.setResult('error') - return + response.setResult('error') + return } if (inargs['oid4vp'] == 'SUCCEEDED') { - response.setResult('ok') - return + response.setResult('ok') + return } @@ -181,39 +169,40 @@ def traceparent = "00-${spanCtxt.getTraceId()}-${spanCtxt.getSpanId()}-${spanCtx if (!session['agov.eid.verification']) { // Initialize the verification session on the verifier - def endPoint = "${parameters.get('eidVerifierBaseUrl')}/api/v1/verifications" + def endPoint = "${parameters.get('eidVerifierBaseUrl')}/api/v1/verifications" try { def httpResponse = Http.post() - .url(endPoint) - .header("Accept", "application/json") - .header("traceparent", traceparent) - .entity(Http.entity() - .content(verification_request_template.replaceAll("\\{\\{UUID\\}\\}", UUID.randomUUID().toString())) - .contentType("application/json") - .build()) - .build() - .send(httpClient) + .url(endPoint) + .header("Accept", "application/json") + .header("traceparent", traceparent) + .entity(Http.entity() + .content(verification_request_template.replaceAll("\\{\\{UUID}}", UUID.randomUUID().toString())) + .contentType("application/json") + .build()) + .build() + .send(httpClient) if (httpResponse.code() != 200) { - LOG.debug("Result: ${httpResponse}") + LOG.debug("Result: ${httpResponse}") response.setResult('error') return } def json = new JsonSlurper().parseText(httpResponse.bodyAsString()) - LOG.debug("Result: ${json}") + LOG.debug("Result: ${json}") sess.setAttribute('agov.eid.verification', 'true') sess.setAttribute('agov.eid.verification.id', json.id) sess.setAttribute('agov.eid.verification.link', json.verification_url) - if (json.state != 'PENDING') { + if (json.state != 'PENDING') { response.setResult('error') return - } - } catch(Exception e) { + } + } + catch (Exception e) { LOG.error("Eid verification failed: $e") response.setResult('error') return @@ -226,21 +215,21 @@ if (getHeader('Content-Type') == 'application/json' && inargs.containsKey('o.id. // TODO/haburger/2025-03-24: we should make sure, that we have an actual session on the verifier with id.v // and that authRequestId is correct - def idvalue = ( !inargs['o.id.v'] || inargs['o.id.v'] == 'NEW' ) ? session['agov.eid.verification.id'] : inargs['o.id.v'] + def idvalue = (!inargs['o.id.v'] || inargs['o.id.v'] == 'NEW') ? session['agov.eid.verification.id'] : inargs['o.id.v'] try { def endPoint = "${parameters.get('eidVerifierBaseUrl')}/api/v1/verifications/${idvalue}" def httpResponse = Http.get() - .url(endPoint) - .header("Accept", "application/json") - .header("traceparent", traceparent) - .build() - .send(httpClient) + .url(endPoint) + .header("Accept", "application/json") + .header("traceparent", traceparent) + .build() + .send(httpClient) if (httpResponse.code() != 200) { // TODO/haburger/2025-03-25: 404 we should create a new verification request - LOG.debug("Result: ${httpResponse}") + LOG.debug("Result: ${httpResponse}") result = """{ "oid4vp": { "status": "ERROR", @@ -251,44 +240,47 @@ if (getHeader('Content-Type') == 'application/json' && inargs.containsKey('o.id. }}""" LOG.warn("<== Response: ${responseCode}") } + else { - def json = new JsonSlurper().parseText(httpResponse.bodyAsString()) + def json = new JsonSlurper().parseText(httpResponse.bodyAsString()) - if (json.state == 'SUCCESS') { - def claims = json.wallet_response.credential_subject_data + if (json.state == 'SUCCESS') { + def claims = json.wallet_response.credential_subject_data - // TODO/haburger/2025-03-25: format changes to align with IDM read data - sess.setAttribute('ch.nevis.idm.User.firstName', claims.given_name) - sess.setAttribute('ch.nevis.idm.User.lastName', claims.family_name) - sess.setAttribute('ch.nevis.idm.User.birthDate', claims.birth_date) - sess.setAttribute('ch.nevis.idm.User.gender', claims.sex) - sess.setAttribute('ch.nevis.idm.User.prop.svnr', claims.personal_administrative_number) - sess.setAttribute('ch.nevis.idm.User.prop.placeOfBirth', claims.birth_place) - sess.setAttribute('ch.nevis.idm.User.prop.eIdNumber', claims.personal_administrative_number) - sess.setAttribute('ch.nevis.idm.User.prop.nationality', claims.nationality.toString()) - sess.setAttribute('ValidFrom', claims.issuance_date) - sess.setAttribute('ValidTo', claims.expiry_date) - sess.setAttribute('authenticatedWith', "urn:qa.agov.ch:names:tc:authfactor:eid") - sess.setAttribute('idVerification', "Eid") - sess.setAttribute('contextClassRefToSet', "urn:qa.agov.ch:names:tc:ac:classes:600") + // TODO/haburger/2025-03-25: format changes to align with IDM read data + sess.setAttribute('ch.nevis.idm.User.firstName', claims.given_name) + sess.setAttribute('ch.nevis.idm.User.lastName', claims.family_name) + sess.setAttribute('ch.nevis.idm.User.birthDate', claims.birth_date) + sess.setAttribute('ch.nevis.idm.User.gender', claims.sex) + sess.setAttribute('ch.nevis.idm.User.prop.svnr', claims.personal_administrative_number) + sess.setAttribute('ch.nevis.idm.User.prop.placeOfBirth', claims.birth_place) + sess.setAttribute('ch.nevis.idm.User.prop.eIdNumber', claims.personal_administrative_number) + sess.setAttribute('ch.nevis.idm.User.prop.nationality', claims.nationality.toString()) + sess.setAttribute('ValidFrom', claims.issuance_date) + sess.setAttribute('ValidTo', claims.expiry_date) + sess.setAttribute('authenticatedWith', "urn:qa.agov.ch:names:tc:authfactor:eid") + sess.setAttribute('idVerification', "Eid") + sess.setAttribute('contextClassRefToSet', "urn:qa.agov.ch:names:tc:ac:classes:600") - response.setUserId(claims.personal_administrative_number) - response.setLoginId(claims.document_number) - response.setAuthLevel("EID") + response.setUserId(claims.personal_administrative_number) + response.setLoginId(claims.document_number) + response.setAuthLevel("EID") - result = """{ + result = """{ "oid4vp": { "status": "SUCCEEDED", "verification_url": "${session['agov.eid.verification.link']}", "id": "${idvalue}", "error_code": "NONE" }}""" - } else if (json.state == 'FAILED') { - // TODO/haburger/2025-03-25: ERROR_CODE_TO_STATUS_MAPPER[json.wallet_response.error_code] == 'FAILED' we should - // initiate a new verification and return the new id, url together with the message + } + else if (json.state == 'FAILED') { + // TODO/haburger/2025-03-25: ERROR_CODE_TO_STATUS_MAPPER[json.wallet_response.error_code] == 'FAILED' we should + // initiate a new verification and return the new id, url together with the message - LOG.error("Eid verification failed: ${json.wallet_response.error_code} (${json.wallet_response.error_description})") - result = """{ + LOG + .error("Eid verification failed: ${json.wallet_response.error_code} (${json.wallet_response.error_description})") + result = """{ "oid4vp": { "status": "${ERROR_CODE_TO_STATUS_MAPPER[json.wallet_response.error_code] ?: 'ERROR'}", "verification_url": "${session['agov.eid.verification.link']}", @@ -296,16 +288,20 @@ if (getHeader('Content-Type') == 'application/json' && inargs.containsKey('o.id. "error_code": "${json.wallet_response.error_code}", "error_message": "${json.wallet_response.error_description}" }}""" - } else { - result = """{ + } + else { + result = """{ "oid4vp": { "status": "${inargs['o.id.v'] == 'NEW' ? 'INITIATED' : 'PENDING'}", "verification_url": "${session['agov.eid.verification.link']}", "id": "${idvalue}", "error_code": "NONE" }}""" + } } - } catch(Exception e) { + + } + catch (Exception e) { LOG.error("Eid verification failed: ${e}") result = """{ "oid4vp": {