org.jboss.aerogear.android.authorization.oauth2.OAuth2AuthzService.java Source code

Java tutorial

Introduction

Here is the source code for org.jboss.aerogear.android.authorization.oauth2.OAuth2AuthzService.java

Source

/**
 * JBoss, Home of Professional Open Source
 * Copyright Red Hat, Inc., and individual contributors.
 *
 * 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 org.jboss.aerogear.android.authorization.oauth2;

import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
import android.util.Pair;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import org.jboss.aerogear.android.store.DataManager;
import org.jboss.aerogear.android.store.generator.IdGenerator;
import org.jboss.aerogear.android.pipe.http.HeaderAndBody;
import org.jboss.aerogear.android.pipe.http.HttpException;
import org.jboss.aerogear.android.pipe.http.HttpProvider;
import org.jboss.aerogear.android.store.sql.SQLStore;
import org.jboss.aerogear.android.store.sql.SQLStoreConfiguration;
import org.jboss.aerogear.android.pipe.http.HttpRestProvider;

import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.*;

import static org.jboss.aerogear.android.pipe.util.UrlUtils.appendToBaseURL;

/**
 * This service manages tokens for Authorization sessions. It can also perform
 * basic OAuth2 Access Token/ Authorization exchange and manages refresh tokens.
 * 
 */
public class OAuth2AuthzService extends Service {

    private final AuthzBinder binder = new AuthzBinder(this);

    private SQLStore<OAuth2AuthzSession> sessionStore;
    private static final String TAG = OAuth2AuthzService.class.getSimpleName();

    public OAuth2AuthzService() {
    }

    @Override
    public void onCreate() {
        super.onCreate();
    }

    /**
     * This will exchange an Authorization token for an Access Token
     * 
     * @param accountId the ID for the {@link OAuth2AuthzSession}
     * @param config the config
     * @return an accesstoken
     * @throws OAuth2AuthorizationException if something went wrong in the
     *             exchange
     */
    public String fetchAccessToken(String accountId, OAuth2Properties config) throws OAuth2AuthorizationException {
        OAuth2AuthzSession storedAccount = sessionStore.read(accountId);
        if (storedAccount == null) {
            return null;
        }

        if (!isNullOrEmpty(storedAccount.getAccessToken()) && storedAccount.tokenIsNotExpired()) {
            System.out.println("Existing access token In fetchAccessToken OAuth2AuthzService");

            return storedAccount.getAccessToken();
        } else if (!isNullOrEmpty(storedAccount.getRefreshToken())) {
            System.out.println("Refresh access token In fetchAccessToken OAuth2AuthzService");

            refreshAccount(storedAccount, config);
            sessionStore.save(storedAccount);
            return storedAccount.getAccessToken();
        } else if (!isNullOrEmpty(storedAccount.getAuthorizationCode())) {
            System.out.println("Code token In fetchAccessToken OAuth2AuthzService");

            exchangeAuthorizationCodeForAccessToken(storedAccount, config);
            sessionStore.save(storedAccount);
            return storedAccount.getAccessToken();
        } else {
            return null;
        }

    }

    /**
     * Put a session into the store.
     * 
     * @param account a new session
     */
    public void addAccount(OAuth2AuthzSession account) {
        String accountId = account.getAccountId();

        if (hasAccount(accountId)) {
            sessionStore.remove(accountId);
        }

        sessionStore.save(account);
    }

    /**
     * Will check if there is an account which has previously been granted an
     * authorization code and access code
     * 
     * @param accountId the accountId to check
     * @return true if there is a session for the account.
     */
    public boolean hasAccount(String accountId) {
        OAuth2AuthzSession storedAccount = sessionStore.read(accountId);
        if (storedAccount == null) {
            return false;
        }
        return !isNullOrEmpty(storedAccount.getAuthorizationCode())
                || !isNullOrEmpty(storedAccount.getAccessToken());
    }

    /**
     * Returns the OAuth2AuthzSession for accountId if any
     * 
     * @param accountId the accountId to look up
     * @return an OAuth2AuthzSession or null
     */
    public OAuth2AuthzSession getAccount(String accountId) {
        return sessionStore.read(accountId);
    }

