Package org.restlet.ext.openid

Source Code of org.restlet.ext.openid.OpenIdVerifier

/**
* Copyright 2005-2011 Noelios Technologies.
*
* The contents of this file are subject to the terms of one of the following
* open source licenses: LGPL 3.0 or LGPL 2.1 or CDDL 1.0 or EPL 1.0 (the
* "Licenses"). You can select the license that you prefer but you may not use
* this file except in compliance with one of these Licenses.
*
* You can obtain a copy of the LGPL 3.0 license at
* http://www.opensource.org/licenses/lgpl-3.0.html
*
* You can obtain a copy of the LGPL 2.1 license at
* http://www.opensource.org/licenses/lgpl-2.1.php
*
* You can obtain a copy of the CDDL 1.0 license at
* http://www.opensource.org/licenses/cddl1.php
*
* You can obtain a copy of the EPL 1.0 license at
* http://www.opensource.org/licenses/eclipse-1.0.php
*
* See the Licenses for the specific language governing permissions and
* limitations under the Licenses.
*
* Alternatively, you can obtain a royalty free commercial license with less
* limitations, transferable or non-transferable, directly at
* http://www.noelios.com/products/restlet-engine
*
* Restlet is a registered trademark of Noelios Technologies.
*/

package org.restlet.ext.openid;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.json.JSONException;
import org.json.JSONObject;
import org.openid4java.OpenIDException;
import org.openid4java.consumer.ConsumerException;
import org.openid4java.consumer.ConsumerManager;
import org.openid4java.consumer.VerificationResult;
import org.openid4java.discovery.Discovery;
import org.openid4java.discovery.DiscoveryInformation;
import org.openid4java.discovery.Identifier;
import org.openid4java.message.AuthRequest;
import org.openid4java.message.AuthSuccess;
import org.openid4java.message.MessageExtension;
import org.openid4java.message.ParameterList;
import org.openid4java.message.ax.AxMessage;
import org.openid4java.message.ax.FetchRequest;
import org.openid4java.message.ax.FetchResponse;
import org.restlet.Context;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.data.Cookie;
import org.restlet.data.CookieSetting;
import org.restlet.data.Form;
import org.restlet.data.MediaType;
import org.restlet.ext.openid.internal.OpenIdConsumer;
import org.restlet.representation.Representation;
import org.restlet.representation.StringRepresentation;
import org.restlet.security.User;
import org.restlet.security.Verifier;

/**
* Verifier that will do remote verification of using a provided
* openid_identifier. The verifier will search for the openid_identifier in the
* following three ways
* <ol>
* <li>Check the query for an openid_identifier
* <li>Check the request attribute map for an openid_identifier
* <li>Use the default openid_identifier if possible
* </ol>
* If an openid_identifier is found, it will do a temporary redirect (or return
* a form) to the identifier to continue the authentication process. Upon
* successful authentication the verifier will set the User.
* <p>
* The verifier can also try to request the following attributes to be returned
* by the OpenIdProvider - setOptionalAttribute and setRequiredAttribute.
* </p>
* <ul>
* <li>nickname
* <li>email
* <li>fullname
* <li>dob
* <li>gender
* <li>postcode
* <li>country
* <li>language
* <li>timezone
* </ul>
*
* @author Martin Svensson
*
*/
public class OpenIdVerifier implements Verifier {

    public enum AX {
        country, dob, email, first, friendly, fullname, gender, language, last, postcode, timezone;
    }

    public static final ConcurrentHashMap<AX, String> ax = new ConcurrentHashMap<AX, String>(
            9);

    private static final Discovery discovery = new Discovery();

    private static final ConcurrentHashMap<String, ConsumerManager> managers = new ConcurrentHashMap<String, ConsumerManager>();

    public static final String PROVIDER_FLICKR = "http://flickr.com";

    public static final String PROVIDER_GOOGLE = "https://www.google.com/accounts/o8/id";

    public static final String PROVIDER_MYOPENID = "https://www.myopenid.com/";

    public static final String PROVIDER_MYSPACE = "http://api.myspace.com/openid";

    public static final String PROVIDER_YAHOO = "http://me.yahoo.com";

