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

Commit d862c205 authored by David Luhmer's avatar David Luhmer
Browse files

add custom auth dialog (get rid of account manager)

parent dae4ad6f
Loading
Loading
Loading
Loading
+0 −5
Original line number Diff line number Diff line
@@ -2,13 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.nextcloud.android.sso">

    <uses-permission android:name="com.nextcloud.android.sso" />
    <uses-permission android:name="android.permission.INTERNET"/>

    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
    <uses-permission
        android:name="android.permission.USE_CREDENTIALS"
        android:maxSdkVersion="22" />

    <application android:label="@string/app_name" />
</manifest>
+56 −78
Original line number Diff line number Diff line
@@ -2,32 +2,29 @@ package com.nextcloud.android.sso;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.RequiresApi;
import android.util.Log;

import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException;
import com.nextcloud.android.sso.exceptions.NextcloudFilesAppNotInstalledException;
import com.nextcloud.android.sso.exceptions.NextcloudFilesAppNotSupportedException;
import com.nextcloud.android.sso.helper.AsyncTaskHelper;
import com.nextcloud.android.sso.exceptions.SSOException;
import com.nextcloud.android.sso.model.SingleSignOnAccount;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import static com.nextcloud.android.sso.Constants.NEXTCLOUD_FILES_ACCOUNT;
import static com.nextcloud.android.sso.Constants.NEXTCLOUD_SSO;
import static com.nextcloud.android.sso.Constants.NEXTCLOUD_SSO_EXCEPTION;
import static com.nextcloud.android.sso.Constants.SSO_SHARED_PREFERENCE;

