Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7bd0f52c authored by Philip Whitehouse's avatar Philip Whitehouse
Browse files

Front-end changes for Google XOAUTH2

parent 7774ebc7
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -16,6 +16,10 @@
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
    <uses-permission android:name="android.permission.READ_CONTACTS"/>
    <uses-permission android:name="android.permission.READ_SYNC_SETTINGS"/>
    <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
    <uses-permission android:name="android.permission.USE_CREDENTIALS" />


    <!-- Needed to mark a contact as contacted -->
    <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
+2 −1
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.graphics.Color;
import android.net.Uri;
import android.util.Log;

import com.fsck.k9.account.AndroidAccountOAuth2TokenStore;
import com.fsck.k9.activity.setup.AccountSetupCheckSettings.CheckDirection;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.Address;
@@ -1287,7 +1288,7 @@ public class Account implements BaseAccount, StoreConfig {
    }

    public Store getRemoteStore() throws MessagingException {
        return RemoteStore.getInstance(K9.app, this);
        return RemoteStore.getInstance(K9.app, this, Globals.getOAuth2TokenProvider());
    }

    // It'd be great if this actually went into the store implementation
+15 −0
Original line number Diff line number Diff line
@@ -4,14 +4,21 @@ package com.fsck.k9;
import android.content.Context;
import android.support.annotation.VisibleForTesting;

import com.fsck.k9.mail.oauth.OAuth2TokenProvider;


public class Globals {
    private static Context context;
    private static OAuth2TokenProvider oAuth2TokenProvider;

    static void setContext(Context context) {
        Globals.context = context;
    }

    static void setOAuth2TokenProvider(OAuth2TokenProvider oAuth2TokenProvider) {
        Globals.oAuth2TokenProvider = oAuth2TokenProvider;
    }

