edu.washington.shibboleth.attribute.resolver.dc.rws.impl.RwsDataConnector.java Source code

Java tutorial

Introduction

Here is the source code for edu.washington.shibboleth.attribute.resolver.dc.rws.impl.RwsDataConnector.java

Source

/* ========================================================================
 * Copyright (c) 2015 The University of Washington
 *
 * 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 edu.washington.shibboleth.attribute.resolver.dc.rws.impl;

import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.ArrayList;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.StringTokenizer;
import java.lang.IllegalArgumentException;

import java.net.URL;
import java.net.MalformedURLException;
import javax.xml.parsers.ParserConfigurationException;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.xml.namespace.QName;

import javax.naming.NamingException;
import javax.naming.directory.SearchResult;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;

import javax.xml.transform.dom.DOMSource;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathFactory;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.AuthState;

import org.opensaml.security.x509.X509Credential;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;

import net.shibboleth.idp.attribute.IdPAttribute;
import net.shibboleth.idp.attribute.IdPAttributeValue;
import net.shibboleth.idp.attribute.StringAttributeValue;
import net.shibboleth.idp.attribute.resolver.AbstractDataConnector;
import net.shibboleth.idp.attribute.resolver.PluginDependencySupport;
import net.shibboleth.idp.attribute.resolver.ResolutionException;

import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
import net.shibboleth.idp.attribute.resolver.context.AttributeResolverWorkContext;

// import net.shibboleth.idp.attribute.resolver.dc.impl.AbstractSearchDataConnector;
// import net.shibboleth.idp.attribute.resolver.dc.impl.ValidationException;
// import net.shibboleth.idp.attribute.resolver.dc.impl.Validator;
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
import net.shibboleth.utilities.java.support.component.ComponentSupport;
import net.shibboleth.utilities.java.support.logic.Constraint;
import net.shibboleth.utilities.java.support.primitive.StringSupport;

import edu.washington.shibboleth.attribute.resolver.dc.rws.HttpDataSource;

/**
 * <code>RwsDataConnector</code> provides a plugin to retrieve attributes from a restful webservice.
 */
public class RwsDataConnector extends AbstractDataConnector {

    /** Authentication type values. */
    public static enum AUTHENTICATION_TYPE {
        /** No authentication type. */
        NONE,
        /** Basic authentication type. */
        BASIC,
        /** Client cretificate authentication type. */
        CLIENT_CERT
    };

    /** Class logger. */
    private static Logger log = LoggerFactory.getLogger(RwsDataConnector.class);

    /** values returned by this connector. */
    private Map<String, IdPAttribute> attributes;

    /** HTTP data source **/
    private HttpDataSource httpDataSource;

    /** builder for query string **/
    private TemplatedQueryStringBuilder queryStringBuilder;

    /** Authentication type */
    private AUTHENTICATION_TYPE authenticationType;

    /** Username if basic auth */
    private String username;

    /** Password if basic auth */
    private String password;

    /** Cred provider if basic auth */
    private CredentialsProvider credsProvider;

    /** Whether an empty result set is an error. */
    private boolean noResultsIsError;

    /** Whether to cache search results for the duration of the session. */
    private boolean cacheResults;

    /** Time, in milliseconds, to wait for a search to return. */
    private int searchTimeLimit;

    /** Base url of the webservice */
    private String baseUrl;
    private URL baseURL;
    private int basePort;

    /** Data cache. */
    // private Map<String, Map<String, Map<String, IdPAttribute>>> cache;

    /** Whether this data connector has been initialized. */
    private boolean initialized;

    /** Filter value escaping strategy. */
    // private final URLValueEscapingStrategy escapingStrategy = null;

    /** max results. */
    private int maxResults;

    /** parser for ws response. */
    private DocumentBuilder documentBuilder;

    /** xpath evaluator */
    XPathExpression xpathExpression;

    /** Attributes to fetch */
    private List<RwsAttribute> rwsAttributes;

    /**
     * This creates a new data connector with the supplied properties.
     * 
     */
    public RwsDataConnector() {
        super();

    }

    /**
     * Initializes the connector
     */
    @Override
    protected void doInitialize() throws ComponentInitializationException {
        super.doInitialize();

        if (httpDataSource == null) {
            throw new ComponentInitializationException(getLogPrefix() + " no http data source was configured");
        }

        try {
            DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
            domFactory.setNamespaceAware(false); // parameter
            domFactory.setValidating(false);
            String feature = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
            domFactory.setFeature(feature, false);
            feature = "http://apache.org/xml/features/nonvalidating/load-dtd-grammar";
            domFactory.setFeature(feature, false);
            documentBuilder = domFactory.newDocumentBuilder();

        } catch (ParserConfigurationException e) {
            log.error("javax.xml.parsers.ParserConfigurationException: " + e);
        }

        for (int i = 0; i < rwsAttributes.size(); i++) {
            RwsAttribute attr = rwsAttributes.get(i);
            try {
                XPath xpath = XPathFactory.newInstance().newXPath();
                log.debug("xpath for {} is {}", attr.name, attr.xPath);
                attr.xpathExpression = xpath.compile(attr.xPath);
            } catch (XPathExpressionException e) {
                log.error("xpath expr: " + e);
            }
        }

        // initializeCache();

    }

    /** {@inheritDoc} */
    @Override
    @Nonnull
    protected Map<String, IdPAttribute> doDataConnectorResolve(
            @Nonnull final AttributeResolutionContext resolutionContext,
            @Nonnull final AttributeResolverWorkContext workContext) throws ResolutionException {
        ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);

        final Map<String, List<IdPAttributeValue<?>>> dependsAttributes = PluginDependencySupport
                .getAllAttributeValues(workContext, getDependencies());

