adn-agov-iam-project/patterns/54c1b68431bc2e03b61edcaa_re.../recovery_fido2_auth.groovy

151 lines
5.6 KiB
Groovy

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