71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington/*
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington* The contents of this file are subject to the terms of the Common Development and
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington* Distribution License (the License). You may not use this file except in compliance with the
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington* License.
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington*
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington* You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington* specific language governing permission and limitations under the License.
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington*
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington* When distributing Covered Software, include this CDDL Header Notice in each file and include
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington* the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington* Header, with the fields enclosed by brackets [] replaced by your own identifying
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington* information: "Portions copyright [year] [name of copyright owner]".
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington*
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington* Copyright 2014-2016 ForgeRock AS.
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington*/
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunningtonimport com.iplanet.sso.SSOException
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunningtonimport com.sun.identity.idm.IdRepoException
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunningtonimport org.forgerock.oauth2.core.UserInfoClaims
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington/*
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington* Defined variables:
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington* logger - always presents, the "OAuth2Provider" debug logger instance
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington* claims - always present, default server provided claims
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington* session - present if the request contains the session cookie, the user's session object
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington* identity - always present, the identity of the resource owner
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington* scopes - always present, the requested scopes
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington* requestedClaims - Map<String, Set<String>>
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington* always present, not empty if the request contains a claims parameter and server has enabled
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington* claims_parameter_supported, map of requested claims to possible values, otherwise empty,
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington* requested claims with no requested values will have a key but no value in the map. A key with
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington* a single value in its Set indicates this is the only value that should be returned.
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington* Required to return a Map of claims to be added to the id_token claims
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington*
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington* Expected return value structure:
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington* UserInfoClaims {
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington* Map<String, Object> values; // The values of the claims for the user information
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington* Map<String, List<String>> compositeScopes; // Mapping of scope name to a list of claim names.
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington* }
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington*/
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington// user session not guaranteed to be present
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunningtonboolean sessionPresent = session != null
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunningtondef fromSet = { claim, attr ->
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington if (attr != null && attr.size() == 1){
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington attr.iterator().next()
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington } else if (attr != null && attr.size() > 1){
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington attr
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington } else if (logger.warningEnabled()) {
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington logger.warning("OpenAMScopeValidator.getUserInfo(): Got an empty result for claim=$claim");
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington }
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington}
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill CunningtonattributeRetriever = { attribute, claim, identity, requested ->
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington if (requested == null || requested.isEmpty()) {
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington fromSet(claim, identity.getAttribute(attribute))
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington } else if (requested.size() == 1) {
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington requested.iterator().next()
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington } else {
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington throw new RuntimeException("No selection logic for $claim defined. Values: $requested")
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington }
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington}
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington// [ {claim}: {attribute retriever}, ... ]
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill CunningtonclaimAttributes = [
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington "email": attributeRetriever.curry("mail"),
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington "address": { claim, identity, requested -> [ "formatted" : attributeRetriever("postaladdress", claim, identity, requested) ] },
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington "phone_number": attributeRetriever.curry("telephonenumber"),
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington "given_name": attributeRetriever.curry("givenname"),
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington "zoneinfo": attributeRetriever.curry("preferredtimezone"),
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington "family_name": attributeRetriever.curry("sn"),
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington "locale": attributeRetriever.curry("preferredlocale"),
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington "name": attributeRetriever.curry("cn")
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington]
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington// {scope}: [ {claim}, ... ]
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill CunningtonscopeClaimsMap = [
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington "email": [ "email" ],
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington "address": [ "address" ],
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington "phone": [ "phone_number" ],
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington "profile": [ "given_name", "zoneinfo", "family_name", "locale", "name" ]
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington]
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunningtonif (logger.messageEnabled()) {
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington scopes.findAll { s -> !("openid".equals(s) || scopeClaimsMap.containsKey(s)) }.each { s ->
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington logger.message("OpenAMScopeValidator.getUserInfo()::Message: scope not bound to claims: $s")
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington }
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington}
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunningtondef computeClaim = { claim, requestedValues ->
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington try {
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington [ claim, claimAttributes.get(claim)(claim, identity, requestedValues) ]
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington } catch (IdRepoException e) {
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington if (logger.warningEnabled()) {
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington logger.warning("OpenAMScopeValidator.getUserInfo(): Unable to retrieve attribute=$attribute", e);
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington }
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington } catch (SSOException e) {
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington if (logger.warningEnabled()) {
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington logger.warning("OpenAMScopeValidator.getUserInfo(): Unable to retrieve attribute=$attribute", e);
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington }
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington }
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington}
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunningtondef computedClaims = scopes.findAll { s -> !"openid".equals(s) && scopeClaimsMap.containsKey(s) }.inject(claims) { map, s ->
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington scopeClaims = scopeClaimsMap.get(s)
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington map << scopeClaims.findAll { c -> !requestedClaims.containsKey(c) }.collectEntries([:]) { claim -> computeClaim(claim, null) }
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington}.findAll { map -> map.value != null } << requestedClaims.collectEntries([:]) { claim, requestedValue ->
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington computeClaim(claim, requestedValue)
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington}
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunningtondef compositeScopes = scopeClaimsMap.findAll { scope ->
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington scopes.contains(scope.key)
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington}
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunnington
71b128a7314b40bf2b9740cfa80f6cdba76740e8Phill Cunningtonreturn new UserInfoClaims((Map)computedClaims, (Map)compositeScopes)