/**
 *  Nextcloud SingleSignOn
@@ -51,12 +48,10 @@ import java.util.concurrent.Future;
public class AccountImporter {

    private static final String TAG = AccountImporter.class.getCanonicalName();
    private static final String PREF_FILE_NAME = "PrefNextcloudAccount";
    private static final String PREF_ACCOUNT_STRING = "PREF_ACCOUNT_STRING";

    private static final String AUTH_TOKEN = "NextcloudSSO";

    public static final int CHOOSE_ACCOUNT_SSO = 4242;
    public static final int REQUEST_AUTH_TOKEN__SSO = 4243;

    public static boolean AccountsToImportAvailable(Context context) {
        return FindAccounts(context).size() > 0;
@@ -83,17 +78,18 @@ public class AccountImporter {
            pm.getPackageInfo(uri, PackageManager.GET_ACTIVITIES);
            return true;
        } catch (PackageManager.NameNotFoundException e) {
            Log.v(TAG, e.getMessage());
        }
        return false;
    }

    // Find all currently installed nextcloud accounts on the phone
    private static List<Account> FindAccounts(Context context) {
    public static List<Account> FindAccounts(final Context context) {
        final AccountManager accMgr = AccountManager.get(context);
        final Account[] accounts = accMgr.getAccounts();

        List<Account> accountsAvailable = new ArrayList<>();
        for (Account account : accounts) {
        for (final Account account : accounts) {
            if (account.type.equals("nextcloud")) {
                accountsAvailable.add(account);
            }
@@ -102,8 +98,6 @@ public class AccountImporter {
    }




    public static Account GetAccountForName(Context context, String name) {
        for (Account account : FindAccounts(context)) {
            if (account.name.equals(name)) {
@@ -113,19 +107,27 @@ public class AccountImporter {
        return null;
    }

    @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
    public static SingleSignOnAccount BlockingGetAuthToken(final Context context, final Account account) throws Exception {
        SingleSignOnAccount result = AsyncTaskHelper.ExecuteBlockingRequest(new Callable<SingleSignOnAccount>() {
            @Override
            public SingleSignOnAccount call() throws Exception {
                return AccountImporter.GetAuthToken(context, account);
    public static void RequestAuthToken(android.support.v4.app.Fragment fragment, Intent intent) throws NextcloudFilesAppNotSupportedException {
        String accountName = intent.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
        Account account = AccountImporter.GetAccountForName(fragment.getContext(), accountName);

        Intent authIntent = new Intent();
        authIntent.setComponent(new ComponentName("com.nextcloud.client", "com.owncloud.android.ui.activity.SsoGrantPermissionActivity"));
        authIntent.putExtra(NEXTCLOUD_FILES_ACCOUNT, account);
        try {
            fragment.startActivityForResult(authIntent, REQUEST_AUTH_TOKEN__SSO);
        } catch (ActivityNotFoundException e) {
            throw new NextcloudFilesAppNotSupportedException();
        }
        });
        return result;
    }

    public static void HandleFailedAuthRequest(Intent data) throws Exception {
        String exception = data.getStringExtra(NEXTCLOUD_SSO_EXCEPTION);
        SSOException.ParseAndThrowNextcloudCustomException(new Exception(exception));
    }

    public static void ClearAllAuthTokens(Context context) {
        SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(context);
        SharedPreferences mPrefs = GetSharedPreferences(context);
        for(String key : mPrefs.getAll().keySet()) {
            if(key.startsWith(PREF_ACCOUNT_STRING)) {
                mPrefs.edit().remove(key).apply();
@@ -133,73 +135,49 @@ public class AccountImporter {
        }
    }

    // Get the AuthToken (Password) for a selected account
    public static SingleSignOnAccount GetAuthToken(Context context, Account account) throws AuthenticatorException, OperationCanceledException, IOException, NextcloudFilesAppNotSupportedException {
        SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(context);
        String prefKey = PREF_ACCOUNT_STRING + account.name;
    public static SingleSignOnAccount GetSingleSignOnAccount(Context context, final String accountName) throws NextcloudFilesAppAccountNotFoundException {
        SharedPreferences mPrefs = GetSharedPreferences(context);
        String prefKey = GetPrefKeyForAccount(accountName);
        if(mPrefs.contains(prefKey)) {
            try {
                return SingleSignOnAccount.fromString(mPrefs.getString(prefKey, null));
            } catch (ClassNotFoundException e) {
                Log.e(TAG, "This should never happen!");
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        final AccountManager accMgr = AccountManager.get(context);
        Bundle options = new Bundle();
        accMgr.invalidateAuthToken(account.type, AUTH_TOKEN);
        //accMgr.getAuthToken(account, AUTH_TOKEN, null, true, new AccountManagerCallback<Bundle>() {


        // Synchronously access auth token
        Bundle future;

        try {
            if (context instanceof Activity) {
                future = accMgr.getAuthToken(account, AUTH_TOKEN, options, (Activity) context, null, null).getResult(); // Show activity
            } else {
                future = accMgr.getAuthToken(account, AUTH_TOKEN, options, true, null, null).getResult(); // Show notification instead
            }
        } catch (AuthenticatorException ex) {
            throw new NextcloudFilesAppNotSupportedException();
        throw new NextcloudFilesAppAccountNotFoundException();
    }

        String auth_token = future.getString(AccountManager.KEY_AUTHTOKEN);
        String auth_account_type = future.getString(AccountManager.KEY_ACCOUNT_TYPE);
        accMgr.invalidateAuthToken(auth_account_type, auth_token);
    public static SingleSignOnAccount ExtractSingleSignOnAccountFromResponse(Intent intent, Context context) {
        Bundle future = intent.getBundleExtra(NEXTCLOUD_SSO);

        //String accountName = future.getString(AccountManager.KEY_ACCOUNT_NAME);
        //String auth_token = future.getString(AccountManager.KEY_AUTHTOKEN);
        //String auth_account_type = future.getString(AccountManager.KEY_ACCOUNT_TYPE);

        String accountName = future.getString(AccountManager.KEY_ACCOUNT_NAME);
        String username = future.getString(Constants.SSO_USERNAME);
        String token = future.getString(Constants.SSO_TOKEN);
        String server_url = future.getString(Constants.SSO_SERVER_URL);

        SingleSignOnAccount ssoAccount = new SingleSignOnAccount(account.name, username, token, server_url);
        SharedPreferences mPrefs = GetSharedPreferences(context);
        String prefKey = GetPrefKeyForAccount(accountName);
        SingleSignOnAccount ssoAccount = new SingleSignOnAccount(accountName, username, token, server_url);
        try {
            mPrefs.edit().putString(prefKey, SingleSignOnAccount.toString(ssoAccount)).apply();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return ssoAccount;
    }


    public static SingleSignOnAccount GetAuthTokenInSeparateThread(final Context context, final Account account) {
        SingleSignOnAccount ssoAccount = null;
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Callable<SingleSignOnAccount> callable = new Callable<SingleSignOnAccount>() {
            @Override
            public SingleSignOnAccount call() throws NextcloudFilesAppNotSupportedException, AuthenticatorException, OperationCanceledException, IOException {
                return AccountImporter.GetAuthToken(context, account);

    public static SharedPreferences GetSharedPreferences(Context context) {
        return context.getSharedPreferences(SSO_SHARED_PREFERENCE, Context.MODE_PRIVATE);
    }
        };
        Future<SingleSignOnAccount> future = executor.submit(callable);
        try {
            ssoAccount = future.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        executor.shutdown();

        return ssoAccount;
    protected static String GetPrefKeyForAccount(String accountName) {
        return PREF_ACCOUNT_STRING + accountName;
    }
}
+9 −3
Original line number Diff line number Diff line
@@ -3,9 +3,14 @@ package com.nextcloud.android.sso;
public class Constants {

    // Authenticator related constants
    public final static String SSO_USERNAME = "username";
    public final static String SSO_TOKEN = "token";
    public final static String SSO_SERVER_URL = "server_url";
    public static final String SSO_USERNAME = "username";
    public static final String SSO_TOKEN = "token";
    public static final String SSO_SERVER_URL = "server_url";
    public static final String SSO_SHARED_PREFERENCE = "single-sign-on";
    public static final String NEXTCLOUD_SSO_EXCEPTION = "NextcloudSsoException";
    public static final String NEXTCLOUD_SSO = "NextcloudSSO";
    public static final String NEXTCLOUD_FILES_ACCOUNT = "NextcloudFilesAccount";


    // Custom Exceptions
    public static final String EXCEPTION_INVALID_TOKEN = "CE_1";
@@ -13,4 +18,5 @@ public class Constants {
    public static final String EXCEPTION_UNSUPPORTED_METHOD = "CE_3";
    public static final String EXCEPTION_INVALID_REQUEST_URL = "CE_4";
    public static final String EXCEPTION_HTTP_REQUEST_FAILED = "CE_5";
    public static final String EXCEPTION_ACCOUNT_ACCESS_DECLINED = "CE_6";
}
+4 −21
Original line number Diff line number Diff line
@@ -11,16 +11,10 @@ import android.os.RemoteException;
import android.util.Log;

import com.google.gson.Gson;
import com.nextcloud.android.sso.Constants;
import com.nextcloud.android.sso.aidl.IInputStreamService;
import com.nextcloud.android.sso.aidl.IThreadListener;
import com.nextcloud.android.sso.aidl.NextcloudRequest;
import com.nextcloud.android.sso.aidl.ParcelFileDescriptorUtil;
import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException;
import com.nextcloud.android.sso.exceptions.NextcloudHttpRequestFailedException;
import com.nextcloud.android.sso.exceptions.NextcloudInvalidRequestUrlException;
import com.nextcloud.android.sso.exceptions.NextcloudUnsupportedMethodException;
import com.nextcloud.android.sso.exceptions.TokenMismatchException;
import com.nextcloud.android.sso.helper.ExponentialBackoff;
import com.nextcloud.android.sso.model.SingleSignOnAccount;

@@ -40,6 +34,8 @@ import java.lang.reflect.Type;
import io.reactivex.Observable;
import io.reactivex.annotations.NonNull;

import static com.nextcloud.android.sso.exceptions.SSOException.ParseAndThrowNextcloudCustomException;

/**
 *  Nextcloud SingleSignOn
 *
@@ -235,27 +231,14 @@ public class NextcloudAPI {
        // Handle Remote Exceptions
        if(exception != null) {
            if(exception.getMessage() != null) {
                switch (exception.getMessage()) {
                    case Constants.EXCEPTION_INVALID_TOKEN:
                        throw new TokenMismatchException();
                    case Constants.EXCEPTION_ACCOUNT_NOT_FOUND:
                        throw new NextcloudFilesAppAccountNotFoundException();
                    case Constants.EXCEPTION_UNSUPPORTED_METHOD:
                        throw new NextcloudUnsupportedMethodException();
                    case Constants.EXCEPTION_INVALID_REQUEST_URL:
                        throw new NextcloudInvalidRequestUrlException(exception.getCause().getMessage());
                    case Constants.EXCEPTION_HTTP_REQUEST_FAILED:
                        int statusCode = Integer.parseInt(exception.getCause().getMessage());
                        throw new NextcloudHttpRequestFailedException(statusCode);
                    default:
                        throw exception;
                }
                ParseAndThrowNextcloudCustomException(exception);
            }
            throw exception;
        }
        return os;
    }


    /**
     * DO NOT CALL THIS METHOD DIRECTLY - use "performNetworkRequest(...)" instead
     * @param request
+36 −0
Original line number Diff line number Diff line
package com.nextcloud.android.sso.exceptions;

import android.content.Context;

import com.nextcloud.android.sso.R;
import com.nextcloud.android.sso.model.ExceptionMessage;

/**
 *  Nextcloud SingleSignOn
 *
 *  @author David Luhmer
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

public class NextcloudFilesAppAccountPermissionNotGrantedException extends SSOException {

    @Override
    public void loadExceptionMessage(Context context) {
        this.em = new ExceptionMessage(
                context.getString(R.string.nextcloud_files_app_account_permission_not_granted_title),
                context.getString(R.string.nextcloud_files_app_account_permission_not_granted_message)
        );
    }
}
Loading