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 } 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') }