    private static final ConcurrentHashMap<String, Object> session = new ConcurrentHashMap<String, Object>();

    static {
        ax.put(AX.friendly, "http://axschema.org/namePerson/friendly");
        ax.put(AX.email, "http://axschema.org/contact/email"); // "http://schema.openid.net/contact/email"
        ax.put(AX.first, "http://axschema.org/namePerson/first");
        ax.put(AX.last, "http://axschema.org/namePerson/last");
        ax.put(AX.fullname, "http://axschema.org/namePerson");
        ax.put(AX.dob, "http://axschema.org/birthDate");
        ax.put(AX.gender, "http://axschema.org/person/gender");
        ax.put(AX.postcode, "http://axschema.org/contact/postalCode/home");
        ax.put(AX.country, "http://axschema.org/contact/country/home");
        ax.put(AX.language, "http://axschema.org/pref/language");
        ax.put(AX.timezone, "http://axschema.org/pref/timezone");
    }

    private volatile String defaultProvider;

    private final Set<AX> optionalAttributes;

    private final Set<AX> requiredAttributes;

    private volatile boolean useDefault = false;

    /**
     * Default constructor.
     */
    public OpenIdVerifier() {
        optionalAttributes = new HashSet<AX>();
        requiredAttributes = new HashSet<AX>();
    }

    /**
     * Constructor with a default OpenIdProvider/Identifier.
     *
     * @param defaultProvider
     *            The default OpenIdProvider/Identifier.
     */
    public OpenIdVerifier(String defaultProvider) {
        this();
        setDefaultProvider(defaultProvider);
    }

    /**
     * Adds required User attribute to retrieve during authentication.
     *
     * @param attributeName
     *            The name of the attribute. See valid attributes.
     */
    public void addOptionalAttribute(AX attributeName) {
        this.optionalAttributes.add(attributeName);
    }

    /**
     * Adds an optional User attribute to retrieve during authentication
     *
     * @param attributeName
     *            The name of the attribute. See valid attributes.
     */
    public void addRequiredAttribute(AX attributeName) {
        this.requiredAttributes.add(attributeName);
    }

    /**
     * Clears the set of optional attributes to retrieve.
     */
    public void clearOptionalAttributes() {
        this.optionalAttributes.clear();
    }

    /**
     * Clears the set of required attributes to retrieve.
     */
    public void clearRequiredAttributes() {
        this.requiredAttributes.clear();
    }

    /**
     * Returns the representation of an authentication form, in this case, a
     * simple HTML form.
     *
     * @param authReq
     *            The authentication request.
     * @return The representation of an authentication form.
     */
    private Representation generateForm(AuthRequest authReq) {
        StringBuilder sb = new StringBuilder();
        sb.append("<html>");
        sb.append("<head>");
        sb.append("<title>OpenID HTML FORM Redirection</title>");
        sb.append("</head>");
        sb.append("<body onload=\"document.forms['openid-form-redirection'].submit();\">");
        sb.append("<form name=\"openid-form-redirection\" action=\"");
        sb.append(authReq.getOPEndpoint());
        sb.append("\" method=\"post\" accept-charset=\"utf-8\">");
        for (Object key : authReq.getParameterMap().keySet()) {
            sb.append(" <input type=\"hidden\" name=\"");
            sb.append(key.toString());
            // ${parameter.key}
            sb.append("\" value=\"");
            sb.append(authReq.getParameterMap().get(key));
            sb.append("\"/>");
        }
        sb.append("</form>");
        sb.append("</body>");
        sb.append("</html>");
        return new StringRepresentation(sb.toString(), MediaType.TEXT_HTML);
    }

    /**
     * Returns the {@link ConsumerManager} corresponding to the given URI.
     *
     * @param OPUri
     *            The OP endpoint URI.
     * @return The {@link ConsumerManager} corresponding to the given URI.
     */
    private ConsumerManager getManager(String OPUri) {
        Logger l = Context.getCurrentLogger();
        l.info("Getting consumer manager for - " + OPUri);
        if (!managers.containsKey(OPUri)) {
            // create a new manager
            l.info("Creating new consumer manager for - " + OPUri);
            try {
                ConsumerManager cm = new ConsumerManager();
                cm.setConnectTimeout(30000);
                cm.setSocketTimeout(30000);
                cm.setFailedAssocExpire(0); // sec 0 = disabled
                // cm.setMaxAssocAttempts(4); //default
                managers.put(OPUri, cm);
                return cm;
            } catch (ConsumerException e) {
                l.warning("Failed to create ConsumerManager for - " + OPUri);
            }
            return null;
        } else {
            return managers.get(OPUri);
        }
    }

