/** * 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 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 Apache 2.0 license at * http://www.opensource.org/licenses/apache-2.0 * * You can obtain a copy of the LGPL 3.0 license at * http://www.opensource.org/licenses/lgpl-3.0 * * You can obtain a copy of the LGPL 2.1 license at * http://www.opensource.org/licenses/lgpl-2.1 * * You can obtain a copy of the CDDL 1.0 license at * http://www.opensource.org/licenses/cddl1 * * 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://www.restlet.com/products/restlet-framework * * Restlet is a registered trademark of Restlet */ package org.restlet.ext.oauth; import org.deviceconnect.android.localoauth.temp.ResultRepresentation; import org.json.JSONException; import org.json.JSONObject; import org.restlet.data.Form; import org.restlet.ext.json.JsonRepresentation; import org.restlet.ext.oauth.internal.AuthSession; import org.restlet.ext.oauth.internal.AuthSessionTimeoutException; import org.restlet.ext.oauth.internal.Client; import org.restlet.ext.oauth.internal.Scope; import org.restlet.ext.oauth.internal.Scopes; import org.restlet.ext.oauth.internal.Token; import org.restlet.representation.Representation; import org.restlet.security.User; import android.util.Base64; /** * Server resource used to acquire an OAuth token. A code, or refresh token can * be exchanged for a working token. * * Implements OAuth 2.0 (RFC6749) * * Example. Attach an AccessTokenServerResource * * <pre> * { * @code * public Restlet createInboundRoot(){ * ... * root.attach("/token", AccessTokenServerResource.class); * ... * } * } * </pre> * * @author Shotaro Uchida <fantom@xmaker.mx> * @author Kristoffer Gronowski * * @see <a href="http://tools.ietf.org/html/rfc6749#section-3.2">OAuth 2.0 (3.2. * Token Endpoint)</a> */ public class AccessTokenServerResource extends OAuthServerResource { protected static Client getAuthenticatedClient() throws OAuthException { User authenticatedClient = getRequest().getClientInfo().getUser(); if (authenticatedClient == null) { getLogger().warning("Authenticated client_id is missing."); return null; } // XXX: We 'know' the client was authenticated before, 'client' should // not be null. Client client = clients.findById(authenticatedClient.getIdentifier()); getLogger().fine( "Requested by authenticated client " + client.getClientId()); return client; } protected static void ensureGrantTypeAllowed(Client client, GrantType grantType) throws OAuthException { if (!client.isGrantTypeAllowed(grantType)) { throw new OAuthException(OAuthError.unauthorized_client, "Unauthorized grant type.", null); } } /** * Handles the {@link Post} request. The client MUST use the HTTP "POST" * method when making access token requests. (3.2. Token Endpoint) * * @param input * HTML form formated token request per oauth-v2 spec. * @return JSON response with token or error.<br> * ※Local OAuth ではResultRepresentation型のポインタを返す。<br> * getResult()=trueならアクセストークンが含まれている。アクセストークンはgetText()で取得する。<br> * getResult()=falseならエラー。アクセストークンは取得できない。<br> */ public static Representation requestToken(Representation input) throws OAuthException, JSONException { getLogger().fine("Grant request"); final Form params = new Form(input); final GrantType grantType = getGrantType(params); switch (grantType) { case authorization_code: getLogger().info("Authorization Code Grant"); return doAuthCodeFlow(params); // case password: // getLogger().info("Resource Owner Password Credentials Grant"); // return doPasswordFlow(params); // case client_credentials: // getLogger().info("Client Credentials Grantt"); // return doClientFlow(params); // case refresh_token: // getLogger().info("Refreshing an Access Token"); // return doRefreshFlow(params); default: getLogger().warning("Unsupported flow: " + grantType); throw new OAuthException(OAuthError.unsupported_grant_type, "Flow not supported", null); } } /** * Get request parameter "grant_type". * * @param params * @return * @throws OAuthException */ protected static GrantType getGrantType(Form params) throws OAuthException { String typeString = params.getFirstValue(GRANT_TYPE); getLogger().info("Type: " + typeString); try { GrantType type = Enum.valueOf(GrantType.class, typeString); getLogger().fine("Found flow - " + type); return type; } catch (IllegalArgumentException iae) { throw new OAuthException(OAuthError.unsupported_grant_type, "Unsupported flow", null); } catch (NullPointerException npe) { throw new OAuthException(OAuthError.invalid_request, "No grant_type parameter found.", null); } } /** * Get request parameter "code". * * @param params * @return * @throws OAuthException */ protected static String getCode(Form params) throws OAuthException { String code = params.getFirstValue(CODE); if (code == null || code.isEmpty()) { throw new OAuthException(OAuthError.invalid_request, "Mandatory parameter code is missing", null); } return code; } // XXX [MEMO]Restletコードをstatic化。 /** * Get request parameter "redirect_uri". * * @param params * @return * @throws OAuthException */ protected static String getRedirectURI(Form params) throws OAuthException { String redirUri = params.getFirstValue(REDIR_URI); if (redirUri == null || redirUri.isEmpty()) { throw new OAuthException(OAuthError.invalid_request, "Mandatory parameter redirect_uri is missing", null); } return redirUri; } // XXX [MEMO]追加。 protected static String getApplicationName(Form params) throws OAuthException { String base64ApplicationName = params.getFirstValue(APPLICATION_NAME); String applicationName = new String(Base64.decode(base64ApplicationName, Base64.URL_SAFE|Base64.NO_WRAP)); if (applicationName == null || applicationName.isEmpty()) { throw new OAuthException(OAuthError.invalid_request, "Mandatory parameter application_name is missing", null); } return applicationName; } /** * Response JSON document with valid token. The format of the JSON document * is according to 5.1. Successful Response. * * @param token * The token generated by the client. * @param requestedScope * The scope originally requested by the client. * @return The token representation as described in RFC6749 5.1. <br> * ※Local OAuth ではResultRepresentation型のポインタを返し、result=trueならアクセストークンを設定して返す。<br> * アクセストークンは、getText()で返す。 * @throws ResourceException */ protected static Representation responseTokenRepresentation(Token token, String[] requestedScope) throws JSONException { JSONObject response = new JSONObject(); response.put(TOKEN_TYPE, token.getTokenType()); response.put(ACCESS_TOKEN, token.getAccessToken()); // response.put(EXPIRES_IN, token.getExpirePeriod()); String refreshToken = token.getRefreshToken(); if (refreshToken != null && !refreshToken.isEmpty()) { response.put(REFRESH_TOKEN, refreshToken); } Scope[] scope = token.getScope(); if (!Scopes.isIdentical(Scope.toScopeStringArray(scope), requestedScope)) { /* * OPTIONAL, if identical to the scope requested by the client, * otherwise REQUIRED. (5.1. Successful Response) */ response.put(SCOPE, Scopes.toString(scope)); } return new JsonRepresentation(response); } /** * Executes the 'authorization_code' flow. (4.1.3. Access Token Request) * * @param params * @return * @throws OAuthException * @throws JSONException */ private static Representation doAuthCodeFlow(Form params) throws OAuthException, JSONException { // The flow require authenticated client. Client client = getAuthenticatedClient(); if (client == null) { // Use the public client. (4.1.3. Access Token Request) client = getClient(params); } ensureGrantTypeAllowed(client, GrantType.authorization_code); String code = getCode(params); /* * ensure that the authorization code was issued to the authenticated * confidential client, or if the client is public, ensure that the code * was issued to "client_id" in the request, (4.1.3. Access Token * Request) */ AuthSession session = tokens.restoreSession(code); if (!client.getClientId().equals(session.getClientId())) { throw new OAuthException(OAuthError.invalid_grant, "The code was not issued to the client.", null); } try { // Ensure that the session is not timeout. session.updateActivity(); } catch (AuthSessionTimeoutException ex) { throw new OAuthException(OAuthError.invalid_grant, "Code expired.", null); } /* * ensure that the "redirect_uri" parameter is present if the * "redirect_uri" parameter was included in the initial authorization * request as described in Section 4.1.1, and if included ensure that * their values are identical. (4.1.3. Access Token Request) */ if (session.getRedirectionURI().isDynamicConfigured()) { String redirectURI = getRedirectURI(params); if (!redirectURI.equals(session.getRedirectionURI().getURI())) { throw new OAuthException( OAuthError.invalid_grant, "The redirect_uri is not identical to the one included in the initial authorization request.", null); } } String applicationName = getApplicationName(params); Token token = tokens.generateToken(client, session.getScopeOwner(), session.getGrantedScope(), applicationName); String accessToken = token.getAccessToken(); ResultRepresentation resultRepresentation = new ResultRepresentation(); resultRepresentation.setResult(true); resultRepresentation.setText(accessToken); return resultRepresentation; } }