115 files added

This commit is contained in:
haburger 2024-08-21 10:52:51 +00:00
commit 4243be829d
194 changed files with 6783 additions and 0 deletions

12
bundles.yml Normal file
View File

@ -0,0 +1,12 @@
schemaVersion: "1.0"
bundles:
- "nevisadmin-plugin-base-generation:7.2402.1.3"
- "nevisadmin-plugin-oauth:7.2402.1.3"
- "nevisadmin-plugin-nevisdetect:7.2402.1.3"
- "nevisadmin-plugin-nevisauth:7.2402.1.3"
- "nevisadmin-plugin-nevisdp:7.2402.1.3"
- "nevisadmin-plugin-nevisproxy:7.2402.1.3"
- "nevisadmin-plugin-mobile-auth:7.2402.1.3"
- "nevisadmin-plugin-nevisidm:7.2402.1.3"
- "nevisadmin-plugin-fido2:7.2402.1.3"
- "nevisadmin-plugin-authcloud:7.2402.1.3"

View File

@ -0,0 +1,21 @@
<AuthState name="${state.entry}" class="ch.nevis.idm.authstate.IdmUserVerifyState" final="false" resumeState="false">
<ResultCond name="prospect" next="${state.done}"/>
<!-- Security issue : goes to next state if client not found -->
<ResultCond name="default" next="${state.failed}"/>
<ResultCond name="failed" next="${state.failed}"/>
<ResultCond name="clientNotFound" next="${state.failed}"/>
<Response value="AUTH_ERROR">
<Gui name="internal_error">
<GuiElem name="transferId" type="hidden" value="${request:traceId}" optional="true"/>
</Gui>
</Response>
<propertyRef name="nevisIDM_Connector"/>
<property name="user.loginId" value="${inargs:email}"/>
<property name="user.loginType" value="EMAIL"/>
<property name="client.name" value="${param.client.name}"/>
<property name="presetNoteValues" value="false"/>
<property name="detaillevel.profile" value="HIGH"/>
<property name="detaillevel.role" value="HIGH"/>
<property name="detaillevel.authorization" value="HIGH"/>
<property name="detaillevel.dataroom" value="HIGH"/>
</AuthState>

View File

@ -0,0 +1,85 @@
#################### general parameters ####################
# parameter for providing the resource path (see application.webdata.path)
application.webdata.pathparam=logrendresourcepath
# default value for the resource path (nevisProxy needs a forward connector with MappingType=pathinfo and URIPrefix=/login/resources)
application.webdata.pathparam.default=/login/resources
# path to webdata directory for http requests (relative to context)
# isiweb must redirect all requests within this path to logrend (used for images, css, js)
# {0} is replaced with the query parameter defined via "application.webdata.pathparam" (to be provided by isiweb)
# {1} is replaced with the context path of the webapp (without the leading /)
# {2} is replaced by a slash if both {0} and {1} are not empty
# a tailing slash is added if the resulting path is not empty
application.webdata.path={0}{2}{1}
# obfuscate urls
application.url.obfuscate=no
# charset of incoming GuiDescriptor XML (default: <file.encoding> - default for -Dfile.encoding is ISO-8859-1)
application.input.charset=UTF-8
# HTML-encode any inputs before putting them in the output
# (*only* set to "no" if isiweb already encodes the user inputs)
application.inputs.htmlencode=yes
# HTML-encode any GUI element values before putting them in the output - velocity only (default: no, for backwards compatibility)
#application.inputs.htmlencode.guielems=yes
# content type (default: text/html; charset=<file.encoding> - default for file.encoding is ISO-8859-1)
application.render.content.type=text/html; charset=UTF-8
# name of the package
application.package.name=nevislogrend
# name of the current login application (if not empty, this is always used)
application.loginapp.current=
# name of the default login application (used if login application is not defined by other means)
application.loginapp.default=${default-app}
# allow overriding the login application via a configurable header
# the resulting login application name is _<header><headerValue> for historical reasons
application.loginapp.override=header:channel
# time in minutes after which a cache-entry should be checked against the source (0 to turn revalidation off completely)
cache.revalidate.delay=-1
# read cached data from files (file) or from classloader (class)
# if both are given, try to find the file in the given order
cache.source=file
# tags for variable substitution in GUI labels
keytag.start=${
keytag.end=}
# perform litdict mapping on GUI labels
application.gui.litdict=yes
# perform ${bean.<name>} variable substitution on GUI labels
application.gui.substitution=yes
# allow extraction of loginapplicationid from path info (first part until /):
application.accept.loginapplicationidfromuri=no
# Container related configuration
server.name=default
# The protocol that can be used to connect to this instance
# Allowed values: http, https
server.protocol=${protocol}
# the hostname on which the component listener will be installed
server.host=${host}
# the port on which the components listener binds to.
server.port=${port}
# the path section contains all the items lying around in the filesystem
# this points to the instance's configuration folder
path.config=/var/opt/nevislogrend/${instance}/conf
path.instance=/var/opt/nevislogrend/${instance}
${keystore}${keystore-password}${truststore}${truststore-password}
management.healthchecks.enabled=${healthchecks}

View File

@ -0,0 +1,10 @@
<AuthState name="${state.entry}" class="ch.nevis.esauth.auth.states.scripting.ScriptState" final="true" resumeState="true">
<ResultCond name="ok" next="${state.done}"/>
<Response value="AUTH_CONTINUE">
<Gui name="recovery_on_going" label="not.used.label">
<GuiElem name="authRequestId" type="hidden" label="not.used.label" value="${sess:ch.nevis.auth.saml.request.id}" optional="true"/>
<GuiElem name="recovery" type="button" label="not.used" value="recovery" optional="true"/>
</Gui>
</Response>
<property name="script" value="file:///var/opt/nevisauth/default/conf/recovery_ongoing.groovy"/>
</AuthState>

View File

@ -0,0 +1,4 @@
if (inargs['recovery'] != null && inargs['recovery'] == 'recovery' ) {
response.setResult('ok')
return
}

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,11 @@
<AuthState name="${state.entry}" class="ch.nevis.esauth.auth.states.scripting.ScriptState" final="false" resumeState="true">
<ResultCond name="ok" next="${state.done}"/>
<ResultCond name="error" next="${state.failed}"/>
<ResultCond name="notFullyRegistered" next="${state.exit.1}"/>
<Response value="AUTH_CONTINUE">
<Gui name="NoGui">
</Gui>
</Response>
<property name="scriptTraceGroup" value="Recovery"/>
<property name="script" value="file:///var/opt/nevisauth/default/conf/recovery-checkAccount.groovy"/>
</AuthState>

View File

@ -0,0 +1,79 @@
import ch.nevis.esauth.auth.engine.AuthResponse
import groovy.xml.XmlSlurper
// AGOVaq conversion
def maxLoiRoleToCtxClssConvertorMap = [
"level100": "urn:qa.agov.ch:names:tc:ac:classes:100",
"level200": "urn:qa.agov.ch:names:tc:ac:classes:200",
"level300": "urn:qa.agov.ch:names:tc:ac:classes:300",
"level400": "urn:qa.agov.ch:names:tc:ac:classes:400",
"level500": "urn:qa.agov.ch:names:tc:ac:classes:500"
]
def cleanSession() {
def s = request.getAuthSession(true)
s.removeAttribute('agov.op.onboarding.ctxClass')
s.removeAttribute('agov.op.onboarding.minLoi')
s.removeAttribute('agov.op.onboarding.homeName')
s.removeAttribute('agov.op.onboarding.subject')
s.removeAttribute('agov.op.onboarding.process.state')
s.removeAttribute('ch.adnovum.nevisidm.userDto')
s.removeAttribute('saml.response.statusCode')
if (response.getActualRoles().length > 0) {
def actualRoles = Arrays.copyOf(response.getActualRoles(), response.getActualRoles().length)
actualRoles.each{ role -> response.removeActualRole(role) }
}
}
// for autditing
def user = session['ch.adnovum.nevisidm.user.extId'] ?: 'unknown'
def sourceIp = request.getLoginContext()['connection.HttpHeader.X-Real-IP'] ?: 'unknown'
def userAgent = request.getLoginContext()['connection.HttpHeader.user-agent'] ?: 'unknown'
def maxLoi = 'unknown'
// new
if (session['ch.adnovum.nevisidm.userDto'] != null && notes['lasterror'] == null) {
try {
def userDto = new XmlSlurper().parseText(session['ch.adnovum.nevisidm.userDto'])
def userState = userDto.state
LOG.debug("Recovery: Dto is '${userDto}")
LOG.debug("Recovery: state is '${userState}")
if (userState == 'ACTIVE') {
def maxLoiList = userDto.'**'.findAll { node -> node.name() == 'roles' && node.applicationName.text() == 'AGOV-Loi' }.collect({ node -> node.name.text() })
maxLoi = (maxLoiList == null || maxLoiList.isEmpty()) ? null : maxLoiList.sort().last()
def accountStatusRoles = userDto.'**'.findAll { node -> node.name() == 'roles' && node.applicationName.text() == 'AGOV-AccountStatus' }.collect({ node -> node.name.text() })
def hasRecoveryRole = accountStatusRoles.isEmpty() ? null : accountStatusRoles.sort().first()
LOG.debug("Recovery: MaxLoi is '${maxLoi}'")
LOG.debug("Recovery: hasRecoveryRole is '${hasRecoveryRole}'")
if (maxLoi != null && maxLoiRoleToCtxClssConvertorMap.containsKey(maxLoi)) {
response.setResult('ok')
return
} else {
LOG.debug("Recovery: no 'AGOV-Loi'-role assigned to user ${user}")
response.setResult('notFullyRegistered')
return
}
} else {
// state != ACTIVE and no lasterror should not happen
LOG.error("Recovery: state='${userState}' but not lasterror set")
response.setNote('lasterror', '9909')
response.setNote('lasterrorinfo', 'internal error')
response.setResult('error')
return
}
} catch (Exception e) {
LOG.error("Recovery processing failed: Exception " + e)
response.setNote('lasterror', '9909')
response.setNote('lasterrorinfo', 'internal error')
response.setResult('error')
return
}
}
response.setResult('error')
return
// new

View File