    /**
     * Extracts the Target (openid identifier/provider) from a set of
     * parameters. First by looking at the query string, then in the request
     * attributes and finally if a default provider has been set
     *
     * @param queryParams
     *            The set of parameters taken from the query string.
     * @param request
     *            The request.
     * @return The target (openid identifier/provider).
     */
    private String getTarget(Form queryParams, Request request) {
        String target = queryParams.getFirstValue("openid_identifier");
        if (target == null)
            target = (String) request.getAttributes().get("openid_identifier");
        if (target == null && useDefault)
            target = defaultProvider;
        if (target == null) {
            Context.getCurrentLogger().info("no target or return specified");
        }
        return target;
    }

    /**
     * Handles the return of an OpenID authentication request. Basically, it
     * extracts the user identifier, and the attributes to be exchanged from the
     * response.
     *
     * @param queryParams
     *            The query's parameters.
     * @param request
     *            The authentication request.
     * @param response
     *            The authentication response.
     * @return An instance of {@link JSONObject} that contains the user
     *         identifier, and the attributes to be exchanged.
     */
    private JSONObject handleReturn(Form queryParams, Request request,
            Response response) {
        JSONObject obj = null;
        Logger l = Context.getCurrentLogger();
        // Map<String, String> axRequired = new HashMap<String, String>();
        Map<AX, String> axResp = new HashMap<AX, String>();
        Identifier i = verifyResponse(queryParams, axResp, request, response);
        if (i == null) {
            l.info("Authentication Failed");
            return obj;
        }
        l.info("Identifier = " + i.getIdentifier());
        String id = i.getIdentifier();

        if (id != null) {
            // New Code, always return JSON and let filter handle any
            // callback.
            // TODO maybe move it to use Principal.
            obj = new JSONObject();
            try {
                obj.put("id", i.getIdentifier());
                for (AX s : axResp.keySet()) {
                    obj.put(s.toString(), axResp.get(s));
                }
            } catch (JSONException e) {
                l.log(Level.WARNING, "Failed to get the ID!", e);
            }

        }
        // cleanup of cookie
        response.getCookieSettings().remove(OpenIdConsumer.DESCRIPTOR_COOKIE);
        CookieSetting disc = new CookieSetting(
                OpenIdConsumer.DESCRIPTOR_COOKIE, "");
        disc.setMaxAge(0);
        response.getCookieSettings().add(disc);
        return obj;
    }