    public static Context getContext() {
        if (context == null) {
            throw new IllegalStateException("No context provided");
@@ -19,4 +26,12 @@ public class Globals {

        return context;
    }

    public static OAuth2TokenProvider getOAuth2TokenProvider() {
        if (oAuth2TokenProvider == null) {
            throw new IllegalStateException("No OAuth 2.0 Token Provider provided");
        }

        return oAuth2TokenProvider;
    }
}
+2 −0
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import android.text.format.Time;
import android.util.Log;

import com.fsck.k9.Account.SortType;
import com.fsck.k9.account.AndroidAccountOAuth2TokenStore;
import com.fsck.k9.activity.MessageCompose;
import com.fsck.k9.activity.UpgradeDatabases;
import com.fsck.k9.controller.MessagingController;
@@ -510,6 +511,7 @@ public class K9 extends Application {
        super.onCreate();
        app = this;
        Globals.setContext(this);
        Globals.setOAuth2TokenProvider(new AndroidAccountOAuth2TokenStore(this));

        K9MailLib.setDebugStatus(new K9MailLib.DebugStatus() {
            @Override public boolean enabled() {
+137 −0
Original line number Diff line number Diff line
package com.fsck.k9.account;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;

import com.fsck.k9.R;
import com.fsck.k9.mail.AuthenticationFailedException;
import com.fsck.k9.mail.oauth.AuthorizationException;
import com.fsck.k9.mail.oauth.OAuth2TokenProvider;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * An interface between the OAuth2 requirements used for authentication and the AccountManager.
 */
public class AndroidAccountOAuth2TokenStore implements OAuth2TokenProvider {
    private static final String GMAIL_AUTH_TOKEN_TYPE = "oauth2:https://mail.google.com/";
    private static final String GOOGLE_ACCOUNT_TYPE = "com.google";

    private Map<String,String> authTokens = new HashMap<>();
    private AccountManager accountManager;

    public AndroidAccountOAuth2TokenStore(Context applicationContext) {
        this.accountManager = AccountManager.get(applicationContext);
    }

    @Override
    public void authorizeAPI(final String emailAddress, final Activity activity,
                             final OAuth2TokenProviderAuthCallback callback) {
        Account account = getAccountFromManager(emailAddress);
        if (account == null) {
            callback.failure(new AuthorizationException(activity.getString(R.string.xoauth2_account_doesnt_exist)));
            return;
        }
        if (account.name.equals(emailAddress)) {
            accountManager.getAuthToken(account, GMAIL_AUTH_TOKEN_TYPE, null, activity,
                new AccountManagerCallback<Bundle>() {
                    @Override
                    public void run(AccountManagerFuture<Bundle> future) {
                        try {
                            Bundle bundle = future.getResult();
                            Object keyAccountName = bundle.get(AccountManager.KEY_ACCOUNT_NAME);
                            if (keyAccountName == null) {
                                callback.failure(new AuthorizationException(activity.getString(
                                        R.string.xoauth2_no_account)));
                                return;
                            }
                            if (keyAccountName.equals(emailAddress)) {
                                callback.success();
                            } else {
                                callback.failure(new AuthorizationException(activity.getString(
                                        R.string.xoauth2_incorrect_auth_info_provided)));
                            }
                        } catch (OperationCanceledException e) {
                            callback.failure(new AuthorizationException(activity.getString(
                                    R.string.xoauth2_auth_cancelled_by_user), e));
                        } catch (IOException e) {
                            callback.failure(new AuthorizationException(activity.getString(
                                    R.string.xoauth2_unable_to_contact_auth_server), e));
                        } catch (AuthenticatorException e) {
                            callback.failure(new AuthorizationException(activity.getString(
                                    R.string.xoauth2_error_contacting_auth_server), e));
                        }
                    }
                }, null);
        }
    }

    @Override
    public String getToken(String username, long timeoutMillis) throws AuthenticationFailedException {
        if(authTokens.get(username) == null) {
            Account account = getAccountFromManager(username);
            if (account == null) {
                throw new AuthenticationFailedException("Account not available");
            }
            fetchNewAuthToken(username, account, timeoutMillis);
        }
        return authTokens.get(username);
    }

    private Account getAccountFromManager(String username) {
        android.accounts.Account[] accounts = accountManager.getAccountsByType(GOOGLE_ACCOUNT_TYPE);
        for (android.accounts.Account account : accounts) {
            if (account.name.equals(username)) {
                return account;
            }
        }
        return null;
    }

    private void fetchNewAuthToken(String username, Account account, long timeoutMillis)
            throws AuthenticationFailedException {
        try {
            AccountManagerFuture<Bundle> future = accountManager
                    .getAuthToken(account, GMAIL_AUTH_TOKEN_TYPE, false, null, null);
            Bundle bundle = future.getResult(timeoutMillis, TimeUnit.MILLISECONDS);
            if (bundle == null)
                throw new AuthenticationFailedException("No token provided");
            if (bundle.get(AccountManager.KEY_ACCOUNT_NAME) == null)
                throw new AuthenticationFailedException("No account information provided");
            if (bundle.get(AccountManager.KEY_ACCOUNT_NAME).equals(username))
                authTokens.put(username, bundle.get(AccountManager.KEY_AUTHTOKEN).toString());
            else
                throw new AuthenticationFailedException("Unexpected account information provided");
        } catch (Exception e) {
            throw new AuthenticationFailedException(e.getMessage());
        }
    }

    @Override
    public void invalidateToken(String username) {
        accountManager.invalidateAuthToken("com.google", authTokens.get(username));
        authTokens.remove(username);
    }

    @Override
    public List<String> getAccounts() {
        Account[] accounts = accountManager.getAccountsByType(GOOGLE_ACCOUNT_TYPE);
        ArrayList<String> accountNames = new ArrayList<>();
        for (Account account: accounts) {
            accountNames.add(account.name);
        }
        return accountNames;
    }
}
Loading