Package org.restlet.ext.oauth

Source Code of org.restlet.ext.oauth.AccessTokenServerResource

/**
* 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.oauth;

import java.util.List;
import java.util.logging.Level;

import org.json.JSONException;
import org.json.JSONObject;
import org.restlet.data.ChallengeResponse;
import org.restlet.data.ChallengeScheme;
import org.restlet.data.Form;
import org.restlet.data.Status;
import org.restlet.engine.util.Base64;
import org.restlet.ext.oauth.internal.ExpireToken;
import org.restlet.ext.oauth.internal.JsonStringRepresentation;
import org.restlet.ext.oauth.internal.Scopes;
import org.restlet.ext.oauth.internal.Token;
import org.restlet.representation.Representation;
import org.restlet.resource.Post;
import org.restlet.resource.ResourceException;
import org.restlet.security.Role;

/**
* Server resource used to acquire an OAuth token. A code, or refresh token can
* be exchanged for a working token. This resource also supports the none flow.
*
* Note: at the moment password and assertion flows are not supported.
* Implements OAuth 2.0 draft 10
*
* @author Kristoffer Gronowski
*
* @see <a
*      href="http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4">OAuth
*      2 draft 10</a>
*/
public class AccessTokenServerResource extends OAuthServerResource {

    /**
     * Converts a {@link Token} to its equivalent as a {@link JSONObject}.
     *
     * @param token
     *            The token.
     * @param scopes
     *            The list of scopes.
     * @return An instance of {@link Token} equivalent to the given token.
     * @throws ResourceException
     */
    private JSONObject createJsonToken(Token token, String scopes)
            throws ResourceException {
        JSONObject body = new JSONObject();

        try {
            body.put(ACCESS_TOKEN, token.getToken());
            if (token instanceof ExpireToken) {
                ExpireToken et = (ExpireToken) token;
                body.put(EXPIRES_IN, et.getExpirePeriod());
                body.put(REFRESH_TOKEN, et.getRefreshToken());
            }
            // TODO add scope
        } catch (JSONException e) {
            throw new ResourceException(Status.SERVER_ERROR_INTERNAL,
                    "Failed to generate JSON", e);
        }

        return body;
    }

    /**
     * Executes the authentication flow.
     *
     * @param clientId
     *            The client identifier.
     * @param clientSecret
     *            The client's secret.
     * @param params
     *            The authentication parameters.
     * @return The result of the flow.
     * @throws IllegalArgumentException
     */
    // TODO The secret should be a char[].
    private Representation doAuthCodeFlow(String clientId, String clientSecret,
            Form params) throws IllegalArgumentException {
        String redirUri = params.getFirstValue(REDIR_URI);
        if (redirUri == null || redirUri.length() == 0) {
            setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
            return sendError(OAuthError.INVALID_REQUEST,
                    "Mandatory parameter redirect_uri is missing", null);
        }

        String code = params.getFirstValue(CODE);
        if (code == null || code.length() == 0) {
            setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
            return sendError(OAuthError.INVALID_REQUEST,
                    "Mandatory parameter code is missing", null);
        }

        Client client = validate(clientId, clientSecret);
        // null check on failed
        if (client == null) {
            setStatus(Status.CLIENT_ERROR_FORBIDDEN);
            return sendError(OAuthError.INVALID_CLIENT,
                    "Client id verification failed.", null);
        }

        // check the client secret
        if (!clientSecret.equals(client.getClientSecret())) {
            setStatus(Status.CLIENT_ERROR_UNAUTHORIZED);
            return sendError(OAuthError.INVALID_GRANT,
                    "Client secret did not match", null);
        }

        // TODO could add a cookie match on the owner but could fail if code is
        // sent to other entity
        // unauthorized_client, right now this is only performed if
        // ScopedResource getOwner returns the user

        // 5 min timeout on tokens, 0 for unlimited
        Token token = generator.exchangeForToken(code, tokenTimeSec);

        // TODO send back scopes if limited

        JSONObject body = createJsonToken(token, null);

        // Sets the no-store Cache-Control header
        getResponse().setCacheDirectives(noStore);
        return new JsonStringRepresentation(body);
    }

