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

Commit cef1ce27 authored by Marvin W.'s avatar Marvin W. 🐿️
Browse files

Rebase Auth management

parent 1a212052
Loading
Loading
Loading
Loading
+63 −38
Original line number Diff line number Diff line
@@ -19,7 +19,6 @@ package org.microg.gms.auth;
import android.accounts.Account;
import android.accounts.AccountAuthenticatorActivity;
import android.accounts.AccountManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
@@ -36,19 +35,29 @@ import android.widget.ListView;
import android.widget.TextView;

import com.google.android.gms.R;
import com.squareup.wire.Wire;

import org.microg.gms.common.PackageUtils;
import org.microg.gms.people.PeopleManager;

import java.io.IOException;

import static android.accounts.AccountManager.KEY_ACCOUNT_NAME;
import static android.accounts.AccountManager.KEY_ACCOUNT_TYPE;
import static android.accounts.AccountManager.KEY_ANDROID_PACKAGE_NAME;
import static android.accounts.AccountManager.KEY_AUTHTOKEN;
import static android.accounts.AccountManager.KEY_CALLER_UID;

public class AskPermissionActivity extends AccountAuthenticatorActivity {
    public static final String EXTRA_FROM_ACCOUNT_MANAGER = "from_account_manager";
    public static final String EXTRA_CONSENT_DATA = "consent_data";

    private static final String TAG = "GmsAuthAskPermission";
    private Account account;
    private String packageName;
    private String service;
    private AuthManager authManager;
    private ConsentData consentData;
    private boolean fromAccountManager = false;

