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.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')) { 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'] if (userExtId == null) { LOG.error("missing extId of nevisIDM user. check your authentication flow.") notes.setProperty('lasterror', '1') notes.setProperty('lasterrorinfo', 'missing extId of nevisIDM user') response.setResult('error') return } 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 if (responseCode == 400) { LOG.error("FIDO2 options call failed for '${userExtId}'") notes.setProperty('lasterror', '1') notes.setProperty('lasterrorinfo', 'missing extId of nevisIDM user') response.setResult('error') return } def responseText = connection.inputStream.text LOG.debug("<== 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') { 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.debug("<== Response: ${responseCode} : ${responseText}") if (responseCode == 200 && new JsonSlurper().parseText(responseText).status == 'ok') { response.setSessionAttribute('agov.recovery.authenticatedWith', 'urn:qa.agov.ch:names:tc:authfactor:fido') response.setResult('ok') return } } notes.setProperty('lasterror', '1') notes.setProperty('lasterrorinfo', 'FIDO2 authentication failed') response.setResult('error') return } response.setError(1, "FIDO2 authentication failed") showGui()