    /**
     * Creates and sends an authentication request.
     *
     * @param target
     *            The target.
     * @param params
     *            The set of params (not used yet).
     * @param request
     *            The original authenticated request.
     * @param response
     *            The response to complete.
     * @throws Exception
     */
    private void handleTarget(String target, Form params, Request request,
            Response response) throws Exception {
        Logger l = Context.getCurrentLogger();

        // create return to url
        String redir = request.getResourceRef().getHostIdentifier()
                + request.getResourceRef().getPath() + "?return=true";

        List<?> discoveries = null;
        discoveries = discovery.discover(target);
        for (Object o : discoveries) {
            if (o instanceof DiscoveryInformation) {
                DiscoveryInformation di = (DiscoveryInformation) o;
                l.info("Found - " + di.getOPEndpoint());
                target = di.getOPEndpoint().toString();
            }
        }

        ConsumerManager manager = getManager(target);
        DiscoveryInformation discovered = manager.associate(discoveries);

        // store the discovery information in the user's session
        // getContext().getAttributes().put("openid-disc", discovered);
        String sessionId = String.valueOf(System.identityHashCode(discovered));
        session.put(sessionId, discovered);

        response.getCookieSettings().add(
                new CookieSetting(OpenIdConsumer.DESCRIPTOR_COOKIE, sessionId));
        l.info("Setting DESCRIPTOR COOKIE");

        // obtain a AuthRequest message to be sent to the OpenID provider
        AuthRequest authReq = manager.authenticate(discovered, redir); // TODO
                                                                       // maybe
                                                                       // add
                                                                       // TIMESTAMP?;
        String ref = request.getResourceRef().getBaseRef().toString();
        l.info("OpenID - REALM = " + ref);
        authReq.setRealm(ref);

        // Attribute Exchange - getting optional and required
        FetchRequest fetch = null;
        // String[] optional = params.getValuesArray("ax_optional", true);
        for (AX o : this.optionalAttributes) {
            if (fetch == null)
                fetch = FetchRequest.createFetchRequest();
            fetch.addAttribute(o.toString(), ax.get(o), false);
        }

        // String[] required = params.getValuesArray("ax_required", true);
        for (AX r : this.requiredAttributes) {
            if (fetch == null)
                fetch = FetchRequest.createFetchRequest();
            fetch.addAttribute(r.toString(), ax.get(r), true);
        }

        if (fetch != null) {
            authReq.addExtension(fetch);
        }

        if (!discovered.isVersion2()) {
            l.info("OpenId - Http Redirect");
            response.redirectTemporary(authReq.getDestinationUrl(true));

        } else {
            l.info("OpenId - HTML Form Redirect");
            Form msg = new Form();
            for (Object key : authReq.getParameterMap().keySet()) {
                msg.add(key.toString(),
                        authReq.getParameterValue(key.toString()));
                l.info("Adding to form - key " + key.toString() + " : value"
                        + authReq.getParameterValue(key.toString()));
            }
            response.setEntity(generateForm(authReq));
            // response.redirectTemporary(authReq.getDestinationUrl(true));
        }

    }

    /**
     * Checks if it looks like a response to an OpenID authentication request.
     *
     * @param queryParams
     *            The list of parameters.
     * @return True if the parameters ensure that it looks like a response to an
     *         OpenID authentication request.
     */
    private boolean isResponse(Form queryParams) {
        String rc = queryParams.getFirstValue("return");
        Context.getCurrentLogger().info("isReturn - " + rc);
        if (rc != null)
            return true;
        return false;
    }

