8 files updated

This commit is contained in:
haburger 2025-07-09 15:48:01 +00:00
parent 991bfbd7c3
commit 1e3b12f009
11 changed files with 295 additions and 296 deletions

View File

@ -1,264 +1,285 @@
import org.codehaus.groovy.runtime.StackTraceUtils import org.codehaus.groovy.runtime.StackTraceUtils
import groovy.xml.XmlSlurper import groovy.xml.XmlSlurper
def getUserAGOVLoiRoles() { def getUserAGOVLoiRoles() {
// we take the roles from actualRoles // we take the roles from actualRoles
return request.getActualRoles().findAll { role -> role.startsWith('AGOV-Loi.') }.collect({ role -> role.substring(9) }) return request.getActualRoles().findAll { role -> role.startsWith('AGOV-Loi.') }.collect({ role -> role.substring(9) })
} }
def getUserAGOVRecoveryRoles() { def getUserAGOVRecoveryRoles() {
// set attibutes from DTO: -> AGOV // set attibutes from DTO: -> AGOV
def list = new XmlSlurper().parseText(session.get('ch.adnovum.nevisidm.userDto')) def list = new XmlSlurper().parseText(session.get('ch.adnovum.nevisidm.userDto'))
return list.'**'.findAll { node -> node.name() == 'roles' && node.applicationName.text() == 'AGOV-AccountStatus' }.collect({ node -> node.name.text() }) return list.'**'.findAll { node -> node.name() == 'roles' && node.applicationName.text() == 'AGOV-AccountStatus' }.collect({ node -> node.name.text() })
} }
def getUserAGOVLoiIdVerification() { def getUserAGOVLoiIdVerification() {
// set attibutes from DTO: -> idVerification // set attibutes from DTO: -> idVerification
def list = new XmlSlurper().parseText(session.get('ch.adnovum.nevisidm.userDto')) def list = new XmlSlurper().parseText(session.get('ch.adnovum.nevisidm.userDto'))
return list.'**'.findAll {node -> node.name() == 'properties' && node.name.text() == 'idVerification' && node.scopeName.text().contains('AGOV-Loi,')}.collect({ node -> node.value.text()}) return list.'**'.findAll {node -> node.name() == 'properties' && node.name.text() == 'idVerification' && node.scopeName.text().contains('AGOV-Loi,')}.collect({ node -> node.value.text()})
} }
def getUserAGOVLoiIdVerification(level) { def getUserAGOVLoiIdVerification(level) {
// set attibutes from DTO: -> idVerification // set attibutes from DTO: -> idVerification
def list = new XmlSlurper().parseText(session.get('ch.adnovum.nevisidm.userDto')) def list = new XmlSlurper().parseText(session.get('ch.adnovum.nevisidm.userDto'))
return list.'**'.findAll {node -> node.name() == 'properties' && node.name.text() == 'idVerification' && node.scopeName.text() == 'AGOV-Loi,level' + level}.collect({ node -> node.value.text()}) return list.'**'.findAll {node -> node.name() == 'properties' && node.name.text() == 'idVerification' && node.scopeName.text() == 'AGOV-Loi,level' + level}.collect({ node -> node.value.text()})
} }
def getUserAGOVLoiValidFrom(level) { def getUserAGOVLoiValidFrom(level) {
// set attibutes from DTO: -> validFrom // set attibutes from DTO: -> validFrom
def payload = new XmlSlurper().parseText(session.get('ch.adnovum.nevisidm.userDto')) def payload = new XmlSlurper().parseText(session.get('ch.adnovum.nevisidm.userDto'))
return payload.'**'.find {node -> node.name() == 'authorizations' && node.role.name.text() == level}?.validFrom?.text() return payload.'**'.find {node -> node.name() == 'authorizations' && node.role.name.text() == level}?.validFrom?.text()
} }
def getUserAGOVLoiValidTo(level) { def getUserAGOVLoiValidTo(level) {
// set attibutes from DTO: -> validTo // set attibutes from DTO: -> validTo
def payload = new XmlSlurper().parseText(session.get('ch.adnovum.nevisidm.userDto')) def payload = new XmlSlurper().parseText(session.get('ch.adnovum.nevisidm.userDto'))
return payload.'**'.find {node -> node.name() == 'authorizations' && node.role.name.text() == level}?.validTo?.text() return payload.'**'.find {node -> node.name() == 'authorizations' && node.role.name.text() == level}?.validTo?.text()
} }
def getUserIdVerificationForRecovery() { def getUserIdVerificationForRecovery() {
// application is AGOV-AccountStatus // application is AGOV-AccountStatus
def list = new XmlSlurper().parseText(session.get('ch.adnovum.nevisidm.userDto')) def list = new XmlSlurper().parseText(session.get('ch.adnovum.nevisidm.userDto'))
def result = list.'**'.find {node -> node.name() == 'properties' && node.name.text() == 'idVerification' && node.scopeName.text() == 'AGOV-AccountStatus,mustRecover'}?.value?.text() def result = list.'**'.find {node -> node.name() == 'properties' && node.name.text() == 'idVerification' && node.scopeName.text() == 'AGOV-AccountStatus,mustRecover'}?.value?.text()
if (!result) { if (!result) {
// fallback if not explicitly set // fallback if not explicitly set
def currentLoaRole = getUserAGOVLoiRoles()?.sort()?.last() ?: 'level100' def currentLoaRole = getUserAGOVLoiRoles()?.sort()?.last() ?: 'level100'
def chDomicile = list.country.text() == 'ch' def chDomicile = list.country.text() == 'ch'
def lastIdVerification = list.'**'.find {node -> node.name() == 'properties' && node.name.text() == 'idVerification' && node.scopeName.text() == 'AGOV-Loi,' + currentLoaRole}?.value?.text() def lastIdVerification = list.'**'.find {node -> node.name() == 'properties' && node.name.text() == 'idVerification' && node.scopeName.text() == 'AGOV-Loi,' + currentLoaRole}?.value?.text()
switch (currentLoaRole) { switch (currentLoaRole) {
case 'level100': case 'level100':
result = chDomicile ? 'SimpleLetter' : 'Video' result = chDomicile ? 'SimpleLetter' : 'Video'
break break
case 'level200': case 'level200':
result = chDomicile ? 'Bmid' : 'Video' result = chDomicile ? 'Bmid' : 'Video'
break break
case 'level300': case 'level300':
case 'level400': case 'level400':
result = chDomicile ? lastIdVerification : 'Video' result = chDomicile ? lastIdVerification : 'Video'
break break
default: default:
LOG.warn("unexpected loa on account: ${currentLoaRole}") LOG.warn("unexpected loa on account: ${currentLoaRole}")
// safest default, should work in any case // safest default, should work in any case
result = 'Video' result = 'Video'
} }
LOG.warn("Recovery method not set, choosing ${result} (based on currentLoad: ${currentLoaRole}, CH-domicile: ${chDomicile}, last verification method: ${lastIdVerification})") LOG.warn("Recovery method not set, choosing ${result} (based on currentLoad: ${currentLoaRole}, CH-domicile: ${chDomicile}, last verification method: ${lastIdVerification})")
} }
return result return result
} }
def getAqLevelBasedOnIdVerificationForRecovery(idVerification, highestRoleLevelNumber) { def getAqLevelBasedOnIdVerificationForRecovery(idVerification, highestRoleLevelNumber) {
def result = 'urn:qa.agov.ch:names:tc:ac:classes:' def result = 'urn:qa.agov.ch:names:tc:ac:classes:'
switch (idVerification) { switch (idVerification) {
case 'None': case 'None':
result = result.concat('100') result = result.concat('100')
break break
case 'SimpleLetter': case 'SimpleLetter':
result = result.concat('200') result = result.concat('200')
break break
case 'Video': case 'Video':
case 'VideoSelfPaid': case 'VideoSelfPaid':
case 'Bmid': case 'Bmid':
case 'BmidSelfPaid': case 'BmidSelfPaid':
case 'Counter': case 'Counter':
result = result.concat((highestRoleLevelNumber == 400) ? '400' : '300') result = result.concat((highestRoleLevelNumber == 400) ? '400' : '300')
break break
default: case 'Eid':
LOG.warn("unexpected idVerification for recovery on account: ${idVerification}") result = result.concat('400')
// safest default, should work in any case break
result = result.concat('' + highestRoleLevelNumber) default:
} LOG.warn("unexpected idVerification for recovery on account: ${idVerification}")
// safest default, should work in any case
return result result = result.concat('' + highestRoleLevelNumber)
} }
def getUserMustRecoverValidFrom() { return result
// set attibutes from DTO: -> validFrom }
def payload = new XmlSlurper().parseText(session.get('ch.adnovum.nevisidm.userDto'))
def authzNode = payload.'**'.find {node -> node.name() == 'authorizations' && node.role.name.text() == 'mustRecover'} def getUserMustRecoverValidFrom() {
return (authzNode) ? ((authzNode.validFrom && !authzNode.validFrom.text().isEmpty()) ? authzNode.validFrom?.text() : authzNode.ctlCreDat?.text()) : '' // set attibutes from DTO: -> validFrom
} def payload = new XmlSlurper().parseText(session.get('ch.adnovum.nevisidm.userDto'))
def authzNode = payload.'**'.find {node -> node.name() == 'authorizations' && node.role.name.text() == 'mustRecover'}
// Accounting return (authzNode) ? ((authzNode.validFrom && !authzNode.validFrom.text().isEmpty()) ? authzNode.validFrom?.text() : authzNode.ctlCreDat?.text()) : ''
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' // Accounting
def user = session['ch.adnovum.nevisidm.user.extId'] ?: 'unknown' def requester = session['ch.nevis.auth.saml.request.scoping.requesterId'] ?: 'unknown'
def credentialType = session['authenticatedWith'] ?: 'unknown' def requestId = session['ch.nevis.auth.saml.request.id'] ?: 'unknown'
def sourceIp = request.getLoginContext()['connection.HttpHeader.X-Real-IP'] ?: 'unknown' def requestedAq = session['agov.requestedRoleLevel'] ?: 'unknown'
def userAgent = request.getLoginContext()['connection.HttpHeader.user-agent'] ?: request.getLoginContext()['connection.HttpHeader.User-Agent'] ?: 'unknown' def user = session['ch.adnovum.nevisidm.user.extId'] ?: 'unknown'
def credentialType = session['authenticatedWith'] ?: 'unknown'
try { def sourceIp = request.getLoginContext()['connection.HttpHeader.X-Real-IP'] ?: 'unknown'
// beef def userAgent = request.getLoginContext()['connection.HttpHeader.user-agent'] ?: request.getLoginContext()['connection.HttpHeader.User-Agent'] ?: 'unknown'
def session = request.getAuthSession(true)
def highestRoleLevelNumber = 0 try {
def requestedRoleLevelNumber = session.get('agov.requestedRoleLevel').toInteger() // beef
def adressVerificationList = getUserAGOVLoiIdVerification('200') def s = request.getAuthSession(true)
def adressVerification = 'None' def highestRoleLevelNumber = 0
if (adressVerificationList && !adressVerificationList.isEmpty()) {
adressVerification = adressVerificationList[0] if (!session.get('agov.requestedRoleLevel')) {
} LOG.error("IDP: internal error: agov.requestedRoleLevel not set in session")
def authenticationMethod = session.get('authenticatedWith') response.setResult('error');
return
LOG.debug('CheckLoa: Requested role level '+ requestedRoleLevelNumber) }
LOG.debug('CheckLoa: idVerification: ' + getUserAGOVLoiIdVerification()) def requestedRoleLevelNumber = session.get('agov.requestedRoleLevel').toInteger()
LOG.debug('CheckLoa: adressVerification : ' + adressVerification)
def authenticationMethod = session.get('authenticatedWith')
def idVerificationMethodList = getUserAGOVLoiIdVerification() if (!authenticationMethod) {
LOG.error("IDP: internal error: authenticationMethod not set in session")
session.setAttribute('idVerification', idVerificationMethodList.isEmpty() ? 'None' : idVerificationMethodList.last()) response.setResult('error');
session.setAttribute('agov.adressVerification', '' + adressVerification) return
}
if (requestedRoleLevelNumber == 0) { // data transformations needed for SAML and OIDC
// AuthnFailed_Zero_RoleLvl // Transform sex to number
response.setResult('error'); if(session.get('ch.nevis.idm.User.gender') == 'MALE'){
return s.setAttribute('ch.nevis.idm.User.gender', '1')
} }
if(session.get('ch.nevis.idm.User.gender') == 'FEMALE'){
if (session.get('ch.adnovum.nevisidm.profileExtId') == '') { s.setAttribute('ch.nevis.idm.User.gender', '2')
LOG.error("Event='DATAERROR', Requester='${requester}', RequestId='${requestId}', RequestedAq=${requestedAq}, User=${user}, CredentialType='${credentialType}', errorMessage='Account without Profile', SourceIp=${sourceIp}, UserAgent='${userAgent}'") }
if(s.get('ch.nevis.idm.User.gender') == 'OTHER'){
session.setAttribute('contextClassRefToSet', 'urn:qa.agov.ch:names:tc:ac:classes:100') session.setAttribute('ch.nevis.idm.User.gender', '3')
}
// if the account has no profile, we must not return address or svnr
session.setAttribute('agov.appAddressRequired', 'false')
session.setAttribute('agov.appSvnrAllowed', 'false') // handle accounts qa attributes, and set them in session
// account itself, only needed if not authenticated with e-ID
response.setResult('ok') if (!'urn:qa.agov.ch:names:tc:authfactor:eid'.equalsIgnoreCase(authenticationMethod)) {
return def idVerificationList = getUserAGOVLoiIdVerification()
} def idVerification = 'None'
if (idVerificationList && !idVerificationList.isEmpty()) {
// Transform sex to number idVerification = idVerificationList.last()
if(session.get('ch.nevis.idm.User.gender') == 'MALE'){ }
session.setAttribute('ch.nevis.idm.User.gender', '1') s.setAttribute('idVerification', idVerification)
}
if(session.get('ch.nevis.idm.User.gender') == 'FEMALE'){ // contextClassRefToSet based on highest level-role assigned to default profile
session.setAttribute('ch.nevis.idm.User.gender', '2') for (String role : getUserAGOVLoiRoles()) {
} if (role.startsWith('level')) {
if(session.get('ch.nevis.idm.User.gender') == 'OTHER'){ def roleLevel = role.substring(5)
session.setAttribute('ch.nevis.idm.User.gender', '3') int roleLevelNumber = Integer.parseInt(roleLevel)
}
if (highestRoleLevelNumber< roleLevelNumber) {
for (String role : getUserAGOVLoiRoles()) { highestRoleLevelNumber=roleLevelNumber
if (role.startsWith('level')) { }
def roleLevel = role.substring(5) }
int roleLevelNumber = Integer.parseInt(roleLevel) }
if (highestRoleLevelNumber< roleLevelNumber) { LOG.debug('CheckLoa: Highest role Level ' + highestRoleLevelNumber.toString() +' contextclassref ' + requestedRoleLevelNumber.toString())
highestRoleLevelNumber=roleLevelNumber LOG.debug('CheckLoa: Compare ' + (highestRoleLevelNumber>=requestedRoleLevelNumber))
}
} //set attribute Actual Role Level
} s.setAttribute('agov.actualRoleLevel', '' + highestRoleLevelNumber)
LOG.debug('CheckLoa: actual role level (agov) '+ highestRoleLevelNumber)
LOG.debug('CheckLoa: Highest role Level ' + highestRoleLevelNumber.toString() +' contextclassref ' + requestedRoleLevelNumber.toString())
LOG.debug('CheckLoa: Compare ' + (highestRoleLevelNumber>=requestedRoleLevelNumber)) // set attribute ValidFrom and ValidTo (only for higher than 100)
if (highestRoleLevelNumber > 100) {
//set attribute Actual Role Level def validFrom = getUserAGOVLoiValidFrom('level'.concat(highestRoleLevelNumber.toString()))
session.setAttribute('agov.actualRoleLevel', '' + highestRoleLevelNumber) def validTo = getUserAGOVLoiValidTo('level'.concat(highestRoleLevelNumber.toString()))
LOG.debug('CheckLoa: actual role level (agov) '+ highestRoleLevelNumber)
LOG.debug('CheckLoa: ValidFrom :' + validFrom)
if (highestRoleLevelNumber > 0) { LOG.debug('CheckLoa: ValidTo :' + validTo)
// set attribute contextClassRefToSet
session.setAttribute('contextClassRefToSet','urn:qa.agov.ch:names:tc:ac:classes:' .concat(highestRoleLevelNumber.toString())) if(validFrom != '') {
} else { s.setAttribute('ValidFrom', '' + validFrom)
// by default 100 }
session.setAttribute('contextClassRefToSet','urn:qa.agov.ch:names:tc:ac:classes:100' ) if(validTo != '') {
} s.setAttribute('ValidTo', '' + validTo)
}
// no login for users with a recovery role }
def recoveryRoleList = getUserAGOVRecoveryRoles() if (highestRoleLevelNumber > 0) {
// set attribute contextClassRefToSet
if (recoveryRoleList.contains('mustRecover')) { s.setAttribute('contextClassRefToSet','urn:qa.agov.ch:names:tc:ac:classes:' .concat(highestRoleLevelNumber.toString()))
session.setAttribute('agov.recovery.authnContextClassRef', 'urn:qa.agov.ch:names:tc:ac:classes:mustRecover') } else {
session.setAttribute('agov.recovery.authenticatedWith', session.getAttribute('authenticatedWith') ?: 'unknown' ) // by default 100
s.setAttribute('contextClassRefToSet','urn:qa.agov.ch:names:tc:ac:classes:100' )
def origIdVerification = getUserAGOVLoiIdVerification(highestRoleLevelNumber.toString()) ?: 'None' }
def idVerification = getUserIdVerificationForRecovery() ?: origIdVerification }
session.setAttribute('agov.recovery.currentIdVerification', '' + idVerification ) // address related, needed in any case (also e-ID)
def adressVerificationList = getUserAGOVLoiIdVerification('200')
// align currentAgovAq with the method selected for idVerification def adressVerification = 'None'
def currentAgovAqForRecovery = getAqLevelBasedOnIdVerificationForRecovery(idVerification, highestRoleLevelNumber) if (adressVerificationList && !adressVerificationList.isEmpty()) {
session.setAttribute('agov.recovery.currentAgovAq', '' + currentAgovAqForRecovery) adressVerification = adressVerificationList[0]
}
def validFrom = getUserMustRecoverValidFrom() ?: '' s.setAttribute('agov.adressVerification', '' + adressVerification)
session.setAttribute('agov.recovery.currentAgovAqRoleValidFrom', '' + validFrom )
if (!session.get('ch.adnovum.nevisidm.profileExtId')) {
LOG.debug("CheckLoa: mustRecover: origIdVerification=${origIdVerification}, idVerification=${idVerification}, currentAgovAqForRecovery=${currentAgovAqForRecovery}") LOG.error("Event='DATAERROR', Requester='${requester}', RequestId='${requestId}', RequestedAq=${requestedAq}, User=${user}, CredentialType='${credentialType}', errorMessage='Account without Profile', SourceIp=${sourceIp}, UserAgent='${userAgent}'")
response.setResult('exit.2') // if the account has no profile, we must not return address or svnr
return s.setAttribute('agov.appAddressRequired', 'false')
s.setAttribute('agov.appSvnrAllowed', 'false')
} else if (recoveryRoleList.contains('recovery')) {
if (recoveryRoleList.contains('recoveryCascade')) { response.setResult('ok')
session.setAttribute('agov.recovery.authnContextClassRef', 'urn:qa.agov.ch:names:tc:ac:classes:recoveryCascade') return
} else { }
session.setAttribute('agov.recovery.authnContextClassRef', 'urn:qa.agov.ch:names:tc:ac:classes:recovery')
} // no login for users with a recovery role (but onyl when not logging in with e-Id)
session.setAttribute('agov.recovery.authenticatedWith', session.getAttribute('authenticatedWith') ?: 'unknown') // TODO/haburger/2025-07-01: automatic recovery if logging in with e-Id
session.setAttribute('agov.recovery.currentAgovAq', session.getAttribute('contextClassRefToSet') ?: 'urn:qa.agov.ch:names:tc:ac:classes:100' ) if (!'urn:qa.agov.ch:names:tc:authfactor:eid'.equalsIgnoreCase(authenticationMethod)) {
LOG.debug('CheckLoa: idVerification2= '+ getUserAGOVLoiIdVerification(highestRoleLevelNumber.toString())) // no login for users with a recovery role
def idVerification = getUserAGOVLoiIdVerification(highestRoleLevelNumber.toString()) def recoveryRoleList = getUserAGOVRecoveryRoles()
session.setAttribute('agov.recovery.currentIdVerification', (idVerification.isEmpty() ? 'None' : idVerification.first()))
def validFrom = getUserAGOVLoiValidFrom('level'.concat(highestRoleLevelNumber.toString())) ?: '' if (recoveryRoleList.contains('mustRecover')) {
session.setAttribute('agov.recovery.currentAgovAqRoleValidFrom', validFrom) s.setAttribute('agov.recovery.authnContextClassRef', 'urn:qa.agov.ch:names:tc:ac:classes:mustRecover')
s.setAttribute('agov.recovery.authenticatedWith', session.getAttribute('authenticatedWith') ?: 'unknown' )
response.setResult('exit.2')
return def origIdVerification = getUserAGOVLoiIdVerification(highestRoleLevelNumber.toString()) ?: 'None'
} def idVerification = getUserIdVerificationForRecovery() ?: origIdVerification
s.setAttribute('agov.recovery.currentIdVerification', '' + idVerification )
if (highestRoleLevelNumber>=requestedRoleLevelNumber) { // align currentAgovAq with the method selected for idVerification
def currentAgovAqForRecovery = getAqLevelBasedOnIdVerificationForRecovery(idVerification, highestRoleLevelNumber)
// set attribute ValidFrom and ValidTo (only for higher than 100) s.setAttribute('agov.recovery.currentAgovAq', '' + currentAgovAqForRecovery)
if (highestRoleLevelNumber > 100) {
def validFrom = getUserAGOVLoiValidFrom('level'.concat(highestRoleLevelNumber.toString())) def validFrom = getUserMustRecoverValidFrom() ?: ''
def validTo = getUserAGOVLoiValidTo('level'.concat(highestRoleLevelNumber.toString())) s.setAttribute('agov.recovery.currentAgovAqRoleValidFrom', '' + validFrom )
LOG.debug('CheckLoa: ValidFrom :' + validFrom) LOG.debug("CheckLoa: mustRecover: origIdVerification=${origIdVerification}, idVerification=${idVerification}, currentAgovAqForRecovery=${currentAgovAqForRecovery}")
LOG.debug('CheckLoa: ValidTo :' + validTo)
response.setResult('exit.2')
if(validFrom != '') { return
session.setAttribute('ValidFrom', '' + validFrom)
} } else if (recoveryRoleList.contains('recovery')) {
if(validTo != '') { if (recoveryRoleList.contains('recoveryCascade')) {
session.setAttribute('ValidTo', '' + validTo) s.setAttribute('agov.recovery.authnContextClassRef', 'urn:qa.agov.ch:names:tc:ac:classes:recoveryCascade')
} } else {
} s.setAttribute('agov.recovery.authnContextClassRef', 'urn:qa.agov.ch:names:tc:ac:classes:recovery')
response.setResult('ok') }
return; s.setAttribute('agov.recovery.authenticatedWith', session.getAttribute('authenticatedWith') ?: 'unknown')
} else { s.setAttribute('agov.recovery.currentAgovAq', session.getAttribute('contextClassRefToSet') ?: 'urn:qa.agov.ch:names:tc:ac:classes:100' )
// Insufficient_LoaInfo LOG.debug('CheckLoa: idVerification2= '+ getUserAGOVLoiIdVerification(highestRoleLevelNumber.toString()))
response.setResult('exit.1'); def idVerification = getUserAGOVLoiIdVerification(highestRoleLevelNumber.toString())
return; s.setAttribute('agov.recovery.currentIdVerification', (idVerification.isEmpty() ? 'None' : idVerification.first()))
} def validFrom = getUserAGOVLoiValidFrom('level'.concat(highestRoleLevelNumber.toString())) ?: ''
} catch (Exception ex) { s.setAttribute('agov.recovery.currentAgovAqRoleValidFrom', validFrom)
LOG.error("Event='DATAERROR', Requester='${requester}', RequestId='${requestId}', RequestedAq=${requestedAq}, User=${user}, CredentialType='${credentialType}', errorMessage='exception occured: ${ex}', SourceIp=${sourceIp}, UserAgent='${userAgent}'")
ex = StackTraceUtils.sanitize(ex) response.setResult('exit.2')
def affectedLines = ex.stackTrace.findAll { it.className.startsWith('Script') }.collect { "${it.methodName}:${it.lineNumber}" } return
LOG.error("FATAL: Script failure (at lines: ${affectedLines})", ex) }
// AuthnFailed_Zero_RoleLvl } else {
response.setResult('error'); // authenticated with e-ID, we adjust highestRoleLevelNumber to e-ID login
return; highestRoleLevelNumber = 500
} s.setAttribute('agov.actualRoleLevel', '' + highestRoleLevelNumber)
LOG.debug('CheckLoa: actual role level (agov) '+ highestRoleLevelNumber)
}
// verifiy that AQ level is high enough
if (highestRoleLevelNumber>=requestedRoleLevelNumber) {
response.setResult('ok')
return;
} else {
// Insufficient_LoaInfo
response.setResult('exit.1');
return;
}
} catch (Exception ex) {
LOG.error("Event='DATAERROR', Requester='${requester}', RequestId='${requestId}', RequestedAq=${requestedAq}, User=${user}, CredentialType='${credentialType}', errorMessage='exception occured: ${ex}', SourceIp=${sourceIp}, UserAgent='${userAgent}'")
ex = StackTraceUtils.sanitize(ex)
def affectedLines = ex.stackTrace.findAll { it.className.startsWith('Script') }.collect { "${it.methodName}:${it.lineNumber}" }
LOG.error("FATAL: Script failure (at lines: ${affectedLines})", ex)
// AuthnFailed_Zero_RoleLvl
response.setResult('error');
return;
}

View File

@ -27,6 +27,7 @@ def compareAndUpdateSessionVariables(sess, keys, isProperty){
return updatedKeys return updatedKeys
} }
// TODO/haburger/2025-07-01: we should also set the verificationMethod, etc. of the level400 role
String user_update_dto_template = ''' String user_update_dto_template = '''
{ {
"name": { "name": {

View File

@ -33,31 +33,6 @@ def clearEidSession(){
s.removeAttribute('agov.eid.User.origin') s.removeAttribute('agov.eid.User.origin')
} }
def updateLoginHistory(idmRestClient, userExtId, credentialExtId) {
try {
def baseUrl = parameters.get("baseUrl")
def clientExtId = parameters.get("clientExtId")
def endpoint = "$baseUrl/api/core/v1/$clientExtId/users/$userExtId/login-info"
def dto = "{\"success\": true,\"credentialExtId\": \"${credentialExtId}\"}"
def postRequest = new HTTPRequestWrapper()
postRequest.addToHeaders('Content-Type', ['application/json'])
postRequest.setPayLoad(dto.getBytes('UTF-8'))
postRequest.setPayLoad(dto.getBytes('UTF-8'))
def result = idmRestClient.postWithResponse(endpoint, postRequest)
if (result.getStatusCode() != 200) {
// best effort, we log only
// TODO/haburger/2025-06-24: context parameters are missing here (also in getAccounts)
LOG.warn("Event='DATAERROR', Requester='${requester}', RequestId='${requestId}', RequestedAq=${requestedAq}, User=${userExtId}, CredentialType='E-ID Link', SourceIp=${sourceIp}, UserAgent='${userAgent}', reason='failed to update login history for credential ${credentialExtId} (http status: ${result.getStatusCode()})'")
}
} catch (Exception e) {
// best effort, we log only
// TODO/haburger/2025-06-24: context parameters are missing here (also in getAccounts)
LOG.warn("Event='DATAERROR', Requester='${requester}', RequestId='${requestId}', RequestedAq=${requestedAq}, User=${userExtId}, CredentialType='E-ID Link', SourceIp=${sourceIp}, UserAgent='${userAgent}', reason='failed to update login history for credential ${credentialExtId} (${e})'")
}
}
def getAccounts(json, String svnr) { def getAccounts(json, String svnr) {
def idm_users_dto = json["Resources"] def idm_users_dto = json["Resources"]
def accounts = [:] def accounts = [:]
@ -126,9 +101,6 @@ if(inargs['submit'] && inargs['login'] && inargs['login'] != ''){
sess.setAttribute('agov.eid.linkingCredentialExtId', account["credentialExtId"]) sess.setAttribute('agov.eid.linkingCredentialExtId', account["credentialExtId"])
sess.setAttribute('agov.eid.linkedAccountExtId', account["extId"]) sess.setAttribute('agov.eid.linkedAccountExtId', account["extId"])
// update login history
updateLoginHistory(idmRestClient, account["extId"], account["credentialExtId"])
if(account["firstLogin"]){ if(account["firstLogin"]){
response.setResult('firstLogin') response.setResult('firstLogin')
return return
@ -206,9 +178,6 @@ try {
sess.setAttribute('agov.eid.linkingCredentialExtId', account["credentialExtId"]) sess.setAttribute('agov.eid.linkingCredentialExtId', account["credentialExtId"])
sess.setAttribute('agov.eid.linkedAccountExtId', account["extId"]) sess.setAttribute('agov.eid.linkedAccountExtId', account["extId"])
// update login history
updateLoginHistory(idmRestClient, account["extId"], account["credentialExtId"])
if(account["firstLogin"]){ if(account["firstLogin"]){
response.setResult('firstLogin') response.setResult('firstLogin')
return return

View File

@ -5,6 +5,7 @@ pattern:
name: "Ask_Mobile_Number" name: "Ask_Mobile_Number"
properties: properties:
authStatesFile: "res://6d83506dfcc430c12d81dfa3#authStatesFile" authStatesFile: "res://6d83506dfcc430c12d81dfa3#authStatesFile"
parameters: "var://ask_mobile_number-template-parameters"
onSuccess: onSuccess:
- "pattern://2cdd910036aa06b102863a4f" - "pattern://2cdd910036aa06b102863a4f"
onFailure: onFailure:

View File

@ -8,7 +8,7 @@ pattern:
properties: properties:
authStatesFile: "res://306ce091fd87bad6174d9e8b#authStatesFile" authStatesFile: "res://306ce091fd87bad6174d9e8b#authStatesFile"
onSuccess: onSuccess:
- "pattern://b87d0d2b640e8e545ad70234" - "pattern://2cdd910036aa06b102863a4f"
onFailure: onFailure:
- "pattern://4c65de021d362462324a3a5f" - "pattern://4c65de021d362462324a3a5f"
nextSteps: nextSteps:

View File

@ -7,6 +7,13 @@ variables:
maxAllowed: 1 maxAllowed: 1
value: ".agov-d.azure.adnovum.net" value: ".agov-d.azure.adnovum.net"
requireOverloading: true requireOverloading: true
ask_mobile_number-template-parameters:
className: "ch.nevis.admin.v4.plugin.base.generation.property.TextProperty"
parameters:
required: false
syntax: "YAML"
value: null
requireOverloading: true
auth-session-store-database-host: auth-session-store-database-host:
className: "ch.nevis.admin.v4.plugin.base.generation.property.HostPortProperty" className: "ch.nevis.admin.v4.plugin.base.generation.property.HostPortProperty"
parameters: parameters: