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

Commit ef419b2e authored by Costin Manolache's avatar Costin Manolache
Browse files

DO NOT MERGE

Backport (with modifications ) some changes from Honeycomb, that would allow authenticators to control caching and permissions.

This is backward compatible - both new and old authenticators will work with old and new framework,
but the functionality will only be present if both sides support it.

Change-Id: Ib2838cc2159f45264b38c844cd4c1d6f315d8064
parent 919853ce
Loading
Loading
Loading
Loading
+36 −2
Original line number Diff line number Diff line
@@ -18,17 +18,22 @@ package android.accounts;

import android.content.pm.PackageManager;
import android.content.pm.RegisteredServicesCache;
import android.content.pm.ResolveInfo;
import android.content.pm.XmlSerializerAndParser;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.util.AttributeSet;
import android.util.Log;
import android.text.TextUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlSerializer;
import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.util.List;

/**
 * A cache of services that export the {@link IAccountAuthenticator} interface. This cache
@@ -63,11 +68,40 @@ import java.io.IOException;
                    com.android.internal.R.styleable.AccountAuthenticator_smallIcon, 0);
            final int prefId = sa.getResourceId(
                    com.android.internal.R.styleable.AccountAuthenticator_accountPreferences, 0);
            
            boolean customTokens = false;
            try {
                // In HC this will be an attribute in authenticator.xml, this is a workaround
                // using meta-data to avoid changes to the API. 
                // If meta-data is absent the old behavior is preserved. 
                // Authenticator will know if AccountManager supports customTokens or not.
                PackageManager pm = mContext.getPackageManager();
                List<ResolveInfo> resolveInfos = pm.queryIntentServices(
                        new Intent(AccountManager.ACTION_AUTHENTICATOR_INTENT),
                        PackageManager.GET_META_DATA);
                for (ResolveInfo resolveInfo: resolveInfos) {
                    android.content.pm.ServiceInfo si = resolveInfo.serviceInfo;
                    if (!packageName.equals(si.packageName)) {
                        continue;
                    }
                    Object ctString = si.metaData.get(AccountManager.ACTION_AUTHENTICATOR_INTENT 
                            + ".customTokens");
                    if (ctString != null) {
                        customTokens = true;
                    }
                }
            } catch (Throwable t) {
                // Protected against invalid data in meta or unexpected 
                // conditions - the authenticator will not have the new 
                // features. 
                Log.e(TAG, "Error getting customTokens metadata " + t);
            }
            
            if (TextUtils.isEmpty(accountType)) {
                return null;
            }
            return new AuthenticatorDescription(accountType, packageName, labelId, iconId,
                    smallIconId, prefId);
                    smallIconId, prefId, customTokens);
        } finally {
            sa.recycle();
        }
+18 −0
Original line number Diff line number Diff line
@@ -188,6 +188,24 @@ public class AccountManager {
    public static final String KEY_ERROR_CODE = "errorCode";
    public static final String KEY_ERROR_MESSAGE = "errorMessage";
    public static final String KEY_USERDATA = "userdata";
    /**
     * Authenticators using 'customTokens' option will also get the UID of the
     * caller
     * @hide
     */
    public static final String KEY_CALLER_UID = "callerUid";

    /**
     * @hide 
     */
    public static final String KEY_CALLER_PID = "callerPid";

    /**
     * Boolean, if set and 'customTokens' the authenticator is responsible for
     * notifications.
     * @hide
     */
    public static final String KEY_NOTIFY_ON_FAILURE = "notifyOnAuthFailure";

    public static final String ACTION_AUTHENTICATOR_INTENT =
            "android.accounts.AccountAuthenticator";
+63 −12
Original line number Diff line number Diff line
@@ -91,6 +91,8 @@ public class AccountManagerService

    private final Context mContext;

    private final PackageManager mPackageManager;

    private HandlerThread mMessageThread;
    private final MessageHandler mMessageHandler;