    /**
     * Updates the given user with the values of the specified list of
     * attributes taken from the given {@link JSONObject}.
     *
     * @param attributeSet
     *            The set of attributes.
     * @param object
     *            The source object.
     * @param user
     *            The User instance to update.
     */
    private void setAttributes(Set<AX> attributeSet, JSONObject object,
            User user) {
        if (attributeSet.size() < 1)
            return;
        try {
            if (attributeSet.contains(AX.email)
                    && !object.isNull(AX.email.toString())) {
                user.setEmail(object.getString(AX.email.toString()));
            }
            if (attributeSet.contains(AX.first)
                    && !object.isNull(AX.first.toString())) {
                user.setEmail(object.getString(AX.first.toString()));
            }
            if (attributeSet.contains(AX.last)
                    && !object.isNull(AX.last.toString())) {
                user.setEmail(object.getString(AX.last.toString()));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Sets the default provider. Will also set useDefaultProvider to true.
     */
    public void setDefaultProvider(String provider) {
        this.defaultProvider = provider;
        this.useDefault = true;
    }

    /**
     * Indicates if the defaultProvider must be used in case none is provided in
     * the request.
     *
     * @param useDefault
     *            True if the defaultProvider must be used.
     */
    public void setUseDefaultProvider(boolean useDefault) {
        this.useDefault = useDefault;
    }

    /**
     * Verifies a request. The verifier will be called twice to verify a request
     * since verification is done remotely using callbacks. Also sets the user
     * object.
     *
     * @return {@link Verifier#RESULT_INVALID} if it fails,
     *         {@link Verifier#RESULT_VALID} if success,
     *         {@link Verifier#RESULT_MISSING} while waiting for a callback
     *         response.
     */
    public int verify(Request request, Response response) {
        Form params = request.getResourceRef().getQueryAsForm();
        if (this.isResponse(params)) {
            JSONObject obj = this.handleReturn(params, request, response);
            if (obj == null) {
                return Verifier.RESULT_INVALID;
            } else {
                if (!obj.isNull("id")) {
                    try {
                        Context.getCurrentLogger().info(obj.toString(2));
                        String id = obj.getString("id");
                        User u = new User();
                        u.setIdentifier(id);
                        request.getClientInfo().setUser(u);
                        // set any attributes
                        setAttributes(optionalAttributes, obj, u);
                        setAttributes(requiredAttributes, obj, u);
                        return Verifier.RESULT_VALID;

                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                }
                Context.getCurrentLogger().info("Could not find identifier");

                return Verifier.RESULT_INVALID;
            }
        }
        String target = this.getTarget(params, request);
        if (target != null) {
            try {
                this.handleTarget(target, params, request, response);
                return Verifier.RESULT_MISSING;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        Context.getCurrentLogger()
                .info("No Target or Return - reporting error");
        return Verifier.RESULT_INVALID;
    }

    // --- processing the authentication response ---
    @SuppressWarnings("unchecked")
    public Identifier verifyResponse(Form params, Map<AX, String> axResp,
            Request request, Response resp) {
        Logger l = Context.getCurrentLogger();
        try {
            // extract the parameters from the authentication response
            // (which comes in as a HTTP request from the OpenID provider)
            ParameterList response = new ParameterList(params.getValuesMap());
            l.info("response = " + response);

            // retrieve the previously stored discovery information
            l.info("GET COOKIES");
            String openidDisc = request.getCookies().getFirstValue(
                    OpenIdConsumer.DESCRIPTOR_COOKIE);
            // String openidDisc =
            // getCookieSettings().getFirstValue(DESCRIPTOR_COOKIE);
            l.info("openIdDiscServer - "
                    + resp.getCookieSettings().getFirstValue(
                            "DESCRIPTOR_COOKIE"));
            l.info("openIdDiscServerLength -" + resp.getCookieSettings().size());
            l.info("openIdDiscClient - " + openidDisc);
            l.info("openIdDiscClientLength -" + request.getCookies().size());

            if (resp.getCookieSettings().size() > 0) {
                for (CookieSetting setting : resp.getCookieSettings()) {
                    l.info("CookieSetting: " + setting.getName()
                            + setting.getFirst());
                }
            }
            if (request.getCookies().size() > 0) {
                for (Cookie setting : request.getCookies()) {
                    l.info("Cookie: " + setting.getName() + setting.getFirst());
                }
            }

            DiscoveryInformation discovered = (DiscoveryInformation) session
                    .get(openidDisc); // TODO cleanup

            l.info("discovered = " + discovered);

            // extract the receiving URL from the HTTP request

            l.info("getOriginalRef = " + request.getOriginalRef());

            ConsumerManager manager = getManager(discovered.getOPEndpoint()
                    .toString());
            String redir = request.getResourceRef().getHostIdentifier()
                    + request.getResourceRef().getPath() + "?return=true";

            VerificationResult verification = manager.verify(redir, response,
                    discovered);

            // examine the verification result and extract the verified
            // identifier
            Identifier verified = verification.getVerifiedId();
            l.info("verified = " + verified);
            if (verified != null) {
                AuthSuccess authSuccess = (AuthSuccess) verification
                        .getAuthResponse();

                if (authSuccess.hasExtension(AxMessage.OPENID_NS_AX)) {
                    FetchResponse fetchResp = (FetchResponse) authSuccess
                            .getExtension(AxMessage.OPENID_NS_AX);

                    MessageExtension ext = authSuccess
                            .getExtension(AxMessage.OPENID_NS_AX);
                    if (ext instanceof FetchResponse) {
                        List<String> aliases = fetchResp.getAttributeAliases();
                        for (String alias : aliases) {
                            String value = fetchResp.getAttributeValue(alias);
                            axResp.put(AX.valueOf(alias), value);
                        }
                    }
                }

                return verified; // success
            }
        } catch (OpenIDException e) {
            l.log(Level.INFO, "", e);
        }
        return null;
    }

}
TOP

Related Classes of org.restlet.ext.openid.OpenIdVerifier

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.