    /**
     * Fetches all OAuth2AuthzSessions in the system.
     * 
     * @return all OAuth2AuthzSession's in the system
     */
    public List<String> getAccounts() {
        Collection<OAuth2AuthzSession> sessions = sessionStore.readAll();
        ArrayList<String> accountIds = new ArrayList<String>();

        for (OAuth2AuthzSession session : sessions) {
            accountIds.add(session.getAccountId());
        }

        return accountIds;

    }

    @Override
    public IBinder onBind(Intent intent) {
        openSessionStore();
        return binder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        return super.onUnbind(intent);
    }

    private void openSessionStore() {
        DataManager.config("sessionStore", SQLStoreConfiguration.class).forClass(OAuth2AuthzSession.class)
                .withContext(getApplicationContext()).withIdGenerator(new IdGenerator() {
                    @Override
                    public Serializable generate() {
                        return UUID.randomUUID().toString();
                    }
                }).store();

        sessionStore = (SQLStore<OAuth2AuthzSession>) DataManager.getStore("sessionStore");
        sessionStore.openSync();
    }

    private void exchangeAuthorizationCodeForAccessToken(OAuth2AuthzSession storedAccount, OAuth2Properties config)
            throws OAuth2AuthorizationException {
        final Map<String, String> data = new HashMap<String, String>();

        data.put("code", storedAccount.getAuthorizationCode());
        data.put("client_id", storedAccount.getClientId());
        if (config.getRedirectURL() != null) {
            data.put("redirect_uri", config.getRedirectURL());
        }
        data.put("grant_type", "authorization_code");
        if (config.getClientSecret() != null) {
            data.put("client_secret", config.getClientSecret());
        }

        if (!config.getAdditionalAccessParams().isEmpty()) {
            for (Pair<String, String> param : config.getAdditionalAccessParams()) {
                try {
                    data.put(URLEncoder.encode(param.first, "UTF-8"), param.second);
                } catch (UnsupportedEncodingException e) {
                    throw new RuntimeException(e);
                }
            }
        }

        runAccountAction(storedAccount, config, data,
                appendToBaseURL(config.getBaseURL(), config.getAccessTokenEndpoint()));

    }

    private void refreshAccount(OAuth2AuthzSession storedAccount, OAuth2Properties config)
            throws OAuth2AuthorizationException {
        final Map<String, String> data = new HashMap<String, String>();

        data.put("refresh_token", storedAccount.getRefreshToken());
        data.put("grant_type", "refresh_token");
        data.put("client_id", storedAccount.getClientId());
        if (config.getClientSecret() != null) {
            data.put("client_secret", config.getClientSecret());
        }

        if (!config.getAdditionalAccessParams().isEmpty()) {
            for (Pair<String, String> param : config.getAdditionalAccessParams()) {
                try {
                    data.put(URLEncoder.encode(param.first, "UTF-8"), param.second);
                } catch (UnsupportedEncodingException e) {
                    throw new RuntimeException(e);
                }
            }
        }

        runAccountAction(storedAccount, config, data,
                appendToBaseURL(config.getBaseURL(), config.getRefreshEndpoint()));
    }

