com.nulli.openam.plugins.NeoUniversalCondition.java Source code

Java tutorial

Introduction

Here is the source code for com.nulli.openam.plugins.NeoUniversalCondition.java

Source

/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions copyright [year] [name of copyright owner]".
 *
 * Copyright 2015 Nulli Secundus Inc.
 */

package com.nulli.openam.plugins;

import com.iplanet.log.ConnectionException;

import com.iplanet.sso.SSOException;
import com.iplanet.sso.SSOToken;
import com.sun.identity.authentication.util.ISAuthConstants;
import com.sun.identity.entitlement.ConditionDecision;
import com.sun.identity.entitlement.EntitlementCondition;
import com.sun.identity.entitlement.EntitlementException;

import static com.sun.identity.entitlement.EntitlementException.PROPERTY_VALUE_NOT_DEFINED;

import com.sun.identity.entitlement.PrivilegeManager;
import com.sun.identity.shared.debug.Debug;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.iplanet.log.ConnectionException;
import javax.security.auth.Subject;

import org.apache.commons.codec.binary.Base64;
import org.forgerock.openam.core.CoreWrapper;

import static org.forgerock.openam.entitlement.conditions.environment.ConditionConstants.*;

import org.forgerock.openam.entitlement.conditions.environment.EntitlementCoreWrapper;
import org.forgerock.openam.utils.StringUtils;
import org.json.JSONException;
import org.json.JSONObject;

import sun.misc.BASE64Encoder;
import org.neo4j.driver.v1.*;
import org.neo4j.driver.v1.exceptions.*;

import java.util.ArrayList;
import java.util.List;

/**
 * Neo4j-Universal Policy Environmental Condition Plugin for OpenAM
 *
 * An implementation of an
 * {@link com.sun.identity.entitlement.EntitlementCondition} that will check
 * whether the principal meets condition based on a parameterized cypher 
 * query against a Neo4j graph dabatase.
 *
 * @since 12.0.0
 * @author Hadi Ahmadi
 */
public class NeoUniversalCondition implements EntitlementCondition {

    private static final String NEO_DB_URL = "neoDbURL";
    private static final String NEO_DB_USERNAME = "neoDbUsername";
    private static final String NEO_DB_PASSWORD = "neoDbPassword";
    private static final String NEO_CYPHER_QUERY = "neoCypherQuery";
    private static final String NEO_QUERY_PARAMS = "neoQueryParams";
    // TODO Add policy advice support
    //private static final String NEO_ADVICE_MAP = "neoAdviceMap";
    private static final String NEO_ALLOW_RESULT = "neoAllowResult";
    private static final String NEO_DENY_RESULT = "neoDenyResult";

    private final Debug debug;
    private final CoreWrapper coreWrapper;
    private final EntitlementCoreWrapper entitlementCoreWrapper;
    private Driver driver;

    private String dbURL = null;
    private String dbUsername = null;
    private String dbPassword = null;
    private String cypherQuery = null;
    private String paramsJson = null;
    //private String adviceMapJson = null;
    private String allowCypherResult = null;
    private String denyCypherResult = null;

    private boolean realmEmpty = false;
    private String displayType;

    /**
     * Constructs a new NeoUniversalCondition instance.
     */
    public NeoUniversalCondition() {
        this(PrivilegeManager.debug, new CoreWrapper(), new EntitlementCoreWrapper());
    }

    /**
     * Constructs a new NeoUniversalCondition instance.
     *
     * @param debug A Debug instance.
     * @param coreWrapper An instance of the CoreWrapper.
     * @param entitlementCoreWrapper An instance of the EntitlementCoreWrapper.
     */
    NeoUniversalCondition(Debug debug, CoreWrapper coreWrapper, EntitlementCoreWrapper entitlementCoreWrapper) {
        this.debug = debug;
        this.coreWrapper = coreWrapper;
        this.entitlementCoreWrapper = entitlementCoreWrapper;
    }

    private static String getInitStringValue(Set<String> set) {
        return ((set == null) || set.isEmpty()) ? "" : set.iterator().next();
    }

    private static String getStringValue(Set<String> set) {
        return ((set == null) || set.isEmpty()) ? null : set.iterator().next();
    }

    @Override
    public String getDisplayType() {
        return displayType;
    }

    @Override
    public void setDisplayType(String displayType) {
        this.displayType = displayType;
    }

    public String getDbURL() {
        return dbURL;
    }

    public void setDbURL(String dbURL) {
        this.dbURL = dbURL;
    }

    public String getDbUsername() {
        return dbUsername;
    }

    public void setDbUsername(String dbUsername) {
        this.dbUsername = dbUsername;
    }

