Compare commits

..

No commits in common. "master" and "r-25da499166053032321f08e80ed9ec3136c59cd1" have entirely different histories.

482 changed files with 123153 additions and 14768 deletions

View File

@ -11,8 +11,8 @@ metadata:
spec:
type: "NevisAuth"
replicas: 1
version: "8.2505.5"
gitInitVersion: "1.4.0"
version: "8.2411.3"
gitInitVersion: "1.3.0"
runAsNonRoot: true
ports:
management: 9000
@ -39,14 +39,13 @@ spec:
management:
httpGet:
path: "/nevisauth/liveness"
initialDelaySeconds: 50
periodSeconds: 5
timeoutSeconds: 6
failureThreshold: 30
failureThreshold: 50
podDisruptionBudget:
maxUnavailable: "50%"
git:
tag: "r-d6878093aefa2bfb8cc241b61fff5fe94bc95282"
tag: "r-317ed268556b37656f27fb58fcffd4797cea27e4"
dir: "DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth-sts"
credentials: "git-credentials"
keystores:

View File

@ -3,7 +3,6 @@ accept.button.label=Accept
cancel.button.label=Cancel
continue.button.label=Continue
deputy.profile.label=(Deputy Profile)
error.account.exists=Account already exists. Continue to log in.
error.saml.failed=Please close your browser and try again.
error_1=Please check your input.
error_10=Please select the correct user account.
@ -71,8 +70,6 @@ policyInfo.regex.numeric=▪ must contain at least {0} numeric characters.
policyInfo.regex.upper=▪ must contain at least {0} upper case characters.
policyInfo.title=The password has to comply with the following password policy:
reject.button.label=Deny
signup.button.label=Signup
skip.button.label=Skip
submit.button.label=Submit
tan.sent=Please enter the security code which has been sent to your mobile phone.
title.logout=Logout
@ -80,5 +77,4 @@ title.logout.confirmation=Logout
title.logout.reminder=Logout
title.oauth.consent=Client Authorization
title.saml.failed=Error
title.signup=Create account
title.timeout.page=Logout

View File

@ -3,7 +3,6 @@ accept.button.label=Akzeptieren
cancel.button.label=Abbrechen
continue.button.label=Weiter
deputy.profile.label=(Profil Stellvertreter)
error.account.exists=Konto existiert bereits. Melden Sie sich an.
error.saml.failed=Bitte schliessen Sie Ihren Browser und versuchen Sie es erneut.
error_1=Bitte überprüfen Sie Ihre Eingabe.
error_10=Bitte wählen Sie den gewünschten Benutzer.
@ -71,8 +70,6 @@ policyInfo.regex.numeric=▪ muss mindestens {0} numerische Zeichen enthalte
policyInfo.regex.upper=▪ muss mindestens {0} Grossbuchstaben enthalten.
policyInfo.title=Das Passwort muss den folgenden Passwort-Richtlinien entsprechen:
reject.button.label=Ablehnen
signup.button.label=Registrieren
skip.button.label=Überspringen
submit.button.label=Senden
tan.sent=Bitte erfassen Sie den Sicherheitscode, welcher an Ihr Mobiltelefon gesendet wurde.
title.logout=Logout
@ -80,5 +77,4 @@ title.logout.confirmation=Logout
title.logout.reminder=Logout
title.oauth.consent=Client Authorisierung
title.saml.failed=Error
title.signup=Konto erstellen
title.timeout.page=Logout

View File

@ -3,7 +3,6 @@ accept.button.label=Accept
cancel.button.label=Cancel
continue.button.label=Continue
deputy.profile.label=(Deputy Profile)
error.account.exists=Account already exists. Continue to log in.
error.saml.failed=Please close your browser and try again.
error_1=Please check your input.
error_10=Please select the correct user account.
@ -71,8 +70,6 @@ policyInfo.regex.numeric=▪ must contain at least {0} numeric characters.
policyInfo.regex.upper=▪ must contain at least {0} upper case characters.
policyInfo.title=The password has to comply with the following password policy:
reject.button.label=Deny
signup.button.label=Signup
skip.button.label=Skip
submit.button.label=Submit
tan.sent=Please enter the security code which has been sent to your mobile phone.
title.logout=Logout
@ -80,5 +77,4 @@ title.logout.confirmation=Logout
title.logout.reminder=Logout
title.oauth.consent=Client Authorization
title.saml.failed=Error
title.signup=Create account
title.timeout.page=Logout

View File

@ -3,7 +3,6 @@ accept.button.label=Accepter
cancel.button.label=Abandonner
continue.button.label=Continuer
deputy.profile.label=(Profil du suppléant)
error.account.exists=Le compte existe déjà. Continuez à vous connecter.
error.saml.failed=Fermez votre navigateur et r;eacute;essayez.
error_1=Veuillez vérifier vos données, s.v.p.
error_10=Choisissez votre compte.
@ -71,8 +70,6 @@ policyInfo.regex.numeric=▪ doit comprendre au minimum {0} caractères
policyInfo.regex.upper=▪ doit contenir au moins {0} caractère(s) majuscule(s).
policyInfo.title=Le mot de passe doit respecter les règles suivantes:
reject.button.label=Refuser
signup.button.label=Inscription
skip.button.label=Passer
submit.button.label=Envoyer
tan.sent=Veuillez saisir le code de sécurité que vous avez reçu au votre téléphone mobile.
title.logout=Logout
@ -80,5 +77,4 @@ title.logout.confirmation=Logout
title.logout.reminder=Logout
title.oauth.consent=Autorisation du client
title.saml.failed=Error
title.signup=Créer un compte
title.timeout.page=Logout

View File

@ -1,9 +1,8 @@
accept.button.label=Accetta
cancel.button.label=Annulla
accept.button.label=Accettare
cancel.button.label=Abortire
continue.button.label=Continua
deputy.profile.label=(profilo del delegato)
error.account.exists=L'account esiste gi<67>. Prosegui col login.
error.saml.failed=Chiudi il browser e riprova.
error_1=Verificare i dati immessi.
error_10=Per favore selezionare il conto utente corretto.
@ -70,9 +69,7 @@ policyInfo.regex.nonLetter=&#9642; non pu&ograve; contenere pi&ugrave; di {0} nu
policyInfo.regex.numeric=&#9642; deve contenere un minimo di {0} carattere/i numerico/i.
policyInfo.regex.upper=&#9642; deve conenere almeno {0} carattere/i maiuscolo/i.
policyInfo.title=La password deve rispettare le seguenti direttive:
reject.button.label=Rifiuta
signup.button.label=Iscriviti
skip.button.label=Salta
reject.button.label=Rifiuti
submit.button.label=Continua
tan.sent=Inserisci il codice di sicurezza che &egrave; stato inviato al tuo telefono cellulare.
title.logout=Logout
@ -80,5 +77,4 @@ title.logout.confirmation=Logout
title.logout.reminder=Logout
title.oauth.consent=Autorizzazione del client
title.saml.failed=Error
title.signup=Crea un account
title.timeout.page=Logout

View File

@ -13,9 +13,8 @@ JAVA_OPTS=(
"-javaagent:/opt/agent/opentelemetry-javaagent.jar"
"-Dotel.javaagent.logging=application"
"-Dotel.javaagent.configuration-file=/var/opt/nevisauth/default/conf/otel.properties"
"-Dotel.resource.attributes=service.version=8.2505.5,service.instance.id=$HOSTNAME"
"-Dotel.resource.attributes=service.version=8.2411.3,service.instance.id=$HOSTNAME"
"-Djavax.net.ssl.trustStore=/var/opt/keys/trust/auth-sts-idp-extended-truststore/truststore.p12"
"-Djavax.net.ssl.trustStorePassword=\${exec:/var/opt/keys/trust/auth-sts-idp-extended-truststore/keypass}"
)

View File

@ -431,6 +431,4 @@
<!-- source: pattern://eaae1a7d4c4e0ce653074f22 -->
<property name="secToken.binary" value="true"/>
</WebService>
<!-- source: pattern://4bad2fe3ccc54716cc87138f -->
<RESTService name="ManagementService" class="ch.nevis.esauth.rest.service.session.ManagementService"/>
</esauth-server>

View File

@ -16,12 +16,16 @@ Configuration:
level: "INFO"
- name: "EsAuthStart"
level: "INFO"
- name: "org.apache.catalina.loader.WebappClassLoader"
level: "FATAL"
- name: "org.apache.catalina.startup.HostConfig"
level: "ERROR"
- name: "ch.nevis.esauth.events"
level: "FATAL"
- name: "AGOV-ACCT"
level: "DEBUG"
- name: "AgovCaptcha"
level: "DEBUG"
- name: "ArtifactResolutionService"
level: "DEBUG"
- name: "AuthEngine"
level: "INFO"
- name: "AuthPerf"
@ -29,10 +33,8 @@ Configuration:
- name: "IdmAuth"
level: "DEBUG"
- name: "OpTrace"
level: "INFO"
- name: "Recovery"
level: "DEBUG"
- name: "Saml"
- name: "Recovery"
level: "DEBUG"
- name: "Script"
level: "DEBUG"

View File

@ -1,5 +1,4 @@
otel.service.name = auth-sts
otel.traces.sampler = always_on
otel.traces.exporter = none
otel.metrics.exporter = none
otel.logs.exporter = none

View File

@ -14,4 +14,4 @@ try {
LOG.warn("Exception in Script: ${e}")
} finally {
response.setResult('ok')
}
}

View File

@ -13,4 +13,4 @@ try {
LOG.warn("Exception in Script: ${e}")
} finally {
response.setResult('ok')
}
}

View File

@ -10,7 +10,7 @@ metadata:
patternId: "7022472ae407577ae604bbb8"
spec:
keystores:
- name: "auth-sts-sh4r3d-internal-idp-auth-signer"
namespace: "adn-agov-nevisidm-01-uat"
- name: "auth-sh4r3d-internal-idp-auth-signer"
namespace: "adn-agov-nevisidm-01-uat"
- name: "auth-sts-sh4r3d-internal-idp-auth-signer"
namespace: "adn-agov-nevisidm-01-uat"

View File

@ -11,8 +11,8 @@ metadata:
spec:
type: "NevisAuth"
replicas: 1
version: "8.2505.5"
gitInitVersion: "1.4.0"
version: "8.2411.3"
gitInitVersion: "1.3.0"
runAsNonRoot: true
ports:
management: 9000
@ -39,19 +39,15 @@ spec:
management:
httpGet:
path: "/nevisauth/liveness"
initialDelaySeconds: 50
periodSeconds: 5
timeoutSeconds: 6
failureThreshold: 30
failureThreshold: 50
podDisruptionBudget:
maxUnavailable: "50%"
git:
tag: "r-53c09bd6632aebeda2b892197a01a8f7f185561d"
tag: "r-0a95034444af9c2e5b4a8c12cc3a0f444f6b0447"
dir: "DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/auth"
credentials: "git-credentials"
database:
name: "auth"
requiredVersion: "8.2505.5"
keystores:
- "auth-sh4r3d-internal-idp-auth-signer"
- "auth-auth-realm-mobile-fido-uaf-tls-client-nevisfido"

View File

@ -1,26 +0,0 @@
apiVersion: "operator.nevis-security.ch/v1"
kind: "NevisDatabase"
metadata:
name: "auth"
namespace: "adn-agov-nevisidm-01-uat"
labels:
deploymentTarget: "auth"
annotations:
projectKey: "DEFAULT-ADN-AGOV-PROJECT"
patternId: "b7b59e97b3fd18bb60178573"
spec:
type: "NevisAuth"
databaseType: "MariaDB"
version: "8.2505.5"
url: "mariadb-session-store-service.adn-agov-nevisidm-ob-01-uat"
port: 3306
database: "nevisauth"
bootstrap: true
migrate: true
rootCredentials:
name: "root-mariadb-session-store"
namespace: "adn-agov-nevisidm-ob-01-uat"
podSecurity:
policy: "baseline"
automountServiceAccountToken: false
timeZone: "Europe/Zurich"

View File

@ -1,54 +1,54 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIJqzBVBgkqhkiG9w0BBQ0wSDAnBgkqhkiG9w0BBQwwGgQUvdFHAj0YggoFr07l
OCEjWZAMT1oCAggAMB0GCWCGSAFlAwQBKgQQc0LHn1pUPI8PXXos61VpwgSCCVBh
wA/Ghkde2sb3r+cGG6k7iyM3UWPWu0f0Ac+i4uoKoQhGWlbsMVj/GRgDCcfr5D+C
2DOvjttdX17UIbEpSC8qbUsplrlSnZGZrizQN5oS7iKNegFQENUpj7uNjZ7ASJy3
ZOIOsCvuNau+7teDrlIfcUe/A7M9Pm+ZkVFhCDEys1igRb9Sv0EBwQ6aYeei2BtF
KbnVuEJTi38uGj1VB1E6z8YswlqRIPjcs2UnOUuQ3GBMDLnd4hYGvYOs6Sh8p3kh
ELP/vZ7zNtSdKVjmsTLyk7BVFkOI5sdBS6igon1aqDqTsY3POgLoqtqi3fF4BKIZ
mhsU7CfF++AutxHaDWXj+0qLcKkA3SSnYdKOJmOBeBEnqqFv5SQ2YZe/DCetfhjS
SpY4aST2aCfSAWzK6Amo2/TH7bLqgqwqs+RICcQLpOVD9OLSDX+7vqqo+xWzdONw
pm+l/x/9NEgTSEwZv7gJxPg8omBub7HR/SHR7BSb8dsld8wUdgNFeixY6NXTLxHv
92HKR6Cw5vFd0OaDGlQL1ay3UAc2SPNE2/0oHEAbwoRPKcNhhHGXl8skhuKHgupp
6gWRpsQeKechQCysP/wRMm7v0przBCUm+PSUpbT5aV3j+iQYXGdme9E01TaRaKKb
BhiuyzDBPwXeUktBpvpq7d/pOp3eTbF8cbXXDP+DqRl6KcHK5wB+wEavqVo06RGZ
NtdHzOJTT1V0cUMobsI7hC0TcB0YeeZBRX66tMIrjCl4QuS2nv8Kn0oz2nZ8htfm
5M6cwBd/NymfGIEI2RR53fv5dN917WsagY5n0lQzNV4VAK1WrxfxtbUuQVKK5S02
zqGxriMQ9CA1tU86Ec0Gk4mbiEwExArD1YprHl0p8HhEV34J9VjG+GhSXpKp/1zL
wl+LChF/GxW6INFxH0qo7ecFodoJPTNdTxFHhzdMBoXf2sXpR9nFMuvuRdlS6rKy
zytxZLwT8EF7f4x0BgxCDFD+/1WonSSWahgMWfmthrt9MSFH17ZMd3/aVkJwDxrk
61IBEgJI/DhGniNnzK171XiG7cpunwd7TV4RV1i8munPMi4Za1w4rwTzhnLzZ1/R
jK5AO5waKqecmrMFOhWrcekwn43Tx0PpOeAA9iDlfGPGrY0mCgKTmlccqgrFKtn6
sjNRsRQ8/77cBRbX8Acrc4wG1814ggLMp1RxRgoHLnzIz0tSbay6eE/TuUMqRalQ
HAurDKHOJEjS3Kv5SKli0MzsTwGxyoycF6er76CYiIo+n1CBBRrIg/iDaLkKV4TK
E56rxVfVKmN1yg5lNYTg+F7DDudY4/R6RGmORi9dsmgGS/qeKcX/ggdXrgt1Hd07
0xOQmR1rdKnmNoqJXoYhSmMHvCRBc1Yf4xkfvOsE8LQoG91lpucsWjAJM6FnHZRU
TlOXa/Z3DDtbr17arJdFtOSsaYodhZcG42diamhbMvKyoYYTwwXubFKOZCQplrin
343cmbhpGfIyhSMerWOsULDffhizfkH8cyXjb2bJZk1zX8/CUtPegAjv0L0zdtv+
6A8UZqGDSbzzGuksUtcNLpnaQeDoLm2GlF8r6JCGRt/31ROI2Eqf71hve55s2DE1
whdv+YxmphNgnCn095p8gnOZMmYz2tQMEtslKr+TmYWNxSoB9MCtTDAbtRNxkfnn
rjZxe2vHNapJ6VmIfDDuyNxz3323Z9sAzLkqGAe83Zx7XLpXjs0HUaG2EQnMffT8
Frfr9ptczfav1tkmFQMBmCL5xS4/1gkQyNwB2wy8Kdez0T6Oxm31D63HgwKT9pmE
6EGnxUOBvNk3MEeiaC10plR3cl2PxANqfbtwPuor/a2IQq2zABnjaPgrQn1zexB5
0ncTjv3OcQLAH0di7V0vKpTIQpUL8QM+Sor5YRSO36CgJxVrS7aKo8W0QRSUwgy9
PGEHu3tagqs05ryIcyU0KaO3KJzkGA/in/OGtm2x3/lFogsvTajleIDcqO6rHYGV
JYtXn8drG31cbmTtak+N/VfmAVpQ6PJG8b3YevW1W1ySxriTm4jGMvtunDtreyEB
MXzSeWhtWot6IBWDMNqh9JIghmG+gwI1xD2AK1BR9ifSgjQ8ZA8mc2C2kinka9wl
Sl7/9/rdsQQRJs7inNUvJ8W4eY62ILlRyAe0xaUlo08JUhlK3Xf3LWD4frRfHoBx
hCxfOAnlSzaRksatd0N72LiVLIL864peScyMpvS1EaE1aUGhfnFemb5wXIewyY1g
Hj6bKTQlt0iB+aVj1EWSfGrZ8sshWB91dBNCssu0q+DHHzAX1wkE0i8eNlLlFcmm
aDReRJSS+7qAVGdksEyzE+IGAzbXnYKyWudpdB/WwR+6kDEKsqFv52z0i0JH83Tj
QvinHcyh3nLfXf+GV9LYjLhZEOkHm8diHgYdRMsY2d21jd0q6Eo7hiQzF3pSutj2
GxDya0+rDK8LP9LboYOUTyJaNZPcqlTrQjQQls55kTnHinImYgiT91w6GhFS4GU4
E3KSIsYzBo64HjHl0vLwcfJ6ghvUMu4cTW1z1L0+ieKqiajIMuvQmIxhS9fO2qVg
FbsihnJKq/EbeU7uMGq/3FJWJk0D0G8SiJsgP85mbY90qePW3CvnoRnH6PemYCeF
T3qJMPFgT2ncLhIrC5cR7F27DCU/CH1jJW4GRx7PeNBeLErWpDghzeJS5IJFW5q8
RIw/HJaLd6TmPNnjQ7XXpU6J519EHRmFDnANXooLDFnwDqam0sokdg9ix4yQYw+e
jh3mOQJ5lwtccSFpcgGvzApA+xd62//qFixqe0zoq9ThEvPB9wKQe8aAtCsDxrvw
PKLbsdy9OdqM1h3TWh+ioWZJb69LRA9MoArAZ8ntpHluQ1amL1wiV8wJReXD4kua
fGbf+S1wnUlH4lTkJa0ApTIM0OsWzYFb2F8VDdgvfmtCSYlbS37Qy4+TKJFNtMEA
FQyLUmAlgCdgAiBLVrrV9uDYeRnPVUShlsyZCwBUm92cjDiQkSWhDjro7NQTBMfo
I4A+5OhaX61eNJYFqXv0KWBTGjRnW/dhAilNlc0QWKO+p4mwtTUlwVe0EMb3naxh
9ioJUHlwkcfJWBQAVAR/pbslzlpND8wE8NnH5P6z0H95ft3Q6v+JYD2zdhTTfTlw
X/YlQuf14Vuey6B9bnAPHKh2zE5x53MwVL0OvnfVnw==
MIIJqzBVBgkqhkiG9w0BBQ0wSDAnBgkqhkiG9w0BBQwwGgQU95KG57RacAYBmkeQ
DIe1bZS0sbkCAggAMB0GCWCGSAFlAwQBKgQQyxdAya9Sd4oHLO1pzVWcYASCCVDT
ozdXT3vjyqMzza4QKaMD4ywSAzGhQRM/TnxU5JbRLNMpdtq76Mfet2pv++UUjcof
16EsdOOpDQdxdzQWmwGUNwjkX5YyWTaAefV8l9n6Bp8LV0XabS9We3g5Jr1KjuzP
O/xJgB2o6BcD/WRPeOaANSGoyWce4rCkpDwqxrp+tY9EK19SoCZG9Zy2hnPPH2Hc
QgtgCAzqaXIp49KIXHn/Uo532lIz3WqkkhzVakwgAKLKIvc/SwgP0eSXLvPjeJYS
L8DngPP0YD7IPgIs7WmMNNE7or69e7mO0miUOl7xStNHzHpLmtLNbYI7Pk6NLT7N
kWfh2+E21R7llsW57boMACXVr7N3CHOlZQhUNViyjPayo1njVnp6gGzuIxluhHJY
CL070oqBeEYVfvE07HQ4Qd0BL5c02pdrKjdzBYyLwzSNKn2RzgS2R/XtEqdmOUo+
iuRngv9D1UPSI2xlFhv84778ktEeSf8l1nLltqhPJAmJUjSAcu/zjN4Q+HXqMRaF
IocDV4I7CaXDc2E0YdU8uHuzzUHLflJ2OZwU5N7tkoVOtAYHKUwCP4J/zpLSe2V2
MIh40IVJK4gzb+iyBiOnsnKKQCKMPbS4lH8zC2S486MgjgbhlZeFg0nOF955c61l
Sb4MBrexU4s1TUg/fDpYt6jPZoKivN72jzi60kV43gBFHmP3X4SRAUQ4Y3h5NFF8
h2p4wvYRsYEexjJU/+WJG4Yi1wSi3oEqD161a6vPOsKBLBdLRo1vgnQdGFx/k83X
vjPlI2eEUMPCntNBbrTy8eUSJz/0OH2phztZpHuh5cfy4ErUi19d9ywZUlhurGvX
dC7ouTEqRZLkkSCfGTQM0q0O4JQJTLb5N4gWdZxQd2UwGv3jCK7m5eWx3bTdhhXi
179DoSpYBCJF3msn0ROO6PxsccH0w/I6KMi3QNmsDlXhDr6XIBya8CU0lx9lp0pl
5q62D26Ylr2fovd3qKKbwP6RaZarCzKLO6dWdyMqtUwVlX2FDCFd/SPGWc2TmuVS
vLb981Zm13AfYtNUSfusroDp3TEuvl7cwozg7p33SQhuCmgKnxMd0iXd5QQZjrR0
t+y22dHrD1agkkoFMLz/+d+930J0sY4odG/HbL2Bv8ZelVUjA8XSFoGBEA+rfQCg
DGmLh5a+/yfzxCEKWVLqmwHWbSkub8bXdl6EKEyaO9qo1KCLAf3tArQx45sqw8bK
8AYq2mrNIiMDhHub+XEEC0Aw2lZkJOrwwMEsTcZWfBvj56MdRNXuZMvPdarTbnDx
zzxatqIwfvpOy/S2Poyrc6GuprbZCM6N+cDLdWQqAHVwAlx77NhiJ6s3vUnE3vB7
aHgmXU+a8uPA64tKKaRNQJ31f7viCkWJXEbbEhVTzCvFcoqbKPPMm9w7nO8PMUTu
BmwSFEKhd3BDKZavqTHKi66fF3A5ALFYAkMw/AlvinMitb9s+7WlWQrdvSFkqHsY
wNQ1ankleYd24/8ZllvsQpleLMepDSxP6zUMpXSHbTKp5MZeoCaaY1RCkg7aOduz
brnD7lRAfLp0H72nxVgC7n6VjidOSruF7k9WIN9VVbP0ZVL/QtkKRWd/hEmtMNaH
ELg2ekdm3zvdBuvtr0jNiCxbhTr3j5OWQkT/BjZxHpZfA14XEROJC2Slo3PxUwBH
0lE0cICWTeaeYcCX8ofawN+t1Qa6UD0sLl2670Kc7pozkJM4ul19rGA2KsHX89gE
CaB1CkhFCqZhPbqX9yonv9XZtLb8Of8rBNVd/2QKN4/tOXcMYshzakSfSSIsyxxt
QgMPRfz0nJTtP7v8ZbwIO+ayGoUeH7aYKhQ6Ku3qW9XuYiy+oMTIOToCSddnEI5t
JNuPkT9kzA9stkRbFV5kBvrv5LWprWDXdA/wyAWG7txncWj6UzGlP8C3KhtMHLHv
CiOXrE8UJdNNeT52dYI9slg+tzcCfz3sqMr9zXratvT6JMzrQZqCSis8vIx18TIK
N5yDWHDFUOeNpo7aRqd5goW3qProwfZDjBXiqE4J+AJ5wc73PuftHt2l00zvLDWs
SFIRvXbavNBA7GxpVtN8Qxmk6Lm0u0pBiastndowgAI5OIQVuwoA21vXyC5n9pMd
bPJsmiPyme62OkCWmAjBNDLNVViwKMH8BxmLKJxX+6ysNsn0YY1+9YfI/zC3j4jM
OYsK1c0NvFIv5aUxRQZLTJJt9C299jGNvdAJsfdp4LHejzZUjnx3nguz/l6RI1Vb
vjQ1qDRPhkgErGXSHsCoCt+z5Y6mq17JWEX/FiXBWQbfSGoG/ZvoOqiBybCQ3HNl
o9QM1sNQ5fUZDh0TgwkJB91rZXPwi828RklMW8VZszZir5gziTnndhw0ADLCZZ6z
nA0vZAI7sjoEeIgiJq3egrsSLq2ZQRQsh5QF+Xo2QktleGvPrtMv//ZyGz4l59yc
wX/7DtABurFhVs3KdYohcqXk2v5jJCMs+j9YDn6540QR6yXcbifp9ySqhm/PeH91
UuL16YKxoV6QBZIGE0vjdUitGKNsS+H4ibD/0ZHYG+VcyL90eIrBq61CjfIO79O0
L9+G4gKB91stXwtpqZWXTrlzrnjloZOPhqyQN/bs/liWQ6qy0a6Cd6nbWc141An1
zEiOihbwLJ4ziCut+bq5lwyw6z/wWEhaVNnYspEEBr2URLMHbnBceS6zXoePT0ur
9mQQLitmtlANlJ93vBDPhCaEjkK1v5J7MmIHQzyLSQGuLdXwz50piJukWru3aNax
skloghJYeTMILEcGAszvyVtcvPqkrJnZXx4Qp7Luj5HK9THr78v3T4nWzirfqxPZ
x70xRyhsC2lLcIrJ+3jkXj44edIqdh3Wvi30L2x2iUFyZ0ojQJQDo/+5b+p9k36L
Dk8ktpeIa/BE3NsfcFaWn9bvRkQ6UAQcNn1zmkavfw5TLI4C1PnD/WUpPHZdhzNV
K87CsUawxjEg0uCCaViShF6bD9mOWQxE3SM9yNizjTmotF6KrgkT16y/qZ17KGQM
hJ5PraGu9jvg+L/MrQpr91eyJaeh9JFl9dM/SPM0mXo5q813bdMmqD4cc3YWCLee
dHtmaKJ08KD1cJqHBz0DRLVV+zH00BMoYt5HZ5DmHFU1zhDekWZLhilbyWt8+z1E
bzsoEAfZvyfvF7fJuxQ/HhYdR6TX5H+aNzZZivVc6g==
-----END ENCRYPTED PRIVATE KEY-----

View File

@ -1,56 +1,56 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
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
-----END ENCRYPTED PRIVATE KEY-----
-----BEGIN CERTIFICATE-----

View File

@ -1,32 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIFdzCCA1+gAwIBAgIUdL2pr5w+jKA9HF9llVbMRTK4MO8wDQYJKoZIhvcNAQEL
BQAwSzELMAkGA1UEBhMCQ0gxDTALBgNVBAcMBEJlcm4xEjAQBgNVBAoMCUFHT1Yg
V29yazEZMBcGA1UEAwwQYXRiLXdvcmstaWRwLWtleTAeFw0yNTA5MDMwNjQ2Mjha
Fw0zNTA5MDEwNjQ2MjhaMEsxCzAJBgNVBAYTAkNIMQ0wCwYDVQQHDARCZXJuMRIw
EAYDVQQKDAlBR09WIFdvcmsxGTAXBgNVBAMMEGF0Yi13b3JrLWlkcC1rZXkwggIi
MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2s6fPlpWv/1zEnail7TCUphEQ
A/dr/uY+qQqA/okB+Okd5hGDow7zBe/zICn7PJlGXzkq87o4Q3ZFvOFLqvlhwprp
OQquIviN6VBss2F3c174Zkk7ksciLQzPYjGBgw+l/ZeZY/AOYBeConsrHobTbjPd
StI8FZr8zVnamMWd/nBnryA5mZy9+vKz3iPJXPXZmyhBnOJfPZjMmkLvY9wEfGfc
rGrbqh6f7grleVNU16Rt46TtJRIqWEAdqi1I81d3kEWuqHkYCZf1ZJpDtprJPVko
fWViFzMz7zuAK5kdaGVwu0R7zeKz6FCHWWQ5bqScQbZ53zX6D3sP6ZNnZXdo6n0L
i+x17sgZa6VJtWF6s/UUxl8jPteprfRHrgIT3yKK9ewpXEhcc4aNJyCTiXpicOOn
QUBkkxyT7MtG1j51GPFcoFsBn4X9A1BXUmz2+YrDfFKtj0LwKZe6naI5v+FGtqeQ
/GeRpaFISwg/L5ewHe3NTH//8ZyWQsbJ2FEIff3LM+0+ivrORJs45GW12ny6MDY1
Q8PTEsPL/9nhY1Mf99qpB9ivouVF/vGDWont16PhaZ2N31Osbbok3Emfbk0MVfvh
MuY0PPX/eWfn+5WlxBegS9PXbrcNW7MV0vsow8Js9+B29nao/VeFOQDfrU9p//xu
nDkeh9z5vqRP7clgMQIDAQABo1MwUTAdBgNVHQ4EFgQUqqmWA9MTwbzRFOfxZbu8
nIyk4dEwHwYDVR0jBBgwFoAUqqmWA9MTwbzRFOfxZbu8nIyk4dEwDwYDVR0TAQH/
BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAnh1nayZy7CjTDvXjht0jNEyCPahL
/gzcfx173FWnDbG3DMqjKB0u7bbpWIdStvTHpvs4NOg7H1/3Xc3cu3vtw6PF3Tkt
ZGJrMgZ5H9BUPW7BeNPqylh0Xj9vWUhxOdRfthzHcuSg2H6k5GBe+ROVIWLcc5g2
vIuEEnpL9H5mlt4MofodPJjDrOvbJ5eDOGnNlcSKgPy8ZxrvyesmjFquu9/941p5
wOpGhfVRH6U9GBIy1wWjjO4y2oRtgdgV0Dm57VNaxNi4R0cRW+eg7H7jED2gWVdS
Zftkrq44/lXFnWZDXWq8JJs0QPPD30i8fbGvZjRbrVQus5wW+dlirSkljQD8WpiY
N7PS2y+Io9WDetabxDSkHQGduldlHqnjvvR7TtLBT73fbmrra7nLrxbwAyQs/lp9
r2904tzgBfhHb5GCrYE1s3h339eb/HXZlPqG1EcYimsAIyyBQ7WyHOgXq5RqwgbW
9O8aQUWPQrdtWrv8BkYSjjgDSxj9Pu7yBFnSdyI879uvBZDYovm/MmgcguAaJ8UC
PUcchbvgdLJHnbBA5aFm/Fkhb2WKi3Q0vExUHM3sXazJAAjIplbunHkqf8Wc7lva
94y3AXN9dg5LEjcwkjQbyGmmuSFq0Hse0b1KE+4INYUigECUcXuKYWrP0RuPzCKU
4g4p3ZpFGmoq4lM=
-----END CERTIFICATE-----

View File

@ -10,11 +10,11 @@ agov-ident.invalid-url.message=Link can't be processed
agov-ident.invalid-url.title=Invalid Link
agov-ident.onboarding=Registration & Verification
agov-ident.retry=Try again
button.submit=Submit
cancel.button.label=Cancel
continue.button.label=Continue
darkModeSwitch.aria.label=Dark mode toggle
deputy.profile.label=(Deputy Profile)
error.account.exists=Account already exists. Continue to log in.
error.policy.failed=The new password does not comply with the policy.
error.saml.failed=Please close your browser and try again.
error_1=Please check your input.
@ -65,7 +65,7 @@ general.edit=Edit
general.email=Email
general.email.address=Email address
general.entryCode=Code entry
general.fieldRequired=Field required
general.fieldRequired=Field required.
general.getStarted=Get started
general.goAGOVHelp=Go to AGOV help
general.goAccessApp=Login with AGOV access
@ -83,8 +83,8 @@ general.recovery=Recovery
general.recovery.help.link=https://help.agov.ch/?c=100recovery
general.recoveryCode.downloadPdf=Download as PDF
general.recoveryCode.inputLabel=Recovery code
general.recoveryCode.repeatCodeError=The code you entered was incorrect. Please ensure you have stored it correctly and try again.
general.recoveryCode.repeatCodeModal.description=To ensure you have recorded your code correctly, please repeat it below. A lost or incorrectly stored recovery code can make it more difficult to recover your account.
general.recoveryCode.repeatCodeError=The code you entered was incorrect. Please ensure you have stored it correctly, then continue to resubmit.
general.recoveryCode.repeatCodeModal.description=A lost or incorrectly stored recovery code can make it more difficult to recover your account. To ensure you have recorded your code correctly, please repeat it below.
general.recoveryCode.repeatCodeModal.title=Repeat recovery code
general.recoveryCode.reveal=Reveal recovery code
general.recoveryOngoing=Ongoing recovery
@ -111,8 +111,8 @@ language.fr=Fran&ccedil;ais
language.it=Italiano
language.rm=Rumantsch
languageDropdown.aria.label=Select language
loainfo.description.200=To access the application, we need to verify your data. The process can take up to 2-3 days.
loainfo.description.300=To access the application we need to verify your data. You can choose your preferred process in the next step.
loainfo.description.200=To access the application, we need to verify your data. The process can take up to 2 - 3 days.
loainfo.description.300=To access the application we need to verify your data through one of two processes. You can choose your preferred process in the next step.
loainfo.description.400=To access the application we need you to add your SSN (AHV) number.
loainfo.helper=Your data needs to be verified!
loainfo.later=Later
@ -124,8 +124,8 @@ logout.label=Logout
logout.text=You have successfully logged out.
mauth_usernameless.EID=Continue with CH E-ID
mauth_usernameless.banner.error=Authentication interrupted.<br>Please try again when the page reloads.
mauth_usernameless.banner.info=Scan successful. Please continue in the AGOV access app.
mauth_usernameless.banner.success=Authentication successful.<br>Please wait to be logged in.
mauth_usernameless.banner.info=Scan successful.<br>Please continue in the AGOV access app.
mauth_usernameless.banner.success=Authentication successful!<br>Please wait to be logged in.
mauth_usernameless.cannotLogin=Lost access to your app / security key?
mauth_usernameless.cannotLogin.accessApp=Lost access to your app?
mauth_usernameless.cannotLogin.securityKey=Lost access to your security key?
@ -214,7 +214,7 @@ prompt.newpassword=New Password
prompt.newpassword.confirm=Confirm Password
prompt.password=Password
prompt.userid=User-ID
providePhoneNumber.banner=Phone number must be able to receive SMS. It will not be used to contact you.
providePhoneNumber.banner=Phone number must be able to receive SMS.<br>This phone number will not be used to contact you.
providePhoneNumber.description=AGOV now supports recovery with your phone number. This will allow you to continue with an SMS during recovery if you have lost access to your recovery code.
providePhoneNumber.errorBanner=Phone numbers do not match. Please try again.
providePhoneNumber.inputLabel=Phone number (optional)
@ -222,7 +222,7 @@ providePhoneNumber.laterModal.description1=Without a phone number, a recovery of
providePhoneNumber.laterModal.description2=Adding a phone number helps you to recover your account in a matter of minutes.
providePhoneNumber.laterModal.description3=This phone number will not be used to contact you.
providePhoneNumber.laterModal.title=Continue without a phone number?
providePhoneNumber.modal.description=To ensure you have recorded your phone number correctly, please repeat it below. An incorrectly stored phone number can make it more difficult to recover your account.
providePhoneNumber.modal.description=An incorrectly stored phone number can make it more difficult to recover your account. To ensure you have recorded your phone number correctly, please repeat it below.
providePhoneNumber.modal.inputLabel=Phone number
providePhoneNumber.modal.title=Repeat phone number
providePhoneNumber.saveButtonText=Save
@ -231,14 +231,12 @@ pwreset.done.info=Your password was successfully changed. Please click on contin
pwreset.email.sent=If your user ID exists, an email to reset your password has been sent to you.
pwreset.info.linktext=Password forgotten
pwreset.noticket=Your password reset link is no longer valid. Please generate a new one.
qrCode.label=Click to open QR code in pop-up window.
recovery_accessapp_auth.accessAppRegistered=AGOV access app already registered
recovery_accessapp_auth.instruction1=You have already registered a new AGOV access app !!!ACCESS_APP_NAME!!! as part of the recovery process.
recovery_accessapp_auth.instruction2=Please use !!!ACCESS_APP_NAME!!! to identify you.
recovery_check_code.banner.lockedError=Too many invalid input attempts. Please try again in a few minutes.
recovery_check_code.codeIncorrect=Code entered is incorrect. Please try again.
recovery_check_code.enterRecoveryCode=Recovery code
recovery_check_code.expired=Too many attempts or your recovery code has expired.
recovery_check_code.enterRecoveryCode=Enter recovery code
recovery_check_code.instruction=Please enter below your personal 12-digit recovery code. You will have received the recovery code as a PDF file during registration or in AGOV me.
recovery_check_code.invalid.code=The code is invalid
recovery_check_code.invalid.code.required=Code required
@ -251,9 +249,9 @@ recovery_check_code.too_many_tries.instruction2=Please go to AGOV help in order
recovery_check_noCode.banner.error=Too many attempts.
recovery_check_noCode.instruction1=You might have tried to enter the recovery code too many times.
recovery_check_noCode.instruction2=Please close the web browser and start the account recovery again in ten minutes from <a class='link' href='https://agov.ch/me'>https://agov.ch/me</a>.
recovery_code.banner.error=Please reveal your recovery code to be able to continue.
recovery_code.banner.error=Please reveal your new code to be able to continue.
recovery_code.instruction=Recovery codes help you gain access to your account in case you lost all of your login factors. Please store the recovery code in a safe place.
recovery_code.newRecoveryCode=Introducing recovery code
recovery_code.newRecoveryCode=Introducing Recovery Code
recovery_code.validUntil=Valid until:
recovery_fidokey_auth.button=Start key authentication
recovery_fidokey_auth.fidoInstruction=Click on "Start key authentication"
@ -297,8 +295,6 @@ recovery_start_info.banner.warning=You will not be able to use your account unti
recovery_start_info.instruction=During the recovery process you will register a new login factor. If your account contains any verified information you might also have to go through a verification process to finish the recovery.
recovery_start_info.title=You are about to start the recovery process
reject.button.label=Deny
signup.button.label=Signup
skip.button.label=Skip
submit.button.label=Submit
tan.sent=Please enter the security code which has been sent to your mobile phone.
title.login=Login
@ -309,7 +305,6 @@ title.oauth.consent=Client Authorization
title.pwchange.label=Password Change
title.pwreset=Password Forgotten
title.saml.failed=Error
title.signup=Create account
title.timeout.page=Logout
user_input.invalid.email=Please enter a valid email address
user_input.invalid.email.required=Field required

View File

@ -10,11 +10,11 @@ agov-ident.invalid-url.message=Link kann nicht verarbeitet werden
agov-ident.invalid-url.title=Ung&uuml;ltiger Link
agov-ident.onboarding=Registrierung & Verifikation
agov-ident.retry=Versuchen Sie es erneut
button.submit=Senden
cancel.button.label=Abbrechen
continue.button.label=Weiter
darkModeSwitch.aria.label=Dark-Mode-Schalter
deputy.profile.label=(Profil Stellvertreter)
error.account.exists=Konto existiert bereits. Melden Sie sich an.
error.policy.failed=Das neue Passwort stimmt nicht mit der Richtlinie &uuml;berein.
error.saml.failed=Bitte schliessen Sie Ihren Browser und versuchen Sie es erneut.
error_1=Bitte &uuml;berpr&uuml;fen Sie Ihre Eingaben.
@ -65,7 +65,7 @@ general.edit=&Auml;ndern
general.email=E-Mail
general.email.address=E-Mail-Adresse
general.entryCode=Code-Eingabe
general.fieldRequired=Erforderliches Feld
general.fieldRequired=Erforderliches Feld.
general.getStarted=Los geht's
general.goAGOVHelp=Weiter zur AGOV help
general.goAccessApp=Login mit AGOV access
@ -83,8 +83,8 @@ general.recovery=Wiederherstellung
general.recovery.help.link=https://help.agov.ch/?c=100recovery
general.recoveryCode.downloadPdf=Als PDF herunterladen
general.recoveryCode.inputLabel=Wiederherstellungscode
general.recoveryCode.repeatCodeError=Der von Ihnen eingegebene Code war nicht korrekt. Bitte vergewissern Sie sich, dass Sie ihn richtig abgespeichert haben, und versuchen Sie es erneut.
general.recoveryCode.repeatCodeModal.description=Um sicherzustellen, dass Sie Ihren Code richtig gespeichert haben, wiederholen Sie ihn bitte unten. Ein verlorener oder falsch gespeicherter Wiederherstellungscode kann die Wiederherstellung Ihres Kontos erschweren.
general.recoveryCode.repeatCodeError=Der von Ihnen eingegebene Code war nicht korrekt. Bitte vergewissern Sie sich, dass Sie ihn richtig abgespeichert haben, und fahren Sie dann mit der erneuten Eingabe fort.
general.recoveryCode.repeatCodeModal.description=Ein verlorener oder falsch gespeicherter Wiederherstellungscode kann die Wiederherstellung Ihres Kontos erschweren. Um sicherzustellen, dass Sie Ihren Code richtig gespeichert haben, wiederholen Sie ihn bitte unten.
general.recoveryCode.repeatCodeModal.title=Wiederherstellungscode wiederholen
general.recoveryCode.reveal=Wiederherstellungscode enth&uuml;llen
general.recoveryOngoing=Wiederherstellung nicht abgeschlossen
@ -111,8 +111,8 @@ language.fr=Fran&ccedil;ais
language.it=Italiano
language.rm=Rumantsch
languageDropdown.aria.label=Sprache w&auml;hlen
loainfo.description.200=Um auf diese Applikation zuzugreifen, m&uuml;ssen wir Ihre Angaben verifizieren. Der Vorgang kann bis zu 2&ndash;3 Tage dauern.
loainfo.description.300=Um auf diese Applikation zuzugreifen, m&uuml;ssen wir Ihre Angaben verifizieren. Sie k&ouml;nnen Ihre bevorzugte Methode im n&auml;chsten Schritt ausw&auml;hlen.
loainfo.description.200=Um auf diese Applikation zuzugreifen, m&uuml;ssen wir Ihre Angaben verifizieren. Der Vorgang kann bis zu 2 - 3 Tage dauern.
loainfo.description.300=Um auf diese Applikation zuzugreifen, m&uuml;ssen wir Ihre Angaben durch einen von zwei Vorg&auml;ngen verifizieren. Sie k&ouml;nnen die bevorzugte Methode im n&auml;chsten Schritt ausw&auml;hlen.
loainfo.description.400=Bitte AHV-Nummer angeben, um auf die Applikation zuzugreifen.
loainfo.helper=Ihre pers&ouml;nlichen Daten m&uuml;ssen &uuml;berpr&uuml;ft werden!
loainfo.later=Sp&auml;ter
@ -124,8 +124,8 @@ logout.label=Logout
logout.text=Sie haben sich erfolgreich abgemeldet.
mauth_usernameless.EID=Mit Schweizer E-ID fortfahren
mauth_usernameless.banner.error=Authentifizierung unterbrochen.<br>Bitte versuchen Sie es erneut, nachdem die Seite neu geladen wurde.
mauth_usernameless.banner.info=Scan erfolgreich. Bitte fahren Sie in der AGOV access App fort.
mauth_usernameless.banner.success=Authentifizierung erfolgreich.<br>Bitte warten Sie, bis Sie eingeloggt werden.
mauth_usernameless.banner.info=Scan erfolgreich.<br>Bitte fahren Sie in der AGOV access App fort.
mauth_usernameless.banner.success=Authentifizierung erfolgreich!<br>Bitte warten Sie, bis Sie eingeloggt werden.
mauth_usernameless.cannotLogin=Zugriff auf App / Sicherheitsschl&uuml;ssel verloren?
mauth_usernameless.cannotLogin.accessApp=Zugriff auf App verloren?
mauth_usernameless.cannotLogin.securityKey=Zugriff auf Sicherheitsschl&uuml;ssel verloren?
@ -214,7 +214,7 @@ prompt.newpassword=Neues Passwort
prompt.newpassword.confirm=Passwort best&auml;tigen
prompt.password=Passwort
prompt.userid=Benutzer-ID
providePhoneNumber.banner=Die Mobilnummer muss f&uuml;r den Empfang von SMS geeignet sein. Sie wird nicht verwendet, um Sie zu kontaktieren.
providePhoneNumber.banner=Die Mobilnummer muss f&uuml;r den Empfang von SMS geeignet sein.<br>Diese Mobilnummer wird nicht verwendet, um Sie zu kontaktieren.
providePhoneNumber.description=AGOV erlaubt nun die Wiederherstellung mittels Mobilnummer. So k&ouml;nnen Sie w&auml;hrend der Wiederherstellung mit einer SMS fortfahren, wenn Sie Ihren Wiederherstellungscode verloren haben.
providePhoneNumber.errorBanner=Die Mobilnummern stimmen nicht &uuml;berein. Bitte versuchen Sie es erneut.
providePhoneNumber.inputLabel=Mobilnummer (optional)
@ -222,7 +222,7 @@ providePhoneNumber.laterModal.description1=Ohne Mobilnummer kann die Wiederherst
providePhoneNumber.laterModal.description2=Durch Hinzuf&uuml;gen einer Mobilnummer k&ouml;nnen Sie Ihr Konto in wenigen Minuten wiederherstellen.
providePhoneNumber.laterModal.description3=Diese Mobilnummer wird nicht verwendet, um Sie zu kontaktieren.
providePhoneNumber.laterModal.title=Ohne Mobilnummer weiterfahren?
providePhoneNumber.modal.description=Um sicherzustellen, dass Sie Ihre Mobilnummer richtig gespeichert haben, wiederholen Sie sie bitte unten. Eine falsch gespeicherte Mobilnummer kann die Wiederherstellung Ihres Kontos erschweren.
providePhoneNumber.modal.description=Eine falsch gespeicherte Mobilnummer kann die Wiederherstellung Ihres Kontos erschweren. Um sicherzustellen, dass Sie Ihre Mobilnummer richtig gespeichert haben, wiederholen Sie sie bitte unten.
providePhoneNumber.modal.inputLabel=Mobilnummer
providePhoneNumber.modal.title=Mobilnummer wiederholen
providePhoneNumber.saveButtonText=Speichern
@ -231,14 +231,12 @@ pwreset.done.info=Ihr Passwort wurde erfolgreich ge&auml;ndert. Bitte klicken Si
pwreset.email.sent=Wenn Ihre Benutzer-ID existiert, haben Sie eine E-Mail erhalten, um Ihr Passwort zurückzusetzen..
pwreset.info.linktext=Passwort vergessen
pwreset.noticket=Ihr Link ist nicht mehr g&uuml;ltig. Bitte generieren Sie ein Neuen.
qrCode.label=Klicken Sie, um den QR-Code in einem Fenster zu &ouml;ffnen.
recovery_accessapp_auth.accessAppRegistered=AGOV access App schon registriert
recovery_accessapp_auth.instruction1=Sie haben bereits eine neue AGOV access App !!!ACCESS_APP_NAME!!! im Rahmen des Wiederherstellungsprozesses registriert.
recovery_accessapp_auth.instruction2=Verwenden Sie !!!ACCESS_APP_NAME!!! um sich zu identifizieren.
recovery_check_code.banner.lockedError=Zu viele Fehlversuche. Bitte versuchen Sie es in ein paar Minuten noch einmal.
recovery_check_code.codeIncorrect=Der eingegebene Code ist nicht korrekt. Bitte versuchen Sie es erneut.
recovery_check_code.enterRecoveryCode=Wiederherstellungscode
recovery_check_code.expired=Zu viele Versuche oder Ihr Wiederherstellungscode ist abgelaufen.
recovery_check_code.enterRecoveryCode=Wiederherstellungscode eingeben
recovery_check_code.instruction=Bitte geben Sie unten Ihren pers&ouml;nlichen 12-stelligen Wiederherstellungscode ein. Sie haben den Wiederherstellungscode in einer PDF-Datei bei der Registrierung oder in AGOV me erhalten.
recovery_check_code.invalid.code=Code ist ung&uuml;ltig
recovery_check_code.invalid.code.required=Code erforderlich
@ -297,8 +295,6 @@ recovery_start_info.banner.warning=Sie k&ouml;nnen Ihr Konto nicht nutzen, bis d
recovery_start_info.instruction=W&auml;hrend des Wiederherstellungsprozesses werden Sie einen neuen Login-Faktor registrieren. Wenn Ihr Konto verifizierte Informationen enth&auml;lt, m&uuml;ssen Sie zum Abschluss des Wiederherstellungsprozesses m&ouml;glicherweise auch einen Verifikationsprozess durchlaufen.
recovery_start_info.title=Sie sind dabei, den Wiederherstellungsprozess zu starten
reject.button.label=Ablehnen
signup.button.label=Registrieren
skip.button.label=&Uuml;berspringen
submit.button.label=Senden
tan.sent=Bitte erfassen Sie den Sicherheitscode, welcher an Ihr Mobiltelefon gesendet wurde.
title.login=Login
@ -309,7 +305,6 @@ title.oauth.consent=Client Authorisierung
title.pwchange.label=Passwort &auml;ndern
title.pwreset=Passwort Vergesssen
title.saml.failed=Error
title.signup=Konto erstellen
title.timeout.page=Logout
user_input.invalid.email=Bitte geben Sie eine g&uuml;ltige E-Mail ein
user_input.invalid.email.required=Erforderliches Feld

View File

@ -10,11 +10,11 @@ agov-ident.invalid-url.message=Link can't be processed
agov-ident.invalid-url.title=Invalid Link
agov-ident.onboarding=Registration & Verification
agov-ident.retry=Try again
button.submit=Submit
cancel.button.label=Cancel
continue.button.label=Continue
darkModeSwitch.aria.label=Dark mode toggle
deputy.profile.label=(Deputy Profile)
error.account.exists=Account already exists. Continue to log in.
error.policy.failed=The new password does not comply with the policy.
error.saml.failed=Please close your browser and try again.
error_1=Please check your input.
@ -65,7 +65,7 @@ general.edit=Edit
general.email=Email
general.email.address=Email address
general.entryCode=Code entry
general.fieldRequired=Field required
general.fieldRequired=Field required.
general.getStarted=Get started
general.goAGOVHelp=Go to AGOV help
general.goAccessApp=Login with AGOV access
@ -83,8 +83,8 @@ general.recovery=Recovery
general.recovery.help.link=https://help.agov.ch/?c=100recovery
general.recoveryCode.downloadPdf=Download as PDF
general.recoveryCode.inputLabel=Recovery code
general.recoveryCode.repeatCodeError=The code you entered was incorrect. Please ensure you have stored it correctly and try again.
general.recoveryCode.repeatCodeModal.description=To ensure you have recorded your code correctly, please repeat it below. A lost or incorrectly stored recovery code can make it more difficult to recover your account.
general.recoveryCode.repeatCodeError=The code you entered was incorrect. Please ensure you have stored it correctly, then continue to resubmit.
general.recoveryCode.repeatCodeModal.description=A lost or incorrectly stored recovery code can make it more difficult to recover your account. To ensure you have recorded your code correctly, please repeat it below.
general.recoveryCode.repeatCodeModal.title=Repeat recovery code
general.recoveryCode.reveal=Reveal recovery code
general.recoveryOngoing=Ongoing recovery
@ -111,8 +111,8 @@ language.fr=Fran&ccedil;ais
language.it=Italiano
language.rm=Rumantsch
languageDropdown.aria.label=Select language
loainfo.description.200=To access the application, we need to verify your data. The process can take up to 2-3 days.
loainfo.description.300=To access the application we need to verify your data. You can choose your preferred process in the next step.
loainfo.description.200=To access the application, we need to verify your data. The process can take up to 2 - 3 days.
loainfo.description.300=To access the application we need to verify your data through one of two processes. You can choose your preferred process in the next step.
loainfo.description.400=To access the application we need you to add your SSN (AHV) number.
loainfo.helper=Your data needs to be verified!
loainfo.later=Later
@ -124,8 +124,8 @@ logout.label=Logout
logout.text=You have successfully logged out.
mauth_usernameless.EID=Continue with CH E-ID
mauth_usernameless.banner.error=Authentication interrupted.<br>Please try again when the page reloads.
mauth_usernameless.banner.info=Scan successful. Please continue in the AGOV access app.
mauth_usernameless.banner.success=Authentication successful.<br>Please wait to be logged in.
mauth_usernameless.banner.info=Scan successful.<br>Please continue in the AGOV access app.
mauth_usernameless.banner.success=Authentication successful!<br>Please wait to be logged in.
mauth_usernameless.cannotLogin=Lost access to your app / security key?
mauth_usernameless.cannotLogin.accessApp=Lost access to your app?
mauth_usernameless.cannotLogin.securityKey=Lost access to your security key?
@ -214,7 +214,7 @@ prompt.newpassword=New Password
prompt.newpassword.confirm=Confirm Password
prompt.password=Password
prompt.userid=User-ID
providePhoneNumber.banner=Phone number must be able to receive SMS. It will not be used to contact you.
providePhoneNumber.banner=Phone number must be able to receive SMS.<br>This phone number will not be used to contact you.
providePhoneNumber.description=AGOV now supports recovery with your phone number. This will allow you to continue with an SMS during recovery if you have lost access to your recovery code.
providePhoneNumber.errorBanner=Phone numbers do not match. Please try again.
providePhoneNumber.inputLabel=Phone number (optional)
@ -222,7 +222,7 @@ providePhoneNumber.laterModal.description1=Without a phone number, a recovery of
providePhoneNumber.laterModal.description2=Adding a phone number helps you to recover your account in a matter of minutes.
providePhoneNumber.laterModal.description3=This phone number will not be used to contact you.
providePhoneNumber.laterModal.title=Continue without a phone number?
providePhoneNumber.modal.description=To ensure you have recorded your phone number correctly, please repeat it below. An incorrectly stored phone number can make it more difficult to recover your account.
providePhoneNumber.modal.description=An incorrectly stored phone number can make it more difficult to recover your account. To ensure you have recorded your phone number correctly, please repeat it below.
providePhoneNumber.modal.inputLabel=Phone number
providePhoneNumber.modal.title=Repeat phone number
providePhoneNumber.saveButtonText=Save
@ -231,14 +231,12 @@ pwreset.done.info=Your password was successfully changed. Please click on contin
pwreset.email.sent=If your user ID exists, an email to reset your password has been sent to you.
pwreset.info.linktext=Password forgotten
pwreset.noticket=Your password reset link is no longer valid. Please generate a new one.
qrCode.label=Click to open QR code in pop-up window.
recovery_accessapp_auth.accessAppRegistered=AGOV access app already registered
recovery_accessapp_auth.instruction1=You have already registered a new AGOV access app !!!ACCESS_APP_NAME!!! as part of the recovery process.
recovery_accessapp_auth.instruction2=Please use !!!ACCESS_APP_NAME!!! to identify you.
recovery_check_code.banner.lockedError=Too many invalid input attempts. Please try again in a few minutes.
recovery_check_code.codeIncorrect=Code entered is incorrect. Please try again.
recovery_check_code.enterRecoveryCode=Recovery code
recovery_check_code.expired=Too many attempts or your recovery code has expired.
recovery_check_code.enterRecoveryCode=Enter recovery code
recovery_check_code.instruction=Please enter below your personal 12-digit recovery code. You will have received the recovery code as a PDF file during registration or in AGOV me.
recovery_check_code.invalid.code=The code is invalid
recovery_check_code.invalid.code.required=Code required
@ -251,9 +249,9 @@ recovery_check_code.too_many_tries.instruction2=Please go to AGOV help in order
recovery_check_noCode.banner.error=Too many attempts.
recovery_check_noCode.instruction1=You might have tried to enter the recovery code too many times.
recovery_check_noCode.instruction2=Please close the web browser and start the account recovery again in ten minutes from <a class='link' href='https://agov.ch/me'>https://agov.ch/me</a>.
recovery_code.banner.error=Please reveal your recovery code to be able to continue.
recovery_code.banner.error=Please reveal your new code to be able to continue.
recovery_code.instruction=Recovery codes help you gain access to your account in case you lost all of your login factors. Please store the recovery code in a safe place.
recovery_code.newRecoveryCode=Introducing recovery code
recovery_code.newRecoveryCode=Introducing Recovery Code
recovery_code.validUntil=Valid until:
recovery_fidokey_auth.button=Start key authentication
recovery_fidokey_auth.fidoInstruction=Click on "Start key authentication"
@ -297,8 +295,6 @@ recovery_start_info.banner.warning=You will not be able to use your account unti
recovery_start_info.instruction=During the recovery process you will register a new login factor. If your account contains any verified information you might also have to go through a verification process to finish the recovery.
recovery_start_info.title=You are about to start the recovery process
reject.button.label=Deny
signup.button.label=Signup
skip.button.label=Skip
submit.button.label=Submit
tan.sent=Please enter the security code which has been sent to your mobile phone.
title.login=Login
@ -309,7 +305,6 @@ title.oauth.consent=Client Authorization
title.pwchange.label=Password Change
title.pwreset=Password Forgotten
title.saml.failed=Error
title.signup=Create account
title.timeout.page=Logout
user_input.invalid.email=Please enter a valid email address
user_input.invalid.email.required=Field required

View File

@ -2,19 +2,19 @@
accept.button.label=Accepter
agov-ident.done.message=Votre compte AGOV est maintenant pr&ecirc;t &agrave; &ecirc;tre utilis&eacute;. Veuillez fermer cette page.
agov-ident.done.title=Termin&eacute;
agov-ident.failed.instruction=Vous devez disposer d'un compte AGOV et passer avec succ&egrave;s la v&eacute;rification des donn&eacute;es sugg&eacute;r&eacute;e pour terminer l'inscription. Veuillez r&eacute;essayer.
agov-ident.failed.instruction=Vous avez besoin d'un compte AGOV et de passer la v&eacute;rification des donn&eacute;es sugg&eacute;r&eacute;e pour terminer avec succ&egrave;s l'enregistrement. Veuillez r&eacute;essayer.
agov-ident.failed.message=Enregistrement annul&eacute; ou v&eacute;rification des donn&eacute;es report&eacute;e
agov-ident.failed.title=V&eacute;rification requise
agov-ident.invalid-url.instruction=Le lien que vous avez utilis&eacute; pour acc&eacute;der &agrave; cette page n'est pas valide. Veuillez vous assurer de l'utiliser tel qu'il a &eacute;t&eacute; re&ccedil;u, sans fautes de frappe, ou cliquez directement sur la page o&ugrave; il est publi&eacute;.
agov-ident.invalid-url.instruction=Le lien que vous avez utilis&eacute; pour acc&eacute;der &agrave; cette page n'est pas valide. Veillez l'utiliser tel qu'il a &eacute;t&eacute; re&ccedil;u, sans fautes de frappe, ou cliquez directement sur la page o&ugrave; il est publi&eacute;.
agov-ident.invalid-url.message=Le lien ne peut pas &ecirc;tre trait&eacute;
agov-ident.invalid-url.title=Lien non valide
agov-ident.onboarding=Enregistrement et v&eacute;rification
agov-ident.retry=Essayez &agrave; nouveau
button.submit=Envoyer
cancel.button.label=Abandonner
continue.button.label=Continuer
darkModeSwitch.aria.label=Activer l'apparence sombre
deputy.profile.label=(Profil du suppl&eacute;ant)
error.account.exists=Le compte existe d&#233;j&#224;. Continuez &#224; vous connecter.
error.policy.failed=Votre nouveau mot de passe ne conforme pas aux mesures de s&eacute;curit&eacute;
error.saml.failed=Fermez votre navigateur et r;eacute;essayez.
error_1=Veuillez v&eacute;rifier votre saisie.
@ -65,7 +65,7 @@ general.edit=Editer
general.email=E-mail
general.email.address=Adresse e-mail
general.entryCode=Entrer le code
general.fieldRequired=Champ requis
general.fieldRequired=Champ requis.
general.getStarted=D&eacute;marrer
general.goAGOVHelp=Rendez-vous sur AGOV help
general.goAccessApp=Login avec AGOV access
@ -83,15 +83,15 @@ general.recovery=R&eacute;cup&eacute;ration
general.recovery.help.link=https://help.agov.ch/?c=100recovery
general.recoveryCode.downloadPdf=T&eacute;l&eacute;charger en format PDF
general.recoveryCode.inputLabel=Code de r&eacute;cup&eacute;ration
general.recoveryCode.repeatCodeError=Le code que vous avez saisi est incorrect. Veuillez vous assurer que l'avez enregistr&eacute; correctement et r&eacute;essayer.
general.recoveryCode.repeatCodeModal.description=Pour vous assurer que vous avez correctement enregistr&eacute; votre code, veillez le r&eacute;p&eacute;ter ci-dessous. Un code de r&eacute;cup&eacute;ration perdu ou mal enregistr&eacute; peut rendre la r&eacute;cup&eacute;ration de votre compte plus difficile.
general.recoveryCode.repeatCodeError=Le code que vous avez saisi est incorrect. Veuillez vous assurer que vous l'avez enregistr&eacute; correctement, puis essayer de le soumettre &agrave; nouveau.
general.recoveryCode.repeatCodeModal.description=Un code de r&eacute;cup&eacute;ration perdu ou mal enregistr&eacute; peut rendre la r&eacute;cup&eacute;ration de votre compte plus difficile. Pour vous assurer que vous avez correctement enregistr&eacute; votre code, veuillez le r&eacute;p&eacute;ter ci-dessous.
general.recoveryCode.repeatCodeModal.title=R&eacute;p&eacute;ter le code de r&eacute;cup&eacute;ration
general.recoveryCode.reveal=R&eacute;v&eacute;ler le code de r&eacute;cup&eacute;ration
general.recoveryOngoing=R&eacute;cup&eacute;ration en cours
general.register=Cr&eacute;er un compte
general.registerNow=Enregistrez-vous d&egrave;s maintenant!
general.registration=Enregistrement
general.registration.dontHaveAnAccountYet=Vous n'avez pas encore de compte AGOV ?
general.registration.dontHaveAnAccountYet=Vous n'avez pas de compte AGOV ?
general.registration.seeOptions=Voir les options d'enregistrement
general.securityKey=Cl&eacute; de s&eacute;curit&eacute;
general.skip.content=Passer au contenu principal
@ -111,8 +111,8 @@ language.fr=Fran&ccedil;ais
language.it=Italiano
language.rm=Rumantsch
languageDropdown.aria.label=S&eacute;lectionner la langue
loainfo.description.200=Pour acc&eacute;der &agrave; l'application, nous devons v&eacute;rifier vos donn&eacute;es. Ce processus peut prendre jusqu'&agrave; 2&ndash;3 jours.
loainfo.description.300=Pour acc&eacute;der &agrave; l'application, nous devons v&eacute;rifier vos donn&eacute;es. Vous pouvez choisir la proc&eacute;dure que vous pr&eacute;f&eacute;rez &agrave; l'&eacute;tape suivante.
loainfo.description.200=Pour acc&eacute;der &agrave; l'application, nous devons v&eacute;rifier vos donn&eacute;es. Ce processus peut prendre jusqu'&agrave; 2 ou 3 jours.
loainfo.description.300=Pour acc&eacute;der &agrave; l'application, nous devons v&eacute;rifier vos donn&eacute;es par le biais de l'une des deux proc&eacute;dures suivantes. Vous pouvez choisir la proc&eacute;dure que vous pr&eacute;f&eacute;rez &agrave; l'&eacute;tape suivante.
loainfo.description.400=Veuillez saisir votre num&eacute;ro AVS pour acc&eacute;der &agrave; l'application.
loainfo.helper=Vos donn&eacute;es doivent &ecirc;tre v&eacute;rifi&eacute;es!
loainfo.later=Plus tard
@ -124,14 +124,14 @@ logout.label=Logout
logout.text=Au revoir
mauth_usernameless.EID=Continuer avec l'e-ID suisse
mauth_usernameless.banner.error=Authentification interrompue.<br>Veuillez r&eacute;essayer lorsque la page sera recharg&eacute;e.
mauth_usernameless.banner.info=Scan r&eacute;ussi. Veuillez continuer dans l'application AGOV access.
mauth_usernameless.banner.success=Authentification r&eacute;ussie.<br>Veuillez attendre d'&ecirc;tre connect&eacute;.
mauth_usernameless.banner.info=Scan r&eacute;ussi!<br> Veuillez continuer dans l'application AGOV access.
mauth_usernameless.banner.success=Authentification r&eacute;ussie!<br>Veuillez attendre d'&ecirc;tre connect&eacute;.
mauth_usernameless.cannotLogin=Avez-vous perdu l'acc&egrave;s &agrave; votre application / votre cl&eacute; de s&eacute;curit&eacute; ?
mauth_usernameless.cannotLogin.accessApp=Vous avez perdu l'acc&egrave;s &agrave; votre application AGOV access ?
mauth_usernameless.cannotLogin.securityKey=Avez-vous perdu l'acc&egrave;s &agrave; votre cl&eacute; de s&eacute;curit&eacute; ?
mauth_usernameless.hideQR=Cacher le code QR
mauth_usernameless.instructions=Connectez-vous en scannant le code QR avec l'application AGOV access
mauth_usernameless.noAccount=Vous n'avez pas encore de compte AGOV ?
mauth_usernameless.noAccount=Vous n'avez pas de compte AGOV ?
mauth_usernameless.selectLoginMethod=S&eacute;l&eacute;ctionner la m&eacute;thode de connexion
mauth_usernameless.showQR=Afficher le code QR
mauth_usernameless.startRecovery=Commencer la r&eacute;cup&eacute;ration du compte
@ -214,7 +214,7 @@ prompt.newpassword=Nouveau mot de passe
prompt.newpassword.confirm=Confirmez le mot de passe
prompt.password=Mot de passe
prompt.userid=ID de l&#39;utilisateur
providePhoneNumber.banner=Ce num&eacute;ro de t&eacute;l&eacute;phone doit pouvoir recevoir des SMS. Il ne sera pas utilis&eacute; pour vous contacter.
providePhoneNumber.banner=Ce num&eacute;ro de t&eacute;l&eacute;phone doit pouvoir recevoir des SMS.<br>Ce num&eacute;ro de t&eacute;l&eacute;phone ne sera pas utilis&eacute; pour vous contacter.
providePhoneNumber.description=AGOV prend d&eacute;sormais en charge la r&eacute;cup&eacute;ration avec votre num&eacute;ro de t&eacute;l&eacute;phone. Cela vous permettra de vous envoyer un SMS pendant la r&eacute;cup&eacute;ration si vous avez perdu l'acc&egrave;s &agrave; votre code de r&eacute;cup&eacute;ration.
providePhoneNumber.errorBanner=Les num&eacute;ros de t&eacute;l&eacute;phone fournies ne correspondent pas. Veuillez r&eacute;essayer.
providePhoneNumber.inputLabel=Num&eacute;ro de t&eacute;l&eacute;phone (facultatif)
@ -222,7 +222,7 @@ providePhoneNumber.laterModal.description1=Sans num&eacute;ro de t&eacute;l&eacu
providePhoneNumber.laterModal.description2=Ajouter un num&eacute;ro de t&eacute;l&eacute;phone vous permet de r&eacute;cup&eacute;rer votre compte en quelques minutes.
providePhoneNumber.laterModal.description3=Ce num&eacute;ro de t&eacute;l&eacute;phone ne sera pas utilis&eacute; pour vous contacter.
providePhoneNumber.laterModal.title=Continuer sans num&eacute;ro de t&eacute;l&eacute;phone ?
providePhoneNumber.modal.description=Pour vous assurer que vous avez correctement enregistr&eacute; votre num&eacute;ro de t&eacute;l&eacute;phone, veillez le r&eacute;p&eacute;ter ci-dessous. Un num&eacute;ro de t&eacute;l&eacute;phone mal enregistr&eacute; peut rendre la r&eacute;cup&eacute;ration de votre compte plus difficile.
providePhoneNumber.modal.description=Un num&eacute;ro de t&eacute;l&eacute;phone mal enregistr&eacute; peut rendre plus difficile la r&eacute;cup&eacute;ration de votre compte. Pour vous assurer que vous avez correctement enregistr&eacute; votre num&eacute;ro de t&eacute;l&eacute;phone, veuillez le r&eacute;p&eacute;ter ci-dessous.
providePhoneNumber.modal.inputLabel=Num&eacute;ro de t&eacute;l&eacute;phone
providePhoneNumber.modal.title=R&eacute;p&eacute;ter votre num&eacute;ro de t&eacute;l&eacute;phone
providePhoneNumber.saveButtonText=Sauvegarder
@ -231,15 +231,13 @@ pwreset.done.info=Votre mot de passe a &eacute;t&eacute; chang&eacute avec succ&
pwreset.email.sent=Si votre identifiant n'existe pas, vous avez reçu un courriel pour réinitialiser votre mot de passe.
pwreset.info.linktext=Mot de passe oublié
pwreset.noticket=Votre lien n&apos;est plus valide. Veuillez en g&eacute;n&eacute;rer un nouveau.
qrCode.label=Cliquez pour ouvrir le code QR dans une fen&ecirc;tre.
recovery_accessapp_auth.accessAppRegistered=L'application AGOV access est d&eacute;j&agrave; enregistr&eacute;e
recovery_accessapp_auth.instruction1=Vous avez d&eacute;j&agrave; enregistr&eacute; une nouvelle application AGOV access !!!ACCESS_APP_NAME!!! dans le cadre du processus de r&eacute;cup&eacute;ration.
recovery_accessapp_auth.instruction2=Veuillez utiliser !!!ACCESS_APP_NAME!!! pour vous identifier.
recovery_check_code.banner.lockedError=Trop de saisies erron&eacute;es. Veuillez r&eacute;essayer dans quelques minutes.
recovery_check_code.codeIncorrect=Le code saisi est incorrect. Veuillez r&eacute;essayer.
recovery_check_code.enterRecoveryCode=Code de r&eacute;cup&eacute;ration
recovery_check_code.expired=Trop de tentatives ou votre code de r&eacute;cup&eacute;ration a expir&eacute;.
recovery_check_code.instruction=Veuillez saisir votre code de r&eacute;cup&eacute;ration &agrave; 12 chiffres. Lors de votre inscription, vous avez re&ccedil;u le code de r&eacute;cup&eacute;ration sous la forme d&rsquo;un fichier PDF ou dans &laquo; AGOV me &raquo;.
recovery_check_code.enterRecoveryCode=Saisir le code de r&eacute;cup&eacute;ration
recovery_check_code.instruction=Veuillez saisir votre code de r&eacute;cup&eacute;ration &agrave; douze chiffres. Lors de votre inscription, vous avez re&ccedil;u le code de r&eacute;cup&eacute;ration sous la forme d&rsquo;un fichier PDF ou dans &laquo; AGOV me &raquo;.
recovery_check_code.invalid.code=Le code est invalide
recovery_check_code.invalid.code.required=Code requis
recovery_check_code.invalid.code.tooLong=Le code est trop long
@ -251,7 +249,7 @@ recovery_check_code.too_many_tries.instruction2=Veuillez vous rendre sur AGOV he
recovery_check_noCode.banner.error=Trop de tentatives.
recovery_check_noCode.instruction1=Vous avez peut-&ecirc;tre essay&eacute; de saisir le code de r&eacute;cup&eacute;ration trop de fois.
recovery_check_noCode.instruction2=Veuillez fermer le navigateur web et recommencer la r&eacute;cup&eacute;ration du compte dans dix minutes &agrave; partir de <a class='link' href='https://agov.ch/me'>https://agov.ch/me</a>.
recovery_code.banner.error=Veuillez r&eacute;v&eacute;ler votre code de r&eacute;cup&eacute;ration pour pouvoir continuer.
recovery_code.banner.error=Veuillez indiquer votre nouveau code pour pouvoir continuer.
recovery_code.instruction=Les codes de r&eacute;cup&eacute;ration vous permettent d'acc&eacute;der &agrave; votre compte au cas o&ugrave; vous auriez perdu tous vos identifiants. Conservez le code de r&eacute;cup&eacute;ration en lieu s&ucirc;r.
recovery_code.newRecoveryCode=Introduction du code de r&eacute;cup&eacute;ration
recovery_code.validUntil=Valable jusqu'au:
@ -297,8 +295,6 @@ recovery_start_info.banner.warning=Vous ne pourrez pas utiliser votre compte tan
recovery_start_info.instruction=Le processus de r&eacute;cup&eacute;ration n&eacute;cessitera l&rsquo;enregistrement d&rsquo;un nouveau facteur d&rsquo;authentification. Si votre compte contient des informations ayant d&eacute;j&agrave; &eacute;t&eacute; v&eacute;rifi&eacute;es, il se peut que vous deviez les faire v&eacute;rifier &agrave; nouveau pour terminer la r&eacute;cup&eacute;ration.
recovery_start_info.title=Vous &ecirc;tes sur le point de d&eacute;marrer le processus de r&eacute;cup&eacute;ration.
reject.button.label=Refuser
signup.button.label=Inscription
skip.button.label=Passer
submit.button.label=Envoyer
tan.sent=Veuillez saisir le code de s&eacute;curit&eacute; que vous avez re&ccedil;u au votre t&eacute;l&eacute;phone mobile.
title.login=Login
@ -309,7 +305,6 @@ title.oauth.consent=Autorisation du client
title.pwchange.label=Changer mot de passe
title.pwreset=Mot de Passe Oubli&eacute;
title.saml.failed=Error
title.signup=Cr&#233;er un compte
title.timeout.page=Logout
user_input.invalid.email=Veuillez saisir un e-mail valable.
user_input.invalid.email.required=Champ requis

View File

@ -1,5 +1,5 @@
accept.button.label=Accetta
accept.button.label=Accettare
agov-ident.done.message=Il vostro conto AGOV &egrave; ora pronto per l'uso. Pu&ograve; chiudere questa pagina.
agov-ident.done.title=Finito
agov-ident.failed.instruction=Per completare la registrazione &egrave; necessario disporre di un account AGOV e superare la verifica dei dati suggerita. Riprova.
@ -10,11 +10,11 @@ agov-ident.invalid-url.message=Il link non pu&ograve; essere elaborato
agov-ident.invalid-url.title=Link non valido
agov-ident.onboarding=Registrazione e verifica
agov-ident.retry=Riprova
cancel.button.label=Annulla
button.submit=Continua
cancel.button.label=Abortire
continue.button.label=Continua
darkModeSwitch.aria.label=Attivare la modalit&agrave; scura
deputy.profile.label=(profilo del delegato)
error.account.exists=L'account esiste gi<67>. Prosegui col login.
error.policy.failed=La nuova password non &egrave; stata accettata. Scegliere una password che sia conforme ai criteri di password.
error.saml.failed=Chiudi il browser e riprova.
error_1=Verificare i dati inseriti.
@ -65,7 +65,7 @@ general.edit=Modificare
general.email=e-mail
general.email.address=Indirizzo e-mail
general.entryCode=Codice
general.fieldRequired=Campo obbligatorio
general.fieldRequired=Campo obbligatorio.
general.getStarted=Iniziare
general.goAGOVHelp=Vai ad AGOV help
general.goAccessApp=Login con AGOV access
@ -75,7 +75,7 @@ general.help.link=https://agov.ch/help
general.login=Accedere
general.login.accessApp=Accesso con l'App AGOV access
general.login.securityKey=Login con la chiave di sicurezza
general.loginSecurityKey=Inizi l'accesso con chiave di sicurezza
general.loginSecurityKey=Iniziare il login con la chiave di sicurezza
general.moreOptions=ALTRE OPZIONI
general.or=O
general.otherLoginMethods=Altri metodi di login
@ -83,8 +83,8 @@ general.recovery=Ripristino
general.recovery.help.link=https://help.agov.ch/?c=100recovery
general.recoveryCode.downloadPdf=Salva come PDF
general.recoveryCode.inputLabel=Codice di ripristino
general.recoveryCode.repeatCodeError=Il codice inserito non &egrave; corretto. Verifichi di averlo salvato correttamente e riprovi.
general.recoveryCode.repeatCodeModal.description=Per assicurarsi di aver registrato correttamente il suo codice, lo ripeta qui sotto. Un codice di ripristino perso o registrato in modo errato pu&ograve; rendere pi&ugrave; difficile il ripristino del suo account.
general.recoveryCode.repeatCodeError=Il codice inserito non &egrave; corretto. Assicurati di averlo memorizzato correttamente, quindi riprova a inviarlo.
general.recoveryCode.repeatCodeModal.description=Un codice di ripristino perso o memorizzato in modo errato pu&ograve; rendere pi&ugrave; difficile il recupero del tuo account. Per assicurarti di aver registrato correttamente il codice, inseriscilo di nuovo qui sotto.
general.recoveryCode.repeatCodeModal.title=Ripeti il codice di ripristino
general.recoveryCode.reveal=Mostri il codice di ripristino
general.recoveryOngoing=Ripristino in corso
@ -111,21 +111,21 @@ language.fr=Fran&ccedil;ais
language.it=Italiano
language.rm=Rumantsch
languageDropdown.aria.label=Selezionare la lingua
loainfo.description.200=Per accedere all'applicazione, dobbiamo verificare i suoi dati. Il processo pu&ograve; richiedere da 2&ndash;3 giorni.
loainfo.description.300=Per accedere all'applicazione, dobbiamo verificare i suoi dati. Potr&agrave; scegliere il processo preferito nel passaggio successivo.
loainfo.description.200=Per accedere all'app &egrave; necessaria una verifica dei dati. La procedura pu&ograve; richiedere fino a 2&ndash;3 giorni lavorativi.
loainfo.description.300=Per accedere all'app dobbiamo verificare i suoi dati tramite uno dei due processi. Al prossimo passaggio, pu&ograve; selezionare la procedura di verifica desiderata.
loainfo.description.400=Per accedere all'applicazione &egrave; necessario inserire il numero AVS.
loainfo.helper=I dati devono essere verificati!
loainfo.later=Pi&ugrave; tardi
loainfo.startNow=Vuole iniziare il processo ora?
loainfo.startVerification=Inizi la verificazione
loainfo.startNow=Iniziare la procedura?
loainfo.startVerification=Iniziare la verifica
loainfo.title=Verificare i dati.
login.button.label=Login
logout.label=Logout
logout.text=&Egrave; uscito con successo.
mauth_usernameless.EID=Continuare con CH e-ID
mauth_usernameless.banner.error=Autenticazione interrotta.<br>Riprovare dopo che la pagina si sar&agrave; ricaricata.
mauth_usernameless.banner.info=Scansione eseguita. Continuare nell'app AGOV access.
mauth_usernameless.banner.success=Autenticazione riuscita.<br>Attenda l&rsquo;accesso.
mauth_usernameless.banner.info=La scansione &egrave; stata eseguita.<br>Continuare nell'app AGOV access.
mauth_usernameless.banner.success=Autenticazione riuscita!<br>Aspettare di essere connessi.
mauth_usernameless.cannotLogin=Ha perso l'accesso alla sua app/chiave di sicurezza?
mauth_usernameless.cannotLogin.accessApp=Ha perso l'accesso al suo App AGOV access?
mauth_usernameless.cannotLogin.securityKey=Ha perso l'accesso alla sua chiave di sicurezza?
@ -134,7 +134,7 @@ mauth_usernameless.instructions=Per accedere, scansionare il codice QR con l'app
mauth_usernameless.noAccount=Non ha ancora un AGOV account?
mauth_usernameless.selectLoginMethod=Selezionare il metodo di login
mauth_usernameless.showQR=Visualizza il codice QR
mauth_usernameless.startRecovery=Inizi il ripristino dell&rsquo;account
mauth_usernameless.startRecovery=Inizia il recupero dell'account
mauth_usernameless.useSecurityKey=Accedere utilizzando una chiave di sicurezza.
mauth_usernameless.useSecurityKeyInfo=Una chiave di sicurezza fisica permette di accedere in modo sicuro senza utilizzare un telefono.
method.certificate.label=Certificato
@ -214,7 +214,7 @@ prompt.newpassword=Nuova Password
prompt.newpassword.confirm=Conferma password
prompt.password=Password
prompt.userid=Nome utente
providePhoneNumber.banner=Il numero di telefono deve poter ricevere SMS. Non sar&agrave; utilizzato per contattarla.
providePhoneNumber.banner=Il numero di telefono deve essere in grado di ricevere SMS.<br>Questo numero di telefono non sar&agrave; utilizzato per contattarti.
providePhoneNumber.description=AGOV ora supporta il ripristino tramite il tuo numero di telefono. Questo ti permetter&agrave; di continuare con un SMS durante il ripristino se hai perso l'accesso al tuo codice di ripristino.
providePhoneNumber.errorBanner=Il numero di telefono non corrispondono. Si prega di riprovare.
providePhoneNumber.inputLabel=Numero di telefono (facoltativo)
@ -222,7 +222,7 @@ providePhoneNumber.laterModal.description1=Senza un numero di telefono, il recup
providePhoneNumber.laterModal.description2=Aggiungere un numero di telefono ti aiuta a recuperare il tuo account in pochi minuti.
providePhoneNumber.laterModal.description3=Questo numero di telefono non sar&agrave; utilizzato per contattarti.
providePhoneNumber.laterModal.title=Continuare senza un numero di telefono?
providePhoneNumber.modal.description=Per assicurarsi di aver registrato correttamente il suo numero di telefono, lo ripeta qui sotto. Un numero registrato in modo errato pu&ograve; rendere pi&ugrave; difficile il ripristino del suo account.
providePhoneNumber.modal.description=Un numero di telefono memorizzato in modo errato pu&ograve; rendere pi&ugrave; difficile il recupero del tuo account. Per assicurarti di aver registrato correttamente il tuo numero di telefono, inseriscilo di nuovo qui sotto.
providePhoneNumber.modal.inputLabel=Numero di telefono
providePhoneNumber.modal.title=Ripetere il numero di telefono
providePhoneNumber.saveButtonText=Salva
@ -231,14 +231,12 @@ pwreset.done.info=La password &egrave; stata modificata con successo. Fare clic
pwreset.email.sent=Se il vostro ID utente esiste, vi è stata inviata un'e-mail per reimpostare la password.
pwreset.info.linktext=Password dimenticata
pwreset.noticket=Il biglietto per la reimpostazione della password non &egrave; pi&ugrave; valido. Si prega di generarne uno nuovo.
qrCode.label=Clicchi per aprire il codice QR in una finestra pop-up.
recovery_accessapp_auth.accessAppRegistered=App di accesso AGOV gi&agrave; registrata
recovery_accessapp_auth.instruction1=Ha gi&agrave; registrato una nuova app AGOV access !!!SECURITY_KEY_NAME!!! come parte del processo di recupero.
recovery_accessapp_auth.instruction2=Si prega di usare !!!ACCESS_APP_NAME!!! per l'identificazione.
recovery_check_code.banner.lockedError=Troppi tentativi di inserimento non validi. Riprovare tra qualche minuto.
recovery_check_code.codeIncorrect=Il codice inserito non &egrave; corretto. Riprovare.
recovery_check_code.enterRecoveryCode=Codice di ripristino
recovery_check_code.expired=Troppi tentativi o il codice di ripristino &egrave; scaduto.
recovery_check_code.enterRecoveryCode=Inserisca il codice di recupero
recovery_check_code.instruction=Inserire qui sotto il codice di ripristino a 12 caratteri alfanumerici. Ha ricevuto questo codice in un file PDF al momento della registration o in AGOV me.
recovery_check_code.invalid.code=Il codice non &egrave; valido
recovery_check_code.invalid.code.required=Codice richiesto
@ -251,17 +249,17 @@ recovery_check_code.too_many_tries.instruction2=Si prega di andare alla guida di
recovery_check_noCode.banner.error=Troppi tentativi.
recovery_check_noCode.instruction1=Potresti aver tentato di inserire il codice di ripristino troppe volte.
recovery_check_noCode.instruction2=Chiudi il browser web e inizia nuovamente il processo di ripristino dell'account tra dieci minuti da <a class='link' href='https://agov.ch/me'>https://agov.ch/me</a>.
recovery_code.banner.error=Mostri il suo codice di ripristino per poter continuare.
recovery_code.banner.error=La preghiamo di rivelare il suo nuovo codice per poter continuare.
recovery_code.instruction=Il codice di ripristino le aiuta ad accedere al suo conto in caso in cui lei abbia perso le credentiali di accesso. Per favore, conservi il codice di ripristino in un luogo sicuro.
recovery_code.newRecoveryCode=Introduzione del codice di ripristino
recovery_code.validUntil=Valido fino a:
recovery_fidokey_auth.button=Inizi l'authenticazione della chiave
recovery_fidokey_auth.fidoInstruction=Clicchi su "Inizi l'authenticazione della chiave"
recovery_fidokey_auth.button=Iniziare l'authenticazione della chiave
recovery_fidokey_auth.fidoInstruction=Cliccare su "Iniziare l'authenticazione della chiave"
recovery_fidokey_auth.instruction1=Ha gi&agrave; registrato una nuova chiave di sicurezza !!!SECURITY_KEY_NAME!!! come parte del processo di recupero.
recovery_fidokey_auth.instruction2=Si prega di usare !!!SECURITY_KEY_NAME!!! per poter seguire i passaggi seguenti per identificarti.
recovery_fidokey_auth.keyRegistered=Chiave di sicurezza gi&agrave; registrata
recovery_intro_email.banner.error=Il link utilizzato &egrave; scaduto. Per ricevere un nuovo link, inserire l&rsquo;indirizzo e-mail.
recovery_intro_email.banner.info=Inserisca il suo indirizzo email, cos&igrave; potremo inviarle un link per iniziare il processo di ripristino.
recovery_intro_email.banner.info=Per ricevere il link e avviare il processo di ripristino, inserire l&rsquo;indirizzo e-mail.
recovery_intro_email.important=Importante:
recovery_intro_email.process=Il processo di ripristino deve essere utilizzato solo se ha perso l'accesso ai suoi fattori di accesso (app AGOV access eliminata, chiave di sicurezza persa, telefono smarrito, ecc.).
recovery_intro_email_sent.banner.button=Non avete ricevuto l'e-mail?
@ -271,7 +269,7 @@ recovery_on_going.instruction=&Egrave; in corso un processo di ripristino. Il pr
recovery_on_going.title=Completare il processo di ripristino.
recovery_questionnaire_instructions.banner.info=Tenga presente che in alcuni casi &egrave; necessario utilizzare il codice di ripristino per un ripristino riuscito.
recovery_questionnaire_instructions.explanation=In base alle sue risposte sembra essere necessario un ripristino AGOV-Login. Fare clic su Continua e seguire le istruzioni visualizzate sullo schermo.
recovery_questionnaire_instructions.instruction1=Indichi l&rsquo;indirizzo e-mail associato al suo account, cos&igrave; potremo inviarle un link per iniziare il processo di ripristino
recovery_questionnaire_instructions.instruction1=Si prega di fornire l'indirizzo email del suo account in modo di poter inviarle un link per iniziare il processo di recupero
recovery_questionnaire_instructions.instruction2=Si prega di seguire i passaggi per recuperare il suo account (i passaggi varieranno a seconda del livello di verifica dell'account)
recovery_questionnaire_loginfactor.banner.error=Si prega di selezionare una risposta.
recovery_questionnaire_loginfactor.no=No
@ -292,13 +290,11 @@ recovery_questionnaire_reason_selection.answer7=Ho i miei token di sicurezza o l
recovery_questionnaire_reason_selection.answer8=Ho perso l'accesso a tutte le mie chiavi di sicurezza e alle app AGOV access
recovery_questionnaire_reason_selection.answer9=Ho problemi con uno dei miei fattori di accesso (PIN cancellato, reimpostato, dimenticato)
recovery_questionnaire_reason_selection.banner.error=Si prega di selezionare il motivo.
recovery_questionnaire_reason_selection.instruction=Selezioni il motivo per cui sta iniziando il processo di ripristino:
recovery_questionnaire_reason_selection.instruction=Si prega di selezionare il motivo per cui sta avviando il processo di recupero:
recovery_start_info.banner.warning=Non &egrave; possibile utilizzare l&rsquo;account finch&eacute; il processo di ripristino non sar&agrave; concluso.
recovery_start_info.instruction=Durante il processo di ripristino registrer&agrave; un nuovo fattore di login. Se il suo account contiene informazioni verificate, potrebbe dover effettuare anche un processo di verificazione per completare il ripristino.
recovery_start_info.title=Sta per iniziare il processo di ripristino
reject.button.label=Rifiuta
signup.button.label=Iscriviti
skip.button.label=Salta
recovery_start_info.instruction=Durante il processo di ripristino sar&agrave; registrato un nuovo fattore di accesso. Se l&rsquo;account contiene informazioni verificate, potrebbe essere necessario avviare un processo di verifica per completare il ripristino.
recovery_start_info.title=Il processo di ripristino sta per iniziare.
reject.button.label=Rifiuti
submit.button.label=Continua
tan.sent=Inserisci il codice di sicurezza che &egrave; stato inviato al tuo telefono cellulare.
title.login=Login
@ -309,7 +305,6 @@ title.oauth.consent=Autorizzazione del client
title.pwchange.label=Cambiare Password
title.pwreset=Password Dimenticata
title.saml.failed=Error
title.signup=Crea un account
title.timeout.page=Logout
user_input.invalid.email=Inserire un'e-mail valida.
user_input.invalid.email.required=Campo obbligatorio

View File

@ -50,4 +50,3 @@ if (inargs.containsKey('onReload')) {
clearFidoUAFSession()
response.setResult('default')
}

View File

@ -40,7 +40,7 @@ if(loa_str){
// BUNDBITBK-5005: Set cookie to remember the last authentication method
def agovAuthMethodCookie = "LOGINMETHOD=${AUTHENTICATON_URN_TO_COOKIE_MAPPER[session.getAttribute('authenticatedWith')]}; Domain=${parameters.get('cookie.domain')}; Path=/; Max-Age=1800; SameSite=Strict; Secure; HttpOnly"
response.setHeader('Set-Cookie3', agovAuthMethodCookie)
response.setHeader('Set-Cookie2', agovAuthMethodCookie)
// delete the login cookie
def agovLoginCookie = "agovLogin=deleted; Domain=${parameters.get('cookie.domain')}; Path=/; Max-Age=0; SameSite=Strict; Secure; HttpOnly"

View File

@ -21,4 +21,4 @@ def agovLoginCookie = "agovLogin=deleted; Domain=${parameters.get('cookie.domain
response.setHeader('Set-Cookie', agovLoginCookie)
response.setStatus(AuthResponse.AUTH_ERROR)
return
return

View File

@ -1,109 +1,109 @@
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
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]
}
// Accounting
def requester = session['ch.nevis.auth.saml.request.scoping.requesterId'] ?: 'unknown'
def requestId = session['ch.nevis.auth.saml.request.id'] ?: 'unknown'
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'
IdmRestClient idmRestClient = IdmRestClientFactory.get(parameters)
String clientExtId = session.get('ch.adnovum.nevisidm.user.clientExtId')
String userExtId = session.get('ch.adnovum.nevisidm.user.extId')
String mobile = session.get('ch.nevis.idm.User.mobile')
String baseUrl = parameters.get('baseUrl')
String endPoint = "${baseUrl}/core/v1/${clientExtId}/users/${userExtId}"
if (!(parameters.get('ask_mobile_number_enabled')?.toLowerCase()?.trim() == "true")) {
LOG.debug("Feature 'ask mobile number' is disabled")
response.setResult('done')
return
}
if (mobile) {
LOG.debug("User '${user}' has already registered a mobile number")
response.setResult('done')
return
}
def agovSkipAskingMobileCookieValue = 'missing'
if (getHeader('cookie') != null) {
def cookies = getHeader('cookie')
if (cookies.matches('^.*agovSkipAskingMobile=([^;]+).*$')) {
agovSkipAskingMobileCookieValue = cookies.replaceAll('^.*agovSkipAskingMobile=([^;]+).*$', '$1')
}
}
if (agovSkipAskingMobileCookieValue == 'true') {
// Don't aske the user again...
LOG.info("Event='SKIPPEDMOBILENUMBER', Requester='${requester}', RequestId='${requestId}', User=${user}, SourceIp=${sourceIp}, UserAgent='${userAgent}'")
response.setResult('done')
return
}
if (!inargs['submit'] && (!inargs['mobile'] || !inargs['mobile'].isEmpty()) && inargs['language'] && inargs['language'] != session['ch.nevis.session.user.language']) {
// language switch, nothing else to do, just display again the GUI
response.setStatus(AuthResponse.AUTH_CONTINUE)
return
}
if (inargs['submit'] && (!inargs['mobile'] || inargs['mobile'].isEmpty()) && inargs['skip']) {
// no mobile, and user wants to skip it
LOG.info("Event='NOMOBILENUMBER', Requester='${requester}', RequestId='${requestId}', User=${user}, SourceIp=${sourceIp}, UserAgent='${userAgent}', Persistent='${ inargs['skip'] == 'persistent' ? true : false }'")
if (inargs['skip'] == 'persistent') {
// persistent cookie for 30d;
def agovSkipAskingMobileCookie = "agovSkipAskingMobile=true; Domain=${parameters.get('cookie.domain')}; Path=/; Max-Age=2592000; SameSite=Strict; Secure; HttpOnly"
// setHeader doesn't support multiple headers with the same name, so we use
// a different one, and rewrite it in the proxy with Lua
response.setHeader('Set-Cookie2', agovSkipAskingMobileCookie)
}
response.setResult('done')
return
}
if (inargs['submit'] && inargs['mobile'] && !inargs['mobile'].isEmpty()) {
// IMPORTANT/haburger/2024-DEC-09: the pattern must be the same as ch.adnovum.agov.common.util.InputPatterns.PHONE_NUMBER_PATTERN
if (!inargs['mobile'].replaceAll('\\s', '').matches('^(?:\\+[0-9]+)?$')) {
LOG.warn("Event='MOBILEFAILED', Requester='${requester}', RequestId='${requestId}', User=${user}, SourceIp=${sourceIp}, UserAgent='${userAgent}', reason='User provided invalid number (${inargs['mobile']})'")
response.setResult('done')
return
}
String result
// mobile is also stored without spaces
def patchBdy = "{\"contacts\":{\"mobile\":\"${inargs['mobile'].replaceAll('\\s', '')}\"},\"modificationComment\":\"added mobile number from user during request ${requestId}\"}"
try {
result = idmRestClient.patch(endPoint, patchBdy)
} catch(Exception e) {
LOG.warn("Event='MOBILEFAILED', Requester='${requester}', RequestId='${requestId}', User=${user}, SourceIp=${sourceIp}, UserAgent='${userAgent}', reason='failed to save number (${e})'")
}
response.setResult('done')
return
}
// we should ask the user
response.setStatus(AuthResponse.AUTH_CONTINUE)
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
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]
}
// Accounting
def requester = session['ch.nevis.auth.saml.request.scoping.requesterId'] ?: 'unknown'
def requestId = session['ch.nevis.auth.saml.request.id'] ?: 'unknown'
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'
IdmRestClient idmRestClient = IdmRestClientFactory.get(parameters)
String clientExtId = session.get('ch.adnovum.nevisidm.user.clientExtId')
String userExtId = session.get('ch.adnovum.nevisidm.user.extId')
String mobile = session.get('ch.nevis.idm.User.mobile')
String baseUrl = parameters.get('baseUrl')
String endPoint = "${baseUrl}/core/v1/${clientExtId}/users/${userExtId}"
if (!(parameters.get('ask_mobile_number_enabled')?.toLowerCase()?.trim() == "true")) {
LOG.debug("Feature 'ask mobile number' is disabled")
response.setResult('done')
return
}
if (mobile) {
LOG.debug("User '${user}' has already registered a mobile number")
response.setResult('done')
return
}
def agovSkipAskingMobileCookieValue = 'missing'
if (getHeader('cookie') != null) {
def cookies = getHeader('cookie')
if (cookies.matches('^.*agovSkipAskingMobile=([^;]+).*$')) {
agovSkipAskingMobileCookieValue = cookies.replaceAll('^.*agovSkipAskingMobile=([^;]+).*$', '$1')
}
}
if (agovSkipAskingMobileCookieValue == 'true') {
// Don't aske the user again...
LOG.info("Event='SKIPPEDMOBILENUMBER', Requester='${requester}', RequestId='${requestId}', User=${user}, SourceIp=${sourceIp}, UserAgent='${userAgent}'")
response.setResult('done')
return
}
if (!inargs['submit'] && (!inargs['mobile'] || !inargs['mobile'].isEmpty()) && inargs['language'] && inargs['language'] != session['ch.nevis.session.user.language']) {
// language switch, nothing else to do, just display again the GUI
response.setStatus(AuthResponse.AUTH_CONTINUE)
return
}
if (inargs['submit'] && (!inargs['mobile'] || inargs['mobile'].isEmpty()) && inargs['skip']) {
// no mobile, and user wants to skip it
LOG.info("Event='NOMOBILENUMBER', Requester='${requester}', RequestId='${requestId}', User=${user}, SourceIp=${sourceIp}, UserAgent='${userAgent}', Persistent='${ inargs['skip'] == 'persistent' ? true : false }'")
if (inargs['skip'] == 'persistent') {
// persistent cookie for 30d;
def agovSkipAskingMobileCookie = "agovSkipAskingMobile=true; Domain=${parameters.get('cookie.domain')}; Path=/; Max-Age=2592000; SameSite=Strict; Secure; HttpOnly"
// setHeader doesn't support multiple headers with the same name, so we use
// a different one, and rewrite it in the proxy with Lua
response.setHeader('Set-Cookie2', agovSkipAskingMobileCookie)
}
response.setResult('done')
return
}
if (inargs['submit'] && inargs['mobile'] && !inargs['mobile'].isEmpty()) {
// IMPORTANT/haburger/2024-DEC-09: the pattern must be the same as ch.adnovum.agov.common.util.InputPatterns.PHONE_NUMBER_PATTERN
if (!inargs['mobile'].replaceAll('\\s', '').matches('^(?:\\+[0-9]+)?$')) {
LOG.warn("Event='MOBILEFAILED', Requester='${requester}', RequestId='${requestId}', User=${user}, SourceIp=${sourceIp}, UserAgent='${userAgent}', reason='User provided invalid number (${inargs['mobile']})'")
response.setResult('done')
return
}
String result
// mobile is also stored without spaces
def patchBdy = "{\"contacts\":{\"mobile\":\"${inargs['mobile'].replaceAll('\\s', '')}\"},\"modificationComment\":\"added mobile number from user during request ${requestId}\"}"
try {
result = idmRestClient.patch(endPoint, patchBdy)
} catch(Exception e) {
LOG.warn("Event='MOBILEFAILED', Requester='${requester}', RequestId='${requestId}', User=${user}, SourceIp=${sourceIp}, UserAgent='${userAgent}', reason='failed to save number (${e})'")
}
response.setResult('done')
return
}
// we should ask the user
response.setStatus(AuthResponse.AUTH_CONTINUE)

View File

@ -1,180 +1,180 @@
boolean isEnabled() {
def paths = parameters.get("paths")
if (paths && !paths.isEmpty()) {
for (path in paths.split(',')) {
String url = request.currentResource
if (url.matches(path)) {
return true
}
}
}
return false
}
boolean isLevel(String role) {
if (role != null && role.isNumber()) {
def number = Integer.parseInt(role)
if (number > 0 && number <= 9) {
return true
}
}
return false
}
int getCurrentLevel() {
int level = 1 // level 1 is reached by definition on successful authentication
// levels are stored as roles once the authentication is done
for (String role : response.getActualRoles()) {
if (isLevel(role)) {
Integer number = Integer.parseInt(role)
if (number > level) {
level = number
}
}
}
LOG.debug("current level: $level")
return level
}
Integer getRequestedLevel() {
// try to determine required level based on SAML request (SP-initiated)
def context = session['ch.nevis.auth.saml.request.authnContextClassRef']
if (context == null) {
// this is expected for non-Nevis SAML partners
LOG.debug("unable to determine required authentication level: no AuthnContext")
return null
}
String prefix = 'urn:nevis:level:'
Integer level = null
if (context.contains(prefix)) {
def start = context.indexOf(prefix) // the prefix can appear anywhere in the context but only once
def remainder = context.substring(start + prefix.length())
for (String candidate : remainder.split(',')) {
if (!candidate.isNumber()) {
continue // must be an actual role
}
def number = Integer.parseInt(candidate)
if (level == null || number < level) {
level = number
}
}
}
if (level == null) {
// an AuthnContext has been sent but it does not contain the required authentication level
LOG.debug("unable to determine required authentication level from request: $context")
}
else {
LOG.info("extracted required authentication level from request: $context -> $level")
}
return level
}
Integer getRequiredLevel(levels, String issuer) {
// try to determine required level based on request
def level = getRequestedLevel()
if (level != null) {
LOG.info("required authentication level from request: $level")
return level
}
// else determine required level based on configuration (IDP-initiated or no authnContextClassRef sent)
if (issuer != null && levels.containsKey(issuer)) {
level = levels[issuer]
LOG.debug("required authentication level for issuer $issuer defined as $level")
return level
}
// else return null
LOG.debug("required authentication level for issuer $issuer is not defined")
return null
}
void setAuthnContext() {
def parts = [] as Set
def authLevel = response.getAuthLevel()
if (authLevel != null) {
if (isLevel(authLevel)) {
parts.add("urn:nevis:level:$authLevel")
}
else { // might be legacy auth.weak / auth.strong
parts.add(authLevel)
}
}
for (String role : response.getActualRoles()) {
if (isLevel(role)) { // previous authLevels might have been added to the roles already
parts.add("urn:nevis:level:$role")
}
// levels can also be normal roles so we add them always
parts.add(role)
}
def value = parts.sort().join(",")
LOG.debug("calculated AuthnContextClassRef for SAML Response: $value")
session['saml.idp.response.authncontext'] = value
}
boolean stepupRequired(levels, String issuer) {
Integer requiredLevel = getRequiredLevel(levels, issuer)
if (requiredLevel == null) {
LOG.info("unable to determine required authentication level for request from issuer $issuer")
setAuthnContext()
return false
}
Integer currentLevel = getCurrentLevel()
if (currentLevel >= requiredLevel) {
LOG.info("required authentication level $requiredLevel has been reached (current level $currentLevel)")
setAuthnContext()
return false
}
LOG.info("required authentication level $requiredLevel has not been reached (current level $currentLevel) - session upgrade needed")
request.setRequiredRoles("$requiredLevel")
return true
}
boolean hasAnyRequiredRole(i2r, issuer) {
if (issuer != null && i2r.containsKey(issuer)) {
def roles = i2r[issuer]
for (role in response.getActualRoles()) {
if (roles.contains(role)) {
return true
}
}
}
}
if (!isEnabled()) {
LOG.info("skipping SAML authorization checks.")
response.setResult('ok') // skip execution
return
}
// issuer set by IdentityProviderState (SP-initiated)
def issuer = session['ch.nevis.auth.saml.request.issuer']
// issuer to minimum required authentication level
def i2l = [:]
if (stepupRequired(i2l, issuer)) {
LOG.info("authentication level stepup required.")
response.setResult("stepup")
return // we are done for now
}
// issuer to list of required roles
def i2r = [:]
// issuer to ResultCond name
def i2e = [:]
i2e.put('https://trustbroker.agov-epr-lab.azure.adnovum.net', 'forbidden_0')
i2e.put('https://trustbroker-idp.agov-epr-lab.azure.adnovum.net', 'forbidden_1')
if (!i2r.isEmpty() && !hasAnyRequiredRole(i2r, issuer)) {
LOG.info("required roles check failed.")
response.setResult(i2e[issuer])
return // we are done
}
boolean isEnabled() {
def paths = parameters.get("paths")
if (paths && !paths.isEmpty()) {
for (path in paths.split(',')) {
String url = request.currentResource
if (url.matches(path)) {
return true
}
}
}
return false
}
boolean isLevel(String role) {
if (role != null && role.isNumber()) {
def number = Integer.parseInt(role)
if (number > 0 && number <= 9) {
return true
}
}
return false
}
int getCurrentLevel() {
int level = 1 // level 1 is reached by definition on successful authentication
// levels are stored as roles once the authentication is done
for (String role : response.getActualRoles()) {
if (isLevel(role)) {
Integer number = Integer.parseInt(role)
if (number > level) {
level = number
}
}
}
LOG.debug("current level: $level")
return level
}
Integer getRequestedLevel() {
// try to determine required level based on SAML request (SP-initiated)
def context = session['ch.nevis.auth.saml.request.authnContextClassRef']
if (context == null) {
// this is expected for non-Nevis SAML partners
LOG.debug("unable to determine required authentication level: no AuthnContext")
return null
}
String prefix = 'urn:nevis:level:'
Integer level = null
if (context.contains(prefix)) {
def start = context.indexOf(prefix) // the prefix can appear anywhere in the context but only once
def remainder = context.substring(start + prefix.length())
for (String candidate : remainder.split(',')) {
if (!candidate.isNumber()) {
continue // must be an actual role
}
def number = Integer.parseInt(candidate)
if (level == null || number < level) {
level = number
}
}
}
if (level == null) {
// an AuthnContext has been sent but it does not contain the required authentication level
LOG.debug("unable to determine required authentication level from request: $context")
}
else {
LOG.info("extracted required authentication level from request: $context -> $level")
}
return level
}
Integer getRequiredLevel(levels, String issuer) {
// try to determine required level based on request
def level = getRequestedLevel()
if (level != null) {
LOG.info("required authentication level from request: $level")
return level
}
// else determine required level based on configuration (IDP-initiated or no authnContextClassRef sent)
if (issuer != null && levels.containsKey(issuer)) {
level = levels[issuer]
LOG.debug("required authentication level for issuer $issuer defined as $level")
return level
}
// else return null
LOG.debug("required authentication level for issuer $issuer is not defined")
return null
}
void setAuthnContext() {
def parts = [] as Set
def authLevel = response.getAuthLevel()
if (authLevel != null) {
if (isLevel(authLevel)) {
parts.add("urn:nevis:level:$authLevel")
}
else { // might be legacy auth.weak / auth.strong
parts.add(authLevel)
}
}
for (String role : response.getActualRoles()) {
if (isLevel(role)) { // previous authLevels might have been added to the roles already
parts.add("urn:nevis:level:$role")
}
// levels can also be normal roles so we add them always
parts.add(role)
}
def value = parts.sort().join(",")
LOG.debug("calculated AuthnContextClassRef for SAML Response: $value")
session['saml.idp.response.authncontext'] = value
}
boolean stepupRequired(levels, String issuer) {
Integer requiredLevel = getRequiredLevel(levels, issuer)
if (requiredLevel == null) {
LOG.info("unable to determine required authentication level for request from issuer $issuer")
setAuthnContext()
return false
}
Integer currentLevel = getCurrentLevel()
if (currentLevel >= requiredLevel) {
LOG.info("required authentication level $requiredLevel has been reached (current level $currentLevel)")
setAuthnContext()
return false
}
LOG.info("required authentication level $requiredLevel has not been reached (current level $currentLevel) - session upgrade needed")
request.setRequiredRoles("$requiredLevel")
return true
}
boolean hasAnyRequiredRole(i2r, issuer) {
if (issuer != null && i2r.containsKey(issuer)) {
def roles = i2r[issuer]
for (role in response.getActualRoles()) {
if (roles.contains(role)) {
return true
}
}
}
}
if (!isEnabled()) {
LOG.info("skipping SAML authorization checks.")
response.setResult('ok') // skip execution
return
}
// issuer set by IdentityProviderState (SP-initiated)
def issuer = session['ch.nevis.auth.saml.request.issuer']
// issuer to minimum required authentication level
def i2l = [:]
if (stepupRequired(i2l, issuer)) {
LOG.info("authentication level stepup required.")
response.setResult("stepup")
return // we are done for now
}
// issuer to list of required roles
def i2r = [:]
// issuer to ResultCond name
def i2e = [:]
i2e.put('https://trustbroker.agov-epr-lab.azure.adnovum.net', 'forbidden_0')
i2e.put('https://trustbroker-idp.agov-epr-lab.azure.adnovum.net', 'forbidden_1')
if (!i2r.isEmpty() && !hasAnyRequiredRole(i2r, issuer)) {
LOG.info("required roles check failed.")
response.setResult(i2e[issuer])
return // we are done
}
response.setResult('ok')

View File

@ -1,285 +1,285 @@
import org.codehaus.groovy.runtime.StackTraceUtils
import groovy.xml.XmlSlurper
def getUserAGOVLoiRoles() {
// we take the roles from actualRoles
return request.getActualRoles().findAll { role -> role.startsWith('AGOV-Loi.') }.collect({ role -> role.substring(9) })
}
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
case 'Eid':
result = result.concat('400')
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 s = request.getAuthSession(true)
def highestRoleLevelNumber = 0
if (!session.get('agov.requestedRoleLevel')) {
LOG.error("IDP: internal error: agov.requestedRoleLevel not set in session")
response.setResult('error');
return
}
def requestedRoleLevelNumber = session.get('agov.requestedRoleLevel').toInteger()
def authenticationMethod = session.get('authenticatedWith')
if (!authenticationMethod) {
LOG.error("IDP: internal error: authenticationMethod not set in session")
response.setResult('error');
return
}
// data transformations needed for SAML and OIDC
// Transform sex to number
if(session.get('ch.nevis.idm.User.gender') == 'MALE'){
s.setAttribute('ch.nevis.idm.User.gender', '1')
}
if(session.get('ch.nevis.idm.User.gender') == 'FEMALE'){
s.setAttribute('ch.nevis.idm.User.gender', '2')
}
if(s.get('ch.nevis.idm.User.gender') == 'OTHER'){
s.setAttribute('ch.nevis.idm.User.gender', '3')
}
// handle accounts qa attributes, and set them in session
// account itself, only needed if not authenticated with e-ID
if (!'urn:qa.agov.ch:names:tc:authfactor:eid'.equalsIgnoreCase(authenticationMethod)) {
def idVerificationList = getUserAGOVLoiIdVerification()
def idVerification = 'None'
if (idVerificationList && !idVerificationList.isEmpty()) {
idVerification = idVerificationList.last()
}
s.setAttribute('idVerification', idVerification)
// contextClassRefToSet based on highest level-role assigned to default profile
for (String role : getUserAGOVLoiRoles()) {
if (role.startsWith('level')) {
def roleLevel = role.substring(5)
int roleLevelNumber = Integer.parseInt(roleLevel)
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
s.setAttribute('agov.actualRoleLevel', '' + highestRoleLevelNumber)
LOG.debug('CheckLoa: actual role level (agov) '+ highestRoleLevelNumber)
// 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 != '') {
s.setAttribute('ValidFrom', '' + validFrom)
}
if(validTo != '') {
s.setAttribute('ValidTo', '' + validTo)
}
}
if (highestRoleLevelNumber > 0) {
// set attribute contextClassRefToSet
s.setAttribute('contextClassRefToSet','urn:qa.agov.ch:names:tc:ac:classes:' .concat(highestRoleLevelNumber.toString()))
} else {
// by default 100
s.setAttribute('contextClassRefToSet','urn:qa.agov.ch:names:tc:ac:classes:100' )
}
}
// address related, needed in any case (also e-ID)
def adressVerificationList = getUserAGOVLoiIdVerification('200')
def adressVerification = 'None'
if (adressVerificationList && !adressVerificationList.isEmpty()) {
adressVerification = adressVerificationList[0]
}
s.setAttribute('agov.adressVerification', '' + adressVerification)
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}'")
// if the account has no profile, we must not return address or svnr
s.setAttribute('agov.appAddressRequired', 'false')
s.setAttribute('agov.appSvnrAllowed', 'false')
response.setResult('ok')
return
}
// no login for users with a recovery role (but onyl when not logging in with e-Id)
// TODO/haburger/2025-07-01: automatic recovery if logging in with e-Id
if (!'urn:qa.agov.ch:names:tc:authfactor:eid'.equalsIgnoreCase(authenticationMethod)) {
// no login for users with a recovery role
def recoveryRoleList = getUserAGOVRecoveryRoles()
if (recoveryRoleList.contains('mustRecover')) {
s.setAttribute('agov.recovery.authnContextClassRef', 'urn:qa.agov.ch:names:tc:ac:classes:mustRecover')
s.setAttribute('agov.recovery.authenticatedWith', session.get('authenticatedWith') ?: 'unknown' )
def origIdVerification = getUserAGOVLoiIdVerification(highestRoleLevelNumber.toString()) ?: 'None'
def idVerification = getUserIdVerificationForRecovery() ?: origIdVerification
s.setAttribute('agov.recovery.currentIdVerification', '' + idVerification )
// align currentAgovAq with the method selected for idVerification
def currentAgovAqForRecovery = getAqLevelBasedOnIdVerificationForRecovery(idVerification, highestRoleLevelNumber)
s.setAttribute('agov.recovery.currentAgovAq', '' + currentAgovAqForRecovery)
def validFrom = getUserMustRecoverValidFrom() ?: ''
s.setAttribute('agov.recovery.currentAgovAqRoleValidFrom', '' + validFrom )
LOG.debug("CheckLoa: mustRecover: origIdVerification=${origIdVerification}, idVerification=${idVerification}, currentAgovAqForRecovery=${currentAgovAqForRecovery}")
response.setResult('exit.2')
return
} else if (recoveryRoleList.contains('recovery')) {
if (recoveryRoleList.contains('recoveryCascade')) {
s.setAttribute('agov.recovery.authnContextClassRef', 'urn:qa.agov.ch:names:tc:ac:classes:recoveryCascade')
} else {
s.setAttribute('agov.recovery.authnContextClassRef', 'urn:qa.agov.ch:names:tc:ac:classes:recovery')
}
s.setAttribute('agov.recovery.authenticatedWith', session.get('authenticatedWith') ?: 'unknown')
s.setAttribute('agov.recovery.currentAgovAq', session.get('contextClassRefToSet') ?: 'urn:qa.agov.ch:names:tc:ac:classes:100' )
LOG.debug('CheckLoa: idVerification2= '+ getUserAGOVLoiIdVerification(highestRoleLevelNumber.toString()))
def idVerification = getUserAGOVLoiIdVerification(highestRoleLevelNumber.toString())
s.setAttribute('agov.recovery.currentIdVerification', (idVerification.isEmpty() ? 'None' : idVerification.first()))
def validFrom = getUserAGOVLoiValidFrom('level'.concat(highestRoleLevelNumber.toString())) ?: ''
s.setAttribute('agov.recovery.currentAgovAqRoleValidFrom', validFrom)
response.setResult('exit.2')
return
}
} else {
// authenticated with e-ID, we adjust highestRoleLevelNumber to e-ID login
highestRoleLevelNumber = 500
s.setAttribute('agov.actualRoleLevel', '' + highestRoleLevelNumber)
LOG.debug('CheckLoa: actual role level (agov) '+ highestRoleLevelNumber)
}
// verifiy that AQ level is high enough
if (highestRoleLevelNumber>=requestedRoleLevelNumber) {
response.setResult('ok')
return;
} else {
// Insufficient_LoaInfo
response.setResult('exit.1');
return;
}
} catch (Exception ex) {
LOG.error("Event='DATAERROR', Requester='${requester}', RequestId='${requestId}', RequestedAq=${requestedAq}, User=${user}, CredentialType='${credentialType}', errorMessage='exception occured: ${ex}', SourceIp=${sourceIp}, UserAgent='${userAgent}'")
ex = StackTraceUtils.sanitize(ex)
def affectedLines = ex.stackTrace.findAll { it.className.startsWith('Script') }.collect { "${it.methodName}:${it.lineNumber}" }
LOG.error("FATAL: Script failure (at lines: ${affectedLines})", ex)
// AuthnFailed_Zero_RoleLvl
response.setResult('error');
return;
}
import org.codehaus.groovy.runtime.StackTraceUtils
import groovy.xml.XmlSlurper
def getUserAGOVLoiRoles() {
// we take the roles from actualRoles
return request.getActualRoles().findAll { role -> role.startsWith('AGOV-Loi.') }.collect({ role -> role.substring(9) })
}
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
case 'Eid':
result = result.concat('400')
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 s = request.getAuthSession(true)
def highestRoleLevelNumber = 0
if (!session.get('agov.requestedRoleLevel')) {
LOG.error("IDP: internal error: agov.requestedRoleLevel not set in session")
response.setResult('error');
return
}
def requestedRoleLevelNumber = session.get('agov.requestedRoleLevel').toInteger()
def authenticationMethod = session.get('authenticatedWith')
if (!authenticationMethod) {
LOG.error("IDP: internal error: authenticationMethod not set in session")
response.setResult('error');
return
}
// data transformations needed for SAML and OIDC
// Transform sex to number
if(session.get('ch.nevis.idm.User.gender') == 'MALE'){
s.setAttribute('ch.nevis.idm.User.gender', '1')
}
if(session.get('ch.nevis.idm.User.gender') == 'FEMALE'){
s.setAttribute('ch.nevis.idm.User.gender', '2')
}
if(s.get('ch.nevis.idm.User.gender') == 'OTHER'){
session.setAttribute('ch.nevis.idm.User.gender', '3')
}
// handle accounts qa attributes, and set them in session
// account itself, only needed if not authenticated with e-ID
if (!'urn:qa.agov.ch:names:tc:authfactor:eid'.equalsIgnoreCase(authenticationMethod)) {
def idVerificationList = getUserAGOVLoiIdVerification()
def idVerification = 'None'
if (idVerificationList && !idVerificationList.isEmpty()) {
idVerification = idVerificationList.last()
}
s.setAttribute('idVerification', idVerification)
// contextClassRefToSet based on highest level-role assigned to default profile
for (String role : getUserAGOVLoiRoles()) {
if (role.startsWith('level')) {
def roleLevel = role.substring(5)
int roleLevelNumber = Integer.parseInt(roleLevel)
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
s.setAttribute('agov.actualRoleLevel', '' + highestRoleLevelNumber)
LOG.debug('CheckLoa: actual role level (agov) '+ highestRoleLevelNumber)
// 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 != '') {
s.setAttribute('ValidFrom', '' + validFrom)
}
if(validTo != '') {
s.setAttribute('ValidTo', '' + validTo)
}
}
if (highestRoleLevelNumber > 0) {
// set attribute contextClassRefToSet
s.setAttribute('contextClassRefToSet','urn:qa.agov.ch:names:tc:ac:classes:' .concat(highestRoleLevelNumber.toString()))
} else {
// by default 100
s.setAttribute('contextClassRefToSet','urn:qa.agov.ch:names:tc:ac:classes:100' )
}
}
// address related, needed in any case (also e-ID)
def adressVerificationList = getUserAGOVLoiIdVerification('200')
def adressVerification = 'None'
if (adressVerificationList && !adressVerificationList.isEmpty()) {
adressVerification = adressVerificationList[0]
}
s.setAttribute('agov.adressVerification', '' + adressVerification)
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}'")
// if the account has no profile, we must not return address or svnr
s.setAttribute('agov.appAddressRequired', 'false')
s.setAttribute('agov.appSvnrAllowed', 'false')
response.setResult('ok')
return
}
// no login for users with a recovery role (but onyl when not logging in with e-Id)
// TODO/haburger/2025-07-01: automatic recovery if logging in with e-Id
if (!'urn:qa.agov.ch:names:tc:authfactor:eid'.equalsIgnoreCase(authenticationMethod)) {
// no login for users with a recovery role
def recoveryRoleList = getUserAGOVRecoveryRoles()
if (recoveryRoleList.contains('mustRecover')) {
s.setAttribute('agov.recovery.authnContextClassRef', 'urn:qa.agov.ch:names:tc:ac:classes:mustRecover')
s.setAttribute('agov.recovery.authenticatedWith', session.getAttribute('authenticatedWith') ?: 'unknown' )
def origIdVerification = getUserAGOVLoiIdVerification(highestRoleLevelNumber.toString()) ?: 'None'
def idVerification = getUserIdVerificationForRecovery() ?: origIdVerification
s.setAttribute('agov.recovery.currentIdVerification', '' + idVerification )
// align currentAgovAq with the method selected for idVerification
def currentAgovAqForRecovery = getAqLevelBasedOnIdVerificationForRecovery(idVerification, highestRoleLevelNumber)
s.setAttribute('agov.recovery.currentAgovAq', '' + currentAgovAqForRecovery)
def validFrom = getUserMustRecoverValidFrom() ?: ''
s.setAttribute('agov.recovery.currentAgovAqRoleValidFrom', '' + validFrom )
LOG.debug("CheckLoa: mustRecover: origIdVerification=${origIdVerification}, idVerification=${idVerification}, currentAgovAqForRecovery=${currentAgovAqForRecovery}")
response.setResult('exit.2')
return
} else if (recoveryRoleList.contains('recovery')) {
if (recoveryRoleList.contains('recoveryCascade')) {
s.setAttribute('agov.recovery.authnContextClassRef', 'urn:qa.agov.ch:names:tc:ac:classes:recoveryCascade')
} else {
s.setAttribute('agov.recovery.authnContextClassRef', 'urn:qa.agov.ch:names:tc:ac:classes:recovery')
}
s.setAttribute('agov.recovery.authenticatedWith', session.getAttribute('authenticatedWith') ?: 'unknown')
s.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())
s.setAttribute('agov.recovery.currentIdVerification', (idVerification.isEmpty() ? 'None' : idVerification.first()))
def validFrom = getUserAGOVLoiValidFrom('level'.concat(highestRoleLevelNumber.toString())) ?: ''
s.setAttribute('agov.recovery.currentAgovAqRoleValidFrom', validFrom)
response.setResult('exit.2')
return
}
} else {
// authenticated with e-ID, we adjust highestRoleLevelNumber to e-ID login
highestRoleLevelNumber = 500
s.setAttribute('agov.actualRoleLevel', '' + highestRoleLevelNumber)
LOG.debug('CheckLoa: actual role level (agov) '+ highestRoleLevelNumber)
}
// verifiy that AQ level is high enough
if (highestRoleLevelNumber>=requestedRoleLevelNumber) {
response.setResult('ok')
return;
} else {
// Insufficient_LoaInfo
response.setResult('exit.1');
return;
}
} catch (Exception ex) {
LOG.error("Event='DATAERROR', Requester='${requester}', RequestId='${requestId}', RequestedAq=${requestedAq}, User=${user}, CredentialType='${credentialType}', errorMessage='exception occured: ${ex}', SourceIp=${sourceIp}, UserAgent='${userAgent}'")
ex = StackTraceUtils.sanitize(ex)
def affectedLines = ex.stackTrace.findAll { it.className.startsWith('Script') }.collect { "${it.methodName}:${it.lineNumber}" }
LOG.error("FATAL: Script failure (at lines: ${affectedLines})", ex)
// AuthnFailed_Zero_RoleLvl
response.setResult('error');
return;
}

View File

@ -1,100 +0,0 @@
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() {
LOG.debug("start new FIDO UAF session (skipping ${session['ch.nevis.auth.fido.uaf.fidouafsessionid']}")
def s = request.getAuthSession(true)
s.removeAttribute('ch.nevis.auth.fido.uaf.fidouafsessionid')
inargs.remove('fallback')
}
def clearIdmSessionAttributes() {
def s = request.getAuthSession(true)
def sessionKeySet = new HashSet(session.keySet())
sessionKeySet.each { key ->
if ( key ==~ /ch.nevis.idm.*/ || key ==~ /ch.adnovum.nevisidm.*/ ) {
s.removeAttribute(key)
}
}
}
def sess = request.getAuthSession(true)
// 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
}
if (inargs['fidoUafDone'] == 'true') {
// get clean state, before validating user in IDM
LOG.debug("clear IDM session attributes")
clearIdmSessionAttributes()
}
// continue with OutOfBandFidoUafAuthState
response.setResult('ok')
}
// dispatch form post with fallback input field : transition to FIDO Token authentication
if (inargs['fallback'] == 'fallback') {
sess.setAttribute("eid.placeholder.text", "Fido2 login not implemented yet")
response.setResult('fido2')
}
// dispatch to recovery
if (inargs['fallback'] == 'recovery') {
response.addOutArg('nevis.transfer.destination', parameters.get('recoveryurl'))
response.setStatus(ch.nevis.esauth.auth.engine.AuthResponse.AUTH_CONTINUE)
response.setIsRedirectTransfer(true)
// Remove existing cookies before redirecting to RECOVERY
def agovRecoveryCookie = "agovRecovery=deleted; Path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Strict; Secure; HttpOnly"
response.setHeader('Set-Cookie', agovRecoveryCookie)
return
}
// dispatch form post with fallback input field : go to registration with right loa
if (inargs['fallback'] == 'register') {
sess.setAttribute("eid.placeholder.text", "Registration should not be called here?")
response.setResult('registration')
}
// cancel and go back to login
if (inargs['fallback'] == 'back') {
response.setResult('back')
}
// dispatch form post with onReload input field : refresh QR-code FIDO UAF
if (inargs.containsKey('onReload')) {
clearFidoUAFSession()
response.setResult('default')
}

View File

@ -1,23 +0,0 @@
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['agov.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='GOTOEIDLINKING', 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

@ -1,159 +1,158 @@
import java.text.SimpleDateFormat
import groovy.text.SimpleTemplateEngine
import ch.nevis.idm.client.IdmRestClient
import ch.nevis.idm.client.IdmRestClientFactory
def getDateWithoutTimestamp(String date){
def result = date
if(date.matches('^[0-9-]+[+]{1}.*')){
result = date.replaceAll('[+]{1}.*', "")
}
return result
}
// NOTE/aca/2025/06/19: We could also reload the data from idm after the update instead of updating the session variables manualy -> probably better and less error-prone
def compareAndUpdateSessionVariables(sess, keys, isProperty){
def updatedKeys = []
for(key in keys){
def idmkey = isProperty ? "ch.nevis.idm.User.prop.$key" : "ch.nevis.idm.User.$key"
def eidValue = session["agov.eid.User.$key"] ?: ""
def idmValue = session[idmkey] ?: ""
if(!idmValue || eidValue != idmValue){
sess.setAttribute(idmkey, eidValue)
updatedKeys.add(key)
}
}
return updatedKeys
}
// TODO/haburger/2025-07-01: we should also set the verificationMethod, etc. of the level400 role
String user_update_dto_template = '''
{
"name": {
"firstName": "$firstName",
"familyName": "$familyName"
},
"properties": {
"svnr": "$svnr",
"placeOfBirth": "$placeOfBirth",
"nationality": "$nationality",
"eIdNumber": "$eIdNumber"
},
"gender": "$gender",
"birthDate": "$birthDate",
"modificationComment": "updated user information with eid attributes during request $request"
}
'''
// 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 sess = request.getAuthSession(true)
// Convert EID gender format to IDM
if(sess.get('agov.eid.User.gender') == '1'){
sess.setAttribute('agov.eid.User.gender', 'MALE')
}
if(sess.get('agov.eid.User.gender') == '2'){
sess.setAttribute('agov.eid.User.gender', 'FEMALE')
}
if(sess.get('agov.eid.User.gender') == '3'){
sess.setAttribute('agov.eid.User.gender', 'OTHER')
}
// Compare eid and idm attributes + update idm session variables if they differ
def attributesToAudit = compareAndUpdateSessionVariables(sess, ["firstName", "lastName", "gender"], false)
// NOTE/aca/2025/06/14/: Potentally Throw a DATA ERROR if the properties are different? -> should the svnr number ever change?
def propertiesToAudit = compareAndUpdateSessionVariables(sess, ["svnr", "eIdNumber", "nationality", "placeOfBirth"], true)
// Handle birthdate seperately, since it can contain a timestamp -> we probably don't want to update if only the timestamp is wrong
String eidBirthdate = getDateWithoutTimestamp(session["agov.eid.User.birthDate"] ?: "")
String idmBirthdate = getDateWithoutTimestamp(session["ch.nevis.idm.User.birthDate"] ?: "")
LOG.debug("eidBirthdate: $eidBirthdate idmBirthdate: $idmBirthdate")
if(eidBirthdate != idmBirthdate){
sess.setAttribute("ch.nevis.idm.User.birthDate", eidBirthdate)
// For some reson IdmGetPropertyState uses a different date format than IdmSetPropertyState?
//def date = new SimpleDateFormat('yyyy-MM-dd').parse(eidBirthdate)
//def idmFromatedBirthDate = new SimpleDateFormat('dd.MM.yyyy').format(date)
//sess.setAttribute("ch.nevis.idm.User.birthDate.idmFormat", idmFromatedBirthDate)
attributesToAudit.add("birthDate")
}
// Check if we need to update IDM
def auditedRequired = attributesToAudit.size() > 0 || propertiesToAudit.size() > 0
if(auditedRequired){
// update attributes in idm & transition to User notification
IdmRestClient idmRestClient = IdmRestClientFactory.get(parameters)
String baseUrl = parameters.get("baseUrl")
String clientExtId = parameters.get("clientExtId")
String endPoint = "$baseUrl/api/core/v1"
String userExtId = sess.getAttribute("ch.nevis.idm.User.extId")
String requestUrl = "$endPoint/$clientExtId/users/$userExtId"
def binding = [
"firstName": sess.getAttribute('agov.eid.User.firstName'),
"familyName": sess.getAttribute('agov.eid.User.lastName'),
"svnr": sess.getAttribute('agov.eid.User.svnr'),
"placeOfBirth": sess.getAttribute('agov.eid.User.placeOfBirth'),
"nationality": sess.getAttribute('agov.eid.User.nationality'),
"eIdNumber": sess.getAttribute('agov.eid.User.eIdNumber'),
"gender": sess.getAttribute('agov.eid.User.gender').toLowerCase(),
"birthDate": sess.getAttribute('agov.eid.User.birthDate'),
"request": requestId
]
def templateEngine = new SimpleTemplateEngine()
def userUpdateDto = templateEngine.createTemplate(user_update_dto_template).make(binding).toString()
try {
idmRestClient.patch(requestUrl, userUpdateDto)
}catch(Exception e) {
LOG.error("Failed to update User data in IDM: ${e}")
LOG.error("Event='DATAERROR', Requester='${requester}', RequestId='${requestId}', RequestedAq=${requestedAq}, User=${user}, CredentialType='${credentialType}', SourceIp=${sourceIp}, UserAgent='${userAgent}', reason='Failed to update User data in IDM'")
response.setResult('error')
return
}
String printKeys = attributesToAudit.toListString()
LOG.debug("AuditedAttributes: $printKeys")
// Transform gender back to number
if(sess.get('ch.nevis.idm.User.gender') == 'MALE'){
sess.setAttribute('ch.nevis.idm.User.gender', '1')
}
if(sess.get('ch.nevis.idm.User.gender') == 'FEMALE'){
sess.setAttribute('ch.nevis.idm.User.gender', '2')
}
if(sess.get('ch.nevis.idm.User.gender') == 'OTHER'){
sess.setAttribute('ch.nevis.idm.User.gender', '3')
}
response.setResult('audited')
}else{
// Attributes match & no notification needed => continue by updating the linking credential and sending the saml assertion
// NOTE/aca/2025/06/19: We skip checking the account state, recovery code, mobile number and LoA
LOG.debug("No Audit Required: Logging user in")
response.setResult('noChange')
}
import java.text.SimpleDateFormat
import groovy.text.SimpleTemplateEngine
import ch.nevis.idm.client.IdmRestClient
import ch.nevis.idm.client.IdmRestClientFactory
def getDateWithoutTimestamp(String date){
def result = date
if(date.matches('^[0-9-]+[+]{1}.*')){
result = date.replaceAll('[+]{1}.*', "")
}
return result
}
// NOTE/aca/2025/06/19: We could also reload the data from idm after the update instead of updating the session variables manualy -> probably better and less error-prone
def compareAndUpdateSessionVariables(sess, keys, isProperty){
def updatedKeys = []
for(key in keys){
def idmkey = isProperty ? "ch.nevis.idm.User.prop.$key" : "ch.nevis.idm.User.$key"
def eidValue = session["agov.eid.User.$key"] ?: ""
def idmValue = session[idmkey] ?: ""
if(!idmValue || eidValue != idmValue){
sess.setAttribute(idmkey, eidValue)
updatedKeys.add(key)
}
}
return updatedKeys
}
// TODO/haburger/2025-07-01: we should also set the verificationMethod, etc. of the level400 role
String user_update_dto_template = '''
{
"name": {
"firstName": "$firstName",
"familyName": "$familyName"
},
"properties": {
"svnr": "$svnr",
"placeOfBirth": "$placeOfBirth",
"nationality": "$nationality",
"eIdNumber": "$eIdNumber"
},
"gender": "$gender",
"birthDate": "$birthDate",
"modificationComment": "updated user information with eid attributes during request $request"
}
'''
// 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 sess = request.getAuthSession(true)
// Convert EID gender format to IDM
if(sess.get('agov.eid.User.gender') == '1'){
sess.setAttribute('agov.eid.User.gender', 'MALE')
}
if(sess.get('agov.eid.User.gender') == '2'){
sess.setAttribute('agov.eid.User.gender', 'FEMALE')
}
if(sess.get('agov.eid.User.gender') == '3'){
sess.setAttribute('agov.eid.User.gender', 'OTHER')
}
// Compare eid and idm attributes + update idm session variables if they differ
def attributesToAudit = compareAndUpdateSessionVariables(sess, ["firstName", "lastName", "gender"], false)
// NOTE/aca/2025/06/14/: Potentally Throw a DATA ERROR if the properties are different? -> should the svnr number ever change?
def propertiesToAudit = compareAndUpdateSessionVariables(sess, ["svnr", "eIdNumber", "nationality", "placeOfBirth"], true)
// Handle birthdate seperately, since it can contain a timestamp -> we probably don't want to update if only the timestamp is wrong
String eidBirthdate = getDateWithoutTimestamp(session["agov.eid.User.birthDate"] ?: "")
String idmBirthdate = getDateWithoutTimestamp(session["ch.nevis.idm.User.birthDate"] ?: "")
LOG.debug("eidBirthdate: $eidBirthdate idmBirthdate: $idmBirthdate")
if(eidBirthdate != idmBirthdate){
sess.setAttribute("ch.nevis.idm.User.birthDate", eidBirthdate)
// For some reson IdmGetPropertyState uses a different date format than IdmSetPropertyState?
//def date = new SimpleDateFormat('yyyy-MM-dd').parse(eidBirthdate)
//def idmFromatedBirthDate = new SimpleDateFormat('dd.MM.yyyy').format(date)
//sess.setAttribute("ch.nevis.idm.User.birthDate.idmFormat", idmFromatedBirthDate)
attributesToAudit.add("birthDate")
}
// Check if we need to update IDM
def auditedRequired = attributesToAudit.size() > 0 || propertiesToAudit.size() > 0
if(auditedRequired){
// update attributes in idm & transition to User notification
IdmRestClient idmRestClient = IdmRestClientFactory.get(parameters)
String baseUrl = parameters.get("baseUrl")
String clientExtId = parameters.get("clientExtId")
String endPoint = "$baseUrl/api/core/v1"
String userExtId = sess.getAttribute("ch.nevis.idm.User.extId")
String requestUrl = "$endPoint/$clientExtId/users/$userExtId"
def binding = [
"firstName": sess.getAttribute('agov.eid.User.firstName'),
"familyName": sess.getAttribute('agov.eid.User.lastName'),
"svnr": sess.getAttribute('agov.eid.User.svnr'),
"placeOfBirth": sess.getAttribute('agov.eid.User.placeOfBirth'),
"nationality": sess.getAttribute('agov.eid.User.nationality'),
"eIdNumber": sess.getAttribute('agov.eid.User.eIdNumber'),
"gender": sess.getAttribute('agov.eid.User.gender').toLowerCase(),
"birthDate": sess.getAttribute('agov.eid.User.birthDate'),
"request": requestId
]
def templateEngine = new SimpleTemplateEngine()
def userUpdateDto = templateEngine.createTemplate(user_update_dto_template).make(binding).toString()
try {
idmRestClient.patch(requestUrl, userUpdateDto)
}catch(Exception e) {
LOG.error("Failed to update User data in IDM: ${e}")
LOG.error("Event='DATAERROR', Requester='${requester}', RequestId='${requestId}', RequestedAq=${requestedAq}, User=${user}, CredentialType='${credentialType}', SourceIp=${sourceIp}, UserAgent='${userAgent}', reason='Failed to update User data in IDM'")
response.setResult('error')
return
}
String printKeys = attributesToAudit.toListString()
LOG.debug("AuditedAttributes: $printKeys")
// Transform gender back to number
if(sess.get('ch.nevis.idm.User.gender') == 'MALE'){
sess.setAttribute('ch.nevis.idm.User.gender', '1')
}
if(sess.get('ch.nevis.idm.User.gender') == 'FEMALE'){
sess.setAttribute('ch.nevis.idm.User.gender', '2')
}
if(sess.get('ch.nevis.idm.User.gender') == 'OTHER'){
sess.setAttribute('ch.nevis.idm.User.gender', '3')
}
response.setResult('audited')
}else{
// Attributes match & no notification needed => continue by updating the linking credential and sending the saml assertion
// NOTE/aca/2025/06/19: We skip checking the account state, recovery code, mobile number and LoA
LOG.debug("No Audit Required: Logging user in")
response.setResult('noChange')
}

View File

@ -1,201 +1,203 @@
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.json.JsonBuilder
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 clearEidSession(){
def s = request.getAuthSession(true)
s.removeAttribute('agov.eid.verification')
s.removeAttribute('agov.eid.verification.id')
s.removeAttribute('agov.eid.verification.link')
s.removeAttribute('agov.eid.linkedAccountsDto')
s.removeAttribute('agov.eid.User.birthDate')
s.removeAttribute('agov.eid.User.eIdNumber')
s.removeAttribute('agov.eid.User.firstName')
s.removeAttribute('agov.eid.User.lastName')
s.removeAttribute('agov.eid.User.gender')
s.removeAttribute('agov.eid.User.nationality')
s.removeAttribute('agov.eid.User.placeOfBirth')
s.removeAttribute('agov.eid.User.svnr')
s.removeAttribute('agov.eid.User.origin')
}
def getAccounts(json, String svnr) {
String svnrWithPrefix = "urn:ch-agov-eid:$svnr"
def idm_users_dto = json["Resources"]
def accounts = [:]
def frontend_dto = []
for(user in idm_users_dto){
def credentials_dto = user["urn:nevis:idm:scim:schemas:v1:extension:User"]["credentials"]
if(!credentials_dto){
LOG.warn("Event='DATAERROR', Requester='${requester}', RequestId='${requestId}', RequestedAq=${requestedAq}, User=${extId}, CredentialType='${credentialType}', SourceIp=${sourceIp}, UserAgent='${userAgent}', reason='AGOV account has no credentials'")
}
for(cred in credentials_dto){
def foundCredential = false
def extId = user["externalId"]
//TODO/aca/2025/06/11: Can we have multiple email adresses? -> if yes search for primary
String email = user["emails"][0]["value"]
if(cred["type"] == "SAMLFEDERATION" && ( cred["issuerNameId"] == svnr || cred["issuerNameId"] == svnrWithPrefix )){
// we found more than one federation credential in one AGOV account -> Throw data error
if(foundCredential){
LOG.error("Event='DATAERROR', Requester='${requester}', RequestId='${requestId}', RequestedAq=${requestedAq}, User=${extId}, CredentialType='${credentialType}', SourceIp=${sourceIp}, UserAgent='${userAgent}', reason='Multiple EId linking credentials found in one AGOV account'")
return [null,null]
}
// extract login info
def firstLogin = true
if(cred["credentialLoginInfo"]){
if(cred["credentialLoginInfo"]["lastLogin"] && cred["credentialLoginInfo"]["lastLogin"] != ""){
firstLogin = false
}
}
//NOTE/aca/2025/06/11: Assume that this is sanitized when registered.
def accountName = cred['subjectNameId']
def credentialExtId = cred['extId']
accounts.put(email, [ "extId": extId, "credentialExtId": cred['extId'], "firstLogin": firstLogin ] )
frontend_dto.add(["email": email, "description": accountName])
foundCredential=true
}
}
}
return [ accounts, [ "accounts": frontend_dto ] ]
}
def sess = request.getAuthSession(true)
IdmRestClient idmRestClient = IdmRestClientFactory.get(parameters)
// 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'
if(inargs['submit'] && inargs['login'] && inargs['login'] != ''){
LOG.debug("Account with email: ${inargs['login']} was selceted -> Continuing")
def accounts = new JsonSlurper().parseText(session['agov.eid.linkedAccountsDto'])
def account = accounts.get( inargs['login'].trim() )
sess.setAttribute('agov.eid.linkingCredentialExtId', account["credentialExtId"])
sess.setAttribute('agov.eid.linkedAccountExtId', account["extId"])
if(account["firstLogin"]){
response.setResult('firstLogin')
return
}
response.setResult('ok')
return
}
if(inargs['cancelEid'] && inargs['cancelEid'] == 'cancel'){
LOG.debug("Account selection was canceled: back to initial login screen")
clearEidSession()
response.setResult('backToVerification')
return
}
if(getHeader('Content-Type') == 'application/json'){
String account_selection_dto = session['agov.eid.linkedAccountsFrontendDto']
response.setContent(account_selection_dto.toString())
response.setContentType('application/json')
response.setHttpStatusCode(200)
response.setIsDirectResponse(true)
response.setStatus(AuthResponse.AUTH_CONTINUE)
return
}
String baseUrl = parameters.get("baseUrl")
String clientExtId = parameters.get("clientExtId")
String endPoint = "$baseUrl/api/scim/v1/$clientExtId/Users"
// Fetch account identifier
String svnr = sess.getAttribute("agov.eid.User.svnr")
LOG.debug("search for accounts with SVNR: $svnr")
// Pepare GET request
String attributes = "externalId,emails,urn:nevis:idm:scim:schemas:v1:extension:User.credentials.type,urn:nevis:idm:scim:schemas:v1:extension:User.credentials.issuerNameId,urn:nevis:idm:scim:schemas:v1:extension:User.credentials.subjectNameId,urn:nevis:idm:scim:schemas:v1:extension:User.credentials.extId,urn:nevis:idm:scim:schemas:v1:extension:User.credentials.credentialLoginInfo.lastLogin"
String filter = "urn:nevis:idm:scim:schemas:v1:extension:User.credentials.type=='SAMLFEDERATION'%20AND%20%28%20urn:nevis:idm:scim:schemas:v1:extension:User.credentials.issuerNameId%20==%20'$svnr'%20OR%20urn:nevis:idm:scim:schemas:v1:extension:User.credentials.issuerNameId%20==%20'urn:ch-agov-eid:$svnr'%29"
String requestUrl = "$endPoint?count=20&attributes=$attributes&filter=$filter"
String scimResponse
try {
scimResponse = idmRestClient.get(requestUrl)
//TODO/aca/2025/06/11: Fetch more pages if more than 20 entries have been found
LOG.debug("SCIM Response: $scimResponse")
def json = new JsonSlurper().parseText(scimResponse)
def (accounts, frontend_dto) = getAccounts(json, svnr)
// unrecoverable DATA ERROR happend
if(accounts == null){
response.setResult('error')
return
}
def numAccounts = accounts.size()
LOG.debug("Linked accounts found: " + frontend_dto.toString())
if(numAccounts == 0){
// No account found => show account linking dialog options
response.setResult('noAccount')
return
}else if(numAccounts == 1){
// One account found -> continue with loading attributes from idm (+ notification if it is the first login)
def account = accounts.values().first()
sess.setAttribute('agov.eid.linkingCredentialExtId', account["credentialExtId"])
sess.setAttribute('agov.eid.linkedAccountExtId', account["extId"])
if(account["firstLogin"]){
response.setResult('firstLogin')
return
}
response.setResult('ok')
return
}else{
// Multiple accounts found -> Dispatch the account selection screen
sess.setAttribute('agov.eid.linkedAccountsDto', new JsonBuilder(accounts).toString())
sess.setAttribute('agov.eid.linkedAccountsFrontendDto', new JsonBuilder(frontend_dto).toString())
LOG.debug("Show GUI")
response.setStatus(AuthResponse.AUTH_CONTINUE)
return
}
} catch(Exception e) {
LOG.error("Fetching Agov Accounts Failed: ${e}")
sess.setAttribute("eid.placeholder.text", "EId: An exception occured while fetching the AGOV accounts\n: ${e}")
response.setResult('error')
return
}
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.json.JsonBuilder
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 clearEidSession(){
def s = request.getAuthSession(true)
s.removeAttribute('agov.eid.verification')
s.removeAttribute('agov.eid.verification.id')
s.removeAttribute('agov.eid.verification.link')
s.removeAttribute('agov.eid.linkedAccountsDto')
s.removeAttribute('agov.eid.User.birthDate')
s.removeAttribute('agov.eid.User.eIdNumber')
s.removeAttribute('agov.eid.User.firstName')
s.removeAttribute('agov.eid.User.lastName')
s.removeAttribute('agov.eid.User.gender')
s.removeAttribute('agov.eid.User.nationality')
s.removeAttribute('agov.eid.User.placeOfBirth')
s.removeAttribute('agov.eid.User.svnr')
s.removeAttribute('agov.eid.User.origin')
}
def getAccounts(json, String svnr) {
def idm_users_dto = json["Resources"]
def accounts = [:]
def frontend_dto = []
for(user in idm_users_dto){
def credentials_dto = user["urn:nevis:idm:scim:schemas:v1:extension:User"]["credentials"]
if(!credentials_dto){
LOG.warn("Event='DATAERROR', Requester='${requester}', RequestId='${requestId}', RequestedAq=${requestedAq}, User=${extId}, CredentialType='${credentialType}', SourceIp=${sourceIp}, UserAgent='${userAgent}', reason='AGOV account has no credentials'")
}
for(cred in credentials_dto){
def foundCredential = false
def extId = user["externalId"]
//TODO/aca/2025/06/11: Can we have multiple email adresses? -> if yes search for primary
String email = user["emails"][0]["value"]
if(cred["type"] == "SAMLFEDERATION" && cred["issuerNameId"] == svnr){
// we found a second federation credential in one AGOV account -> Throw data error
if(foundCredential){
LOG.error("Event='DATAERROR', Requester='${requester}', RequestId='${requestId}', RequestedAq=${requestedAq}, User=${extId}, CredentialType='${credentialType}', SourceIp=${sourceIp}, UserAgent='${userAgent}', reason='Multiple EId linking credentials found in one AGOV account'")
return [null,null]
}
// extract login info
def firstLogin = true
if(cred["credentialLoginInfo"]){
if(cred["credentialLoginInfo"]["lastLogin"] && cred["credentialLoginInfo"]["lastLogin"] != ""){
firstLogin = false
}
}
//NOTE/aca/2025/06/11: Assume that this is sanitized when registered.
def accountName = cred['subjectNameId']
def credentialExtId = cred['extId']
accounts.put(email, [ "extId": extId, "credentialExtId": cred['extId'], "firstLogin": firstLogin ] )
frontend_dto.add(["email": email, "description": accountName])
foundCredential=true
}
}
}
return [ accounts, [ "accounts": frontend_dto ] ]
}
def sess = request.getAuthSession(true)
IdmRestClient idmRestClient = IdmRestClientFactory.get(parameters)
// 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'
if(inargs['submit'] && inargs['login'] && inargs['login'] != ''){
LOG.debug("Account with email: ${inargs['login']} was selceted -> Continuing")
def accounts = new JsonSlurper().parseText(session['agov.eid.linkedAccountsDto'])
def account = accounts.get( inargs['login'].trim() )
sess.setAttribute('agov.eid.linkingCredentialExtId', account["credentialExtId"])
sess.setAttribute('agov.eid.linkedAccountExtId', account["extId"])
if(account["firstLogin"]){
response.setResult('firstLogin')
return
}
response.setResult('ok')
return
}
if(inargs['cancelEid'] && inargs['cancelEid'] == 'cancel'){
LOG.debug("Account selection was canceled: back to initial login screen")
clearEidSession()
response.setResult('backToVerification')
return
}
if(getHeader('Content-Type') == 'application/json'){
String account_selection_dto = session['agov.eid.linkedAccountsFrontendDto']
response.setContent(account_selection_dto.toString())
response.setContentType('application/json')
response.setHttpStatusCode(200)
response.setIsDirectResponse(true)
response.setStatus(AuthResponse.AUTH_CONTINUE)
return
}
String baseUrl = parameters.get("baseUrl")
String clientExtId = parameters.get("clientExtId")
String endPoint = "$baseUrl/api/scim/v1/$clientExtId/Users"
// Fetch account identifier
String svnr = sess.getAttribute("agov.eid.User.svnr")
LOG.debug("search for accounts with SVNR: $svnr")
// Pepare GET request
String attributes = "externalId,emails,urn:nevis:idm:scim:schemas:v1:extension:User.credentials.type,urn:nevis:idm:scim:schemas:v1:extension:User.credentials.issuerNameId,urn:nevis:idm:scim:schemas:v1:extension:User.credentials.subjectNameId,urn:nevis:idm:scim:schemas:v1:extension:User.credentials.extId,urn:nevis:idm:scim:schemas:v1:extension:User.credentials.credentialLoginInfo.lastLogin"
String filter = "urn:nevis:idm:scim:schemas:v1:extension:User.credentials.type=='SAMLFEDERATION'%20AND%20urn:nevis:idm:scim:schemas:v1:extension:User.credentials.issuerNameId=='$svnr'"
String requestUrl = "$endPoint?count=20&attributes=$attributes&filter=$filter"
String scimResponse
try {
scimResponse = idmRestClient.get(requestUrl)
//TODO/aca/2025/06/11: Fetch more pages if more than 20 entries have been found
LOG.debug("SCIM Response: $scimResponse")
def json = new JsonSlurper().parseText(scimResponse)
def (accounts, frontend_dto) = getAccounts(json, svnr)
// unrecoverable DATA ERROR happend
if(!accounts){
response.setResult('error')
return
}
def numAccounts = accounts.size()
LOG.debug("Linked accounts found: " + frontend_dto.toString())
if(numAccounts == 0){
//TODO/aca/2025-06-10: Implement next step
// Redirect to an error page or linking page when that's ready and decided
sess.setAttribute("eid.placeholder.text", "EId: No AGOV Account found case not implemented yet")
response.setResult('noAccount')
return
}else if(numAccounts == 1){
// One account found -> continue with loading attributes from idm (+ notification if it is the first login)
def account = accounts.values().first()
sess.setAttribute('agov.eid.linkingCredentialExtId', account["credentialExtId"])
sess.setAttribute('agov.eid.linkedAccountExtId', account["extId"])
if(account["firstLogin"]){
response.setResult('firstLogin')
return
}
response.setResult('ok')
return
}else{
// Multiple accounts found -> Dispatch the account selection screen
sess.setAttribute('agov.eid.linkedAccountsDto', new JsonBuilder(accounts).toString())
sess.setAttribute('agov.eid.linkedAccountsFrontendDto', new JsonBuilder(frontend_dto).toString())
LOG.debug("Show GUI")
response.setStatus(AuthResponse.AUTH_CONTINUE)
return
}
} catch(Exception e) {
LOG.error("Fetching Agov Accounts Failed: ${e}")
sess.setAttribute("eid.placeholder.text", "EId: An exception occured while fetching the AGOV accounts\n: ${e}")
response.setResult('error')
return
}

View File

@ -1,38 +1,38 @@
import ch.nevis.idm.client.IdmRestClient
import ch.nevis.idm.client.IdmRestClientFactory
String user_notification_dto = '''
{
"clientExtId": "{{clientExtId}}",
"userExtId": "{{userExtId}}",
"notificationType": "userNotification4",
"sendingMethod": [
"Email"
],
"async": false
}
'''
IdmRestClient idmRestClient = IdmRestClientFactory.get(parameters)
def sess = request.getAuthSession(true)
String baseUrl = parameters.get("baseUrl")
String clientExtId = parameters.get("clientExtId")
String endPoint = "$baseUrl/api/notification/v1/"
String userExtId = sess.getAttribute("agov.eid.linkedAccountExtId")
String restRequest = user_notification_dto.replaceAll("\\{\\{clientExtId}}", clientExtId).replaceAll("\\{\\{userExtId}}", userExtId)
try {
idmRestClient.post(endPoint, restRequest)
}catch(Exception e) {
LOG.error("Failed to send User Notification: First Login: ${e}")
response.setResult('error')
return
}
response.setResult('ok')
import ch.nevis.idm.client.IdmRestClient
import ch.nevis.idm.client.IdmRestClientFactory
String user_notification_dto = '''
{
"clientExtId": "{{clientExtId}}",
"userExtId": "{{userExtId}}",
"notificationType": "userNotification4",
"sendingMethod": [
"Email"
],
"async": false
}
'''
IdmRestClient idmRestClient = IdmRestClientFactory.get(parameters)
def sess = request.getAuthSession(true)
String baseUrl = parameters.get("baseUrl")
String clientExtId = parameters.get("clientExtId")
String endPoint = "$baseUrl/api/notification/v1/"
String userExtId = sess.getAttribute("agov.eid.linkedAccountExtId")
String restRequest = user_notification_dto.replaceAll("\\{\\{clientExtId}}", clientExtId).replaceAll("\\{\\{userExtId}}", userExtId)
try {
idmRestClient.post(endPoint, restRequest)
}catch(Exception e) {
LOG.error("Failed to send User Notification: First Login: ${e}")
response.setResult('error')
return
}
response.setResult('ok')
return

View File

@ -1,38 +1,38 @@
import ch.nevis.idm.client.IdmRestClient
import ch.nevis.idm.client.IdmRestClientFactory
String user_notification_dto = '''
{
"clientExtId": "{{clientExtId}}",
"userExtId": "{{userExtId}}",
"notificationType": "userNotification3",
"sendingMethod": [
"Email"
],
"async": false
}
'''
IdmRestClient idmRestClient = IdmRestClientFactory.get(parameters)
def sess = request.getAuthSession(true)
String baseUrl = parameters.get("baseUrl")
String clientExtId = parameters.get("clientExtId")
String endPoint = "$baseUrl/api/notification/v1/"
String userExtId = sess.getAttribute("ch.nevis.idm.User.extId")
String restRequest = user_notification_dto.replaceAll("\\{\\{clientExtId}}", clientExtId).replaceAll("\\{\\{userExtId}}", userExtId)
try {
idmRestClient.post(endPoint, restRequest)
}catch(Exception e) {
LOG.error("Failed to send User Notification: Idm Update with EId data: ${e}")
response.setResult('error')
return
}
response.setResult('ok')
import ch.nevis.idm.client.IdmRestClient
import ch.nevis.idm.client.IdmRestClientFactory
String user_notification_dto = '''
{
"clientExtId": "{{clientExtId}}",
"userExtId": "{{userExtId}}",
"notificationType": "userNotification3",
"sendingMethod": [
"Email"
],
"async": false
}
'''
IdmRestClient idmRestClient = IdmRestClientFactory.get(parameters)
def sess = request.getAuthSession(true)
String baseUrl = parameters.get("baseUrl")
String clientExtId = parameters.get("clientExtId")
String endPoint = "$baseUrl/api/notification/v1/"
String userExtId = sess.getAttribute("ch.nevis.idm.User.extId")
String restRequest = user_notification_dto.replaceAll("\\{\\{clientExtId}}", clientExtId).replaceAll("\\{\\{userExtId}}", userExtId)
try {
idmRestClient.post(endPoint, restRequest)
}catch(Exception e) {
LOG.error("Failed to send User Notification: Idm Update with EId data: ${e}")
response.setResult('error')
return
}
response.setResult('ok')
return

View File

@ -1,17 +1,17 @@
import ch.nevis.esauth.auth.engine.AuthResponse
if(inargs['cancel']){
LOG.debug("Account registration canceled: Send response with error")
response.setResult('back')
return
}
if(inargs['register'] == "agov"){
LOG.debug("AGOV account registration was selected")
response.setResult('register')
return
}
LOG.debug("Show GUI")
response.setStatus(AuthResponse.AUTH_CONTINUE)
import ch.nevis.esauth.auth.engine.AuthResponse
if(inargs['cancel']){
LOG.debug("Account registration canceled: Send response with error")
response.setResult('back')
return
}
if(inargs['register'] == "agov"){
LOG.debug("AGOV account registration was selected")
response.setResult('register')
return
}
LOG.debug("Show GUI")
response.setStatus(AuthResponse.AUTH_CONTINUE)
return

View File

@ -1,27 +0,0 @@
import ch.nevis.esauth.auth.engine.AuthResponse
def sess = request.getAuthSession(true)
if(inargs['cancelEid']){
LOG.debug("Account registration canceled: Send response with error")
response.setResult('cancel')
return
}
if(inargs['continue'] == 'link_account'){
LOG.debug("AGOV account linking")
//sess.setAttribute("eid.placeholder.text", "EId: Implicit account linking not implemented yet")
response.setResult('link')
return
}
if(inargs['continue'] == 'register_account'){
LOG.debug("AGOV account registration was selected")
sess.setAttribute("eid.placeholder.text", "EId: Account registration with implicit linking not implemented yet")
response.setResult('register')
return
}
LOG.debug("Show GUI")
response.setStatus(AuthResponse.AUTH_CONTINUE)
return

View File

@ -1,36 +1,36 @@
import ch.nevis.idm.client.IdmRestClient
import ch.nevis.idm.client.IdmRestClientFactory
String login_info_update_dto = '''
{
"success": true,
"credentialExtId": "{{credentialExtId}}"
}
'''
IdmRestClient idmRestClient = IdmRestClientFactory.get(parameters)
def sess = request.getAuthSession(true)
String baseUrl = parameters.get("baseUrl")
String clientExtId = parameters.get("clientExtId")
String endPoint = "$baseUrl/api/core/v1"
String userExtId = sess.getAttribute("ch.nevis.idm.User.extId")
String linkingCredentialExtId = sess.getAttribute("agov.eid.linkingCredentialExtId")
String requestUrl = "$endPoint/$clientExtId/users/$userExtId/login-info"
String restRequest = login_info_update_dto.replaceAll("\\{\\{credentialExtId}}", linkingCredentialExtId)
try {
idmRestClient.post(requestUrl, restRequest)
}catch(Exception e) {
LOG.error("Failed to Update Linking Credential info: ${e}")
response.setResult('error')
return
}
response.setResult('ok')
import ch.nevis.idm.client.IdmRestClient
import ch.nevis.idm.client.IdmRestClientFactory
String login_info_update_dto = '''
{
"success": true,
"credentialExtId": "{{credentialExtId}}"
}
'''
IdmRestClient idmRestClient = IdmRestClientFactory.get(parameters)
def sess = request.getAuthSession(true)
String baseUrl = parameters.get("baseUrl")
String clientExtId = parameters.get("clientExtId")
String endPoint = "$baseUrl/api/core/v1"
String userExtId = sess.getAttribute("ch.nevis.idm.User.extId")
String linkingCredentialExtId = sess.getAttribute("agov.eid.linkingCredentialExtId")
String requestUrl = "$endPoint/$clientExtId/users/$userExtId/login-info"
String restRequest = login_info_update_dto.replaceAll("\\{\\{credentialExtId}}", linkingCredentialExtId)
try {
idmRestClient.post(requestUrl, restRequest)
}catch(Exception e) {
LOG.error("Failed to Update Linking Credential info: ${e}")
response.setResult('error')
return
}
response.setResult('ok')
return

View File

@ -1,456 +1,454 @@
import ch.nevis.esauth.auth.engine.AuthResponse
import ch.nevis.esauth.sess.Session
import ch.nevis.esauth.util.httpclient.api.HttpClient
import groovy.json.JsonSlurper
import io.opentelemetry.api.trace.Span
import java.time.LocalDate
import java.time.ZoneId
import java.time.ZoneOffset
import com.fasterxml.uuid.Generators
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]
}
// returns true on success and false on failure
def getNewVerification(Session sess, HttpClient httpClient, String verification_request_template, String traceparent){
// Initialize the verification session on the verifier
def endPoint = "${parameters.get('eidVerifierBaseUrl')}/api/v1/verifications"
try {
def httpResponse = Http.post()
.url(endPoint)
.header("Accept", "application/json")
.header("traceparent", traceparent)
.entity(Http.entity()
.content(verification_request_template.replaceAll("\\{\\{UUID}}", UUID.randomUUID().toString()))
.contentType("application/json")
.build())
.build()
.send(httpClient)
if (httpResponse.code() != 200) {
LOG.debug("Result: ${httpResponse}")
return false
}
def json = new JsonSlurper().parseText(httpResponse.bodyAsString())
LOG.debug("Result: ${json}")
sess.setAttribute('agov.eid.verification', 'true')
sess.setAttribute('agov.eid.verification.id', json.id)
sess.setAttribute('agov.eid.verification.link', json.verification_url)
// TODO/aca/2025-04-04:This could probably also be INITIATED, once the verifier supports this status
if (json.state != 'PENDING') {
return false
}
}
catch (Exception e) {
LOG.error("Eid verification failed: $e")
return false
}
return true
}
def clearEidSession(){
def s = request.getAuthSession(true)
s.removeAttribute('agov.eid.verification')
s.removeAttribute('agov.eid.verification.id')
s.removeAttribute('agov.eid.verification.link')
}
def verification_request_template = '''
{ "presentation_definition": {
"id": "{{UUID}}",
"name": "AGOV Verification",
"purpose": "AGOV Login",
"format": {
"vc+sd-jwt": {
"sd-jwt_alg_values": [
"ES256"
],
"kb-jwt_alg_values": [
"ES256"
]
}
},
"input_descriptors": [
{
"id": "agov-all-attributes",
"name": "AGOV Identity Verification",
"purpose": "verification and authentication",
"format": {
"vc+sd-jwt": {
"sd-jwt_alg_values": [
"ES256"
],
"kb-jwt_alg_values": [
"ES256"
]
}
},
"constraints": {
"fields": [
{
"path": [
"$.family_name"
]
},
{
"path": [
"$.given_name"
]
},
{
"path": [
"$.birth_date"
]
},
{
"path": [
"$.sex"
]
},
{
"path": [
"$.place_of_origin"
]
},
{
"path": [
"$.birth_place"
]
},
{
"path": [
"$.nationality"
]
},
{
"path": [
"$.personal_administrative_number"
]
},
{
"path": [
"$.document_number"
]
},
{
"path": [
"$.issuance_date"
]
},
{
"path": [
"$.expiry_date"
]
},
{
"path": [
"$.issuing_authority"
]
},
{
"path": [
"$.issuing_country"
]
}
]
}
}
]
}
}
'''
def ERROR_CODE_TO_STATUS_MAPPER = [
'CREDENTIAL_INVALID' : 'FAILED',
'JWT_EXPIRED' : 'ERROR',
'INVALID_FORMAT' : 'ERROR',
'CREDENTIAL_EXPIRED' : 'FAILED',
'MISSING_NONCE' : 'ERROR',
'UNSUPPORTED_FORMAT' : 'ERROR',
'CREDENTIAL_REVOKED' : 'FAILED',
'CREDENTIAL_SUSPENDED' : 'FAILED',
'HOLDER_BINDING_MISMATCH' : 'ERROR',
'CREDENTIAL_MISSING_DATA' : 'FAILED',
'UNRESOLVABLE_STATUS_LIST' : 'ERROR',
'PUBLIC_KEY_OF_ISSUER_UNRESOLVABLE': 'ERROR',
'CLIENT_REJECTED' : 'CANCELED',
'ISSUER_NOT_ACCEPTED' : 'ERROR'
]
// ---------------
// check, whether we are still processing the correct AuthnRequest
// or if the frontend requested a timeout
if ( (inargs.containsKey('authRequestId') && (inargs['authRequestId'] != session['ch.nevis.auth.saml.request.id'])) || inargs['oid4vp'] == 'TIMEOUT') {
// wrong request, "force" a timeout
LOG.debug('authentication timeout enforced, due to concurrent requests (authRequestId missmatch) -> return a 408')
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 sess = request.getAuthSession(true)
if (inargs['oid4vp'] == 'ERROR') {
LOG.debug("oid4vp error")
response.setResult('error')
return
}
if (inargs['oid4vp'] == 'SUCCEEDED') {
LOG.debug("oid4vp succeeded")
response.setResult('ok')
return
}
// switch to access App
if (inargs['accessApp'] == 'accessApp') {
//TODO/aca/2025/06/19: In theory we could also land here when we send 'SUCCESS' to the frontend -> would be better to clear all session vaiables that can be set in this Authstate
//TODO/aca/2025/06/19: Should we here rather set the LOGINMETHOD cookie and send an error assertion, since otherwise we might swich states too often and Nevis will kill the session?
clearEidSession()
LOG.debug("Switch to Access App")
sess.setAttribute('agov.lastLoginMethod', 'accessApp')
response.setResult('agovLogin')
return
}
// switch to fido2
if (inargs['securityKey'] == 'securityKey') {
clearEidSession()
LOG.debug("Switch to Security Key")
sess.setAttribute('agov.lastLoginMethod', 'securityKey')
response.setResult('agovLogin')
return
}
// switch to registration
if (inargs['fallback'] == 'register') {
clearEidSession()
LOG.debug("Switch to registration")
response.setResult('register')
return
}
HttpClient httpClient = HttpClients.create(parameters)
def spanCtxt = Span.current().getSpanContext()
def traceparent = "00-${spanCtxt.getTraceId()}-${spanCtxt.getSpanId()}-${spanCtxt.getTraceFlags().asHex()}"
if (getHeader('Content-Type') == 'application/json' && inargs.containsKey('o.id.v')) {
LOG.debug("Request Status Update")
// request for a status update from the verifier
def result
// FE requested a new verification
if (inargs['o.id.v'] == 'NEW' || inargs['o.id.v'] == 'RESET') {
LOG.debug("Initializing new verification")
if(!getNewVerification(sess, httpClient, verification_request_template, traceparent)){
response.setResult('error')
return
}
}
def idvalue = (!inargs['o.id.v'] || inargs['o.id.v'] == 'NEW' || inargs['o.id.v'] == 'RESET') ? session['agov.eid.verification.id'] : inargs['o.id.v']
LOG.error("IDValSent: " + idvalue)
// check, whether we are still processing the same verification request or if a new one was generated in e.g. another Tab
if(inargs['o.id.v'] && inargs['o.id.v'] != 'NEW' && inargs['o.id.v'] != 'RESET' && inargs['o.id.v'] != session['agov.eid.verification.id']){
// wrong request, tell fe to stop polling and request a timeout
LOG.debug('authentication timeout enforced, due to concurrent requests (verificationRequest missmatch) -> Notify FE & then return a 408')
result = """{
"oid4vp": {
"status": "TIMEOUT",
"verification_url": "${session['agov.eid.verification.link']}",
"id": "${idvalue}",
"error_code": "REQUEST-MISMATCH",
"error_message": "Request Mismatch Detected: Forcing Timeout"
}}"""
response.setContent(result.toString())
response.setContentType('application/json')
response.setHttpStatusCode(200)
response.setIsDirectResponse(true)
response.setStatus(AuthResponse.AUTH_CONTINUE)
return
}
try {
def endPoint = "${parameters.get('eidVerifierBaseUrl')}/api/v1/verifications/${idvalue}"
def httpResponse = Http.get()
.url(endPoint)
.header("Accept", "application/json")
.header("traceparent", traceparent)
.build()
.send(httpClient)
// 404 -> request a new verification
if(httpResponse.code() == 404){
// Frontend should know that we are starting a new request and not recieve an error
def status = "FAILED"
// Delete session variable to start a new verification
sess.removeAttribute('agov.eid.verification')
result = """{
"oid4vp": {
"status": "${status}",
"verification_url": "",
"id": "",
"error_code": "HTTP-ERROR",
"error_message": "Faild to verify status of verification, http status: ${httpResponse.code()}"
}}"""
LOG.warn("<== Response: ${httpResponse.code()}")
}
else if (httpResponse.code() != 200) {
LOG.debug("Result: ${httpResponse}")
def status = "ERROR"
result = """{
"oid4vp": {
"status": "${status}",
"verification_url": "${session['agov.eid.verification.link']}",
"id": "${idvalue}",
"error_code": "HTTP-ERROR",
"error_message": "failed to verify status of verification ${idvalue}, http status: ${httpResponse.code()}"
}}"""
LOG.warn("<== Response: ${httpResponse.code()}")
}
else {
def json = new JsonSlurper().parseText(httpResponse.bodyAsString())
LOG.debug(httpResponse.bodyAsString())
if (json.state == 'SUCCESS') {
def claims = json.wallet_response.credential_subject_data
LOG.debug("Store user data in session")
def validFrom = LocalDate.parse(claims.issuance_date, DateTimeFormatter.ISO_LOCAL_DATE).atStartOfDay(ZoneId.systemDefault()).format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)
def validTo = LocalDate.parse(claims.expiry_date, DateTimeFormatter.ISO_LOCAL_DATE).atTime(23,59,59).atOffset(ZoneOffset.systemDefault()).format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)
sess.setAttribute('agov.eid.User.firstName', claims.given_name)
sess.setAttribute('agov.eid.User.lastName', claims.family_name)
sess.setAttribute('agov.eid.User.birthDate', claims.birth_date)
sess.setAttribute('agov.eid.User.gender', claims.sex)
sess.setAttribute('agov.eid.User.svnr', claims.personal_administrative_number.replace('.',''))
sess.setAttribute('agov.eid.User.placeOfBirth', claims.birth_place)
sess.setAttribute('agov.eid.User.placeOfOrigin', claims.place_of_origin)
sess.setAttribute('agov.eid.User.eIdNumber', claims.document_number)
// Simpler for later comparison -> Is converted again to upper case in the saml assertion
sess.setAttribute('agov.eid.User.nationality', claims.nationality.toString().toLowerCase())
sess.setAttribute('ValidFrom', validFrom)
sess.setAttribute('ValidTo', validTo)
sess.setAttribute('authenticatedWith', "urn:qa.agov.ch:names:tc:authfactor:eid")
sess.setAttribute('idVerification', "Eid")
// BUNDBITBK-5203 Dynamic aq levels
def requestedRoleLevel = session['agov.requestedRoleLevel']
if(requestedRoleLevel == "600"){
sess.setAttribute('contextClassRefToSet', "urn:qa.agov.ch:names:tc:ac:classes:600")
}else{
sess.setAttribute('contextClassRefToSet', "urn:qa.agov.ch:names:tc:ac:classes:500")
}
// subjectUUID v5
def namespace = UUID.fromString(parameters.get('eidUUIDNamespace'))
def uuid = Generators.nameBasedGenerator(namespace).generate(claims.personal_administrative_number)
LOG.debug("UUID derived from svnr: ${uuid}")
String uuidString = uuid.toString()
sess.setAttribute('agov.subjectUUID', '' + uuidString)
response.setUserId(uuidString)
sess.setAttribute('ch.adnovum.nevisidm.user.extId', uuidString)
response.setLoginId(claims.document_number)
response.setAuthLevel("EID")
result = """{
"oid4vp": {
"status": "SUCCEEDED",
"verification_url": "${session['agov.eid.verification.link']}",
"id": "${idvalue}",
"error_code": "NONE"
}}"""
}
else if (json.state == 'FAILED') {
LOG
.error("Eid verification failed: ${json.wallet_response.error_code} (${json.wallet_response.error_description})")
def status = ERROR_CODE_TO_STATUS_MAPPER[json.wallet_response.error_code] ?: 'ERROR'
// Send new request & return variables with new id and url
if(status == 'FAILED' || status == 'CANCELED'){
// Delete session variable to start a new verification
sess.removeAttribute('agov.eid.verification')
// Clear variables for for a cleaner result
sess.removeAttribute('agov.eid.verification.link')
}
result = """{
"oid4vp": {
"status": "${status}",
"verification_url": "${session['agov.eid.verification.link']}",
"id": "${idvalue}",
"error_code": "${json.wallet_response.error_code}",
"error_message": "${json.wallet_response.error_description}"
}}"""
}
else {
result = """{
"oid4vp": {
"status": "${inargs['o.id.v'] == 'NEW' || inargs['o.id.v'] == 'RESET' ? 'INITIATED' : 'PENDING'}",
"verification_url": "${session['agov.eid.verification.link']}",
"id": "${idvalue}",
"error_code": "NONE"
}}"""
}
}
}
catch (Exception e) {
LOG.error("Eid verification failed: ${e}")
result = """{
"oid4vp": {
"status": "ERROR",
"verification_url": "${session['agov.eid.verification.link']}",
"id": "${idvalue}",
"error_code": "HTTP-ERROR",
"error_message": "failed to verify status of verification ${idvalue}, http exception"
}}"""
}
response.setContent(result.toString())
response.setContentType('application/json')
response.setHttpStatusCode(200)
response.setIsDirectResponse(true)
response.setStatus(AuthResponse.AUTH_CONTINUE)
return
}
// if we reach this place, display GUI
LOG.debug("Show GUI")
response.setStatus(AuthResponse.AUTH_CONTINUE)
return
import ch.nevis.esauth.auth.engine.AuthResponse
import ch.nevis.esauth.sess.Session
import ch.nevis.esauth.util.httpclient.api.HttpClient
import groovy.json.JsonSlurper
import io.opentelemetry.api.trace.Span
import java.time.LocalDate
import java.time.ZoneId
import java.time.ZoneOffset
import com.fasterxml.uuid.Generators
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]
}
// returns true on success and false on failure
def getNewVerification(Session sess, HttpClient httpClient, String verification_request_template, String traceparent){
// Initialize the verification session on the verifier
def endPoint = "${parameters.get('eidVerifierBaseUrl')}/api/v1/verifications"
try {
def httpResponse = Http.post()
.url(endPoint)
.header("Accept", "application/json")
.header("traceparent", traceparent)
.entity(Http.entity()
.content(verification_request_template.replaceAll("\\{\\{UUID}}", UUID.randomUUID().toString()))
.contentType("application/json")
.build())
.build()
.send(httpClient)
if (httpResponse.code() != 200) {
LOG.debug("Result: ${httpResponse}")
return false
}
def json = new JsonSlurper().parseText(httpResponse.bodyAsString())
LOG.debug("Result: ${json}")
sess.setAttribute('agov.eid.verification', 'true')
sess.setAttribute('agov.eid.verification.id', json.id)
sess.setAttribute('agov.eid.verification.link', json.verification_url)
// TODO/aca/2025-04-04:This could probably also be INITIATED, once the verifier supports this status
if (json.state != 'PENDING') {
return false
}
}
catch (Exception e) {
LOG.error("Eid verification failed: $e")
return false
}
return true
}
def clearEidSession(){
def s = request.getAuthSession(true)
s.removeAttribute('agov.eid.verification')
s.removeAttribute('agov.eid.verification.id')
s.removeAttribute('agov.eid.verification.link')
}
def verification_request_template = '''
{ "presentation_definition": {
"id": "{{UUID}}",
"name": "AGOV Verification",
"purpose": "AGOV Login",
"format": {
"vc+sd-jwt": {
"sd-jwt_alg_values": [
"ES256"
],
"kb-jwt_alg_values": [
"ES256"
]
}
},
"input_descriptors": [
{
"id": "agov-all-attributes",
"name": "AGOV Identity Verification",
"purpose": "verification and authentication",
"format": {
"vc+sd-jwt": {
"sd-jwt_alg_values": [
"ES256"
],
"kb-jwt_alg_values": [
"ES256"
]
}
},
"constraints": {
"fields": [
{
"path": [
"$.family_name"
]
},
{
"path": [
"$.given_name"
]
},
{
"path": [
"$.birth_date"
]
},
{
"path": [
"$.sex"
]
},
{
"path": [
"$.place_of_origin"
]
},
{
"path": [
"$.birth_place"
]
},
{
"path": [
"$.nationality"
]
},
{
"path": [
"$.personal_administrative_number"
]
},
{
"path": [
"$.document_number"
]
},
{
"path": [
"$.issuance_date"
]
},
{
"path": [
"$.expiry_date"
]
},
{
"path": [
"$.issuing_authority"
]
},
{
"path": [
"$.issuing_country"
]
}
]
}
}
]
}
}
'''
def ERROR_CODE_TO_STATUS_MAPPER = [
'CREDENTIAL_INVALID' : 'FAILED',
'JWT_EXPIRED' : 'ERROR',
'INVALID_FORMAT' : 'ERROR',
'CREDENTIAL_EXPIRED' : 'FAILED',
'MISSING_NONCE' : 'ERROR',
'UNSUPPORTED_FORMAT' : 'ERROR',
'CREDENTIAL_REVOKED' : 'FAILED',
'CREDENTIAL_SUSPENDED' : 'FAILED',
'HOLDER_BINDING_MISMATCH' : 'ERROR',
'CREDENTIAL_MISSING_DATA' : 'FAILED',
'UNRESOLVABLE_STATUS_LIST' : 'ERROR',
'PUBLIC_KEY_OF_ISSUER_UNRESOLVABLE': 'ERROR',
'CLIENT_REJECTED' : 'CANCELED',
'ISSUER_NOT_ACCEPTED' : 'ERROR'
]
// ---------------
// check, whether we are still processing the correct AuthnRequest
// or if the frontend requested a timeout
if ( (inargs.containsKey('authRequestId') && (inargs['authRequestId'] != session['ch.nevis.auth.saml.request.id'])) || inargs['oid4vp'] == 'TIMEOUT') {
// wrong request, "force" a timeout
LOG.debug('authentication timeout enforced, due to concurrent requests (authRequestId missmatch) -> return a 408')
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 sess = request.getAuthSession(true)
if (inargs['oid4vp'] == 'ERROR') {
LOG.debug("oid4vp error")
response.setResult('error')
return
}
if (inargs['oid4vp'] == 'SUCCEEDED') {
LOG.debug("oid4vp succeeded")
response.setResult('ok')
return
}
// switch to access App
if (inargs['accessApp'] == 'accessApp') {
//TODO/aca/2025/06/19: In theory we could also land here when we send 'SUCCESS' to the frontend -> would be better to clear all session vaiables that can be set in this Authstate
//TODO/aca/2025/06/19: Should we here rather set the LOGINMETHOD cookie and send an error assertion, since otherwise we might swich states too often and Nevis will kill the session?
clearEidSession()
LOG.debug("Switch to Access App")
sess.setAttribute('agov.lastLoginMethod', 'accessApp')
response.setResult('agovLogin')
return
}
// switch to fido2
if (inargs['securityKey'] == 'securityKey') {
clearEidSession()
LOG.debug("Switch to Security Key")
sess.setAttribute('agov.lastLoginMethod', 'securityKey')
response.setResult('agovLogin')
return
}
// switch to registration
if (inargs['fallback'] == 'register') {
clearEidSession()
LOG.debug("Switch to registration")
response.setResult('register')
return
}
HttpClient httpClient = HttpClients.create(parameters)
def spanCtxt = Span.current().getSpanContext()
def traceparent = "00-${spanCtxt.getTraceId()}-${spanCtxt.getSpanId()}-${spanCtxt.getTraceFlags().asHex()}"
if (getHeader('Content-Type') == 'application/json' && inargs.containsKey('o.id.v')) {
LOG.debug("Request Status Update")
// request for a status update from the verifier
def result
// FE requested a new verification
if (inargs['o.id.v'] == 'NEW' || inargs['o.id.v'] == 'RESET') {
LOG.debug("Initializing new verification")
if(!getNewVerification(sess, httpClient, verification_request_template, traceparent)){
response.setResult('error')
return
}
}
def idvalue = (!inargs['o.id.v'] || inargs['o.id.v'] == 'NEW' || inargs['o.id.v'] == 'RESET') ? session['agov.eid.verification.id'] : inargs['o.id.v']
LOG.error("IDValSent: " + idvalue)
// check, whether we are still processing the same verification request or if a new one was generated in e.g. another Tab
if(inargs['o.id.v'] && inargs['o.id.v'] != 'NEW' && inargs['o.id.v'] != 'RESET' && inargs['o.id.v'] != session['agov.eid.verification.id']){
// wrong request, tell fe to stop polling and request a timeout
LOG.debug('authentication timeout enforced, due to concurrent requests (verificationRequest missmatch) -> Notify FE & then return a 408')
result = """{
"oid4vp": {
"status": "TIMEOUT",
"verification_url": "${session['agov.eid.verification.link']}",
"id": "${idvalue}",
"error_code": "REQUEST-MISMATCH",
"error_message": "Request Mismatch Detected: Forcing Timeout"
}}"""
response.setContent(result.toString())
response.setContentType('application/json')
response.setHttpStatusCode(200)
response.setIsDirectResponse(true)
response.setStatus(AuthResponse.AUTH_CONTINUE)
return
}
try {
def endPoint = "${parameters.get('eidVerifierBaseUrl')}/api/v1/verifications/${idvalue}"
def httpResponse = Http.get()
.url(endPoint)
.header("Accept", "application/json")
.header("traceparent", traceparent)
.build()
.send(httpClient)
// 404 -> request a new verification
if(httpResponse.code() == 404){
// Frontend should know that we are starting a new request and not recieve an error
def status = "FAILED"
// Delete session variable to start a new verification
sess.removeAttribute('agov.eid.verification')
result = """{
"oid4vp": {
"status": "${status}",
"verification_url": "",
"id": "",
"error_code": "HTTP-ERROR",
"error_message": "Faild to verify status of verification, http status: ${httpResponse.code()}"
}}"""
LOG.warn("<== Response: ${httpResponse.code()}")
}
else if (httpResponse.code() != 200) {
LOG.debug("Result: ${httpResponse}")
def status = "ERROR"
result = """{
"oid4vp": {
"status": "${status}",
"verification_url": "${session['agov.eid.verification.link']}",
"id": "${idvalue}",
"error_code": "HTTP-ERROR",
"error_message": "failed to verify status of verification ${idvalue}, http status: ${httpResponse.code()}"
}}"""
LOG.warn("<== Response: ${httpResponse.code()}")
}
else {
def json = new JsonSlurper().parseText(httpResponse.bodyAsString())
LOG.debug(httpResponse.bodyAsString())
if (json.state == 'SUCCESS') {
def claims = json.wallet_response.credential_subject_data
LOG.debug("Store user data in session")
def validFrom = LocalDate.parse(claims.issuance_date, DateTimeFormatter.ISO_LOCAL_DATE).atStartOfDay(ZoneId.systemDefault()).format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)
def validTo = LocalDate.parse(claims.expiry_date, DateTimeFormatter.ISO_LOCAL_DATE).atTime(23,59,59).atOffset(ZoneOffset.systemDefault()).format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)
sess.setAttribute('agov.eid.User.firstName', claims.given_name)
sess.setAttribute('agov.eid.User.lastName', claims.family_name)
sess.setAttribute('agov.eid.User.birthDate', claims.birth_date)
sess.setAttribute('agov.eid.User.gender', claims.sex)
sess.setAttribute('agov.eid.User.svnr', claims.personal_administrative_number.replace('.',''))
sess.setAttribute('agov.eid.User.placeOfBirth', claims.birth_place)
sess.setAttribute('agov.eid.User.eIdNumber', claims.document_number)
// Simpler for later comparison -> Is converted again to upper case in the saml assertion
sess.setAttribute('agov.eid.User.nationality', claims.nationality.toString().toLowerCase())
sess.setAttribute('ValidFrom', validFrom)
sess.setAttribute('ValidTo', validTo)
sess.setAttribute('authenticatedWith', "urn:qa.agov.ch:names:tc:authfactor:eid")
sess.setAttribute('idVerification', "Eid")
// BUNDBITBK-5203 Dynamic aq levels
def requestedRoleLevel = session['agov.requestedRoleLevel']
if(requestedRoleLevel == "600"){
sess.setAttribute('contextClassRefToSet', "urn:qa.agov.ch:names:tc:ac:classes:600")
}else{
sess.setAttribute('contextClassRefToSet', "urn:qa.agov.ch:names:tc:ac:classes:500")
}
// subjectUUID v5
def namespace = UUID.fromString(parameters.get('eidUUIDNamespace'))
def uuid = Generators.nameBasedGenerator(namespace).generate(claims.personal_administrative_number)
LOG.debug("UUID derived from svnr: ${uuid}")
String uuidString = uuid.toString()
sess.setAttribute('agov.subjectUUID', '' + uuidString)
response.setUserId(uuidString)
sess.setAttribute('ch.adnovum.nevisidm.user.extId', uuidString)
response.setLoginId(claims.document_number)
response.setAuthLevel("EID")
result = """{
"oid4vp": {
"status": "SUCCEEDED",
"verification_url": "${session['agov.eid.verification.link']}",
"id": "${idvalue}",
"error_code": "NONE"
}}"""
}
else if (json.state == 'FAILED') {
LOG
.error("Eid verification failed: ${json.wallet_response.error_code} (${json.wallet_response.error_description})")
def status = ERROR_CODE_TO_STATUS_MAPPER[json.wallet_response.error_code] ?: 'ERROR'
// Send new request & return variables with new id and url
if(status == 'FAILED' || status == 'CANCELED'){
// Delete session variable to start a new verification
sess.removeAttribute('agov.eid.verification')
// Clear variables for for a cleaner result
sess.removeAttribute('agov.eid.verification.link')
}
result = """{
"oid4vp": {
"status": "${status}",
"verification_url": "${session['agov.eid.verification.link']}",
"id": "${idvalue}",
"error_code": "${json.wallet_response.error_code}",
"error_message": "${json.wallet_response.error_description}"
}}"""
}
else {
result = """{
"oid4vp": {
"status": "${inargs['o.id.v'] == 'NEW' || inargs['o.id.v'] == 'RESET' ? 'INITIATED' : 'PENDING'}",
"verification_url": "${session['agov.eid.verification.link']}",
"id": "${idvalue}",
"error_code": "NONE"
}}"""
}
}
}
catch (Exception e) {
LOG.error("Eid verification failed: ${e}")
result = """{
"oid4vp": {
"status": "ERROR",
"verification_url": "${session['agov.eid.verification.link']}",
"id": "${idvalue}",
"error_code": "HTTP-ERROR",
"error_message": "failed to verify status of verification ${idvalue}, http exception"
}}"""
}
response.setContent(result.toString())
response.setContentType('application/json')
response.setHttpStatusCode(200)
response.setIsDirectResponse(true)
response.setStatus(AuthResponse.AUTH_CONTINUE)
return
}
// if we reach this place, display GUI
LOG.debug("Show GUI")
response.setStatus(AuthResponse.AUTH_CONTINUE)
return

View File

@ -122,4 +122,4 @@ if (inargs['submit']) {
}
// show the GUI
response.setStatus(AuthResponse.AUTH_CONTINUE)
response.setStatus(AuthResponse.AUTH_CONTINUE)

View File

@ -13,9 +13,8 @@ JAVA_OPTS=(
"-javaagent:/opt/agent/opentelemetry-javaagent.jar"
"-Dotel.javaagent.logging=application"
"-Dotel.javaagent.configuration-file=/var/opt/nevisauth/default/conf/otel.properties"
"-Dotel.resource.attributes=service.version=8.2505.5,service.instance.id=$HOSTNAME"
"-Dotel.resource.attributes=service.version=8.2411.3,service.instance.id=$HOSTNAME"
"-Djavax.net.ssl.trustStore=/var/opt/keys/trust/auth-idp-extended-truststore/truststore.p12"
"-Djavax.net.ssl.trustStorePassword=\${exec:/var/opt/keys/trust/auth-idp-extended-truststore/keypass}"
)

View File

@ -5,8 +5,6 @@
<SessionCoordinator sessionInitialInactivityTimeout="600" sessionInactivityTimeout="28800" sessionMaxLifetime="28800" sessionIdPreGenerate="true">
<!-- source: pattern://7022472ae407577ae604bbb8 -->
<LocalSessionStore maxSessions="100000"/>
<!-- source: pattern://b7b59e97b3fd18bb60178573 -->
<RemoteSessionStore connectionUser="pipe:///var/opt/nevisauth/default/conf/credentials/dbUser" connectionPassword="pipe:///var/opt/nevisauth/default/conf/credentials/dbPassword" connectionUrl="jdbc:mariadb://mariadb-session-store-service.adn-agov-nevisidm-ob-01-uat:3306/nevisauth?serverTimezone=UTC&amp;sslMode=disable&amp;autocommit=true" connectionMaxLifeTime="1800000" connectionMaxIdleTime="600000" connectionMinPoolSize="10" connectionMaxPoolSize="10" connectionAutomaticDbSchemaSetup="false" storeUnauthenticatedSessions="true"/>
<!-- source: pattern://7022472ae407577ae604bbb8 -->
<TokenAssembler name="DefaultTokenAssembler">
<Selector default="true"/>
@ -47,8 +45,6 @@
<!-- source: pattern://94e0b7b92ff2593f958c1eec -->
<field src="session" key="ch.adnovum.nevisidm.clientId" as="clientId"/>
<!-- source: pattern://94e0b7b92ff2593f958c1eec -->
<field src="session" key="ch.nevis.session.domain" as="domain"/>
<!-- source: pattern://94e0b7b92ff2593f958c1eec -->
<field src="request" key="ActualRoles" as="roles"/>
</TokenSpec>
<!-- source: pattern://94e0b7b92ff2593f958c1eec -->
@ -69,8 +65,6 @@
<!-- source: pattern://94e0b7b92ff2593f958c1eec -->
<field src="session" key="ch.adnovum.nevisidm.clientId" as="clientId"/>
<!-- source: pattern://94e0b7b92ff2593f958c1eec -->
<field src="session" key="ch.nevis.session.domain" as="domain"/>
<!-- source: pattern://94e0b7b92ff2593f958c1eec -->
<field src="request" key="ActualRoles" as="roles"/>
</TokenSpec>
<!-- source: pattern://94e0b7b92ff2593f958c1eec -->
@ -110,11 +104,6 @@
<!-- source: pattern://947daa1313709b0f26a64432 -->
<KeyObject name="EId_Rest_Client_Trust_Store" certificate="/var/opt/keys/trust/env-ca/truststore.jks"/>
</KeyStore>
<!-- source: pattern://95220b3005deb118adeb01aa -->
<KeyStore name="EId_Account_Linking_Mobile_NLess_Auth">
<!-- source: pattern://95220b3005deb118adeb01aa -->
<KeyObject name="FIDO_UAF_Truststore" certificate="/var/opt/keys/trust/env-ca/truststore.jks"/>
</KeyStore>
<!-- source: pattern://947daa1313709b0f26a64432 -->
<KeyStore name="EId_Select_AGOV_Account">
<!-- source: pattern://947daa1313709b0f26a64432 -->
@ -134,11 +123,6 @@
<!-- source: pattern://8dbec5bb024707d73fca93ef -->
<KeyObject name="https://trustbroker-idp.agov-w.azure.adnovum.net" certificate="/var/opt/keys/trust/idp-pem-atb/truststore.jks"/>
</KeyStore>
<!-- source: pattern://b09a3092a59797b317c06ae4 -->
<KeyStore name="EncryptionKeys">
<!-- source: pattern://b09a3092a59797b317c06ae4 -->
<KeyObject name="DefaultEncryptionKey" certificate="/var/opt/keys/trust/idp-pem-atb-enc/truststore.jks"/>
</KeyStore>
<!-- source: pattern://cb8c63274fe346280de0ffd5 -->
<KeyStore name="Auth_Realm_Mobile_FIDO_UAFKeyStore">
<!-- source: pattern://cb8c63274fe346280de0ffd5 -->
@ -157,8 +141,8 @@
<KeyObject name="internal_tls_Truststore" certificate="/var/opt/keys/trust/env-ca/truststore.jks"/>
</KeyStore>
</SessionCoordinator>
<!-- source: pattern://b7b59e97b3fd18bb60178573 -->
<RemoteOutOfContextDataStore connectionUser="pipe:///var/opt/nevisauth/default/conf/credentials/dbUser" connectionPassword="pipe:///var/opt/nevisauth/default/conf/credentials/dbPassword" connectionUrl="jdbc:mariadb://mariadb-session-store-service.adn-agov-nevisidm-ob-01-uat:3306/nevisauth?serverTimezone=UTC&amp;sslMode=disable&amp;autocommit=true" connectionMaxLifeTime="1800000" connectionMaxIdleTime="600000" connectionMinPoolSize="10" connectionMaxPoolSize="10" connectionAutomaticDbSchemaSetup="false"/>
<!-- source: pattern://7022472ae407577ae604bbb8 -->
<LocalOutOfContextDataStore reaperPeriod="60"/>
<!-- source: pattern://204c22beaccdfd22727af378, pattern://06aeae2d799e492f5580d03b, pattern://7022472ae407577ae604bbb8, pattern://7022472ae407577ae604bbb8, pattern://9a8294b080ea769d22924af0, pattern://f393012a278e525956a362d3, pattern://c686c1bdd5355351f7f98cc8, pattern://7fb39bfd6c34685866a22180, pattern://b8bdab6e4634a1d81f20e5bb, pattern://cb8c63274fe346280de0ffd5, pattern://9a1d3c6052019748d3510261, pattern://ae023be7e097522c74e31d17, pattern://81ae3547acc02160f787a546, pattern://0327ca909dfcaf2d332da104, pattern://584964c837512845d7940809, pattern://e0fda9336be9c69dafc9b69e, pattern://7022472ae407577ae604bbb8, pattern://cb8c63274fe346280de0ffd5, pattern://204c22beaccdfd22727af378, pattern://06aeae2d799e492f5580d03b, pattern://7022472ae407577ae604bbb8 -->
<AuthEngine useLiteralDictionary="true" literalDictionaryLanguages="en,de,fr,it" inputLanguageCookie="LANG" compatLevel="none" addAutheLevelToSecRoles="true" classPath="/var/opt/nevisauth/default/plugin:/opt/nevisidmcl/nevisauth/lib:/opt/nevisfidocl/nevisauth/lib:/opt/nevisauth/plugin" propagateSession="false">
<!-- source: pattern://4fcfadb4a5c946ead7e6e995 -->
@ -231,8 +215,6 @@
<!-- source: pattern://f63c475c35b616b7c6c1901c -->
<GuiElem name="agov.appDisplayNameEN" type="hidden" value="${sess:agov.appDisplayNameEN}" optional="true"/>
<!-- source: pattern://f63c475c35b616b7c6c1901c -->
<GuiElem name="agov.appDisplayNameRM" type="hidden" value="${sess:agov.appDisplayNameRM}" optional="true"/>
<!-- source: pattern://f63c475c35b616b7c6c1901c -->
<GuiElem name="agov.appSamlRpEntityId" type="hidden" value="https://auth.agov-w.azure.adnovum.net/app-info/app-icon?entity-id=${sess:ch.nevis.auth.saml.request.scoping.requesterId}" optional="true"/>
<!-- source: pattern://f63c475c35b616b7c6c1901c -->
<GuiElem name="authRequestId" type="hidden" value="${sess:ch.nevis.auth.saml.request.id}" optional="true"/>
@ -431,8 +413,6 @@
<!-- source: pattern://73efd00d67082ff1eb927922 -->
<ResultCond name="main" next="Auth_Realm_Main_IDP_Auth_Realm_Main_IDP_Custom_AGOV_IDP"/>
<!-- source: pattern://73efd00d67082ff1eb927922 -->
<ResultCond name="main_secure" next="Auth_Realm_Main_IDP_Auth_Realm_Main_IDP_Custom_AGOV_IDP_SEC"/>
<!-- source: pattern://73efd00d67082ff1eb927922 -->
<Response value="AUTH_CONTINUE">
<!-- source: pattern://73efd00d67082ff1eb927922 -->
<Gui name="saml_dispatcher" label="title.saml.failed">
@ -574,7 +554,7 @@
<!-- source: pattern://4f15bae09cbda04a7a515158 -->
<ResultCond name="firstLogin" next="Auth_Realm_Main_IDP_EId_Select_AGOV_Account_NotifyUser"/>
<!-- source: pattern://4f15bae09cbda04a7a515158 -->
<ResultCond name="noAccount" next="Auth_Realm_Main_IDP_EId_Start_AGOV_Account_Linking"/>
<ResultCond name="noAccount" next="Auth_Realm_Main_IDP_Eid_Placeholder"/>
<!-- source: pattern://4f15bae09cbda04a7a515158 -->
<ResultCond name="ok" next="Auth_Realm_Main_IDP_EId_Fetch_IDM_Attributes"/>
<!-- source: pattern://4f15bae09cbda04a7a515158 -->
@ -719,12 +699,6 @@
<property name="out.attribute.http://schemas.agov.ch/ws/2023/05/identity/claims/sex" value="${sess:ch.nevis.idm.User.gender}"/>
<!-- source: pattern://1d81bd987455a8e1ee044ccf -->
<property name="out.attribute.http://schemas.agov.ch/ws/2023/05/identity/claims/dateOfBirth" value="${sess:ch.nevis.idm.User.birthDate:^(\d\d\d\d-\d\d-\d\d).*$}"/>
<!-- source: pattern://1d81bd987455a8e1ee044ccf -->
<property name="out.attribute.http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" value="${sess:ch.nevis.idm.User.email}"/>
<!-- source: pattern://1d81bd987455a8e1ee044ccf -->
<property name="out.attribute.http://schemas.agov.ch/ws/2023/05/identity/claims/socialSecurityNumber" value="#{ (sess['agov.appSvnrAllowed'] == 'true') ? sess['ch.nevis.idm.User.prop.svnr'] : ''}"/>
<!-- source: pattern://1d81bd987455a8e1ee044ccf -->
<property name="out.attribute.http://schemas.agov.ch/ws/2025/07/identity/claims/op/conversationId" value="${inctx:connection.HttpHeader.traceparent:^([0-9a-f]+)-([0-9a-f]+)-([0-9a-f]+)-([0-9a-f]+)$:$2}"/>
</AuthState>
<AuthState name="Auth_Realm_Main_IDP_Auth_Realm_Main_IDP_Custom_EPD_Artifact_IDP" class="ch.nevis.esauth.auth.states.saml.IdentityProviderState" final="false" resumeState="true">
<!-- source: pattern://5a75ffc73b91b88cfab6168e -->
@ -813,12 +787,6 @@
<property name="out.attribute.http://schemas.agov.ch/ws/2023/05/identity/claims/sex" value="${sess:ch.nevis.idm.User.gender}"/>
<!-- source: pattern://5a75ffc73b91b88cfab6168e -->
<property name="out.attribute.http://schemas.agov.ch/ws/2023/05/identity/claims/dateOfBirth" value="${sess:ch.nevis.idm.User.birthDate:^(\d\d\d\d-\d\d-\d\d).*$}"/>
<!-- source: pattern://5a75ffc73b91b88cfab6168e -->
<property name="out.attribute.http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" value="${sess:ch.nevis.idm.User.email}"/>
<!-- source: pattern://5a75ffc73b91b88cfab6168e -->
<property name="out.attribute.http://schemas.agov.ch/ws/2023/05/identity/claims/socialSecurityNumber" value="#{ (sess['agov.appSvnrAllowed'] == 'true') ? sess['ch.nevis.idm.User.prop.svnr'] : ''}"/>
<!-- source: pattern://5a75ffc73b91b88cfab6168e -->
<property name="out.attribute.http://schemas.agov.ch/ws/2025/07/identity/claims/op/conversationId" value="${inctx:connection.HttpHeader.traceparent:^([0-9a-f]+)-([0-9a-f]+)-([0-9a-f]+)-([0-9a-f]+)$:$2}"/>
</AuthState>
<AuthState name="Auth_Realm_Main_IDP_Auth_Realm_Main_IDP_Custom_AGOV_IDP" class="ch.nevis.esauth.auth.states.saml.IdentityProviderState" final="false" resumeState="true">
<!-- source: pattern://92cb6d5256008a32f12ceb93 -->
@ -860,10 +828,6 @@
<!-- source: pattern://92cb6d5256008a32f12ceb93 -->
<property name="logoutTrigger" value="#{request['currentResource'].contains('logout') || inargs.containsKey('logout') || inargs.containsKey('SAMLLogout')}"/>
<!-- source: pattern://92cb6d5256008a32f12ceb93 -->
<property name="in.verify" value="Assertion, AuthnRequest, ArtifactResolve, ArtifactResponse"/>
<!-- source: pattern://92cb6d5256008a32f12ceb93 -->
<property name="in.prospectVerification" value="ArtifactResolve"/>
<!-- source: pattern://92cb6d5256008a32f12ceb93 -->
<property name="out.binding" value="http-post"/>
<!-- source: pattern://92cb6d5256008a32f12ceb93 -->
<property name="out.post.relayStateEncoding" value="HTML"/>
@ -918,8 +882,6 @@
<!-- source: pattern://92cb6d5256008a32f12ceb93 -->
<property name="out.attribute.http://schemas.agov.ch/ws/2023/05/identity/claims/placeOfBirth" value="#{ (sess['agov.appSvnrAllowed'] == 'true') ? sess['ch.nevis.idm.User.prop.placeOfBirth'] : ''}"/>
<!-- source: pattern://92cb6d5256008a32f12ceb93 -->
<property name="out.attribute.http://schemas.agov.ch/ws/2025/07/identity/claims/placeOfOrigin" value="#{ (sess['agov.appSvnrAllowed'] == 'true') ? sess['agov.eid.User.placeOfOrigin'] : ''}"/>
<!-- source: pattern://92cb6d5256008a32f12ceb93 -->
<property name="out.attribute.http://schemas.agov.ch/ws/2023/05/identity/claims/eIdNumber" value="${sess:ch.nevis.idm.User.prop.eIdNumber}"/>
<!-- source: pattern://92cb6d5256008a32f12ceb93 -->
<property name="out.attribute.http://schemas.agov.ch/ws/2023/05/identity/claims/qa/dateOfVerification" value="${sess:ValidFrom}"/>
@ -947,21 +909,6 @@
<property name="out.attribute.http://schemas.agov.ch/ws/2024/02/identity/claims/address/qa/verificationMethod" value="#{ (sess['agov.appAddressRequired'] == 'true') ? ''.concat(sess.get('agov.adressVerification')).replace('Location', 'Domicile') : '' }"/>
<!-- source: pattern://92cb6d5256008a32f12ceb93 -->
<property name="out.attribute.http://schemas.agov.ch/ws/2024/02/identity/claims/address/countryName" value="#{ (sess['agov.appAddressRequired'] == 'true') ? sess['agov.countryName'] : ''}"/>
<!-- source: pattern://92cb6d5256008a32f12ceb93 -->
<property name="out.attribute.http://schemas.agov.ch/ws/2025/07/identity/claims/op/conversationId" value="${inctx:connection.HttpHeader.traceparent:^([0-9a-f]+)-([0-9a-f]+)-([0-9a-f]+)-([0-9a-f]+)$:$2}"/>
</AuthState>
<AuthState name="Auth_Realm_Main_IDP_Auth_Realm_Main_IDP_Custom_AGOV_IDP_SEC" class="ch.nevis.esauth.auth.states.standard.ConditionalDispatcherState" final="false" resumeState="false">
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<ResultCond name="default" next="Auth_Realm_Main_IDP_Auth_Realm_Main_IDP_Custom_AGOV_IDP_SEC_post"/>
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<ResultCond name="useArtifact" next="Auth_Realm_Main_IDP_Auth_Realm_Main_IDP_Custom_AGOV_IDP_SEC_artifact"/>
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<Response value="AUTH_ERROR">
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<Gui name="AuthErrorDialog"/>
</Response>
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<property name="condition:useArtifact" value="${sess:agov.idp.use.artifact:^true$}"/>
</AuthState>
<AuthState name="Auth_Realm_Main_IDP_ReturnTimeoutButKeepSession" class="ch.nevis.esauth.auth.states.scripting.ScriptState" final="false" resumeState="true">
<!-- source: pattern://826166d230a6a4849f2837ae -->
@ -1065,29 +1012,15 @@
<!-- source: pattern://4f15bae09cbda04a7a515158 -->
<property name="parameter.idm.httpclient.tls.trustStoreRef" value="EId_Select_AGOV_Account"/>
</AuthState>
<AuthState name="Auth_Realm_Main_IDP_EId_Start_AGOV_Account_Linking" class="ch.nevis.esauth.auth.states.scripting.ScriptState" final="false" resumeState="true">
<!-- source: pattern://328e529ed345d17cacb4ec66 -->
<ResultCond name="cancel" next="Auth_Realm_Main_IDP_SendSamlResponseWithError"/>
<!-- source: pattern://328e529ed345d17cacb4ec66 -->
<ResultCond name="link" next="Auth_Realm_Main_IDP_EId_Account_Linking_Mobile_NLess_Auth"/>
<!-- source: pattern://328e529ed345d17cacb4ec66 -->
<ResultCond name="register" next="Auth_Realm_Main_IDP_Eid_Placeholder"/>
<!-- source: pattern://328e529ed345d17cacb4ec66 -->
<AuthState name="Auth_Realm_Main_IDP_Eid_Placeholder" class="ch.nevis.esauth.auth.states.standard.AuthGeneric" final="false" resumeState="true">
<!-- source: pattern://47f8f6ef24f62431fbe1b530 -->
<Response value="AUTH_CONTINUE">
<!-- source: pattern://328e529ed345d17cacb4ec66 -->
<Gui name="eid_linking_account">
<!-- source: pattern://328e529ed345d17cacb4ec66 -->
<GuiElem name="authRequestId" type="hidden" value="${sess:ch.nevis.auth.saml.request.id}" optional="true"/>
<!-- source: pattern://328e529ed345d17cacb4ec66 -->
<GuiElem name="app_name" type="hidden" value="${sess:agov.appDisplayNameEN}" optional="true"/>
<!-- source: pattern://328e529ed345d17cacb4ec66 -->
<GuiElem name="firstname" type="hidden" value="${sess:agov.eid.User.firstName}" optional="true"/>
<!-- source: pattern://328e529ed345d17cacb4ec66 -->
<GuiElem name="lastname" type="hidden" value="${sess:agov.eid.User.lastName}" optional="true"/>
<!-- source: pattern://47f8f6ef24f62431fbe1b530 -->
<Gui name="Eid_Placeholder" label="eID Placeholder">
<!-- source: pattern://47f8f6ef24f62431fbe1b530 -->
<GuiElem name="infotext" type="info" label="${session:eid.placeholder.text}"/>
</Gui>
</Response>
<!-- source: pattern://328e529ed345d17cacb4ec66 -->
<property name="script" value="file:///var/opt/nevisauth/default/conf/eid_start_account_linking.groovy"/>
</AuthState>
<AuthState name="Auth_Realm_Main_IDP_EId_Fetch_IDM_Attributes" class="ch.nevis.idm.authstate.IdmUserVerifyState" final="false" resumeState="false">
<!-- source: pattern://b8bdab6e4634a1d81f20e5bb -->
@ -1218,100 +1151,6 @@
<Arg name="ch.nevis.isiweb4.response.status" value="403"/>
</Response>
</AuthState>
<AuthState name="Auth_Realm_Main_IDP_Auth_Realm_Main_IDP_Custom_AGOV_IDP_SEC_post" class="ch.nevis.esauth.auth.states.saml.IdentityProviderState" final="false" resumeState="true">
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<ResultCond name="IDP-initiated-ConcurrentLogout" next="Auth_Realm_Main_IDP_Auth_Realm_Main_IDP_Custom_Concurrent_Logout"/>
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<ResultCond name="IDP-initiated-SingleLogout" next="Auth_Realm_Main_IDP_Auth_Realm_Main_IDP_Custom_Prepare_Done"/>
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<ResultCond name="LogoutCompleted" next="Auth_Realm_Main_IDP_Auth_Realm_Main_IDP_Custom_Logout_Done"/>
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<ResultCond name="LogoutFailed" next="Auth_Realm_Main_IDP_Auth_Realm_Main_IDP_Custom_Logout_Fail"/>
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<ResultCond name="SP-initiated-ConcurrentLogout" next="Auth_Realm_Main_IDP_Auth_Realm_Main_IDP_Custom_Concurrent_Logout"/>
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<ResultCond name="SP-initiated-SingleLogout" next="Auth_Realm_Main_IDP_Auth_Realm_Main_IDP_Custom_Prepare_Done"/>
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<ResultCond name="authenticate:IDP-initiated-SSO" next="Auth_Realm_Main_IDP_RequestedRoleLevel"/>
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<ResultCond name="authenticate:SP-initiated-SSO" next="Auth_Realm_Main_IDP_RequestedRoleLevel"/>
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<ResultCond name="invalidAssertionConsumerUrl" next="Auth_Realm_Main_IDP_Auth_Realm_Main_IDP_Custom_AGOV_IDP_SEC"/>
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<ResultCond name="ok" next="Auth_Realm_Main_IDP_Auth_Realm_Main_IDP_Custom_Prepare_Done"/>
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<ResultCond name="stepup:IDP-initiated-SSO" next="Auth_Realm_Main_IDP_Auth_Realm_Main_IDP_Custom_Selector"/>
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<ResultCond name="stepup:SP-initiated-SSO" next="Auth_Realm_Main_IDP_Auth_Realm_Main_IDP_Custom_Selector"/>
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<Response value="AUTH_ERROR">
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<Gui name="saml_idp" label="title.saml.failed">
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<GuiElem name="lasterror" type="error" label="error.saml.failed"/>
</Gui>
</Response>
<propertyRef name="Auth_Realm_Main_IDP_Auth_Realm_Main_IDP_Custom_AGOV_IDP"/>
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<property name="out.issuer" value="https://auth.agov-w.azure.adnovum.net/SAML2SEC/"/>
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<property name="out.binding" value="http-post"/>
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<property name="out.post.relayStateEncoding" value="HTML"/>
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<property name="out.encrypt" value="none"/>
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<property name="out.encrypt.keystoreref" value="EncryptionKeys"/>
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<property name="out.encrypt.keyobjectref" value="DefaultEncryptionKey"/>
</AuthState>
<AuthState name="Auth_Realm_Main_IDP_Auth_Realm_Main_IDP_Custom_AGOV_IDP_SEC_artifact" class="ch.nevis.esauth.auth.states.saml.IdentityProviderState" final="false" resumeState="true">
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<ResultCond name="IDP-initiated-ConcurrentLogout" next="Auth_Realm_Main_IDP_Auth_Realm_Main_IDP_Custom_Concurrent_Logout"/>
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<ResultCond name="IDP-initiated-SingleLogout" next="Auth_Realm_Main_IDP_Auth_Realm_Main_IDP_Custom_Prepare_Done"/>
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<ResultCond name="LogoutCompleted" next="Auth_Realm_Main_IDP_Auth_Realm_Main_IDP_Custom_Logout_Done"/>
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<ResultCond name="LogoutFailed" next="Auth_Realm_Main_IDP_Auth_Realm_Main_IDP_Custom_Logout_Fail"/>
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<ResultCond name="SP-initiated-ConcurrentLogout" next="Auth_Realm_Main_IDP_Auth_Realm_Main_IDP_Custom_Concurrent_Logout"/>
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<ResultCond name="SP-initiated-SingleLogout" next="Auth_Realm_Main_IDP_Auth_Realm_Main_IDP_Custom_Prepare_Done"/>
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<ResultCond name="authenticate:IDP-initiated-SSO" next="Auth_Realm_Main_IDP_RequestedRoleLevel"/>
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<ResultCond name="authenticate:SP-initiated-SSO" next="Auth_Realm_Main_IDP_RequestedRoleLevel"/>
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<ResultCond name="invalidAssertionConsumerUrl" next="Auth_Realm_Main_IDP_Auth_Realm_Main_IDP_Custom_AGOV_IDP_SEC"/>
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<ResultCond name="ok" next="Auth_Realm_Main_IDP_Auth_Realm_Main_IDP_Custom_Prepare_Done"/>
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<ResultCond name="stepup:IDP-initiated-SSO" next="Auth_Realm_Main_IDP_Auth_Realm_Main_IDP_Custom_Selector"/>
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<ResultCond name="stepup:SP-initiated-SSO" next="Auth_Realm_Main_IDP_Auth_Realm_Main_IDP_Custom_Selector"/>
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<Response value="AUTH_ERROR">
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<Gui name="saml_idp" label="title.saml.failed">
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<GuiElem name="lasterror" type="error" label="error.saml.failed"/>
</Gui>
</Response>
<propertyRef name="Auth_Realm_Main_IDP_Auth_Realm_Main_IDP_Custom_AGOV_IDP"/>
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<property name="out.issuer" value="https://auth.agov-w.azure.adnovum.net/SAML2SEC/"/>
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<property name="out.binding" value="http-artifact"/>
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<property name="out.post.relayStateEncoding" value="HTML"/>
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<property name="out.encrypt" value="none"/>
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<property name="out.encrypt.keystoreref" value="EncryptionKeys"/>
<!-- source: pattern://bb9e7806a04578e0ad468829 -->
<property name="out.encrypt.keyobjectref" value="DefaultEncryptionKey"/>
</AuthState>
<AuthState name="Auth_Realm_Main_IDP_Fido_Email_Verify" class="ch.nevis.idm.authstate.IdmUserVerifyState" final="false" resumeState="false">
<!-- source: pattern://7fb39bfd6c34685866a22180 -->
<ResultCond name="clientNotFound" next="Auth_Realm_Main_IDP_AuthnFailed_Client_NotFound"/>
@ -1425,48 +1264,6 @@
<!-- source: pattern://e0fda9336be9c69dafc9b69e -->
<property name="admin.service.connection.0" value="https://idm.adn-agov-nevisidm-admin-01-uat:8989/nevisidm/services/v1/AdminService"/>
</AuthState>
<AuthState name="Auth_Realm_Main_IDP_EId_Account_Linking_Mobile_NLess_Auth" class="ch.nevis.esauth.auth.states.scripting.ScriptState" final="false" resumeState="true">
<!-- source: pattern://da38e049a1ff97663fb30a45 -->
<ResultCond name="back" next="Auth_Realm_Main_IDP_SendSamlResponseWithError"/>
<!-- source: pattern://da38e049a1ff97663fb30a45 -->
<ResultCond name="default" next="Auth_Realm_Main_IDP_EId_Account_Linking_Mobile_NLess_Auth"/>
<!-- source: pattern://da38e049a1ff97663fb30a45 -->
<ResultCond name="fido2" next="Auth_Realm_Main_IDP_Eid_Placeholder"/>
<!-- source: pattern://da38e049a1ff97663fb30a45 -->
<ResultCond name="ok" next="Auth_Realm_Main_IDP_EId_Account_Linking_Mobile_NLess_Auth_Processing"/>
<!-- source: pattern://da38e049a1ff97663fb30a45 -->
<ResultCond name="registration" next="Auth_Realm_Main_IDP_Eid_Placeholder"/>
<!-- source: pattern://da38e049a1ff97663fb30a45 -->
<Response value="AUTH_CONTINUE">
<!-- source: pattern://da38e049a1ff97663fb30a45 -->
<Gui name="eid_linking_mauth_usernameless">
<!-- source: pattern://da38e049a1ff97663fb30a45 -->
<GuiElem name="authRequestId" type="hidden" value="${sess:ch.nevis.auth.saml.request.id}" optional="true"/>
<!-- source: pattern://da38e049a1ff97663fb30a45 -->
<GuiElem name="fallback" type="button" label="mobile_auth.cancel.button.label" value="true" optional="true"/>
<!-- source: pattern://da38e049a1ff97663fb30a45 -->
<GuiElem name="accessApp" type="hidden" value="${sess:agov.recovery.accessapp}" optional="true"/>
</Gui>
</Response>
<!-- source: pattern://da38e049a1ff97663fb30a45 -->
<property name="scriptTraceGroup" value="AGOV-ACCT"/>
<!-- source: pattern://da38e049a1ff97663fb30a45 -->
<property name="script" value="file:///var/opt/nevisauth/default/conf/eid_account_linking_mobile_nless_auth.groovy"/>
<!-- source: pattern://da38e049a1ff97663fb30a45 -->
<property name="parameter.agovmeregistrationurl" value="https://ob.agov-w.azure.adnovum.net/mock-me/registration"/>
<!-- source: pattern://da38e049a1ff97663fb30a45 -->
<property name="parameter.recoveryurl" value="https://auth.agov-w.azure.adnovum.net/AUTH/RECOVERY/"/>
</AuthState>
<AuthState name="Auth_Realm_Main_IDP_Eid_Placeholder" class="ch.nevis.esauth.auth.states.standard.AuthGeneric" final="false" resumeState="true">
<!-- source: pattern://47f8f6ef24f62431fbe1b530 -->
<Response value="AUTH_CONTINUE">
<!-- source: pattern://47f8f6ef24f62431fbe1b530 -->
<Gui name="Eid_Placeholder" label="eID Placeholder">
<!-- source: pattern://47f8f6ef24f62431fbe1b530 -->
<GuiElem name="infotext" type="info" label="${session:eid.placeholder.text}"/>
</Gui>
</Response>
</AuthState>
<AuthState name="Auth_Realm_Main_IDP_EId_Fetch_IDM_Attributes_getProperties" class="ch.nevis.idm.authstate.IdmGetPropertiesState" final="false" resumeState="false">
<!-- source: pattern://b8bdab6e4634a1d81f20e5bb -->
<ResultCond name="clientNotFound" next="Auth_Realm_Main_IDP_Authentication_Failed"/>
@ -1515,14 +1312,14 @@
<property name="detaillevel.default" value="EXCLUDE"/>
</AuthState>
<AuthState name="Auth_Realm_Main_IDP_Prepare_Done" class="ch.nevis.esauth.auth.states.scripting.ScriptState" final="false">
<!-- source: pattern://6061abea33a234fad73897b7, pattern://359792ce61c28c723ab7d354, pattern://4fcfadb4a5c946ead7e6e995 -->
<!-- source: pattern://6061abea33a234fad73897b7, pattern://4fcfadb4a5c946ead7e6e995 -->
<ResultCond name="default" next="Auth_Realm_Main_IDP_Auth_Done"/>
<!-- source: pattern://6061abea33a234fad73897b7, pattern://359792ce61c28c723ab7d354, pattern://4fcfadb4a5c946ead7e6e995 -->
<!-- source: pattern://6061abea33a234fad73897b7, pattern://4fcfadb4a5c946ead7e6e995 -->
<Response value="AUTH_DONE">
<!-- source: pattern://6061abea33a234fad73897b7, pattern://359792ce61c28c723ab7d354, pattern://4fcfadb4a5c946ead7e6e995 -->
<!-- source: pattern://6061abea33a234fad73897b7, pattern://4fcfadb4a5c946ead7e6e995 -->
<Gui name="ContinueResponse"/>
</Response>
<!-- source: pattern://6061abea33a234fad73897b7, pattern://359792ce61c28c723ab7d354, pattern://4fcfadb4a5c946ead7e6e995 -->
<!-- source: pattern://6061abea33a234fad73897b7, pattern://4fcfadb4a5c946ead7e6e995 -->
<property name="script" value="file:///var/opt/nevisauth/default/conf/prepare_done.groovy"/>
</AuthState>
<AuthState name="Auth_Realm_Main_IDP_Auth_Realm_Main_IDP_Custom_Authorization" class="ch.nevis.esauth.auth.states.scripting.ScriptState" final="false" resumeState="true">
@ -1627,27 +1424,6 @@
<!-- source: pattern://f393012a278e525956a362d3 -->
<property name="parameter.idm.httpclient.tls.trustStoreRef" value="Ensure_Account_State"/>
</AuthState>
<AuthState name="Auth_Realm_Main_IDP_EId_Account_Linking_Mobile_NLess_Auth_Processing" class="ch.nevis.auth.fido.uaf.authstate.OutOfBandFidoUafAuthState" final="false" resumeState="false">
<!-- source: pattern://da38e049a1ff97663fb30a45 -->
<ResultCond name="dispatchFailed" next="Auth_Realm_Main_IDP_EId_Account_Linking_Mobile_NLess_Auth_Processing"/>
<!-- source: pattern://da38e049a1ff97663fb30a45 -->
<ResultCond name="error" next="Auth_Realm_Main_IDP_EId_Account_Linking_Mobile_NLess_Auth_Processing"/>
<!-- source: pattern://da38e049a1ff97663fb30a45 -->
<ResultCond name="failed" next="Auth_Realm_Main_IDP_EId_Account_Linking_Mobile_NLess_Auth"/>
<!-- source: pattern://da38e049a1ff97663fb30a45 -->
<ResultCond name="ok" next="Auth_Realm_Main_IDP_EId_Account_Linking_Mobile_NLess_Auth_PostProcessing" authLevel="2"/>
<!-- source: pattern://da38e049a1ff97663fb30a45 -->
<Response value="AUTH_ERROR">
<!-- source: pattern://da38e049a1ff97663fb30a45 -->
<Arg name="ch.nevis.isiweb4.response.status" value="403"/>
</Response>
<!-- source: pattern://da38e049a1ff97663fb30a45 -->
<property name="fidoUafServerUrl" value="https://fido-uaf:9443/nevisfido"/>
<!-- source: pattern://da38e049a1ff97663fb30a45 -->
<property name="dispatcher" value="link"/>
<!-- source: pattern://da38e049a1ff97663fb30a45 -->
<property name="httpclient.tls.trustStoreRef" value="EId_Account_Linking_Mobile_NLess_Auth"/>
</AuthState>
<AuthState name="Auth_Realm_Main_IDP_EId_Compare_And_Update_IDM_Attributes" class="ch.nevis.esauth.auth.states.scripting.ScriptState" final="false" resumeState="true">
<!-- source: pattern://306ce091fd87bad6174d9e8b -->
<ResultCond name="audited" next="Auth_Realm_Main_IDP_EId_Compare_And_Update_IDM_Attributes_NotifyUser"/>
@ -1665,9 +1441,9 @@
<property name="parameter.idm.httpclient.tls.trustStoreRef" value="EId_Compare_And_Update_IDM_Attributes"/>
</AuthState>
<AuthState name="Auth_Realm_Main_IDP_Auth_Done" class="ch.nevis.esauth.auth.states.standard.AuthDone" final="false">
<!-- source: pattern://6061abea33a234fad73897b7, pattern://359792ce61c28c723ab7d354, pattern://4fcfadb4a5c946ead7e6e995 -->
<!-- source: pattern://6061abea33a234fad73897b7, pattern://4fcfadb4a5c946ead7e6e995 -->
<Response value="AUTH_DONE">
<!-- source: pattern://6061abea33a234fad73897b7, pattern://359792ce61c28c723ab7d354, pattern://4fcfadb4a5c946ead7e6e995 -->
<!-- source: pattern://6061abea33a234fad73897b7, pattern://4fcfadb4a5c946ead7e6e995 -->
<Gui name="ContinueResponse"/>
</Response>
</AuthState>
@ -1772,14 +1548,6 @@
<!-- source: pattern://f393012a278e525956a362d3 -->
<property name="detaillevel.default" value="EXCLUDE"/>
</AuthState>
<AuthState name="Auth_Realm_Main_IDP_EId_Account_Linking_Mobile_NLess_Auth_PostProcessing" class="ch.nevis.esauth.auth.states.standard.TransformAttributes" final="false" resumeState="false">
<!-- source: pattern://da38e049a1ff97663fb30a45 -->
<ResultCond name="default" next="Auth_Realm_Main_IDP_EId_Account_Linking_Redirect_To_Agovme"/>
<!-- source: pattern://da38e049a1ff97663fb30a45 -->
<Response value="AUTH_CONTINUE"/>
<!-- source: pattern://da38e049a1ff97663fb30a45 -->
<property name="sess:eid.placeholder.text" value="EId: Redirection to AGOV me not implemented yet"/>
</AuthState>
<AuthState name="Auth_Realm_Main_IDP_EId_Compare_And_Update_IDM_Attributes_NotifyUser" class="ch.nevis.esauth.auth.states.scripting.ScriptState" final="false" resumeState="true">
<!-- source: pattern://306ce091fd87bad6174d9e8b -->
<ResultCond name="error" next="Auth_Realm_Main_IDP_SendSamlResponseWithError"/>
@ -1848,82 +1616,6 @@
<!-- source: pattern://9ff0369f3cf662f95d94ff09 -->
<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="Auth_Realm_Main_IDP_EId_Account_Linking_Redirect_To_Agovme" class="ch.nevis.esauth.auth.states.saml.IdentityProviderState" final="false" resumeState="false">
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<ResultCond name="ok" next="Auth_Realm_Main_IDP_EId_Account_Linking_Redirect_To_Agovme_Handle_Redirect"/>
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<Response value="AUTH_ERROR">
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<Gui name="internal_error">
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<GuiElem name="transferId" type="hidden" value="${request:traceId}" optional="true"/>
</Gui>
</Response>
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<property name="in.binding" value="none"/>
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<property name="out.binding" value="internal"/>
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<property name="out.sign" value="Response Assertion"/>
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<property name="out.signatureKeyInfo" value="Certificate"/>
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<property name="out.ttl" value="20"/>
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<property name="out.extension.Bearer" value="ch.nevis.esauth.auth.states.saml.extensions.SubjectConfirmationExtender"/>
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<property name="Bearer.ttl" value="20"/>
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<property name="out.keystoreref" value="Store_IDP_AGOV"/>
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<property name="out.keyobjectref" value="Signer_IDP_AGOV"/>
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<property name="spURL" value="https://ob.agov-w.azure.adnovum.net/mock-me/process"/>
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<property name="acsUrlWhitelist.uris" value="not used"/>
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<property name="out.attribute.http://schemas.agov.ch/ws/2023/05/identity/claims/authenticatedWith" value="${sess:agov.authenticatedWith}"/>
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<property name="out.attribute.http://schemas.agov.ch/ws/2023/05/identity/claims/requestedRoleLevel" value="${sess:agov.requestedRoleLevel}"/>
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<property name="out.attribute.http://schemas.agov.ch/ws/2023/09/identity/claim/rpEntityId" value="${sess:agov.rpEntityId}"/>
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<property name="out.attribute.http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname" value="${sess:agov.eid.User.firstName}"/>
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<property name="out.attribute.http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname" value="${sess:agov.eid.User.lastName}"/>
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<property name="out.attribute.http://schemas.agov.ch/ws/2023/05/identity/claims/socialSecurityNumber" value="${sess:agov.eid.User.svnr}"/>
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<property name="out.attribute.http://schemas.agov.ch/ws/2023/05/identity/claims/placeOfBirth" value="${sess:agov.eid.User.placeOfBirth}"/>
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<property name="out.attribute.http://schemas.agov.ch/ws/2025/07/identity/claims/placeOfOrigin" value="${sess:agov.eid.User.placeOfOrigin}"/>
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<property name="out.attribute.http://schemas.agov.ch/ws/2023/05/identity/claims/dateOfBirth" value="${sess:agov.eid.User.birthDate}"/>
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<property name="out.attribute.http://schemas.agov.ch/ws/2023/05/identity/claims/nationality" value="${sess:agov.eid.User.nationality}"/>
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<property name="out.attribute.http://schemas.agov.ch/ws/2023/05/identity/claims/eIdNumber" value="${sess:agov.eid.User.eIdNumber}"/>
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<property name="out.attribute.http://schemas.agov.ch/ws/2023/05/identity/claims/sex" value="${sess:agov.eid.User.gender}"/>
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<property name="out.attribute.http://schemas.agov.ch/ws/2023/05/identity/claims/qa/dateOfVerification" value="${sess:ValidFrom}"/>
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<property name="out.attribute.http://schemas.agov.ch/ws/2023/05/identity/claims/qa/validTillDate" value="${sess:ValidTo}"/>
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<property name="out.attribute.http://schemas.agov.ch/ws/2025/07/identity/claims/op/conversationId" value="${inctx:connection.HttpHeader.traceparent:^([0-9a-f]+)-([0-9a-f]+)-([0-9a-f]+)-([0-9a-f]+)$:$2}"/>
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<property name="out.authnContextClassRef" value="${sess:agov.authnContextClassRef}"/>
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<property name="out.subject" value="${sess:ch.adnovum.nevisidm.user.extId}"/>
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<property name="out.subject.format" value="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"/>
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<property name="out.issuer" value="https://auth.agov-w.azure.adnovum.net/SAML2/"/>
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<property name="out.attributeDelimiter" value=",\s*"/>
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<property name="out.audienceRestriction" value="https://ob.agov-w.azure.adnovum.net/mock-me/process"/>
</AuthState>
<AuthState name="Auth_Realm_Main_IDP_CheckLoa" class="ch.nevis.esauth.auth.states.scripting.ScriptState" final="false">
<!-- source: pattern://2cdd910036aa06b102863a4f -->
<ResultCond name="error" next="Auth_Realm_Main_IDP_AuthnFailed_Zero_RoleLvl"/>
@ -1996,21 +1688,6 @@
<!-- source: pattern://9ff0369f3cf662f95d94ff09 -->
<property name="parameter.cookie.domain" value="auth.agov-w.azure.adnovum.net"/>
</AuthState>
<AuthState name="Auth_Realm_Main_IDP_EId_Account_Linking_Redirect_To_Agovme_Handle_Redirect" class="ch.nevis.esauth.auth.states.scripting.ScriptState" final="false" resumeState="true">
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<ResultCond name="ok" next="Auth_Realm_Main_IDP_Prepare_Done"/>
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<Response value="AUTH_CONTINUE">
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<Gui name="not_used"/>
</Response>
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<property name="scriptTraceGroup" value="AGOV-ACCT"/>
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<property name="parameter.agovmedirecturl" value="https://ob.agov-w.azure.adnovum.net/mock-me/process"/>
<!-- source: pattern://359792ce61c28c723ab7d354 -->
<property name="script" value="file:///var/opt/nevisauth/default/conf/eid_account_linking_redirect_to_agovme.groovy"/>
</AuthState>
<AuthState name="Auth_Realm_Main_IDP_AuthnFailed_Zero_RoleLvl" class="ch.nevis.esauth.auth.states.standard.TransformAttributes" final="false">
<!-- source: pattern://50b861438e79c2332862d3ca -->
<ResultCond name="ok" next="Auth_Realm_Main_IDP_SendSamlResponseWithError"/>
@ -3406,6 +3083,8 @@
<!-- source: pattern://e0fda9336be9c69dafc9b69e -->
<ResultCond name="SOAP:showGui" next="NotUsed_Auth_Realm_Prepare_Done"/>
<!-- source: pattern://e0fda9336be9c69dafc9b69e -->
<ResultCond name="default" next="NotUsed_Auth_Realm_Prepare_Done"/>
<!-- source: pattern://e0fda9336be9c69dafc9b69e -->
<ResultCond name="ok" next="NotUsed_Auth_Realm_Prepare_Done" startOver="true"/>
<!-- source: pattern://e0fda9336be9c69dafc9b69e -->
<ResultCond name="showGui" next="NotUsed_Auth_Realm_NotUsed_Pwd_Login-IdmPostProcessing"/>
@ -3424,12 +3103,6 @@
<property name="detaillevel.default" value="EXCLUDE"/>
<!-- source: pattern://e0fda9336be9c69dafc9b69e -->
<property name="detaillevel.user" value="MEDIUM"/>
<!-- source: pattern://e0fda9336be9c69dafc9b69e -->
<property name="detaillevel.profile" value="MEDIUM"/>
<!-- source: pattern://e0fda9336be9c69dafc9b69e -->
<property name="detaillevel.role" value="LOW"/>
<!-- source: pattern://e0fda9336be9c69dafc9b69e -->
<property name="forceDataReload" value="true"/>
</AuthState>
<AuthState name="NotUsed_Auth_Realm_NotUsed_Pwd_Login-IdmPasswordChange" class="ch.nevis.idm.authstate.IdmChangePasswordState" final="false">
<!-- source: pattern://e0fda9336be9c69dafc9b69e -->
@ -3507,7 +3180,7 @@
<!-- source: pattern://e0fda9336be9c69dafc9b69e -->
<GuiElem name="isiwebnewpw2" type="pw-text" label="prompt.newpassword.confirm"/>
<!-- source: pattern://e0fda9336be9c69dafc9b69e -->
<GuiElem name="submit" type="submit" label="submit.button.label"/>
<GuiElem name="submit" type="submit" label="button.submit"/>
</Gui>
</Response>
<propertyRef name="nevisIDM_Connector"/>
@ -3570,21 +3243,4 @@
<!-- source: pattern://ab5a82719993921822e95751 -->
<property name="out.keyobjectref" value="Signer_IDP_AGOV"/>
</WebService>
<!-- source: pattern://14efdcb489f3f295fcbdf811 -->
<WebService name="IDP_AGOV_SEC_ARS" class="ch.nevis.esauth.auth.adapter.saml.ArtifactResolutionService" uri="/nevisauth/services/ars/sec" SSODomain="Auth_Realm_Main_IDP">
<!-- source: pattern://14efdcb489f3f295fcbdf811 -->
<property name="issuer" value="https://auth.agov-w.azure.adnovum.net/SAML2SEC/"/>
<!-- source: pattern://14efdcb489f3f295fcbdf811 -->
<property name="out.keystoreref" value="Store_IDP_AGOV"/>
<!-- source: pattern://14efdcb489f3f295fcbdf811 -->
<property name="out.keyobjectref" value="Signer_IDP_AGOV"/>
<!-- source: pattern://14efdcb489f3f295fcbdf811 -->
<property name="in.keystoreref" value="Store_IDP_AGOV"/>
<!-- source: pattern://14efdcb489f3f295fcbdf811 -->
<property name="in.verify" value="ArtifactResolve"/>
<!-- source: pattern://14efdcb489f3f295fcbdf811 -->
<property name="in.prospectVerification" value=""/>
</WebService>
<!-- source: pattern://7022472ae407577ae604bbb8 -->
<RESTService name="ManagementService" class="ch.nevis.esauth.rest.service.session.ManagementService"/>
</esauth-server>

View File

@ -1,39 +1,39 @@
import groovy.json.JsonSlurper
import io.opentelemetry.api.trace.Span
def sess = request.getAuthSession(true)
def spanCtxt = Span.current().getSpanContext()
def traceparent = "00-${spanCtxt.getTraceId()}-${spanCtxt.getSpanId()}-${spanCtxt.getTraceFlags().asHex()}"
def jsonSlurper = new JsonSlurper()
def lang = (session['ch.nevis.idm.User.language']?:'DE').trim()
def endppoint = "${parameters.get('baseurl')}/api/v1/countries?lang=${lang.toUpperCase()}"
def countryCode = (session['ch.nevis.idm.User.country']?:'CH').trim().toLowerCase()
try {
LOG.debug("UTILITY: Countries: Request url: ${endppoint}")
def httpClient = HttpClients.create(parameters)
def httpResponse = Http.get().url(endppoint).header('traceparent', traceparent).build().send(httpClient)
LOG.debug('UTILITY: Countries: Response Message: ' + httpResponse.reasonPhrase())
LOG.debug('UTILITY: Countries: Response Status Code: ' + httpResponse.code())
LOG.debug('UTILITY: Countries: Response: ' + httpResponse.bodyAsString())
if (httpResponse.code() == 200) {
def json = jsonSlurper.parseText(httpResponse.bodyAsString())
// {"country.af":"Afghanistan","country.al":"Albanie"... }
def countryName = json["country.${countryCode}"]
LOG.debug("UTILITY: Countries: countryName for ${countryCode}: ${countryName}")
if (countryName) {
sess.setAttribute('agov.countryName', countryName)
}
} else {
LOG.warn("UTILITY: Countries: Failed to fetch country translations. (httpResponse.code: ${httpResponse.code()})")
}
} catch (Exception e) {
LOG.warn("UTILITY: Countries: Failed to fetch country translations. (${e})")
}
import groovy.json.JsonSlurper
import io.opentelemetry.api.trace.Span
def sess = request.getAuthSession(true)
def spanCtxt = Span.current().getSpanContext()
def traceparent = "00-${spanCtxt.getTraceId()}-${spanCtxt.getSpanId()}-${spanCtxt.getTraceFlags().asHex()}"
def jsonSlurper = new JsonSlurper()
def lang = (session['ch.nevis.idm.User.language']?:'DE').trim()
def endppoint = "${parameters.get('baseurl')}/api/v1/countries?lang=${lang.toUpperCase()}"
def countryCode = (session['ch.nevis.idm.User.country']?:'CH').trim().toLowerCase()
try {
LOG.debug("UTILITY: Countries: Request url: ${endppoint}")
def httpClient = HttpClients.create(parameters)
def httpResponse = Http.get().url(endppoint).header('traceparent', traceparent).build().send(httpClient)
LOG.debug('UTILITY: Countries: Response Message: ' + httpResponse.reasonPhrase())
LOG.debug('UTILITY: Countries: Response Status Code: ' + httpResponse.code())
LOG.debug('UTILITY: Countries: Response: ' + httpResponse.bodyAsString())
if (httpResponse.code() == 200) {
def json = jsonSlurper.parseText(httpResponse.bodyAsString())
// {"country.af":"Afghanistan","country.al":"Albanie"... }
def countryName = json["country.${countryCode}"]
LOG.debug("UTILITY: Countries: countryName for ${countryCode}: ${countryName}")
if (countryName) {
sess.setAttribute('agov.countryName', countryName)
}
} else {
LOG.warn("UTILITY: Countries: Failed to fetch country translations. (httpResponse.code: ${httpResponse.code()})")
}
} catch (Exception e) {
LOG.warn("UTILITY: Countries: Failed to fetch country translations. (${e})")
}
response.setResult('ok')

View File

@ -1,38 +1,38 @@
import groovy.json.JsonSlurper
import io.opentelemetry.api.trace.Span
def url = parameters.get('url')
def realIpHttpHeaderName = parameters.get('realIpHttpHeaderName') ?: 'X-Real-IP'
def ip = request.getLoginContext()['connection.HttpHeader.X-Real-IP'] ?: 'unknown'
try {
def spanCtxt = Span.current().getSpanContext()
def traceparent = "00-${spanCtxt.getTraceId()}-${spanCtxt.getSpanId()}-${spanCtxt.getTraceFlags().asHex()}"
def jsonSlurper = new JsonSlurper()
def httpClient = HttpClients.create(parameters)
def httpResponse = Http.get().url(url).header('traceparent', traceparent)
.header(realIpHttpHeaderName, ip).build().send(httpClient)
LOG.debug('Response Status Code: ' + httpResponse.code())
LOG.debug('Response: ' + httpResponse.bodyAsString())
if (httpResponse.code() == 200) {
def json = jsonSlurper.parseText(httpResponse.bodyAsString())
response.setSessionAttribute('agov.fido2.captchaSettings.enabled', String.valueOf(json.friendlyCaptureClientSettings.enabled))
response.setSessionAttribute('agov.fido2.captchaSettings.siteKey', json.friendlyCaptureClientSettings.siteKey)
response.setSessionAttribute('agov.fido2.captchaSettings.puzzleUrl', json.friendlyCaptureClientSettings.puzzleUrl)
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')
import groovy.json.JsonSlurper
import io.opentelemetry.api.trace.Span
def url = parameters.get('url')
def realIpHttpHeaderName = parameters.get('realIpHttpHeaderName') ?: 'X-Real-IP'
def ip = request.getLoginContext()['connection.HttpHeader.X-Real-IP'] ?: 'unknown'
try {
def spanCtxt = Span.current().getSpanContext()
def traceparent = "00-${spanCtxt.getTraceId()}-${spanCtxt.getSpanId()}-${spanCtxt.getTraceFlags().asHex()}"
def jsonSlurper = new JsonSlurper()
def httpClient = HttpClients.create(parameters)
def httpResponse = Http.get().url(url).header('traceparent', traceparent)
.header(realIpHttpHeaderName, ip).build().send(httpClient)
LOG.debug('Response Status Code: ' + httpResponse.code())
LOG.debug('Response: ' + httpResponse.bodyAsString())
if (httpResponse.code() == 200) {
def json = jsonSlurper.parseText(httpResponse.bodyAsString())
response.setSessionAttribute('agov.fido2.captchaSettings.enabled', String.valueOf(json.friendlyCaptureClientSettings.enabled))
response.setSessionAttribute('agov.fido2.captchaSettings.siteKey', json.friendlyCaptureClientSettings.siteKey)
response.setSessionAttribute('agov.fido2.captchaSettings.puzzleUrl', json.friendlyCaptureClientSettings.puzzleUrl)
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

@ -1,63 +1,63 @@
import io.opentelemetry.api.trace.Span
def url = parameters.get('url')
def email = inargs['userInputValue_prompt.email']
def token = inargs['captcha_response']?: 'MISSING'
def enabled = (session['agov.fido2.captchaSettings.enabled']?:'true').toBoolean()
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 {
if (!enabled) {
LOG.info("FriendlyCAPTCHA is disabled, allowing operation for ${payload}")
response.setResult('ok')
return
}
def spanCtxt = Span.current().getSpanContext()
def traceparent = "00-${spanCtxt.getTraceId()}-${spanCtxt.getSpanId()}-${spanCtxt.getTraceFlags().asHex()}"
def httpClient = HttpClients.create(parameters)
def httpResponse = Http.post()
.url(url)
.header("Accept", "application/json")
.header("X-FriendlyCAPTCHA-Token", token)
.header("traceparent", traceparent)
.entity(Http.entity()
.content(payload)
.contentType("application/json")
.build())
.build()
.send(httpClient)
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')
}
import io.opentelemetry.api.trace.Span
def url = parameters.get('url')
def email = inargs['userInputValue_prompt.email']
def token = inargs['captcha_response']?: 'MISSING'
def enabled = (session['agov.fido2.captchaSettings.enabled']?:'true').toBoolean()
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 {
if (!enabled) {
LOG.info("FriendlyCAPTCHA is disabled, allowing operation for ${payload}")
response.setResult('ok')
return
}
def spanCtxt = Span.current().getSpanContext()
def traceparent = "00-${spanCtxt.getTraceId()}-${spanCtxt.getSpanId()}-${spanCtxt.getTraceFlags().asHex()}"
def httpClient = HttpClients.create(parameters)
def httpResponse = Http.post()
.url(url)
.header("Accept", "application/json")
.header("X-FriendlyCAPTCHA-Token", token)
.header("traceparent", traceparent)
.entity(Http.entity()
.content(payload)
.contentType("application/json")
.build())
.build()
.send(httpClient)
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')
}

View File

@ -20,4 +20,4 @@ if(outargs.containsKey('saml.SAMLResponse')) {
}
else {
response.setResult('ok')
}
}

View File

@ -1,197 +1,168 @@
import groovy.xml.XmlSlurper
import groovy.xml.slurpersupport.GPathResult
import groovy.xml.slurpersupport.NodeChild
import java.util.zip.Inflater
import java.util.zip.InflaterInputStream
/**
* Gets the value of the Referer header.
* If the header is missing the fallback is returned
*
* This method is used when SAML IDP / Dispatch Error Redirect is not set
*
* @param fallback - value to return if the Referer header is missing
* @return value of header or fallback
*/
def getReferer(String fallback) {
return request.getHttpHeader('Referer') ?: fallback
}
def redirect(String url) {
outargs.put('nevis.transfer.type', 'redirect')
outargs.put('nevis.transfer.destination', url)
}
String getNormalisedSamlMessage(String parameter) {
if (parameter == null) {
return
}
String text
byte[] decoded
// if parameter is raw xml then continue otherwise try to parse the base64 encoding
if (parameter.startsWith("<")) {
text = new String(parameter)
}
else {
decoded = parameter.decodeBase64()
text = new String(decoded)
}
return text
}
String getNodeText(GPathResult xml, String nodeName) {
return xml.depthFirst().find { GPathResult node -> {
node.name().endsWith(":${nodeName}") || node.name().equalsIgnoreCase(nodeName)
}
}?.text()?.trim()
}
String getAttribute(GPathResult xml, String attributeName) {
return xml.depthFirst().find { GPathResult node -> {
node.attributes().containsKey(attributeName)
}
}?.attributes()?.get(attributeName)
}
String getNodeText(String parameter, String nodeName) {
String samlMessage = getNormalisedSamlMessage(parameter)
if (samlMessage == null) {
return
}
def parser = new XmlSlurper()
def xml = parser.parseText(samlMessage)
return getNodeText(xml, nodeName)
}
String getAttribute(String parameter, String attributeName) {
String samlMessage = getNormalisedSamlMessage(parameter)
if (samlMessage == null) {
return
}
def parser = new XmlSlurper()
def xml = parser.parseText(samlMessage)
return getAttribute(xml, attributeName)
}
String getIssuer(String value) {
return getNodeText(value, 'Issuer')
}
String getAttributeConsumingServiceIndex(String value) {
return getAttribute(value, 'AttributeConsumingServiceIndex')
}
String getProtocolBinding(String value) {
return getAttribute(value, 'ProtocolBinding')
}
def dispatchIssuer(i2s, String issuer, boolean secureMode) {
def result = i2s.get(issuer)
if (result == null) {
LOG.info("No SP found for issuer '$issuer'. Hint: check SAML SP Connector patterns.")
}
// dispatch different idp if artifact binding is enabled
if(parameters.get('epdMode') == 'artifact' && result == 'epd'){
LOG.debug("EPD: Artifact mode")
result = result + "_artifact"
} else if (result == 'main' && secureMode) {
LOG.debug("AGOV: Secure mode requested")
result = result + "_secure"
}
response.setResult(result)
session.put('saml.inbound.issuer', issuer)
session.put('saml.idp.result', result) // remember decision for sub-sequent requests without a SAML message
}
def dispatchIssuer(i2s, String issuer) {
dispatchIssuer(i2s, issuer, false)
}
def dispatchMessage(i2s, String message) {
def issuer = getIssuer(message)
def secureMode = (getAttributeConsumingServiceIndex(message) == '10101')
def useArtifact = ('urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact' == getProtocolBinding(message))
LOG.info("secureMode requested: ${secureMode}")
if (issuer == null) {
LOG.info("No issuer found in incoming SAML message. Giving up.")
}
session.put('saml.inbound.issuer', issuer)
session.put('agov.idp.use.artifact', '' + useArtifact)
dispatchIssuer(i2s, issuer, secureMode)
}
if (parameters.get('logoutConfirmation') == 'true' && "stepup" == request.getMethod()) {
String url = request.currentResource
def path = new URL(url).getPath()
if (path.endsWith("/logout")) {
// next AuthState will show a logout confirmation GUI
response.setResult('confirm')
return
}
}
// ensure session exists
if (request.getSession(false) == null) {
session = request.getSession(true).getData()
}
// issuer (any case) -> ResultCond name
def i2s = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER)
i2s.put(parameters.get('atb'), 'main')
i2s.put(parameters.get('epd_atb'), 'epd')
if (parameters.get('spInitiated') == 'true' && inargs.containsKey('SAMLRequest')) { // SP-initiated authentication
LOG.debug("found SAMLRequest parameter for SP-initiated authentication")
String message = inargs.get('SAMLRequest')
dispatchMessage(i2s, message)
return
}
if (inargs.containsKey('SAMLResponse')) { // response to IDP-initiated SAML Logout
LOG.debug("found SAMLResponse parameter")
String message = inargs.get('SAMLResponse')
dispatchMessage(i2s, message)
return
}
if (parameters.get('spInitiated') == 'true' && inargs.containsKey('soapheader')) { // SP-initiated SOAP with soapheader
LOG.debug("found soapheader parameter for SP-initiated")
String message = inargs.get('soapheader')
dispatchMessage(i2s, message)
return
}
if (parameters.get('spInitiated') == 'true' && inargs.containsKey('')) { // SP-initiated SOAP with empty
LOG.debug("found empty parameter for SP-initiated SOAP message")
String message = inargs.get('')
dispatchMessage(i2s, message)
return
}
String issuer = inargs['Issuer'] ?: inargs['issuer']
if (parameters.get('idpInitiated') == 'true' && issuer != null) { // IDP-initiated authentication
LOG.debug("found Issuer parameter for IDP-initiated authentication")
dispatchIssuer(i2s, issuer)
return
}
// used as fallback in case of ?logout (we need an IdentityProviderState)
if (inargs.containsKey("logout") && session.containsKey('saml.idp.result')) {
def result = session.get('saml.idp.result')
LOG.debug("dispatching to last used ResultCond: $result")
response.setResult(result)
return
}
def location = getReferer('/')
LOG.info("Unable to dispatch request. Giving up and redirecting (back) to $location")
import groovy.xml.XmlSlurper
import groovy.xml.slurpersupport.GPathResult
import groovy.xml.slurpersupport.NodeChild
import java.util.zip.Inflater
import java.util.zip.InflaterInputStream
/**
* Gets the value of the Referer header.
* If the header is missing the fallback is returned
*
* This method is used when SAML IDP / Dispatch Error Redirect is not set
*
* @param fallback - value to return if the Referer header is missing
* @return value of header or fallback
*/
def getReferer(String fallback) {
return request.getHttpHeader('Referer') ?: fallback
}
def redirect(String url) {
outargs.put('nevis.transfer.type', 'redirect')
outargs.put('nevis.transfer.destination', url)
}
/**
* Extracts the content of the Issuer element from a parsed SAML message.
* The Issuer is optional according to SAML specification but we need it for dispatching.
*
* @param xml - as parsed by Groovy XmlSlurper
* @return text content of Issuer element converted or null
*/
String getIssuer(GPathResult xml) {
return xml.depthFirst().find { GPathResult node -> {
node.name().endsWith(":Issuer") || node.name().equalsIgnoreCase("Issuer")
}
}?.text()
}
String getIssuer(String value) {
if (value == null) {
return
}
String text
byte[] decoded
def parser = new XmlSlurper()
// if value is raw xml then continue otherwise try to parse the base64 encoding
if (value.startsWith("<")) {
text = new String(value)
}
else {
decoded = value.decodeBase64()
text = new String(decoded)
LOG.info("received SAML request $value")
}
// after decoded, if redirect binding, we need to parse string to xml
if (text.startsWith("<")) {
LOG.debug("assuming POST/SOAP binding")
// plain String (POST/SOAP parameter)
def xml = parser.parseText(text)
return getIssuer(xml)
}
else {
LOG.debug("assuming redirect binding")
// should be deflate encoded (query parameter)
def is = new InflaterInputStream(new ByteArrayInputStream(decoded), new Inflater(true))
def xml = parser.parse(is)
return getIssuer(xml)
}
}
def dispatchIssuer(i2s, String issuer) {
def result = i2s.get(issuer)
if (result == null) {
LOG.info("No SP found for issuer '$issuer'. Hint: check SAML SP Connector patterns.")
}
// dispatch different idp if artifact binding is enabled
if(parameters.get('epdMode') == 'artifact' && result == 'epd'){
LOG.debug("EPD: Artifact mode")
result = result + "_artifact"
}else{
LOG.debug("EPD: POST mode")
}
response.setResult(result)
session.put("saml.inbound.issuer", issuer)
session.put('saml.idp.result', result) // remember decision for sub-sequent requests without a SAML message
}
def dispatchMessage(i2s, String message) {
def issuer = getIssuer(message)
if (issuer == null) {
LOG.info("No issuer found in incoming SAML message. Giving up.")
}
session.put("saml.inbound.issuer", issuer)
dispatchIssuer(i2s, issuer)
}
if (parameters.get('logoutConfirmation') == 'true' && "stepup" == request.getMethod()) {
String url = request.currentResource
def path = new URL(url).getPath()
if (path.endsWith("/logout")) {
// next AuthState will show a logout confirmation GUI
response.setResult('confirm')
return
}
}
// ensure session exists
if (request.getSession(false) == null) {
session = request.getSession(true).getData()
}
// issuer (any case) -> ResultCond name
def i2s = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER)
i2s.put(parameters.get('atb'), 'main')
i2s.put(parameters.get('epd_atb'), 'epd')
if (parameters.get('spInitiated') == 'true' && inargs.containsKey('SAMLRequest')) { // SP-initiated authentication
LOG.debug("found SAMLRequest parameter for SP-initiated authentication")
String message = inargs.get('SAMLRequest')
dispatchMessage(i2s, message)
return
}
if (inargs.containsKey('SAMLResponse')) { // response to IDP-initiated SAML Logout
LOG.debug("found SAMLResponse parameter")
String message = inargs.get('SAMLResponse')
dispatchMessage(i2s, message)
return
}
if (parameters.get('spInitiated') == 'true' && inargs.containsKey('soapheader')) { // SP-initiated SOAP with soapheader
LOG.debug("found soapheader parameter for SP-initiated")
String message = inargs.get('soapheader')
dispatchMessage(i2s, message)
return
}
if (parameters.get('spInitiated') == 'true' && inargs.containsKey('')) { // SP-initiated SOAP with empty
LOG.debug("found empty parameter for SP-initiated SOAP message")
String message = inargs.get('')
dispatchMessage(i2s, message)
return
}
String issuer = inargs['Issuer'] ?: inargs['issuer']
if (parameters.get('idpInitiated') == 'true' && issuer != null) { // IDP-initiated authentication
LOG.debug("found Issuer parameter for IDP-initiated authentication")
dispatchIssuer(i2s, issuer)
return
}
// used as fallback in case of ?logout (we need an IdentityProviderState)
if (inargs.containsKey("logout") && session.containsKey('saml.idp.result')) {
def result = session.get('saml.idp.result')
LOG.debug("dispatching to last used ResultCond: $result")
response.setResult(result)
return
}
def location = getReferer('/')
LOG.info("Unable to dispatch request. Giving up and redirecting (back) to $location")
redirect(location)

View File

@ -1,33 +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)
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

@ -1,10 +1,10 @@
def requester = 'unknown'
def requestId = session['ch.nevis.auth.saml.request.id'] ?: 'unknown'
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 reason = session['agov.recovery.reason'] ?: 'unknown'
LOG.info("Event='RECOVERY-REASON', Requester='${requester}', RequestId='${requestId}', User=${user}, SourceIp=${sourceIp}, UserAgent='${userAgent}', Reason='${reason}'")
def requester = 'unknown'
def requestId = session['ch.nevis.auth.saml.request.id'] ?: 'unknown'
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 reason = session['agov.recovery.reason'] ?: 'unknown'
LOG.info("Event='RECOVERY-REASON', Requester='${requester}', RequestId='${requestId}', User=${user}, SourceIp=${sourceIp}, UserAgent='${userAgent}', Reason='${reason}'")
response.setResult('ok')

View File

@ -16,12 +16,16 @@ Configuration:
level: "INFO"
- name: "EsAuthStart"
level: "INFO"
- name: "org.apache.catalina.loader.WebappClassLoader"
level: "FATAL"
- name: "org.apache.catalina.startup.HostConfig"
level: "ERROR"
- name: "ch.nevis.esauth.events"
level: "FATAL"
- name: "AGOV-ACCT"
level: "DEBUG"
- name: "AgovCaptcha"
level: "DEBUG"
- name: "ArtifactResolutionService"
level: "DEBUG"
- name: "AuthEngine"
level: "INFO"
- name: "AuthPerf"
@ -29,10 +33,8 @@ Configuration:
- name: "IdmAuth"
level: "DEBUG"
- name: "OpTrace"
level: "INFO"
- name: "Recovery"
level: "DEBUG"
- name: "Saml"
- name: "Recovery"
level: "DEBUG"
- name: "Script"
level: "DEBUG"

View File

@ -1,64 +1,64 @@
def redirect(location) {
outargs.put('nevis.transfer.type', 'redirect')
outargs.put('nevis.transfer.destination', location)
}
def getReturnURL() {
if (inargs.containsKey('return')) {
return inargs.get('return')
}
// determine returnURL based on Referer header (if present and not pointing to this page)
def referer = request.getHttpHeader('Referer')
if (referer == null) {
LOG.debug('no Referer header found')
return null
}
// strip query String for comparison
String previous = referer.contains('?') ? referer.substring(0, referer.indexOf("?")) : referer
def current = request.getCurrentResource()
if (current.startsWith(previous)) {
LOG.debug("Referer header $referer cannot be used as return URL - cyclic redirect")
return null
}
return referer
}
if (inargs.containsKey('logout-confirm')) {
def current = request.getCurrentResource()
// user has confirmed logout -> replace /logout with /?logout
String location
if (current.contains('?')) {
location = current.replace("/logout?", "/?logout&")
}
else {
location = current.replace("/logout", "/?logout")
}
redirect(location)
return
}
if (inargs.containsKey('logout-abort')) {
// user has aborted logout -> redirect to stored return URL
def location = session.get('logout-abort-url')
redirect(location)
return
}
// user has not clicked any button -> render GUI
response.setGuiName('saml_logout_confirm')
response.setGuiLabel('title.logout.confirmation')
// not setting a target as the API has been removed
response.addInfoGuiField('info', 'info.logout.confirmation', null)
response.addButtonGuiField('logout-confirm', 'continue.button.label', 'true')
def returnURL = getReturnURL()
if (returnURL != null) {
// store return URL in session
session.put('logout-abort-url', returnURL)
}
if (session.containsKey('logout-abort-url')) {
// add cancel button to go back
response.addButtonGuiField('logout-abort', 'cancel.button.label', 'true')
def redirect(location) {
outargs.put('nevis.transfer.type', 'redirect')
outargs.put('nevis.transfer.destination', location)
}
def getReturnURL() {
if (inargs.containsKey('return')) {
return inargs.get('return')
}
// determine returnURL based on Referer header (if present and not pointing to this page)
def referer = request.getHttpHeader('Referer')
if (referer == null) {
LOG.debug('no Referer header found')
return null
}
// strip query String for comparison
String previous = referer.contains('?') ? referer.substring(0, referer.indexOf("?")) : referer
def current = request.getCurrentResource()
if (current.startsWith(previous)) {
LOG.debug("Referer header $referer cannot be used as return URL - cyclic redirect")
return null
}
return referer
}
if (inargs.containsKey('logout-confirm')) {
def current = request.getCurrentResource()
// user has confirmed logout -> replace /logout with /?logout
String location
if (current.contains('?')) {
location = current.replace("/logout?", "/?logout&")
}
else {
location = current.replace("/logout", "/?logout")
}
redirect(location)
return
}
if (inargs.containsKey('logout-abort')) {
// user has aborted logout -> redirect to stored return URL
def location = session.get('logout-abort-url')
redirect(location)
return
}
// user has not clicked any button -> render GUI
response.setGuiName('saml_logout_confirm')
response.setGuiLabel('title.logout.confirmation')
// not setting a target as the API has been removed
response.addInfoGuiField('info', 'info.logout.confirmation', null)
response.addButtonGuiField('logout-confirm', 'continue.button.label', 'true')
def returnURL = getReturnURL()
if (returnURL != null) {
// store return URL in session
session.put('logout-abort-url', returnURL)
}
if (session.containsKey('logout-abort-url')) {
// add cancel button to go back
response.addButtonGuiField('logout-abort', 'cancel.button.label', 'true')
}

View File

@ -1,5 +1,4 @@
otel.service.name = auth
otel.traces.sampler = always_on
otel.traces.exporter = none
otel.metrics.exporter = none
otel.logs.exporter = none

View File

@ -1,25 +1,25 @@
import ch.nevis.esauth.auth.engine.AuthResponse
if (inargs['cancel'] && inargs['cancel'] == 'cancel') {
def s = request.getAuthSession(true)
s.removeAttribute('agov.recovery.moreThanOneLf')
response.setResult('doCancel')
return
}
if (inargs['continue'] && inargs['continue'] == 'yes') {
response.setSessionAttribute('agov.recovery.moreThanOneLf', 'yes')
response.setResult('loginFactorYes')
return
}
if (inargs['continue'] && inargs['continue'] == 'no') {
response.setSessionAttribute('agov.recovery.moreThanOneLf', 'no')
response.setResult('loginFactorNo')
return
}
// if we reach this, display the GUI again
response.setStatus(AuthResponse.AUTH_CONTINUE)
import ch.nevis.esauth.auth.engine.AuthResponse
if (inargs['cancel'] && inargs['cancel'] == 'cancel') {
def s = request.getAuthSession(true)
s.removeAttribute('agov.recovery.moreThanOneLf')
response.setResult('doCancel')
return
}
if (inargs['continue'] && inargs['continue'] == 'yes') {
response.setSessionAttribute('agov.recovery.moreThanOneLf', 'yes')
response.setResult('loginFactorYes')
return
}
if (inargs['continue'] && inargs['continue'] == 'no') {
response.setSessionAttribute('agov.recovery.moreThanOneLf', 'no')
response.setResult('loginFactorNo')
return
}
// if we reach this, display the GUI again
response.setStatus(AuthResponse.AUTH_CONTINUE)
return

View File

@ -1,28 +1,28 @@
import ch.nevis.esauth.auth.engine.AuthResponse
if (inargs['reason']) {
response.setSessionAttribute('agov.recovery.reason', '' + inargs['reason'])
}
if (inargs['cancel'] && inargs['cancel'] == 'cancel') {
def s = request.getAuthSession(true)
s.removeAttribute('agov.recovery.moreThanOneLf')
s.removeAttribute('agov.recovery.reason')
response.setResult('doCancel')
return
}
if (inargs['continue'] && inargs['continue'] == 'yes') {
response.setResult('validReasons')
return
}
if (inargs['continue'] && inargs['continue'] == 'no') {
response.setResult('invalidReasons')
return
}
// if we reach this, display the GUI again
response.setStatus(AuthResponse.AUTH_CONTINUE)
import ch.nevis.esauth.auth.engine.AuthResponse
if (inargs['reason']) {
response.setSessionAttribute('agov.recovery.reason', '' + inargs['reason'])
}
if (inargs['cancel'] && inargs['cancel'] == 'cancel') {
def s = request.getAuthSession(true)
s.removeAttribute('agov.recovery.moreThanOneLf')
s.removeAttribute('agov.recovery.reason')
response.setResult('doCancel')
return
}
if (inargs['continue'] && inargs['continue'] == 'yes') {
response.setResult('validReasons')
return
}
if (inargs['continue'] && inargs['continue'] == 'no') {
response.setResult('invalidReasons')
return
}
// if we reach this, display the GUI again
response.setStatus(AuthResponse.AUTH_CONTINUE)
return

View File

@ -76,4 +76,4 @@ if (session['ch.adnovum.nevisidm.userDto'] != null && notes['lasterror'] == null
response.setResult('error')
return
// new
// new

View File

@ -1,22 +1,22 @@
if (session['agov.recovery.redirectDone']) {
// user navigated back from AGOV.me, go again for the code
// clean up SAML state first,
// IdentityProviderState sets session attributes as follows
// <IDP-State-Name>-session-participants.<SAML-RP-ISSUER> = <ACS-URL>
// State name contains the name of the pattern 'Recovery_redirectAgovMe'
def s = request.getAuthSession(true)
def sessionKeySet = new HashSet(session.keySet())
sessionKeySet.each { key ->
if ( key ==~ /.*Recovery_redirectAgovMe-session-participants.*/ ) {
LOG.debug("Deleted session attribute '${key}'")
s.removeAttribute(key)
}
}
s.removeAttribute('agov.recovery.redirectDone')
response.setResult('back')
} else {
// redirect
response.setSessionAttribute('agov.recovery.redirectDone', 'true')
response.setResult('redirect')
}
if (session['agov.recovery.redirectDone']) {
// user navigated back from AGOV.me, go again for the code
// clean up SAML state first,
// IdentityProviderState sets session attributes as follows
// <IDP-State-Name>-session-participants.<SAML-RP-ISSUER> = <ACS-URL>
// State name contains the name of the pattern 'Recovery_redirectAgovMe'
def s = request.getAuthSession(true)
def sessionKeySet = new HashSet(session.keySet())
sessionKeySet.each { key ->
if ( key ==~ /.*Recovery_redirectAgovMe-session-participants.*/ ) {
LOG.debug("Deleted session attribute '${key}'")
s.removeAttribute(key)
}
}
s.removeAttribute('agov.recovery.redirectDone')
response.setResult('back')
} else {
// redirect
response.setSessionAttribute('agov.recovery.redirectDone', 'true')
response.setResult('redirect')
}

View File

@ -1,267 +1,267 @@
import groovy.json.JsonSlurper
import groovy.xml.XmlSlurper
import org.codehaus.groovy.runtime.StackTraceUtils
import ch.nevis.idm.client.IdmRestClient
import ch.nevis.idm.client.IdmRestClientFactory
// 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"
]
// https://docs.nevis.net/nevisidm/Developer-Guide/SOAP-Interface/Interface-specification/Value-types#enum-value-types
def blockingCredentialStates = ['DISABLED', 'EXPIRED', 'LOCKED', 'ARCHIVED', 'RESET_CODE']
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()) : ''
}
def userHasNewLoginFactor() {
IdmRestClient idmRestClient = IdmRestClientFactory.get(parameters)
String baseUrl = parameters.get('baseUrl')
String clientExtId = session.get('ch.adnovum.nevisidm.user.clientExtId')
String userExtId = session.get('ch.adnovum.nevisidm.user.extId')
String baseEndPoint = "$baseUrl/api/core/v1/$clientExtId/users/$userExtId"
response.setSessionAttribute('agov.recovery.newLoginFactor', 'NONE')
try {
def credInfoArray = new JsonSlurper().parseText(idmRestClient.get("$baseEndPoint/generic-credentials"))
def accessApp = credInfoArray['items'].find( it -> it.stateName == "active")
if (accessApp) {
response.setSessionAttribute('agov.recovery.accessapp', accessApp.properties.fidouaf_name)
response.setSessionAttribute('agov.recovery.accessapp.dispatchTargetId', accessApp.identification.replaceAll('dispatch_target_', ''))
response.setSessionAttribute('agov.recovery.newLoginFactor', 'ACCESS_APP')
return true
}
credInfoArray = new JsonSlurper().parseText(idmRestClient.get("$baseEndPoint/fido2"))
def fido2Key = credInfoArray['items'].find( it -> it.stateName == "active")
if (fido2Key) {
response.setSessionAttribute('agov.recovery.securityKey', fido2Key.userFriendlyName)
response.setSessionAttribute('agov.recovery.newLoginFactor', 'FIDO2')
return true
}
} catch(Exception e) {
LOG.error(e.toString())
}
return false
}
// 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
def recoveryCode = userDto.'**'.find {node -> node.name() == 'credentials' && node.type.text() == 'CONTEXT_PASSWORD' && node.context.text() == 'RECOVERY'}
LOG.debug("Recovery: Dto is '${userDto}")
LOG.debug("Recovery: state is '${userState}")
LOG.debug("Recovery: RecoveryCode is '${recoveryCode ? recoveryCode : 'none'}'")
if (userState == 'ACTIVE') {
response.setSessionAttribute('agov.recovery.authnContextClassRef', 'urn:qa.agov.ch:names:tc:ac:classes:recovery')
response.setSessionAttribute('agov.recovery.authenticatedWith', 'urn:qa.agov.ch:names:tc:authfactor:email')
response.setSessionAttribute('agov.recovery.codeStatus', 'notNeeded')
response.setSessionAttribute('agov.recovery.codeDetailStatus', 'n/a')
response.setSessionAttribute('agov.recovery.newLoginFactor', 'NONE')
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) {
if (maxLoi != 'level100') {
response.setSessionAttribute('agov.recovery.codeDetailStatus', '' + 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' }
def hasRecoveryCascadeRole = userDto.'**'.find { node -> node.name() == 'roles' && node.applicationName.text() == 'AGOV-AccountStatus' && node.name.text() == 'recoveryCascade' }
def hasNewLoginFactor = hasRecoveryRole && userHasNewLoginFactor()
if (mustRecover) {
// attributes are defined over the mustRecover authorization
response.setSessionAttribute('agov.recovery.authnContextClassRef', 'urn:qa.agov.ch:names:tc:ac:classes:mustRecover')
response.setSessionAttribute('agov.recovery.codeDetailStatus', 'mustRecover')
idVerification = getUserIdVerificationForRecovery(maxLoi ?: 'level100') ?: idVerification
agovAqValidFrom = getUserMustRecoverValidFrom()
maxLoi = getAqLevelBasedOnIdVerificationForRecovery(idVerification, maxLoi)
} else if (hasRecoveryCascadeRole && hasNewLoginFactor) {
response.setSessionAttribute('agov.recovery.authnContextClassRef', 'urn:qa.agov.ch:names:tc:ac:classes:recoveryCascade')
}
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}'")
LOG.debug("Recovery: hasNewLoginFactor is '${hasNewLoginFactor}'")
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) && !hasNewLoginFactor) {
// AQ100 accounts need to use the recovery code, if they can
// check the status of recoveryCode credential
if (recoveryCode && !blockingCredentialStates.contains(recoveryCode.state.text())) {
LOG.debug("Recovery: emailAndCode")
response.setResult('needCode')
return
} else {
LOG.warn("AGOVaq100 recovery: skipped Recovery-Code check '${recoveryCode ? recoveryCode.state.text() : 'MISSING'}'")
response.setSessionAttribute('agov.recovery.codeStatus', 'skipped')
response.setSessionAttribute('agov.recovery.codeDetailStatus', "unusable (state: ${recoveryCode ? recoveryCode.state.text() : 'MISSING'})")
response.setResult('ok')
return
}
} else if ((maxLoi == 'level100') && hasNewLoginFactor) {
LOG.debug("Recovery: new Login Factor")
response.setSessionAttribute('agov.recovery.codeStatus', 'skipped')
response.setSessionAttribute('agov.recovery.codeDetailStatus', "new login factor already registered (${session['agov.recovery.newLoginFactor']})")
response.setResult('ok')
return
} else {
LOG.debug("Recovery: 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
import groovy.json.JsonSlurper
import groovy.xml.XmlSlurper
import org.codehaus.groovy.runtime.StackTraceUtils
import ch.nevis.idm.client.IdmRestClient
import ch.nevis.idm.client.IdmRestClientFactory
// 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"
]
// https://docs.nevis.net/nevisidm/Developer-Guide/SOAP-Interface/Interface-specification/Value-types#enum-value-types
def blockingCredentialStates = ['DISABLED', 'EXPIRED', 'LOCKED', 'ARCHIVED', 'RESET_CODE']
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()) : ''
}
def userHasNewLoginFactor() {
IdmRestClient idmRestClient = IdmRestClientFactory.get(parameters)
String baseUrl = parameters.get('baseUrl')
String clientExtId = session.get('ch.adnovum.nevisidm.user.clientExtId')
String userExtId = session.get('ch.adnovum.nevisidm.user.extId')
String baseEndPoint = "$baseUrl/api/core/v1/$clientExtId/users/$userExtId"
response.setSessionAttribute('agov.recovery.newLoginFactor', 'NONE')
try {
def credInfoArray = new JsonSlurper().parseText(idmRestClient.get("$baseEndPoint/generic-credentials"))
def accessApp = credInfoArray['items'].find( it -> it.stateName == "active")
if (accessApp) {
response.setSessionAttribute('agov.recovery.accessapp', accessApp.properties.fidouaf_name)
response.setSessionAttribute('agov.recovery.accessapp.dispatchTargetId', accessApp.identification.replaceAll('dispatch_target_', ''))
response.setSessionAttribute('agov.recovery.newLoginFactor', 'ACCESS_APP')
return true
}
credInfoArray = new JsonSlurper().parseText(idmRestClient.get("$baseEndPoint/fido2"))
def fido2Key = credInfoArray['items'].find( it -> it.stateName == "active")
if (fido2Key) {
response.setSessionAttribute('agov.recovery.securityKey', fido2Key.userFriendlyName)
response.setSessionAttribute('agov.recovery.newLoginFactor', 'FIDO2')
return true
}
} catch(Exception e) {
LOG.error(e.toString())
}
return false
}
// 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
def recoveryCode = userDto.'**'.find {node -> node.name() == 'credentials' && node.type.text() == 'CONTEXT_PASSWORD' && node.context.text() == 'RECOVERY'}
LOG.debug("Recovery: Dto is '${userDto}")
LOG.debug("Recovery: state is '${userState}")
LOG.debug("Recovery: RecoveryCode is '${recoveryCode ? recoveryCode : 'none'}'")
if (userState == 'ACTIVE') {
response.setSessionAttribute('agov.recovery.authnContextClassRef', 'urn:qa.agov.ch:names:tc:ac:classes:recovery')
response.setSessionAttribute('agov.recovery.authenticatedWith', 'urn:qa.agov.ch:names:tc:authfactor:email')
response.setSessionAttribute('agov.recovery.codeStatus', 'notNeeded')
response.setSessionAttribute('agov.recovery.codeDetailStatus', 'n/a')
response.setSessionAttribute('agov.recovery.newLoginFactor', 'NONE')
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) {
if (maxLoi != 'level100') {
response.setSessionAttribute('agov.recovery.codeDetailStatus', '' + 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' }
def hasRecoveryCascadeRole = userDto.'**'.find { node -> node.name() == 'roles' && node.applicationName.text() == 'AGOV-AccountStatus' && node.name.text() == 'recoveryCascade' }
def hasNewLoginFactor = hasRecoveryRole && userHasNewLoginFactor()
if (mustRecover) {
// attributes are defined over the mustRecover authorization
response.setSessionAttribute('agov.recovery.authnContextClassRef', 'urn:qa.agov.ch:names:tc:ac:classes:mustRecover')
response.setSessionAttribute('agov.recovery.codeDetailStatus', 'mustRecover')
idVerification = getUserIdVerificationForRecovery(maxLoi ?: 'level100') ?: idVerification
agovAqValidFrom = getUserMustRecoverValidFrom()
maxLoi = getAqLevelBasedOnIdVerificationForRecovery(idVerification, maxLoi)
} else if (hasRecoveryCascadeRole && hasNewLoginFactor) {
response.setSessionAttribute('agov.recovery.authnContextClassRef', 'urn:qa.agov.ch:names:tc:ac:classes:recoveryCascade')
}
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}'")
LOG.debug("Recovery: hasNewLoginFactor is '${hasNewLoginFactor}'")
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) && !hasNewLoginFactor) {
// AQ100 accounts need to use the recovery code, if they can
// check the status of recoveryCode credential
if (recoveryCode && !blockingCredentialStates.contains(recoveryCode.state.text())) {
LOG.debug("Recovery: emailAndCode")
response.setResult('needCode')
return
} else {
LOG.warn("AGOVaq100 recovery: skipped Recovery-Code check '${recoveryCode ? recoveryCode.state.text() : 'MISSING'}'")
response.setSessionAttribute('agov.recovery.codeStatus', 'skipped')
response.setSessionAttribute('agov.recovery.codeDetailStatus', "unusable (state: ${recoveryCode ? recoveryCode.state.text() : 'MISSING'})")
response.setResult('ok')
return
}
} else if ((maxLoi == 'level100') && hasNewLoginFactor) {
LOG.debug("Recovery: new Login Factor")
response.setSessionAttribute('agov.recovery.codeStatus', 'skipped')
response.setSessionAttribute('agov.recovery.codeDetailStatus', "new login factor already registered (${session['agov.recovery.newLoginFactor']})")
response.setResult('ok')
return
} else {
LOG.debug("Recovery: 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

@ -1,39 +1,39 @@
import groovy.json.JsonSlurper
import io.opentelemetry.api.trace.Span
def url = parameters.get('url')
def realIpHttpHeaderName = parameters.get('realIpHttpHeaderName') ?: 'X-Real-IP'
def ip = request.getLoginContext()['connection.HttpHeader.X-Real-IP'] ?: 'unknown'
try {
def spanCtxt = Span.current().getSpanContext()
def traceparent = "00-${spanCtxt.getTraceId()}-${spanCtxt.getSpanId()}-${spanCtxt.getTraceFlags().asHex()}"
def jsonSlurper = new JsonSlurper()
def httpClient = HttpClients.create(parameters)
def httpResponse = Http.get().url(url).header('traceparent', traceparent)
.header(realIpHttpHeaderName, ip).build().send(httpClient)
LOG.debug('Response Status Code: ' + httpResponse.code())
LOG.debug('Response: ' + httpResponse.bodyAsString())
if (httpResponse.code() == 200) {
def json = jsonSlurper.parseText(httpResponse.bodyAsString())
response.setSessionAttribute('agov.recovery.captchaSettings.enabled', String.valueOf(json.friendlyCaptureClientSettings.enabled))
response.setSessionAttribute('agov.recovery.captchaSettings.siteKey', json.friendlyCaptureClientSettings.siteKey)
response.setSessionAttribute('agov.recovery.captchaSettings.puzzleUrl', json.friendlyCaptureClientSettings.puzzleUrl)
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')
import groovy.json.JsonSlurper
import io.opentelemetry.api.trace.Span
def url = parameters.get('url')
def realIpHttpHeaderName = parameters.get('realIpHttpHeaderName') ?: 'X-Real-IP'
def ip = request.getLoginContext()['connection.HttpHeader.X-Real-IP'] ?: 'unknown'
try {
def spanCtxt = Span.current().getSpanContext()
def traceparent = "00-${spanCtxt.getTraceId()}-${spanCtxt.getSpanId()}-${spanCtxt.getTraceFlags().asHex()}"
def jsonSlurper = new JsonSlurper()
def httpClient = HttpClients.create(parameters)
def httpResponse = Http.get().url(url).header('traceparent', traceparent)
.header(realIpHttpHeaderName, ip).build().send(httpClient)
LOG.debug('Response Status Code: ' + httpResponse.code())
LOG.debug('Response: ' + httpResponse.bodyAsString())
if (httpResponse.code() == 200) {
def json = jsonSlurper.parseText(httpResponse.bodyAsString())
response.setSessionAttribute('agov.recovery.captchaSettings.enabled', String.valueOf(json.friendlyCaptureClientSettings.enabled))
response.setSessionAttribute('agov.recovery.captchaSettings.siteKey', json.friendlyCaptureClientSettings.siteKey)
response.setSessionAttribute('agov.recovery.captchaSettings.puzzleUrl', json.friendlyCaptureClientSettings.puzzleUrl)
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

@ -60,4 +60,4 @@ try {
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')
}
}

View File

@ -21,4 +21,4 @@ if (inargs['cd'] != null) {
if (inargs['cd'] == null && session['agov.recovery.code'] != null) {
response.setResult('exit.1')
return
}
}

View File

@ -1,22 +1,22 @@
import ch.nevis.esauth.auth.engine.AuthResponse
if (inargs['recovery'] != null && inargs['recovery'] == 'recovery' ) {
// clean up SAML state, to make sure the redirect will really be processed
// IdentityProviderState sets session attributes as follows
// <IDP-State-Name>-session-participants.<SAML-RP-ISSUER> = <ACS-URL>
// State name contains the name of the pattern 'Recovery_redirectAgovMe'
def s = request.getAuthSession(true)
def sessionKeySet = new HashSet(session.keySet())
sessionKeySet.each { key ->
if ( key ==~ /.*Recovery_redirectAgovMe-session-participants.*/ ) {
LOG.debug("Deleted session attribute '${key}'")
s.removeAttribute(key)
}
}
response.setResult('ok')
return
}
// if we reach this, display the GUI again
response.setStatus(AuthResponse.AUTH_CONTINUE)
import ch.nevis.esauth.auth.engine.AuthResponse
if (inargs['recovery'] != null && inargs['recovery'] == 'recovery' ) {
// clean up SAML state, to make sure the redirect will really be processed
// IdentityProviderState sets session attributes as follows
// <IDP-State-Name>-session-participants.<SAML-RP-ISSUER> = <ACS-URL>
// State name contains the name of the pattern 'Recovery_redirectAgovMe'
def s = request.getAuthSession(true)
def sessionKeySet = new HashSet(session.keySet())
sessionKeySet.each { key ->
if ( key ==~ /.*Recovery_redirectAgovMe-session-participants.*/ ) {
LOG.debug("Deleted session attribute '${key}'")
s.removeAttribute(key)
}
}
response.setResult('ok')
return
}
// if we reach this, display the GUI again
response.setStatus(AuthResponse.AUTH_CONTINUE)
return

View File

@ -1,41 +1,41 @@
import io.opentelemetry.api.trace.Span
def url = parameters.get('url')
def email = inargs['email']
def language = session['ch.nevis.session.user.language'] ?: 'en'
def payload = '{ "email": "' + email + '", "language": "' + language + '"}'
try {
def spanCtxt = Span.current().getSpanContext()
def traceparent = "00-${spanCtxt.getTraceId()}-${spanCtxt.getSpanId()}-${spanCtxt.getTraceFlags().asHex()}"
def httpClient = HttpClients.create(parameters)
def httpResponse = Http.post()
.url(url)
.header("Accept", "application/json")
.header("traceparent", traceparent)
.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')
import io.opentelemetry.api.trace.Span
def url = parameters.get('url')
def email = inargs['email']
def language = session['ch.nevis.session.user.language'] ?: 'en'
def payload = '{ "email": "' + email + '", "language": "' + language + '"}'
try {
def spanCtxt = Span.current().getSpanContext()
def traceparent = "00-${spanCtxt.getTraceId()}-${spanCtxt.getSpanId()}-${spanCtxt.getTraceFlags().asHex()}"
def httpClient = HttpClients.create(parameters)
def httpResponse = Http.post()
.url(url)
.header("Accept", "application/json")
.header("traceparent", traceparent)
.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

@ -105,8 +105,7 @@ try {
session.setAttribute('agov.appDisplayNameFR', '' + json.displayNameFr)
session.setAttribute('agov.appDisplayNameIT', '' + json.displayNameIt)
session.setAttribute('agov.appDisplayNameEN', '' + json.displayNameEn)
session.setAttribute('agov.appDisplayNameRM', '' + json.displayNameRm)
//session.setAttribute('agov.appDisplayNameRM', '' + ( (json.displayNameRm) ? json.displayNameDe : json.displayNameRm))
session.setAttribute('agov.appDisplayNameRM', '' + ((json.appDisplayNameRM) ? json.appDisplayNameRM : json.appDisplayNameDE))
// if aq500 or 600 is requested -> the only available login method is eid -> continue directly there
// if eid is disabled -> show an error page

View File

@ -8,4 +8,4 @@ response.setHeader('IDP-AUTH', 'Timeout')
// CONTINUE to keep the other request beeing processed
response.setStatus(AuthResponse.AUTH_CONTINUE)
return
return

View File

@ -29,4 +29,3 @@ if ( inargs['submit'] && inargs['submit'] == 'submit' ) {
response.setResult('stay')
return

View File

@ -22,4 +22,4 @@ if ( inargs['continue'] && inargs['continue'] == 'continue' ) {
}
response.setResult('stay')
return
return

View File

@ -11,8 +11,8 @@ metadata:
spec:
type: "NevisFIDO"
replicas: 1
version: "8.2505.5"
gitInitVersion: "1.4.0"
version: "8.2411.2"
gitInitVersion: "1.3.0"
runAsNonRoot: true
ports:
rest: 9443
@ -40,19 +40,18 @@ spec:
management:
httpGet:
path: "/nevisfido/health"
initialDelaySeconds: 30
periodSeconds: 5
timeoutSeconds: 6
failureThreshold: 30
failureThreshold: 50
podDisruptionBudget:
maxUnavailable: "50%"
git:
tag: "r-484395a405f9f7123da379fa8df82e197d2dbd71"
tag: "r-0a95034444af9c2e5b4a8c12cc3a0f444f6b0447"
dir: "DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/fido-uaf"
credentials: "git-credentials"
database:
name: "fido-uaf"
requiredVersion: "8.2505.5"
requiredVersion: "8.2411.1"
keystores:
- "fido-uaf-default-server-identity"
- "fido-uaf-default-client-identity"

View File

@ -10,7 +10,7 @@ metadata:
patternId: "ca92034f995b39fde562293c"
spec:
keystores:
- name: "auth-sts-sh4r3d-internal-idp-auth-signer"
namespace: "adn-agov-nevisidm-01-uat"
- name: "auth-sh4r3d-internal-idp-auth-signer"
namespace: "adn-agov-nevisidm-01-uat"
- name: "auth-sts-sh4r3d-internal-idp-auth-signer"
namespace: "adn-agov-nevisidm-01-uat"

View File

@ -11,7 +11,7 @@ metadata:
spec:
type: "NevisFIDO"
databaseType: "MariaDB"
version: "8.2505.5"
version: "8.2411.2"
url: "mariadb-session-store-service.adn-agov-nevisidm-ob-01-uat"
port: 3306
database: "nevisfido_uaf"

View File

@ -9,4 +9,4 @@
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/agov-dev%40agov-test.iam.gserviceaccount.com"
}
}

View File

@ -7,5 +7,5 @@ JAVA_OPTS=(
"-javaagent:/opt/agent/opentelemetry-javaagent.jar"
"-Dotel.javaagent.logging=application"
"-Dotel.javaagent.configuration-file=/var/opt/nevisfido/default/conf/otel.properties"
"-Dotel.resource.attributes=service.version=8.2505.5,service.instance.id=$HOSTNAME"
)
"-Dotel.resource.attributes=service.version=8.2411.2,service.instance.id=$HOSTNAME"
)

View File

@ -3,13 +3,14 @@
"aaid" : "F1D0#0001",
"description" : "Android NEVIS Mobile Authentication PIN Authenticator",
"assertionScheme" : "UAFV1TLV",
"attestationRootCertificates" : [],
"supportedExtensions" : [
{
"id" : "ch.nevis.auth.fido.uaf.google-attestation-root-keys",
"fail_if_unknown" : false,
"data" : "[ \"MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAr7bHgiuxpwHsK7Qui8xUFmOr75gvMsd/dTEDDJdSSxtf6An7xyqpRR90PL2abxM1dEqlXnf2tqw1Ne4Xwl5jlRfdnJLmN0pTy/4lj4/7tv0Sk3iiKkypnEUtR6WfMgH0QZfKHM1+di+y9TFRtv6y//0rb+T+W8a9nsNL/ggjnar86461qO0rOs2cXjp3kOG1FEJ5MVmFmBGtnrKpa73XpXyTqRxB/M0n1n/W9nGqC4FSYa04T6N5RIZGBN2z2MT5IKGbFlbC8UrW0DxW7AYImQQcHtGl/m00QLVWutHQoVJYnFPlXTcHYvASLu+RhhsbDmxMgJJ0mcDpvsC4PjvB+TxywElgS70vE0XmLD+OJtvsBslHZvPBKCOdT0MS+tgSOIfga+z1Z1g7+DVagf7quvmag8jfPioyKvxnK/EgsTUVi2ghzq8wm27ud/mIM7AY2qEORR8Go3TVB4HzWQgpZrt3i5MIlCaY504LzSRiigHCzAPlHws+W0rB5N+er5/2pJKnfBSDiCiFAVtCLOZ7gLiMm0jhO2B6tUXHI/+MRPjy02i59lINMRRev56GKtcd9qO/0kUJWdZTdA2XoS82ixPvZtXQpUpuL12ab+9EaDK8Z4RHJYYfCT3Q5vNAXaiWQ+8PTWm2QgBR/bkwSWc+NpUFgNPN9PvQi8WEg5UmAGMCAwEAAQ==\" ]"
}
"attestationRootCertificates" : [
"MIIFYDCCA0igAwIBAgIJAOj6GWMU0voYMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNVBAUTEGY5MjAwOWU4NTNiNmIwNDUwHhcNMTYwNTI2MTYyODUyWhcNMjYwNTI0MTYyODUyWjAbMRkwFwYDVQQFExBmOTIwMDllODUzYjZiMDQ1MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAr7bHgiuxpwHsK7Qui8xUFmOr75gvMsd/dTEDDJdSSxtf6An7xyqpRR90PL2abxM1dEqlXnf2tqw1Ne4Xwl5jlRfdnJLmN0pTy/4lj4/7tv0Sk3iiKkypnEUtR6WfMgH0QZfKHM1+di+y9TFRtv6y//0rb+T+W8a9nsNL/ggjnar86461qO0rOs2cXjp3kOG1FEJ5MVmFmBGtnrKpa73XpXyTqRxB/M0n1n/W9nGqC4FSYa04T6N5RIZGBN2z2MT5IKGbFlbC8UrW0DxW7AYImQQcHtGl/m00QLVWutHQoVJYnFPlXTcHYvASLu+RhhsbDmxMgJJ0mcDpvsC4PjvB+TxywElgS70vE0XmLD+OJtvsBslHZvPBKCOdT0MS+tgSOIfga+z1Z1g7+DVagf7quvmag8jfPioyKvxnK/EgsTUVi2ghzq8wm27ud/mIM7AY2qEORR8Go3TVB4HzWQgpZrt3i5MIlCaY504LzSRiigHCzAPlHws+W0rB5N+er5/2pJKnfBSDiCiFAVtCLOZ7gLiMm0jhO2B6tUXHI/+MRPjy02i59lINMRRev56GKtcd9qO/0kUJWdZTdA2XoS82ixPvZtXQpUpuL12ab+9EaDK8Z4RHJYYfCT3Q5vNAXaiWQ+8PTWm2QgBR/bkwSWc+NpUFgNPN9PvQi8WEg5UmAGMCAwEAAaOBpjCBozAdBgNVHQ4EFgQUNmHhAHyIBQlRi0RsR/8aTMnqTxIwHwYDVR0jBBgwFoAUNmHhAHyIBQlRi0RsR/8aTMnqTxIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwQAYDVR0fBDkwNzA1oDOgMYYvaHR0cHM6Ly9hbmRyb2lkLmdvb2dsZWFwaXMuY29tL2F0dGVzdGF0aW9uL2NybC8wDQYJKoZIhvcNAQELBQADggIBACDIw41L3KlXG0aMiS//cqrG+EShHUGo8HNsw30W1kJtjn6UBwRM6jnmiwfBPb8VA91chb2vssAtX2zbTvqBJ9+LBPGCdw/E53Rbf86qhxKaiAHOjpvAy5Y3m00mqC0w/Zwvju1twb4vhLaJ5NkUJYsUS7rmJKHHBnETLi8GFqiEsqTWpG/6ibYCv7rYDBJDcR9W62BW9jfIoBQcxUCUJouMPH25lLNcDc1ssqvC2v7iUgI9LeoM1sNovqPmQUiG9rHli1vXxzCyaMTjwftkJLkf6724DFhuKug2jITV0QkXvaJWF4nUaHOTNA4uJU9WDvZLI1j83A+/xnAJUucIv/zGJ1AMH2boHqF8CY16LpsYgBt6tKxxWH00XcyDCdW2KlBCeqbQPcsFmWyWugxdcekhYsAWyoSf818NUsZdBWBaR/OukXrNLfkQ79IyZohZbvabO/X+MVT3rriAoKc8oE2Uws6DF+60PV7/WIPjNvXySdqspImSN78mflxDqwLqRBYkA3I75qppLGG9rp7UCdRjxMl8ZDBld+7yvHVgt1cVzJx9xnyGCC23UaicMDSXYrB4I4WHXPGjxhZuCuPBLTdOLU8YRvMYdEvYebWHMpvwGCF6bAx3JBpIeOQ1wDB5y0USicV3YgYGmi+NZfhA4URSh77Yd6uuJOJENRaNVTzk",
"MIIFHDCCAwSgAwIBAgIJANUP8luj8tazMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNVBAUTEGY5MjAwOWU4NTNiNmIwNDUwHhcNMTkxMTIyMjAzNzU4WhcNMzQxMTE4MjAzNzU4WjAbMRkwFwYDVQQFExBmOTIwMDllODUzYjZiMDQ1MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAr7bHgiuxpwHsK7Qui8xUFmOr75gvMsd/dTEDDJdSSxtf6An7xyqpRR90PL2abxM1dEqlXnf2tqw1Ne4Xwl5jlRfdnJLmN0pTy/4lj4/7tv0Sk3iiKkypnEUtR6WfMgH0QZfKHM1+di+y9TFRtv6y//0rb+T+W8a9nsNL/ggjnar86461qO0rOs2cXjp3kOG1FEJ5MVmFmBGtnrKpa73XpXyTqRxB/M0n1n/W9nGqC4FSYa04T6N5RIZGBN2z2MT5IKGbFlbC8UrW0DxW7AYImQQcHtGl/m00QLVWutHQoVJYnFPlXTcHYvASLu+RhhsbDmxMgJJ0mcDpvsC4PjvB+TxywElgS70vE0XmLD+OJtvsBslHZvPBKCOdT0MS+tgSOIfga+z1Z1g7+DVagf7quvmag8jfPioyKvxnK/EgsTUVi2ghzq8wm27ud/mIM7AY2qEORR8Go3TVB4HzWQgpZrt3i5MIlCaY504LzSRiigHCzAPlHws+W0rB5N+er5/2pJKnfBSDiCiFAVtCLOZ7gLiMm0jhO2B6tUXHI/+MRPjy02i59lINMRRev56GKtcd9qO/0kUJWdZTdA2XoS82ixPvZtXQpUpuL12ab+9EaDK8Z4RHJYYfCT3Q5vNAXaiWQ+8PTWm2QgBR/bkwSWc+NpUFgNPN9PvQi8WEg5UmAGMCAwEAAaNjMGEwHQYDVR0OBBYEFDZh4QB8iAUJUYtEbEf/GkzJ6k8SMB8GA1UdIwQYMBaAFDZh4QB8iAUJUYtEbEf/GkzJ6k8SMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgIEMA0GCSqGSIb3DQEBCwUAA4ICAQBOMaBc8oumXb2voc7XCWnuXKhBBK3e2KMGz39t7lA3XXRe2ZLLAkLM5y3J7tURkf5a1SutfdOyXAmeE6SRo83Uh6WszodmMkxK5GM4JGrnt4pBisu5igXEydaW7qq2CdC6DOGjG+mEkN8/TA6p3cnoL/sPyz6evdjLlSeJ8rFBH6xWyIZCbrcpYEJzXaUOEaxxXxgYz5/cTiVKN2M1G2okQBUIYSY6bjEL4aUN5cfo7ogP3UvliEo3Eo0YgwuzR2v0KR6C1cZqZJSTnghIC/vAD32KdNQ+c3N+vl2OTsUVMC1GiWkngNx1OO1+kXW+YTnnTUOtOIswUP/Vqd5SYgAImMAfY8U9/iIgkQj6T2W6FsScy94IN9fFhE1UtzmLoBIuUFsVXJMTz+Jucth+IqoWFua9v1R93/k98p41pjtFX+H8DslVgfP097vju4KDlqN64xV1grw3ZLl4CiOe/A91oeLm2UHOq6wn3esB4r2EIQKb6jTVGu5sYCcdWpXr0AUVqcABPdgL+H7qJguBw09ojm6xNIrw2OocrDKsudk/okr/AwqEyPKw9WnMlQgLIKw1rODG2NvU9oR3GVGdMkUBZutL8VuFkERQGt6vQ2OCw0sV47VMkuYbacK/xyZFiRcrPJPb41zgbQj9XAEyLKCHex0SdDrx+tWUDqG8At2JHA==",
"MIIFHDCCAwSgAwIBAgIJAMNrfES5rhgxMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNVBAUTEGY5MjAwOWU4NTNiNmIwNDUwHhcNMjExMTE3MjMxMDQyWhcNMzYxMTEzMjMxMDQyWjAbMRkwFwYDVQQFExBmOTIwMDllODUzYjZiMDQ1MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAr7bHgiuxpwHsK7Qui8xUFmOr75gvMsd/dTEDDJdSSxtf6An7xyqpRR90PL2abxM1dEqlXnf2tqw1Ne4Xwl5jlRfdnJLmN0pTy/4lj4/7tv0Sk3iiKkypnEUtR6WfMgH0QZfKHM1+di+y9TFRtv6y//0rb+T+W8a9nsNL/ggjnar86461qO0rOs2cXjp3kOG1FEJ5MVmFmBGtnrKpa73XpXyTqRxB/M0n1n/W9nGqC4FSYa04T6N5RIZGBN2z2MT5IKGbFlbC8UrW0DxW7AYImQQcHtGl/m00QLVWutHQoVJYnFPlXTcHYvASLu+RhhsbDmxMgJJ0mcDpvsC4PjvB+TxywElgS70vE0XmLD+OJtvsBslHZvPBKCOdT0MS+tgSOIfga+z1Z1g7+DVagf7quvmag8jfPioyKvxnK/EgsTUVi2ghzq8wm27ud/mIM7AY2qEORR8Go3TVB4HzWQgpZrt3i5MIlCaY504LzSRiigHCzAPlHws+W0rB5N+er5/2pJKnfBSDiCiFAVtCLOZ7gLiMm0jhO2B6tUXHI/+MRPjy02i59lINMRRev56GKtcd9qO/0kUJWdZTdA2XoS82ixPvZtXQpUpuL12ab+9EaDK8Z4RHJYYfCT3Q5vNAXaiWQ+8PTWm2QgBR/bkwSWc+NpUFgNPN9PvQi8WEg5UmAGMCAwEAAaNjMGEwHQYDVR0OBBYEFDZh4QB8iAUJUYtEbEf/GkzJ6k8SMB8GA1UdIwQYMBaAFDZh4QB8iAUJUYtEbEf/GkzJ6k8SMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgIEMA0GCSqGSIb3DQEBCwUAA4ICAQBTNNZe5cuf8oiq+jV0itTGzWVhSTjOBEk2FQvh11J3o3lna0o7rd8RFHnN00q4hi6TapFhh4qaw/iG6Xg+xOan63niLWIC5GOPFgPeYXM9+nBb3zZzC8ABypYuCusWCmt6Tn3+Pjbz3MTVhRGXuT/TQH4KGFY4PhvzAyXwdjTOCXID+aHud4RLcSySr0Fq/L+R8TWalvM1wJJPhyRjqRCJerGtfBagiALzvhnmY7U1qFcS0NCnKjoO7oFedKdWlZz0YAfu3aGCJd4KHT0MsGiLZez9WP81xYSrKMNEsDK+zK5fVzw6jA7cxmpXcARTnmAuGUeI7VVDhDzKeVOctf3a0qQLwC+d0+xrETZ4r2fRGNw2YEs2W8Qj6oDcfPvq9JySe7pJ6wcHnl5EZ0lwc4xH7Y4Dx9RA1JlfooLMw3tOdJZH0enxPXaydfAD3YifeZpFaUzicHeLzVJLt9dvGB0bHQLE4+EqKFgOZv2EoP686DQqbVS1u+9k0p2xbMA105TBIk7npraa8VM0fnrRKi7wlZKwdH+aNAyhbXRW9xsnODJ+g8eF452zvbiKKngEKirK5LGieoXBX7tZ9D1GNBH2Ob3bKOwwIWdEFle/YF/h6zWgdeoaNGDqVBrLr2+0DtWoiB1aDEjLWl9FmyIUyUm7mD/vFDkzF+wm7cyWpQpCVQ==",
"MIIFHDCCAwSgAwIBAgIJAPHBcqaZ6vUdMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNVBAUTEGY5MjAwOWU4NTNiNmIwNDUwHhcNMjIwMzIwMTgwNzQ4WhcNNDIwMzE1MTgwNzQ4WjAbMRkwFwYDVQQFExBmOTIwMDllODUzYjZiMDQ1MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAr7bHgiuxpwHsK7Qui8xUFmOr75gvMsd/dTEDDJdSSxtf6An7xyqpRR90PL2abxM1dEqlXnf2tqw1Ne4Xwl5jlRfdnJLmN0pTy/4lj4/7tv0Sk3iiKkypnEUtR6WfMgH0QZfKHM1+di+y9TFRtv6y//0rb+T+W8a9nsNL/ggjnar86461qO0rOs2cXjp3kOG1FEJ5MVmFmBGtnrKpa73XpXyTqRxB/M0n1n/W9nGqC4FSYa04T6N5RIZGBN2z2MT5IKGbFlbC8UrW0DxW7AYImQQcHtGl/m00QLVWutHQoVJYnFPlXTcHYvASLu+RhhsbDmxMgJJ0mcDpvsC4PjvB+TxywElgS70vE0XmLD+OJtvsBslHZvPBKCOdT0MS+tgSOIfga+z1Z1g7+DVagf7quvmag8jfPioyKvxnK/EgsTUVi2ghzq8wm27ud/mIM7AY2qEORR8Go3TVB4HzWQgpZrt3i5MIlCaY504LzSRiigHCzAPlHws+W0rB5N+er5/2pJKnfBSDiCiFAVtCLOZ7gLiMm0jhO2B6tUXHI/+MRPjy02i59lINMRRev56GKtcd9qO/0kUJWdZTdA2XoS82ixPvZtXQpUpuL12ab+9EaDK8Z4RHJYYfCT3Q5vNAXaiWQ+8PTWm2QgBR/bkwSWc+NpUFgNPN9PvQi8WEg5UmAGMCAwEAAaNjMGEwHQYDVR0OBBYEFDZh4QB8iAUJUYtEbEf/GkzJ6k8SMB8GA1UdIwQYMBaAFDZh4QB8iAUJUYtEbEf/GkzJ6k8SMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgIEMA0GCSqGSIb3DQEBCwUAA4ICAQB8cMqTllHc8U+qCrOlg3H7174lmaCsbo/bJ0C17JEgMLb4kvrqsXZs01U3mB/qABg/1t5Pd5AORHARs1hhqGICW/nKMav574f9rZN4PC2ZlufGXb7sIdJpGiO9ctRhiLuYuly10JccUZGEHpHSYM2GtkgYbZba6lsCPYAAP83cyDV+1aOkTf1RCp/lM0PKvmxYN10RYsK631jrleGdcdkxoSK//mSQbgcWnmAEZrzHoF1/0gso1HZgIn0YLzVhLSA/iXCX4QT2h3J5z3znluKG1nv8NQdxei2DIIhASWfu804CA96cQKTTlaae2fweqXjdN1/v2nqOhngNyz1361mFmr4XmaKH/ItTwOe72NI9ZcwS1lVaCvsIkTDCEXdm9rCNPAY10iTunIHFXRh+7KPzlHGewCq/8TOohBRn0/NNfh7uRslOSZ/xKbN9tMBtw37Z8d2vvnXq/YWdsm1+JLVwn6yYD/yacNJBlwpddla8eaVMjsF6nBnIgQOf9zKSe06nSTqvgwUHosgOECZJZ1EuzbH4yswbt02tKtKEFhx+v+OTge/06V+jGsqTWLsfrOCNLuA8H++z+pUENmpqnnHovaI47gC+TNpkgYGkkBT6B/m/U01BuOBBTzhIlMEZq9qkDWuM2cA5kW5V3FJUcfHnw1IdYIg2Wxg7yHcQZemFQg==",
"MIIC8jCCAdqgAwIBAgIGAZFrLh2fMA0GCSqGSIb3DQEBCwUAMDoxDjAMBgNVBAMMBXRlc3R5MQswCQYDVQQGEwJVUzEbMBkGCSqGSIb3DQEJARYMYWJjQGFjbWUuY29tMB4XDTI0MDgxOTE1MDc1MFoXDTI1MDgxOTE1MDc1MFowOjEOMAwGA1UEAwwFdGVzdHkxCzAJBgNVBAYTAlVTMRswGQYJKoZIhvcNAQkBFgxhYmNAYWNtZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDqitlYBzaxbPF389ZT5xkSS9Le1qdIOuc+dLVpBSWP9PEJhVZROgdOHs5f666iAcBedQm73sew3rpl+02J4fSgGmPkIYm1G2vkIrpt0eB9KzSc0AiLZbrPcFZOLHcOLoqVTfoRhnmAksHDC2f8euNKhCyriK8xlJb/xPfAfCn4r58ZGsQPUS7cJL6FLYh7FjrqfYDS10VOrQvGOALrG5NUj1DdqRq0M+klgs+6oJdUZTtY62BKkWh3N+7moNvrqykpv+ydFUJltgezDcb4Br8Nkw/breSPnomRfyHIcAcfATZcOPJlI8pO0zFZDIz8r7ESMnBhAxNaZgsUhR2XbaqbAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAGw5XLY6GeFJMP350+djhcVqAw+E4HZqCJu1BMpYC0qS2D85fFi3gNuV0TnqB52abX1WBDDJK1CA0SPdyo/nX+qQzP6Dba1AVRKpRzdcsDsMDN3eMC08tajHgIIf5tNDv+HGE/MT2br4o5oducmQMOfV1NTJO1xhXYVqbsUnyrq3S6kD9WS8zRl6ruY1rT26eCQ4hTLHPaAiVsoXh5TBRXYCvGlAw7o2d9cmsbySforZ2wgdZwmu43B5eHNnt4NlDxZRyz6iEDP0nT877aB2ffsOKHAkJNuTvF5JSfnVzLmiyfa/7NI1ujfzcpA2UUXoWa7WN0wACiZQot8Zmswonjc=",
"MIIC8jCCAdqgAwIBAgIGAZFrJblQMA0GCSqGSIb3DQEBCwUAMDoxDTALBgNVBAMMBHRlc3QxCzAJBgNVBAYTAkNIMRwwGgYJKoZIhvcNAQkBFg1mYWtlQGFjbWUuY29tMB4XDTI0MDgxOTE0NTg0MFoXDTI1MDgxOTE0NTg0MFowOjENMAsGA1UEAwwEdGVzdDELMAkGA1UEBhMCQ0gxHDAaBgkqhkiG9w0BCQEWDWZha2VAYWNtZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCcWDBNmdq13fYHnhsmLndAW+MfbI6PeU4OenqfbrTtQUxqpyqhP6QccPYKX2SK3JeQo5uuF1jRD/9i9vAXI9NyiMMHSItjt9LjRs7bWnY4lokYGCAcSZooR9fGZX63dBSQo73V7MC8LDFGy5rw6dGDOmh0ktKxFzaT/nav8/Mx8FyG7M9+b5OPIBo2yze5Rd5cdErGJuUYa9No93BBr5tq+JfnmR/gwgCOke97ovhNj+sMu5bt946AxC6t00wNyPNVlJHKi1os0c/pWztTQkoRAx/w0JYKS9Afl0ZnGWQQ5PNLHHecp2GzriBpQAPXq81QTbOh5H7SzvhkaFQ4oxstAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAD8GOaeMDqj2mzMmCqR6Cr3ChkbDAkdsBa5lOAikMKs7/tJyaw8iA5yH0nyobC58Jb61IATuxABPUALhP3RiNsUhnQQF/Dh+6CnCTD/2wsZmr8vUvNqyCLom+xkMT6Wayd9LYW4UONARv1qCLVI4RhiAr5kcomwqZnuj2DRF697lbSQDoz3iuKrCyBYSCBhS+k7UXpqpMyB2D6quRuPqh7JNtMjGSeMiNpMXhx5f4kl1YWb8NU93LDwHFR2kwnGmPA3M272VitcJC4dz3itGRKm9EYGd6d5D7kdC6lqpZPSIopChvXDyVrXjQgckvgtSGKscs6AvYgjthJGsR2z3Eao=",
"MIIC8jCCAdqgAwIBAgIGAZFrLh2fMA0GCSqGSIb3DQEBCwUAMDoxDjAMBgNVBAMMBXRlc3R5MQswCQYDVQQGEwJVUzEbMBkGCSqGSIb3DQEJARYMYWJjQGFjbWUuY29tMB4XDTI0MDgxOTE1MDc1MFoXDTI1MDgxOTE1MDc1MFowOjEOMAwGA1UEAwwFdGVzdHkxCzAJBgNVBAYTAlVTMRswGQYJKoZIhvcNAQkBFgxhYmNAYWNtZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDqitlYBzaxbPF389ZT5xkSS9Le1qdIOuc+dLVpBSWP9PEJhVZROgdOHs5f666iAcBedQm73sew3rpl+02J4fSgGmPkIYm1G2vkIrpt0eB9KzSc0AiLZbrPcFZOLHcOLoqVTfoRhnmAksHDC2f8euNKhCyriK8xlJb/xPfAfCn4r58ZGsQPUS7cJL6FLYh7FjrqfYDS10VOrQvGOALrG5NUj1DdqRq0M+klgs+6oJdUZTtY62BKkWh3N+7moNvrqykpv+ydFUJltgezDcb4Br8Nkw/breSPnomRfyHIcAcfATZcOPJlI8pO0zFZDIz8r7ESMnBhAxNaZgsUhR2XbaqbAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAGw5XLY6GeFJMP350+djhcVqAw+E4HZqCJu1BMpYC0qS2D85fFi3gNuV0TnqB52abX1WBDDJK1CA0SPdyo/nX+qQzP6Dba1AVRKpRzdcsDsMDN3eMC08tajHgIIf5tNDv+HGE/MT2br4o5oducmQMOfV1NTJO1xhXYVqbsUnyrq3S6kD9WS8zRl6ruY1rT26eCQ4hTLHPaAiVsoXh5TBRXYCvGlAw7o2d9cmsbySforZ2wgdZwmu43B5eHNnt4NlDxZRyz6iEDP0nT877aB2ffsOKHAkJNuTvF5JSfnVzLmiyfa/7NI1ujfzcpA2UUXoWa7WN0wACiZQot8Zmswonjc="
],
"attestationTypes" : [ 15879, 15880 ],
"upv" : [ {
@ -33,13 +34,14 @@
"aaid" : "F1D0#0002",
"description" : "Android NEVIS Mobile Authentication Fingerprint Authenticator",
"assertionScheme" : "UAFV1TLV",
"attestationRootCertificates" : [],
"supportedExtensions" : [
{
"id" : "ch.nevis.auth.fido.uaf.google-attestation-root-keys",
"fail_if_unknown" : false,
"data" : "[ \"MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAr7bHgiuxpwHsK7Qui8xUFmOr75gvMsd/dTEDDJdSSxtf6An7xyqpRR90PL2abxM1dEqlXnf2tqw1Ne4Xwl5jlRfdnJLmN0pTy/4lj4/7tv0Sk3iiKkypnEUtR6WfMgH0QZfKHM1+di+y9TFRtv6y//0rb+T+W8a9nsNL/ggjnar86461qO0rOs2cXjp3kOG1FEJ5MVmFmBGtnrKpa73XpXyTqRxB/M0n1n/W9nGqC4FSYa04T6N5RIZGBN2z2MT5IKGbFlbC8UrW0DxW7AYImQQcHtGl/m00QLVWutHQoVJYnFPlXTcHYvASLu+RhhsbDmxMgJJ0mcDpvsC4PjvB+TxywElgS70vE0XmLD+OJtvsBslHZvPBKCOdT0MS+tgSOIfga+z1Z1g7+DVagf7quvmag8jfPioyKvxnK/EgsTUVi2ghzq8wm27ud/mIM7AY2qEORR8Go3TVB4HzWQgpZrt3i5MIlCaY504LzSRiigHCzAPlHws+W0rB5N+er5/2pJKnfBSDiCiFAVtCLOZ7gLiMm0jhO2B6tUXHI/+MRPjy02i59lINMRRev56GKtcd9qO/0kUJWdZTdA2XoS82ixPvZtXQpUpuL12ab+9EaDK8Z4RHJYYfCT3Q5vNAXaiWQ+8PTWm2QgBR/bkwSWc+NpUFgNPN9PvQi8WEg5UmAGMCAwEAAQ==\" ]"
}
"attestationRootCertificates" : [
"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"
],
"attestationTypes" : [ 15879, 15880 ],
"upv" : [ {
@ -63,13 +65,14 @@
"aaid" : "F1D0#0003",
"description" : "Android NEVIS Mobile Authentication Biometric Authenticator",
"assertionScheme" : "UAFV1TLV",
"attestationRootCertificates" : [],
"supportedExtensions" : [
{
"id" : "ch.nevis.auth.fido.uaf.google-attestation-root-keys",
"fail_if_unknown" : false,
"data" : "[ \"MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAr7bHgiuxpwHsK7Qui8xUFmOr75gvMsd/dTEDDJdSSxtf6An7xyqpRR90PL2abxM1dEqlXnf2tqw1Ne4Xwl5jlRfdnJLmN0pTy/4lj4/7tv0Sk3iiKkypnEUtR6WfMgH0QZfKHM1+di+y9TFRtv6y//0rb+T+W8a9nsNL/ggjnar86461qO0rOs2cXjp3kOG1FEJ5MVmFmBGtnrKpa73XpXyTqRxB/M0n1n/W9nGqC4FSYa04T6N5RIZGBN2z2MT5IKGbFlbC8UrW0DxW7AYImQQcHtGl/m00QLVWutHQoVJYnFPlXTcHYvASLu+RhhsbDmxMgJJ0mcDpvsC4PjvB+TxywElgS70vE0XmLD+OJtvsBslHZvPBKCOdT0MS+tgSOIfga+z1Z1g7+DVagf7quvmag8jfPioyKvxnK/EgsTUVi2ghzq8wm27ud/mIM7AY2qEORR8Go3TVB4HzWQgpZrt3i5MIlCaY504LzSRiigHCzAPlHws+W0rB5N+er5/2pJKnfBSDiCiFAVtCLOZ7gLiMm0jhO2B6tUXHI/+MRPjy02i59lINMRRev56GKtcd9qO/0kUJWdZTdA2XoS82ixPvZtXQpUpuL12ab+9EaDK8Z4RHJYYfCT3Q5vNAXaiWQ+8PTWm2QgBR/bkwSWc+NpUFgNPN9PvQi8WEg5UmAGMCAwEAAQ==\" ]"
}
"attestationRootCertificates" : [
"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"
],
"attestationTypes" : [ 15879, 15880 ],
"upv" : [ {
@ -93,13 +96,14 @@
"aaid" : "F1D0#0004",
"description" : "Android NEVIS Mobile Authentication Device Passcode Authenticator",
"assertionScheme" : "UAFV1TLV",
"attestationRootCertificates" : [],
"supportedExtensions" : [
{
"id" : "ch.nevis.auth.fido.uaf.google-attestation-root-keys",
"fail_if_unknown" : false,
"data" : "[ \"MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAr7bHgiuxpwHsK7Qui8xUFmOr75gvMsd/dTEDDJdSSxtf6An7xyqpRR90PL2abxM1dEqlXnf2tqw1Ne4Xwl5jlRfdnJLmN0pTy/4lj4/7tv0Sk3iiKkypnEUtR6WfMgH0QZfKHM1+di+y9TFRtv6y//0rb+T+W8a9nsNL/ggjnar86461qO0rOs2cXjp3kOG1FEJ5MVmFmBGtnrKpa73XpXyTqRxB/M0n1n/W9nGqC4FSYa04T6N5RIZGBN2z2MT5IKGbFlbC8UrW0DxW7AYImQQcHtGl/m00QLVWutHQoVJYnFPlXTcHYvASLu+RhhsbDmxMgJJ0mcDpvsC4PjvB+TxywElgS70vE0XmLD+OJtvsBslHZvPBKCOdT0MS+tgSOIfga+z1Z1g7+DVagf7quvmag8jfPioyKvxnK/EgsTUVi2ghzq8wm27ud/mIM7AY2qEORR8Go3TVB4HzWQgpZrt3i5MIlCaY504LzSRiigHCzAPlHws+W0rB5N+er5/2pJKnfBSDiCiFAVtCLOZ7gLiMm0jhO2B6tUXHI/+MRPjy02i59lINMRRev56GKtcd9qO/0kUJWdZTdA2XoS82ixPvZtXQpUpuL12ab+9EaDK8Z4RHJYYfCT3Q5vNAXaiWQ+8PTWm2QgBR/bkwSWc+NpUFgNPN9PvQi8WEg5UmAGMCAwEAAQ==\" ]"
}
"attestationRootCertificates" : [
"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"
],
"attestationTypes" : [ 15879, 15880 ],
"upv" : [ {
@ -123,13 +127,14 @@
"aaid" : "F1D0#0005",
"description" : "Android NEVIS Mobile Authentication Password Authenticator",
"assertionScheme" : "UAFV1TLV",
"attestationRootCertificates" : [],
"supportedExtensions" : [
{
"id" : "ch.nevis.auth.fido.uaf.google-attestation-root-keys",
"fail_if_unknown" : false,
"data" : "[ \"MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAr7bHgiuxpwHsK7Qui8xUFmOr75gvMsd/dTEDDJdSSxtf6An7xyqpRR90PL2abxM1dEqlXnf2tqw1Ne4Xwl5jlRfdnJLmN0pTy/4lj4/7tv0Sk3iiKkypnEUtR6WfMgH0QZfKHM1+di+y9TFRtv6y//0rb+T+W8a9nsNL/ggjnar86461qO0rOs2cXjp3kOG1FEJ5MVmFmBGtnrKpa73XpXyTqRxB/M0n1n/W9nGqC4FSYa04T6N5RIZGBN2z2MT5IKGbFlbC8UrW0DxW7AYImQQcHtGl/m00QLVWutHQoVJYnFPlXTcHYvASLu+RhhsbDmxMgJJ0mcDpvsC4PjvB+TxywElgS70vE0XmLD+OJtvsBslHZvPBKCOdT0MS+tgSOIfga+z1Z1g7+DVagf7quvmag8jfPioyKvxnK/EgsTUVi2ghzq8wm27ud/mIM7AY2qEORR8Go3TVB4HzWQgpZrt3i5MIlCaY504LzSRiigHCzAPlHws+W0rB5N+er5/2pJKnfBSDiCiFAVtCLOZ7gLiMm0jhO2B6tUXHI/+MRPjy02i59lINMRRev56GKtcd9qO/0kUJWdZTdA2XoS82ixPvZtXQpUpuL12ab+9EaDK8Z4RHJYYfCT3Q5vNAXaiWQ+8PTWm2QgBR/bkwSWc+NpUFgNPN9PvQi8WEg5UmAGMCAwEAAQ==\" ]"
}
"attestationRootCertificates" : [
"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"
],
"attestationTypes" : [ 15879, 15880 ],
"upv" : [ {
@ -263,5 +268,4 @@
"publicKeyAlgAndEncodings" : [ 257 ],
"tcDisplay" : 1,
"tcDisplayContentType" : "text/plain"
}
]
}]

View File

@ -37,7 +37,7 @@ fido-uaf:
max-text-length: 2000
metadata:
path: "conf/metadata/metadata.json"
idm-connection-type: "rest"
idm-connection-type: "soap"
dispatchers:
- type: "firebase-cloud-messaging"
dry-run: false
@ -45,7 +45,6 @@ fido-uaf:
registration-redeem-url: "https://auth.agov-w.azure.adnovum.net/nevisfido/token/redeem/registration"
authentication-redeem-url: "https://auth.agov-w.azure.adnovum.net/nevisfido/token/redeem/authentication"
deregistration-redeem-url: "https://auth.agov-w.azure.adnovum.net/nevisfido/token/redeem/deregistration"
message-ttl: "180s"
- type: "png-qr-code"
registration-redeem-url: "https://auth.agov-w.azure.adnovum.net/nevisfido/token/redeem/registration"
authentication-redeem-url: "https://auth.agov-w.azure.adnovum.net/nevisfido/token/redeem/authentication"
@ -55,11 +54,8 @@ fido-uaf:
authentication-redeem-url: "https://auth.agov-w.azure.adnovum.net/nevisfido/token/redeem/authentication"
deregistration-redeem-url: "https://auth.agov-w.azure.adnovum.net/nevisfido/token/redeem/deregistration"
base-url: "ch.agov.access-t://x-callback-url/authenticate"
full-basic-attestation:
android-verification-level: "default"
android-permissive-mode-enabled: true
android-attestation-key-revocation:
reload-interval: "21600s"
basic-full-attestation:
android-verification-level: "strict"
authorization:
registration:
type: "sectoken"
@ -99,18 +95,18 @@ fido-uaf:
session-repository:
type: "sql"
jdbc-url: "jdbc:mariadb://mariadb-session-store-service.adn-agov-nevisidm-ob-01-uat:3306/nevisfido_uaf?sslMode=disable&autocommit=true"
max-connection-lifetime: "10m"
user: "${exec:/var/opt/nevisfido/default/conf/credentials/dbUser}"
password: "${exec:/var/opt/nevisfido/default/conf/credentials/dbPassword}"
schema-user: ""
schema-user-password: ""
automatic-db-schema-setup: false
max-connection-lifetime: "1800s"
connection-timeout: "30s"
min-connection-pool-size: 10
max-connection-pool-size: 10
max-connection-idle-time: "600s"
credential-repository:
type: "nevisidm"
client-id: "cfa9c9b9-119f-4dff-9bb8-86d7c0cf2720"
user-attribute: "extId"
administration-url: "https://idm.adn-agov-nevisidm-admin-01-uat:8989/nevisidm/services/v1_46/AdminService"
admin-service-version: "v1_46"
rest-url: "https://idm.adn-agov-nevisidm-admin-01-uat:8989/nevisidm"
keystore: "/var/opt/keys/own/fido-uaf-default-client-identity/keystore.p12"
keystore-type: "pkcs12"

View File

@ -1,5 +1,4 @@
otel.service.name = fido-uaf
otel.traces.sampler = always_on
otel.traces.exporter = none
otel.metrics.exporter = none
otel.logs.exporter = none

View File

@ -44,4 +44,4 @@ if is_nevisfido_healthy():
sys.exit(0)
else:
raise_last_error_in_log()
sys.exit(1)
sys.exit(1)

View File

@ -11,8 +11,8 @@ metadata:
spec:
type: "NevisFIDO"
replicas: 1
version: "8.2505.5"
gitInitVersion: "1.4.0"
version: "8.2411.2"
gitInitVersion: "1.3.0"
runAsNonRoot: true
ports:
management: 9089
@ -40,14 +40,13 @@ spec:
management:
httpGet:
path: "/nevisfido/health"
initialDelaySeconds: 30
periodSeconds: 5
timeoutSeconds: 6
failureThreshold: 30
failureThreshold: 50
podDisruptionBudget:
maxUnavailable: "50%"
git:
tag: "r-484395a405f9f7123da379fa8df82e197d2dbd71"
tag: "r-317ed268556b37656f27fb58fcffd4797cea27e4"
dir: "DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/fido2"
credentials: "git-credentials"
keystores:

View File

@ -6,5 +6,5 @@ JAVA_OPTS=(
"-javaagent:/opt/agent/opentelemetry-javaagent.jar"
"-Dotel.javaagent.logging=application"
"-Dotel.javaagent.configuration-file=/var/opt/nevisfido/default/conf/otel.properties"
"-Dotel.resource.attributes=service.version=8.2505.5,service.instance.id=$HOSTNAME"
)
"-Dotel.resource.attributes=service.version=8.2411.2,service.instance.id=$HOSTNAME"
)

View File

@ -1,21 +1,3 @@
fido2:
enabled: true
user-presence-requirement: "always"
rp-name: "AGOV-RelPartName"
rp-id: "adnovum.net"
origins:
- "https://ob.agov-w.azure.adnovum.net"
- "https://auth.agov-w.azure.adnovum.net"
- "https://nevisidm.agov-w.azure.adnovum.net"
signature-algorithms:
- "ES256"
- "EdDSA"
display-name-source: "email"
metadata:
allow-listing-enabled: false
timeout:
user-verification: "300s"
no-user-verification: "120s"
server:
port: 9443
protocol: "https"
@ -42,5 +24,27 @@ credential-repository:
truststore-passphrase: "${exec:/var/opt/keys/trust/fido2-idp-extended-truststore/keypass}"
truststore-type: "pkcs12"
user-attribute: "extId"
fido2:
enabled: true
rp-name: "AGOV-RelPartName"
rp-id: "adnovum.net"
origins:
- "https://ob.agov-w.azure.adnovum.net"
- "https://auth.agov-w.azure.adnovum.net"
- "https://nevisidm.agov-w.azure.adnovum.net"
signature-algorithms:
- "RS1"
- "RS256"
- "RS384"
- "RS512"
- "ES256"
- "ES384"
- "ES512"
display-name-source: "email"
metadata:
allow-listing-enabled: false
timeout:
user-verification: "300s"
no-user-verification: "120s"
session-repository:
type: "in-memory"

View File

@ -1,5 +1,4 @@
otel.service.name = fido2
otel.traces.sampler = always_on
otel.traces.exporter = none
otel.metrics.exporter = none
otel.logs.exporter = none

View File

@ -44,4 +44,4 @@ if is_nevisfido_healthy():
sys.exit(0)
else:
raise_last_error_in_log()
sys.exit(1)
sys.exit(1)

View File

@ -11,8 +11,8 @@ metadata:
spec:
type: "NevisLogrend"
replicas: 1
version: "8.2505.5"
gitInitVersion: "1.4.0"
version: "8.2411.2"
gitInitVersion: "1.3.0"
runAsNonRoot: true
ports:
server: 8988
@ -38,14 +38,13 @@ spec:
startupProbe:
server:
tcpSocket: true
initialDelaySeconds: 30
periodSeconds: 5
timeoutSeconds: 4
failureThreshold: 30
failureThreshold: 50
podDisruptionBudget:
maxUnavailable: "50%"
git:
tag: "r-5e17b7ae74eadb8800587a4f4db74406a7e21e95"
tag: "r-25da499166053032321f08e80ed9ec3136c59cd1"
dir: "DEFAULT-ADN-AGOV-PROJECT/DEFAULT-ADN-AGOV-INV/logrend"
credentials: "git-credentials"
podSecurity:

View File

@ -10,5 +10,5 @@ JAVA_OPTS=(
"-javaagent:/opt/agent/opentelemetry-javaagent.jar"
"-Dotel.javaagent.logging=application"
"-Dotel.javaagent.configuration-file=/var/opt/nevislogrend/default/conf/otel.properties"
"-Dotel.resource.attributes=service.version=8.2505.5,service.instance.id=$HOSTNAME"
)
"-Dotel.resource.attributes=service.version=8.2411.2,service.instance.id=$HOSTNAME"
)

View File

@ -1,5 +1,3 @@
ico=image/x-icon
json=application/json
woff=font/woff
woff2=font/woff2
woff2=font/woff2

View File

@ -1,5 +1,4 @@
otel.service.name = logrend
otel.traces.sampler = always_on
otel.traces.exporter = none
otel.metrics.exporter = none
otel.logs.exporter = none

View File

@ -9,6 +9,7 @@ agov-ident.invalid-url.message=Link kann nicht verarbeitet werden
agov-ident.invalid-url.title=Ung&uuml;ltiger Link
agov-ident.onboarding=Registrierung & Verifikation
agov-ident.retry=Versuchen Sie es erneut
button.submit=Senden
darkModeSwitch.aria.label=Dark-Mode-Schalter
error.policy.failed=Das neue Passwort stimmt nicht mit der Richtlinie &uuml;berein.
error_1=Bitte &uuml;berpr&uuml;fen Sie Ihre Eingaben.
@ -59,7 +60,7 @@ general.edit=&Auml;ndern
general.email=E-Mail
general.email.address=E-Mail-Adresse
general.entryCode=Code-Eingabe
general.fieldRequired=Erforderliches Feld
general.fieldRequired=Erforderliches Feld.
general.getStarted=Los geht's
general.goAGOVHelp=Weiter zur AGOV help
general.goAccessApp=Login mit AGOV access
@ -77,8 +78,8 @@ general.recovery=Wiederherstellung
general.recovery.help.link=https://help.agov.ch/?c=100recovery
general.recoveryCode.downloadPdf=Als PDF herunterladen
general.recoveryCode.inputLabel=Wiederherstellungscode
general.recoveryCode.repeatCodeError=Der von Ihnen eingegebene Code war nicht korrekt. Bitte vergewissern Sie sich, dass Sie ihn richtig abgespeichert haben, und versuchen Sie es erneut.
general.recoveryCode.repeatCodeModal.description=Um sicherzustellen, dass Sie Ihren Code richtig gespeichert haben, wiederholen Sie ihn bitte unten. Ein verlorener oder falsch gespeicherter Wiederherstellungscode kann die Wiederherstellung Ihres Kontos erschweren.
general.recoveryCode.repeatCodeError=Der von Ihnen eingegebene Code war nicht korrekt. Bitte vergewissern Sie sich, dass Sie ihn richtig abgespeichert haben, und fahren Sie dann mit der erneuten Eingabe fort.
general.recoveryCode.repeatCodeModal.description=Ein verlorener oder falsch gespeicherter Wiederherstellungscode kann die Wiederherstellung Ihres Kontos erschweren. Um sicherzustellen, dass Sie Ihren Code richtig gespeichert haben, wiederholen Sie ihn bitte unten.
general.recoveryCode.repeatCodeModal.title=Wiederherstellungscode wiederholen
general.recoveryCode.reveal=Wiederherstellungscode enth&uuml;llen
general.recoveryOngoing=Wiederherstellung nicht abgeschlossen
@ -101,8 +102,8 @@ language.fr=Fran&ccedil;ais
language.it=Italiano
language.rm=Rumantsch
languageDropdown.aria.label=Sprache w&auml;hlen
loainfo.description.200=Um auf diese Applikation zuzugreifen, m&uuml;ssen wir Ihre Angaben verifizieren. Der Vorgang kann bis zu 2&ndash;3 Tage dauern.
loainfo.description.300=Um auf diese Applikation zuzugreifen, m&uuml;ssen wir Ihre Angaben verifizieren. Sie k&ouml;nnen Ihre bevorzugte Methode im n&auml;chsten Schritt ausw&auml;hlen.
loainfo.description.200=Um auf diese Applikation zuzugreifen, m&uuml;ssen wir Ihre Angaben verifizieren. Der Vorgang kann bis zu 2 - 3 Tage dauern.
loainfo.description.300=Um auf diese Applikation zuzugreifen, m&uuml;ssen wir Ihre Angaben durch einen von zwei Vorg&auml;ngen verifizieren. Sie k&ouml;nnen die bevorzugte Methode im n&auml;chsten Schritt ausw&auml;hlen.
loainfo.description.400=Bitte AHV-Nummer angeben, um auf die Applikation zuzugreifen.
loainfo.helper=Ihre pers&ouml;nlichen Daten m&uuml;ssen &uuml;berpr&uuml;ft werden!
loainfo.later=Sp&auml;ter
@ -111,8 +112,8 @@ loainfo.startVerification=Verifikation starten
loainfo.title=Verifizieren Sie Ihre Daten
mauth_usernameless.EID=Mit Schweizer E-ID fortfahren
mauth_usernameless.banner.error=Authentifizierung unterbrochen.<br>Bitte versuchen Sie es erneut, nachdem die Seite neu geladen wurde.
mauth_usernameless.banner.info=Scan erfolgreich. Bitte fahren Sie in der AGOV access App fort.
mauth_usernameless.banner.success=Authentifizierung erfolgreich.<br>Bitte warten Sie, bis Sie eingeloggt werden.
mauth_usernameless.banner.info=Scan erfolgreich.<br>Bitte fahren Sie in der AGOV access App fort.
mauth_usernameless.banner.success=Authentifizierung erfolgreich!<br>Bitte warten Sie, bis Sie eingeloggt werden.
mauth_usernameless.cannotLogin=Zugriff auf App / Sicherheitsschl&uuml;ssel verloren?
mauth_usernameless.cannotLogin.accessApp=Zugriff auf App verloren?
mauth_usernameless.cannotLogin.securityKey=Zugriff auf Sicherheitsschl&uuml;ssel verloren?
@ -163,7 +164,7 @@ prompt.newpassword=Neues Passwort
prompt.newpassword.confirm=Passwort best&auml;tigen
prompt.password=Passwort
prompt.userid=Benutzer-ID
providePhoneNumber.banner=Die Mobilnummer muss f&uuml;r den Empfang von SMS geeignet sein. Sie wird nicht verwendet, um Sie zu kontaktieren.
providePhoneNumber.banner=Die Mobilnummer muss f&uuml;r den Empfang von SMS geeignet sein.<br>Diese Mobilnummer wird nicht verwendet, um Sie zu kontaktieren.
providePhoneNumber.description=AGOV erlaubt nun die Wiederherstellung mittels Mobilnummer. So k&ouml;nnen Sie w&auml;hrend der Wiederherstellung mit einer SMS fortfahren, wenn Sie Ihren Wiederherstellungscode verloren haben.
providePhoneNumber.errorBanner=Die Mobilnummern stimmen nicht &uuml;berein. Bitte versuchen Sie es erneut.
providePhoneNumber.inputLabel=Mobilnummer (optional)
@ -171,7 +172,7 @@ providePhoneNumber.laterModal.description1=Ohne Mobilnummer kann die Wiederherst
providePhoneNumber.laterModal.description2=Durch Hinzuf&uuml;gen einer Mobilnummer k&ouml;nnen Sie Ihr Konto in wenigen Minuten wiederherstellen.
providePhoneNumber.laterModal.description3=Diese Mobilnummer wird nicht verwendet, um Sie zu kontaktieren.
providePhoneNumber.laterModal.title=Ohne Mobilnummer weiterfahren?
providePhoneNumber.modal.description=Um sicherzustellen, dass Sie Ihre Mobilnummer richtig gespeichert haben, wiederholen Sie sie bitte unten. Eine falsch gespeicherte Mobilnummer kann die Wiederherstellung Ihres Kontos erschweren.
providePhoneNumber.modal.description=Eine falsch gespeicherte Mobilnummer kann die Wiederherstellung Ihres Kontos erschweren. Um sicherzustellen, dass Sie Ihre Mobilnummer richtig gespeichert haben, wiederholen Sie sie bitte unten.
providePhoneNumber.modal.inputLabel=Mobilnummer
providePhoneNumber.modal.title=Mobilnummer wiederholen
providePhoneNumber.saveButtonText=Speichern
@ -180,14 +181,12 @@ pwreset.done.info=Ihr Passwort wurde erfolgreich ge&auml;ndert. Bitte klicken Si
pwreset.email.sent=Wenn Ihre Benutzer-ID existiert, haben Sie eine E-Mail erhalten, um Ihr Passwort zurückzusetzen..
pwreset.info.linktext=Passwort vergessen
pwreset.noticket=Ihr Link ist nicht mehr g&uuml;ltig. Bitte generieren Sie ein Neuen.
qrCode.label=Klicken Sie, um den QR-Code in einem Fenster zu &ouml;ffnen.
recovery_accessapp_auth.accessAppRegistered=AGOV access App schon registriert
recovery_accessapp_auth.instruction1=Sie haben bereits eine neue AGOV access App !!!ACCESS_APP_NAME!!! im Rahmen des Wiederherstellungsprozesses registriert.
recovery_accessapp_auth.instruction2=Verwenden Sie !!!ACCESS_APP_NAME!!! um sich zu identifizieren.
recovery_check_code.banner.lockedError=Zu viele Fehlversuche. Bitte versuchen Sie es in ein paar Minuten noch einmal.
recovery_check_code.codeIncorrect=Der eingegebene Code ist nicht korrekt. Bitte versuchen Sie es erneut.
recovery_check_code.enterRecoveryCode=Wiederherstellungscode
recovery_check_code.expired=Zu viele Versuche oder Ihr Wiederherstellungscode ist abgelaufen.
recovery_check_code.enterRecoveryCode=Wiederherstellungscode eingeben
recovery_check_code.instruction=Bitte geben Sie unten Ihren pers&ouml;nlichen 12-stelligen Wiederherstellungscode ein. Sie haben den Wiederherstellungscode in einer PDF-Datei bei der Registrierung oder in AGOV me erhalten.
recovery_check_code.invalid.code=Code ist ung&uuml;ltig
recovery_check_code.invalid.code.required=Code erforderlich
@ -245,7 +244,6 @@ recovery_questionnaire_reason_selection.instruction=Bitte w&auml;hlen Sie einen
recovery_start_info.banner.warning=Sie k&ouml;nnen Ihr Konto nicht nutzen, bis der Wiederherstellungsprozess abgeschlossen ist.
recovery_start_info.instruction=W&auml;hrend des Wiederherstellungsprozesses werden Sie einen neuen Login-Faktor registrieren. Wenn Ihr Konto verifizierte Informationen enth&auml;lt, m&uuml;ssen Sie zum Abschluss des Wiederherstellungsprozesses m&ouml;glicherweise auch einen Verifikationsprozess durchlaufen.
recovery_start_info.title=Sie sind dabei, den Wiederherstellungsprozess zu starten
submit.button.label=Senden
title=NEVIS SSO Portal
title.login=Login
title.pwchange.label=Passwort &auml;ndern

View File

@ -9,6 +9,7 @@ agov-ident.invalid-url.message=Link kann nicht verarbeitet werden
agov-ident.invalid-url.title=Ung&uuml;ltiger Link
agov-ident.onboarding=Registrierung & Verifikation
agov-ident.retry=Versuchen Sie es erneut
button.submit=Senden
darkModeSwitch.aria.label=Dark-Mode-Schalter
error.policy.failed=Das neue Passwort stimmt nicht mit der Richtlinie &uuml;berein.
error_1=Bitte &uuml;berpr&uuml;fen Sie Ihre Eingaben.
@ -59,7 +60,7 @@ general.edit=&Auml;ndern
general.email=E-Mail
general.email.address=E-Mail-Adresse
general.entryCode=Code-Eingabe
general.fieldRequired=Erforderliches Feld
general.fieldRequired=Erforderliches Feld.
general.getStarted=Los geht's
general.goAGOVHelp=Weiter zur AGOV help
general.goAccessApp=Login mit AGOV access
@ -77,8 +78,8 @@ general.recovery=Wiederherstellung
general.recovery.help.link=https://help.agov.ch/?c=100recovery
general.recoveryCode.downloadPdf=Als PDF herunterladen
general.recoveryCode.inputLabel=Wiederherstellungscode
general.recoveryCode.repeatCodeError=Der von Ihnen eingegebene Code war nicht korrekt. Bitte vergewissern Sie sich, dass Sie ihn richtig abgespeichert haben, und versuchen Sie es erneut.
general.recoveryCode.repeatCodeModal.description=Um sicherzustellen, dass Sie Ihren Code richtig gespeichert haben, wiederholen Sie ihn bitte unten. Ein verlorener oder falsch gespeicherter Wiederherstellungscode kann die Wiederherstellung Ihres Kontos erschweren.
general.recoveryCode.repeatCodeError=Der von Ihnen eingegebene Code war nicht korrekt. Bitte vergewissern Sie sich, dass Sie ihn richtig abgespeichert haben, und fahren Sie dann mit der erneuten Eingabe fort.
general.recoveryCode.repeatCodeModal.description=Ein verlorener oder falsch gespeicherter Wiederherstellungscode kann die Wiederherstellung Ihres Kontos erschweren. Um sicherzustellen, dass Sie Ihren Code richtig gespeichert haben, wiederholen Sie ihn bitte unten.
general.recoveryCode.repeatCodeModal.title=Wiederherstellungscode wiederholen
general.recoveryCode.reveal=Wiederherstellungscode enth&uuml;llen
general.recoveryOngoing=Wiederherstellung nicht abgeschlossen
@ -101,8 +102,8 @@ language.fr=Fran&ccedil;ais
language.it=Italiano
language.rm=Rumantsch
languageDropdown.aria.label=Sprache w&auml;hlen
loainfo.description.200=Um auf diese Applikation zuzugreifen, m&uuml;ssen wir Ihre Angaben verifizieren. Der Vorgang kann bis zu 2&ndash;3 Tage dauern.
loainfo.description.300=Um auf diese Applikation zuzugreifen, m&uuml;ssen wir Ihre Angaben verifizieren. Sie k&ouml;nnen Ihre bevorzugte Methode im n&auml;chsten Schritt ausw&auml;hlen.
loainfo.description.200=Um auf diese Applikation zuzugreifen, m&uuml;ssen wir Ihre Angaben verifizieren. Der Vorgang kann bis zu 2 - 3 Tage dauern.
loainfo.description.300=Um auf diese Applikation zuzugreifen, m&uuml;ssen wir Ihre Angaben durch einen von zwei Vorg&auml;ngen verifizieren. Sie k&ouml;nnen die bevorzugte Methode im n&auml;chsten Schritt ausw&auml;hlen.
loainfo.description.400=Bitte AHV-Nummer angeben, um auf die Applikation zuzugreifen.
loainfo.helper=Ihre pers&ouml;nlichen Daten m&uuml;ssen &uuml;berpr&uuml;ft werden!
loainfo.later=Sp&auml;ter
@ -111,8 +112,8 @@ loainfo.startVerification=Verifikation starten
loainfo.title=Verifizieren Sie Ihre Daten
mauth_usernameless.EID=Mit Schweizer E-ID fortfahren
mauth_usernameless.banner.error=Authentifizierung unterbrochen.<br>Bitte versuchen Sie es erneut, nachdem die Seite neu geladen wurde.
mauth_usernameless.banner.info=Scan erfolgreich. Bitte fahren Sie in der AGOV access App fort.
mauth_usernameless.banner.success=Authentifizierung erfolgreich.<br>Bitte warten Sie, bis Sie eingeloggt werden.
mauth_usernameless.banner.info=Scan erfolgreich.<br>Bitte fahren Sie in der AGOV access App fort.
mauth_usernameless.banner.success=Authentifizierung erfolgreich!<br>Bitte warten Sie, bis Sie eingeloggt werden.
mauth_usernameless.cannotLogin=Zugriff auf App / Sicherheitsschl&uuml;ssel verloren?
mauth_usernameless.cannotLogin.accessApp=Zugriff auf App verloren?
mauth_usernameless.cannotLogin.securityKey=Zugriff auf Sicherheitsschl&uuml;ssel verloren?
@ -163,7 +164,7 @@ prompt.newpassword=Neues Passwort
prompt.newpassword.confirm=Passwort best&auml;tigen
prompt.password=Passwort
prompt.userid=Benutzer-ID
providePhoneNumber.banner=Die Mobilnummer muss f&uuml;r den Empfang von SMS geeignet sein. Sie wird nicht verwendet, um Sie zu kontaktieren.
providePhoneNumber.banner=Die Mobilnummer muss f&uuml;r den Empfang von SMS geeignet sein.<br>Diese Mobilnummer wird nicht verwendet, um Sie zu kontaktieren.
providePhoneNumber.description=AGOV erlaubt nun die Wiederherstellung mittels Mobilnummer. So k&ouml;nnen Sie w&auml;hrend der Wiederherstellung mit einer SMS fortfahren, wenn Sie Ihren Wiederherstellungscode verloren haben.
providePhoneNumber.errorBanner=Die Mobilnummern stimmen nicht &uuml;berein. Bitte versuchen Sie es erneut.
providePhoneNumber.inputLabel=Mobilnummer (optional)
@ -171,7 +172,7 @@ providePhoneNumber.laterModal.description1=Ohne Mobilnummer kann die Wiederherst
providePhoneNumber.laterModal.description2=Durch Hinzuf&uuml;gen einer Mobilnummer k&ouml;nnen Sie Ihr Konto in wenigen Minuten wiederherstellen.
providePhoneNumber.laterModal.description3=Diese Mobilnummer wird nicht verwendet, um Sie zu kontaktieren.
providePhoneNumber.laterModal.title=Ohne Mobilnummer weiterfahren?
providePhoneNumber.modal.description=Um sicherzustellen, dass Sie Ihre Mobilnummer richtig gespeichert haben, wiederholen Sie sie bitte unten. Eine falsch gespeicherte Mobilnummer kann die Wiederherstellung Ihres Kontos erschweren.
providePhoneNumber.modal.description=Eine falsch gespeicherte Mobilnummer kann die Wiederherstellung Ihres Kontos erschweren. Um sicherzustellen, dass Sie Ihre Mobilnummer richtig gespeichert haben, wiederholen Sie sie bitte unten.
providePhoneNumber.modal.inputLabel=Mobilnummer
providePhoneNumber.modal.title=Mobilnummer wiederholen
providePhoneNumber.saveButtonText=Speichern
@ -180,14 +181,12 @@ pwreset.done.info=Ihr Passwort wurde erfolgreich ge&auml;ndert. Bitte klicken Si
pwreset.email.sent=Wenn Ihre Benutzer-ID existiert, haben Sie eine E-Mail erhalten, um Ihr Passwort zurückzusetzen..
pwreset.info.linktext=Passwort vergessen
pwreset.noticket=Ihr Link ist nicht mehr g&uuml;ltig. Bitte generieren Sie ein Neuen.
qrCode.label=Klicken Sie, um den QR-Code in einem Fenster zu &ouml;ffnen.
recovery_accessapp_auth.accessAppRegistered=AGOV access App schon registriert
recovery_accessapp_auth.instruction1=Sie haben bereits eine neue AGOV access App !!!ACCESS_APP_NAME!!! im Rahmen des Wiederherstellungsprozesses registriert.
recovery_accessapp_auth.instruction2=Verwenden Sie !!!ACCESS_APP_NAME!!! um sich zu identifizieren.
recovery_check_code.banner.lockedError=Zu viele Fehlversuche. Bitte versuchen Sie es in ein paar Minuten noch einmal.
recovery_check_code.codeIncorrect=Der eingegebene Code ist nicht korrekt. Bitte versuchen Sie es erneut.
recovery_check_code.enterRecoveryCode=Wiederherstellungscode
recovery_check_code.expired=Zu viele Versuche oder Ihr Wiederherstellungscode ist abgelaufen.
recovery_check_code.enterRecoveryCode=Wiederherstellungscode eingeben
recovery_check_code.instruction=Bitte geben Sie unten Ihren pers&ouml;nlichen 12-stelligen Wiederherstellungscode ein. Sie haben den Wiederherstellungscode in einer PDF-Datei bei der Registrierung oder in AGOV me erhalten.
recovery_check_code.invalid.code=Code ist ung&uuml;ltig
recovery_check_code.invalid.code.required=Code erforderlich
@ -245,7 +244,6 @@ recovery_questionnaire_reason_selection.instruction=Bitte w&auml;hlen Sie einen
recovery_start_info.banner.warning=Sie k&ouml;nnen Ihr Konto nicht nutzen, bis der Wiederherstellungsprozess abgeschlossen ist.
recovery_start_info.instruction=W&auml;hrend des Wiederherstellungsprozesses werden Sie einen neuen Login-Faktor registrieren. Wenn Ihr Konto verifizierte Informationen enth&auml;lt, m&uuml;ssen Sie zum Abschluss des Wiederherstellungsprozesses m&ouml;glicherweise auch einen Verifikationsprozess durchlaufen.
recovery_start_info.title=Sie sind dabei, den Wiederherstellungsprozess zu starten
submit.button.label=Senden
title=NEVIS SSO Portal
title.login=Login
title.pwchange.label=Passwort &auml;ndern

View File

@ -9,6 +9,7 @@ agov-ident.invalid-url.message=Link can't be processed
agov-ident.invalid-url.title=Invalid Link
agov-ident.onboarding=Registration & Verification
agov-ident.retry=Try again
button.submit=Submit
darkModeSwitch.aria.label=Dark mode toggle
error.policy.failed=The new password does not comply with the policy.
error_1=Please check your input.
@ -59,7 +60,7 @@ general.edit=Edit
general.email=Email
general.email.address=Email address
general.entryCode=Code entry
general.fieldRequired=Field required
general.fieldRequired=Field required.
general.getStarted=Get started
general.goAGOVHelp=Go to AGOV help
general.goAccessApp=Login with AGOV access
@ -77,8 +78,8 @@ general.recovery=Recovery
general.recovery.help.link=https://help.agov.ch/?c=100recovery
general.recoveryCode.downloadPdf=Download as PDF
general.recoveryCode.inputLabel=Recovery code
general.recoveryCode.repeatCodeError=The code you entered was incorrect. Please ensure you have stored it correctly and try again.
general.recoveryCode.repeatCodeModal.description=To ensure you have recorded your code correctly, please repeat it below. A lost or incorrectly stored recovery code can make it more difficult to recover your account.
general.recoveryCode.repeatCodeError=The code you entered was incorrect. Please ensure you have stored it correctly, then continue to resubmit.
general.recoveryCode.repeatCodeModal.description=A lost or incorrectly stored recovery code can make it more difficult to recover your account. To ensure you have recorded your code correctly, please repeat it below.
general.recoveryCode.repeatCodeModal.title=Repeat recovery code
general.recoveryCode.reveal=Reveal recovery code
general.recoveryOngoing=Ongoing recovery
@ -101,8 +102,8 @@ language.fr=Fran&ccedil;ais
language.it=Italiano
language.rm=Rumantsch
languageDropdown.aria.label=Select language
loainfo.description.200=To access the application, we need to verify your data. The process can take up to 2-3 days.
loainfo.description.300=To access the application we need to verify your data. You can choose your preferred process in the next step.
loainfo.description.200=To access the application, we need to verify your data. The process can take up to 2 - 3 days.
loainfo.description.300=To access the application we need to verify your data through one of two processes. You can choose your preferred process in the next step.
loainfo.description.400=To access the application we need you to add your SSN (AHV) number.
loainfo.helper=Your data needs to be verified!
loainfo.later=Later
@ -111,8 +112,8 @@ loainfo.startVerification=Start verification
loainfo.title=Verify your data
mauth_usernameless.EID=Continue with CH E-ID
mauth_usernameless.banner.error=Authentication interrupted.<br>Please try again when the page reloads.
mauth_usernameless.banner.info=Scan successful. Please continue in the AGOV access app.
mauth_usernameless.banner.success=Authentication successful.<br>Please wait to be logged in.
mauth_usernameless.banner.info=Scan successful.<br>Please continue in the AGOV access app.
mauth_usernameless.banner.success=Authentication successful!<br>Please wait to be logged in.
mauth_usernameless.cannotLogin=Lost access to your app / security key?
mauth_usernameless.cannotLogin.accessApp=Lost access to your app?
mauth_usernameless.cannotLogin.securityKey=Lost access to your security key?
@ -163,7 +164,7 @@ prompt.newpassword=New Password
prompt.newpassword.confirm=Confirm Password
prompt.password=Password
prompt.userid=User-ID
providePhoneNumber.banner=Phone number must be able to receive SMS. It will not be used to contact you.
providePhoneNumber.banner=Phone number must be able to receive SMS.<br>This phone number will not be used to contact you.
providePhoneNumber.description=AGOV now supports recovery with your phone number. This will allow you to continue with an SMS during recovery if you have lost access to your recovery code.
providePhoneNumber.errorBanner=Phone numbers do not match. Please try again.
providePhoneNumber.inputLabel=Phone number (optional)
@ -171,7 +172,7 @@ providePhoneNumber.laterModal.description1=Without a phone number, a recovery of
providePhoneNumber.laterModal.description2=Adding a phone number helps you to recover your account in a matter of minutes.
providePhoneNumber.laterModal.description3=This phone number will not be used to contact you.
providePhoneNumber.laterModal.title=Continue without a phone number?
providePhoneNumber.modal.description=To ensure you have recorded your phone number correctly, please repeat it below. An incorrectly stored phone number can make it more difficult to recover your account.
providePhoneNumber.modal.description=An incorrectly stored phone number can make it more difficult to recover your account. To ensure you have recorded your phone number correctly, please repeat it below.
providePhoneNumber.modal.inputLabel=Phone number
providePhoneNumber.modal.title=Repeat phone number
providePhoneNumber.saveButtonText=Save
@ -180,14 +181,12 @@ pwreset.done.info=Your password was successfully changed. Please click on contin
pwreset.email.sent=If your user ID exists, an email to reset your password has been sent to you.
pwreset.info.linktext=Password forgotten
pwreset.noticket=Your password reset link is no longer valid. Please generate a new one.
qrCode.label=Click to open QR code in pop-up window.
recovery_accessapp_auth.accessAppRegistered=AGOV access app already registered
recovery_accessapp_auth.instruction1=You have already registered a new AGOV access app !!!ACCESS_APP_NAME!!! as part of the recovery process.
recovery_accessapp_auth.instruction2=Please use !!!ACCESS_APP_NAME!!! to identify you.
recovery_check_code.banner.lockedError=Too many invalid input attempts. Please try again in a few minutes.
recovery_check_code.codeIncorrect=Code entered is incorrect. Please try again.
recovery_check_code.enterRecoveryCode=Recovery code
recovery_check_code.expired=Too many attempts or your recovery code has expired.
recovery_check_code.enterRecoveryCode=Enter recovery code
recovery_check_code.instruction=Please enter below your personal 12-digit recovery code. You will have received the recovery code as a PDF file during registration or in AGOV me.
recovery_check_code.invalid.code=The code is invalid
recovery_check_code.invalid.code.required=Code required
@ -200,9 +199,9 @@ recovery_check_code.too_many_tries.instruction2=Please go to AGOV help in order
recovery_check_noCode.banner.error=Too many attempts.
recovery_check_noCode.instruction1=You might have tried to enter the recovery code too many times.
recovery_check_noCode.instruction2=Please close the web browser and start the account recovery again in ten minutes from <a class='link' href='https://agov.ch/me'>https://agov.ch/me</a>.
recovery_code.banner.error=Please reveal your recovery code to be able to continue.
recovery_code.banner.error=Please reveal your new code to be able to continue.
recovery_code.instruction=Recovery codes help you gain access to your account in case you lost all of your login factors. Please store the recovery code in a safe place.
recovery_code.newRecoveryCode=Introducing recovery code
recovery_code.newRecoveryCode=Introducing Recovery Code
recovery_code.validUntil=Valid until:
recovery_fidokey_auth.button=Start key authentication
recovery_fidokey_auth.fidoInstruction=Click on "Start key authentication"
@ -245,7 +244,6 @@ recovery_questionnaire_reason_selection.instruction=Please select the reason you
recovery_start_info.banner.warning=You will not be able to use your account until the recovery process has been concluded.
recovery_start_info.instruction=During the recovery process you will register a new login factor. If your account contains any verified information you might also have to go through a verification process to finish the recovery.
recovery_start_info.title=You are about to start the recovery process
submit.button.label=Submit
title=NEVIS SSO Portal
title.login=Login
title.pwchange.label=Password Change

View File

@ -1,14 +1,15 @@
agov-ident.done.message=Votre compte AGOV est maintenant pr&ecirc;t &agrave; &ecirc;tre utilis&eacute;. Veuillez fermer cette page.
agov-ident.done.title=Termin&eacute;
agov-ident.failed.instruction=Vous devez disposer d'un compte AGOV et passer avec succ&egrave;s la v&eacute;rification des donn&eacute;es sugg&eacute;r&eacute;e pour terminer l'inscription. Veuillez r&eacute;essayer.
agov-ident.failed.instruction=Vous avez besoin d'un compte AGOV et de passer la v&eacute;rification des donn&eacute;es sugg&eacute;r&eacute;e pour terminer avec succ&egrave;s l'enregistrement. Veuillez r&eacute;essayer.
agov-ident.failed.message=Enregistrement annul&eacute; ou v&eacute;rification des donn&eacute;es report&eacute;e
agov-ident.failed.title=V&eacute;rification requise
agov-ident.invalid-url.instruction=Le lien que vous avez utilis&eacute; pour acc&eacute;der &agrave; cette page n'est pas valide. Veuillez vous assurer de l'utiliser tel qu'il a &eacute;t&eacute; re&ccedil;u, sans fautes de frappe, ou cliquez directement sur la page o&ugrave; il est publi&eacute;.
agov-ident.invalid-url.instruction=Le lien que vous avez utilis&eacute; pour acc&eacute;der &agrave; cette page n'est pas valide. Veillez l'utiliser tel qu'il a &eacute;t&eacute; re&ccedil;u, sans fautes de frappe, ou cliquez directement sur la page o&ugrave; il est publi&eacute;.
agov-ident.invalid-url.message=Le lien ne peut pas &ecirc;tre trait&eacute;
agov-ident.invalid-url.title=Lien non valide
agov-ident.onboarding=Enregistrement et v&eacute;rification
agov-ident.retry=Essayez &agrave; nouveau
button.submit=Envoyer
darkModeSwitch.aria.label=Activer l'apparence sombre
error.policy.failed=Votre nouveau mot de passe ne conforme pas aux mesures de s&eacute;curit&eacute;
error_1=Veuillez v&eacute;rifier votre saisie.
@ -59,7 +60,7 @@ general.edit=Editer
general.email=E-mail
general.email.address=Adresse e-mail
general.entryCode=Entrer le code
general.fieldRequired=Champ requis
general.fieldRequired=Champ requis.
general.getStarted=D&eacute;marrer
general.goAGOVHelp=Rendez-vous sur AGOV help
general.goAccessApp=Login avec AGOV access
@ -77,15 +78,15 @@ general.recovery=R&eacute;cup&eacute;ration
general.recovery.help.link=https://help.agov.ch/?c=100recovery
general.recoveryCode.downloadPdf=T&eacute;l&eacute;charger en format PDF
general.recoveryCode.inputLabel=Code de r&eacute;cup&eacute;ration
general.recoveryCode.repeatCodeError=Le code que vous avez saisi est incorrect. Veuillez vous assurer que l'avez enregistr&eacute; correctement et r&eacute;essayer.
general.recoveryCode.repeatCodeModal.description=Pour vous assurer que vous avez correctement enregistr&eacute; votre code, veillez le r&eacute;p&eacute;ter ci-dessous. Un code de r&eacute;cup&eacute;ration perdu ou mal enregistr&eacute; peut rendre la r&eacute;cup&eacute;ration de votre compte plus difficile.
general.recoveryCode.repeatCodeError=Le code que vous avez saisi est incorrect. Veuillez vous assurer que vous l'avez enregistr&eacute; correctement, puis essayer de le soumettre &agrave; nouveau.
general.recoveryCode.repeatCodeModal.description=Un code de r&eacute;cup&eacute;ration perdu ou mal enregistr&eacute; peut rendre la r&eacute;cup&eacute;ration de votre compte plus difficile. Pour vous assurer que vous avez correctement enregistr&eacute; votre code, veuillez le r&eacute;p&eacute;ter ci-dessous.
general.recoveryCode.repeatCodeModal.title=R&eacute;p&eacute;ter le code de r&eacute;cup&eacute;ration
general.recoveryCode.reveal=R&eacute;v&eacute;ler le code de r&eacute;cup&eacute;ration
general.recoveryOngoing=R&eacute;cup&eacute;ration en cours
general.register=Cr&eacute;er un compte
general.registerNow=Enregistrez-vous d&egrave;s maintenant!
general.registration=Enregistrement
general.registration.dontHaveAnAccountYet=Vous n'avez pas encore de compte AGOV ?
general.registration.dontHaveAnAccountYet=Vous n'avez pas de compte AGOV ?
general.registration.seeOptions=Voir les options d'enregistrement
general.securityKey=Cl&eacute; de s&eacute;curit&eacute;
general.skip.content=Passer au contenu principal
@ -101,8 +102,8 @@ language.fr=Fran&ccedil;ais
language.it=Italiano
language.rm=Rumantsch
languageDropdown.aria.label=S&eacute;lectionner la langue
loainfo.description.200=Pour acc&eacute;der &agrave; l'application, nous devons v&eacute;rifier vos donn&eacute;es. Ce processus peut prendre jusqu'&agrave; 2&ndash;3 jours.
loainfo.description.300=Pour acc&eacute;der &agrave; l'application, nous devons v&eacute;rifier vos donn&eacute;es. Vous pouvez choisir la proc&eacute;dure que vous pr&eacute;f&eacute;rez &agrave; l'&eacute;tape suivante.
loainfo.description.200=Pour acc&eacute;der &agrave; l'application, nous devons v&eacute;rifier vos donn&eacute;es. Ce processus peut prendre jusqu'&agrave; 2 ou 3 jours.
loainfo.description.300=Pour acc&eacute;der &agrave; l'application, nous devons v&eacute;rifier vos donn&eacute;es par le biais de l'une des deux proc&eacute;dures suivantes. Vous pouvez choisir la proc&eacute;dure que vous pr&eacute;f&eacute;rez &agrave; l'&eacute;tape suivante.
loainfo.description.400=Veuillez saisir votre num&eacute;ro AVS pour acc&eacute;der &agrave; l'application.
loainfo.helper=Vos donn&eacute;es doivent &ecirc;tre v&eacute;rifi&eacute;es!
loainfo.later=Plus tard
@ -111,14 +112,14 @@ loainfo.startVerification=D&eacute;marrer la v&eacute;rification
loainfo.title=V&eacute;rifiez vos donn&eacute;es
mauth_usernameless.EID=Continuer avec l'e-ID suisse
mauth_usernameless.banner.error=Authentification interrompue.<br>Veuillez r&eacute;essayer lorsque la page sera recharg&eacute;e.
mauth_usernameless.banner.info=Scan r&eacute;ussi. Veuillez continuer dans l'application AGOV access.
mauth_usernameless.banner.success=Authentification r&eacute;ussie.<br>Veuillez attendre d'&ecirc;tre connect&eacute;.
mauth_usernameless.banner.info=Scan r&eacute;ussi!<br> Veuillez continuer dans l'application AGOV access.
mauth_usernameless.banner.success=Authentification r&eacute;ussie!<br>Veuillez attendre d'&ecirc;tre connect&eacute;.
mauth_usernameless.cannotLogin=Avez-vous perdu l'acc&egrave;s &agrave; votre application / votre cl&eacute; de s&eacute;curit&eacute; ?
mauth_usernameless.cannotLogin.accessApp=Vous avez perdu l'acc&egrave;s &agrave; votre application AGOV access ?
mauth_usernameless.cannotLogin.securityKey=Avez-vous perdu l'acc&egrave;s &agrave; votre cl&eacute; de s&eacute;curit&eacute; ?
mauth_usernameless.hideQR=Cacher le code QR
mauth_usernameless.instructions=Connectez-vous en scannant le code QR avec l'application AGOV access
mauth_usernameless.noAccount=Vous n'avez pas encore de compte AGOV ?
mauth_usernameless.noAccount=Vous n'avez pas de compte AGOV ?
mauth_usernameless.selectLoginMethod=S&eacute;l&eacute;ctionner la m&eacute;thode de connexion
mauth_usernameless.showQR=Afficher le code QR
mauth_usernameless.startRecovery=Commencer la r&eacute;cup&eacute;ration du compte
@ -163,7 +164,7 @@ prompt.newpassword=Nouveau mot de passe
prompt.newpassword.confirm=Confirmez le mot de passe
prompt.password=Mot de passe
prompt.userid=ID de l&#39;utilisateur
providePhoneNumber.banner=Ce num&eacute;ro de t&eacute;l&eacute;phone doit pouvoir recevoir des SMS. Il ne sera pas utilis&eacute; pour vous contacter.
providePhoneNumber.banner=Ce num&eacute;ro de t&eacute;l&eacute;phone doit pouvoir recevoir des SMS.<br>Ce num&eacute;ro de t&eacute;l&eacute;phone ne sera pas utilis&eacute; pour vous contacter.
providePhoneNumber.description=AGOV prend d&eacute;sormais en charge la r&eacute;cup&eacute;ration avec votre num&eacute;ro de t&eacute;l&eacute;phone. Cela vous permettra de vous envoyer un SMS pendant la r&eacute;cup&eacute;ration si vous avez perdu l'acc&egrave;s &agrave; votre code de r&eacute;cup&eacute;ration.
providePhoneNumber.errorBanner=Les num&eacute;ros de t&eacute;l&eacute;phone fournies ne correspondent pas. Veuillez r&eacute;essayer.
providePhoneNumber.inputLabel=Num&eacute;ro de t&eacute;l&eacute;phone (facultatif)
@ -171,7 +172,7 @@ providePhoneNumber.laterModal.description1=Sans num&eacute;ro de t&eacute;l&eacu
providePhoneNumber.laterModal.description2=Ajouter un num&eacute;ro de t&eacute;l&eacute;phone vous permet de r&eacute;cup&eacute;rer votre compte en quelques minutes.
providePhoneNumber.laterModal.description3=Ce num&eacute;ro de t&eacute;l&eacute;phone ne sera pas utilis&eacute; pour vous contacter.
providePhoneNumber.laterModal.title=Continuer sans num&eacute;ro de t&eacute;l&eacute;phone ?
providePhoneNumber.modal.description=Pour vous assurer que vous avez correctement enregistr&eacute; votre num&eacute;ro de t&eacute;l&eacute;phone, veillez le r&eacute;p&eacute;ter ci-dessous. Un num&eacute;ro de t&eacute;l&eacute;phone mal enregistr&eacute; peut rendre la r&eacute;cup&eacute;ration de votre compte plus difficile.
providePhoneNumber.modal.description=Un num&eacute;ro de t&eacute;l&eacute;phone mal enregistr&eacute; peut rendre plus difficile la r&eacute;cup&eacute;ration de votre compte. Pour vous assurer que vous avez correctement enregistr&eacute; votre num&eacute;ro de t&eacute;l&eacute;phone, veuillez le r&eacute;p&eacute;ter ci-dessous.
providePhoneNumber.modal.inputLabel=Num&eacute;ro de t&eacute;l&eacute;phone
providePhoneNumber.modal.title=R&eacute;p&eacute;ter votre num&eacute;ro de t&eacute;l&eacute;phone
providePhoneNumber.saveButtonText=Sauvegarder
@ -180,15 +181,13 @@ pwreset.done.info=Votre mot de passe a &eacute;t&eacute; chang&eacute avec succ&
pwreset.email.sent=Si votre identifiant n'existe pas, vous avez reçu un courriel pour réinitialiser votre mot de passe.
pwreset.info.linktext=Mot de passe oublié
pwreset.noticket=Votre lien n&apos;est plus valide. Veuillez en g&eacute;n&eacute;rer un nouveau.
qrCode.label=Cliquez pour ouvrir le code QR dans une fen&ecirc;tre.
recovery_accessapp_auth.accessAppRegistered=L'application AGOV access est d&eacute;j&agrave; enregistr&eacute;e
recovery_accessapp_auth.instruction1=Vous avez d&eacute;j&agrave; enregistr&eacute; une nouvelle application AGOV access !!!ACCESS_APP_NAME!!! dans le cadre du processus de r&eacute;cup&eacute;ration.
recovery_accessapp_auth.instruction2=Veuillez utiliser !!!ACCESS_APP_NAME!!! pour vous identifier.
recovery_check_code.banner.lockedError=Trop de saisies erron&eacute;es. Veuillez r&eacute;essayer dans quelques minutes.
recovery_check_code.codeIncorrect=Le code saisi est incorrect. Veuillez r&eacute;essayer.
recovery_check_code.enterRecoveryCode=Code de r&eacute;cup&eacute;ration
recovery_check_code.expired=Trop de tentatives ou votre code de r&eacute;cup&eacute;ration a expir&eacute;.
recovery_check_code.instruction=Veuillez saisir votre code de r&eacute;cup&eacute;ration &agrave; 12 chiffres. Lors de votre inscription, vous avez re&ccedil;u le code de r&eacute;cup&eacute;ration sous la forme d&rsquo;un fichier PDF ou dans &laquo; AGOV me &raquo;.
recovery_check_code.enterRecoveryCode=Saisir le code de r&eacute;cup&eacute;ration
recovery_check_code.instruction=Veuillez saisir votre code de r&eacute;cup&eacute;ration &agrave; douze chiffres. Lors de votre inscription, vous avez re&ccedil;u le code de r&eacute;cup&eacute;ration sous la forme d&rsquo;un fichier PDF ou dans &laquo; AGOV me &raquo;.
recovery_check_code.invalid.code=Le code est invalide
recovery_check_code.invalid.code.required=Code requis
recovery_check_code.invalid.code.tooLong=Le code est trop long
@ -200,7 +199,7 @@ recovery_check_code.too_many_tries.instruction2=Veuillez vous rendre sur AGOV he
recovery_check_noCode.banner.error=Trop de tentatives.
recovery_check_noCode.instruction1=Vous avez peut-&ecirc;tre essay&eacute; de saisir le code de r&eacute;cup&eacute;ration trop de fois.
recovery_check_noCode.instruction2=Veuillez fermer le navigateur web et recommencer la r&eacute;cup&eacute;ration du compte dans dix minutes &agrave; partir de <a class='link' href='https://agov.ch/me'>https://agov.ch/me</a>.
recovery_code.banner.error=Veuillez r&eacute;v&eacute;ler votre code de r&eacute;cup&eacute;ration pour pouvoir continuer.
recovery_code.banner.error=Veuillez indiquer votre nouveau code pour pouvoir continuer.
recovery_code.instruction=Les codes de r&eacute;cup&eacute;ration vous permettent d'acc&eacute;der &agrave; votre compte au cas o&ugrave; vous auriez perdu tous vos identifiants. Conservez le code de r&eacute;cup&eacute;ration en lieu s&ucirc;r.
recovery_code.newRecoveryCode=Introduction du code de r&eacute;cup&eacute;ration
recovery_code.validUntil=Valable jusqu'au:
@ -245,7 +244,6 @@ recovery_questionnaire_reason_selection.instruction=Veuillez s&eacute;lectionner
recovery_start_info.banner.warning=Vous ne pourrez pas utiliser votre compte tant que le processus de r&eacute;cup&eacute;ration n'aura pas &eacute;t&eacute; termin&eacute;.
recovery_start_info.instruction=Le processus de r&eacute;cup&eacute;ration n&eacute;cessitera l&rsquo;enregistrement d&rsquo;un nouveau facteur d&rsquo;authentification. Si votre compte contient des informations ayant d&eacute;j&agrave; &eacute;t&eacute; v&eacute;rifi&eacute;es, il se peut que vous deviez les faire v&eacute;rifier &agrave; nouveau pour terminer la r&eacute;cup&eacute;ration.
recovery_start_info.title=Vous &ecirc;tes sur le point de d&eacute;marrer le processus de r&eacute;cup&eacute;ration.
submit.button.label=Envoyer
title=NEVIS SSO Portal
title.login=Login
title.pwchange.label=Changer mot de passe

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