@ -0,0 +1,257 @@
import org.codehaus.groovy.runtime.StackTraceUtils
import groovy.xml.XmlSlurper
def getUserAGOVLoiRoles() {
// set attibutes from DTO: -> AGOVaq
def list = new XmlSlurper().parseText(session.get('ch.adnovum.nevisidm.userDto'))
return list.'**'.findAll { node -> node.name() == 'roles' && node.applicationName.text() == 'AGOV-Loi' }.collect({ node -> node.name.text() })
}
def getUserAGOVRecoveryRoles() {
// set attibutes from DTO: -> AGOV
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() })
}
def getUserAGOVLoiIdVerification() {
// set attibutes from DTO: -> idVerification
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()})
}
def getUserAGOVLoiIdVerification(level) {
// set attibutes from DTO: -> idVerification
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()})
}
def getUserAGOVLoiValidFrom(level) {
// set attibutes from DTO: -> validFrom
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()
}
def getUserAGOVLoiValidTo(level) {
// set attibutes from DTO: -> validTo
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()
}
def getUserIdVerificationForRecovery() {
// application is AGOV-AccountStatus
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()
if (!result) {
// fallback if not explicitly set
def currentLoaRole = getUserAGOVLoiRoles()?.sort()?.last() ?: 'level100'
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()
switch (currentLoaRole) {
case 'level100':
result = chDomicile ? 'SimpleLetter' : 'Video'
break
case 'level200':
result = chDomicile ? 'Bmid' : 'Video'
break
case 'level300':
case 'level400':
result = chDomicile ? lastIdVerification : 'Video'
break
default:
LOG.warn("unexpected loa on account: ${currentLoaRole}")
// safest default, should work in any case
result = 'Video'
}
LOG.warn("Recovery method not set, choosing ${result} (based on currentLoad: ${currentLoaRole}, CH-domicile: ${chDomicile}, last verification method: ${lastIdVerification})")
}
return result
}
def getAqLevelBasedOnIdVerificationForRecovery(idVerification, highestRoleLevelNumber) {
def result = 'urn:qa.agov.ch:names:tc:ac:classes:'
switch (idVerification) {
case 'None':
result = result.concat('100')
break
case 'SimpleLetter':
result = result.concat('200')
break
case 'Video':
case 'VideoSelfPaid':
case 'Bmid':
case 'BmidSelfPaid':
case 'Counter':
result = result.concat((highestRoleLevelNumber == 400) ? '400' : '300')
break
default:
LOG.warn("unexpected idVerification for recovery on account: ${idVerification}")
// safest default, should work in any case
result = result.concat('' + highestRoleLevelNumber)
}
return result
}
def getUserMustRecoverValidFrom() {
// 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'}
return (authzNode) ? ((authzNode.validFrom && !authzNode.validFrom.text().isEmpty()) ? authzNode.validFrom?.text() : authzNode.ctlCreDat?.text()) : ''
}
// 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'
try {
// beef
def session = request.getAuthSession(true)
def highestRoleLevelNumber = 0
def requestedRoleLevelNumber = session.get('agov.requestedRoleLevel').toInteger()
def adressVerificationList = getUserAGOVLoiIdVerification('200')
def adressVerification = 'None'
if (adressVerificationList && !adressVerificationList.isEmpty()) {
adressVerification = adressVerificationList[0]
}
LOG.debug('CheckLoa: Requested role level '+ requestedRoleLevelNumber)
LOG.debug('CheckLoa: idVerification: ' + getUserAGOVLoiIdVerification())
LOG.debug('CheckLoa: adressVerification : ' + adressVerification)
def idVerificationMethodList = getUserAGOVLoiIdVerification()
session.setAttribute('idVerification', idVerificationMethodList.isEmpty() ? 'None' : idVerificationMethodList.last())
session.setAttribute('agov.adressVerification', '' + adressVerification)
if (requestedRoleLevelNumber == 0) {
// AuthnFailed_Zero_RoleLvl
response.setResult('error');
return
}
if (session.get('ch.adnovum.nevisidm.profileExtId') == '') {
LOG.error("Event='DATAERROR', Requester='${requester}', RequestId='${requestId}', RequestedAq=${requestedAq}, User=${user}, CredentialType='${credentialType}', errorMessage='Account without Profile', SourceIp=${sourceIp}, UserAgent='${userAgent}'")
session.setAttribute('contextClassRefToSet', 'urn:qa.agov.ch:names:tc:ac:classes:100')
response.setResult('ok')
return
}
// Transform sex to number
if(session.get('ch.nevis.idm.User.gender') == 'MALE'){
session.setAttribute('ch.nevis.idm.User.gender', '1')
}
if(session.get('ch.nevis.idm.User.gender') == 'FEMALE'){
session.setAttribute('ch.nevis.idm.User.gender', '2')
}
if(session.get('ch.nevis.idm.User.gender') == 'OTHER'){
session.setAttribute('ch.nevis.idm.User.gender', '3')
}
for (String role : getUserAGOVLoiRoles()) {
if (role.startsWith('level')) {
def roleLevel = role.substring(5)
int roleLevelNumber = Integer.parseInt(roleLevel)
if (highestRoleLevelNumber == 0) {
highestRoleLevelNumber = roleLevelNumber
}
if (highestRoleLevelNumber< roleLevelNumber) {
highestRoleLevelNumber=roleLevelNumber
}
}
}
LOG.debug('CheckLoa: Highest role Level' + highestRoleLevelNumber.toString() +' contextclassref' + requestedRoleLevelNumber.toString())
LOG.debug('CheckLoa: Compare' + (highestRoleLevelNumber>=requestedRoleLevelNumber))
//set attribute Actual Role Level
session.setAttribute('agov.actualRoleLevel', '' + highestRoleLevelNumber)
LOG.debug('CheckLoa: actual role level (agov) '+ highestRoleLevelNumber)
if (highestRoleLevelNumber > 0) {
// set attribute contextClassRefToSet
session.setAttribute('contextClassRefToSet','urn:qa.agov.ch:names:tc:ac:classes:' .concat(highestRoleLevelNumber.toString()))
} else {
// by default 100
session.setAttribute('contextClassRefToSet','urn:qa.agov.ch:names:tc:ac:classes:100' )
}
// no login for users with a recovery role
for (String role : getUserAGOVRecoveryRoles()) {
if (role == 'mustRecover') {
session.setAttribute('agov.recovery.authnContextClassRef', 'urn:qa.agov.ch:names:tc:ac:classes:mustRecover')
session.setAttribute('agov.recovery.authenticatedWith', session.getAttribute('authenticatedWith') ?: 'unknown' )
def origIdVerification = getUserAGOVLoiIdVerification(highestRoleLevelNumber.toString()) ?: 'None'
def idVerification = getUserIdVerificationForRecovery() ?: origIdVerification
session.setAttribute('agov.recovery.currentIdVerification', '' + idVerification )
// align currentAgovAq with the method selected for idVerification
def currentAgovAqForRecovery = getAqLevelBasedOnIdVerificationForRecovery(idVerification, highestRoleLevelNumber)
session.setAttribute('agov.recovery.currentAgovAq', '' + currentAgovAqForRecovery)
def validFrom = getUserMustRecoverValidFrom() ?: ''
session.setAttribute('agov.recovery.currentAgovAqRoleValidFrom', '' + validFrom )
LOG.debug("CheckLoa: mustRecover: origIdVerification=${origIdVerification}, idVerification=${idVerification}, currentAgovAqForRecovery=${currentAgovAqForRecovery}")
response.setResult('exit.2')
return
} else if (role == 'recovery') {
session.setAttribute('agov.recovery.authnContextClassRef', 'urn:qa.agov.ch:names:tc:ac:classes:recovery')
session.setAttribute('agov.recovery.authenticatedWith', session.getAttribute('authenticatedWith') ?: 'unknown')
session.setAttribute('agov.recovery.currentAgovAq', session.getAttribute('contextClassRefToSet') ?: 'urn:qa.agov.ch:names:tc:ac:classes:100' )
LOG.debug('CheckLoa: idVerification2= '+ getUserAGOVLoiIdVerification(highestRoleLevelNumber.toString()))
def idVerification = getUserAGOVLoiIdVerification(highestRoleLevelNumber.toString())
session.setAttribute('agov.recovery.currentIdVerification', (idVerification.isEmpty() ? 'None' : idVerification.first()))
def validFrom = getUserAGOVLoiValidFrom('level'.concat(highestRoleLevelNumber.toString())) ?: ''
session.setAttribute('agov.recovery.currentAgovAqRoleValidFrom', validFrom)
response.setResult('exit.2')
return
}
}
if (highestRoleLevelNumber>=requestedRoleLevelNumber) {
// set attribute ValidFrom and ValidTo (only for higher than 100)
if (highestRoleLevelNumber > 100) {
def validFrom = getUserAGOVLoiValidFrom('level'.concat(highestRoleLevelNumber.toString()))
def validTo = getUserAGOVLoiValidTo('level'.concat(highestRoleLevelNumber.toString()))
LOG.debug('CheckLoa: ValidFrom :' + validFrom)
LOG.debug('CheckLoa: ValidTo :' + validTo)
if(validFrom != '') {
session.setAttribute('ValidFrom', '' + validFrom)
}
if(validTo != '') {
session.setAttribute('ValidTo', '' + validTo)
}
}
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

@ -0,0 +1,16 @@
<AuthState name="${state.entry}" class="ch.nevis.esauth.auth.states.scripting.ScriptState" final="false">
<ResultCond name="error" next="${state.exit.1}"/>
<ResultCond name="cancel" next="${state.failed}"/>
<ResultCond name="ok" next="${state.done}" />
<Response value="AUTH_CONTINUE">
<Gui name="fido2_auth" label="title.login.fido2">
</Gui>
<!-- does this realy do something ? -->
<Arg name="fido2UserVerification" value="required"/>
</Response>
<property name="parameter.cancel" value="OnCancel_Dispatch"/>
<property name="parameter.fido" value="${param.fido2.serviceAndPort:fido2:9443}"/>
<property name="parameter.rpId" value="${param.rpId:auth.agov.admin.ch}"/>
<property name="scriptTraceGroup" value="AGOV-ACCT"/>
<property name="script" value="file:///var/opt/nevisauth/default/conf/fido2_auth.groovy"/>
</AuthState>

View File

@ -0,0 +1,202 @@
import groovy.json.JsonBuilder
import groovy.json.JsonSlurper
import java.util.UUID
if (inargs.containsKey('cancel_fido2')) {
response.setResult('cancel')
LOG.debug("Fido2Auth: authentication cancelled by user")
return
}
def base64url(uuid) {
def msb = uuid.getMostSignificantBits()
def lsb = uuid.getLeastSignificantBits()
return new byte[] {
(byte) msb,
(byte) (msb >> 8),
(byte) (msb >> 16),
(byte) (msb >> 24),
(byte) (msb >> 32),
(byte) (msb >> 40),
(byte) (msb >> 48),
(byte) (msb >> 56),
(byte) lsb,
(byte) (lsb >> 8),
(byte) (lsb >> 16),
(byte) (lsb >> 24),
(byte) (lsb >> 32),
(byte) (lsb >> 40),
(byte) (lsb >> 48),
(byte) (lsb >> 56)
}.encodeBase64Url().toString()
}
def showGui() {
response.setGuiName('fido2_auth') // name is the trigger for including the JS
response.setGuiLabel('title.login.fido2')
response.addInfoGuiField('info', 'info.login.fido2', null)
response.addHiddenGuiField('authRequestId', 'not used', session['ch.nevis.auth.saml.request.id'])
response.addTextGuiField('email', 'email', session['ch.nevis.idm.User.email'])
if (notes.containsKey('lasterrorinfo') || notes.containsKey('lasterror')) {
response.addErrorGuiField('lasterror', notes['lasterrorinfo'], notes['lasterror'])
}
if (parameters.containsKey('cancel')) {
response.addButtonGuiField('cancel_fido2', 'cancel.login.fido2.button.label', 'true')
}
}
def getPath() {
if (inargs.containsKey('path')) { // form POST
return inargs['path']
}
if (inargs.containsKey('o.path.v')) { // AJAX POST
return inargs['o.path.v']
}
return null
}
def post(connection, json) {
connection.setRequestMethod("POST")
connection.setRequestProperty("Content-Type", "application/json")
connection.setDoOutput(true) // required to write body
String body = json.toString()
LOG.debug("Fido2Auth: ==> Request: '${body}'")
connection.getOutputStream().write(body.getBytes())
}
String userExtId = session['ch.adnovum.nevisidm.user.extId'] ?: session['ch.nevis.idm.User.extId'] ?: request.getUserId() ?: notes['userid']
if (userExtId == null) {
LOG.error("Fido2Auth: missing extId of nevisIDM user. check your authentication flow.")
}
// without the user extId this script won't work and we can fail with a System Error
Objects.requireNonNull(userExtId)
def path = getPath()
if (path == null) {
showGui() // POST from JavaScript not received
return
}
def connection = null
try {
def fullPath = "https://${parameters.get('fido')}${path}"
LOG.debug("Fido2Auth: opening connection to '${fullPath}'")
connection = new URL(fullPath).openConnection()
} catch (Exception e) {
LOG.error("Fido2Auth: opening connection failed", e)
notes.setProperty('lasterrorinfo', 'FIDO2 authentication failed')
response.setResult('error')
return
}
def json = new JsonBuilder()
if (path == '/nevisfido/fido2/attestation/options') {
json {
"username" userExtId
"userVerification" "required"
}
post(connection, json)
def responseCode = connection.responseCode
// non existing account, or account without FIDO2 key case
if (responseCode == 404 || responseCode == 400) {
LOG.debug("Fido2Auth: <== Response: ${responseCode}")
// 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'
def tAuth = System.currentTimeMillis() - (request.getSession(true).getCreationTime().getEpochSecond() * 1000)
LOG.info("Event='NOACCOUNT', Requester='${requester}', RequestId='${requestId}', RequestedAq=${requestedAq}, User=${session['ch.nevis.idm.User.email']}, CredentialType='${credentialType}', tAuth=${tAuth}ms, SourceIp=${sourceIp}, UserAgent='${userAgent}'")
// returning a fake options structure, which shouldn't leak whether the user account exists or not
// keyId is unique per environment and email, fido2SessionId and challenge are renewed each time
def keyId = UUID.nameUUIDFromBytes("${parameters['rpId']}.${session['ch.nevis.idm.User.email']}".getBytes())
def responseText = """{"status": "ok",
"errorMessage": "",
"fido2SessionId": "${UUID.randomUUID()}",
"challenge": "${base64url(UUID.randomUUID())}",
"timeout": 300000,
"rpId": "${parameters['rpId']}",
"allowCredentials": [
{
"type": "public-key",
"id": "${base64url(keyId)}",
"transports": []
}
],
"userVerification": "required"}"""
response.setContent(responseText) // return response from nevisFIDO "as-is"
response.setContentType('application/json')
response.setHttpStatusCode(200)
response.setIsDirectResponse(true)
return
}
def responseText = connection.inputStream.text
LOG.debug("Fido2Auth: <== Response: ${responseCode} : ${responseText}")
response.setContent(responseText) // return response from nevisFIDO "as-is"
response.setContentType('application/json')
response.setHttpStatusCode(200)
response.setIsDirectResponse(true)
return
}
if (path == '/nevisfido/fido2/assertion/result') {
if (inargs.containsKey('authRequestId') && (inargs['authRequestId'] != session['ch.nevis.auth.saml.request.id'])) {
// wrong request, "force" a timeout
LOG.debug('Fido2Auth: authentication timeout enforced, due to concurrent requests')
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
}
def userHandleValue = userExtId.getBytes().encodeBase64Url().toString()
LOG.debug("Fido2Auth: encoded userHandle: ${userHandleValue}")
json {
"id" inargs['id']
"type" inargs['type']
response {
"clientDataJSON" inargs['response.clientDataJSON']
"authenticatorData" inargs['response.authenticatorData']
"signature" inargs['response.signature']
"userHandle" userHandleValue
}
}
post(connection, json)
def responseCode = connection.responseCode
// test if credentials exist
if (responseCode != 400) {
def responseText = connection.inputStream.text
LOG.debug("Fido2Auth: <== Response: ${responseCode} : ${responseText}")
if (responseCode == 200 && new JsonSlurper().parseText(responseText).status == 'ok') {
response.setResult('ok')
return
}
}
//response.setHttpStatusCode(400)
//response.setIsDirectResponse(true)
// DEFINE how to handel error
notes.setProperty('lasterror', '1')
notes.setProperty('lasterrorinfo', 'FIDO2 authentication failed')
response.setResult('error')
return
}
response.setError(1, "FIDO2 authentication failed")
showGui()

View File

@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ
RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD
VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX
DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y
ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy
VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr
mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr
IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK
mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu
XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy
dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye
jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1
BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3
DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92
9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx
jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0
Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz
ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS
R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp
-----END CERTIFICATE-----

View File

@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI
2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx
1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ
q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz
tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ
vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP
BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV
5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY
1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4
NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG
Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91
8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe
pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl
MrY=
-----END CERTIFICATE-----

View File

@ -0,0 +1,27 @@
<AuthState name="${state.entry}" class="ch.nevis.esauth.auth.states.scripting.ScriptState" final="false" resumeState="true">
<ResultCond name="ok" next="${state.entry}_Processing"/>
<ResultCond name="default" next="${state.entry}"/>
<Response value="AUTH_CONTINUE">
<Gui name="recovery_accessapp_auth">
<GuiElem name="authRequestId" type="hidden" value="${sess:ch.nevis.auth.saml.request.id}" optional="true"/>
<GuiElem name="fallback" type="button" label="mobile_auth.cancel.button.label" value="true" optional="true"/>
<GuiElem name="accessApp" type="hidden" value="${sess:agov.recovery.accessapp}" optional="true"/>
</Gui>
</Response>
<property name="scriptTraceGroup" value="AGOV-ACCT"/>
<property name="script" value="file:///var/opt/nevisauth/default/conf/Recovery_mobile_nless_auth.groovy"/>
<property name="parameter.agovmeregistrationurl" value="${var.agovmeregistrationurl}"/>
<property name="parameter.recoveryurl" value="${var.recoveryurl}"/>
</AuthState>
<AuthState name="${state.entry}_Processing" class="ch.nevis.auth.fido.uaf.authstate.OutOfBandFidoUafAuthState" final="false" resumeState="false">
<ResultCond name="error" next="${state.entry}_Processing"/>
<ResultCond name="failed" next="${state.entry}"/>
<ResultCond name="ok" next="${state.done}" authLevel="2"/>
<Response value="AUTH_ERROR">
<Arg name="ch.nevis.isiweb4.response.status" value="403"/>
</Response>
<property name="fidoUafServerUrl" value="https://fido-uaf:9443/nevisfido"/>
<property name="dispatcher" value="link"/>
<property name="httpclient.tls.trustStoreRef" value="${keystore}"/>
</AuthState>

View File

@ -0,0 +1,53 @@
import groovy.json.JsonBuilder
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 clearFidoUAFSession() {
def s = request.getAuthSession(true)
s.removeAttribute('ch.nevis.auth.fido.uaf.fidouafsessionid')
inargs.remove('fallback')
}
// dispatch AJAX calls and form POST when operation is done
if (inargs['fidoUafDone'] == 'true' ||
inargs.containsKey('o.fidoUafSessionId.v') ||
getHeader('Content-Type') == 'application/json') {
if (inargs.containsKey('o.fidoUafSessionId.v') && (inargs['o.fidoUafSessionId.v'] != session['ch.nevis.auth.fido.uaf.fidouafsessionid'])) {
// received polling for wrong fido session; make sure, that stops
LOG.debug("received polling for wrong fido session ${inargs['o.fidoUafSessionId.v']} (correct: ${session['ch.nevis.auth.fido.uaf.fidouafsessionid']})")
def json = new JsonBuilder()
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
}
// continue with OutOfBandFidoUafAuthState
response.setResult('ok')
}
// dispatch form post with onReload input field : refresh QR-code FIDO UAF
if (inargs.containsKey('onReload')) {
clearFidoUAFSession()
response.setResult('default')
}

View File

@ -0,0 +1,8 @@
<AuthState name="${state.entry}" class="ch.nevis.esauth.auth.states.scripting.ScriptState" final="false">
<Response value="AUTH_ERROR">
<Gui name="NotUsed"/>
</Response>
<property name="parameter.cookie.domain" value="${param.cookie.domain}"/>
<property name="scriptTraceGroup" value="AGOV-ACCT"/>
<property name="script" value="file:///var/opt/nevisauth/default/conf/SendSamlResponseWithError.groovy"/>
</AuthState>

View File

@ -0,0 +1,24 @@
import ch.nevis.esauth.auth.engine.AuthResponse
// 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'
def tAuth = System.currentTimeMillis() - (request.getSession(true).getCreationTime().getEpochSecond() * 1000)
def errorCode = notes['saml.errorCode'] ?: 'unknown'
def errorMessage = notes['saml.errorMessage'] ?: 'unknown'
LOG.info("Event='SAMLERROR', Requester='${requester}', RequestId='${requestId}', RequestedAq=${requestedAq}, User=${user}, CredentialType='${credentialType}', tAuth=${tAuth}ms, errorCode='${errorCode}', errorMessage='${errorMessage}', SourceIp=${sourceIp}, UserAgent='${userAgent}'")
// delete the login cookie
def agovLoginCookie = "agovLogin=deleted; Domain=${parameters.get('cookie.domain')}; Path=/; Max-Age=0; SameSite=Strict; Secure; HttpOnly"
response.setHeader('Set-Cookie', agovLoginCookie)
response.setStatus(AuthResponse.AUTH_ERROR)
return

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,16 @@
<AuthState name="${state.entry}" class="ch.nevis.esauth.auth.states.scripting.ScriptState" final="false">
<ResultCond name="error" next="${state.exit.1}"/>
<ResultCond name="cancel" next="${state.failed}"/>
<ResultCond name="ok" next="${state.done}" />
<Response value="AUTH_CONTINUE">
<Gui name="recovery_fidokey_auth" label="title.login.fido2">
<!-- <Gui name="fido2_auth" label="title.login.fido2">-->
<GuiElem name="securityKey" type="hidden" value="${sess:agov.recovery.securityKey}" optional="true"/>
</Gui>
<!-- does this realy do something ? -->
<Arg name="fido2UserVerification" value="required"/>
</Response>
<property name="parameter.cancel" value="OnCancel_Dispatch"/>
<property name="parameter.fido" value="fido2:9443"/>
<property name="script" value="file:///var/opt/nevisauth/default/conf/recovery_fido2_auth.groovy"/>
</AuthState>

View File

@ -0,0 +1,151 @@
import groovy.json.JsonBuilder
import groovy.json.JsonSlurper
if (inargs.containsKey('cancel_fido2')) {
response.setResult('cancel')
return
}
def showGui() {
response.setGuiName('recovery_fidokey_auth') // name is the trigger for including the JS
//response.setGuiName('fido2_auth') // name is the trigger for including the JS
response.setGuiLabel('title.login.fido2')
response.addInfoGuiField('info', 'info.login.fido2', null)
response.addHiddenGuiField('authRequestId', 'not used', session['ch.nevis.auth.saml.request.id'])
response.addHiddenGuiField('securityKey', 'not used', session['agov.recovery.securityKey'])
response.addTextGuiField('email', 'email', session['ch.nevis.idm.User.email'])
if (notes.containsKey('lasterrorinfo') || notes.containsKey('lasterror')) {
response.addErrorGuiField('lasterror', notes['lasterrorinfo'], notes['lasterror'])
}
if (parameters.containsKey('cancel')) {
// TODO koenig 20221021: replace with specific label
response.addButtonGuiField('cancel_fido2', 'cancel.login.fido2.button.label', 'true')
}
}
def getPath() {
if (inargs.containsKey('path')) { // form POST
return inargs['path']
}
if (inargs.containsKey('o.path.v')) { // AJAX POST
return inargs['o.path.v']
}
return null
}
def post(connection, json) {
connection.setRequestMethod("POST")
connection.setRequestProperty("Content-Type", "application/json")
connection.setDoOutput(true) // required to write body
String body = json.toString()
LOG.info("==> Request: ${body}")
connection.getOutputStream().write(body.getBytes())
}
String userExtId = session['ch.adnovum.nevisidm.user.extId'] ?: session['ch.nevis.idm.User.extId'] ?: request.getUserId() ?: notes['userid']
if (userExtId == null) {
LOG.error("missing extId of nevisIDM user. check your authentication flow.")
}
// without the user extId this script won't work and we can fail with a System Error
Objects.requireNonNull(userExtId)
def path = getPath()
if (path == null) {
showGui() // POST from JavaScript not received
return
}
def connection = new URL("https://${parameters.get('fido')}${path}").openConnection()
def json = new JsonBuilder()
if (path == '/nevisfido/fido2/attestation/options') {
json {
"username" userExtId
"userVerification" "required"
}
post(connection, json)
def responseCode = connection.responseCode
// account without FIDO2 case
if (responseCode == 400) {
def responseText = '''{"status": "ok",
"errorMessage": "",
"fido2SessionId": "270312ae-8d74-4ded-ad89-5310da2d2e6f",
"challenge": "tKCqUM6URnykri1ZFz-3ww",
"timeout": 300000,
"rpId": "agov-d.azure.adnovum.net",
"allowCredentials": [
{
"type": "public-key",
"id": "WVzzUwxOf-1doTGkrdRHWPDbETTawkULLPsEiwiQwA2AFC4_YgL5OVmJJOT2OulAZSq_tvOfNlMSRKRXyXH2kw",
"transports": []
}
],
"userVerification": "preferred"}'''
LOG.info("<== Response: ${responseCode}")
response.setContent(responseText) // return response from nevisFIDO "as-is"
response.setContentType('application/json')
response.setHttpStatusCode(200)
response.setIsDirectResponse(true)
return
}
def responseText = connection.inputStream.text
LOG.info("<== Response: ${responseCode} : ${responseText}")
response.setContent(responseText) // return response from nevisFIDO "as-is"
response.setContentType('application/json')
response.setHttpStatusCode(200)
response.setIsDirectResponse(true)
return
}
if (path == '/nevisfido/fido2/assertion/result') {
if (inargs.containsKey('authRequestId') && (inargs['authRequestId'] != session['ch.nevis.auth.saml.request.id'])) {
// wrong request, "force" a timeout
LOG.info('authentication timeout enforced, due to concurrent requests')
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
}
def userHandleValue = userExtId.getBytes().encodeBase64Url().toString()
LOG.info("encoded userHandle: ${userHandleValue}")
json {
"id" inargs['id']
"type" inargs['type']
response {
"clientDataJSON" inargs['response.clientDataJSON']
"authenticatorData" inargs['response.authenticatorData']
"signature" inargs['response.signature']
"userHandle" userHandleValue
}
}
post(connection, json)
def responseCode = connection.responseCode
// test if credentials exist
if (responseCode != 400) {
def responseText = connection.inputStream.text
LOG.info("<== Response: ${responseCode} : ${responseText}")
if (responseCode == 200 && new JsonSlurper().parseText(responseText).status == 'ok') {
response.setResult('ok')
return
}
}
//response.setHttpStatusCode(400)
//response.setIsDirectResponse(true)
// DEFINE how to handel error
notes.setProperty('lasterror', '1')
notes.setProperty('lasterrorinfo', 'FIDO2 authentication failed')
response.setResult('error')
return
}
response.setError(1, "FIDO2 authentication failed")
showGui()

View File

@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDlDCCAnwCCQC4xKJxfbSLBzANBgkqhkiG9w0BAQsFADCBizELMAkGA1UEBhMC
Y2gxEDAOBgNVBAoMB2Fkbm92dW0xDTALBgNVBAsMBGFnb3YxJzAlBgNVBAMMHmph
a29iLmFnb3YtZC5henVyZS5hZG5vdnVtLm5ldDEyMDAGCSqGSIb3DQEJARYjaW5m
b0BqYWtvYi5hZ292LWQuYXp1cmUuYWRub3Z1bS5uZXQwHhcNMjMwMzIxMTUyMjI0
WhcNMjgwMzE5MTUyMjI0WjCBizELMAkGA1UEBhMCY2gxEDAOBgNVBAoMB2Fkbm92
dW0xDTALBgNVBAsMBGFnb3YxJzAlBgNVBAMMHmpha29iLmFnb3YtZC5henVyZS5h
ZG5vdnVtLm5ldDEyMDAGCSqGSIb3DQEJARYjaW5mb0BqYWtvYi5hZ292LWQuYXp1
cmUuYWRub3Z1bS5uZXQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDB
8LPO6Nack2z1whTratLxRD9KraO8QGrVTEa0p+23EKJH4WYE0QGzXbX4oFl2maXE
QDeCBofTnrl8sL3yVxBy56qO8T3VxYtt9akjty6PujO48bgJr2VGjGgtPYPUeOEk
lzCS616732Bnxc2iqo267G/tGooRIOOqefSyhEGmbI3KGv/zYZn/qxQo/A+5f+6y
zEoKdmnBF6vnowvffKfdFKI8udd5eKmfyrc5iNYHXoVP/HmqKbkyrBw1U0ysihRJ
3vyJVDtirQ5chLn0jOZ4UZ5SBck9+784yrVqpNbsWAe3NU+Vfx4wCk/rPWRDa1E2
fk+gEVvEMUFt4UvU25BdAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAIxToc9muu9z
d4yycZCgZbDtzrq2Un+m/m3TrBNzLV7UBbGt7HW9/kxvdFJTKgNEj+ZD1cY6O6D7
vrWV0Xb1XPgkaAfypc4Y7IOUTFDR/ib4siP9gPkHvr5WSIip3mFgX9yIV910N/hh
ImE9/Jtf/q9MopBu8J6zRmL/J8mVewVdcU3xqz27OVMMSht0Du8FcpIrNQwqc1LN
tCgdj+pw5vl7NH546WlyYNpLEkAeBpJ3XCBDDwcQftC+/cQ7GKJGtOJ4ODdxWMyX
ThsHJ7MCTiN+MoW5CeioGSmngezh4Gs5SpGAGVvwNvXW2jiGdRDdHmyxDPjCQvYA
wwZrmgtpYE0=
-----END CERTIFICATE-----

View File

@ -0,0 +1,30 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIFHzBJBgkqhkiG9w0BBQ0wPDAbBgkqhkiG9w0BBQwwDgQIrK+HT9oczr4CAggA
MB0GCWCGSAFlAwQBKgQQTod/pSdiG2RFbQP2Nd05NASCBNB7YpOzsJLORdEhmk3M
0Wk3qJDZU6RNXh80oEQP/nlr4Suikuw3zOxKUYT1M78+2DaCCu3bLUVzziTsjhdO
QBMYYAwvIMz/InjsEevdyggTQ1C/xbx45zHb7Ho8uYH/Epnk/9T9EQ5lPy0eOI6S
rkX0SeMl018/+YyykoMTg3/eCbnzxmoMymolvPIqz1keeBAY/Dyu565S+BK4wnMq
NnfAgU/4HzD+xTuGVCxbxmL4c+d4BZa4eG01Abn4rX0AjoPx35Yi+ES24zVbRSlR
WaJT5zcI4m2P2Y9n9KKxUcznXGt7GKV+IQ3cApU5eufhMEai5VPODunXDRlZsXVF
+reMLFXSYyIXSXQ6TjCCqyLtxQmwd0xmld0YDobuQEeU1Y30UNj2X+VOED91/6my
xe0KlnLenkiuVjyHFbKsV3yEaAYSBwHej4LuJzb3cCLUjh7Jdxh/cRLnbNzsc1rw
FofMEMdl5Lyqhje3AxHy95+a2cumk3BMmTdJ2l230nyFVDX/Fbf7hJgF9SPBCvF1
nS6bm6QOdJHNhEC+Te+GMAFzXWbnvYr/zdYLv75Vcs9Lho/HfPWU1WgPDk1H4xTF
U9cKCRBpf//SpVX8mizQJBrhIbp/Tew3LvC3h9DLqRXmVqQF4G7+7oz5bKWgtxMV
r5m5/6XcFLCZp9zyDt7SMGasdELub82S1BE1YHHREYqybR2koBp2x1nF8N1P/u28
QXtyVI+wiDXZ9l9zls7HPY6cAMS/nE+fpvjaLmcPxNj5uw6GfYCQblPxacBuMc8a
I8LAVprYqWLi3WZ/5HQIsAINt7G60P++avvvMA/bIkON9T5eNJuEeqo9gt/iVR6E
n9SyVovJUtLC6faBYNwjffNNU9pUPP+fft6Y9iE7OF4L6CQf3tp4KQR9681MBp0v
nxZnzadarXohWObxXVgBm4jSNIwUexm1vouxFrA3Lh+ofGpIpSrm/A54aKyv/S77
NXC6zHX2cDyY4ZIfDQ/mRiMvuR/4sNw5MiR04/DItNV2wORIFIbpTp3tEyfCINBr
7b7nayBWgrJYxu5ixD05bDpjqwgV6ajIuTvLG8sMFoQnS9e0rVzD73jHy3vsmJ2P
GS0Nj8u6f0njKc3Cr/i1/l/hXX/JtqhvZZTQiSVygqFhHi3Eo91hfGLR3fk+tUTs
ktOc53BVgbN1LzKJKqgyBddp25PxTy2y9qJL2iTTScUv98kSM017XBSZ+BdOhl1A
Qfgxv7u7zlj195X+vVaFVq0VMrfhhDDuDajKjA0zhO5sMPL/tH/2FPsPlnDuwS4b
9vSJH2qybeTcRfZEdu7vIbQdmS8JNTKvDE27YA+zur3yE/dc5qg6NZH2B68CRXLc
oKkyGrcKhpPry0mL9xUMM53iGcTV21KnsOdn/F/XKgZqhc8Y/80z2pifJ9w8DWLn
XSfKBJKmj/59HD0S6bdRddgMxrhkOR4uhkf5fedgIF7lkL+ovA3xek1j1z1X4fPM
ov46RsskJhclqbADip5qJ/IErNjlac/0dTELdxjLTnGJ3lM0/WbCbPpEP+/nLtCd
dgvmAnhykEiW2WpaEzP/K6T+OSKsNFmhrw6kX+XHDqqgjglJ/0XI4gwC1BDvn927
J8qw1PEvnK6TfEH3PrEDK8DwJQ==
-----END ENCRYPTED PRIVATE KEY-----

View File

@ -0,0 +1,243 @@
<AuthState name="${state.entry}" class="ch.nevis.esauth.auth.states.scripting.ScriptState" final="false" resumeState="false">
<ResultCond name="default" next="${state.entry}_dispatch"/>
<Response value="AUTH_CONTINUE"/>
<property name="script" value="file:///var/opt/nevisauth/default/conf/initializeRecovery.groovy"/>
</AuthState>
<AuthState name="${state.entry}_dispatch" class="ch.nevis.esauth.auth.states.standard.ConditionalDispatcherState" final="false" resumeState="true">
<ResultCond name="default" next="${state.exit.5}"/>
<!-- TODO/haburger/2024-AUG-20: remove Google after successfull migration to Friendly Captcha -->
<!-- <ResultCond name="cancel, hasCaptchaInfos, visible" next="${state.exit.2}"/> -->
<!-- <ResultCond name="hasCode, hasCaptchaInfos, visible" next="${state.exit.2}"/> -->
<!-- <ResultCond name="hasCaptchaInfos, invalidUrlTicket, visible" next="${state.entry}_enterEmail"/> -->
<!-- <ResultCond name="hasCaptchaInfos, continue, visible" next="${state.entry}_enterEmail"/> -->
<!-- <ResultCond name="invalidUrl, hasCaptchaInfos, visible" next="${state.entry}_ticketInvalid"/> -->
<ResultCond name="invalidUrlTicket" next="${state.exit.5}"/>
<ResultCond name="hasCode" next="${state.exit.2}"/>
<ResultCond name="cancel" next="${state.exit.2}"/>
<ResultCond name="cancel, hasCaptchaInfos" next="${state.exit.2}"/>
<ResultCond name="hasCode, hasCaptchaInfos" next="${state.exit.2}"/>
<ResultCond name="hasCaptchaInfos" next="${state.entry}_loginFactorQuestion"/>
<ResultCond name="hasCaptchaInfos, invalidUrlTicket" next="${state.entry}_enterEmail"/>
<ResultCond name="hasSessionCode, hasCaptchaInfos" next="${state.entry}_verifyUrlTicketIntro"/>
<ResultCond name="hasCaptchaInfos, continue" next="${state.exit.6}"/>
<ResultCond name="hasSessionCode" next="${state.entry}_verifyUrlTicketIntro"/>
<ResultCond name="hasCode, hasSessionCode" next="${state.exit.2}"/>
<ResultCond name="invalidUrl" next="${state.entry}_ticketInvalid"/>
<ResultCond name="invalidUrl, hasCaptchaInfos" next="${state.entry}_ticketInvalid"/>
<Response value="AUTH_CONTINUE">
<Gui name="NoGui">
</Gui>
</Response>
<property name="condition:cancel" value="#{inargs.containsKey('cancel')}"/>
<property name="condition:hasCode" value="#{inargs.containsKey('cd')}"/>
<property name="condition:hasSessionCode" value="#{sess.get('agov.recovery.code')}"/>
<property name="condition:invalidUrl" value="#{!inctx.getProperty('connection.actualURL').matches('((https://.*/AUTH/RECOVERY/\\?$)|(https://.*/AUTH/RECOVERY/$)|(https://.*/AUTH/RECOVERY/\\?language=(de|fr|it|en))|(https://.*/AUTH/RECOVERY/\\?cd=.*))')}"/>
<property name="condition:invalidUrlTicket" value="${notes:invalidUrlTicket}"/>
<property name="condition:hasCaptchaInfos" value="#{sess.get('agov.recovery.captchaSettings.puzzleUrl')}"/>
<!-- TODO/haburger/2024-AUG-20: remove Google after successfull migration to Friendly Captcha -->
<!-- <property name="condition:hasCaptchaInfos" value="#{sess.get('agov.recovery.json.accountUrl')}"/> -->
<!-- <property name="condition:continue" value="#{inargs.containsKey('continue')}"/> -->
<!-- <property name="condition:visible" value="#{sess.get('agov.recovery.X-ReCAPTCHA-Integration') eq 'VISIBLE'}"/> -->
</AuthState>
<AuthState name="${state.entry}_loginFactorQuestion" class="ch.nevis.esauth.auth.states.standard.ConditionalDispatcherState" final="true" resumeState="true">
<ResultCond name="cancel" next="${state.exit.2}"/>
<ResultCond name="loginFactorYes" next="${state.entry}_reasonSelection"/>
<ResultCond name="loginFactorNo" next="${state.entry}_reasonSelection"/>
<Response value="AUTH_CONTINUE">
<Gui name="recovery_questionnaire_loginfactor">
<GuiElem name="intro" type="info" label="recovery.intro.message"/>
<GuiElem name="authRequestId" type="hidden" value="${sess:ch.nevis.auth.saml.request.id}" optional="true"/>
<GuiElem name="cancel" type="submit" label="cancel.button.label" value="cancel"/>
<GuiElem name="submit" type="submit" label="submit.button.label" value="submit"/>
</Gui>
</Response>
<property name="condition:cancel" value="${inargs:cancel}==cancel"/>
<property name="condition:loginFactorYes" value="${inargs:continue}==yes"/>
<property name="condition:loginFactorNo" value="${inargs:continue}==no"/>
</AuthState>
<AuthState name="${state.entry}_reasonSelection" class="ch.nevis.esauth.auth.states.standard.ConditionalDispatcherState" final="true" resumeState="true">
<ResultCond name="cancel" next="${state.entry}_loginFactorQuestion"/>
<ResultCond name="validReasons" next="${state.entry}_instructions"/>
<ResultCond name="invalidReasons" next="${state.entry}_noRecovery"/>
<Response value="AUTH_CONTINUE">
<Gui name="recovery_questionnaire_reason_selection">
<GuiElem name="intro" type="info" label="recovery.intro.message"/>
<GuiElem name="authRequestId" type="hidden" value="${sess:ch.nevis.auth.saml.request.id}" optional="true"/>
<GuiElem name="question" type="hidden" value="${inargs:continue}" optional="true"/>
<GuiElem name="cancel" type="submit" label="cancel.button.label" value="cancel"/>
<GuiElem name="submit" type="submit" label="submit.button.label" value="submit"/>
</Gui>
</Response>
<property name="condition:cancel" value="${inargs:cancel}==cancel"/>
<property name="condition:validReasons" value="${inargs:continue}==yes"/>
<property name="condition:invalidReasons" value="${inargs:continue}==no"/>
</AuthState>
<AuthState name="${state.entry}_instructions" class="ch.nevis.esauth.auth.states.standard.ConditionalDispatcherState" final="true" resumeState="true">
<ResultCond name="cancel" next="${state.entry}_loginFactorQuestion"/>
<ResultCond name="continue" next="${state.entry}_enterEmail"/>
<Response value="AUTH_CONTINUE">
<Gui name="recovery_questionnaire_instructions">
<GuiElem name="intro" type="info" label="recovery.intro.message"/>
<GuiElem name="authRequestId" type="hidden" value="${sess:ch.nevis.auth.saml.request.id}" optional="true"/>
<GuiElem name="cancel" type="submit" label="cancel.button.label" value="cancel"/>
<GuiElem name="submit" type="submit" label="submit.button.label" value="submit"/>
</Gui>
</Response>
<property name="condition:cancel" value="${inargs:cancel}==cancel"/>
<property name="condition:continue" value="${inargs:continue}==continue"/>
</AuthState>
<AuthState name="${state.entry}_noRecovery" class="ch.nevis.esauth.auth.states.standard.ConditionalDispatcherState" final="true" resumeState="true">
<ResultCond name="cancel" next="${state.exit.2}"/>
<Response value="AUTH_CONTINUE">
<Gui name="recovery_questionnaire_no_recovery">
<GuiElem name="intro" type="info" label="recovery.intro.message"/>
<GuiElem name="authRequestId" type="hidden" value="${sess:ch.nevis.auth.saml.request.id}" optional="true"/>
<GuiElem name="cancel" type="submit" label="cancel.button.label" value="cancel"/>
</Gui>
</Response>
<property name="condition:cancel" value="${inargs:cancel}==cancel"/>
</AuthState>
<AuthState name="${state.entry}_enterEmail" class="ch.nevis.esauth.auth.states.scripting.ScriptState" final="true" resumeState="true">
<ResultCond name="cancel" next="${state.exit.2}"/>
<ResultCond name="verifyEmail" next="${state.entry}_saveEmail"/>
<ResultCond name="stay" next="${state.entry}_enterEmail"/>
<Response value="AUTH_CONTINUE">
<Gui name="recovery_intro_email">
<GuiElem name="intro" type="info" label="recovery.intro.message"/>
<GuiElem name="authRequestId" type="hidden" value="${sess:ch.nevis.auth.saml.request.id}" optional="true"/>
<GuiElem name="lasterror" type="error" label="${notes:lasterrorinfo}" value="${notes:lasterror}"/>
<GuiElem name="email" type="text" label="prompt.email" value="#{(sess.containsKey('agov.recovery.email'))?sess.get('agov.recovery.email'):inargs.getProperty('email', '')}" optional="true"/>
<GuiElem name="captchaSettings.enabled" type="hidden" value="${sess:agov.recovery.captchaSettings.enabled}" optional="true"/>
<GuiElem name="friendlyCaptchaSettings.siteKey" type="hidden" value="${sess:agov.recovery.captchaSettings.siteKey}" optional="true"/>
<GuiElem name="friendlyCaptchaSettings.puzzleUrl" type="hidden" value="${sess:agov.recovery.captchaSettings.puzzleUrl}" optional="true"/>
<!-- TODO/haburger/2024-AUG-20: remove Google after successfull migration to Friendly Captcha -->
<!-- <GuiElem name="captchaSettings.reCaptchaInvisibleSiteKey" type="hidden" value="${sess:agov.recovery.json.captchaSettings.reCaptchaInvisibleSiteKey}" optional="true"/> -->
<!-- <GuiElem name="captchaSettings.reCaptchaVisibleSiteKey" type="hidden" value="${sess:agov.recovery.json.captchaSettings.reCaptchaVisibleSiteKey}" optional="true"/> -->
<!-- <GuiElem name="X-ReCAPTCHA-Integration" type="hidden" value="${sess:agov.recovery.X-ReCAPTCHA-Integration}" optional="true"/> -->
<GuiElem name="cancel" type="submit" label="cancel.button.label" value="cancel"/>
<GuiElem name="submit" type="submit" label="submit.button.label" value="submit"/>
</Gui>
</Response>
<property name="script" value="file:///var/opt/nevisauth/default/conf/sanitizeAndDispatchRecoveryEmailInput.groovy"/>
</AuthState>
<AuthState name="${state.entry}_saveEmail" class="ch.nevis.esauth.auth.states.standard.TransformAttributes" final="false">
<ResultCond name="default" next="${state.exit.6}"/>
<Response value="AUTH_CONTINUE"/>
<property name="sess:agov.recovery.email" value="${inargs:email}"/>
</AuthState>
<AuthState name="${state.entry}_verifyUrlTicketIntro" class="ch.nevis.esauth.auth.states.standard.ConditionalDispatcherState" final="true" resumeState="true">
<ResultCond name="cancel" next="${state.failed}"/>
<ResultCond name="confirm" next="${state.entry}_verifyUrlTicket"/>
<Response value="AUTH_CONTINUE">
<Gui name="recovery_start_info">
<GuiElem name="intro" type="info" label="recovery.intro.message"/>
<GuiElem name="authRequestId" type="hidden" value="${sess:ch.nevis.auth.saml.request.id}" optional="true"/>
<GuiElem name="email" type="text" label="prompt.email" value="${sess:ch.nevis.session.loginid}" optional="true"/>
<GuiElem name="cancel" type="submit" label="cancel.button.label" value="cancel"/>
<GuiElem name="submit" type="submit" label="submit.button.label" value="submit"/>
</Gui>
</Response>
<property name="condition:confirm" value="#{inargs.containsKey('confirm') &amp;&amp; inargs.getProperty('confirm') eq 'confirm'}"/>
</AuthState>
<AuthState name="${state.entry}_verifyUrlTicket" final="false" class="ch.nevis.idm.authstate.IdmURLTicketVerifyState">
<ResultCond name="ok" next="${state.entry}_IdmGetPropertiesStateTicket" authLevel="auth.weak"/>
<ResultCond name="tmpLocked" next="${state.entry}_invalidateCode"/>
<ResultCond name="lockWarn" next="${state.entry}_invalidateCode"/>
<ResultCond name="nowLocked" next="${state.entry}_invalidateCode"/>
<ResultCond name="locked" next="${state.entry}_invalidateCode"/>
<ResultCond name="failed" next="${state.entry}_invalidateCode"/>
<Response value="AUTH_CONTINUE">
<Gui name="NoGui"/>
</Response>
<propertyRef name="nevisIDM_Connector"/>
<property name="user.ticket" value="${session:agov.recovery.code}"/>
</AuthState>
<AuthState name="${state.entry}_invalidateCode" class="ch.nevis.esauth.auth.states.standard.TransformAttributes" final="false" resumeState="false">
<ResultCond name="default" next="${state.entry}"/>
<Response value="AUTH_CONTINUE"/>
<property name="sess:agov.recovery.code" value=""/>
<property name="removeOnEmptyValue" value="true"/>
<property name="notes:invalidUrlTicket" value="was invalid"/>
</AuthState>
<AuthState class="ch.nevis.esauth.auth.states.standard.AuthGeneric" final="true" name="${state.entry}_ticketInvalid">
<Response value="AUTH_ERROR">
<Arg name="nevis.transfer.type" value="redirect"/>
<Arg name="nevis.transfer.destination" value="/AUTH/RECOVERY/"/>
</Response>
</AuthState>
<AuthState name="${state.entry}_IdmGetPropertiesStateTicket" final="false" class="ch.nevis.idm.authstate.IdmGetPropertiesState" resumeState="false">
<ResultCond name="ok" next="${state.entry}_verifyUser"/>
<ResultCond name="clientNotFound" next="${state.failed}"/>
<ResultCond name="default" next="${state.failed}"/>
<Response value="AUTH_ERROR">
<Gui name="internal_error">
<GuiElem name="transferId" type="hidden" value="${request:traceId}" optional="true"/>
</Gui>
</Response>
<propertyRef name="nevisIDM_Connector"/>
<property name="forceDataReload" value="true"/>
<!-- Returned Attributes in SecToken -->
<property name="user.attributes" value="${param.attributes}"/>
<property name="user.properties" value="${param.properties}"/>
<property name="userExtId" value="${request:userid}"/>
<property name="chooseDefaultProfile" value="true"/>
<property name="client.name" value="${param.client.name}"/>
<property name="detaillevel.profile" value="HIGH"/>
<property name="detaillevel.role" value="HIGH"/>
<property name="detaillevel.authorization" value="HIGH"/>
<property name="detaillevel.dataroom" value="HIGH"/>
<property name="detaillevel.credential" value="HIGH"/>
</AuthState>
<AuthState name="${state.entry}_verifyUser" class="ch.nevis.esauth.auth.states.scripting.ScriptState" final="false" resumeState="true">
<ResultCond name="ok" next="${state.exit.1}"/>
<ResultCond name="needCode" next="${state.entry}_IdmUserIdPasswordLogin"/>
<ResultCond name="error" next="${state.failed}"/>
<ResultCond name="alreadyInRecovery" next="${state.exit.3}"/>
<ResultCond name="notFullyRegistered" next="${state.exit.7}"/>
<Response value="AUTH_CONTINUE">
<Gui name="${state.entry}Dialog" label="op-onboarding.intro.title">
<GuiElem name="info" type="info" label="op-onboarding.intro.message"/>
<GuiElem name="lasterror" type="error" label="${notes:lasterrorinfo}" value="${notes:lasterror}"/>
<GuiElem name="submit" type="button" label="continue.button.label" value="go"/>
</Gui>
</Response>
<property name="scriptTraceGroup" value="Recovery"/>
<property name="script" value="file:///var/opt/nevisauth/default/conf/recovery-preprocessing.groovy"/>
</AuthState>
<AuthState name="${state.entry}_IdmUserIdPasswordLogin" final="true" resumeState="true" class="ch.nevis.idm.authstate.IdmPasswordVerifyState">
<ResultCond name="ok" next="${state.exit.1}" authLevel="auth.weak"/>
<ResultCond name="pwChange" next="${state.entry}_IdmUserIdPasswordLogin" authLevel="auth.weak"/>
<ResultCond name="lockWarn" next="${state.entry}_IdmUserIdPasswordLogin"/>
<ResultCond name="nowLocked" next="${state.entry}_codeLocked"/>
<ResultCond name="locked" next="${state.entry}_codeLocked"/>
<ResultCond name="tmpLocked" next="${state.entry}_codeLocked"/>
<ResultCond name="failed" next="${state.entry}_IdmUserIdPasswordLogin"/>
<ResultCond name="clientNotFound" next="${state.entry}_IdmUserIdPasswordLogin"/>
<ResultCond name="disabled" next="${state.entry}_codeLocked"/>
<Response value="AUTH_CONTINUE">
<Gui name="recovery_check_code">
<GuiElem name="lasterror" type="error" label="${notes:lasterrorinfo}" value="${notes:lasterror}"/>
<GuiElem name="code" type="pw-text" label="not-used" value="hide-input-in-logs" optional="true"/>
<GuiElem name="authRequestId" type="hidden" value="${sess:ch.nevis.auth.saml.request.id}" optional="true"/>
</Gui>
</Response>
<propertyRef name="nevisIDM_Connector"/>
<property name="user.loginType" value="LOGINID"/>
<property name="credential.type" value="contextPassword"/>
<property name="credential.context" value="RECOVERY"/>
<property name="user.password" value="#{inargs.getProperty('code').replace('-', '')}"/>
<property name="user.loginid" value="${sess:ch.adnovum.nevisidm.user.loginId}"/>
<property name="client.name" value="agov"/>
</AuthState>
<AuthState name="${state.entry}_codeLocked" class="ch.nevis.esauth.auth.states.standard.AuthGeneric" final="true" resumeState="false">
<Response value="AUTH_ERROR">
<Gui name="recovery_check_noCode">
</Gui>
</Response>
</AuthState>

View File

@ -0,0 +1,33 @@
if (inargs['authRequestId'] && (!session['ch.nevis.auth.saml.request.id'] || inargs['authRequestId'] != session['ch.nevis.auth.saml.request.id'])) {
// make sure we start from scratch
def mInargs = request.getInArgs()
mInargs.remove('email')
mInargs.remove('recaptcha_sitekey')
mInargs.remove('recaptcha_response')
mInargs.remove('continue')
mInargs.remove('authRequestId')
mInargs.remove('cancel')
}
if (inargs['cd'] && session['agov.recovery.code']) {
// we are called with a new URL --> make sure we start from scratch
def s = request.getAuthSession(true)
def sessionKeySet = new HashSet(session.keySet())
sessionKeySet.each { key ->
if ( key ==~ /ch.nevis.idm.*/ || key ==~ /ch.adnovum.nevisidm.*/ || key ==~ /agov.recovery.*/ ) {
s.removeAttribute(key)
}
}
}
if (!session['ch.nevis.auth.saml.request.id']) {
response.setSessionAttribute('ch.nevis.auth.saml.request.id', java.util.UUID.randomUUID().toString())
}
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'
response.setSessionAttribute('agov.recovery.ip', '' + sourceIp)
response.setSessionAttribute('agov.recovery.userAgent', '' + userAgent)
response.setResult('default')

View File

@ -0,0 +1,192 @@
import org.codehaus.groovy.runtime.StackTraceUtils
import groovy.xml.XmlSlurper
// AGOVaq conversion
def maxLoiRoleToCtxClssConvertorMap = [
"level100": "urn:qa.agov.ch:names:tc:ac:classes:100",
"level200": "urn:qa.agov.ch:names:tc:ac:classes:200",
"level300": "urn:qa.agov.ch:names:tc:ac:classes:300",
"level400": "urn:qa.agov.ch:names:tc:ac:classes:400",
"level500": "urn:qa.agov.ch:names:tc:ac:classes:500"
]
def getUserIdVerificationForRecovery(currentLoaRole) {
// application is AGOV-AccountStatus
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()
if (!result) {
// fallback if not explicitly set
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() ?: 'missing'
switch (currentLoaRole) {
case 'level100':
result = chDomicile ? 'SimpleLetter' : 'Video'
break
case 'level200':
result = chDomicile ? 'Bmid' : 'Video'
break
case 'level300':
case 'level400':
result = chDomicile ? lastIdVerification : 'Video'
break
default:
LOG.warn("unexpected loa on account: ${currentLoaRole}")
// safest default, should work in any case
result = 'Video'
}
LOG.warn("Recovery method not set, choosing ${result} (based on currentLoad: ${currentLoaRole}, CH-domicile: ${chDomicile}, last verification method: ${lastIdVerification})")
}
return result
}
def getAqLevelBasedOnIdVerificationForRecovery(idVerification, highestRoleLevel) {
def result = 'level'
switch (idVerification) {
case 'None':
result = result.concat('100')
break
case 'SimpleLetter':
result = result.concat('200')
break
case 'Video':
case 'VideoSelfPaid':
case 'Bmid':
case 'BmidSelfPaid':
case 'Counter':
result = result.concat((highestRoleLevel == 'level400') ? '400' : '300')
break
default:
LOG.warn("unexpected idVerification for recovery on account: ${idVerification}")
// safest default, should work in any case
result = highestRoleLevel
}
return result
}
def getUserMustRecoverValidFrom() {
// 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'}
return (authzNode) ? ((authzNode.validFrom && !authzNode.validFrom.text().isEmpty()) ? authzNode.validFrom?.text() : authzNode.ctlCreDat?.text()) : ''
}
// for autditing
def user = session['ch.adnovum.nevisidm.user.extId'] ?: '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'
def maxLoi = null
// new
if (session['ch.adnovum.nevisidm.userDto'] != null && notes['lasterror'] == null) {
try {
def userDto = new XmlSlurper().parseText(session['ch.adnovum.nevisidm.userDto'])
def userState = userDto.state
LOG.debug("Recovery: Dto is '${userDto}")
LOG.debug("Recovery: state is '${userState}")
def session = request.getAuthSession(true)
if (userState == 'ACTIVE') {
session.setAttribute('agov.recovery.authnContextClassRef', 'urn:qa.agov.ch:names:tc:ac:classes:recovery')
def maxLoiList = userDto.'**'.findAll { node -> node.name() == 'roles' && node.applicationName.text() == 'AGOV-Loi' }.collect({ node -> node.name.text() })
maxLoi = (maxLoiList == null || maxLoiList.isEmpty()) ? null : maxLoiList.sort().last()
def idVerification = null
def agovAqValidFrom = null
if (maxLoi) {
idVerification = userDto.'**'.find { node -> node.name() == 'properties' && node.name.text() == 'idVerification' && node.scopeName.text() == 'AGOV-Loi,' + maxLoi}?.value?.text()
idVerification = idVerification ?: 'None'
agovAqValidFrom = userDto.'**'.find { node -> node.name() == 'authorizations' && node.role.name.text() == maxLoi}?.validFrom?.text()
agovAqValidFrom = agovAqValidFrom?: userDto.'**'.find { node -> node.name() == 'authorizations' && node.role.name.text() == maxLoi}?.ctlCreDat?.text()
}
def mustRecover = userDto.'**'.find { node -> node.name() == 'roles' && node.applicationName.text() == 'AGOV-AccountStatus' && node.name.text() == 'mustRecover' }
def hasRecoveryRole = userDto.'**'.find { node -> node.name() == 'roles' && node.applicationName.text() == 'AGOV-AccountStatus' && node.name.text() == 'recovery' }
if (mustRecover) {
// attributes are defined over the mustRecover authorization
session.setAttribute('agov.recovery.authnContextClassRef', 'urn:qa.agov.ch:names:tc:ac:classes:mustRecover')
idVerification = getUserIdVerificationForRecovery(maxLoi ?: 'level100') ?: idVerification
agovAqValidFrom = getUserMustRecoverValidFrom()
maxLoi = getAqLevelBasedOnIdVerificationForRecovery(idVerification, maxLoi)
}
LOG.debug("Recovery: MaxLoi is '${maxLoi}'")
LOG.debug("Recovery: IdVerification is ${idVerification}")
LOG.debug("Recovery: agovAqValidFrom is ${agovAqValidFrom}")
LOG.debug("Recovery: mustRecover is '${mustRecover}'")
LOG.debug("Recovery: hasRecoveryRole is '${hasRecoveryRole}'")
if (maxLoi != null) {
if (maxLoiRoleToCtxClssConvertorMap.containsKey(maxLoi)) {
LOG.debug("Recovery: MaxLoiMapping is " + maxLoiRoleToCtxClssConvertorMap[maxLoi])
response.setSessionAttribute('agov.recovery.currentAgovAq', '' + maxLoiRoleToCtxClssConvertorMap[maxLoi])
response.setSessionAttribute('agov.recovery.currentIdVerification', '' + idVerification)
response.setSessionAttribute('agov.recovery.currentAgovAqRoleValidFrom', '' + agovAqValidFrom)
if ((maxLoi == 'level100') && (mustRecover == null)) {
// mustRecover role not set, so code needs to be checked
LOG.debug("Recovery: emailAndCode")
response.setSessionAttribute('agov.recovery.authenticatedWith', 'urn:qa.agov.ch:names:tc:authfactor:emailAndCode')
response.setResult('needCode')
return
} else {
LOG.debug("Recovery: email")
response.setSessionAttribute('agov.recovery.authenticatedWith', 'urn:qa.agov.ch:names:tc:authfactor:email')
response.setResult('ok')
return
}
} else {
LOG.error("Recovery: Failed to convert '${maxLoi}' to AGOVaq")
response.setResult('error')
return
}
} else {
// maxLoi is null
LOG.debug("Recovery: no 'AGOV-Loi'-role assigned to user ${user}")
if ((hasRecoveryRole != null) && (mustRecover == null)) {
response.setResult('notFullyRegistered')
return
} else {
LOG.error("Recovery: no 'AGOV-Loi'-role assigned to user ${user} and no recovery role ")
response.setResult('error')
return
}
}
} else {
// state != ACTIVE and no lasterror should not happen
LOG.error("Recovery: state='${userState}' but not lasterror set")
response.setNote('lasterror', '9909')
response.setNote('lasterrorinfo', 'internal error')
response.setResult('error')
return
}
} catch (Exception e) {
e = StackTraceUtils.sanitize(e)
def affectedLines = e.stackTrace.findAll { it.className.startsWith('Script') }.collect { "${it.methodName}:${it.lineNumber}" }
LOG.error("FATAL: Recovery processing failed (at lines: ${affectedLines})", e)
response.setNote('lasterror', '9909')
response.setNote('lasterrorinfo', 'internal error')
response.setResult('error')
return
}
}
LOG.error("Recovery: userDto missing or failure before (lasterror='${notes.getProperty('lasterror', '-')}')")
response.setNote('lasterror', '9909')
response.setNote('lasterrorinfo', 'internal error')
response.setResult('error')
return

View File

@ -0,0 +1,25 @@
def EMAIL_REGEXP = '^(([^<>()\\[\\]\\\\\\.,;:\\s@"]+(\\.[^<>()\\[\\]\\\\\\.,;:\\s@"]+)*)|(\\.\\+))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$'
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'
if (inargs['cancel'] && inargs['cancel'] == 'cancel') {
response.setResult('cancel')
return
}
if ( inargs['continue'] && inargs['continue'] == 'continue' ) {
if (inargs['email'] && inargs['email'].matches(EMAIL_REGEXP)) {
response.setResult('verifyEmail')
return
} else {
LOG.warn("User attempted to bypass frontend emailvalidation with inavlid email: '${inargs['email']}', SourceIp=${sourceIp}, UserAgent='${userAgent}'")
request.getInArgs().setProperty('email', 'inavalid@email.org')
response.setResult('stay')
return
}
}
response.setResult('stay')
return

View File

@ -0,0 +1,20 @@
<AuthState name="${state.entry}" class="ch.nevis.esauth.auth.states.xml.DocumentProcessor" final="false" resumeState="false">
<ResultCond name="ok" next="${state.done}"/>
<!-- errors ignored, we transition anyway to done -->
<ResultCond name="nomatch" next="${state.done}"/>
<ResultCond name="validation-failed" next="${state.done}"/>
<ResultCond name="default" next="${state.done}"/>
<Response value="AUTH_ERROR"/>
<property name="sess:agov.countryName" value="xpath:/country-names/country[@code='${sess:ch.nevis.idm.User.country}']/@${sess:ch.nevis.idm.User.language}"/>
<property name="optional" value="sess:agov.countryName"/>
<property name="documentSourceType" value="RESOURCE"/>
<property name="documentSource" value="file:/var/opt/nevisauth/default/conf/countries.xml"/>
<!-- scheduler interval: only load it once, then we use it -->
<property name="scheduler.interval" value="-1"/>
<property name="maxAge" value="-1"/>
</AuthState>

View File

@ -0,0 +1,250 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<country-names>
<country code="af" en="Afghanistan" de="Afghanistan" fr="Afghanistan" it="Afghanistan"/>
<country code="al" en="Albania" de="Albanien" fr="Albanie" it="Albania"/>
<country code="dz" en="Algeria" de="Algerien" fr="Algérie" it="Algeria"/>
<country code="as" en="American Samoa" de="Amerikanisch-Samoa" fr="Samoa américaines" it="Samoa Americane"/>
<country code="ad" en="Andorra" de="Andorra" fr="Andorre" it="Andorra"/>
<country code="ao" en="Angola" de="Angola" fr="Angola" it="Angola"/>
<country code="ai" en="Anguilla" de="Anguilla" fr="Anguilla" it="Anguilla"/>
<country code="aq" en="Antarctica" de="Antarktis" fr="Antarctique" it="Antartide"/>
<country code="ag" en="Antigua and Barbuda" de="Antigua und Barbuda" fr="Antigua-et-Barbuda" it="Antigua e Barbuda"/>
<country code="ar" en="Argentina" de="Argentinien" fr="Argentine" it="Argentina"/>
<country code="am" en="Armenia" de="Armenien" fr="Arménie" it="Armenia"/>
<country code="aw" en="Aruba" de="Aruba" fr="Aruba" it="Aruba"/>
<country code="au" en="Australia" de="Australien" fr="Australie" it="Australia"/>
<country code="at" en="Austria" de="Österreich" fr="Autriche" it="Austria"/>
<country code="az" en="Azerbaijan" de="Aserbaidschan" fr="Azerbaïdjan" it="Azerbaigian"/>
<country code="bs" en="Bahamas" de="Bahamas" fr="Bahamas" it="Bahamas"/>
<country code="bh" en="Bahrain" de="Bahrain" fr="Bahreïn" it="Bahrein"/>
<country code="bd" en="Bangladesh" de="Bangladesch" fr="Bangladesh" it="Bangladesh"/>
<country code="bb" en="Barbados" de="Barbados" fr="Barbade" it="Barbados"/>
<country code="by" en="Belarus" de="Belarus" fr="Bélarus" it="Bielorussia"/>
<country code="be" en="Belgium" de="Belgien" fr="Belgique" it="Belgio"/>
<country code="bz" en="Belize" de="Belize" fr="Belize" it="Belize"/>
<country code="bj" en="Benin" de="Benin" fr="Bénin" it="Benin"/>
<country code="bm" en="Bermuda" de="Bermudas" fr="Bermudes" it="Bermuda"/>
<country code="bt" en="Bhutan" de="Bhutan" fr="Bhoutan" it="Bhutan"/>
<country code="bo" en="Bolivia" de="Bolivien" fr="Bolivie" it="Bolivia"/>
<country code="ba" en="Bosnia-Herzegovina" de="Bosnien-Herzegowina" fr="Bosnie et Herzégovine" it="Bosnia ed Erzegovina"/>
<country code="bw" en="Botswana" de="Botsuana" fr="Botswana" it="Botswana"/>
<country code="bv" en="Bouvet Island" de="Bouvetinsel" fr="Île Bouvet" it="Isola Bouvet"/>
<country code="br" en="Brazil" de="Brasilien" fr="Brésil" it="Brasile"/>
<country code="io" en="British Indian Ocean Territory" de="Britisches Territorium im Indischen Ozean" fr="Territoire britannique de locéan Indien" it="Territorio Britannico dellOceano Indiano"/>
<country code="bn" en="Brunei" de="Brunei" fr="Brunei" it="Brunei"/>
<country code="bg" en="Bulgaria" de="Bulgarien" fr="Bulgarie" it="Bulgaria"/>
<country code="bf" en="Burkina Faso" de="Burkina Faso" fr="Burkina Faso " it="Burkina Faso"/>
<country code="bi" en="Burundi" de="Burundi" fr="Burundi" it="Burundi"/>
<country code="kh" en="Cambodia" de="Kambodscha" fr="Cambodge" it="Cambogia"/>
<country code="cm" en="Cameroon" de="Kamerun" fr="Cameroun" it="Camerun"/>
<country code="ca" en="Canada" de="Kanada" fr="Canada" it="Canada"/>
<country code="cv" en="Cape Verde" de="Cabo Verde" fr="Cabo Verde" it="Capo Verde"/>
<country code="ky" en="Cayman Islands" de="Kaiman-Inseln" fr="Îles Caïmans" it="Isole Cayman"/>
<country code="cf" en="Central African Republic" de="Zentralafrikanische Republik" fr="République centrafricaine" it="Repubblica Centrafricana"/>
<country code="td" en="Chad" de="Tschad" fr="Tchad" it="Ciad"/>
<country code="cl" en="Chile" de="Chile" fr="Chili" it="Cile"/>
<country code="cn" en="China (People's Republic OF)" de="China (Volksrepublik)" fr="Chine (République populaire de Chine)" it="Cina, Repubblica popolare cinese"/>
<country code="cx" en="Christmas Island (Indian Ocean)" de="Weihnachtsinsel (Indischer Ozean)" fr="Île Christmas (océan Indien)" it="Isola di Natale"/>
<country code="cc" en="Cocos (Keeling) Island" de="Kokosinseln (Keeling)" fr="Îles Cocos" it="Isole Cocos (Keeling)"/>
<country code="co" en="Colombia" de="Kolumbien" fr="Colombie" it="Colombia"/>
<country code="km" en="Comoros" de="Komoren" fr="Comores" it="Comore"/>
<country code="cg" en="Congo (Republic)" de="Kongo (Republik)" fr="République du Congo" it="Repubblica del Congo"/>
<country code="cd" en="Congo, Democratic Republic" de="Kongo, Demokratische Republik" fr="République démocratique du Congo" it="Repubblica democratica del Congo"/>
<country code="ck" en="Cook Islands" de="Cookinseln" fr="Îles Cook" it="Isole Cook"/>
<country code="cr" en="Costa Rica" de="Costa Rica" fr="Costa Rica" it="Costa Rica"/>
<country code="hr" en="Croatia" de="Kroatien" fr="Croatie" it="Croazia"/>
<country code="cu" en="Cuba" de="Kuba" fr="Cuba" it="Cuba"/>
<country code="cw" en="Curaçao" de="Curaçao" fr="Curaçao" it="Curaçao"/>
<country code="cy" en="Cyprus" de="Zypern" fr="Chypre" it="Cipro"/>
<country code="cz" en="Czech Republic" de="Tschechische Republik" fr="Tchéquie" it="Repubblica Ceca"/>
<country code="dk" en="Denmark" de="Dänemark" fr="Danemark" it="Danimarca"/>
<country code="dj" en="Djibouti" de="Dschibuti" fr="Djibouti" it="Gibuti"/>
<country code="dm" en="Dominica" de="Dominica" fr="Dominique" it="Dominica"/>
<country code="do" en="Dominican Republic" de="Dominikanische Republik" fr="République dominicaine" it="Repubblica Dominicana"/>
<country code="ec" en="Ecuador" de="Ecuador" fr="Équateur" it="Ecuador"/>
<country code="eg" en="Egypt" de="Ägypten" fr="Égypte" it="Egitto"/>
<country code="sv" en="El Salvador" de="El Salvador" fr="El Salvador" it="El Salvador"/>
<country code="gq" en="Equatorial Guinea" de="Äquatorialguinea" fr="Guinée équatoriale" it="Guinea equatoriale"/>
<country code="er" en="Eritrea" de="Eritrea" fr="Érythrée" it="Eritrea"/>
<country code="ee" en="Estonia" de="Estland" fr="Estonie" it="Estonia"/>
<country code="et" en="Ethiopia" de="Äthiopien" fr="Éthiopie" it="Etiopia"/>
<country code="fk" en="Falkland Islands" de="Falklandinseln" fr="Îles Falkland" it="Isole Falkland"/>
<country code="fo" en="Faroe Islands" de="Färöerinseln" fr="Îles Féroé" it="Isole Faroe"/>
<country code="fj" en="Fiji" de="Fidschi" fr="Fidji" it="Figi"/>
<country code="fi" en="Finland" de="Finnland" fr="Finlande" it="Finlandia"/>
<country code="fr" en="France" de="Frankreich" fr="France" it="Francia"/>
<country code="gf" en="French Guiana" de="Französisch-Guayana" fr="Guyane française" it="Guyana francese"/>
<country code="pf" en="French Polynesia" de="Französisch-Polynesien" fr="Polynésie française" it="Polinesia francese"/>
<country code="ga" en="Gabon" de="Gabun" fr="Gabon" it="Gabon"/>
<country code="gm" en="Gambia" de="Gambia" fr="Gambie" it="Gambia"/>
<country code="ge" en="Georgia" de="Georgien" fr="Géorgie" it="Georgia"/>
<country code="de" en="Germany" de="Deutschland" fr="Allemagne" it="Germania"/>
<country code="gh" en="Ghana" de="Ghana" fr="Ghana" it="Ghana"/>
<country code="gi" en="Gibraltar" de="Gibraltar" fr="Gibraltar" it="Gibilterra"/>
<country code="gb" en="Great Britain and Northern Ireland" de="Grossbritannien und Nordirland" fr="Royaume-Uni" it="Regno Unito"/>
<country code="gr" en="Greece" de="Griechenland" fr="Grèce" it="Grecia"/>
<country code="gl" en="Greenland" de="Grönland" fr="Groenland" it="Groenlandia"/>
<country code="gd" en="Grenada" de="Grenada" fr="Grenade" it="Grenada"/>
<country code="gp" en="Guadeloupe" de="Guadeloupe" fr="Guadeloupe" it="Guadalupa"/>
<country code="gu" en="Guam" de="Guam" fr="Guam" it="Guam"/>
<country code="gt" en="Guatemala" de="Guatemala" fr="Guatemala" it="Guatemala"/>
<country code="gg" en="Guernsey" de="Guernsey" fr="Guernesey" it="Guernsey"/>
<country code="gn" en="Guinea (Republic)" de="Guinea (Republik)" fr="République de Guinée" it="Guinea"/>
<country code="gw" en="Guinea-Bissau" de="Guinea-Bissau" fr="Guinée-Bissau" it="Guinea-Bissau"/>
<country code="gy" en="Guyana" de="Guyana" fr="Guyana" it="Guyana"/>
<country code="ht" en="Haiti" de="Haiti" fr="Haïti" it="Haiti"/>
<country code="hm" en="Heard AND McDonald Islands" de="Heard- und McDonald-Inseln" fr="Îles Heard et McDonald" it="Isola Heard e Isole McDonald"/>
<country code="hn" en="Honduras" de="Honduras" fr="Honduras" it="Honduras"/>
<country code="hk" en="Hong Kong" de="Hongkong" fr="Hong Kong" it="Hong Kong"/>
<country code="hu" en="Hungary" de="Ungarn" fr="Hongrie" it="Ungheria"/>
<country code="is" en="Iceland" de="Island" fr="Islande" it="Islanda"/>
<country code="in" en="India" de="Indien" fr="Inde" it="India"/>
<country code="id" en="Indonesia" de="Indonesien" fr="Indonésie" it="Indonesia"/>
<country code="ir" en="Iran" de="Iran" fr="Iran" it="Iran"/>
<country code="iq" en="Iraq" de="Irak" fr="Irak" it="Iraq"/>
<country code="ie" en="Ireland" de="Irland" fr="Irlande" it="Irlanda"/>
<country code="im" en="Island OF Man" de="Isle of Man" fr="Île de Man" it="Isola di Man"/>
<country code="il" en="Israel" de="Israel" fr="Israël" it="Israele"/>
<country code="it" en="Italy" de="Italien" fr="Italie" it="Italia"/>
<country code="ci" en="Ivory Coast" de="Côte d'Ivoire" fr="Côte dIvoire" it="Costa dAvorio"/>
<country code="jm" en="Jamaica" de="Jamaika" fr="Jamaïque" it="Giamaica"/>
<country code="jp" en="Japan" de="Japan" fr="Japon" it="Giappone"/>
<country code="je" en="Jersey" de="Jersey" fr="Jersey" it="Jersey"/>
<country code="jo" en="Jordan" de="Jordanien" fr="Jordanie" it="Giordania"/>
<country code="kz" en="Kazakhstan" de="Kasachstan" fr="Kazakhstan" it="Kazakstan"/>
<country code="ke" en="Kenya" de="Kenia" fr="Kenya" it="Kenya"/>
<country code="ki" en="Kiribati" de="Kiribati" fr="Kiribati" it="Kiribati"/>
<country code="kp" en="Korea, Democratic People's Republic of (North Korea)" de="Korea, Demokratische Volksrepublik (Nordkorea)" fr="République populaire démocratique de Corée (Corée du Nord)" it="Repubblica popolare democratica di Corea (Corea del Nord)"/>
<country code="kr" en="Korea, Republic of (South Korea)" de="Korea, Republik (Südkorea)" fr="République de Corée (Corée du Sud)" it="Repubblica di Corea (Corea del Sud)"/>
<country code="xk" en="Kosovo / Unmik" de="Kosovo / UNMIK" fr="Kosovo" it="Kosovo / UNMIK"/>
<country code="kw" en="Kuwait" de="Kuwait" fr="Koweït" it="Kuwait"/>
<country code="kg" en="Kyrgyzstan" de="Kirgisistan" fr="Kirghizistan" it="Kirghizistan"/>
<country code="la" en="Laos" de="Laos" fr="Laos" it="Laos"/>
<country code="lv" en="Latvia" de="Lettland" fr="Lettonie" it="Lettonia"/>
<country code="lb" en="Lebanon" de="Libanon" fr="Liban" it="Libano"/>
<country code="ls" en="Lesotho" de="Lesotho" fr="Lesotho" it="Lesotho"/>
<country code="lr" en="Liberia" de="Liberia" fr="Libéria" it="Liberia"/>
<country code="ly" en="Libya" de="Libyen" fr="Libye" it="Libia"/>
<country code="li" en="Liechtenstein" de="Liechtenstein" fr="Liechtenstein" it="Liechtenstein"/>
<country code="lt" en="Lithuania" de="Litauen" fr="Lituanie" it="Lituania"/>
<country code="lu" en="Luxembourg" de="Luxemburg" fr="Luxembourg" it="Lussemburgo"/>
<country code="mo" en="Macao" de="Macao" fr="Macao" it="Macao"/>
<country code="mk" en="Macedonia, the Former Yugoslav Republic of" de="Mazedonien, ehemalige jugoslawische Republik" fr="Macédoine du Nord" it="Macedonia del Nord"/>
<country code="mg" en="Madagascar" de="Madagaskar" fr="Madagascar" it="Madagascar"/>
<country code="mw" en="Malawi" de="Malawi" fr="Malawi" it="Malawi"/>
<country code="my" en="Malaysia" de="Malaysia" fr="Malaisie" it="Malaysia"/>
<country code="mv" en="Maldives" de="Malediven" fr="Maldives" it="Maldive"/>
<country code="ml" en="Mali" de="Mali" fr="Mali" it="Mali"/>
<country code="mt" en="Malta" de="Malta" fr="Malte" it="Malta"/>
<country code="mp" en="Mariana Islands" de="Marianen" fr="Îles Mariannes" it="Isole Marianne"/>
<country code="mh" en="Marshall Islands" de="Marshallinseln" fr="Îles Marshall" it="Isole Marshall"/>
<country code="mq" en="Martinique" de="Martinique" fr="Martinique" it="Martinica"/>
<country code="mr" en="Mauritania" de="Mauretanien" fr="Mauritanie" it="Mauritania"/>
<country code="mu" en="Mauritius Island" de="Mauritius" fr="Île Maurice" it="Maurizio"/>
<country code="yt" en="Mayotte" de="Mayotte" fr="Mayotte" it="Mayotte"/>
<country code="mx" en="Mexico" de="Mexiko" fr="Mexique" it="Messico"/>
<country code="fm" en="Micronesia (Federated States OF)" de="Mikronesien (Föderierte Staaten von)" fr="États fédérés de Micronésie" it="Stati Federati di Micronesia"/>
<country code="md" en="Moldova" de="Moldau" fr="Moldavie" it="Moldova"/>
<country code="mc" en="Monaco" de="Monaco" fr="Monaco" it="Monaco"/>
<country code="mn" en="Mongolia" de="Mongolei" fr="Mongolie" it="Mongolia"/>
<country code="me" en="Montenegro, Republic" de="Montenegro, Republik" fr="Monténégro" it="Montenegro"/>
<country code="ms" en="Montserrat" de="Montserrat" fr="Montserrat" it="Montserrat"/>
<country code="ma" en="Morocco" de="Marokko" fr="Maroc" it="Marocco"/>
<country code="mz" en="Mozambique" de="Mosambik" fr="Mozambique" it="Mozambico"/>
<country code="mm" en="Myanmar (Union of)" de="Myanmar (Union)" fr="Myanmar" it="Myanmar"/>
<country code="na" en="Namibia" de="Namibia" fr="Namibie" it="Namibia"/>
<country code="nr" en="Nauru" de="Nauru" fr="Nauru" it="Nauru"/>
<country code="np" en="Nepal" de="Nepal" fr="Népal" it="Nepal"/>
<country code="nl" en="Netherlands" de="Niederlande" fr="Pays-Bas" it="Paesi Bassi"/>
<country code="nc" en="New Caledonia" de="Neukaledonien" fr="Nouvelle-Calédonie" it="Nuova Caledonia"/>
<country code="nz" en="New Zealand" de="Neuseeland" fr="Nouvelle-Zélande" it="Nuova Zelanda"/>
<country code="ni" en="Nicaragua" de="Nicaragua" fr="Nicaragua" it="Nicaragua"/>
<country code="ne" en="Niger" de="Niger" fr="Niger" it="Niger"/>
<country code="ng" en="Nigeria" de="Nigeria" fr="Nigéria" it="Nigeria"/>
<country code="nu" en="Niua" de="Niue" fr="Nioué" it="Isole Niua"/>
<country code="nf" en="Norfolk Island" de="Norfolkinsel" fr="Île Norfolk" it="Isola Norfolk"/>
<country code="no" en="Norway" de="Norwegen" fr="Norvège" it="Norvegia"/>
<country code="om" en="Oman" de="Oman" fr="Oman" it="Oman"/>
<country code="pk" en="Pakistan" de="Pakistan" fr="Pakistan" it="Pakistan"/>
<country code="pw" en="Palau" de="Palau" fr="Palaos" it="Palau"/>
<country code="ps" en="Palestine" de="Palästina" fr="Palestine" it="Palestina"/>
<country code="pa" en="Panama" de="Panama" fr="Panama" it="Panama"/>
<country code="pg" en="Papua New Guinea" de="Papua-Neuguinea" fr="Papouasie-Nouvelle-Guinée" it="Papua Nuova Guinea"/>
<country code="py" en="Paraguay" de="Paraguay" fr="Paraguay" it="Paraguay"/>
<country code="pe" en="Peru" de="Peru" fr="Pérou" it="Perù"/>
<country code="ph" en="Philippines" de="Philippinen" fr="Philippines" it="Filippine"/>
<country code="pn" en="Pitcairn" de="Pitcairn" fr="Îles Pitcairn" it="Isole Pitcairn"/>
<country code="pl" en="Poland" de="Polen" fr="Pologne" it="Polonia"/>
<country code="pt" en="Portugal" de="Portugal" fr="Portugal" it="Portogallo"/>
<country code="pr" en="Puerto Rico" de="Puerto Rico" fr="Porto Rico" it="Porto Rico"/>
<country code="qa" en="Qatar" de="Katar" fr="Qatar" it="Qatar"/>
<country code="re" en="Réunion" de="Réunion" fr="La Réunion" it="Isola della Riunione"/>
<country code="ro" en="Romania" de="Rumänien" fr="Roumanie" it="Romania"/>
<country code="ru" en="Russian Federation" de="Russische Föderation" fr="Russie" it="Russia"/>
<country code="rw" en="Rwanda" de="Ruanda" fr="Rwanda" it="Ruanda"/>
<country code="sb" en="Salomon Islands" de="Salomoninseln" fr="Îles Salomon" it="Isole Salomone"/>
<country code="sm" en="San Marino" de="San Marino" fr="Saint-Marin" it="San Marino"/>
<country code="sa" en="Saudi Arabia" de="Saudi-Arabien" fr="Arabie saoudite" it="Arabia Saudita"/>
<country code="sn" en="Senegal" de="Senegal" fr="Sénégal" it="Senegal"/>
<country code="rs" en="Serbia, Republic" de="Serbien, Republik" fr="Serbie" it="Serbia"/>
<country code="sc" en="Seychelles" de="Seychellen" fr="Seychelles" it="Seychelles"/>
<country code="sl" en="Sierra Leone" de="Sierra Leone" fr="Sierra Leone" it="Sierra Leone"/>
<country code="sg" en="Singapore" de="Singapur" fr="Singapour" it="Singapore"/>
<country code="sk" en="Slovak Republic" de="Slowakei" fr="Slovaquie" it="Slovacchia"/>
<country code="si" en="Slovenia" de="Slowenien" fr="Slovénie" it="Slovenia"/>
<country code="so" en="Somalia" de="Somalia" fr="Somalie" it="Somalia"/>
<country code="za" en="South Africa" de="Südafrika" fr="Afrique du Sud" it="Sudafrica"/>
<country code="gs" en="South Georgia AND the south Sandwich Islands" de="Südgeorgien und die Südlichen Sandwichinseln" fr="Îles Géorgie du Sud et Sandwich du Sud" it="Georgia del Sud e Sandwich Australi"/>
<country code="ss" en="South Sudan" de="Südsudan" fr="Soudan du Sud" it="Sudan del Sud"/>
<country code="es" en="Spain" de="Spanien" fr="Espagne" it="Spagna"/>
<country code="lk" en="Sri Lanka" de="Sri Lanka" fr="Sri Lanka" it="Sri Lanka"/>
<country code="bl" en="St. Barthélemy" de="St. Barthélemy" fr="Saint-Barthélemy" it="Saint Barthélemy"/>
<country code="kn" en="St. Christopher (St. Kitts) and Nevis" de="St. Kitts und Nevis" fr="Saint-Christophe-et-Niévès" it="Saint Kitts e Nevis"/>
<country code="sh" en="St. Helena, Ascension and Tristan da Cunha" de="St. Helena, Ascension und Tristan da Cunha" fr="Sainte-Hélène, Ascension et Tristan da Cunha" it="SantElena, Ascensione e Tristan da Cunha"/>
<country code="lc" en="St. Lucia" de="St. Lucia" fr="Sainte-Lucie" it="Santa Lucia"/>
<country code="sx" en="St. Maarten" de="Sint Maarten" fr="Sint-Maarten" it="Sint Maarten"/>
<country code="mf" en="St. Martin" de="St. Martin" fr="Saint-Martin" it="Saint Martin"/>
<country code="pm" en="St. Pierre and Miquelon" de="St. Pierre und Miquelon" fr="Saint-Pierre-et-Miquelon" it="Saint-Pierre e Miquelon"/>
<country code="st" en="St. Tome and Principe" de="São Tomé und Príncipe" fr="Sao Tomé-et-Principe" it="São Tomé e Príncipe"/>
<country code="vc" en="St. Vincent and the Grenadines" de="St. Vincent und die Grenadinen" fr="Saint-Vincent-et-les-Grenadines" it="Saint Vincent e Grenadine"/>
<country code="sd" en="Sudan" de="Sudan" fr="Soudan" it="Sudan"/>
<country code="sr" en="Suriname" de="Suriname" fr="Suriname" it="Suriname"/>
<country code="sj" en="Svalbard and Jan Mayen Island" de="Svalbard und Jan Mayen-Insel" fr="Svalbard et Jan Mayen" it="Svalbard e Jan Mayen"/>
<country code="sz" en="Swaziland" de="Eswatini" fr="Swaziland" it="Eswatini"/>
<country code="se" en="Sweden" de="Schweden" fr="Suède" it="Svezia"/>
<country code="ch" en="Switzerland" de="Schweiz" fr="Suisse" it="Svizzera"/>
<country code="sy" en="Syria" de="Syrien" fr="Syrie" it="Siria"/>
<country code="tw" en="Taiwan (Chinese Taipei)" de="Taiwan (Chinesisches Taipei)" fr="Taïwan (Taipei chinois)" it="Taiwan (Taipei cinese)"/>
<country code="tj" en="Tajikistan" de="Tadschikistan" fr="Tadjikistan" it="Tagikistan"/>
<country code="tz" en="Tanzania" de="Tansania" fr="Tanzanie" it="Tanzania"/>
<country code="th" en="Thailand" de="Thailand" fr="Thaïlande" it="Thailandia"/>
<country code="tl" en="Timor-Leste" de="Timor-Leste" fr="Timor-Leste" it="Timor-Leste"/>
<country code="tg" en="Togo" de="Togo" fr="Togo" it="Togo"/>
<country code="tk" en="Tokelau" de="Tokelau" fr="Tokélaou" it="Tokelau"/>
<country code="to" en="Tonga" de="Tonga" fr="Tonga" it="Tonga"/>
<country code="tt" en="Trinidad and Tobago" de="Trinidad und Tobago" fr="Trinité-et-Tobago" it="Trinidad e Tobago"/>
<country code="tn" en="Tunisia" de="Tunesien" fr="Tunisie" it="Tunisia"/>
<country code="tr" en="Turkey" de="Türkiye" fr="Turquie" it="Turchia"/>
<country code="tm" en="Turkmenistan" de="Turkmenistan" fr="Turkménistan" it="Turkmenistan"/>
<country code="tc" en="Turks and Caicos" de="Turks- und Caicosinseln" fr="Turks-et-Caïcos" it="Isole Turks e Caicos"/>
<country code="tv" en="Tuvalu" de="Tuvalu" fr="Tuvalu" it="Tuvalu"/>
<country code="ug" en="Uganda" de="Uganda" fr="Ouganda" it="Uganda"/>
<country code="ua" en="Ukraine" de="Ukraine" fr="Ukraine" it="Ucraina"/>
<country code="ae" en="United Arab Emirates" de="Vereinigte Arabische Emirate" fr="Émirats arabes unis" it="Emirati Arabi Uniti"/>
<country code="um" en="United States Minor Outlying Islands" de="United States Minor Outlying Islands" fr="Îles mineures éloignées des États-Unis" it="Isole Minori Esterne degli Stati Uniti"/>
<country code="us" en="United States of America" de="Vereinigte Staaten von Amerika" fr="États-Unis dAmérique" it="Stati Uniti"/>
<country code="uy" en="Uruguay" de="Uruguay" fr="Uruguay" it="Uruguay"/>
<country code="uz" en="Uzbekistan" de="Usbekistan" fr="Ouzbékistan" it="Uzbekistan"/>
<country code="vu" en="Vanuatu" de="Vanuatu" fr="Vanuatu" it="Vanuatu"/>
<country code="va" en="Vatican City State" de="Vatikanstadt" fr="Saint-Siège (Cité du Vatican)" it="Città del Vaticano"/>
<country code="ve" en="Venezuela" de="Venezuela" fr="Venezuela" it="Venezuela"/>
<country code="vn" en="Vietnam" de="Vietnam" fr="Vietnam" it="Vietnam"/>
<country code="vi" en="Virgin Islands (USA)" de="Jungferninseln (USA)" fr="Îles Vierges américaines" it="Isole Vergini"/>
<country code="vg" en="Virgin Islands, British (Tortola)" de="British Virgin Islands (Tortola)" fr="Îles Vierges britanniques (Tortola)" it="Isole Vergini britanniche"/>
<country code="wf" en="Wallis and Futuna Islands" de="Wallis und Futuna" fr="Îles Wallis-et-Futuna" it="Wallis e Futuna"/>
<country code="eh" en="Western Sahara" de="Westsahara" fr="Sahara occidental" it="Sahara occidentale"/>
<country code="ws" en="Western Samoa" de="Samoa" fr="Samoa" it="Samoa occidentale"/>
<country code="ye" en="Yemen" de="Jemen" fr="Yémen" it="Yemen"/>
<country code="zm" en="Zambia" de="Sambia" fr="Zambie" it="Zambia"/>
<country code="zw" en="Zimbabwe" de="Simbabwe" fr="Zimbabwe" it="Zimbabwe" />
</country-names>

View File

@ -0,0 +1,225 @@
<Domain inactiveInterval="30" reauthInterval="0" statelessAuth="true">
<Entry method="authenticate" state="Check_Trusted_Caller"/>
<Entry method="stepup" state="STS_Audit_Failure"/>
</Domain>
<!-- ***** Authenticate Caller ***** -->
<AuthState name="Check_Trusted_Caller" class="ch.nevis.esauth.auth.states.cache.ReadFromCacheState" final="false">
<ResultCond name="ok" next="Dispatcher_TokenType"/>
<ResultCond name="miss" next="Validation_Client_Cert"/>
<Response value="AUTH_ERROR" />
<property name="cacheSpace" value="TechAuthCache"/>
<property name="hashAlgorithm" value="SHA-512"/>
<property name="sess:agov.techuser.extId" value="${param.cert.source}"/>
</AuthState>
<AuthState name="Validation_Client_Cert" class="ch.nevis.idm.authstate.IdmX509State" final="false" resumeState="true">
<ResultCond name="default" next="STS_Audit_Failure"/>
<ResultCond name="ok" next="Validation_Client_Cert_PostProcessing"/>
<Response value="AUTH_ERROR">
<Gui name="AuthErrorDialog">
<GuiElem name="lasterror" type="error" label="#{notes.containsKey('lasterror') ? 'error.login.cert.' : ''}#{notes['lasterror']}"/>
</Gui>
</Response>
<propertyRef name="nevisIDM_Connector"/>
<property name="user.certificate" value="${param.cert.source}"/>
<property name="client.name" value="${param.techuser.client.name}"/>
</AuthState>
<AuthState name="Validation_Client_Cert_PostProcessing" class="ch.nevis.idm.authstate.IdmGetPropertiesState" final="false" resumeState="true">
<ResultCond name="default" next="STS_Audit_Failure"/>
<ResultCond name="ok" next="Check_Impersonator"/>
<propertyRef name="nevisIDM_Connector"/>
<property name="detaillevel.default" value="EXCLUDE"/>
<property name="chooseDefaultProfile" value="true"/>
</AuthState>
<AuthState name="Check_Impersonator" class="ch.nevis.esauth.auth.states.standard.ConditionalDispatcherState" final="false">
<ResultCond name="isImpersonator" next="Clear_Session"/>
<ResultCond name="default" next="STS_Audit_Failure"/>
<Response value="AUTH_ERROR">
<Arg name="ch.nevis.isiweb4.response.status" value="403"/>
</Response>
<property name="condition:isImpersonator" value="${response/actualRoles/^.*(nevisIdm\.Impersonator).*$}"/>
</AuthState>
<AuthState name="Clear_Session" class="ch.nevis.esauth.auth.states.standard.TransformAttributes" final="false">
<ResultCond name="ok" next="Cache_Trusted_Caller"/>
<Response value="AUTH_ERROR">
<Arg name="ch.nevis.isiweb4.response.status" value="403"/>
</Response>
<property name="sess:agov.techuser.extId" value="${sess:ch.adnovum.nevisidm.user.extId}"/>
<property name="sess:ch.adnovum.nevisidm.clientExtId" value=""/>
<property name="sess:ch.adnovum.nevisidm.clientId" value=""/>
<property name="sess:ch.adnovum.nevisidm.clientName" value=""/>
<property name="sess:ch.adnovum.nevisidm.profileExtId" value=""/>
<property name="sess:ch.adnovum.nevisidm.profileId" value=""/>
<property name="sess:ch.adnovum.nevisidm.profileName" value=""/>
<property name="sess:ch.adnovum.nevisidm.user.clientExtId" value=""/>
<property name="sess:ch.adnovum.nevisidm.user.extId" value=""/>
<property name="sess:ch.adnovum.nevisidm.user.loginId" value=""/>
<property name="sess:ch.adnovum.nevisidm.userDto" value=""/>
<property name="sess:ch.adnovum.nevisidm.userExtId" value=""/>
<property name="sess:ch.nevis.idm.User.extId" value=""/>
<property name="removeOnEmptyValue" value="true"/>
</AuthState>
<AuthState name="Cache_Trusted_Caller" class="ch.nevis.esauth.auth.states.cache.WriteToCacheState" final="false">
<ResultCond name="ok" next="Dispatcher_TokenType"/>
<ResultCond name="failed" next="STS_Audit_Failure"/>
<Response value="AUTH_ERROR" />
<property name="cacheSpace" value="TechAuthCache"/>
<property name="hashAlgorithm" value="SHA-512"/>
<!-- maxAge: 1 hour -->
<property name="maxAge" value="3600"/>
<!-- maxEntries: 2 as we have only 1 tech user, which should use that service -->
<property name="maxEntries" value="2"/>
<property name="overwriteOldEntries" value="false"/>
<property name="${param.cert.source}" value="${sess:agov.techuser.extId}"/>
</AuthState>
<!-- ***** Dispatch Requests ***** -->
<AuthState name="Dispatcher_TokenType" class="ch.nevis.esauth.auth.states.standard.ConditionalDispatcherState" final="false">
<ResultCond name="SamlAssertion" next="Service_Provider_State"/>
<ResultCond name="checkOblCode" next="Verify_Shadow_User"/>
<ResultCond name="usernameToken" next="Verify_User_extID"/>
<ResultCond name="default" next="STS_Audit_Failure"/>
<Response value="AUTH_ERROR">
<Arg name="ch.nevis.isiweb4.response.status" value="403"/>
</Response>
<property name="condition:SamlAssertion" value="${request:currentResource:/nevisauth/services/sts/saml:true}"/>
<property name="condition:checkOblCode" value="${request:currentResource:/nevisauth/services/sts/check:true}"/>
<property name="condition:usernameToken" value="${request:currentResource:/nevisauth/services/sts/username:true}"/>
</AuthState>
<!-- ***** SAML Assertion to Token, and usernameToken ***** -->
<AuthState name="Service_Provider_State" class="ch.nevis.esauth.auth.states.saml.ServiceProviderState" final="false" resumeState="true">
<ResultCond name="default" next="STS_Audit_Failure"/>
<ResultCond name="ok" next="Verify_User_extID" authLevel="auth.weak"/>
<property name="consumerURL" value="${param.saml.assertion.acsurl}"/>
<property name="in.verify" value="Assertion"/>
<property name="in.internalBindingSource" value="${inargs:SAMLAssertion}"/>
<property name="in.binding" value="internal-assertion"/>
<property name="in.max_age" value="${param.saml.assertion.max_age}"/>
<property name="in.audience" value="${param.saml.assertion.audience}"/>
<property name="in.keystoreref" value="${keystore}"/>
<property name="in.prospectVerification" value="SubjectConfirmation"/>
<property name="out.sign" value="none"/>
<property name="out.binding" value="none"/>
<property name="out.ttl" value="30"/>
<property name="out.issuer" value="not-used"/>
</AuthState>
<AuthState name="Verify_User_extID" class="ch.nevis.idm.authstate.IdmUserVerifyState" final="false" resumeState="true">
<ResultCond name="clientNotFound" next="STS_Audit_Failure"/>
<ResultCond name="failed" next="STS_Audit_Failure"/>
<ResultCond name="prospect" next="Verify_User_extID_IdmGetPropertiesState"/>
<Response value="AUTH_ERROR">
<Gui name="AuthFailDialog"/>
</Response>
<propertyRef name="nevisIDM_Connector"/>
<property name="userExtId" value="${inargs:UserID}"/>
<property name="client.name" value="${param.accounts.client.name}"/>
</AuthState>
<AuthState name="Verify_User_extID_IdmGetPropertiesState" class="ch.nevis.idm.authstate.IdmGetPropertiesState" final="false" resumeState="true">
<ResultCond name="SOAP:showGui" next="STS_Audit_Success"/>
<ResultCond name="default" next="STS_Audit_Failure"/>
<ResultCond name="ok" next="STS_Audit_Success"/>
<Response value="AUTH_CONTINUE">
<Gui name="AuthProfileSelectionDialog">
<GuiElem name="lasterror" type="error" label="${notes:lasterrorinfo}" value="${notes:lasterror}"/>
</Gui>
</Response>
<propertyRef name="nevisIDM_Connector"/>
<property name="user.attributes" value="loginId,extId,firstName,name,email,gender,birthDate,language,sex,addressLine1,postalCode,city,country,street,houseNumber,locality"/>
<property name="chooseDefaultProfile" value="true"/>
</AuthState>
<!-- ***** Ceck Obl Code ***** -->
<AuthState name="Verify_Shadow_User" class="ch.nevis.idm.authstate.IdmPasswordVerifyState" final="false" resumeState="false">
<ResultCond name="cancel" next="Verify_Shadow_User_Error"/>
<ResultCond name="clientNotFound" next="Verify_Shadow_User_Error"/>
<ResultCond name="disabled" next="Verify_Shadow_User_Error"/>
<ResultCond name="failed" next="Verify_Shadow_User_Error"/>
<ResultCond name="lockWarn" next="Verify_Shadow_User_Error"/>
<ResultCond name="locked" next="Verify_Shadow_User_Error"/>
<ResultCond name="nowLocked" next="Verify_Shadow_User_Error"/>
<ResultCond name="ok" next="Verify_Shadow_User_DeleteCredential" authLevel="auth.weak"/>
<ResultCond name="pwChange" next="Verify_Shadow_User_Error"/>
<ResultCond name="tmpLocked" next="Verify_Shadow_User_Error"/>
<Response value="AUTH_ERROR">
<Gui name="ErrorDialog" label="error">
<GuiElem name="lasterror" type="error" label="${notes:lasterrorinfo}" value="${notes:lasterror}"/>
<GuiElem name="isiwebpasswd" type="pw-text" label="not-used" value="just-ot-hide-it-in-logs" optional="true" />
</Gui>
</Response>
<propertyRef name="nevisIDM_Connector"/>
<property name="user.loginType" value="LOGINID"/>
<property name="credential.type" value="contextPassword"/>
<property name="credential.context" value="AGOV"/>
<property name="client.name" value="${param.shadow-accounts.client.name}"/>
<property name="user.loginId" value="${inargs:isiwebuserid}"/>
<property name="user.password" value="${inargs:isiwebpasswd}"/>
<property name="detaillevel.user" value="MEDIUM"/>
<property name="detaillevel.profile" value="LOW"/>
<property name="detaillevel.property" value="MEDIUM"/>
<property name="detaillevel.credential" value="MEDIUM"/>
<property name="detaillevel.certificate" value="MEDIUM"/>
<property name="detaillevel.default" value="EXCLUDE"/>
</AuthState>
<AuthState name="Verify_Shadow_User_Error" class="ch.nevis.esauth.auth.states.standard.AuthLogout" final="true" resumeState="true">
<Response value="AUTH_ERROR">
<Gui name="ErrorDialog" label="error">
<GuiElem name="lasterror" type="error" label="${notes:lasterrorinfo}" value="${notes:lasterror}"/>
</Gui>
</Response>
</AuthState>
<AuthState name="Verify_Shadow_User_DeleteCredential" class="ch.nevis.idm.authstate.IdmDeleteCredentialState" final="false" resumeState="true">
<ResultCond name="failed" next="STS_Audit_Success"/>
<ResultCond name="noCredential" next="STS_Audit_Success"/>
<ResultCond name="ok" next="STS_Audit_Success"/>
<Response value="AUTH_ERROR"/>
<propertyRef name="${realm}_Verify_Shadow_User"/>
<property name="cred.context" value="AGOV"/>
<property name="cred.type" value="CONTEXT_PASSWORD"/>
</AuthState>
<!-- ***** Terminal States ***** -->
<AuthState name="STS_Audit_Success" class="ch.nevis.esauth.auth.states.scripting.ScriptState" final="false">
<ResultCond name="error" next="Authentication_Failed"/>
<ResultCond name="ok" next="Auth_Done"/>
<Response value="AUTH_ERROR">
<Arg name="ch.nevis.isiweb4.response.status" value="403"/>
</Response>
<property name="scriptTraceGroup" value="AGOV-ACCT"/>
<property name="script" value="file:///var/opt/nevisauth/default/conf/sts_audit_success.groovy"/>
</AuthState>
<AuthState name="Auth_Done" class="ch.nevis.esauth.auth.states.standard.AuthDone" final="false">
<Response value="AUTH_DONE">
<Gui name="ContinueResponse"/>
</Response>
</AuthState>
<AuthState name="STS_Audit_Failure" class="ch.nevis.esauth.auth.states.scripting.ScriptState" final="false">
<ResultCond name="error" next="Authentication_Failed"/>
<ResultCond name="ok" next="Authentication_Failed"/>
<Response value="AUTH_ERROR">
<Arg name="ch.nevis.isiweb4.response.status" value="403"/>
</Response>
<property name="scriptTraceGroup" value="AGOV-ACCT"/>
<property name="script" value="file:///var/opt/nevisauth/default/conf/sts_audit_failure.groovy"/>
</AuthState>
<AuthState name="Authentication_Failed" class="ch.nevis.esauth.auth.states.standard.AuthError" final="false">
<Response value="AUTH_ERROR">
<Gui name="Error">
<GuiElem name="info" type="error" label="error_99"/>
<GuiElem name="submit" type="button" label="continue.button.label"/>
</Gui>
</Response>
</AuthState>

View File

@ -0,0 +1,17 @@
try {
def user = inargs['UserID'] ?: session['ch.adnovum.nevisidm.user.extId'] ?: 'unknown'
def techuser = session['agov.techuser.extId'] ?: 'unknown'
def sourceIp = request.getTransportLayerInformation().getRemoteIP() ?: 'unknown'
def credentialType = request.getResource().replaceAll("\\/nevisauth\\/services\\/sts\\/(.+)\\/", "\$1").toUpperCase()
def lasterrorinfo = notes.getProperty('lasterrorinfo', '-')
def lasterror = notes.getProperty('lasterror', '-')
if (credentialType=='SAML') {
credentialType = 'PASSWORD'
}
LOG.warn("Event='TKNFAILED', Techuser=${techuser}, User=${user}, CredentialType='${credentialType}', SourceIp=${sourceIp}, lasterrorinfo='${lasterrorinfo}', lasterror=${lasterror}")
} catch (Exception e) {
LOG.warn("Exception in Script: ${e}")
} finally {
response.setResult('ok')
}

View File

@ -0,0 +1,16 @@
try {
def user = inargs['UserID'] ?: session['ch.adnovum.nevisidm.user.extId'] ?: 'unknown'
def techuser = session['agov.techuser.extId'] ?: 'unknown'
def sourceIp = request.getTransportLayerInformation().getRemoteIP() ?: 'unknown'
def credentialType = request.getResource().replaceAll("\\/nevisauth\\/services\\/sts\\/(.+)\\/", "\$1").toUpperCase()
if (credentialType=='SAML') {
credentialType = 'PASSWORD'
}
LOG.info("Event='TKNISSUED', Techuser=${techuser}, User=${user}, CredentialType='${credentialType}', SourceIp=${sourceIp}")
} catch (Exception e) {
LOG.warn("Exception in Script: ${e}")
} finally {
response.setResult('ok')
}

View File

@ -0,0 +1,43 @@
<AuthState name="${state.entry}" class="ch.nevis.esauth.auth.states.saml.IdentityProviderState" final="false" resumeState="false">
<ResultCond name="ok" next="${state.entry}_Handle_Redirect"/>
<Response value="AUTH_ERROR">
<Gui name="internal_error">
<GuiElem name="transferId" type="hidden" value="${request:traceId}" optional="true"/>
</Gui>
</Response>
<property name="in.binding" value="none"/>
<property name="out.binding" value="internal"/>
<property name="out.sign" value="Response Assertion"/>
<property name="out.signatureKeyInfo" value="Certificate"/>
<!-- assertion validity time -->
<property name="out.ttl" value="${param.assertionValidityTime}"/>
<!-- subject confirmation: Bearer -->
<property name="out.extension.Bearer" value="ch.nevis.esauth.auth.states.saml.extensions.SubjectConfirmationExtender"/>
<property name="Bearer.ttl" value="${param.assertionValidityTime}"/>
<property name="out.keystoreref" value="Store_IDP_AGOV"/>
<property name="out.keyobjectref" value="Signer_IDP_AGOV"/>
<property name="spURL" value="${param.agovmedirecturl}"/>
<property name="acsUrlWhitelist.uris" value="not used"/>
<!-- adttributes -->
<property name="out.authnContextClassRef" value="${sess:agov.recovery.authnContextClassRef}"/>
<property name="out.attribute.http://schemas.agov.ch/ws/2023/05/identity/claims/authenticatedWith" value="${sess:agov.recovery.authenticatedWith}"/>
<property name="out.attribute.http://schemas.agov.ch/ws/2023/11/identity/claims/currentAgovAq" value="${sess:agov.recovery.currentAgovAq}"/>
<property name="out.attribute.http://schemas.agov.ch/ws/2024/01/identity/claims/currentIdVerification" value="${sess:agov.recovery.currentIdVerification}"/>
<property name="out.attribute.http://schemas.agov.ch/ws/2023/05/identity/claims/qa/dateOfVerification"
value="${sess:agov.recovery.currentAgovAqRoleValidFrom}"/>
<property name="out.attributeDelimiter" value=",\s*" />
<property name="out.subject" value="${sess:ch.adnovum.nevisidm.user.extId}"/>
<property name="out.subject.format" value="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"/>
<property name="out.issuer" value="${param.issuer}"/>
<property name="out.audienceRestriction" value="${param.directAudience}"/>
</AuthState>
<AuthState name="${state.entry}_Handle_Redirect" class="ch.nevis.esauth.auth.states.scripting.ScriptState" final="false" resumeState="true">
<ResultCond name="ok" next="${state.done}"/>
<Response value="AUTH_CONTINUE">
<Gui name="not_used"/>
</Response>
<property name="scriptTraceGroup" value="AGOV-ACCT"/>
<property name="parameter.agovmedirecturl" value="${param.agovmedirecturl}"/>
<property name="script" value="file:///var/opt/nevisauth/default/conf/handleRedirectRecovery.groovy"/>
</AuthState>

View File

@ -0,0 +1,23 @@
if(outargs.containsKey('saml.SAMLResponse')) {
// 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'
LOG.info("Event='GOTORECOVERY', Requester='${requester}', RequestId='${requestId}', RequestedAq=${requestedAq}, User=${user}, CredentialType='${credentialType}', SourceIp=${sourceIp}, UserAgent='${userAgent}'")
// Redirect
response.addOutArg('nevis.transfer.destination', parameters.get('agovmedirecturl'))
response.addOutArg('nevis.transfer.field.SAMLResponse', outargs.getProperty('saml.SAMLResponse').bytes.encodeBase64().toString())
response.setStatus(ch.nevis.esauth.auth.engine.AuthResponse.AUTH_CONTINUE)
response.setIsRedirectTransfer(false)
response.removeOutArg('saml.SAMLResponse')
}
else {
response.setResult('ok')
}

View File

@ -0,0 +1,20 @@
<AuthState name="${state.entry}" class="ch.nevis.esauth.auth.states.standard.ConditionalDispatcherState" final="true" resumeState="true">
<ResultCond name="default" next="${state.entry}"/>
<ResultCond name="noEmail" next="${state.entry}_noEmail"/>
<ResultCond name="hasCode" next="${state.done}"/>
<Response value="AUTH_CONTINUE">
<Gui name="recovery_intro_email_sent">
<GuiElem name="authRequestId" type="hidden" value="${sess:ch.nevis.auth.saml.request.id}" optional="true"/>
<GuiElem name="noEmail" type="submit" label="not used" value="not used" optional="true"/>
</Gui>
</Response>
<property name="condition:noEmail" value="#{inargs.containsKey('noEmail')}"/>
<property name="condition:hasCode" value="#{inargs.containsKey('cd')}"/>
</AuthState>
<AuthState name="${state.entry}_noEmail" class="ch.nevis.esauth.auth.states.standard.TransformAttributes" final="false" resumeState="false">
<ResultCond name="default" next="${state.failed}"/>
<Response value="AUTH_CONTINUE"/>
<property name="sess:agov.recovery.code" value=""/>
<property name="removeOnEmptyValue" value="true"/>
<property name="notes:invalidUrlTicket" value="not received"/>
</AuthState>

View File

@ -0,0 +1,17 @@
function outputHeader(request, response)
trace = request:getTracer()
-- after successful authentication neviauth returns the SecToken as attribute to the proxy
secToken = request:getAttribute("ch.nevis.isiweb4.auth.SecToken")
if secToken then
trace:debug("SessionInvalidationFilter: SecToken part of the attributes returned from nevisAuth: "..secToken)
session = request:getSession(false)
if session then
session:invalidate()
trace:info("SessionInvalidationFilter: Session invalidated after successful authentication")
else
trace:debug("SessionInvalidationFilter: SecToken but no session, nothing to do")
end
else
trace:debug("SessionInvalidationFilter: No SecToken, nothing to do")
end
end

View File

@ -0,0 +1,129 @@
import groovy.xml.XmlSlurper
import groovy.json.JsonSlurper
//import ch.nevis.esauth.util.httpclient.api.HttpClients
//import ch.nevis.esauth.util.httpclient.api.Http
int getRequestedLevel(String authnContextClassRef, def roleList){
if (!authnContextClassRef) {
return 100
}
if (authnContextClassRef && authnContextClassRef.startsWith('urn:qa.agov.ch:names:tc:ac:classes:')) {
def requestedLevel = authnContextClassRef.substring(35)
LOG.debug('authnContextClassRef agov found: ' + requestedLevel)
if (requestedLevel.isNumber()) {
int requestedLevelNumber = Integer.parseInt(requestedLevel)
LOG.debug('contains ' + roleList.contains(requestedLevelNumber))
if (requestedLevel.isNumber() && roleList.contains(requestedLevelNumber)) {
LOG.debug('Requested role number: ' + requestedLevel)
return requestedLevelNumber
}
}
else return 0
}
else {
return 0
}
}
def session = request.getAuthSession(true)
def context = session.get('ch.nevis.auth.saml.request.authnContextClassRef')
def roleLevels = [100,200,300,400]
def requestedRoleLevelNumber = getRequestedLevel(context, roleLevels)
//set attribute Requested Role Level
session.setAttribute('agov.requestedRoleLevel', '' + requestedRoleLevelNumber)
LOG.debug('Requested role level (agov) '+ requestedRoleLevelNumber)
// SAML finisherstate is now available, we can backup it
session.setAttribute('agov.backup.finishers', '' + session.getAttribute('ch.nevis.session.finishers'))
// Accounting
def requester = session['ch.nevis.auth.saml.request.scoping.requesterId'] ?: 'unknown'
def requestId = session['ch.nevis.auth.saml.request.id'] ?: 'unknown'
def replacedRequestId = session['agov.replacedRequestId'] ?: '-'
def requestedAq = session['agov.requestedRoleLevel'] ?: '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'
LOG.info("Event='AUTHREQUEST', Requester='${requester}', RequestId='${requestId}', ReplacedRequestId='${replacedRequestId}', RequestedAq=${requestedAq}, SourceIp=${sourceIp}, UserAgent='${userAgent}'")
def appAddressRequiredWhitelist = ',' + (parameters.get('appAddressRequired.whitelist') ?: '').replaceAll('\\s','') + ','
def appIsOnappAddressRequiredWhitelist = appAddressRequiredWhitelist.contains(','+requester+',')
if (requestedRoleLevelNumber == 0 || session.get('ch.nevis.auth.saml.request.scoping.requesterId') == null) {
response.setResult('error');
return
}
try {
def jsonSlurper = new JsonSlurper()
def url = parameters.get('url') + '?entity-id=' + session.get('ch.nevis.auth.saml.request.scoping.requesterId')
LOG.debug('Request url: ' + url)
def httpClient = HttpClients.create(parameters)
def httpResponse = Http.get().url(url).build().send(httpClient)
LOG.debug('Response Message: ' + httpResponse.reasonPhrase())
LOG.debug('Response Status Code: ' + httpResponse.code())
LOG.debug('Response: ' + httpResponse.bodyAsString())
if (httpResponse.code() == 200) {
def json = jsonSlurper.parseText(httpResponse.bodyAsString())
LOG.debug('AdressRequired: ' + json.addrRequired)
LOG.debug('SvnrAllowed: ' + json.svnrAllowed)
LOG.debug('appAddressRequiredWhitelist applies: ' + appIsOnappAddressRequiredWhitelist)
// address will be returned to the application if allowed by connect (json.addrRequired)
// and the authRequest was done with at least AGOVaq 200
// BITBKAGOVSUP-362: or whitelisted to receive the address
session.setAttribute('agov.appAddressRequired', '' + (json.addrRequired && ((requestedRoleLevelNumber >= 200) || appIsOnappAddressRequiredWhitelist)))
// address will be returned to the application if allowed by connect (json.svnrAllowed)
// and the authRequest was done with at least AGOVaq 300
session.setAttribute('agov.appSvnrAllowed', '' + (json.svnrAllowed && requestedRoleLevelNumber >= 300))
session.setAttribute('agov.appDisplayNameDE', '' + json.displayNameDe)
session.setAttribute('agov.appDisplayNameFR', '' + json.displayNameFr)
session.setAttribute('agov.appDisplayNameIT', '' + json.displayNameIt)
session.setAttribute('agov.appDisplayNameEN', '' + json.displayNameEn)
response.setResult('ok')
return
} else {
LOG.warn("Failed to fetch connect meta data for relying party '${session.get('ch.nevis.auth.saml.request.scoping.requesterId')}'")
LOG.warn('Unexcpected HTTP response code: ' + httpResponse.code())
if ( requestedRoleLevelNumber == 100) {
session.setAttribute('agov.appAddressRequired', '' + appIsOnappAddressRequiredWhitelist)
session.setAttribute('agov.appSvnrAllowed', 'false')
response.setResult('ok')
}
else if ( requestedRoleLevelNumber == 200) {
session.setAttribute('agov.appAddressRequired', 'true')
session.setAttribute('agov.appSvnrAllowed', 'false')
response.setResult('ok')
}
else {
response.setResult('error')
response.setError(9071, "Missing meta data for relying party, can't process request")
}
return
}
} catch (Exception e) {
LOG.error("Failed to fetch connect meta data for relying party '${session.get('ch.nevis.auth.saml.request.scoping.requesterId')}'", e)
if ( requestedRoleLevelNumber == 100) {
session.setAttribute('agov.appAddressRequired', '' + appIsOnappAddressRequiredWhitelist)
session.setAttribute('agov.appSvnrAllowed', 'false')
response.setResult('ok')
}
else if ( requestedRoleLevelNumber == 200) {
session.setAttribute('agov.appAddressRequired', 'true')
session.setAttribute('agov.appSvnrAllowed', 'false')
response.setResult('ok')
}
else {
response.setResult('error')
response.setError(9072, "Failure while processing meta data for relying party, can't continue processing request")
}
return
}

View File

@ -0,0 +1,101 @@
def url = parameters.get('url')
def email = inargs['userInputValue_prompt.email']
def token = inargs['captcha_response']?: 'MISSING'
def ip = request.getLoginContext()['connection.HttpHeader.X-Real-IP'] ?: 'unknown'
def userAgent = request.getLoginContext()['connection.HttpHeader.user-agent'] ?: request.getLoginContext()['connection.HttpHeader.User-Agent'] ?: 'unknown'
def payload = "{ \"userIp\": \"${ip}\", \"email\": \"${email}\", \"userAgent\": \"${userAgent}\" }"
LOG.debug('Token: ' + token)
LOG.debug('Payload: ' + payload)
try {
def httpClient = HttpClients.create(parameters)
def httpResponse = Http.post()
.url(url)
.header("Accept", "application/json")
.header("X-FriendlyCAPTCHA-Token", token)
.entity(Http.entity()
.content(payload)
.contentType("application/json")
.build())
.build()
.send(httpClient)
LOG.debug('Response Message: ' + httpResponse.reasonPhrase())
LOG.debug('Response Status Code: ' + httpResponse.code())
LOG.debug('Response: ' + httpResponse.bodyAsString())
if (httpResponse.code() == 200) {
if (httpResponse.bodyAsString().contains('SUCCESSFUL')) {
response.setResult('ok')
return
} else {
LOG.warn("Friendly captcha not successful for '{ \"userIp\": \"${ip}\", \"email\": \"${email}\", \"userAgent\": \"${userAgent}\" }'")
response.setResult('exit.1')
return
}
} else {
LOG.error("Friendly captcha failed with statuscode ${httpResponse.code()} for '{ \"userIp\": \"${ip}\", \"email\": \"${email}\", \"userAgent\": \"${userAgent}\" }'")
response.setResult('error')
response.setError(1, 'Unexpected HTTP reponse')
}
} catch (all) {
// Handle exception and set the transition
LOG.error("Friendly captcha failed with a general error '${all}' for '{ \"userIp\": \"${ip}\", \"email\": \"${email}\", \"userAgent\": \"${userAgent}\" }', service-url: ${url}")
response.setResult('error')
response.setError(1, 'Exception during HTTP call')
}
// TODO/haburger/2024-AUG-20: remove if reCaptcha is not needed anymore
//
// def payload = '{ "email": "' + inargs['userInputValue_prompt.email'] + '", "action": "LOGIN", "userIp": "' + ip + '", "userAgent": "' + userAgent + '"}'
//
// LOG.info('Token: ' + inargs['recaptcha_response'])
// LOG.info('Integration: ' + session['agov.fido2.X-ReCAPTCHA-Integration'])
// LOG.info('Payload: ' + payload)
//
// try {
//
// def httpClient = HttpClients.create(parameters)
// def httpResponse = Http.post()
// .url(url)
// .header("Accept", "application/json")
// .header("X-ReCAPTCHA-Token", inargs['recaptcha_response'])
// .header("X-ReCAPTCHA-Integration", session['agov.fido2.X-ReCAPTCHA-Integration'])
// .entity(Http.entity()
// .content(payload)
// .contentType("application/json")
// .build())
// .build()
// .send(httpClient)
//
// LOG.info('Response Message: ' + httpResponse.reasonPhrase())
// LOG.info('Response Status Code: ' + httpResponse.code())
// LOG.info('Response: ' + httpResponse.bodyAsString())
//
// if (httpResponse.code() == 200) {
// if (httpResponse.bodyAsString().contains('SUCCESSFUL')) {
// response.setResult('ok')
// return
// } else {
//
// response.setSessionAttribute('agov.fido2.X-ReCAPTCHA-Integration', 'VISIBLE')
// response.setResult('exit.1')
// return
// }
// } else {
// LOG.error('Unexcpected HTTP response code: ' + httpResponse.code())
// response.setResult('error')
// response.setError(1, 'Unexpected HTTP reponse')
// }
// } catch (all) {
// // Handle exception and set the transition
// LOG.error('error: ' + all, all)
// response.setResult('error')
// response.setError(1, 'Exception during HTTP call')
// }

View File

@ -0,0 +1,102 @@
def url = parameters.get('url')
def email = inargs['email']
def token = inargs['captcha_response']?: 'MISSING'
def ip = request.getLoginContext()['connection.HttpHeader.X-Real-IP'] ?: 'unknown'
def userAgent = request.getLoginContext()['connection.HttpHeader.user-agent'] ?: request.getLoginContext()['connection.HttpHeader.User-Agent'] ?: 'unknown'
def payload = "{ \"userIp\": \"${ip}\", \"email\": \"${email}\", \"userAgent\": \"${userAgent}\" }"
LOG.debug('Token: ' + token)
LOG.debug('Payload: ' + payload)
try {
def httpClient = HttpClients.create(parameters)
def httpResponse = Http.post()
.url(url)
.header("Accept", "application/json")
.header("X-FriendlyCAPTCHA-Token", token)
.entity(Http.entity()
.content(payload)
.contentType("application/json")
.build())
.build()
.send(httpClient)
LOG.debug('Response Message: ' + httpResponse.reasonPhrase())
LOG.debug('Response Status Code: ' + httpResponse.code())
LOG.debug('Response: ' + httpResponse.bodyAsString())
if (httpResponse.code() == 200) {
if (httpResponse.bodyAsString().contains('SUCCESSFUL')) {
response.setResult('ok')
return
} else {
LOG.warn("Friendly captcha not successful for '{ \"userIp\": \"${ip}\", \"email\": \"${email}\", \"userAgent\": \"${userAgent}\" }'")
response.setResult('exit.1')
return
}
} else {
LOG.error("Friendly captcha failed with statuscode ${httpResponse.code()} for '{ \"userIp\": \"${ip}\", \"email\": \"${email}\", \"userAgent\": \"${userAgent}\" }'")
response.setResult('error')
response.setError(1, 'Unexpected HTTP reponse')
}
} catch (all) {
// Handle exception and set the transition
LOG.error("Friendly captcha failed with a general error '${all}' for '{ \"userIp\": \"${ip}\", \"email\": \"${email}\", \"userAgent\": \"${userAgent}\" }', service-url: ${url}")
response.setResult('error')
response.setError(1, 'Exception during HTTP call')
}
// TODO/haburger/2024-AUG-20: remove if reCaptcha is not needed anymore
// def payload = '{ "email": "' + inargs['email'] + '", "action": "LOGIN", "userIp": "' + session.get('agov.recovery.ip') + '", "userAgent": "' + session.get('agov.recovery.userAgent') + '"}'
//
// LOG.info('Token: ' + inargs['recaptcha_response'])
// LOG.info('Integration: ' + session['agov.recovery.X-ReCAPTCHA-Integration'])
// LOG.info('Payload: ' + payload)
//
// try {
//
// def httpClient = HttpClients.create(parameters)
// def httpResponse = Http.post()
// .url(url)
// .header("Accept", "application/json")
// .header("X-ReCAPTCHA-Token", inargs['recaptcha_response'])
// .header("X-ReCAPTCHA-Integration", session['agov.recovery.X-ReCAPTCHA-Integration'])
// .entity(Http.entity()
// .content(payload)
// .contentType("application/json")
// // .charSet("utf-8")
// .build())
// .build()
// .send(httpClient)
//
// LOG.info('Response Message: ' + httpResponse.reasonPhrase())
// LOG.info('Response Status Code: ' + httpResponse.code())
// LOG.info('Response: ' + httpResponse.bodyAsString())
//
// if (httpResponse.code() == 200) {
// if (httpResponse.bodyAsString().contains('SUCCESSFUL')) {
// response.setResult('ok')
// return
// } else {
//
// response.setSessionAttribute('agov.recovery.X-ReCAPTCHA-Integration', 'VISIBLE')
// response.setResult('exit.1')
// return
// }
// } else {
// LOG.error('Unexcpected HTTP response code: ' + httpResponse.code())
// response.setResult('error')
// response.setError(1, 'Unexpected HTTP reponse')
// }
// } catch (all) {
// // Handle exception and set the transition
// LOG.error('error: ' + all, all)
// response.setResult('error')
// response.setError(1, 'Exception during HTTP call')
// }

View File

@ -0,0 +1,9 @@
<AuthState name="${state.entry}" class="ch.nevis.esauth.auth.states.scripting.ScriptState" final="false" resumeState="false">
<ResultCond name="ok" next="${state.done}"/>
<ResultCond name="continueAfterRepost" next="${state.exit.1}"/>
<Response value="AUTH_ERROR">
</Response>
<property name="scriptTraceGroup" value="AGOV-ACCT"/>
<property name="parameter.cookie.domain" value="${param.cookie.domain}"/>
<property name="script" value="file:///var/opt/nevisauth/default/conf/idp_status_check.groovy"/>
</AuthState>

View File

@ -0,0 +1,145 @@
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
}

View File

@ -0,0 +1,63 @@
<AuthState name="${state.entry}" class="ch.nevis.idm.authstate.IdmUserVerifyState" final="false" resumeState="false">
<ResultCond name="prospect" next="${state.entry}_IdmGetPropertiesState"/>
<!-- Security issue : goes to next state if client not found -->
<ResultCond name="failed" next="${state.entry}_FailedEmailState"/>
<ResultCond name="clientNotFound" next="${state.exit.1}"/>
<Response value="AUTH_ERROR">
<Gui name="internal_error">
<GuiElem name="transferId" type="hidden" value="${system:random.bytes.16}" optional="true"/>
</Gui>
</Response>
<propertyRef name="nevisIDM_Connector"/>
<property name="user.loginId" value="${inargs:userInputValue_prompt.email}"/>
<property name="user.loginType" value="EMAIL"/>
<property name="client.name" value="${param.client.name}"/>
<property name="presetNoteValues" value="false"/>
<property name="detaillevel.user" value="HIGH"/>
<property name="detaillevel.profile" value="HIGH"/>
<property name="detaillevel.role" value="MEDIUM"/>
<property name="detaillevel.authorization" value="HIGH"/>
<property name="detaillevel.dataroom" value="LOW"/>
<property name="detaillevel.credential" value="HIGH"/>
<property name="detaillevel.property" value="HIGH"/>
<property name="detaillevel.unit" value="LOW"/>
<property name="detaillevel.default" value="EXCLUDE"/>
</AuthState>
<AuthState name="${state.entry}_IdmGetPropertiesState" final="false" class="ch.nevis.idm.authstate.IdmGetPropertiesState" resumeState="false">
<ResultCond name="ok" next="${state.done}"/>
<ResultCond name="clientNotFound" next="${state.exit.1}"/>
<ResultCond name="default" next="${state.failed}"/>
<Response value="AUTH_ERROR">
<Gui name="internal_error">
<GuiElem name="transferId" type="hidden" value="${request:traceId}" optional="true"/>
</Gui>
</Response>
<propertyRef name="nevisIDM_Connector"/>
<property name="forceDataReload" value="true"/>
<!-- Returned Attributes in SecToken -->
<property name="user.attributes" value="${param.attributes}"/>
<property name="user.properties" value="${param.properties}"/>
<property name="user.cred.context_password1.state" value="true"/>
<property name="user.cred.context_password1.context" value="true"/>
<property name="userExtId" value="${sess:ch.nevis.session.userid}"/>
<property name="chooseDefaultProfile" value="true"/>
<property name="client.name" value="${param.client.name}"/>
<property name="detaillevel.user" value="HIGH"/>
<property name="detaillevel.profile" value="HIGH"/>
<property name="detaillevel.role" value="MEDIUM"/>
<property name="detaillevel.authorization" value="HIGH"/>
<property name="detaillevel.dataroom" value="LOW"/>
<property name="detaillevel.credential" value="HIGH"/>
<property name="detaillevel.property" value="HIGH"/>
<property name="detaillevel.unit" value="LOW"/>
<property name="detaillevel.default" value="EXCLUDE"/>
</AuthState>
<!-- Dummy authstate for failed email -->
<AuthState name="${state.entry}_FailedEmailState" class="ch.nevis.esauth.auth.states.standard.TransformAttributes" final="false">
<ResultCond name="default" next="${state.done}"/>
<Response value="AUTH_CONTINUE"/>
<property name="sess:ch.adnovum.nevisidm.user.extId" value="4a9b6cf8-0093-3416-8db8-0f063aa85d14"/>
<property name="sess:ch.nevis.idm.User.email" value="${inargs:userInputValue_prompt.email}"/>
</AuthState>

View File

@ -0,0 +1,24 @@
-----BEGIN CERTIFICATE-----
MIIEGDCCAwCgAwIBAgIBBDANBgkqhkiG9w0BAQsFADA4MQswCQYDVQQGEwJjaDEQ
MA4GA1UEChMHQWRub3Z1bTEXMBUGA1UEAxMOYml0ZWlhbS1yb290Q0EwHhcNMjAw
MzA1MTYzMDAwWhcNMzAwMzAzMTYzMDAwWjA0MQswCQYDVQQGEwJjaDEQMA4GA1UE
ChMHQWRub3Z1bTETMBEGA1UEAxMKc2lnbmVyRkVEUzCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBAJoWqbsYhNXW0mDsDJPAiTN896e4QML9qnt7FIhVKKe3
T66lT/nfOkFPUZuKejgbjFFDEDChRJf0Achq7lWGKPrNPnrTxZmU7Bcu86BER76L
4kDcGF/x03W9fgUgQ7X45CXYeq4vqfpzNC+lkZA1OxbpcXZA/4Z39Z3pm7CWXnAg
v6nFABKJ9kVAyhuPyb5yIuGHcdLL+068aVp5sxY/6HoXf889+iVFDgTwSXVYKMyZ
nZbvvd/IIod4WuiXsOspPS9yj+E9yMvtsUtChghcQ17ubo7S1P8JxAQWXngopH8Y
nDeOiesJfR2APDdg7EXWYewARSFr10GxuXoKDjLe148CAwEAAaOCAS8wggErMAkG
A1UdEwQCMAAwPwYJYIZIAYb4QgENBDIWME5ldmlzIEtleUJveCBHZW5lcmF0ZWQg
Q2VydGlmaWNhdGUgdXNpbmcgT3BlblNTTDALBgNVHQ8EBAMCA6gwHQYDVR0lBBYw
FAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBQ4zYpzY1lB5/bKeg3z1kJO
kkdYgDBoBgNVHSMEYTBfgBRRdKau0TH9VQ0E8ob0J+WyYkcs4aE8pDowODELMAkG
A1UEBhMCY2gxEDAOBgNVBAoTB0Fkbm92dW0xFzAVBgNVBAMTDmJpdGVpYW0tcm9v
dENBggkA+97eIJWmttcwEQYJYIZIAYb4QgEBBAQDAgbAMBUGA1UdEQQOMAyCCnNp
Z25lckZFRFMwDQYJKoZIhvcNAQELBQADggEBAHGHJ7DzRNdPl6Kiy4rCoQR/nhTa
VbBsAeB070NpWma2iun3Wf5zIoefbSlPoofP4tOVYUoKtMHTWCYAUnHIEg5H985y
Ym2MFY0vwgMZ+Jvcs7NCHzK9O/tN+uUjkFNLSCfzTb+K9vyF6lj4L4lQWa5++DZ6
kWPaDWvwY/NOSoIehmJupmcJlA1qxzlTc+659xoOk1WyhusNkuiOUjFrLQ+tgRnD
7dGuzJQyBV1Iy/A4IhpN2ootVgrI7NMJ2YetCq7yuipRZka3RoeVhUs8CWFfYRtc
saTCck7atYyMVlPUf03EppC18ILBmbNzYJ58KT2oQywa7+Sdsqx4+5cOOOU=
-----END CERTIFICATE-----

View File

@ -0,0 +1,22 @@
<AuthState name="${state.entry}" final="false" class="ch.nevis.idm.authstate.IdmGetPropertiesState" resumeState="false">
<ResultCond name="ok" next="${state.done}"/>
<ResultCond name="clientNotFound" next="${state.failed}"/>
<ResultCond name="default" next="${state.failed}"/>
<Response value="AUTH_ERROR">
<Gui name="internal_error">
<GuiElem name="transferId" type="hidden" value="${request:traceId}" optional="true"/>
</Gui>
</Response>
<propertyRef name="nevisIDM_Connector"/>
<property name="forceDataReload" value="true"/>
<!-- Returned Attributes in SecToken -->
<property name="user.attributes" value="${param.attributes}"/>
<property name="user.properties" value="${param.properties}"/>
<property name="userExtId" value="${sess:ch.nevis.session.userid}"/>
<property name="chooseDefaultProfile" value="true"/>
<property name="client.name" value="${param.client.name}"/>
<property name="detaillevel.profile" value="HIGH"/>
<property name="detaillevel.role" value="HIGH"/>
<property name="detaillevel.authorization" value="HIGH"/>
<property name="detaillevel.dataroom" value="HIGH"/>
</AuthState>

View File

@ -0,0 +1,7 @@
<AuthState name="${state.entry}" class="ch.nevis.esauth.auth.states.scripting.ScriptState" final="false">
<Response value="AUTH_CONTINUE">
<Gui name="NotUsed"/>
</Response>
<property name="parameter.cookie.domain" value="${param.cookie.domain}"/>
<property name="script" value="file:///var/opt/nevisauth/default/conf/returnTimeoutButKeepSession.groovy"/>
</AuthState>

View File

@ -0,0 +1,11 @@
import ch.nevis.esauth.auth.engine.AuthResponse
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

View File

@ -0,0 +1,11 @@
<AuthState name="${state.entry}" class="ch.nevis.idm.authstate.IdmCreateCredentialState" final="false">
<ResultCond name="ok" next="${state.done}"/>
<ResultCond name="failed" next="${state.failed}"/>
<ResultCond name="credentialExists" next="${state.done}"/>
<Response value="AUTH_CONTINUE">
<Gui name="NoGui"/>
</Response>
<property name="cred.type" value="url_ticket"/>
<property name="cred.modificationComment" value="New ticket ordered"/>
<property name="recreateIfExists" value="true"/>
</AuthState>

View File

@ -0,0 +1,37 @@
<AuthState name="${state.entry}" final="false" class="ch.nevis.idm.authstate.IdmGetPropertiesState" resumeState="false">
<ResultCond name="ok" next="${state.entry}_Check_new_LOA"/>
<ResultCond name="insufficientLoa" next="${state.failed}"/>
<ResultCond name="noRoleLevel" next="${state.exit.1}"/>
<Response value="AUTH_CONTINUE">
<Gui name="internal_error">
<GuiElem name="transferId" type="hidden" value="${request:traceId}" optional="true"/>
</Gui>
</Response>
<propertyRef name="nevisIDM_Connector"/>
<!-- Returned Attributes in SecToken -->
<property name="client.name" value="${param.client.name}"/>
<property name="user.attributes" value="${param.attributes}"/>
<property name="user.properties" value="${param.properties}"/>
<property name="user.cred.context_password1.state" value="true"/>
<property name="chooseDefaultProfile" value="true"/>
<property name="forceDataReload" value="true"/>
<property name="userExtId" value="${sess:ch.nevis.session.userid}"/>
<property name="detaillevel.user" value="HIGH"/>
<property name="detaillevel.profile" value="HIGH"/>
<property name="detaillevel.role" value="MEDIUM"/>
<property name="detaillevel.authorization" value="HIGH"/>
<property name="detaillevel.dataroom" value="LOW"/>
<property name="detaillevel.credential" value="HIGH"/>
<property name="detaillevel.property" value="HIGH"/>
<property name="detaillevel.unit" value="LOW"/>
<property name="detaillevel.default" value="EXCLUDE"/>
</AuthState>
<AuthState name="${state.entry}_Check_new_LOA" class="ch.nevis.esauth.auth.states.scripting.ScriptState" final="false" resumeState="false">
<ResultCond name="ok" next="${state.done}"/>
<ResultCond name="insufficientLoa" next="${state.failed}"/>
<Response value="AUTH_CONTINUE">
<Gui name="not_used"/>
</Response>
<property name="script" value="file:///var/opt/nevisauth/default/conf/checkInsufficientLoa.groovy"/>
</AuthState>

View File

@ -0,0 +1,133 @@
import groovy.xml.XmlSlurper
def getUserAGOVLoiRoles() {
// set attibutes from DTO: -> AGOVaq
def list = new XmlSlurper().parseText(session.get('ch.adnovum.nevisidm.userDto'))
return list.'**'.findAll { node -> node.name() == 'roles' && node.applicationName.text() == 'AGOV-Loi' }.collect({ node -> node.name.text() })
}
def getUserAGOVLoiIdVerification() {
// set attibutes from DTO: -> idVerification
def list = new XmlSlurper().parseText(session.get('ch.adnovum.nevisidm.userDto'))
return list.'**'.findAll {node -> node.name() == 'properties' && node.name.text() == 'idVerification' }.collect({ node -> node.value.text()})
}
def getUserAGOVLoiValidFrom(level) {
// set attibutes from DTO: -> validFrom
def payload = new XmlSlurper().parseText(session.get('ch.adnovum.nevisidm.userDto'))
return payload.'**'.find {node -> node.name() == 'authorizations' && node.role.name.text() == level}.getProperty("validFrom")
}
def getUserAGOVLoiValidTo(level) {
// set attibutes from DTO: -> validTo
def payload = new XmlSlurper().parseText(session.get('ch.adnovum.nevisidm.userDto'))
return payload.'**'.find {node -> node.name() == 'authorizations' && node.role.name.text() == level}.getProperty("validTo")
}
// 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'
try {
// beef
def session = request.getAuthSession(true)
def highestRoleLevelNumber = 0
def requestedRoleLevelNumber = session.get('agov.requestedRoleLevel').toInteger()
def hasValidatedAddress = Arrays.stream(response.getActualRoles()).filter(s -> s == 'AGOV-Loi.level200').findAny().isPresent()
LOG.debug('Requested role level '+ requestedRoleLevelNumber)
LOG.debug('idVerification: ' + getUserAGOVLoiIdVerification())
LOG.debug('hasValidatedAddress : ' + hasValidatedAddress)
session.setAttribute('idVerification', getUserAGOVLoiIdVerification().last())
session.setAttribute('agov.hasValidatedAddress', '' + hasValidatedAddress)
if (requestedRoleLevelNumber == 0) {
// AuthnFailed_Zero_RoleLvl
response.setResult('noRoleLevel');
return
}
if (session.get('ch.adnovum.nevisidm.profileExtId') == '') {
LOG.error("Event='DATAERROR', Requester='${requester}', RequestId='${requestId}', RequestedAq=${requestedAq}, User=${user}, CredentialType='${credentialType}', errorMessage='Account without Profile', SourceIp=${sourceIp}, UserAgent='${userAgent}'")
session.setAttribute('contextClassRefToSet', 'urn:qa.agov.ch:names:tc:ac:classes:100')
response.setResult('ok')
return
}
// Transform sex to number
if(session.get('ch.nevis.idm.User.gender') == 'MALE'){
session.setAttribute('ch.nevis.idm.User.gender', '1')
}
if(session.get('ch.nevis.idm.User.gender') == 'FEMALE'){
session.setAttribute('ch.nevis.idm.User.gender', '2')
}
if(session.get('ch.nevis.idm.User.gender') == 'OTHER'){
session.setAttribute('ch.nevis.idm.User.gender', '3')
}
for (String role : getUserAGOVLoiRoles()) {
if (role.startsWith('level')) {
def roleLevel = role.substring(5)
int roleLevelNumber = Integer.parseInt(roleLevel)
if (highestRoleLevelNumber == 0) {
highestRoleLevelNumber = roleLevelNumber
}
if (highestRoleLevelNumber< roleLevelNumber) {
highestRoleLevelNumber=roleLevelNumber
}
}
}
LOG.debug('Highest role Level' + highestRoleLevelNumber.toString() +' contextclassref' + requestedRoleLevelNumber.toString())
LOG.debug(' Compare' + (highestRoleLevelNumber>=requestedRoleLevelNumber))
//set attribute Actual Role Level
session.setAttribute('agov.actualRoleLevel', '' + highestRoleLevelNumber)
LOG.info('actual role level (agov) '+ highestRoleLevelNumber)
if (highestRoleLevelNumber > 0) {
// set attribute contextClassRefToSet
session.setAttribute('contextClassRefToSet','urn:qa.agov.ch:names:tc:ac:classes:' .concat(highestRoleLevelNumber.toString()))
} else {
// by default 100
session.setAttribute('contextClassRefToSet','urn:qa.agov.ch:names:tc:ac:classes:100' )
}
if (highestRoleLevelNumber>=requestedRoleLevelNumber) {
// set attribute ValidFrom and ValidTo (only for higher than 100)
if (highestRoleLevelNumber > 100) {
def validFrom = getUserAGOVLoiValidFrom('level'.concat(highestRoleLevelNumber.toString()))
def validTo = getUserAGOVLoiValidTo('level'.concat(highestRoleLevelNumber.toString()))
LOG.debug('ValidFrom :' + validFrom)
LOG.debug('ValidTo :' + validTo)
if(validFrom != '') {
session.setAttribute('ValidFrom', '' + validFrom)
}
if(validTo != '') {
session.setAttribute('ValidTo', '' + validTo)
}
}
response.setResult('ok')
return;
} else {
// Insufficient_LoaInfo
response.setResult('insufficientLoa');
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}'")
// AuthnFailed_Zero_RoleLvl
response.setResult('noRoleLevel');
return;
}

View File

@ -0,0 +1,41 @@
//import ch.nevis.esauth.util.httpclient.api.HttpClient;
//import ch.nevis.esauth.util.httpclient.api.HttpClients;
//import ch.nevis.esauth.util.httpclient.api.Http;
def url = parameters.get('url')
//def payload = parameters.get('json')
//def url = "https://me.agov-d.azure.adnovum.net:48081/utility/api/v1/email/031"
def email = inargs['email']
def language = session['ch.nevis.session.user.language'] ?: 'en'
def payload = '{ "email": "' + email + '", "language": "' + language + '"}'
try {
def httpClient = HttpClients.create(parameters)
def httpResponse = Http.post()
.url(url)
.header("Accept", "application/json")
.entity(Http.entity()
.content(payload)
.contentType("application/json")
// .charSet("utf-8")
.build())
.build()
.send(httpClient)
LOG.info('Response Message: ' + httpResponse.reasonPhrase())
LOG.info('Response Status Code: ' + httpResponse.code())
LOG.info('Response: ' + httpResponse.bodyAsString())
if (httpResponse.code() == 200) {
response.setResult('ok')
} else {
LOG.error('Unexcpected HTTP response code: ' + httpResponse.code())
response.setResult('error')
response.setError(1, 'Unexpected HTTP reponse')
}
} catch (all) {
// Handle exception and set the transition
LOG.error('error: ' + all, all)
response.setResult('error')
response.setError(1, 'Exception during HTTP call')
}

View File

@ -0,0 +1,46 @@
<AuthState name="${state.entry}" class="ch.nevis.esauth.auth.states.jwt.JWTToken" final="false" resumeState="false">
<ResultCond name="ok" next="${state.entry}_decryptCode"/>
<Response value="AUTH_ERROR"/>
<property name="token.algorithm" value="${param.token.algorithm}"/>
<property name="out.issuer" value="ag"/>
<property name="out.subject" value="${sess:ch.adnovum.nevisidm.user.extId}"/>
<property name="out.custom.sessionId" value="${sess:ch.nevis.session.conversationId}"/>
<property name="out.include.not_before" value="true"/>
<property name="out.time_to_live" value="${param.token.time_to_live}"/>
<property name="keystoreref" value="${param.token.keystoreref}"/>
<property name="keyobjectref" value="${param.token.keyobjectref }"/>
</AuthState>
<AuthState name="${state.entry}_decryptCode" class="ch.nevis.esauth.auth.states.standard.TransformAttributes" final="false" resumeState="true">
<ResultCond name="default" next="${state.entry}_Process"/>
<Response value="AUTH_CONTINUE"/>
<property name="cryptoKey" value="secret://8jzQ1+F4HHvx7/tKFYRZb2/hFmyXjzt1HXgMJz+Tb16qSMh5Yv2QNnDH0JqsXHAoqtvZu1Nlo5A="/>
<property name="cryptoAlgorithm" value="AES/CTR/PKCS5Padding"/>
<property name="${sess:agov.new.recovery.code.cipher}?notes:agov.new.recovery.code:decrypt-b64" value="${sess:agov.new.recovery.code.cipher}"/>
</AuthState>
<AuthState name="${state.entry}_Process" class="ch.nevis.esauth.auth.states.scripting.ScriptState" final="false" resumeState="false">
<ResultCond name="done" next="${state.done}"/>
<ResultCond name="encryptCode" next="${state.entry}_encryptCode"/>
<ResultCond name="failed" next="${state.failed}"/>
<Response value="AUTH_CONTINUE">
<Gui name="recovery_code" label="general.entryCode">
<GuiElem name="isiwebpasswd" type="hidden" value="#{ notes.getProperty('agov.new.recovery.code', inargs.getProperty('isiwebpasswd')) }" optional="true"/>
<GuiElem name="validTil" type="hidden" value="${sess:agov.new.recovery.code.validTil}" optional="true"/>
<GuiElem name="pdfAuthToken" type="hidden" value="${sess:agov.new.recovery.code.pdfAuthToken}" length="4096" optional="true"/>
<GuiElem name="authRequestId" type="hidden" value="${sess:ch.nevis.auth.saml.request.id}" optional="true"/>
<GuiElem name="submit" type="button" label="continue.button.label" value="submit"/>
</Gui>
</Response>
<property name="scriptTraceGroup" value="AGOV-ACCT"/>
<property name="script" value="file:///var/opt/nevisauth/default/conf/ensureRecoveryCode.groovy"/>
<property name="parameter.utility-service.baseUrl" value="${param.utility-service.baseUrl}"/>
<!--property name="parameter.idm.baseUrl" value="https://${param.idm-service:idm.adn-agov-nevisidm-01-dev}:8989/nevisidm/api"/-->
<property name="parameter.idm.httpclient.tls.trustStoreRef" value="Ensure_Recovery_Code"/>
<property name="parameter.cookie.domain" value="${param.cookie.domain}"/>
</AuthState>
<AuthState name="${state.entry}_encryptCode" class="ch.nevis.esauth.auth.states.standard.TransformAttributes" final="false" resumeState="false">
<ResultCond name="default" next="${state.entry}_Process"/>
<Response value="AUTH_CONTINUE"/>
<property name="cryptoKey" value="secret://8jzQ1+F4HHvx7/tKFYRZb2/hFmyXjzt1HXgMJz+Tb16qSMh5Yv2QNnDH0JqsXHAoqtvZu1Nlo5A="/>
<property name="cryptoAlgorithm" value="AES/CTR/PKCS5Padding"/>
<property name="!${sess:agov.new.recovery.code.cipher}?sess:agov.new.recovery.code.cipher:encrypt-b64" value="${notes:agov.new.recovery.code}"/>
</AuthState>

View File

@ -0,0 +1,105 @@
import ch.nevis.esauth.auth.engine.AuthResponse
import ch.nevis.idm.client.IdmRestClient
import ch.nevis.idm.client.IdmRestClientFactory
import ch.nevis.idm.client.HTTPRequestWrapper
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'
IdmRestClient idmRestClient = IdmRestClientFactory.get(parameters)
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'}
// 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
}
// 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 postRequest = new HTTPRequestWrapper()
postRequest.addToHeaders('Content-Type', ['application/json'])
postRequest.setPayLoad("{\"userExtId\":\"$userExtId\",\"userSessionId\": \"$sessionId\"}".getBytes('UTF-8'))
def result = idmRestClient.postWithResponse(endPoint, postRequest)
if (result.getStatusCode() != 200) {
LOG.debug("Payload: ${new String(postRequest.getPayLoad())}")
LOG.debug("Result: ${result}")
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 ${result.getStatusCode()})")
response.setResult('failed')
return
}
def json = new JsonSlurper().parseText(new String(result.getPayLoad(), 'UTF-8'))
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)

