119 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			Groovy
		
	
	
	
			
		
		
	
	
			119 lines
		
	
	
		
			4.1 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.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() |