import groovy.xml.XmlSlurper import groovy.json.JsonSlurper import io.opentelemetry.api.trace.Span int getRequestedLevel(String authnContextClassRef, def roleList){ if (!authnContextClassRef) { return 100 } if (authnContextClassRef && authnContextClassRef.startsWith('urn:qa.agov.ch:names:tc:ac:classes:')) { def requestedLevel = authnContextClassRef.substring(35) LOG.debug('authnContextClassRef agov found: ' + requestedLevel) if (requestedLevel.isNumber()) { int requestedLevelNumber = Integer.parseInt(requestedLevel) LOG.debug('contains ' + roleList.contains(requestedLevelNumber)) if (requestedLevel.isNumber() && roleList.contains(requestedLevelNumber)) { LOG.debug('Requested role number: ' + requestedLevel) return requestedLevelNumber } } else return 0 } else { return 0 } } def session = request.getAuthSession(true) def context = session.get('ch.nevis.auth.saml.request.authnContextClassRef') if (!context || context == '' || context == 'null') { // EPD call, we set a default of aq300 session.setAttribute('ch.nevis.auth.saml.request.authnContextClassRef', 'urn:qa.agov.ch:names:tc:ac:classes:300') conext = 'urn:qa.agov.ch:names:tc:ac:classes:300' } def roleLevels = [100,200,300,400,500,600] def requestedRoleLevelNumber = getRequestedLevel(context, roleLevels) //set attribute Requested Role Level session.setAttribute('agov.requestedRoleLevel', '' + requestedRoleLevelNumber) LOG.debug('Requested role level (agov) '+ requestedRoleLevelNumber) // SAML finisherstate is now available, we can backup it session.setAttribute('agov.backup.finishers', '' + session.getAttribute('ch.nevis.session.finishers')) // Accounting def requester = session['ch.nevis.auth.saml.request.scoping.requesterId'] ?: 'unknown' def requestId = session['ch.nevis.auth.saml.request.id'] ?: 'unknown' def replacedRequestId = session['agov.replacedRequestId'] ?: '-' def requestedAq = session['agov.requestedRoleLevel'] ?: '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 bestTokenAddressWhitelist = ',' + (parameters.get('bestTokenAddressWhitelist') ?: '').replaceAll('\\s','') + ',' def appRequiresBestTokenWithAddress = bestTokenAddressWhitelist.contains(','+requester+',') def bestTokenSvnrWhitelist = ',' + (parameters.get('bestTokenSvnrWhitelist') ?: '').replaceAll('\\s','') + ',' def appRequiresBestTokenWithSvnr = bestTokenSvnrWhitelist.contains(','+requester+',') LOG.info("Event='AUTHREQUEST', Requester='${requester}', RequestId='${requestId}', ReplacedRequestId='${replacedRequestId}', RequestedAq=${requestedAq}, BestTokenRequired='svnr: ${appRequiresBestTokenWithSvnr}; address: ${appRequiresBestTokenWithAddress}', SourceIp=${sourceIp}, UserAgent='${userAgent}'") if (requestedRoleLevelNumber == 0 || session.get('ch.nevis.auth.saml.request.scoping.requesterId') == null) { response.setResult('error'); return } def eidEnabled = parameters.get('eidPassthroughEnabled') == "true" || parameters.get('eidFullEnabled') == "true" // TODO/aca/2025-06-05: add a condition to check if the client actually allows eid def eidAllowed = eidEnabled // set session variable to later decide to which loginmethods we can switch session.setAttribute('agov.eidAllowed', eidAllowed.toString()) // if aq400 or less is requested then we need to decide which login method to show first // The default login method is eid. If eid is not allowed we prefer fido uaf. def ok_transition = eidAllowed ? 'exit.1' : 'ok' // if there is a login method cookie set form a previous login -> use that instead of the default def lastLoginMethod = session.get('agov.lastLoginMethod') if(lastLoginMethod != null || lastLoginMethod != ""){ if(lastLoginMethod == "accessApp" || lastLoginMethod == "securityKey"){ ok_transition = 'ok' } } // NOTE: if the last login method was eid, but eid is not allowed, we will default to fido uaf try { def spanCtxt = Span.current().getSpanContext() def traceparent = "00-${spanCtxt.getTraceId()}-${spanCtxt.getSpanId()}-${spanCtxt.getTraceFlags().asHex()}" def jsonSlurper = new JsonSlurper() def url = parameters.get('url') + '?entity-id=' + session.get('ch.nevis.auth.saml.request.scoping.requesterId') LOG.debug('Request url: ' + url) def httpClient = HttpClients.create(parameters) def httpResponse = Http.get().url(url).header('traceparent', traceparent).build().send(httpClient) LOG.debug('Response Message: ' + httpResponse.reasonPhrase()) LOG.debug('Response Status Code: ' + httpResponse.code()) LOG.debug('Response: ' + httpResponse.bodyAsString()) if (httpResponse.code() == 200) { def json = jsonSlurper.parseText(httpResponse.bodyAsString()) session.setAttribute('agov.appDisplayNameDE', '' + json.displayNameDe) session.setAttribute('agov.appDisplayNameFR', '' + json.displayNameFr) session.setAttribute('agov.appDisplayNameIT', '' + json.displayNameIt) session.setAttribute('agov.appDisplayNameEN', '' + json.displayNameEn) // if aq500 or 600 is requested -> the only available login method is eid -> continue directly there // if eid is disabled -> show an error page if (requestedRoleLevelNumber == 600 || requestedRoleLevelNumber == 500) { if(eidEnabled){ session.setAttribute('agov.appSvnrAllowed', 'true') response.setResult('exit.1') return }else{ response.setResult('error') response.setError(9073, "LoA 600 not supported") return } } LOG.debug('AdressRequired: ' + json.addrRequired) LOG.debug('SvnrAllowed: ' + json.svnrAllowed) LOG.debug('appRequiresBestTokenWithAddress: ' + appRequiresBestTokenWithAddress) LOG.debug('appRequiresBestTokenWithSvnr: ' + appRequiresBestTokenWithSvnr) // address will be returned to the application if allowed by connect (json.addrRequired) // and the authRequest was done with at least AGOVaq 200 // BUNDBITBK-4307: or best token for address is enabled session.setAttribute('agov.appAddressRequired', '' + (json.addrRequired && ((requestedRoleLevelNumber >= 200) || appRequiresBestTokenWithAddress))) // address will be returned to the application if allowed by connect (json.svnrAllowed) // and the authRequest was done with at least AGOVaq 300 // BUNDBITBK-4307: or best token for svnr is enabled session.setAttribute('agov.appSvnrAllowed', '' + (json.svnrAllowed && ((requestedRoleLevelNumber >= 300) || appRequiresBestTokenWithSvnr))) response.setResult(ok_transition) return } else { LOG.warn("Failed to fetch connect meta data for relying party '${session.get('ch.nevis.auth.saml.request.scoping.requesterId')}'") LOG.warn('Unexcpected HTTP response code: ' + httpResponse.code()) if ( requestedRoleLevelNumber == 100) { session.setAttribute('agov.appAddressRequired', '' + appRequiresBestTokenWithAddress) session.setAttribute('agov.appSvnrAllowed', 'false') response.setResult(ok_transition) } else if ( requestedRoleLevelNumber == 200) { session.setAttribute('agov.appAddressRequired', 'true') session.setAttribute('agov.appSvnrAllowed', 'false') response.setResult(ok_transition) } else { response.setResult('error') response.setError(9071, "Missing meta data for relying party, can't process request") } return } } catch (Exception e) { LOG.error("Failed to fetch connect meta data for relying party '${session.get('ch.nevis.auth.saml.request.scoping.requesterId')}'", e) if ( requestedRoleLevelNumber == 100) { session.setAttribute('agov.appAddressRequired', '' + appRequiresBestTokenWithAddress) session.setAttribute('agov.appSvnrAllowed', 'false') response.setResult(ok_transition) } else if ( requestedRoleLevelNumber == 200) { session.setAttribute('agov.appAddressRequired', 'true') session.setAttribute('agov.appSvnrAllowed', 'false') response.setResult(ok_transition) } else { response.setResult('error') response.setError(9072, "Failure while processing meta data for relying party, can't continue processing request") } return }