    /**
     * Executes the "none" flow.
     *
     * @param clientId
     *            The client identifier.
     * @param clientSecret
     *            The client's secret.
     * @param params
     *            The authentication parameters.
     * @return The result of the flow.
     */
    // TODO The secret should be a char[].
    private Representation doNoneFlow(String clientId, String clientSecret,
            Form params) {
        Client client = validate(clientId, clientSecret);

        // null check on failed
        if (client == null) {
            setStatus(Status.CLIENT_ERROR_FORBIDDEN);
            return sendError(OAuthError.INVALID_CLIENT,
                    "Client id verification failed.", null);
        }

        if (!client.containsUser(AUTONOMOUS_USER))
            client.createUser(AUTONOMOUS_USER);

        AuthenticatedUser user = client.findUser(AUTONOMOUS_USER);

        // Adding all scopes since super-user
        // String[] scopes = parseScope(params.getFirstValue(SCOPE));
        List<Role> roles = Scopes.toRoles(params.getFirstValue(SCOPE));
        for (Role r : roles) {
            getLogger().info("Requested scopes none flow = " + roles);
            user.addRole(r, "");
            getLogger().info("Adding scope = " + r.getName() + " to auto user");
        }

        Token token = generator.generateToken(user, tokenTimeSec);
        JSONObject body = createJsonToken(token, null); // Scopes N/A

        // Sets the no-store Cache-Control header
        getResponse().setCacheDirectives(noStore);
        return new JsonStringRepresentation(body);
    }

    /**
     * Executes the "password" flow.
     *
     * @param clientId
     *            The client identifier.
     * @param clientSecret
     *            The client's secret.
     * @param params
     *            The authentication parameters.
     * @return The result of the flow.
     */
    // TODO The secret should be a char[].
    private Representation doPasswordFlow(String clientId, String clientSecret,
            Form params) {
        Client client = validate(clientId, clientSecret);

        // null check on failed
        if (client == null) {
            setStatus(Status.CLIENT_ERROR_FORBIDDEN);
            return sendError(OAuthError.INVALID_CLIENT,
                    "Client id verification failed.", null);
        }

        String username = params.getFirstValue(USERNAME);
        AuthenticatedUser user = null;

        if (username == null || (user = client.findUser(username)) == null) {
            setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
            return sendError(OAuthError.INVALID_REQUEST,
                    "Mandatory parameter username missing.", null);
        }

        String password = params.getFirstValue(PASSWORD);

        if (password == null) {
            setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
            return sendError(OAuthError.INVALID_REQUEST,
                    "Mandatory parameter password missing.", null);
        }

        if (!password.equals(user.getPassword())) {
            setStatus(Status.CLIENT_ERROR_FORBIDDEN);
            return sendError(OAuthError.INVALID_GRANT, "Password not correct.",
                    null);
        }

        Token token = generator.generateToken(user, tokenTimeSec);
        JSONObject body = createJsonToken(token, null); // Scopes N/A

        // Sets the no-store Cache-Control header
        getResponse().setCacheDirectives(noStore);
        return new JsonStringRepresentation(body);
    }

    /**
     * Executes the "refresh token" flow.
     *
     * @param clientId
     *            The client identifier.
     * @param clientSecret
     *            The client's secret.
     * @param params
     *            The authentication parameters.
     * @return The result of the flow.
     */
    // TODO The secret should be a char[].
    private Representation doRefreshFlow(String clientId, String clientSecret,
            Form params) {
        String rToken = params.getFirstValue(REFRESH_TOKEN);

        if (rToken == null || rToken.length() == 0) {
            setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
            return sendError(OAuthError.INVALID_REQUEST,
                    "Mandatory parameter refresh_token is missing", null);
        }

        Client client = validate(clientId, clientSecret);

        // null check on failed
        if (client == null) {
            setStatus(Status.CLIENT_ERROR_FORBIDDEN);
            return sendError(OAuthError.INVALID_CLIENT,
                    "Client id verification failed.", null);
        }

        Token token = generator.findToken(rToken);

        if (token != null && (token instanceof ExpireToken)) {
            AuthenticatedUser user = token.getUser();

            // Make sure that the user owning the token is owned by this client
            if (client.containsUser(user.getId())) {
                // refresh the token
                generator.refreshToken((ExpireToken) token);

                JSONObject body = createJsonToken(token, null); // Scopes N/A

                // Sets the no-store Cache-Control header
                getResponse().setCacheDirectives(noStore);
                return new JsonStringRepresentation(body);
            } else { // error not owner
                setStatus(Status.CLIENT_ERROR_FORBIDDEN);
                return sendError(OAuthError.UNAUTHORIZED_CLIENT,
                        "User does not match.", null);

            }
        } else { // error no such token.
            setStatus(Status.CLIENT_ERROR_UNAUTHORIZED);
            return sendError(OAuthError.INVALID_GRANT, "Refresh token.", null);

        }

    }