View File

@ -0,0 +1,15 @@
schemaVersion: "1.0"
pattern:
id: "632ae3e34c70513c4d5ae882"
className: "ch.nevis.admin.v4.plugin.nevisauth.patterns.KeyObject"
name: "ATB_Key_Signer"
label: "STS"
notes: "need to add truststore"
link:
sourceProjectKey: "DEFAULT-IAM-FLORIAN"
sourcePatternId: "632ae3e34c70513c4d5ae882"
author: "florip"
lastCopied: "2023-03-30T11:10:57Z"
properties:
trustStore:
- "pattern://8052fd68f4a663629d651f7b"

View File

@ -0,0 +1,11 @@
schemaVersion: "1.0"
pattern:
id: "cdbb957d49fdc6695a978265"
className: "ch.nevis.admin.v4.plugin.nevisproxy.patterns.WebApplicationAccess"
name: "App_Icon_Application"
properties:
host:
- "pattern://1f0702aaabef60a615abf41f"
path: "/app-info/"
backends: "var://backendAppIconUrl"
allowedMethods: "GET"

View File

@ -0,0 +1,7 @@
schemaVersion: "1.0"
pattern:
id: "0eb5c0c45d7239987a22435a"
className: "ch.nevis.admin.v4.plugin.nevisauth.patterns2.AuthenticationDone"
name: "Auth_Done"
label: "UTILS"
properties: {}