        String queryString = queryStringBuilder.getQueryString(resolutionContext, dependsAttributes);
        queryString = queryString.trim();
        log.debug("RWS query filter: {}", queryString);

        // create Attribute objects to return
        Map<String, IdPAttribute> attributes = null;

        // not using cache for now 

        if (attributes == null) {
            log.debug("Retrieving attributes from GWS");
            attributes = getRwsAttributes(queryString);
            // if (cacheResults && attributes != null) {
            // setCachedAttributes(resolutionContext, queryString, attributes);
            // log.debug("Stored results in the cache");
            // }
        }

        log.trace("{} Resolved attributes: {}", getLogPrefix(), attributes);
        return attributes;
    }

    /**
     * This queries the web service and return the resolved attributes.
     *
     * @param queryString <code>String</code> the queryString for the rest get
     * @return <code>List</code> of results
     * @throws ResolutionException if an error occurs performing the search
     */
    protected Map<String, IdPAttribute> getRwsAttributes(String queryString) throws ResolutionException {
        try {
            String xml = httpDataSource.getResource(baseUrl + queryString);

            /** The parser needs to be synchronized **/
            Document doc = null;
            synchronized (this) {
                doc = documentBuilder.parse(new InputSource(new StringReader(xml)));
            }

            Map<String, IdPAttribute> attributes = new HashMap<String, IdPAttribute>();

            /* look for the requested attributes */
            for (int i = 0; i < rwsAttributes.size(); i++) {
                RwsAttribute attr = rwsAttributes.get(i);

                Object result = attr.xpathExpression.evaluate(doc, XPathConstants.NODESET);
                NodeList nodes = (NodeList) result;
                log.debug("got {} matches to the xpath for {}", nodes.getLength(), attr.name);

                List<String> results = new Vector<String>();
                if (nodes.getLength() == 0 && attr.noResultIsError) {
                    log.error("got no attributes for {}, which required attriubtes", attr.name);
                    throw new ResolutionException("no attributes for " + attr.name);
                }
                for (int j = 0; j < nodes.getLength(); j++) {
                    if (maxResults > 0 && maxResults < j) {
                        log.error("too many results for {}", attr.name);
                        break;
                    }
                    results.add((String) nodes.item(j).getTextContent());
                }
                addIdPAttributes(attributes, attr.name, results);
            }
            return attributes;

        } catch (IOException e) {
            log.error("rws io exception: " + e);
            throw new ResolutionException("rws resolver io error: " + e.getMessage());
        } catch (SAXException e) {
            log.error("rws sax exception: " + e);
            throw new ResolutionException("rws resolver parse error: " + e.getMessage());
        } catch (IllegalArgumentException e) {
            log.error("rws arg exception: " + e);
            throw new ResolutionException(e.getMessage());
        } catch (XPathExpressionException e) {
            log.error("rws xpath exception: " + e);
            throw new ResolutionException(e.getMessage());
        }

    }

    /**
     * This adds an attribute name and value to the IdP's list of attributes
     *
     */
    protected void addIdPAttributes(Map<String, IdPAttribute> attributes, String name, List<String> results) {

        if (results.size() > 0) {

            IdPAttribute attribute = new IdPAttribute(name);

            List<IdPAttributeValue<?>> values = new ArrayList<>(results.size());
            for (String result : results) {
                values.add(new StringAttributeValue(result));
            }
            attribute.setValues(values);
            attributes.put(name, attribute);
        }
    }

    /** Property setters */

    /**
     * This sets the http data source bean
     */
    public void setHttpDataSource(HttpDataSource v) {
        httpDataSource = v;
    }

    /**
     * This sets whether this connector will throw an exception if no search results are found.
     * 
     * @param b <code>boolean</code>
     */
    public void setNoResultsIsError(boolean b) {
        noResultsIsError = b;
    }

    /**
     * This sets the base URL for this connector
     *
     */
    public void setBaseUrl(String v) {
        baseUrl = v;
    }

    /**
     * Sets the authentication type
     * 
     * @param type typr 
     */
    public void setAuthenticationType(AUTHENTICATION_TYPE type) {
        authenticationType = type;
    }

    /**
     * This sets the time in milliseconds that the ldap will wait for search results. A value of 0 means to wait
     * indefinitely. This method will remove any cached results.
     * 
     * @see #clearCache()
     * 
     * @param i <code>int</code> milliseconds
     */
    public void setSearchTimeLimit(int i) {
        searchTimeLimit = i;
        clearCache();
    }

    /**
     * This sets the maximum number of search results to return. A value of 0 all entries will be returned.
     * This method will remove any cached results.
     * 
     * @see #clearCache()
     * 
     * @param l <code>long</code> maximum number of search results
     */
    public void setMaxResults(int max) {
        maxResults = max;
        clearCache();
    }

    /**
     * This sets the basic auth username
     * 
     * @param s <code>String</code> username
     */
    public void setUsername(String u) {
        username = u;
    }

    /**
     * This sets the basic auth password
     * 
     * @param s <code>String</code> password
     */
    public void setPassword(String p) {
        password = p;
    }

    /**
     * This sets the attributes to query
     */
    public void setRwsAttributes(List<RwsAttribute> list) {
        rwsAttributes = list;
    }

    /**
     * Sets the builder used to create the executable searches.
     *
     * @param builder builder used to create the executable searches
     */
    public void setQueryStringBuilder(@Nonnull final TemplatedQueryStringBuilder builder) {
        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);

        queryStringBuilder = Constraint.isNotNull(builder, "TemplatedQueryStringBuilder can not be null");
    }

    private void clearCache() {
    }

}