    private void runAccountAction(OAuth2AuthzSession storedAccount, OAuth2Properties config,
            final Map<String, String> data, URL endpoint) throws OAuth2AuthorizationException {
        try {

            final HttpProvider provider = getHttpProvider(endpoint);
            final String formTemplate = "%s=%s";
            provider.setDefaultHeader("Content-Type", "application/x-www-form-urlencoded");

            final StringBuilder bodyBuilder = new StringBuilder();

            String amp = "";
            for (Map.Entry<String, String> entry : data.entrySet()) {
                bodyBuilder.append(amp);
                try {
                    bodyBuilder.append(String.format(formTemplate, entry.getKey(),
                            URLEncoder.encode(entry.getValue(), "UTF-8")));
                } catch (UnsupportedEncodingException e) {
                    throw new RuntimeException(e);
                }
                amp = "&";
            }

            HeaderAndBody headerAndBody;

            try {
                headerAndBody = provider.post(bodyBuilder.toString().getBytes("UTF-8"));

            } catch (HttpException exception) {
                if (exception.getStatusCode() == HttpURLConnection.HTTP_BAD_REQUEST) {
                    JsonElement response = new JsonParser().parse(new String(exception.getData()));
                    JsonObject jsonResponseObject = response.getAsJsonObject();
                    String error = "";
                    if (jsonResponseObject.has("error")) {
                        JsonElement errorObject = jsonResponseObject.get("error");
                        if (errorObject.isJsonPrimitive()) {
                            error = errorObject.getAsString();
                        } else {
                            error = errorObject.toString();
                        }

                    }

                    throw new OAuth2AuthorizationException(error);
                } else {
                    throw exception;
                }
            }
            String responseString = new String(headerAndBody.getBody());

            try {
                JsonElement response = new JsonParser().parse(responseString);
                JsonObject jsonResponseObject = response.getAsJsonObject();

                String accessToken = jsonResponseObject.get("access_token").getAsString();
                storedAccount.setAccessToken(accessToken);

                // Will need to check this one day
                // String tokenType = jsonResponseObject.get("token_type").getAsString();
                if (jsonResponseObject.has("expires_in")) {
                    Long expiresIn = jsonResponseObject.get("expires_in").getAsLong();
                    Long expires_on = new Date().getTime() + expiresIn * 1000;
                    storedAccount.setExpires_on(expires_on);
                }

                if (jsonResponseObject.has("refresh_token")) {
                    String refreshToken = jsonResponseObject.get("refresh_token").getAsString();
                    if (!isNullOrEmpty(refreshToken)) {
                        storedAccount.setRefreshToken(refreshToken);
                    }
                }
            } catch (JsonParseException parseEx) {
                try {
                    //Check if body is http form format
                    String[] values = responseString.split("&");
                    for (String pair : values) {
                        String property[] = pair.split("=");
                        String key = property[0];
                        String value = property[1];
                        if ("access_token".equals(key)) {
                            storedAccount.setAccessToken(value);
                        } else if ("expires_in".equals(key)) {
                            Long expiresIn = Long.parseLong(value);
                            Long expires_on = new Date().getTime() + expiresIn * 1000;
                            storedAccount.setExpires_on(expires_on);
                        } else if ("refresh_token".equals(key)) {
                            if (!isNullOrEmpty(value)) {
                                storedAccount.setRefreshToken(value);
                            }
                        }
                    }
                } catch (Exception e) {
                    Log.e(TAG, e.getMessage(), e);
                    throw new OAuth2AuthorizationException(responseString);
                }
            }

            storedAccount.setAuthorizationCode("");

        } catch (UnsupportedEncodingException ex) {
            // Should never happen...
            Log.d(OAuth2AuthzService.class.getName(), null, ex);
            throw new RuntimeException(ex);
        }
    }

    /**
     * This method allows an implementation to change how the HttpProvider is
     * fetched. Override is mostly used for testing.
     * 
     * @param url the url endpoint
     * @return a httpProvider
     */
    protected HttpProvider getHttpProvider(URL url) {
        return new HttpRestProvider(url);
    }

    /**
     * Removes the account with the provided ID
     * 
     * @param accountId 
     */
    public void removeAccount(String accountId) {
        sessionStore.remove(accountId);
    }

    private boolean isNullOrEmpty(String value) {
        return (value == null || value.isEmpty());
    }

    public static class AuthzBinder extends Binder {

        private final OAuth2AuthzService service;

        private AuthzBinder(OAuth2AuthzService service) {
            this.service = service;
        }

        public OAuth2AuthzService getService() {
            return service;
        }

    }

    /**
     * Defines callbacks for service binding, passed to bindService()
     */
    public static class AGAuthzServiceConnection implements ServiceConnection {

        private OAuth2AuthzService service;
        private boolean bound = false;

        @Override
        public void onServiceConnected(ComponentName className, IBinder iBinder) {
            // We've bound to LocalService, cast the IBinder and get LocalService instance
            AuthzBinder binder = (AuthzBinder) iBinder;
            this.service = binder.service;
            bound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            bound = false;
        }

        public OAuth2AuthzService getService() {
            return service;
        }

        public boolean isBound() {
            return bound;
        }

    }

    ;

}