View File

@ -0,0 +1,7 @@
schemaVersion: "1.0"
pattern:
id: "473f9d6b4ab9d61c1eb8c689"
className: "ch.nevis.admin.v4.plugin.nevisauth.patterns2.AuthenticationFailed"
name: "Auth_Failed"
label: "UTILS"
properties: {}

View File

@ -0,0 +1,29 @@
schemaVersion: "1.0"
pattern:
id: "4fcfadb4a5c946ead7e6e995"
className: "ch.nevis.admin.v4.plugin.nevisauth.patterns2.NevisAuthRealm"
name: "Auth_Realm_Main_IDP"
label: "AUTH"
notes: "Every normal authentication on the IdP starts with a SAMLRequest.\nIf such\
\ a request is present in the ianrgs, and another still ongoing (request.id in\
\ the session), we reset the state engine and start by processing that request."
properties:
authenticate:
- "pattern://68665057549fd887ea09fb86"
auth:
- "pattern://7022472ae407577ae604bbb8"
authParams:
- RecheckAuthentication: "On"
- RenewIdentification: "false"
- RenegotiateCookieOnAuthContinue: "false"
logrend:
- "pattern://097929211988398a87bcbb0c"
template: "res://4fcfadb4a5c946ead7e6e995#template"
labels: "res://4fcfadb4a5c946ead7e6e995#labels"
sessionTracking: "COOKIE"
cookieName: "agov"
initialSessionTimeout: "var://idp-authentication-session-timeout"
sessionTimeout: "30m"
langCookieDomain: "var://auth_realm_main_idp-language-cookie-domain"
resetAuthenticationCondition: "#{ (inargs.containsKey('SAMLRequest') and session.containsKey('ch.nevis.auth.saml.request.id'))\
\ ? 'restart' : '' }"

