io.apiman.plugins.keycloak_oauth_policy.ClaimLookup.java Source code

Java tutorial

Introduction

Here is the source code for io.apiman.plugins.keycloak_oauth_policy.ClaimLookup.java

Source

/*
 * Copyright 2015 JBoss Inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.apiman.plugins.keycloak_oauth_policy;

import java.lang.reflect.Field;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.keycloak.representations.AccessToken;
import org.keycloak.representations.IDToken;
import org.keycloak.representations.JsonWebToken;

import com.fasterxml.jackson.annotation.JsonProperty;

/**
* @author Marc Savy {@literal <msavy@redhat.com>}
*/
@SuppressWarnings("nls")
public class ClaimLookup {
    private static final Map<String, List<Field>> STANDARD_CLAIMS_FIELD_MAP = new LinkedHashMap<>();

    static {
        Class<?> clazz = AccessToken.class;
        do {
            getProperties(clazz, "", new ArrayDeque<Field>());
        } while ((clazz = clazz.getSuperclass()) != null);
        // Legacy mappings, to ensure old configs keep working
        STANDARD_CLAIMS_FIELD_MAP.put("username", STANDARD_CLAIMS_FIELD_MAP.get(IDToken.PREFERRED_USERNAME));
        STANDARD_CLAIMS_FIELD_MAP.put("subject", STANDARD_CLAIMS_FIELD_MAP.get("sub"));
    }

    private static void getProperties(Class<?> klazz, String path, Deque<Field> fieldChain) {
        for (Field f : klazz.getDeclaredFields()) {
            f.setAccessible(true);
            JsonProperty jsonProperty = f.getAnnotation(JsonProperty.class);
            if (jsonProperty != null) {
                fieldChain.push(f);
                // If the inspected type has nested @JsonProperty annotations, we need to inspect it
                if (hasJsonPropertyAnnotation(f)) {
                    getProperties(f.getType(), f.getName() + ".", fieldChain); // Add "." when traversing into new object.
                } else { // Otherwise, just assume it's simple as the best we can do is #toString
                    List<Field> fieldList = new ArrayList<>(fieldChain);
                    Collections.reverse(fieldList);
                    STANDARD_CLAIMS_FIELD_MAP.put(path + jsonProperty.value(), fieldList);
                    fieldChain.pop(); // Pop, as we have now reached end of this chain.
                }
            }
        }
    }

    private static boolean hasJsonPropertyAnnotation(Field f) {
        for (Field g : f.getType().getDeclaredFields()) {
            g.setAccessible(true);
            if (g.getAnnotation(JsonProperty.class) != null)
                return true;
        }
        return false;
    }

    /**
     *
     * @param token token to retrieve claim from
     * @param claim the claim (field key)
     * @return string representaion of claim
     */
    public static String getClaim(IDToken token, String claim) {
        if (claim == null || token == null)
            return null;
        // Get the standard claim field, if available
        if (STANDARD_CLAIMS_FIELD_MAP.containsKey(claim)) {
            return callClaimChain(token, STANDARD_CLAIMS_FIELD_MAP.get(claim));
        } else { // Otherwise look up 'other claims'
            Object otherClaim = getOtherClaimValue(token, claim);
            return otherClaim == null ? null : otherClaim.toString();
        }
    }

    private static String callClaimChain(Object rootObject, List<Field> list) {
        try {
            Object candidate = rootObject;
            for (Field f : list) {
                if ((candidate = f.get(candidate)) == null)
                    break;
            }
            return (candidate == null) ? null : candidate.toString();
        } catch (IllegalArgumentException | IllegalAccessException e) {
            // TODO Use logger. These exceptions shouldn't occur, but if it somehow does happen we need to know.
            System.err.println("Unexpected error looking up token field: " + e); //$NON-NLS-1$
            e.printStackTrace();
        }
        return null;
    }

    @SuppressWarnings("unchecked") // KC code - thanks.
    private static Object getOtherClaimValue(JsonWebToken token, String claim) {
        String[] split = claim.split("\\.");
        Map<String, Object> jsonObject = token.getOtherClaims();
        for (int i = 0; i < split.length; i++) {
            if (i == split.length - 1) {
                return jsonObject.get(split[i]);
            } else {
                Object val = jsonObject.get(split[i]);
                if (!(val instanceof Map))
                    return null;
                jsonObject = (Map<String, Object>) val;
            }
        }
        return null;
    }
}