/** * 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.oauth; import org.restlet.data.Form; import org.restlet.data.Reference; import org.restlet.ext.oauth.internal.AuthSession; import org.restlet.ext.oauth.internal.Client; import org.restlet.ext.oauth.internal.RedirectionURI; import org.restlet.ext.oauth.internal.Scopes; import org.restlet.ext.oauth.internal.ServerToken; import org.restlet.representation.EmptyRepresentation; import org.restlet.representation.Representation; import org.restlet.resource.Get; import org.restlet.resource.Post; import org.restlet.routing.Redirector; import org.restlet.security.User; /** * Restlet implementation class AuthorizationService. Used for initiating an * OAuth 2.0 authorization request. * * This Resource is controlled by to Context Attribute Parameters * * Implements OAuth 2.0 (RFC6749) * * The following example shows how to set up a simple Authorization Service. * * <pre> * { * @code * public Restlet createInboundRoot(){ * ... * ChallengeAuthenticator au = new ChallengeAuthenticator(getContext(), * ChallengeScheme.HTTP_BASIC, "OAuth Test Server"); * au.setVerifier(new MyVerifier()); * au.setNext(AuthorizationServerResource.class); * root.attach("/authorize", au); * ... * } * </pre> * * * @author Shotaro Uchida <fantom@xmaker.mx> * @author Martin Svensson * * @see <a href="http://tools.ietf.org/html/rfc6749#section-3.1">OAuth 2.0</a> */ public class AuthorizationServerResource extends AuthorizationBaseServerResource { /* * The authorization server MUST support the use of the HTTP "GET" method * [RFC2616] for the authorization endpoint and MAY support the use of the * "POST" method as well. (3.1. Authorization Endpoint) */ public static final String PARAMETER_SUPPORT_POST = "supportPost"; /** * Handle the authorization request. * * @param session * The OAuth session. * * @return The result as a {@link Representation}. */ protected Representation doPostAuthorization(AuthSession session, Client client) { Reference ref = new Reference("riap://application" + HttpOAuthHelper.getAuthPage(getContext())); getLogger().fine("Name = " + getApplication().getInboundRoot()); ref.addQueryParameter("client", session.getClientId()); // Requested scope should not be null. String[] scopes = session.getRequestedScope(); for (String s : scopes) { ref.addQueryParameter("scope", s); } // XXX ServerToken token = (ServerToken) tokens.findToken(client, session.getScopeOwner()); if (token != null && !token.isExpired()) { for (String s : token.getScope()) { ref.addQueryParameter("grantedScope", s); } } // Redirect to AuthPage. getLogger().fine("Redir = " + ref); Redirector dispatcher = new Redirector(getContext(), ref.toString(), Redirector.MODE_SERVER_OUTBOUND); // XXX: Remove? getRequest().getAttributes().put(ClientCookieID, // session.getId()); dispatcher.handle(getRequest(), getResponse()); return getResponseEntity(); } /** * Get request parameter "redirect_uri". (See 3.1.2.3. Dynamic * Configuration) * * @param params * @param client * @return * @throws OAuthException */ protected RedirectionURI getRedirectionURI(Form params, Client client) throws OAuthException { String redirectURI = params.getFirstValue(REDIR_URI); String[] redirectURIs = client.getRedirectURIs(); /* * If multiple redirection URIs have been registered, if only part of * the redirection URI has been registered, or if no redirection URI has * been registered, the client MUST include a redirection URI with the * authorization request using the "redirect_uri" request parameter. * (See 3.1.2.3. Dynamic Configuration) */ if (redirectURIs == null || redirectURIs.length != 1) { if (redirectURI == null || redirectURI.isEmpty()) { throw new OAuthException(OAuthError.invalid_request, "Client MUST include a redirection URI.", null); } } else { if (redirectURI == null || redirectURI.isEmpty()) { // If the optional parameter redirect_uri is not provided, // we use the one provided during client registration. return new RedirectionURI(redirectURIs[0]); } } /* * When a redirection URI is included in an authorization request, the * authorization server MUST compare and match the value received * against at least one of the registered redirection URIs (or URI * components) as defined in [RFC3986] Section 6, if any redirection * URIs were registered. (See 3.1.2.3. Dynamic Configuration) */ for (String uri : redirectURIs) { if (redirectURI.startsWith(uri)) { return new RedirectionURI(redirectURI, true); } } // The provided uri is no based on the uri with the client registration. throw new OAuthException(OAuthError.invalid_request, "Callback URI does not match.", null); } /** * Get request parameter "response_type". * * @param params * @return * @throws OAuthException */ protected ResponseType[] getResponseType(Form params) throws OAuthException { String responseType = params.getFirstValue(RESPONSE_TYPE); if (responseType == null || responseType.isEmpty()) { throw new OAuthException(OAuthError.invalid_request, "No response_type parameter found.", null); } /* * Extension response types MAY contain a space-delimited (%x20) list of * values (3.1.1. Response Type) */ String[] typesString = Scopes.parseScope(responseType); // The same // format as // scope. ResponseType[] types = new ResponseType[typesString.length]; for (int i = 0; i < typesString.length; i++) { try { ResponseType type = Enum.valueOf(ResponseType.class, typesString[i]); getLogger().fine("Found flow - " + type); types[i] = type; } catch (IllegalArgumentException iae) { throw new OAuthException(OAuthError.unsupported_response_type, "Unsupported flow", null); } } return types; } @Get("html") public Representation requestAuthorization() throws OAuthException { return requestAuthorization(getQuery()); } /** * Checks that all incoming requests have a type parameter. Requires * response_type, client_id and redirect_uri parameters. For the code flow * client_secret is also mandatory. */ public Representation requestAuthorization(Form params) throws OAuthException { AuthSession session = getAuthSession(); if (session != null) { return doPostAuthorization(session, clients.findById(session.getClientId())); } final Client client; final RedirectionURI redirectURI; try { client = getClient(params); redirectURI = getRedirectionURI(params, client); } catch (OAuthException ex) { /* * MUST NOT automatically redirect the user-agent to the invalid * redirection URI. (see 3.1.2.4. Invalid Endpoint) */ return getErrorPage( HttpOAuthHelper.getErrorPageTemplate(getContext()), ex); } catch (Exception ex) { // All other exception should be caught as server_error. OAuthException oex = new OAuthException(OAuthError.server_error, ex.getMessage(), null); return getErrorPage( HttpOAuthHelper.getErrorPageTemplate(getContext()), oex); } // Start Session session = setupAuthSession(redirectURI); // Setup session attributes try { ResponseType[] responseTypes = getResponseType(params); if (responseTypes.length != 1) { throw new OAuthException(OAuthError.unsupported_response_type, "Extension response types are not supported.", null); } if (!client.isResponseTypeAllowed(responseTypes[0])) { throw new OAuthException(OAuthError.unauthorized_client, "Unauthorized response type.", null); } session.setAuthFlow(responseTypes[0]); session.setClientId(client.getClientId()); String[] scope = getScope(params); session.setRequestedScope(scope); String state = getState(params); if (state != null && !state.isEmpty()) { session.setState(state); } } catch (OAuthException ex) { ungetAuthSession(); throw ex; } User scopeOwner = getRequest().getClientInfo().getUser(); if (scopeOwner != null) { // If user information is present, use as scope owner. session.setScopeOwner(scopeOwner.getIdentifier()); } if (session.getScopeOwner() == null) { // Redirect to login page. Reference ref = new Reference("." + HttpOAuthHelper.getLoginPage(getContext())); ref.addQueryParameter("continue", getRequest().getOriginalRef() .toString(true, false)); // XXX: Don't need full query. redirectTemporary(ref.toString()); return new EmptyRepresentation(); } return doPostAuthorization(session, client); } @Post("html") public Representation requestAuthorization(Representation input) throws OAuthException { Object supportPost = getContext().getAttributes().get( PARAMETER_SUPPORT_POST); if (!Boolean.parseBoolean(supportPost.toString())) { throw new OAuthException( OAuthError.invalid_request, "Authorization endpoint does NOT support the use of the POST method.", null); } return requestAuthorization(new Form(input)); } }