View File

@ -0,0 +1,19 @@
schemaVersion: "1.0"
pattern:
id: "5d7dc3d51416356293a239f7"
className: "ch.nevis.admin.v4.plugin.nevisauth.patterns.GenericAuthRealm"
name: "Auth_Realm_Main_STS"
label: "STS"
properties:
authStatesFile: "res://5d7dc3d51416356293a239f7#authStatesFile"
parameters: "var://auth_realm_main_sts_parameters"
resources: "res://5d7dc3d51416356293a239f7#resources"
keyObjects:
- "pattern://632ae3e34c70513c4d5ae882"
auth:
- "pattern://4bad2fe3ccc54716cc87138f"
logrend:
- "pattern://d19fe773e8f9ae00504352da"
initialSessionTimeout: "30s"
sessionTimeout: "30s"
maxSessionLifetime: "60m"

View File

@ -0,0 +1,21 @@
schemaVersion: "1.0"
pattern:
id: "cb8c63274fe346280de0ffd5"
className: "ch.nevis.admin.v4.plugin.nevisfido.inband.patterns.InBandMobileAuthenticationRealm"
name: "Auth_Realm_Mobile_FIDO_UAF "
label: "AUTH"
link:
sourceProjectKey: "DEFAULT-IAM-FLORIAN"
sourcePatternId: "cb8c63274fe346280de0ffd5"
author: "florip"
lastCopied: "2023-04-27T13:03:12Z"
properties:
tokens:
- "pattern://94e0b7b92ff2593f958c1eec"
nevisfido:
- "pattern://ca92034f995b39fde562293c"
auth:
- "pattern://7022472ae407577ae604bbb8"
logrend:
- "pattern://097929211988398a87bcbb0c"
initialSessionTimeout: "var://idp-authentication-session-timeout"