    @Override
@@ -63,13 +72,21 @@ public class AskPermissionActivity extends AccountAuthenticatorActivity {
        lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
        getWindow().setAttributes(lp);

        account = new Account(getIntent().getStringExtra(AccountManager.KEY_ACCOUNT_NAME),
                getIntent().getStringExtra(AccountManager.KEY_ACCOUNT_TYPE));
        packageName = getIntent().getStringExtra(AccountManager.KEY_ANDROID_PACKAGE_NAME);
        service = getIntent().getStringExtra(AccountManager.KEY_AUTHTOKEN);
        account = new Account(getIntent().getStringExtra(KEY_ACCOUNT_NAME),
                getIntent().getStringExtra(KEY_ACCOUNT_TYPE));
        packageName = getIntent().getStringExtra(KEY_ANDROID_PACKAGE_NAME);
        service = getIntent().getStringExtra(KEY_AUTHTOKEN);
        if (getIntent().hasExtra(EXTRA_CONSENT_DATA)) {
            try {
                consentData = new Wire().parseFrom(getIntent().getByteArrayExtra(EXTRA_CONSENT_DATA), ConsentData.class);
                Log.d(TAG, "Consent: " + consentData);
            } catch (Exception ignored) {
            }
        }
        if (getIntent().hasExtra(EXTRA_FROM_ACCOUNT_MANAGER)) fromAccountManager = true;
        int callerUid = getIntent().getIntExtra(AccountManager.KEY_CALLER_UID, 0);
        int callerUid = getIntent().getIntExtra(KEY_CALLER_UID, 0);
        PackageUtils.checkPackageUid(this, packageName, callerUid);
        authManager = new AuthManager(this, account.name, packageName, service);

        // receive package info
        PackageManager packageManager = getPackageManager();
@@ -126,7 +143,7 @@ public class AskPermissionActivity extends AccountAuthenticatorActivity {
    }

    public void onAllow() {
        AuthManager.storePermission(this, account, packageName, service);
        authManager.setPermitted(true);
        findViewById(android.R.id.button1).setEnabled(false);
        findViewById(android.R.id.button2).setEnabled(false);
        findViewById(R.id.progress_bar).setVisibility(View.VISIBLE);
@@ -134,27 +151,13 @@ public class AskPermissionActivity extends AccountAuthenticatorActivity {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Context context = AskPermissionActivity.this;
                String sig = PackageUtils.firstSignatureDigest(context, packageName);
                AuthRequest request = new AuthRequest().fromContext(context)
                        .email(account.name)
                        .token(AccountManager.get(context).getPassword(account))
                        .service(service)
                        .app(packageName, sig)
                        .hasPermission();
                if (fromAccountManager) {
                    request.callerIsGms().calledFromAccountManager();
                } else {
                    request.callerIsApp();
                }
                try {
                    AuthResponse response = request.getResponse();
                    AuthManager.storeResponse(context, account, packageName, sig, service, response);
                    AuthResponse response = authManager.requestAuth(fromAccountManager);
                    Bundle result = new Bundle();
                    result.putString(AccountManager.KEY_AUTHTOKEN, response.auth);
                    result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
                    result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
                    result.putString(AccountManager.KEY_ANDROID_PACKAGE_NAME, packageName);
                    result.putString(KEY_AUTHTOKEN, response.auth);
                    result.putString(KEY_ACCOUNT_NAME, account.name);
                    result.putString(KEY_ACCOUNT_TYPE, account.type);
                    result.putString(KEY_ANDROID_PACKAGE_NAME, packageName);
                    result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true);
                    setAccountAuthenticatorResult(result);
                } catch (IOException e) {
@@ -167,6 +170,7 @@ public class AskPermissionActivity extends AccountAuthenticatorActivity {
    }

    public void onDeny() {
        authManager.setPermitted(false);
        finish();
    }

@@ -179,6 +183,36 @@ public class AskPermissionActivity extends AccountAuthenticatorActivity {
        return service.startsWith("oauth2:") || service.startsWith("oauth:");
    }

    private String getScopeLabel(String scope) {
        if (consentData != null) {
            for (ConsentData.ScopeDetails scopeDetails : consentData.scopes) {
                if (scope.equals(scopeDetails.id)) {
                    return scopeDetails.title;
                }
            }
        }
        String labelResourceId = "permission_scope_";
        String escapedScope = scope.replace("/", "_").replace("-", "_");
        if (scope.startsWith("https://")) {
            labelResourceId += escapedScope.substring(8);
        } else {
            labelResourceId += escapedScope;
        }
        int labelResource = getResources().getIdentifier(labelResourceId, "string", getPackageName());
        if (labelResource != 0) {
            return getString(labelResource);
        }
        return "unknown";
    }

    private String getServiceLabel(String service) {
        int labelResource = getResources().getIdentifier("permission_service_" + service + "_label", "string", getPackageName());
        if (labelResource != 0) {
            return getString(labelResource);
        }
        return "unknown";
    }

    private class PermissionAdapter extends BaseAdapter {

        @Override
@@ -206,20 +240,11 @@ public class AskPermissionActivity extends AccountAuthenticatorActivity {
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            String item = getItem(position);
            String label = "unknown";
            String labelResourceId;
            String label;
            if (isOAuth()) {
                if (item.startsWith("https://")) {
                    labelResourceId = "permission_scope_" + item.substring(8).replace("/", "_").replace("-", "_");
                } else {
                    labelResourceId = "permission_scope_" + item.replace("/", "_").replace("-", "_");
                }
                label = getScopeLabel(item);
            } else {
                labelResourceId = "permission_service_" + item + "_label";
            }
            int labelResource = getResources().getIdentifier(labelResourceId, "string", getPackageName());
            if (labelResource != 0) {
                label = getString(labelResource);
                label = getServiceLabel(item);
            }
            View view = convertView;
            if (view == null) {
+151 −61
Original line number Diff line number Diff line
@@ -20,96 +20,186 @@ import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.preference.PreferenceManager;
import android.util.Log;

import org.microg.gms.common.Constants;
import org.microg.gms.common.PackageUtils;

import java.io.IOException;

import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;

public class AuthManager {

    private static final String TAG = "GmsAuthManager";
    public static final String PERMISSION_TREE_BASE = "com.google.android.googleapps.permission.GOOGLE_AUTH.";
    private static final String PREF_KEY_TRUST_GOOGLE = "auth_manager_trust_google";

    public static void storeResponse(Context context, Account account, String packageName,
                                     String sig, String service, AuthResponse response) {
        if (service.startsWith("weblogin:")) return;
        AccountManager accountManager = AccountManager.get(context);
        if (response.accountId != null)
            accountManager.setUserData(account, "GoogleUserId", response.accountId);
        if (response.Sid != null)
            accountManager.setAuthToken(account, buildTokenKey(packageName, sig, "SID"), response.Sid);
        if (response.LSid != null)
            accountManager.setAuthToken(account, buildTokenKey(packageName, sig, "LSID"), response.LSid);
        if (response.expiry > 0)
            accountManager.setUserData(account, buildExpireKey(packageName, sig, service), Long.toString(response.expiry));
        if (response.auth != null && response.expiry != 0 && response.storeConsentRemotely) {
            accountManager.setAuthToken(account, buildTokenKey(packageName, sig, service), response.auth);
            accountManager.setUserData(account, buildPermKey(packageName, sig, service), "1");
    private final Context context;
    private final String accountName;
    private final String packageName;
    private final String service;
    private AccountManager accountManager;
    private Account account;
    private String packageSignature;

    public AuthManager(Context context, String accountName, String packageName, String service) {
        this.context = context;
        this.accountName = accountName;
        this.packageName = packageName;
        this.service = service;
    }

    public AccountManager getAccountManager() {
        if (accountManager == null)
            accountManager = AccountManager.get(context);
        return accountManager;
    }

    public static String getToken(Context context, Account account, String packageName,
                                  String sig, String service) {
        if (service.startsWith("weblogin:")) return null;
        AccountManager accountManager = AccountManager.get(context);
        return accountManager.peekAuthToken(account, buildTokenKey(packageName, sig, service));
    public Account getAccount() {
        if (account == null)
            account = new Account(accountName, "com.google");
        return account;
    }

    public static boolean isPermitted(Context context, Account account, String packageName,
                                      String sig, String service) {
        if (service.startsWith("audience:server:client_id:")) {
            // https://developers.google.com/accounts/docs/CrossClientAuth
            Log.d(TAG, "Always permitting scope: " + service);
            return true;
        } else if (service.startsWith("weblogin:")) {
            if (Constants.GMS_PACKAGE_SIGNATURE_SHA1.equals(sig)) {
                Log.d(TAG, "Permitting weblogin, is Google singed app!");
                return true;
    public String getPackageSignature() {
        if (packageSignature == null)
            packageSignature = PackageUtils.firstSignatureDigest(context, packageName);
        return packageSignature;
    }
            return false;
        } else if (!service.startsWith("oauth:") && !service.startsWith("oauth2:")) {

    public String buildTokenKey(String service) {
        return packageName + ":" + getPackageSignature() + ":" + service;
    }

    public String buildTokenKey() {
        return buildTokenKey(service);
    }

    public String buildPermKey() {
        return "perm." + buildTokenKey();
    }

    public void setPermitted(boolean value) {
        setUserData(buildPermKey(), value ? "1" : "0");
    }

    public boolean isPermitted() {
        if (!service.startsWith("oauth")) {
            if (context.getPackageManager().checkPermission(PERMISSION_TREE_BASE + service, packageName) == PackageManager.PERMISSION_GRANTED) {
                Log.d(TAG, "Permitting, permission is present");
                return true;
            }
        }
        AccountManager accountManager = AccountManager.get(context);
        String perm = accountManager.getUserData(account, buildPermKey(packageName, sig, service));
        String perm = getUserData(buildPermKey());
        if (!"1".equals(perm)) {
            Log.d(TAG, "Not permitting, permission not stored for " + packageName + ": " + service);
            return false;
        }
        String exp = accountManager.getUserData(account, buildExpireKey(packageName, sig, service));
        if (exp != null) {
            long expLong = Long.parseLong(exp);
            if (expLong < System.currentTimeMillis() / 1000L) {
                Log.d(TAG, "Permission for " + packageName + " / " + service + " present, but expired");
                return false;
        return true;
    }

    public void setExpiry(long expiry) {
        setUserData(buildExpireKey(), Long.toString(expiry));
    }
        return true;

    public String getUserData(String key) {
        return getAccountManager().getUserData(getAccount(), key);
    }

    public static void storePermission(Context context, Account account, String packageName, String service) {
        storePermission(context, account, packageName, PackageUtils.firstSignatureDigest(context, packageName), service);
    public void setUserData(String key, String value) {
        getAccountManager().setUserData(getAccount(), key, value);
    }

    public static void storePermission(Context context, Account account, String packageName,
                                       String sig, String service) {
        AccountManager accountManager = AccountManager.get(context);
        accountManager.setUserData(account, buildPermKey(packageName, sig, service), "1");
    public String peekAuthToken() {
        return getAccountManager().peekAuthToken(getAccount(), buildTokenKey());
    }

    public static String buildTokenKey(String packageName, String sig, String service) {
    public String getAuthToken() {
        if (service.startsWith("weblogin:")) return null;
        return packageName + ":" + sig + ":" + service;
        if (getExpiry() != -1 && getExpiry() < System.currentTimeMillis() / 1000L) {
            Log.d(TAG, "token present, but expired");
            return null;
        }
        return peekAuthToken();
    }

    public String buildExpireKey() {
        return "EXP." + buildTokenKey();
    }

    public long getExpiry() {
        String exp = getUserData(buildExpireKey());
        if (exp == null) return -1;
        return Long.parseLong(exp);
    }

    public void setAuthToken(String auth) {
        setAuthToken(service, auth);
    }

    public void setAuthToken(String service, String auth) {
        getAccountManager().setAuthToken(getAccount(), buildTokenKey(service), auth);
    }

    public void storeResponse(AuthResponse response) {
        if (service.startsWith("weblogin:")) return;
        if (response.accountId != null)
            setUserData("GoogleUserId", response.accountId);
        if (response.Sid != null)
            setAuthToken("SID", response.Sid);
        if (response.LSid != null)
            setAuthToken("LSID", response.LSid);
        if (response.expiry > 0)
            setExpiry(response.expiry);
        if (response.auth != null && response.expiry != 0 && response.storeConsentRemotely)
            setAuthToken(response.auth);
    }

    public static boolean isTrustGooglePermitted(Context context) {
        return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(PREF_KEY_TRUST_GOOGLE, true);
    }

    private boolean isSystemApp() {
        try {
            int flags = context.getPackageManager().getApplicationInfo(packageName, 0).flags;
            return (flags & FLAG_SYSTEM) > 0 || (flags & FLAG_UPDATED_SYSTEM_APP) > 0;
        } catch (PackageManager.NameNotFoundException e) {
            return false;
        }
    }

    private static String buildPermKey(String packageName, String sig, String service) {
        return "perm." + packageName + ":" + sig + ":" + service;
    public AuthResponse requestAuth(boolean legacy) throws IOException {
        if (isPermitted() || isTrustGooglePermitted(context)) {
            String token = getAuthToken();
            if (token != null) {
                AuthResponse response = new AuthResponse();
                response.issueAdvice = "stored";
                response.auth = token;
                return response;
            }
        }
        AuthRequest request = new AuthRequest().fromContext(context)
                .app(packageName, getPackageSignature())
                .email(accountName)
                .token(getAccountManager().getPassword(account))
                .service(service);
        if (isSystemApp()) request.systemPartition();
        if (isPermitted()) request.hasPermission();
        if (legacy) {
            request.callerIsGms().calledFromAccountManager();
        } else {
            request.callerIsApp();
        }
        AuthResponse response = request.getResponse();
        if (!isPermitted() && !isTrustGooglePermitted(context)) {
            response.auth = null;
        } else {
            storeResponse(response);
        }
        return response;
    }

    private static String buildExpireKey(String packageName, String sig, String service) {
        return "EXP." + packageName + ":" + sig + ":" + service;
    public String getService() {
        return service;
    }
}
+35 −37
Original line number Diff line number Diff line
@@ -16,12 +16,11 @@

package org.microg.gms.auth;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Base64;
import android.util.Log;

import com.google.android.auth.IAuthManagerService;
@@ -30,13 +29,18 @@ import com.google.android.gms.auth.AccountChangeEventsResponse;

import org.microg.gms.common.PackageUtils;

import static android.accounts.AccountManager.KEY_ACCOUNT_NAME;
import static android.accounts.AccountManager.KEY_ACCOUNT_TYPE;
import static android.accounts.AccountManager.KEY_ANDROID_PACKAGE_NAME;
import static android.accounts.AccountManager.KEY_AUTHTOKEN;
import static org.microg.gms.auth.AskPermissionActivity.EXTRA_CONSENT_DATA;

public class AuthManagerServiceImpl extends IAuthManagerService.Stub {
    private static final String TAG = "GmsAuthManagerSvc";

    public static final String GOOGLE_ACCOUNT_TYPE = "com.google";

    public static final String KEY_AUTHORITY = "authority";
    public static final String KEY_ANDROID_PACKAGE_NAME = "androidPackageName";
    public static final String KEY_CALLBACK_INTENT = "callback_intent";
    public static final String KEY_CALLER_UID = "callerUid";
    public static final String KEY_CLIENT_PACKAGE_NAME = "clientPackageName";
@@ -50,7 +54,7 @@ public class AuthManagerServiceImpl extends IAuthManagerService.Stub {
    public static final String KEY_ERROR = "Error";
    public static final String KEY_USER_RECOVERY_INTENT = "userRecoveryIntent";

    private Context context;
    private final Context context;

    public AuthManagerServiceImpl(Context context) {
        this.context = context;
@@ -64,18 +68,26 @@ public class AuthManagerServiceImpl extends IAuthManagerService.Stub {
        boolean notify = extras.getBoolean(KEY_HANDLE_NOTIFICATION, false);

        Log.d(TAG, "getToken: account:" + accountName + " scope:" + scope + " extras:" + extras + ", notify: " + notify);
        Account account = new Account(accountName, GOOGLE_ACCOUNT_TYPE);
        String sig = PackageUtils.firstSignatureDigest(context, packageName);

        if (!AuthManager.isPermitted(context, account, packageName, sig, scope)) {
        AuthManager authManager = new AuthManager(context, accountName, packageName, scope);
        try {
            AuthResponse res = authManager.requestAuth(false);
            if (res.auth != null) {
                Log.d(TAG, "getToken: " + res.auth);
                Bundle result = new Bundle();
                result.putString(KEY_AUTH_TOKEN, res.auth);
                result.putString(KEY_ERROR, "OK");
                return result;
            } else {
                Bundle result = new Bundle();
                result.putString(KEY_ERROR, "Unknown");
                Intent i = new Intent(context, AskPermissionActivity.class);
                i.putExtras(extras);
            i.putExtra(AccountManager.KEY_ANDROID_PACKAGE_NAME, packageName);
            i.putExtra(AccountManager.KEY_ACCOUNT_TYPE, account.type);
            i.putExtra(AccountManager.KEY_ACCOUNT_NAME, account.name);
            i.putExtra(AccountManager.KEY_AUTHTOKEN, scope);
                i.putExtra(KEY_ANDROID_PACKAGE_NAME, packageName);
                i.putExtra(KEY_ACCOUNT_TYPE, GOOGLE_ACCOUNT_TYPE);
                i.putExtra(KEY_ACCOUNT_NAME, accountName);
                i.putExtra(KEY_AUTHTOKEN, scope);
                if (res.consentDataBase64 != null)
                    i.putExtra(EXTRA_CONSENT_DATA, Base64.decode(res.consentDataBase64, Base64.DEFAULT));
                if (notify) {
                    i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    context.startActivity(i);
@@ -84,22 +96,8 @@ public class AuthManagerServiceImpl extends IAuthManagerService.Stub {
                }
                return result;
            }
        try {
            AuthResponse response = new AuthRequest().fromContext(context)
                    .app(packageName, sig)
                    .callerIsApp()
                    .email(accountName)
                    .token(AccountManager.get(context).getPassword(account))
                    .service(scope)
                    .getResponse();
            AuthManager.storeResponse(context, account, packageName, sig, scope, response);
            Log.d("getToken", response.auth);
            Bundle result = new Bundle();
            result.putString(KEY_AUTH_TOKEN, response.auth);
            result.putString(KEY_ERROR, "Unknown");
            return result;
        } catch (Exception e) {
            Log.w("AuthManagerService", e);
            Log.w(TAG, e);
            throw new RemoteException(e.getMessage());
        }
    }
+6 −0
Original line number Diff line number Diff line
@@ -57,6 +57,12 @@ public class AuthResponse {
    public long expiry = -1;
    @ResponseField("storeConsentRemotely")
    public boolean storeConsentRemotely = true;
    @ResponseField("Permission")
    public String permission;
    @ResponseField("ScopeConsentDetails")
    public String scopeConsentDetails;
    @ResponseField("ConsentDataBase64")
    public String consentDataBase64;

    public static AuthResponse parse(String result) {
        AuthResponse response = new AuthResponse();
+7 −5
Original line number Diff line number Diff line
@@ -47,6 +47,9 @@ import org.microg.gms.people.PeopleManager;

import java.util.Locale;

import static org.microg.gms.common.Constants.GMS_PACKAGE_NAME;
import static org.microg.gms.common.Constants.GMS_PACKAGE_SIGNATURE_SHA1;

public class LoginActivity extends AssistantActivity {
    public static final String TMPL_NEW_ACCOUNT = "new_account";
    public static final String EXTRA_TMPL = "tmpl";
@@ -192,10 +195,11 @@ public class LoginActivity extends AssistantActivity {
    }

    private void retrieveGmsToken(final Account account) {
        final String service = "ac2dm";
        final AuthManager authManager = new AuthManager(this, account.name, GMS_PACKAGE_NAME, "ac2dm");
        authManager.setPermitted(true);
        new AuthRequest().fromContext(this)
                .appIsGms()
                .service(service)
                .service(authManager.getService())
                .email(account.name)
                .token(AccountManager.get(this).getPassword(account))
                .systemPartition()
@@ -205,9 +209,7 @@ public class LoginActivity extends AssistantActivity {
                .getResponseAsync(new HttpFormClient.Callback<AuthResponse>() {
                    @Override
                    public void onResponse(AuthResponse response) {
                        AuthManager.storeResponse(LoginActivity.this, account,
                                Constants.GMS_PACKAGE_NAME, Constants.GMS_PACKAGE_SIGNATURE_SHA1,
                                service, response);
                        authManager.storeResponse(response);
                        PeopleManager.loadUserInfo(LoginActivity.this, account);
                        finish();
                    }
Loading