net.netheos.pcsapi.oauth.OAuth2SessionManager.java Source code

Java tutorial

Introduction

Here is the source code for net.netheos.pcsapi.oauth.OAuth2SessionManager.java

Source

/**
 * Copyright (c) 2014 Netheos (http://www.netheos.net)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.netheos.pcsapi.oauth;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import net.netheos.pcsapi.credentials.Credentials;
import net.netheos.pcsapi.credentials.OAuth2AppInfo;
import net.netheos.pcsapi.credentials.OAuth2Credentials;
import net.netheos.pcsapi.credentials.UserCredentials;
import net.netheos.pcsapi.credentials.UserCredentialsRepository;
import net.netheos.pcsapi.exceptions.CRetriableException;
import net.netheos.pcsapi.exceptions.CStorageException;
import net.netheos.pcsapi.request.CResponse;
import net.netheos.pcsapi.storage.StorageBuilder;
import net.netheos.pcsapi.utils.PcsUtils;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * OAuth2 session manager (used by most providers).
 */
public class OAuth2SessionManager extends SessionManager<OAuth2Credentials> {

    private static final Logger LOGGER = LoggerFactory.getLogger(OAuth2SessionManager.class);
    private static final String HEADER_AUTHORIZATION = "Authorization";
    private final Object refreshLock = new Object();

    private final String authorizeUrl;
    private final String accessTokenUrl;
    private final String refreshTokenUrl;
    private final boolean scopeInAuthorization;
    private final Character scopePermsSeparator;
    private final OAuth2AppInfo appInfo;
    private final UserCredentialsRepository userCredentialsRepo;
    private final HttpClient httpClient;

    public OAuth2SessionManager(String authorizeUrl, String accessTokenUrl, String refreshTokenUrl,
            boolean scopeInAuthorization, Character scopePermsSeparator, StorageBuilder builder)

    {
        super(builder.getUserCredentials());

        this.httpClient = builder.getHttpClient();
        this.authorizeUrl = authorizeUrl;
        this.accessTokenUrl = accessTokenUrl;
        this.refreshTokenUrl = refreshTokenUrl;
        this.scopeInAuthorization = scopeInAuthorization;
        this.scopePermsSeparator = scopePermsSeparator;

        this.appInfo = (OAuth2AppInfo) builder.getAppInfo();
        this.userCredentialsRepo = builder.getUserCredentialsRepo();

        // checks if we already have user_credentials:
        if (userCredentials != null) {
            OAuth2Credentials credentials = userCredentials.getCredentials();
            if (credentials.getAccessToken() == null) {
                throw new IllegalStateException("User credentials do not contain any access token");
            }
        }
    }

    @Override
    public CResponse execute(HttpUriRequest request) {
        if (LOGGER.isTraceEnabled()) {
            LOGGER.trace("{}: {}", request.getMethod(), PcsUtils.shortenUrl(request.getURI()));
        }

        if (userCredentials.getCredentials().hasExpired()) {
            refreshToken();
        }

        try {
            request.removeHeaders(HEADER_AUTHORIZATION); // in case we are retrying
            request.addHeader(HEADER_AUTHORIZATION, "Bearer " + userCredentials.getCredentials().getAccessToken());

            HttpResponse httpResponse = httpClient.execute(request);
            return new CResponse(request, httpResponse);

        } catch (IOException ex) {
            // a low level error should be retried :
            throw new CRetriableException(ex);
        }
    }