    /**
     * Handles the {@link Post} request.
     *
     * @param input
     *            HTML form formated token request per oauth-v2 spec.
     * @return JSON response with token or error.
     */
    @Post("form:json")
    public Representation represent(Representation input) {
        getLogger().info("Method = " + getMethod().getName());
        getLogger().info("In request : " + getOriginalRef().toString());

        Form params = new Form(input);
        String typeString = params.getFirstValue(GRANT_TYPE);
        getLogger().info("Token Service - In service type = " + typeString);

        String clientId = params.getFirstValue(CLIENT_ID);
        String clientSecret = params.getFirstValue(CLIENT_SECRET);

        if (clientSecret == null || clientSecret.length() == 0) {
            // Check for a basic HTTP auth
            ChallengeResponse cr = getChallengeResponse();

            if (ChallengeScheme.HTTP_BASIC.equals(cr.getScheme())) {
                String basic = new String(Base64.decode(cr.getRawValue()));
                int colon = basic.indexOf(':');

                if (colon > -1) {
                    clientSecret = basic.substring(colon + 1);
                    getLogger().info(
                            "Found secret in BASIC Authentication : "
                                    + clientSecret);

                    // Also allow for client ID to be transfered in user part
                    if (colon > 0) { // There is a user part
                        clientId = basic.substring(0, colon);
                        getLogger().info(
                                "Found id in BASIC Authentication : "
                                        + clientId);
                    }
                }
            }

        }

        Representation toRet = null;
        if (clientId == null || clientId.length() == 0) {
            setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
            return sendError(OAuthError.INVALID_REQUEST,
                    "Mandatory parameter client_id is missing", null);

            // return new EmptyRepresentation();
        }

        if (clientSecret == null || clientSecret.length() == 0) {
            setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
            return sendError(OAuthError.INVALID_REQUEST,
                    "Mandatory parameter client_secret is missing", null);
            // return new EmptyRepresentation();
        }

        try {
            GrantType type = Enum.valueOf(GrantType.class, typeString);
            getLogger().info("Found flow - " + type);

            try {
                switch (type) {
                case authorization_code:
                    getLogger().info("doWebServerFlow() - flow");
                    toRet = doAuthCodeFlow(clientId, clientSecret, params);
                    break;
                case password:
                    toRet = doPasswordFlow(clientId, clientSecret, params);
                    break;
                case assertion:
                    sendError(OAuthError.UNSUPPORTED_GRANT_TYPE,
                            "Assertion flow not supported", null);
                    setStatus(Status.SERVER_ERROR_NOT_IMPLEMENTED);
                    break;
                case refresh_token:
                    toRet = doRefreshFlow(clientId, clientSecret, params);
                    break;
                case none:
                    toRet = doNoneFlow(clientId, clientSecret, params);
                    break;
                default:
                    toRet = sendError(OAuthError.UNSUPPORTED_GRANT_TYPE,
                            "Flow not supported", null);
                    setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
                }
            } catch (IllegalArgumentException e) { // can not exchange code.
                toRet = sendError(OAuthError.INVALID_GRANT, e.getMessage(),
                        null);
                setStatus(Status.CLIENT_ERROR_UNAUTHORIZED);
            }
        } catch (IllegalArgumentException iae) {
            toRet = sendError(OAuthError.UNSUPPORTED_GRANT_TYPE,
                    "Flow not supported", null);
            setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
        } catch (NullPointerException npe) {
            toRet = sendError(OAuthError.UNSUPPORTED_GRANT_TYPE,
                    "Flow not supported", null);
            setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
        }
        return toRet;
    }

    /**
     * Returns the representation of the given error.
     *
     * @param error
     *            The OAuth error.
     * @param description
     *            The error description.
     * @param errorUri
     *            the error URI.
     * @return The representation of the given error.
     */
    protected Representation sendError(OAuthError error, String description,
            String errorUri) {
        JSONObject result = new JSONObject();

        try {
            result.put(OAuthServerResource.ERROR, error.name());

            if (description != null && description.length() > 0) {
                result.put(OAuthServerResource.ERROR_DESC, description);
            }

            if (errorUri != null && errorUri.length() > 0) {
                result.put(OAuthServerResource.ERROR_URI, errorUri);
            }
            return new JsonStringRepresentation(result);
        } catch (JSONException e) {
            getLogger().log(Level.WARNING, "Error while sending OAuth error.",
                    e);
        }
        return null;
    }

    /**
     * Validates the id/password pair.
     *
     * @param clientId
     *            The client identifier.
     * @param clientSecret
     *            The client's secret.
     * @return The OAuth client that corresponds to the given id..
     */
    // TODO The secret should be a char[].
    private Client validate(String clientId, String clientSecret) {
        Client client = clients.findById(clientId);
        getLogger().info("Client = " + client);

        if (client == null) {
            sendError(OAuthError.INVALID_CLIENT,
                    "Could not find the correct client with id : " + clientId,
                    null);
            setStatus(Status.CLIENT_ERROR_NOT_FOUND);
            return null;
        }

        if (clientSecret == null
                || !clientSecret.equals(client.getClientSecret())) {
            sendError(OAuthError.INVALID_GRANT, "Client secret did not match",
                    null);
            setStatus(Status.CLIENT_ERROR_UNAUTHORIZED);
            getLogger().info(
                    "Could not find or match client secret " + clientSecret
                            + " : " + client.getClientSecret());
        }

        return client;
    }

}
TOP

Related Classes of org.restlet.ext.oauth.AccessTokenServerResource

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.