/*
* Copyright 2012 GitHub Inc.
*
* 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 com.github.mobile.accounts;
import static android.accounts.AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE;
import static android.accounts.AccountManager.KEY_ACCOUNT_NAME;
import static android.accounts.AccountManager.KEY_ACCOUNT_TYPE;
import static android.accounts.AccountManager.KEY_AUTHTOKEN;
import static android.accounts.AccountManager.KEY_BOOLEAN_RESULT;
import static android.accounts.AccountManager.KEY_INTENT;
import static com.github.mobile.accounts.AccountConstants.ACCOUNT_TYPE;
import static com.github.mobile.accounts.AccountConstants.APP_NOTE;
import static com.github.mobile.accounts.AccountConstants.APP_NOTE_URL;
import static com.github.mobile.accounts.LoginActivity.PARAM_AUTHTOKEN_TYPE;
import static com.github.mobile.accounts.LoginActivity.PARAM_USERNAME;
import android.accounts.AbstractAccountAuthenticator;
import android.accounts.Account;
import android.accounts.AccountAuthenticatorResponse;
import android.accounts.AccountManager;
import android.accounts.NetworkErrorException;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import com.github.mobile.DefaultClient;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import org.eclipse.egit.github.core.Authorization;
import org.eclipse.egit.github.core.service.OAuthService;
class AccountAuthenticator extends AbstractAccountAuthenticator {
private static final String TAG = "GitHubAccountAuthenticator";
private Context context;
public AccountAuthenticator(final Context context) {
super(context);
this.context = context;
}
/**
* The user has requested to add a new account to the system. We return an
* intent that will launch our login screen if the user has not logged in
* yet, otherwise our activity will just pass the user's credentials on to
* the account manager.
*/
@Override
public Bundle addAccount(final AccountAuthenticatorResponse response,
final String accountType, final String authTokenType,
final String[] requiredFeatures, final Bundle options)
throws NetworkErrorException {
final Intent intent = new Intent(context, LoginActivity.class);
intent.putExtra(PARAM_AUTHTOKEN_TYPE, authTokenType);
intent.putExtra(KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
final Bundle bundle = new Bundle();
bundle.putParcelable(KEY_INTENT, intent);
return bundle;
}
@Override
public Bundle confirmCredentials(
final AccountAuthenticatorResponse response, final Account account,
final Bundle options) {
return null;
}
@Override
public Bundle editProperties(final AccountAuthenticatorResponse response,
final String accountType) {
return null;
}
private boolean isValidAuthorization(final Authorization auth,
final List<String> requiredScopes) {
if (auth == null)
return false;
if (!APP_NOTE.equals(auth.getNote()))
return false;
if (!APP_NOTE_URL.equals(auth.getNoteUrl()))
return false;
List<String> scopes = auth.getScopes();
return scopes != null && scopes.containsAll(requiredScopes);
}
private Intent createLoginIntent(final AccountAuthenticatorResponse response) {
final Intent intent = new Intent(context, LoginActivity.class);
intent.putExtra(PARAM_AUTHTOKEN_TYPE, ACCOUNT_TYPE);
intent.putExtra(KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
return intent;
}
/**
* Get existing authorization for this app
*
* @param service
* @param scopes
* @return token or null if none found
* @throws IOException
*/
private String getAuthorization(final OAuthService service,
final List<String> scopes) throws IOException {
for (Authorization auth : service.getAuthorizations())
if (isValidAuthorization(auth, scopes))
return auth.getToken();
return null;
}
/**
* Create authorization for this app
*
* @param service
* @param scopes
* @return created token
* @throws IOException
*/
private String createAuthorization(final OAuthService service,
final List<String> scopes) throws IOException {
Authorization auth = new Authorization();
auth.setNote(APP_NOTE);
auth.setNoteUrl(APP_NOTE_URL);
auth.setScopes(scopes);
auth = service.createAuthorization(auth);
return auth != null ? auth.getToken() : null;
}
@Override
public Bundle getAuthToken(final AccountAuthenticatorResponse response,
final Account account, final String authTokenType,
final Bundle options) throws NetworkErrorException {
Log.d(TAG, "Retrieving OAuth2 token");
final Bundle bundle = new Bundle();
if (!ACCOUNT_TYPE.equals(authTokenType))
return bundle;
AccountManager am = AccountManager.get(context);
String password = am.getPassword(account);
if (TextUtils.isEmpty(password)) {
bundle.putParcelable(KEY_INTENT, createLoginIntent(response));
return bundle;
}
DefaultClient client = new DefaultClient();
client.setCredentials(account.name, password);
OAuthService service = new OAuthService(client);
List<String> scopes = Arrays.asList("repo", "user", "gist");
String authToken;
try {
authToken = getAuthorization(service, scopes);
if (TextUtils.isEmpty(authToken))
authToken = createAuthorization(service, scopes);
} catch (IOException e) {
Log.e(TAG, "Authorization retrieval failed", e);
throw new NetworkErrorException(e);
}
if (TextUtils.isEmpty(authToken))
bundle.putParcelable(KEY_INTENT, createLoginIntent(response));
else {
bundle.putString(KEY_ACCOUNT_NAME, account.name);
bundle.putString(KEY_ACCOUNT_TYPE, ACCOUNT_TYPE);
bundle.putString(KEY_AUTHTOKEN, authToken);
am.clearPassword(account);
}
return bundle;
}
@Override
public String getAuthTokenLabel(final String authTokenType) {
if (ACCOUNT_TYPE.equals(authTokenType))
return authTokenType;
else
return null;
}
@Override
public Bundle hasFeatures(final AccountAuthenticatorResponse response,
final Account account, final String[] features)
throws NetworkErrorException {
final Bundle result = new Bundle();
result.putBoolean(KEY_BOOLEAN_RESULT, false);
return result;
}
@Override
public Bundle updateCredentials(
final AccountAuthenticatorResponse response, final Account account,
final String authTokenType, final Bundle options) {
final Intent intent = new Intent(context, LoginActivity.class);
intent.putExtra(PARAM_AUTHTOKEN_TYPE, authTokenType);
intent.putExtra(KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
if (!TextUtils.isEmpty(account.name))
intent.putExtra(PARAM_USERNAME, account.name);
final Bundle bundle = new Bundle();
bundle.putParcelable(KEY_INTENT, intent);
return bundle;
}
}