/** * Copyright 2005-2014 Restlet * * The contents of this file are subject to the terms of one of the following * open source licenses: Apache 2.0 or 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 Apache 2.0 license at * http://www.opensource.org/licenses/apache-2.0 * * You can obtain a copy of the EPL 1.0 license at * http://www.opensource.org/licenses/eclipse-1.0 * * 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://restlet.com/products/restlet-framework * * Restlet is a registered trademark of Restlet S.A.S. */ package org.restlet.ext.openid; import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; import org.restlet.Restlet; import org.restlet.data.Cookie; import org.restlet.data.CookieSetting; import org.restlet.data.Status; import org.restlet.security.Authenticator; import org.restlet.security.User; import org.restlet.security.Verifier; /** * An authenticator that redirects the authentication to some external resource. * After successful authentication, it will do a redirect to the original * request resourceRef. The RedirectAuthenticator keeps track of state using a * session cookie which is not automatically cleaned. * * The typical use case for this {@link Authenticator} is to do remote * authentication using OpenID. * * The RedirectAuthenticator has the following logic based on {@link Verifier} * returns: * <ol> * <li>If the verifier returns {@link Verifier#RESULT_VALID} it will clean up * any unneeded cookies and do a * {@link Response#redirectPermanent(org.restlet.data.Reference)} to the * original resource * <li>If the result is {@link Verifier#RESULT_INVALID} or * {@link Verifier#RESULT_UNKNOWN} it will clean up all cookies and call forbid * (default behavior to set {@link Status#CLIENT_ERROR_FORBIDDEN} if no * errorResource has been set) * <li>If the result is any other it will clean up the identifierCookie. * </ol> * * <pre> * * </pre> * * @author Martin Svensson */ public class RedirectAuthenticator extends Authenticator { /** The default name of the cookie that contains the identifier. */ public final static String DEFAULT_IDENTIFIER_COOKIE = "session_id"; /** * The default name of the cookie that contains the original request's * reference. */ public final static String DEFAULT_ORIGINAL_REF_COOKIE = "original_ref"; public final static String ORIGINAL_REF_ATTRIBUTE = "origRef"; public static void clearIdentifierCookie(String cookieId, Request req, Response res) { Cookie cookie = req.getCookies().getFirst(cookieId); CookieSetting identifierCookie = res.getCookieSettings().getFirst( cookieId); if (identifierCookie == null && cookie != null) { identifierCookie = new CookieSetting(cookieId, null); res.getCookieSettings().add(identifierCookie); } if (identifierCookie != null) identifierCookie.setMaxAge(0); } public static void clearIdentiiferCookie(Request req, Response res) { clearIdentifierCookie(DEFAULT_IDENTIFIER_COOKIE, req, res); } // private final String errorResource; /** * The restlet in charge of handling authentication or authorization * failure. */ private Restlet forbiddenResource; /** The current name of the cookie that contains the identifier. */ private final String identifierCookie; /** * The current name of the cookie that contains the original request's * reference. */ private final String origRefCookie; /** The verifier of the credentials. */ private final Verifier verifier; /** * Initialize a RedirectAuthenticator with a Verifier. * * @param context * - Context * @param verifier * - A Verifier that sets user identifier upon completion */ public RedirectAuthenticator(Context context, Verifier verifier, Restlet forbiddenResource) { super(context); this.forbiddenResource = forbiddenResource; this.verifier = verifier; this.origRefCookie = DEFAULT_ORIGINAL_REF_COOKIE; this.identifierCookie = DEFAULT_IDENTIFIER_COOKIE; } /** * Initializes a RedirectAuthenticator with a Verifier. * * @param context * The context. * @param verifier * The verifier that sets user identifier upon completion. * @param identifierCookie * The name of the cookie that contains the identifier. * @param origRefCookie * The name of the cookie that contains the original request's * reference. * @param forbiddenResource * The Restlet that will handle the call in case of * authentication or authorization failure. */ public RedirectAuthenticator(Context context, Verifier verifier, String identifierCookie, String origRefCookie, Restlet forbiddenResource) { super(context); this.forbiddenResource = forbiddenResource; this.verifier = verifier; this.identifierCookie = identifierCookie != null ? identifierCookie : DEFAULT_IDENTIFIER_COOKIE; this.origRefCookie = origRefCookie != null ? origRefCookie : DEFAULT_ORIGINAL_REF_COOKIE; } @Override protected boolean authenticate(Request request, Response response) { User u = request.getClientInfo().getUser(); String identifier = request.getCookies() .getFirstValue(identifierCookie); String origRef; if (identifier != null) { u = new User(identifier); request.getClientInfo().setUser(u); handleUser(u, true); return true; } if (request.getCookies().getFirstValue(origRefCookie) == null) { origRef = request.getResourceRef().toString(); response.getCookieSettings().add(origRefCookie, request.getResourceRef().toString()); } else { origRef = request.getCookies().getFirstValue(origRefCookie); } int verified = verifier.verify(request, response); getLogger().fine("VERIFIED: " + verified); if (verified == Verifier.RESULT_VALID) { response.getCookieSettings().removeAll(identifierCookie); response.getCookieSettings().add(identifierCookie, request.getClientInfo().getUser().getIdentifier()); handleUser(request.getClientInfo().getUser(), false); // String origRef = // request.getCookies().getFirstValue(origRefCookie); request.getCookies().removeAll(origRefCookie); response.getCookieSettings().removeAll(origRefCookie); if (origRef != null) { response.redirectPermanent(origRef); } return true; } response.getCookieSettings().removeAll(identifierCookie); if (verified == Verifier.RESULT_UNKNOWN || verified == Verifier.RESULT_INVALID) { origRef = response.getCookieSettings().getFirstValue(origRefCookie); if (origRef == null) origRef = request.getCookies().getFirstValue(origRefCookie); // request.getCookies().removeAll(origRefCookie); // response.getCookieSettings().removeAll(origRefCookie); forbid(origRef, request, response); } return false; } @Override protected int authenticated(Request request, Response response) { int ret = super.authenticated(request, response); if (response != null && response.getStatus().isRedirection()) return STOP; return ret; } /** * Rejects the call due to a failed authentication or authorization. This * can be overridden to change the default behavior, for example to display * an error page. By default, calls errorResource.handle (if provided) * otherwise it will set the response status to ClIENT_ERROR_FORBIDDEN * * @param origRef * The original ref stored by the RedirectAuthenticator * @param request * The rejected request. * @param response * The reject response. */ public void forbid(String origRef, Request request, Response response) { if (forbiddenResource == null) response.setStatus(Status.CLIENT_ERROR_FORBIDDEN); else { getLogger().fine("sending to error resource"); forbiddenResource.handle(request, response); } } /** * Handles the retrieved user from the verifier. The only thing that will be * stored is the user identifier (in a cookie). Should be overridden as it * does nothing by default. * * @param user * The user. */ protected void handleUser(User user, boolean cached) { getLogger().info( "Handle User: " + user.getIdentifier() + " " + user.getEmail()); ; } @Override protected int unauthenticated(Request request, Response response) { int ret = super.unauthenticated(request, response); return ret; } }