View File

@ -0,0 +1,15 @@
schemaVersion: "1.0"
pattern:
id: "0b3ce3ceec7bfca3ea524983"
className: "ch.nevis.admin.v4.plugin.nevisauth.patterns2.TransformVariablesStep"
name: "AuthnFailed_Client_NotFound"
label: "UTILS"
notes: "Display screen : informing the user that a problem blocking his account\
\ has been encountered\n"
properties:
variables:
- notes:saml.errorCode: "urn:oasis:names:tc:SAML:2.0:status:AuthnFailed"
- notes:saml.errorMessage: "permanent error, not linked to user, but to system\
\ , Request ID: ${request:traceId}"
onSuccess:
- "pattern://4c65de021d362462324a3a5f"

View File

@ -0,0 +1,18 @@
schemaVersion: "1.0"
pattern:
id: "3cc9ad9d0cc771665881abcc"
className: "ch.nevis.admin.v4.plugin.nevisauth.patterns2.TransformVariablesStep"
name: "AuthnFailed_LockedAccount"
label: "UTILS"
notes: "Display screen : informing the user that a problem blocking his account\
\ has been encountered\n\nmissing info : if you didn't lock the account then go\
\ consult the support page for unlocking the account\n\nErrors : 1: user verification\
\ failed (user not found); 98: account disabled or archived (98 not in use yet)"
properties:
variables:
- notes:saml.errorCode: "urn:oasis:names:tc:SAML:2.0:status:AuthnFailed"
- notes:saml.errorMessage: "Your account is locked , Request ID: ${request:transferId}"
- notes:saml.errorInfo: "If you didn't lock the account then go consult the support\
\ page for unlocking the account"
onSuccess:
- "pattern://473f9d6b4ab9d61c1eb8c689"