    /**
     * Refreshes access token after expiration (before sending request) thanks to the refresh token. The bew access
     * token is then stored in this manager.
     * <p/>
     * Method is synchronized so that no two threads will attempt to refresh at the same time. If a locked thread sees
     * that token has already been refreshed, no refresh is attempted either.
     * <p/>
     * Not all providers support tokens refresh (ex : CloudMe).
     */
    public void refreshToken() throws CStorageException {
        if (refreshTokenUrl == null) {
            throw new CStorageException("Provider does not support token refresh");
        }

        OAuth2Credentials currentCredentials = userCredentials.getCredentials();
        synchronized (refreshLock) {
            OAuth2Credentials afterLockCredentials = userCredentials.getCredentials();

            if (afterLockCredentials.getRefreshToken() == null) {
                throw new CStorageException("No refresh token available");
            }

            // TODO : better check again hasExpired() ?
            if (!afterLockCredentials.equals(currentCredentials)) {
                LOGGER.debug("Not refreshed token in this thread, already done");
                return;
            }

            LOGGER.debug("Refreshing token");

            HttpResponse response;
            try {
                HttpPost post = new HttpPost(accessTokenUrl);

                List<NameValuePair> parameters = new ArrayList<NameValuePair>();
                parameters.add(new BasicNameValuePair(OAuth2.CLIENT_ID, appInfo.getAppId()));
                parameters.add(new BasicNameValuePair(OAuth2.CLIENT_SECRET, appInfo.getAppSecret()));
                parameters
                        .add(new BasicNameValuePair(OAuth2.REFRESH_TOKEN, afterLockCredentials.getRefreshToken()));
                parameters.add(new BasicNameValuePair(OAuth2.SCOPE, getScopeForAuthorization()));
                parameters.add(new BasicNameValuePair(OAuth2.GRANT_TYPE, OAuth2.REFRESH_TOKEN));
                post.setEntity(new UrlEncodedFormEntity(parameters, PcsUtils.UTF8.name()));

                response = httpClient.execute(post);

            } catch (IOException ex) {
                throw new CStorageException("HTTP request while refreshing token has failed", ex);
            }

            // FIXME check status code here
            try {
                String data = EntityUtils.toString(response.getEntity(), PcsUtils.UTF8.name());

                // Update credentials
                JSONObject json = new JSONObject(data);
                afterLockCredentials.update(json);

            } catch (IOException ex) {
                throw new CStorageException("Can't get string from HttpResponse: " + response.toString(), ex);
            } catch (JSONException ex) {
                throw new CStorageException("Error parsing the JSON response", ex);
            }

            try {
                userCredentialsRepo.save(userCredentials);
            } catch (IOException ex) {
                throw new CStorageException("Can't save credentials", ex);
            }
        }
    }

    /**
     * Fetches user credentials
     *
     * @param code oauth2 OTP code
     * @return The user credentials (without userId)
     */
    UserCredentials fetchUserCredentials(String code) {
        HttpPost post = new HttpPost(accessTokenUrl);

        List<NameValuePair> parameters = new ArrayList<NameValuePair>();
        parameters.add(new BasicNameValuePair(OAuth2.CLIENT_ID, appInfo.getAppId()));
        parameters.add(new BasicNameValuePair(OAuth2.CLIENT_SECRET, appInfo.getAppSecret()));
        parameters.add(new BasicNameValuePair(OAuth2.CODE, code));
        parameters.add(new BasicNameValuePair(OAuth2.GRANT_TYPE, OAuth2.AUTHORIZATION_CODE));

        if (appInfo.getRedirectUrl() != null) {
            parameters.add(new BasicNameValuePair(OAuth2.REDIRECT_URI, appInfo.getRedirectUrl()));
        }

        try {
            post.setEntity(new UrlEncodedFormEntity(parameters, PcsUtils.UTF8.name()));
        } catch (UnsupportedEncodingException ex) {
            throw new CStorageException("Can't encode parameters", ex);
        }

        HttpResponse response;

        try {
            response = httpClient.execute(post);
        } catch (IOException e) {
            throw new CStorageException("HTTP request while fetching token has failed", e);
        }

        // FIXME check status code here
        final String json;

        try {
            json = EntityUtils.toString(response.getEntity(), PcsUtils.UTF8.name());
        } catch (IOException e) {
            throw new CStorageException("Can't retrieve json string in HTTP response entity", e);
        }
        LOGGER.debug("fetchUserCredentials - json: {}", json);

        Credentials credentials = Credentials.createFromJson(json);
        userCredentials = new UserCredentials(appInfo, null, // userId is unknown yet
                credentials);

        return userCredentials;
    }

    /**
     * Converts scope (list of permissions) to string for building OAuth authorization URL.
     * <p/>
     * Some providers separate permissions with spaces, other with comma... Other do not support scope at all.
     *
     * @return string for building OAuth authorization URL
     */
    String getScopeForAuthorization() {
        if (!scopeInAuthorization) {
            return null;
        }

        StringBuilder sb = new StringBuilder();
        for (String perm : appInfo.getScope()) {
            sb.append(perm).append(scopePermsSeparator);
        }

        return new String(sb.deleteCharAt(sb.length() - 1));
    }

    UserCredentialsRepository getUserCredentialsRepository() {
        return userCredentialsRepo;
    }

    OAuth2AppInfo getAppInfo() {
        return appInfo;
    }

    String getAuthorizeUrl() {
        return authorizeUrl;
    }

}