diff options
9 files changed, 271 insertions, 25 deletions
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java index 806a55b..aab6e80 100644 --- a/core/java/android/accounts/AccountManager.java +++ b/core/java/android/accounts/AccountManager.java @@ -155,6 +155,8 @@ public class AccountManager { /** @hide */ public static final int ERROR_CODE_USER_RESTRICTED = 100; + /** @hide */ + public static final int ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE = 101; /** * Bundle key used for the {@link String} account name in results @@ -678,8 +680,7 @@ public class AccountManager { * @param handler {@link Handler} identifying the callback thread, * null for the main thread * @return An {@link AccountManagerFuture} which resolves to a Boolean, - * true if the account has been successfully removed, - * false if the authenticator forbids deleting this account. + * true if the account has been successfully removed */ public AccountManagerFuture<Boolean> removeAccount(final Account account, AccountManagerCallback<Boolean> callback, Handler handler) { @@ -698,6 +699,28 @@ public class AccountManager { } /** + * @see #removeAccount(Account, AccountManagerCallback, Handler) + * @hide + */ + public AccountManagerFuture<Boolean> removeAccountAsUser(final Account account, + AccountManagerCallback<Boolean> callback, Handler handler, + final UserHandle userHandle) { + if (account == null) throw new IllegalArgumentException("account is null"); + if (userHandle == null) throw new IllegalArgumentException("userHandle is null"); + return new Future2Task<Boolean>(handler, callback) { + public void doWork() throws RemoteException { + mService.removeAccountAsUser(mResponse, account, userHandle.getIdentifier()); + } + public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException { + if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) { + throw new AuthenticatorException("no result in response"); + } + return bundle.getBoolean(KEY_BOOLEAN_RESULT); + } + }.start(); + } + + /** * Removes an auth token from the AccountManager's cache. Does nothing if * the auth token is not currently in the cache. Applications must call this * method when the auth token is found to have expired or otherwise become @@ -1183,7 +1206,8 @@ public class AccountManager { * <li> {@link AuthenticatorException} if no authenticator was registered for * this account type or the authenticator failed to respond * <li> {@link OperationCanceledException} if the operation was canceled for - * any reason, including the user canceling the creation process + * any reason, including the user canceling the creation process or adding accounts + * (of this type) has been disabled by policy * <li> {@link IOException} if the authenticator experienced an I/O problem * creating a new account, usually because of network trouble * </ul> @@ -1208,6 +1232,30 @@ public class AccountManager { } /** + * @see #addAccount(String, String, String[], Bundle, Activity, AccountManagerCallback, Handler) + * @hide + */ + public AccountManagerFuture<Bundle> addAccountAsUser(final String accountType, + final String authTokenType, final String[] requiredFeatures, + final Bundle addAccountOptions, final Activity activity, + AccountManagerCallback<Bundle> callback, Handler handler, final UserHandle userHandle) { + if (accountType == null) throw new IllegalArgumentException("accountType is null"); + if (userHandle == null) throw new IllegalArgumentException("userHandle is null"); + final Bundle optionsIn = new Bundle(); + if (addAccountOptions != null) { + optionsIn.putAll(addAccountOptions); + } + optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName()); + + return new AmsTask(activity, handler, callback) { + public void doWork() throws RemoteException { + mService.addAccountAsUser(mResponse, accountType, authTokenType, + requiredFeatures, activity != null, optionsIn, userHandle.getIdentifier()); + } + }.start(); + } + + /** * Adds a shared account from the primary user to a secondary user. Adding the shared account * doesn't take effect immediately. When the target user starts up, any pending shared accounts * are attempted to be copied to the target user from the primary via calls to the @@ -1608,8 +1656,10 @@ public class AccountManager { } public void onError(int code, String message) { - if (code == ERROR_CODE_CANCELED || code == ERROR_CODE_USER_RESTRICTED) { - // the authenticator indicated that this request was canceled, do so now + if (code == ERROR_CODE_CANCELED || code == ERROR_CODE_USER_RESTRICTED + || code == ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE) { + // the authenticator indicated that this request was canceled or we were + // forbidden to fulfill; cancel now cancel(true /* mayInterruptIfRunning */); return; } @@ -1668,7 +1718,10 @@ public class AccountManager { } public void onError(int code, String message) { - if (code == ERROR_CODE_CANCELED) { + if (code == ERROR_CODE_CANCELED || code == ERROR_CODE_USER_RESTRICTED + || code == ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE) { + // the authenticator indicated that this request was canceled or we were + // forbidden to fulfill; cancel now cancel(true /* mayInterruptIfRunning */); return; } diff --git a/core/java/android/accounts/AccountManagerFuture.java b/core/java/android/accounts/AccountManagerFuture.java index af00a08..77670d9 100644 --- a/core/java/android/accounts/AccountManagerFuture.java +++ b/core/java/android/accounts/AccountManagerFuture.java @@ -84,7 +84,8 @@ public interface AccountManagerFuture<V> { * will be thrown rather than the call returning normally. * @return the actual result * @throws android.accounts.OperationCanceledException if the request was canceled for any - * reason + * reason (including if it is forbidden + * by policy to modify an account (of that type)) * @throws android.accounts.AuthenticatorException if there was an error communicating with * the authenticator or if the authenticator returned an invalid response * @throws java.io.IOException if the authenticator returned an error response that indicates diff --git a/core/java/android/accounts/CantAddAccountActivity.java b/core/java/android/accounts/CantAddAccountActivity.java index e1717a6..4ac2beb 100644 --- a/core/java/android/accounts/CantAddAccountActivity.java +++ b/core/java/android/accounts/CantAddAccountActivity.java @@ -19,6 +19,7 @@ package android.accounts; import android.app.Activity; import android.os.Bundle; import android.view.View; +import android.widget.TextView; import com.android.internal.R; @@ -27,11 +28,26 @@ import com.android.internal.R; * Just shows an error message about the account restrictions for the limited user. */ public class CantAddAccountActivity extends Activity { + public static final String EXTRA_ERROR_CODE = "android.accounts.extra.ERROR_CODE"; + public static final int MISSING = -1; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.app_not_authorized); + + int errorCode = getIntent().getIntExtra(EXTRA_ERROR_CODE, MISSING); + if (errorCode != MISSING) { + TextView errorText = (TextView) findViewById(R.id.description); + switch (errorCode) { + case AccountManager.ERROR_CODE_USER_RESTRICTED: + errorText.setText(R.string.app_no_restricted_accounts); + break; + default: + // TODO: Get better message. See: http://b/14642886 + errorText.setText(R.string.error_message_title); + } + } } public void onCancelButtonClicked(View view) { diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl index 1373dc8..a04875d 100644 --- a/core/java/android/accounts/IAccountManager.aidl +++ b/core/java/android/accounts/IAccountManager.aidl @@ -38,6 +38,7 @@ interface IAccountManager { void getAccountsByFeatures(in IAccountManagerResponse response, String accountType, in String[] features); boolean addAccountExplicitly(in Account account, String password, in Bundle extras); void removeAccount(in IAccountManagerResponse response, in Account account); + void removeAccountAsUser(in IAccountManagerResponse response, in Account account, int userId); void invalidateAuthToken(String accountType, String authToken); String peekAuthToken(in Account account, String authTokenType); void setAuthToken(in Account account, String authTokenType, String authToken); @@ -52,6 +53,9 @@ interface IAccountManager { void addAccount(in IAccountManagerResponse response, String accountType, String authTokenType, in String[] requiredFeatures, boolean expectActivityLaunch, in Bundle options); + void addAccountAsUser(in IAccountManagerResponse response, String accountType, + String authTokenType, in String[] requiredFeatures, boolean expectActivityLaunch, + in Bundle options, int userId); void updateCredentials(in IAccountManagerResponse response, in Account account, String authTokenType, boolean expectActivityLaunch, in Bundle options); void editProperties(in IAccountManagerResponse response, String accountType, diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 4e7dac0..7a7fdec 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -2598,9 +2598,17 @@ public class DevicePolicyManager { * @see #setAccountManagementDisabled */ public String[] getAccountTypesWithManagementDisabled() { + return getAccountTypesWithManagementDisabledAsUser(UserHandle.getCallingUserId()); + } + + /** + * @see #getAccountTypesWithManagementDisabled() + * @hide + */ + public String[] getAccountTypesWithManagementDisabledAsUser(int userId) { if (mService != null) { try { - return mService.getAccountTypesWithManagementDisabled(); + return mService.getAccountTypesWithManagementDisabledAsUser(userId); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index d36497e..54d1eed 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -146,6 +146,7 @@ interface IDevicePolicyManager { void setAccountManagementDisabled(in ComponentName who, in String accountType, in boolean disabled); String[] getAccountTypesWithManagementDisabled(); + String[] getAccountTypesWithManagementDisabledAsUser(int userId); void setLockTaskPackages(in String[] packages); String[] getLockTaskPackages(); diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 08d354c..aaadc16 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -876,6 +876,7 @@ <java-symbol type="string" name="config_customResolverActivity" /> <java-symbol type="string" name="config_appsAuthorizedForSharedAccounts" /> <java-symbol type="string" name="error_message_title" /> + <java-symbol type="string" name="app_no_restricted_accounts" /> <java-symbol type="string" name="action_bar_home_description_format" /> <java-symbol type="string" name="action_bar_home_subtitle_description_format" /> <java-symbol type="string" name="wireless_display_route_description" /> diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index e152ebe..36d67ee 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -871,15 +871,82 @@ public class AccountManagerService checkManageAccountsPermission(); UserHandle user = Binder.getCallingUserHandle(); UserAccounts accounts = getUserAccountsForCaller(); - if (!canUserModifyAccounts(Binder.getCallingUid(), account.type)) { + int userId = Binder.getCallingUserHandle().getIdentifier(); + if (!canUserModifyAccounts(userId)) { try { + // TODO: This should be ERROR_CODE_USER_RESTRICTED instead. See http://b/16322768 response.onError(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION, "User cannot modify accounts"); } catch (RemoteException re) { } return; } + if (!canUserModifyAccountsForType(userId, account.type)) { + try { + response.onError(AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE, + "User cannot modify accounts of this type (policy)."); + } catch (RemoteException re) { + } + return; + } + + long identityToken = clearCallingIdentity(); + + cancelNotification(getSigninRequiredNotificationId(accounts, account), user); + synchronized (accounts.credentialsPermissionNotificationIds) { + for (Pair<Pair<Account, String>, Integer> pair: + accounts.credentialsPermissionNotificationIds.keySet()) { + if (account.equals(pair.first.first)) { + int id = accounts.credentialsPermissionNotificationIds.get(pair); + cancelNotification(id, user); + } + } + } + + try { + new RemoveAccountSession(accounts, response, account).bind(); + } finally { + restoreCallingIdentity(identityToken); + } + } + + @Override + public void removeAccountAsUser(IAccountManagerResponse response, Account account, + int userId) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "removeAccount: " + account + + ", response " + response + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid() + + ", for user id " + userId); + } + if (response == null) throw new IllegalArgumentException("response is null"); + if (account == null) throw new IllegalArgumentException("account is null"); + + // Only allow the system process to modify accounts of other users + enforceCrossUserPermission(userId, "User " + UserHandle.getCallingUserId() + + " trying to remove account for " + userId); + checkManageAccountsPermission(); + UserAccounts accounts = getUserAccounts(userId); + if (!canUserModifyAccounts(userId)) { + try { + response.onError(AccountManager.ERROR_CODE_USER_RESTRICTED, + "User cannot modify accounts"); + } catch (RemoteException re) { + } + return; + } + if (!canUserModifyAccountsForType(userId, account.type)) { + try { + response.onError(AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE, + "User cannot modify accounts of this type (policy)."); + } catch (RemoteException re) { + } + return; + } + + UserHandle user = new UserHandle(userId); long identityToken = clearCallingIdentity(); cancelNotification(getSigninRequiredNotificationId(accounts, account), user); @@ -1526,20 +1593,23 @@ public class AccountManagerService checkManageAccountsPermission(); // Is user disallowed from modifying accounts? - if (!canUserModifyAccounts(Binder.getCallingUid(), accountType)) { + int userId = Binder.getCallingUserHandle().getIdentifier(); + if (!canUserModifyAccounts(userId)) { try { response.onError(AccountManager.ERROR_CODE_USER_RESTRICTED, "User is not allowed to add an account!"); } catch (RemoteException re) { } - Intent cantAddAccount = new Intent(mContext, CantAddAccountActivity.class); - cantAddAccount.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - long identityToken = clearCallingIdentity(); + showCantAddAccount(AccountManager.ERROR_CODE_USER_RESTRICTED); + return; + } + if (!canUserModifyAccountsForType(userId, accountType)) { try { - mContext.startActivityAsUser(cantAddAccount, UserHandle.CURRENT); - } finally { - restoreCallingIdentity(identityToken); + response.onError(AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE, + "User cannot modify accounts of this type (policy)."); + } catch (RemoteException re) { } + showCantAddAccount(AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE); return; } @@ -1576,6 +1646,92 @@ public class AccountManagerService } @Override + public void addAccountAsUser(final IAccountManagerResponse response, final String accountType, + final String authTokenType, final String[] requiredFeatures, + final boolean expectActivityLaunch, final Bundle optionsIn, int userId) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "addAccount: accountType " + accountType + + ", response " + response + + ", authTokenType " + authTokenType + + ", requiredFeatures " + stringArrayToString(requiredFeatures) + + ", expectActivityLaunch " + expectActivityLaunch + + ", caller's uid " + Binder.getCallingUid() + + ", pid " + Binder.getCallingPid() + + ", for user id " + userId); + } + if (response == null) throw new IllegalArgumentException("response is null"); + if (accountType == null) throw new IllegalArgumentException("accountType is null"); + checkManageAccountsPermission(); + + // Only allow the system process to add accounts of other users + enforceCrossUserPermission(userId, "User " + UserHandle.getCallingUserId() + + " trying to add account for " + userId); + + // Is user disallowed from modifying accounts? + if (!canUserModifyAccounts(userId)) { + try { + response.onError(AccountManager.ERROR_CODE_USER_RESTRICTED, + "User is not allowed to add an account!"); + } catch (RemoteException re) { + } + showCantAddAccount(AccountManager.ERROR_CODE_USER_RESTRICTED); + return; + } + if (!canUserModifyAccountsForType(userId, accountType)) { + try { + response.onError(AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE, + "User cannot modify accounts of this type (policy)."); + } catch (RemoteException re) { + } + showCantAddAccount(AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE); + return; + } + + UserAccounts accounts = getUserAccounts(userId); + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + final Bundle options = (optionsIn == null) ? new Bundle() : optionsIn; + options.putInt(AccountManager.KEY_CALLER_UID, uid); + options.putInt(AccountManager.KEY_CALLER_PID, pid); + + long identityToken = clearCallingIdentity(); + try { + new Session(accounts, response, accountType, expectActivityLaunch, + true /* stripAuthTokenFromResult */) { + @Override + public void run() throws RemoteException { + mAuthenticator.addAccount(this, mAccountType, authTokenType, requiredFeatures, + options); + } + + @Override + protected String toDebugString(long now) { + return super.toDebugString(now) + ", addAccount" + + ", accountType " + accountType + + ", requiredFeatures " + + (requiredFeatures != null + ? TextUtils.join(",", requiredFeatures) + : null); + } + }.bind(); + } finally { + restoreCallingIdentity(identityToken); + } + } + + private void showCantAddAccount(int errorCode) { + Intent cantAddAccount = new Intent(mContext, CantAddAccountActivity.class); + cantAddAccount.putExtra(CantAddAccountActivity.EXTRA_ERROR_CODE, errorCode); + cantAddAccount.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + long identityToken = clearCallingIdentity(); + try { + mContext.startActivity(cantAddAccount); + } finally { + restoreCallingIdentity(identityToken); + } + } + + @Override public void confirmCredentialsAsUser(IAccountManagerResponse response, final Account account, final Bundle options, final boolean expectActivityLaunch, int userId) { @@ -2766,18 +2922,18 @@ public class AccountManagerService Manifest.permission.USE_CREDENTIALS); } - private boolean canUserModifyAccounts(int callingUid, String accountType) { - if (callingUid != Process.myUid()) { - if (getUserManager().getUserRestrictions( - new UserHandle(UserHandle.getUserId(callingUid))) - .getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS)) { - return false; - } + private boolean canUserModifyAccounts(int userId) { + if (getUserManager().getUserRestrictions(new UserHandle(userId)) + .getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS)) { + return false; } + return true; + } + private boolean canUserModifyAccountsForType(int userId, String accountType) { DevicePolicyManager dpm = (DevicePolicyManager) mContext .getSystemService(Context.DEVICE_POLICY_SERVICE); - String[] typesArray = dpm.getAccountTypesWithManagementDisabled(); + String[] typesArray = dpm.getAccountTypesWithManagementDisabledAsUser(userId); for (String forbiddenType : typesArray) { if (forbiddenType.equals(accountType)) { return false; diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 9e4d90d..e40c812 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -3888,11 +3888,17 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public String[] getAccountTypesWithManagementDisabled() { + return getAccountTypesWithManagementDisabledAsUser(UserHandle.getCallingUserId()); + } + + @Override + public String[] getAccountTypesWithManagementDisabledAsUser(int userId) { + enforceCrossUserPermission(userId); if (!mHasFeature) { return null; } synchronized (this) { - DevicePolicyData policy = getUserData(UserHandle.getCallingUserId()); + DevicePolicyData policy = getUserData(userId); final int N = policy.mAdminList.size(); HashSet<String> resultSet = new HashSet<String>(); for (int i = 0; i < N; i++) { |