View File

@ -0,0 +1,18 @@
schemaVersion: "1.0"
pattern:
id: "50b861438e79c2332862d3ca"
className: "ch.nevis.admin.v4.plugin.nevisauth.patterns2.TransformVariablesStep"
name: "AuthnFailed_Zero_RoleLvl"
label: "UTILS"
notes: "Display screen : informing the user that a problem blocking his account\
\ has been encountered\n\nmissing info : if you didn't lock the account then go\
\ consult the support page for unlocking the account"
properties:
variables:
- notes:saml.errorCode: "urn:oasis:names:tc:SAML:2.0:status:AuthnFailed"
- notes:saml.errorMessage: "Your account verification status is expired or invalid\
\ , Request ID: ${request:traceId}"
- notes:saml.errorInfo: "Please consult the support page on how to check your\
\ account status"
onSuccess:
- "pattern://4c65de021d362462324a3a5f"

View File

@ -0,0 +1,16 @@
schemaVersion: "1.0"
pattern:
id: "2cdd910036aa06b102863a4f"
className: "ch.nevis.admin.v4.plugin.nevisauth.patterns2.GroovyScriptStep"
name: "CheckLoa"
label: "LOA"
properties:
scriptFile: "res://2cdd910036aa06b102863a4f#scriptFile"
onSuccess:
- "pattern://594764b3b866d7855f6990a1"
onFailure:
- "pattern://50b861438e79c2332862d3ca"
customSteps:
- "pattern://d1298ac82e8bab66583d5571"
- "pattern://1d38203c48e017b5b3812385"
scriptTraceGroup: "AGOV-ACCT"

View File

@ -0,0 +1,10 @@
schemaVersion: "1.0"
pattern:
id: "bcfe78c02cbe0588528bc3cb"
className: "ch.nevis.admin.v4.plugin.nevisauth.patterns.KeyObject"
name: "ClusterInternalTlsTrustObject"
properties:
keyObjectId: "TlsTrustStore"
type: "truststore"
trustStore:
- "pattern://ff188ae9f50527ef19eccd2c"

View File

@ -0,0 +1,11 @@
schemaVersion: "1.0"
pattern:
id: "bcca48cd422668aa2f78ea42"
className: "ch.nevis.admin.v4.plugin.nevisproxy.patterns.URLHandler"
name: "Correct_Path_to_static_Ressources"
label: "UTILS"
properties:
redirects:
- /SAML2/SSO/undefined/(.*): "/nevislogrend/nevislogrend/applications/Auth_Realm_Main_IDP/webdata/$1"
subPaths: "SAML2/SSO/"
phase: "AFTER_SANITATION"

View File

@ -0,0 +1,22 @@
schemaVersion: "1.0"
pattern:
id: "ecf4381f4653b0aa9a69b417"
className: "ch.nevis.admin.v4.plugin.nevisproxy.patterns.GenericHostContextSettings"
name: "DefaulErrorPages"
label: "UTILS"
properties:
filters: "<filter>\n <filter-name>DefaultErrorFilter</filter-name>\n <filter-class>ch::nevis::isiweb4::filter::error::ErrorFilter</filter-class>\n\
\ <init-param>\n <param-name>StatusCode</param-name>\n <param-value>\n\
\ 400:file:/resources/errorPages/404.html:reset-header:reset-status-code\n\
\ 403:file:/resources/errorPages/403.html:reset-header:reset-status-code\n\
\t 404:file:/resources/errorPages/404.html:reset-header:reset-status-code\n\
\ 408:file:/resources/errorPages/timeout.html:reset-header:reset-status-code\n\
\ 500:file:/resources/errorPages/500.html:reset-header:reset-status-code\n\
\ 502:file:/resources/errorPages/502.html:reset-header:reset-status-code\n\
\ </param-value>\n </init-param>\n <init-param>\n <param-name>CheckAcceptHeader</param-name>\n\
\ <param-value>true</param-value>\n </init-param>\n <init-param>\n\
\ <param-name>PlaceHolders</param-name>\n <param-value>\n \
\ TransferIdHolder:TRANSFER_ID\n TimestampHolder:TIMESTAMP\n\
\ </param-value>\n </init-param>\n</filter>"
filterMappings: "automatic"
phase: "START"

View File

@ -0,0 +1,8 @@
schemaVersion: "1.0"
pattern:
id: "a086e62f29aabe721b8b9e3b"
className: "ch.nevis.admin.v4.plugin.nevisproxy.patterns.GenericServiceSettings"
name: "Default_CSRF_Protection"
properties:
filters: "<filter-mapping>\n <filter-name>CSRF_Default</filter-name>\n <url-pattern>${service.path}</url-pattern>\n\
</filter-mapping>"

View File

@ -0,0 +1,8 @@
schemaVersion: "1.0"
pattern:
id: "ff188ae9f50527ef19eccd2c"
className: "ch.nevis.admin.v4.plugin.nevisproxy.patterns.PemTrustStoreProvider"
name: "ENV_CA"
label: "STORE"
properties:
truststoreFile: "var://env_ca-trusted-certificates"

View File

@ -0,0 +1,15 @@
schemaVersion: "1.0"
pattern:
id: "e3cac41e75980361d7d26bde"
className: "ch.nevis.admin.v4.plugin.nevisauth.patterns2.GenericAuthenticationStep"
name: "Email Input"
label: "FIDO2"
properties:
authStatesFile: "res://e3cac41e75980361d7d26bde#authStatesFile"
onSuccess:
- "pattern://699f22cf1cd4ad08bd973f31"
onFailure:
- "pattern://f63c475c35b616b7c6c1901c"
nextSteps:
- "pattern://826166d230a6a4849f2837ae"
resources: "res://e3cac41e75980361d7d26bde#resources"

View File

@ -0,0 +1,17 @@
schemaVersion: "1.0"
pattern:
id: "f393012a278e525956a362d3"
className: "ch.nevis.admin.v4.plugin.nevisauth.patterns2.GenericAuthenticationStep"
name: "Ensure_Account_State"
notes: "NOTE/haburger/2023-11-29: we continue, even if there was an error during\
\ the recovery code generation"
properties:
authStatesFile: "res://f393012a278e525956a362d3#authStatesFile"
parameters: "var://extid_user_verify-template-parameters"
onSuccess:
- "pattern://9ff0369f3cf662f95d94ff09"
onFailure:
- "pattern://4c65de021d362462324a3a5f"
resources: "res://f393012a278e525956a362d3#resources"
keyObjects:
- "pattern://bcfe78c02cbe0588528bc3cb"

View File

@ -0,0 +1,17 @@
schemaVersion: "1.0"
pattern:
id: "9ff0369f3cf662f95d94ff09"
className: "ch.nevis.admin.v4.plugin.nevisauth.patterns2.GenericAuthenticationStep"
name: "Ensure_Recovery_Code"
notes: "NOTE/haburger/2023-11-29: we continue, even if there was an error during\
\ the recovery code generation"
properties:
authStatesFile: "res://9ff0369f3cf662f95d94ff09#authStatesFile"
parameters: "var://ensure_recovery_code-parameters"
onSuccess:
- "pattern://2cdd910036aa06b102863a4f"
onFailure:
- "pattern://2cdd910036aa06b102863a4f"
resources: "res://9ff0369f3cf662f95d94ff09#resources"
keyObjects:
- "pattern://bcfe78c02cbe0588528bc3cb"

View File