@@ -214,6 +216,7 @@ public class AccountManagerService

    public AccountManagerService(Context context) {
        mContext = context;
        mPackageManager = context.getPackageManager();

        mOpenHelper = new DatabaseHelper(mContext);

@@ -520,6 +523,18 @@ public class AccountManagerService
        if (account == null) throw new IllegalArgumentException("account is null");
        checkManageAccountsPermission();
        long identityToken = clearCallingIdentity();

        cancelNotification(getSigninRequiredNotificationId(account));
        synchronized(mCredentialsPermissionNotificationIds) {
            for (Pair<Pair<Account, String>, Integer> pair:
                mCredentialsPermissionNotificationIds.keySet()) {
                if (account.equals(pair.first.first)) {
                    int id = mCredentialsPermissionNotificationIds.get(pair);
                    cancelNotification(id);
                }
            }
        }

        try {
            new RemoveAccountSession(response, account).bind();
        } finally {
@@ -842,19 +857,49 @@ public class AccountManagerService

    public void getAuthToken(IAccountManagerResponse response, final Account account,
            final String authTokenType, final boolean notifyOnAuthFailure,
            final boolean expectActivityLaunch, final Bundle loginOptions) {
            final boolean expectActivityLaunch, Bundle loginOptionsIn) {
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            Log.v(TAG, "getAuthToken: " + account
                    + ", response " + response
                    + ", authTokenType " + authTokenType
                    + ", notifyOnAuthFailure " + notifyOnAuthFailure
                    + ", expectActivityLaunch " + expectActivityLaunch
                    + ", caller's uid " + Binder.getCallingUid()
                    + ", pid " + Binder.getCallingPid());
        }
        if (response == null) throw new IllegalArgumentException("response is null");
        if (account == null) throw new IllegalArgumentException("account is null");
        if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
        checkBinderPermission(Manifest.permission.USE_CREDENTIALS);
        final int callerUid = Binder.getCallingUid();
        final boolean permissionGranted = permissionIsGranted(account, authTokenType, callerUid);
        final int callerPid = Binder.getCallingPid();

        AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo =
            mAuthenticatorCache.getServiceInfo(
                    AuthenticatorDescription.newKey(account.type));
        final boolean customTokens =
            authenticatorInfo != null && authenticatorInfo.type.customTokens;

        // skip the check if customTokens
        final boolean permissionGranted = customTokens ||
            permissionIsGranted(account, authTokenType, callerUid);

        final Bundle loginOptions = (loginOptionsIn == null) ? new Bundle() :
            loginOptionsIn;
        if (customTokens) {
            // let authenticator know the identity of the caller
            loginOptions.putInt(AccountManager.KEY_CALLER_UID, callerUid);
            loginOptions.putInt(AccountManager.KEY_CALLER_PID, callerPid);
            if (notifyOnAuthFailure) {
                loginOptions.putBoolean(AccountManager.KEY_NOTIFY_ON_FAILURE, true);
            }
        }

        long identityToken = clearCallingIdentity();
        try {
            // if the caller has permission, do the peek. otherwise go the more expensive
            // route of starting a Session
            if (permissionGranted) {
            if (!customTokens && permissionGranted) {
                String authToken = readAuthTokenFromDatabase(account, authTokenType);
                if (authToken != null) {
                    Bundle result = new Bundle();
@@ -908,12 +953,14 @@ public class AccountManagerService
                                        "the type and name should not be empty");
                                return;
                            }
                            if (!customTokens) {
                                saveAuthTokenToDatabase(new Account(name, type),
                                        authTokenType, authToken);
                            }
                        }

                        Intent intent = result.getParcelable(AccountManager.KEY_INTENT);
                        if (intent != null && notifyOnAuthFailure) {
                        if (intent != null && notifyOnAuthFailure && !customTokens) {
                            doNotification(
                                    account, result.getString(AccountManager.KEY_AUTH_FAILED_MESSAGE),
                                    intent);
@@ -972,6 +1019,10 @@ public class AccountManagerService
            AccountAuthenticatorResponse response, String authTokenType, String authTokenLabel) {

        Intent intent = new Intent(mContext, GrantCredentialsPermissionActivity.class);
        // See FLAG_ACTIVITY_NEW_TASK docs for limitations and benefits of the flag.
        // Since it was set in Eclair+ we can't change it without breaking apps using
        // the intent from a non-Activity context.
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.addCategory(
                String.valueOf(getCredentialPermissionNotificationId(account, authTokenType, uid)));

@@ -1849,12 +1900,12 @@ public class AccountManagerService
    }

    private boolean inSystemImage(int callerUid) {
        String[] packages = mContext.getPackageManager().getPackagesForUid(callerUid);
        String[] packages = mPackageManager.getPackagesForUid(callerUid);
        for (String name : packages) {
            try {
                PackageInfo packageInfo =
                        mContext.getPackageManager().getPackageInfo(name, 0 /* flags */);
                if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
                PackageInfo packageInfo = mPackageManager.getPackageInfo(name, 0 /* flags */);
                if (packageInfo != null
                        && (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
                    return true;
                }
            } catch (PackageManager.NameNotFoundException e) {
@@ -1872,7 +1923,7 @@ public class AccountManagerService
                && hasExplicitlyGrantedPermission(account, authTokenType);
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            Log.v(TAG, "checkGrantsOrCallingUidAgainstAuthenticator: caller uid "
                    + callerUid + ", account " + account
                    + callerUid + ", " + account
                    + ": is authenticator? " + fromAuthenticator
                    + ", has explicit permission? " + hasExplicitGrants);
        }
@@ -1884,7 +1935,7 @@ public class AccountManagerService
                mAuthenticatorCache.getAllServices()) {
            if (serviceInfo.type.type.equals(accountType)) {
                return (serviceInfo.uid == callingUid) ||
                        (mContext.getPackageManager().checkSignatures(serviceInfo.uid, callingUid)
                        (mPackageManager.checkSignatures(serviceInfo.uid, callingUid)
                                == PackageManager.SIGNATURE_MATCH);
            }
        }
+18 −2
Original line number Diff line number Diff line
@@ -44,9 +44,16 @@ public class AuthenticatorDescription implements Parcelable {
    /** The package name that can be used to lookup the resources from above. */
    final public String packageName;

    /** A constructor for a full AuthenticatorDescription */
    /** Authenticator handles its own token caching and permission screen 
      * @hide
      */
    final public boolean customTokens;

    /** A constructor for a full AuthenticatorDescription
     *  @hide 
     */
    public AuthenticatorDescription(String type, String packageName, int labelId, int iconId,
            int smallIconId, int prefId) {
            int smallIconId, int prefId, boolean customTokens) {
        if (type == null) throw new IllegalArgumentException("type cannot be null");
        if (packageName == null) throw new IllegalArgumentException("packageName cannot be null");
        this.type = type;
@@ -55,6 +62,12 @@ public class AuthenticatorDescription implements Parcelable {
        this.iconId = iconId;
        this.smallIconId = smallIconId;
        this.accountPreferencesId = prefId;
        this.customTokens = customTokens;
    }

    public AuthenticatorDescription(String type, String packageName, int labelId, int iconId,
            int smallIconId, int prefId) {
        this(type, packageName, labelId, iconId, smallIconId, prefId, false);
    }

    /**
@@ -74,6 +87,7 @@ public class AuthenticatorDescription implements Parcelable {
        this.iconId = 0;
        this.smallIconId = 0;
        this.accountPreferencesId = 0;
        this.customTokens = false;
    }

    private AuthenticatorDescription(Parcel source) {
@@ -83,6 +97,7 @@ public class AuthenticatorDescription implements Parcelable {
        this.iconId = source.readInt();
        this.smallIconId = source.readInt();
        this.accountPreferencesId = source.readInt();
        this.customTokens = source.readByte() == 1;
    }

    /** @inheritDoc */
@@ -115,6 +130,7 @@ public class AuthenticatorDescription implements Parcelable {
        dest.writeInt(iconId);
        dest.writeInt(smallIconId);
        dest.writeInt(accountPreferencesId);
        dest.writeByte((byte) (customTokens ? 1 : 0));
    }

    /** Used to create the object from a parcel. */