    public String getDbPassword() {
        return dbPassword;
    }

    public void setDbPassword(String dbPassword) {
        this.dbPassword = dbPassword;
    }

    public String getCypherQuery() {
        return cypherQuery;
    }

    public void setCypherQuery(String cypherQuery) {
        this.cypherQuery = cypherQuery;
    }

    public String getParamsJson() {
        return paramsJson;
    }

    public void setParamsJson(String ParamsJson) {
        this.paramsJson = ParamsJson;
    }

    /*public String getAdviceMapJson() {
     return adviceMapJson;
     }*/
    public String getAllowCypherResult() {
        return allowCypherResult;
    }

    /* public void setAdviceMapJson(String adviceMapJson) {
     this.adviceMapJson = adviceMapJson;
     }*/
    public void setAllowCypherResult(String allowCypherResult) {
        this.allowCypherResult = allowCypherResult;
    }

    public String getDenyCypherResult() {
        return denyCypherResult;
    }

    public void setDenyCypherResult(String denyCypherResult) {
        this.denyCypherResult = denyCypherResult;
    }

    @Override
    public void init(Map<String, Set<String>> map) {
        for (String key : map.keySet()) {
            if (key.equalsIgnoreCase(NEO_DB_URL)) {
                setDbURL(getInitStringValue(map.get(key)));
            } else if (key.equalsIgnoreCase(NEO_DB_USERNAME)) {
                setDbUsername(getInitStringValue(map.get(key)));
            } else if (key.equalsIgnoreCase(NEO_DB_PASSWORD)) {
                setDbPassword(getInitStringValue(map.get(key)));
            } else if (key.equalsIgnoreCase(NEO_CYPHER_QUERY)) {
                setCypherQuery(getInitStringValue(map.get(key)));
            } else if (key.equalsIgnoreCase(NEO_QUERY_PARAMS)) {
                setParamsJson(getStringValue(map.get(key)));
            } /*else if (key.equalsIgnoreCase(NEO_ADVICE_MAP)) {
              setAdviceMapJson(getStringValue(map.get(key)));
              } */ else if (key.equalsIgnoreCase(NEO_ALLOW_RESULT)) {
                setAllowCypherResult(getStringValue(map.get(key)));
            } else if (key.equalsIgnoreCase(NEO_DENY_RESULT)) {
                setDenyCypherResult(getStringValue(map.get(key)));
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getState() {
        JSONObject jo = new JSONObject();
        try {
            jo.put(NEO_DB_URL, dbURL);
            jo.put(NEO_DB_USERNAME, dbUsername);
            jo.put(NEO_DB_PASSWORD, dbPassword);
            jo.put(NEO_CYPHER_QUERY, cypherQuery);
            jo.put(NEO_QUERY_PARAMS, paramsJson);
            //  jo.put(NEO_ADVICE_MAP, adviceMapJson);
            jo.put(NEO_ALLOW_RESULT, allowCypherResult);
            jo.put(NEO_DENY_RESULT, denyCypherResult);
        } catch (JSONException ex) {
            Logger.getLogger(NeoUniversalCondition.class.getName()).log(Level.SEVERE, null, ex);
        }
        return jo.toString();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setState(String state) {
        try {
            JSONObject jo = new JSONObject(state);

            // TODO check realm from a field!!!
            // String realm = coreWrapper.getRealmFromRealmQualifiedData(paramsJson);
            // realmEmpty = StringUtils.isBlank(realm);

            // TODO sanitize
            if (jo.has(NEO_DB_URL)) {
                setDbURL(jo.getString(NEO_DB_URL));
            }
            if (jo.has(NEO_DB_USERNAME)) {
                setDbUsername(jo.getString(NEO_DB_USERNAME));
            }
            if (jo.has(NEO_DB_PASSWORD)) {
                setDbPassword(jo.getString(NEO_DB_PASSWORD));
            }
            if (jo.has(NEO_CYPHER_QUERY)) {
                setCypherQuery(jo.getString(NEO_CYPHER_QUERY));
            }
            if (jo.has(NEO_QUERY_PARAMS)) {
                setParamsJson(jo.getString(NEO_QUERY_PARAMS));
            }
            /* if (jo.has(NEO_ADVICE_MAP)) {
             setAdviceMapJson(jo.getString(NEO_ADVICE_MAP));
             }*/
            if (jo.has(NEO_ALLOW_RESULT)) {
                setAllowCypherResult(jo.getString(NEO_ALLOW_RESULT));
            }
            if (jo.has(NEO_DENY_RESULT)) {
                setDenyCypherResult(jo.getString(NEO_DENY_RESULT));
            }

        } catch (JSONException e) {
            debug.message("NeoUniversalCondition: Failed to set state", e);
        }
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("deprecation")
    @Override
    public ConditionDecision evaluate(String realm, Subject subject, String resourceName,
            Map<String, Set<String>> env) throws EntitlementException {

        Map<String, Set<String>> advices = new HashMap<String, Set<String>>();

        if (!subject.getPrincipals().isEmpty() && paramsJson != null) {
            try {

                String cypherResult = null;

                JSONObject params = sanitizeParams(paramsJson, realm, subject, resourceName, env);
                cypherResult = neoQuery(cypherQuery, params);

                if (cypherResult == null) {
                    throw new ConnectionException(
                            "Error response received from the Graph DB while querying NeoClientType!");
                }

                if (cypherResult.equalsIgnoreCase(allowCypherResult)) {
                    return new ConditionDecision(true, advices);
                } else if (cypherResult.equalsIgnoreCase(denyCypherResult)) {
                    return new ConditionDecision(false, advices);
                }
            } catch (ConnectionException ex) {
                Logger.getLogger(NeoUniversalCondition.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        return new ConditionDecision(false, advices); // This is a deny
    }

    @Override
    public void validate() throws EntitlementException {
        if (StringUtils.isBlank(cypherQuery)) {
            throw new EntitlementException(PROPERTY_VALUE_NOT_DEFINED, NEO_CYPHER_QUERY);
        }
    }

    private JSONObject sanitizeParams(String params, String realm, Subject subject, String resourceName,
            Map<String, Set<String>> env) throws EntitlementException {
        JSONObject jsonParams = null;
        boolean requestHasParams = false;
        Map<String, String> reqParamMap = new LinkedHashMap<String, String>();

        SSOToken token = (SSOToken) subject.getPrivateCredentials().iterator().next();

        if (resourceName.split("\\?").length > 1) {
            requestHasParams = true;
            String urlQuery = resourceName.split("\\?")[1];
            String[] urlParams = urlQuery.split("&");
            for (String param : urlParams) {
                int idx = param.indexOf("=");
                reqParamMap.put(param.substring(0, idx), param.substring(idx + 1));
            }
        }

        try {
            if (!params.isEmpty()) {
                jsonParams = new JSONObject(params);
                @SuppressWarnings("unchecked")
                Iterator<String> paramItr = jsonParams.keys();

                while (paramItr.hasNext()) {
                    String paramKey = paramItr.next();
                    String paramVal = jsonParams.get(paramKey).toString();
                    if (paramVal.startsWith("__")) {
                        if (paramVal.equals("__userId")) {
                            if (!subject.getPrincipals().isEmpty()) {
                                String userId = getUserId(subject);
                                jsonParams.put(paramKey, userId);
                            } else {
                                throw new EntitlementException(EntitlementException.CONDITION_EVALUATION_FAILED,
                                        "could not find userId (required) from subject");
                            }
                        } else if (paramVal.equals("__resourceName")) {
                            jsonParams.put(paramKey, resourceName);
                        } else if (paramVal.equals("__realm")) {
                            jsonParams.put(paramKey, realm);
                        } else if (paramVal.startsWith("__env__")) {
                            String envParam = paramVal.substring(7);
                            String envParamVal = envMapStringify(envParam, env.get(envParam));
                            jsonParams.put(paramKey, envParamVal);
                        } else if (paramVal.startsWith("__token__")) {
                            String tokenProp = paramVal.substring(7);
                            String tokenPropVal = token.getProperty(tokenProp);
                            jsonParams.put(paramKey, tokenPropVal);
                        } else if (paramVal.startsWith("__token.")) {
                            String tokenMethod = paramVal.substring(6);
                            java.lang.reflect.Method method = token.getClass().getMethod(tokenMethod);
                            String methodRet = method.invoke(token).toString();
                            jsonParams.put(paramKey, methodRet);
                        } else if (paramVal.startsWith("__req__") && requestHasParams) {
                            String reqParam = paramVal.substring(7);
                            if (reqParamMap.containsKey(reqParam)) {
                                String reqParamVal = reqParamMap.get(reqParam);
                                jsonParams.put(paramKey, reqParamVal);
                            }
                        }
                    }
                }

            }

        } catch (JSONException ex) {
            Logger.getLogger(NeoUniversalCondition.class.getName()).log(Level.SEVERE, null, ex);
        } catch (SSOException ex) {
            Logger.getLogger(NeoUniversalCondition.class.getName()).log(Level.SEVERE, null, ex);
        } catch (NoSuchMethodException ex) {
            Logger.getLogger(NeoUniversalCondition.class.getName()).log(Level.SEVERE, null, ex);
        } catch (SecurityException ex) {
            Logger.getLogger(NeoUniversalCondition.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IllegalAccessException ex) {
            Logger.getLogger(NeoUniversalCondition.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IllegalArgumentException ex) {
            Logger.getLogger(NeoUniversalCondition.class.getName()).log(Level.SEVERE, null, ex);
        } catch (InvocationTargetException ex) {
            Logger.getLogger(NeoUniversalCondition.class.getName()).log(Level.SEVERE, null, ex);
        }

        return jsonParams;
    }

    public String getRequestIp(Map<String, Set<String>> env) {
        String ip = null;
        final Object requestIp = env.get(REQUEST_IP);

        if (requestIp instanceof Set) {
            @SuppressWarnings("unchecked")
            Set<String> requestIpSet = (Set<String>) requestIp;
            if (!requestIpSet.isEmpty()) {
                if (requestIpSet.size() > 1) {
                    debug.warning("Environment map {0} cardinality > 1. Using first from: {1}", REQUEST_IP,
                            requestIpSet);
                }
                ip = requestIpSet.iterator().next();
            }
        } else if (requestIp instanceof String) {
            ip = (String) requestIp;
        }

        if (StringUtils.isBlank(ip)) {
            debug.warning("Environment map {0} is null or empty", REQUEST_IP);
        }
        return ip;
    }

    private String neoQuery(String statement, JSONObject params) {
        // TODO Auto-generated method stub
        driver = getDriver(dbURL, dbUsername, dbPassword);
        Session session = null;
        StatementResult result = null;
        if (params != null) {
            String[] parameters = params.toString().substring(1, params.toString().length() - 1).split("\\,|\\:");
            Object[] endparams = new Object[parameters.length];
            for (int i = 0; i < parameters.length; i++) {
                endparams[i] = parameters[i].substring(1, parameters[i].length() - 1);
            }
            try {
                session = driver.session();
                result = session.run(statement, Values.parameters(endparams));
                List<String> results = new ArrayList<String>();
                while (result.hasNext()) {
                    Record record = result.next();

                    for (String key : record.keys())

                    {
                        results.add(record.get(key).asString());
                    }
                }
                if (!results.isEmpty()) {
                    return results.get(0);
                }

            } catch (NoSuchRecordException ne) {
                debug.error("No records returned");
            } catch (Neo4jException e) {
                debug.message("Error while connecting to neo4j " + e.getMessage());
            } finally {
                if (session != null)
                    session.close();
            }

        } else {

            try {
                session = driver.session();
                result = session.run(statement);
                List<String> results = new ArrayList<String>();
                while (result.hasNext()) {
                    Record record = result.next();

                    for (String key : record.keys())

                    {
                        results.add(record.get(key).asString());
                    }
                }
                if (!results.isEmpty()) {
                    return results.get(0);
                }

            } catch (NoSuchRecordException ne) {
                debug.error("No records returned");
            } catch (Neo4jException e) {
                debug.message("Error while connecting to neo4j " + e.getMessage());
            } finally {
                if (session != null)
                    session.close();
            }
        }

        return null;
    }

    private Driver getDriver(String dbURL2, String dbUsername2, String dbPassword2) {
        // TODO Auto-generated method stub
        if (driver != null)
            return driver;
        driver = GraphDatabase.driver(dbURL, AuthTokens.basic(dbUsername, dbPassword));
        return driver;

    }

    @SuppressWarnings("unchecked")
    private String envMapStringify(String param, Object envMap) {
        String envMapStr = null;

        if (envMap instanceof Set) {
            Set<String> envMapSet = (Set<String>) envMap;
            if (!envMapSet.isEmpty()) {
                if (envMapSet.size() > 1) {
                    debug.warning("Environment map {0} cardinality > 1. Using first from: {1}", param, envMapSet);
                }
                envMapStr = envMapSet.iterator().next();
            }
        } else if (envMap instanceof String) {
            envMapStr = (String) envMap;
        }

        if (StringUtils.isBlank(envMapStr)) {
            debug.warning("Environment map {0} is null or empty", param);
        }
        return envMapStr;

    }

    private String getUserId(Subject subject) throws EntitlementException {
        Principal principal = subject.getPrincipals().iterator().next();
        String userDn = principal.getName();
        int start = userDn.indexOf('=');
        int end = userDn.indexOf(',');
        if (end <= start) {
            throw new EntitlementException(EntitlementException.CONDITION_EVALUATION_FAILED,
                    "Name is not a valid DN: " + userDn);
        }
        String userId = userDn.substring(start + 1, end);
        return userId;
    }

}