@ -0,0 +1,36 @@
schemaVersion: "1.0"
pattern:
id: "302b0fa3c5c3d1d17e9b1004"
className: "ch.nevis.admin.v4.plugin.nevisauth.patterns2.GenericAuthenticationStep"
name: "FIDO2_Authentication"
label: "FIDO2"
notes: "Taken from the pattern FIDO2 Authentication (SAML IDP FIDO2 Authentication)\
\ and changed only one line in the fido2_auth.groovy script.\n\n<AuthState name=\"\
Auth_Realm_Main_IDP_TEMP_FIDO2AUTH\" class=\"ch.nevis.auth.fido.fido2.authstate.Fido2AuthState\"\
\ final=\"false\">\n <ResultCond name=\"cancelled\" next=\"Auth_Realm_Main_IDP_Auth_Failed\"\
/>\n <ResultCond name=\"ok\" next=\"Auth_Realm_Main_IDP_CheckLoa\"\
/>\n <Response value=\"AUTH_CONTINUE\">\n <Gui name=\"\
fido2_auth_std\" label=\"title.login.fido2\">\n <GuiElem name=\"\
info\" type=\"info\" label=\"info.login.fido2\"/>\n <GuiElem\
\ name=\"username\" type=\"hidden\" value=\"#{session['ch.adnovum.nevisidm.user.extId']\
\ != null ? session['ch.adnovum.nevisidm.user.extId'] : session['ch.nevis.idm.User.extId']\
\ != null ? session['ch.nevis.idm.User.extId'] : request.getUserId() != null ?\
\ request.getUserId() : notes['userid']}\"/>\n </Gui>\n \
\ <Arg name=\"fido2AuthenticationOptionsPath\" value=\"/nevisfido/fido2/attestation/options\"\
/>\n <Arg name=\"fido2AuthenticationPath\" value=\"/nevisfido/fido2/assertion/result\"\
/>\n <Arg name=\"fido2StatusServicePath\" value=\"/nevisfido/fido2/status\"\
/>\n <Arg name=\"fido2UserVerification\" value=\"required\"/>\n\
\ </Response>\n <property name=\"clientResult\" value=\"\
${inargs:cancel_fido2:^true$:cancelled}\"/>\n <property name=\"fido2UserName\"\
\ value=\"${inargs:o.username.v}\"/>\n <property name=\"fido2ServerUrl\"\
\ value=\"https://fido2:9443/nevisfido/\"/>\n </AuthState>"
properties:
authStatesFile: "res://302b0fa3c5c3d1d17e9b1004#authStatesFile"
parameters: "var://authentication-parameters"
onSuccess:
- "pattern://1a7583c6caa3b5c36599b25e"
onFailure:
- "pattern://af4ec934e8efbef422f03926"
nextSteps:
- "pattern://e3cac41e75980361d7d26bde"
resources: "res://302b0fa3c5c3d1d17e9b1004#resources"

View File

@ -0,0 +1,13 @@
schemaVersion: "1.0"
pattern:
id: "887ada57500885703a4a9408"
className: "ch.nevis.admin.v4.plugin.nevisauth.patterns2.TransformVariablesStep"
name: "FIDO2_ResetSessionInfos"
notes: "TODO/haburger/2024-AUG-20: remove after migration to Friendly Captcha is\
\ done"
properties:
variables:
- sess:agov.fido2.X-ReCAPTCHA-Integration: ""
emptyValue: "remove-variable"
onSuccess:
- "pattern://f39352769cb2a1c88e1a176d"

View File

@ -0,0 +1,36 @@
schemaVersion: "1.0"
pattern:
id: "ca92034f995b39fde562293c"
className: "ch.nevis.admin.v4.plugin.nevisfido.deployable.patterns.NevisFIDODeployable"
name: "FIDO_UAF_Instance"
deploymentHosts: "fido-uaf"
label: "UAF"
notes: "/!\\ client name needs to be the name and not the ID\n\n"
link:
sourceProjectKey: "DEFAULT-IAM-FLORIAN"
sourcePatternId: "ca92034f995b39fde562293c"
author: "florip"
lastCopied: "2023-04-27T13:03:12Z"
properties:
logging:
- "pattern://af4f8f438a9955bc7abec1f3"
frontendAddress: "var://new-nevisfido-uaf-instance-frontend-address"
frontendTrustStore:
- "pattern://69948a66429d85d971608411"
signerTrustStore:
- "pattern://55bf63a1b1716e9631f7080d"
database:
- "pattern://9385d1b33aefe975fb1c5914"
facets: "var://fido_uaf_instance-facets"
firebaseServiceAccount: "var://fido_uaf_instance-firebase-configuration"
firebaseProxyAddress: "var://fido_uaf_instance-firebase-proxy-url"
link: "Custom URI"
customURILink: "var://fido_uaf_instance-custom-uri-link"
nevisidm:
- "pattern://b8a36646f81c3247cdb5d90b"
client: "var://fido_uaf_instance-client-id"
registrationTokenTimeout: "var://fido-uaf-out-of-band-timeout"
authenticationTokenTimeout: "var://fido-uaf-out-of-band-timeout"
addons:
- "pattern://6c7076da1508f186394a3bd2"
- "pattern://90af8358cc587f5c5aa79fec"

View File

@ -0,0 +1,20 @@
schemaVersion: "1.0"
pattern:
id: "4e8cb939780070d6dd185204"
className: "ch.nevis.admin.v4.plugin.nevisfido.devicederegistration.patterns.MobileDeviceDeregistration"
name: "FIDO_UAF_Mobile_Deregistration"
label: "UAF"
link:
sourceProjectKey: "DEFAULT-IAM-FLORIAN"
sourcePatternId: "4e8cb939780070d6dd185204"
author: "florip"
lastCopied: "2023-04-27T13:03:12Z"
properties:
host:
- "pattern://1f0702aaabef60a615abf41f"
realm:
- "pattern://cb8c63274fe346280de0ffd5"
token:
- "pattern://94e0b7b92ff2593f958c1eec"
nevisfido:
- "pattern://ca92034f995b39fde562293c"

View File

@ -0,0 +1,20 @@
schemaVersion: "1.0"
pattern:
id: "116760946160c3cc30abfcab"
className: "ch.nevis.admin.v4.plugin.nevisfido.outofband.patterns.OutOfBandMobileDeviceRegistration"
name: "FIDO_UAF_Mobile_Registration"
label: "UAF"
link:
sourceProjectKey: "DEFAULT-IAM-FLORIAN"
sourcePatternId: "116760946160c3cc30abfcab"
author: "florip"
lastCopied: "2023-04-27T13:03:12Z"
properties:
host:
- "pattern://1f0702aaabef60a615abf41f"
realm:
- "pattern://06aeae2d799e492f5580d03b"
token:
- "pattern://94e0b7b92ff2593f958c1eec"
nevisfido:
- "pattern://ca92034f995b39fde562293c"

View File

@ -0,0 +1,10 @@
schemaVersion: "1.0"
pattern:
id: "95220b3005deb118adeb01aa"
className: "ch.nevis.admin.v4.plugin.nevisauth.patterns.KeyObject"
name: "FIDO_UAF_Truststore"
label: "STORE"
properties:
type: "truststore"
trustStore:
- "pattern://ff188ae9f50527ef19eccd2c"

View File

@ -0,0 +1,10 @@
schemaVersion: "1.0"
pattern:
id: "69948a66429d85d971608411"
className: "ch.nevis.admin.v4.plugin.nevisproxy.patterns.AutomaticTrustStoreProvider"
name: "FIDO_UAF_extended_Frontent_Truststore"
label: "UAF"
notes: "Used to also as trusstore for the firebase outgoing connection (i.e. trust\
\ forward proxy CA if necessary)"
properties:
truststoreFile: "var://fido_uaf_extended_frontent_truststore-fw_proxy_ca_cert"

View File

@ -0,0 +1,17 @@
schemaVersion: "1.0"
pattern:
id: "9a8294b080ea769d22924af0"
className: "ch.nevis.admin.v4.plugin.nevisauth.patterns2.GenericAuthenticationStep"
name: "Fetch_Attributes"
label: "LOA"
notes: "fetch new user attributes and check the new loa level"
properties:
authStatesFile: "res://9a8294b080ea769d22924af0#authStatesFile"
parameters: "var://extid_user_verify-template-parameters"
onSuccess:
- "pattern://b87d0d2b640e8e545ad70234"
onFailure:
- "pattern://d1298ac82e8bab66583d5571"
nextSteps:
- "pattern://50b861438e79c2332862d3ca"
resources: "res://9a8294b080ea769d22924af0#resources"

View File

@ -0,0 +1,10 @@
schemaVersion: "1.0"
pattern:
id: "594764b3b866d7855f6990a1"
className: "ch.nevis.admin.v4.plugin.nevisauth.patterns2.GenericAuthenticationStep"
name: "Fetch_Country_Name"
properties:
authStatesFile: "res://594764b3b866d7855f6990a1#authStatesFile"
onSuccess:
- "pattern://b87d0d2b640e8e545ad70234"
resources: "res://594764b3b866d7855f6990a1#resources"

View File

@ -0,0 +1,11 @@
schemaVersion: "1.0"
pattern:
id: "1a7583c6caa3b5c36599b25e"
className: "ch.nevis.admin.v4.plugin.nevisauth.patterns2.TransformVariablesStep"
name: "Fido2_VariableStep"
label: "FIDO2"
properties:
variables:
- sess:authenticatedWith: "urn:qa.agov.ch:names:tc:authfactor:fido"
onSuccess:
- "pattern://f393012a278e525956a362d3"

View File

@ -0,0 +1,11 @@
schemaVersion: "1.0"
pattern:
id: "56c67433c7a47b6cb06f011a"
className: "ch.nevis.admin.v4.plugin.nevisauth.patterns2.TransformVariablesStep"
name: "FidoUAF_VariableStep"
label: "UAF"
properties:
variables:
- sess:authenticatedWith: "urn:qa.agov.ch:names:tc:authfactor:accessapp"
onSuccess:
- "pattern://c686c1bdd5355351f7f98cc8"

View File

@ -0,0 +1,16 @@
schemaVersion: "1.0"
pattern:
id: "7fb39bfd6c34685866a22180"
className: "ch.nevis.admin.v4.plugin.nevisauth.patterns2.GenericAuthenticationStep"
name: "Fido_Email_Verify"
label: "FIDO2"
notes: "force next authstate\ndummy state for failed state (hardcoded extId)"
properties:
authStatesFile: "res://7fb39bfd6c34685866a22180#authStatesFile"
parameters: "var://extid_user_verify-template-parameters"
onSuccess:
- "pattern://302b0fa3c5c3d1d17e9b1004"
onFailure:
- "pattern://4c65de021d362462324a3a5f"
nextSteps:
- "pattern://0b3ce3ceec7bfca3ea524983"

View File

@ -0,0 +1,21 @@
schemaVersion: "1.0"
pattern:
id: "2951ead44a7a9362a4545094"
className: "ch.nevis.admin.v4.plugin.nevisidm.patterns.NevisIDMDatabase"
name: "IDM_DB"
label: "IDM"
properties:
type: "var://idm_db-database-type"
hosts: "var://idm_db-database-host"
database: "var://idm_db-database-name"
rootCredential: "var://idm_db-root-credential"
rootCredentialNamespace: "var://idm_db-root-credential-namespace"
user: "var://idm_db-database-user"
password: "var://idm_db-database-password"
encryption: "var://idm_db-tls-encryption"
trustStore:
- "pattern://326adce95ad1a0761f2259b7"
jdbcDriver: "var://idm_db-database-jdbc-driver"
oracleVolumeClaimName: "var://idm_db-database-volume-claim"
databaseManagement: "var://agov_dev_idm-db-management"
connectionUrl: "var://idm_db-database-connection-url"

View File

@ -0,0 +1,8 @@
schemaVersion: "1.0"
pattern:
id: "326adce95ad1a0761f2259b7"
className: "ch.nevis.admin.v4.plugin.nevisproxy.patterns.PemTrustStoreProvider"
name: "IDM_DB_TLS_TrustStore"
label: "IDM"
properties:
truststoreFile: "var://idm_db_tls_truststore-trusted-certificates"

View File

@ -0,0 +1,9 @@
schemaVersion: "1.0"
pattern:
id: "93739706b170b426534b8bd5"
className: "ch.nevis.admin.v4.plugin.nevisidm.patterns2.NevisIDMPasswordLogin"
name: "IDM_Login"
label: "IDM"
properties:
nevisIDM:
- "pattern://b8a36646f81c3247cdb5d90b"

View File

@ -0,0 +1,15 @@
schemaVersion: "1.0"
pattern:
id: "71411a755a625f9b850c6cf5"
className: "ch.nevis.admin.v4.plugin.nevisidm.patterns.NevisIDMAdvancedSettings"
name: "IDM_Settings"
label: "IDM"
notes: "used for SAMP IDP\n---\nbatch, event, audit processing disabled on that\
\ instance"
link:
sourceProjectKey: "DEFAULT-IAM-JAKOB"
sourcePatternId: "71411a755a625f9b850c6cf5"
author: "florip"
lastCopied: "2023-03-30T08:57:06Z"
properties:
properties: "var://idm-standard-settings"

View File

@ -0,0 +1,28 @@
schemaVersion: "1.0"
pattern:
id: "c642107fde6b2e07f16bfedb"
className: "ch.nevis.admin.v4.plugin.nevisauth.patterns.SamlIdp"
name: "IDP_AGOV"
label: "IDP"
notes: "ATB -> mod issuer\nIssuer -> Jakob"
link:
sourceProjectKey: "DEFAULT-IAM-JAKOB"
sourcePatternId: "c642107fde6b2e07f16bfedb"
author: "florip"
lastCopied: "2023-03-30T08:57:06Z"
properties:
issuer: "var://idp_agov-saml-issuer"
host:
- "pattern://1f0702aaabef60a615abf41f"
path: "/SAML2/SSO/"
realm:
- "pattern://4fcfadb4a5c946ead7e6e995"
sp:
- "pattern://27cefc3861bce987f6766342"
samlSigner:
- "pattern://56d6268c95f766c86c6aaae9"
metadataService: "disabled"
authenticationType: "sp-initiated"
logoutConfirmation: "disabled"
preProcess:
- "pattern://7a913eec7f78ce674cd87854"

View File

@ -0,0 +1,13 @@
schemaVersion: "1.0"
pattern:
id: "8052fd68f4a663629d651f7b"
className: "ch.nevis.admin.v4.plugin.nevisproxy.patterns.PemTrustStoreProvider"
name: "IDP_PEM_ATB"
label: "IDP"
link:
sourceProjectKey: "DEFAULT-IAM-JAKOB"
sourcePatternId: "8052fd68f4a663629d651f7b"
author: "florip"
lastCopied: "2023-03-30T08:57:06Z"
properties:
truststoreFile: "var://idp_pem_atb-trusted-certificates"

View File

@ -0,0 +1,14 @@
schemaVersion: "1.0"
pattern:
id: "56d6268c95f766c86c6aaae9"
className: "ch.nevis.admin.v4.plugin.nevisproxy.patterns.PemKeyStoreProvider"
name: "IDP_PEM_Signer"
label: "IDP"
link:
sourceProjectKey: "DEFAULT-IAM-JAKOB"
sourcePatternId: "56d6268c95f766c86c6aaae9"
author: "florip"
lastCopied: "2023-03-30T08:57:06Z"
properties:
keystoreFiles: "var://idp_pem_signer-key-store-content"
keyPass: "var://saml-idp-signer-pem-key-store-private-key-passphrase"

View File

@ -0,0 +1,67 @@
schemaVersion: "1.0"
pattern:
id: "27cefc3861bce987f6766342"
className: "ch.nevis.admin.v4.plugin.nevisauth.patterns.SamlSpConnector"
name: "IDP_SP_Connector"
label: "IDP"
notes: "- Subject NameID Format -> urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\n\
- dateOfBirth: to have a date suitable for SAML and OIDC, we remove the TimeZone\
\ charachter ('1993-03-03Z' --> '1993-03-03')\n- verificationMethod: BUNDBITBK-2892\
\ SelfPaid is only for internal use, we remove this from the public assertion\n\
- address.verificationMethod: BUNDBITBK-2921 avoid interface change for hotfix"
link:
sourceProjectKey: "DEFAULT-IAM-JAKOB"
sourcePatternId: "27cefc3861bce987f6766342"
author: "florip"
lastCopied: "2023-03-30T08:57:06Z"
properties:
issuer: "var://idp_sp_connector-sp-issuer"
url: "var://idp_sp_connector-sp-url---assertion-consumer-services"
signerTrust:
- "pattern://8052fd68f4a663629d651f7b"
subjectFormat: "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
subjectConfirmation: "bearer"
attributes:
- http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress: "${sess:ch.nevis.idm.User.email}"
- http://schemas.agov.ch/ws/2023/05/identity/claims/languageOfCorrespondance: "${sess:ch.nevis.idm.User.language}"
- http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname: "${sess:ch.nevis.idm.User.firstName}"
- http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname: "${sess:ch.nevis.idm.User.lastName}"
- http://schemas.agov.ch/ws/2023/05/identity/claims/dateOfBirth: "${sess:ch.nevis.idm.User.birthDate:^(\\\
d\\d\\d\\d-\\d\\d-\\d\\d).*$}"
- http://schemas.agov.ch/ws/2023/05/identity/claims/sex: "${sess:ch.nevis.idm.User.gender}"
- http://schemas.agov.ch/ws/2023/05/identity/claims/socialSecurityNumber: "#{\
\ (sess['agov.appSvnrAllowed'] == 'true') ? sess['ch.nevis.idm.User.prop.svnr']\
\ : ''}"
- http://schemas.agov.ch/ws/2023/05/identity/claims/placeOfBirth: "#{ (sess['agov.appSvnrAllowed']\
\ == 'true') ? sess['ch.nevis.idm.User.prop.placeOfBirth'] : ''}"
- http://schemas.agov.ch/ws/2023/05/identity/claims/eIdNumber: "${sess:ch.nevis.idm.User.prop.eIdNumber}"
- http://schemas.agov.ch/ws/2023/05/identity/claims/qa/dateOfVerification: "${sess:ValidFrom}"
- http://schemas.agov.ch/ws/2023/05/identity/claims/qa/validTillDate: "${sess:ValidTo}"
- http://schemas.agov.ch/ws/2023/05/identity/claims/qa/verificationMethod: "#{\
\ ''.concat(sess.get('idVerification')).replace('SelfPaid', '') }"
- http://schemas.agov.ch/ws/2023/05/identity/claims/nationality: "#{ sess.containsKey('ch.nevis.idm.User.prop.nationality')\
\ ? sess['ch.nevis.idm.User.prop.nationality'].toUpperCase(): '' }"
- http://schemas.agov.ch/ws/2023/05/identity/claims/authenticatedWith: "${sess:authenticatedWith}"
- http://schemas.agov.ch/ws/2023/08/identity/claims/emailVerified: "true"
- http://schemas.agov.ch/ws/2023/08/identity/claims/address/street: "#{ (sess['agov.appAddressRequired']\
\ == 'true') ? sess['ch.nevis.idm.User.street'] : '' }"
- http://schemas.agov.ch/ws/2023/08/identity/claims/address/houseNumber: "#{ (sess['agov.appAddressRequired']\
\ == 'true') ? sess['ch.nevis.idm.User.houseNumber'] : '' }"
- http://schemas.agov.ch/ws/2023/08/identity/claims/address/zipCode: "#{ (sess['agov.appAddressRequired']\
\ == 'true') ? sess['ch.nevis.idm.User.postalCode'] : '' }"
- http://schemas.agov.ch/ws/2023/08/identity/claims/address/town: "#{ (sess['agov.appAddressRequired']\
\ == 'true') ? sess['ch.nevis.idm.User.city'] : '' }"
- http://schemas.agov.ch/ws/2024/02/identity/claims/address/country: "#{ (sess['agov.appAddressRequired']\
\ == 'true') ? sess['ch.nevis.idm.User.country'].toUpperCase() : '' }"
- http://schemas.agov.ch/ws/2024/02/identity/claims/address/qa/verificationMethod: "#{\
\ (sess['agov.appAddressRequired'] == 'true') ? ''.concat(sess.get('agov.adressVerification')).replace('Location',\
\ 'Domicile') : '' }"
- http://schemas.agov.ch/ws/2024/02/identity/claims/address/countryName: "#{ (sess['agov.appAddressRequired']\
\ == 'true') ? sess['agov.countryName'] : ''}"
context: "PasswordProtectedTransport"
assertionLifetime: "30s"
sign:
- "Response"
- "Assertion"
keyInfo: "Certificate"
properties: "var://idp-sp-connector-properties"

View File

@ -0,0 +1,12 @@
schemaVersion: "1.0"
pattern:
id: "7a913eec7f78ce674cd87854"
className: "ch.nevis.admin.v4.plugin.nevisauth.patterns2.GenericAuthenticationStep"
name: "IDP_Status_Check"
label: "IDP"
properties:
authStatesFile: "res://7a913eec7f78ce674cd87854#authStatesFile"
parameters: "var://idp_domain_settings"
nextSteps:
- "pattern://f63c475c35b616b7c6c1901c"
resources: "res://7a913eec7f78ce674cd87854#resources"

View File

@ -0,0 +1,36 @@
schemaVersion: "1.0"
pattern:
id: "8b8167e5de0e69dedb81cacb"
className: "ch.nevis.admin.v4.plugin.nevisproxy.patterns.GenericHostContextSettings"
name: "IdP-Cors-Filter"
properties:
filters: "<filter>\n <filter-name>Lua_${name}</filter-name>\n <filter-class>ch::nevis::isiweb4::filter::lua::LuaFilter</filter-class>\n\
\ <init-param>\n <param-name>Script.OutputHeaderFunctionName</param-name>\n\
\ <param-value>outputHeader</param-value>\n </init-param>\n <init-param>\n\
\ <param-name>Script</param-name>\n <param-value>\n\nfunction\
\ outputHeader(request, response)\n trace = request:getTracer()\n if request:getHeader(\"\
Origin\") then\n if not response:getHeader(\"Access-Control-Allow-Origin\"\
) then\n domains = ${param.cors.allowed.fqdns}\n\n for k,\
\ v in pairs(domains) do\n trace:info(\"Accepted domains=\"..v)\n\
\ end\n\n domainContainsOrigin = false\n origin\
\ = request:getHeader(\"Origin\")\n if (origin ~= nil) then\n \
\ origin = origin:match('^%w+://([^/:]+)')\n for index,\
\ value in ipairs(domains) do\n if (origin == value) then\n \
\ response:setHeader(\"Access-Control-Allow-Origin\", origin)\n\
\ response:setHeader(\"Vary\", \"Origin\")\n \
\ domainContainsOrigin = true\n end\n end\n \
\ if (not domainContainsOrigin) then\n request:getTracer():notice(\"\
VA01\", \"HTTP Origin header \" .. origin .. \" does not match one in the domain\
\ list \")\n response:setHeader(\"Content-Type\", \"text/plain\"\
)\n response:setBody(\"403 Denied\")\n response:send(403)\n\
\ return\n end\n end\n trace:info(\"\
Proxy allowed CORS for '\"..response:getHeader(\"Access-Control-Allow-Origin\"\
)..\"' after receiving a call from origin \"..request:getHeader(\"Origin\"))\n\
\ else\n trace:info(\"Backend allowed CORS for '\"..response:getHeader(\"\
Access-Control-Allow-Origin\")..\"' after receiving a call from origin \"..request:getHeader(\"\
Origin\"))\n end\n else\n trace:info(\"Origin header is null\"\
)\n end\nend\n\n </param-value>\n </init-param>\n</filter>\n\n<filter-mapping>\n\
\ <filter-name>Lua_${name}</filter-name>\n <url-pattern>/SAML2/SSO/*</url-pattern>\n\
</filter-mapping>\n"
phase: "SANITATION"
parameters: "var://idp-cors-filter-template-parameters"

View File

@ -0,0 +1,14 @@
schemaVersion: "1.0"
pattern:
id: "d1298ac82e8bab66583d5571"
className: "ch.nevis.admin.v4.plugin.nevisauth.patterns2.GenericAuthenticationStep"
name: "Insufficient_LoaInfo"
label: "LOA"
properties:
authStatesFile: "res://d1298ac82e8bab66583d5571#authStatesFile"
onSuccess:
- "pattern://cdb228eccc12b4b1dea20d9d"
onFailure:
- "pattern://1793638a6715537e8f491f25"
nextSteps:
- "pattern://826166d230a6a4849f2837ae"

View File

@ -0,0 +1,8 @@
schemaVersion: "1.0"
pattern:
id: "55bf63a1b1716e9631f7080d"
className: "ch.nevis.admin.v4.plugin.nevisproxy.patterns.AutomaticTrustStoreProvider"
name: "Internal-IdP-Auth-Signer-Trust"
properties:
keystore:
- "pattern://aeb2fed9962dcd5f7893db51"

View File

@ -0,0 +1,9 @@
schemaVersion: "1.0"
pattern:
id: "aeb2fed9962dcd5f7893db51"
className: "ch.nevis.admin.v4.plugin.nevisproxy.patterns.AutomaticKeyStoreProvider"
name: "Internal-IdP-Auth-Signer"
properties:
owner:
- "pattern://7022472ae407577ae604bbb8"
- "pattern://4bad2fe3ccc54716cc87138f"

View File

@ -0,0 +1,9 @@
schemaVersion: "1.0"
pattern:
id: "aec56cb572434a42d55de30c"
className: "ch.nevis.admin.v4.plugin.nevisauth.patterns.CustomAuthLogFile"
name: "Log_Auth"
label: "LOGS"
properties:
logLevel: "var://log_auth-default-log-level"
levels: "var://log_auth-log-levels"

View File

@ -0,0 +1,9 @@
schemaVersion: "1.0"
pattern:
id: "10b2e9ba76058c1dadb3935a"
className: "ch.nevis.admin.v4.plugin.fido2.patterns.NevisFIDOLogSettings"
name: "Log_FIDO2"
label: "LOGS"
properties:
logLevel: "var://log_fido2-default-log-level"
levels: "var://log_fido2-log-levels"

Some files were not shown because too many files have changed in this diff Show More