Block access to accounts for limited users.

Make sure that apps that have access to restricted accounts can see them.
If they don't have access, they shouldn't be able to add a new account either.
Show an error message in the account picker if the user/app is not authorized.

Change-Id: I117c0b14d7d06c5ac4e66506df156b174567f5f3
This commit is contained in:
Amith Yamasani
2013-03-30 17:07:47 -07:00
parent 9e8ba8f273
commit 27db46850b
11 changed files with 197 additions and 41 deletions

View File

@@ -16984,6 +16984,7 @@ package android.os {
}
public class UserManager {
method public static synchronized android.os.UserManager get(android.content.Context);
method public long getSerialNumberForUser(android.os.UserHandle);
method public int getUserCount();
method public android.os.UserHandle getUserForSerialNumber(long);

View File

@@ -387,6 +387,23 @@ public class AccountManager {
}
}
/**
* @hide
* For use by internal activities. Returns the list of accounts that the calling package
* is authorized to use, particularly for shared accounts.
* @param packageName package name of the calling app.
* @param uid the uid of the calling app.
* @return the accounts that are available to this package and user.
*/
public Account[] getAccountsForPackage(String packageName, int uid) {
try {
return mService.getAccountsForPackage(packageName, uid);
} catch (RemoteException re) {
// possible security exception
throw new RuntimeException(re);
}
}
/**
* Lists all accounts of a particular type. The account type is a
* string token corresponding to the authenticator and useful domain
@@ -575,7 +592,7 @@ public class AccountManager {
public boolean addAccountExplicitly(Account account, String password, Bundle userdata) {
if (account == null) throw new IllegalArgumentException("account is null");
try {
return mService.addAccount(account, password, userdata);
return mService.addAccountExplicitly(account, password, userdata);
} catch (RemoteException e) {
// won't ever happen
throw new RuntimeException(e);
@@ -1123,7 +1140,7 @@ public class AccountManager {
return new AmsTask(activity, handler, callback) {
public void doWork() throws RemoteException {
mService.addAcount(mResponse, accountType, authTokenType,
mService.addAccount(mResponse, accountType, authTokenType,
requiredFeatures, activity != null, optionsIn);
}
}.start();

View File

@@ -18,9 +18,14 @@ package android.accounts;
import com.google.android.collect.Sets;
import android.app.Activity;
import android.app.ActivityManagerNative;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
@@ -29,6 +34,7 @@ import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.android.internal.R;
@@ -119,6 +125,9 @@ public class ChooseTypeAndAccountActivity extends Activity
private Parcelable[] mExistingAccounts = null;
private int mSelectedItemIndex;
private Button mOkButton;
private int mCallingUid;
private String mCallingPackage;
private boolean mDisallowAddAccounts;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -128,6 +137,24 @@ public class ChooseTypeAndAccountActivity extends Activity
+ savedInstanceState + ")");
}
String message = null;
try {
IBinder activityToken = getActivityToken();
mCallingUid = ActivityManagerNative.getDefault().getLaunchedFromUid(activityToken);
mCallingPackage = ActivityManagerNative.getDefault().getLaunchedFromPackage(
activityToken);
if (mCallingUid != 0 && mCallingPackage != null) {
Bundle restrictions = UserManager.get(this)
.getUserRestrictions(new UserHandle(UserHandle.getUserId(mCallingUid)));
mDisallowAddAccounts =
restrictions.getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, false);
}
} catch (RemoteException re) {
// Couldn't figure out caller details
Log.w(getClass().getSimpleName(), "Unable to get caller identity \n" + re);
}
// save some items we use frequently
final Intent intent = getIntent();
@@ -179,6 +206,11 @@ public class ChooseTypeAndAccountActivity extends Activity
// If there are no relevant accounts and only one relevant account type go directly to
// add account. Otherwise let the user choose.
if (mAccounts.isEmpty()) {
if (mDisallowAddAccounts) {
setContentView(R.layout.app_not_authorized);
setTitle(R.string.error_message_title);
return;
}
if (mSetOfRelevantAccountTypes.size() == 1) {
runAddAccountForAuthenticator(mSetOfRelevantAccountTypes.iterator().next());
} else {
@@ -296,7 +328,8 @@ public class ChooseTypeAndAccountActivity extends Activity
}
if (accountName == null || accountType == null) {
Account[] currentAccounts = AccountManager.get(this).getAccounts();
Account[] currentAccounts = AccountManager.get(this).getAccountsForPackage(
mCallingPackage, mCallingUid);
Set<Account> preExistingAccounts = new HashSet<Account>();
for (Parcelable accountParcel : mExistingAccounts) {
preExistingAccounts.add((Account) accountParcel);
@@ -347,7 +380,8 @@ public class ChooseTypeAndAccountActivity extends Activity
AccountManager.KEY_INTENT);
if (intent != null) {
mPendingRequest = REQUEST_ADD_ACCOUNT;
mExistingAccounts = AccountManager.get(this).getAccounts();
mExistingAccounts = AccountManager.get(this).getAccountsForPackage(mCallingPackage,
mCallingUid);
intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK);
startActivityForResult(intent, REQUEST_ADD_ACCOUNT);
return;
@@ -424,12 +458,14 @@ public class ChooseTypeAndAccountActivity extends Activity
private String[] getListOfDisplayableOptions(ArrayList<Account> accounts) {
// List of options includes all accounts found together with "Add new account" as the
// last item in the list.
String[] listItems = new String[accounts.size() + 1];
String[] listItems = new String[accounts.size() + (mDisallowAddAccounts ? 0 : 1)];
for (int i = 0; i < accounts.size(); i++) {
listItems[i] = accounts.get(i).name;
}
listItems[accounts.size()] = getResources().getString(
R.string.add_account_button_label);
if (!mDisallowAddAccounts) {
listItems[accounts.size()] = getResources().getString(
R.string.add_account_button_label);
}
return listItems;
}
@@ -439,7 +475,8 @@ public class ChooseTypeAndAccountActivity extends Activity
* allowable accounts, if provided.
*/
private ArrayList<Account> getAcceptableAccountChoices(AccountManager accountManager) {
final Account[] accounts = accountManager.getAccounts();
final Account[] accounts = accountManager.getAccountsForPackage(mCallingPackage,
mCallingUid);
ArrayList<Account> accountsToPopulate = new ArrayList<Account>(accounts.length);
for (Account account : accounts) {
if (mSetOfAllowableAccounts != null

View File

@@ -31,10 +31,11 @@ interface IAccountManager {
String getUserData(in Account account, String key);
AuthenticatorDescription[] getAuthenticatorTypes();
Account[] getAccounts(String accountType);
Account[] getAccountsForPackage(String packageName, int uid);
Account[] getAccountsAsUser(String accountType, int userId);
void hasFeatures(in IAccountManagerResponse response, in Account account, in String[] features);
void getAccountsByFeatures(in IAccountManagerResponse response, String accountType, in String[] features);
boolean addAccount(in Account account, String password, in Bundle extras);
boolean addAccountExplicitly(in Account account, String password, in Bundle extras);
void removeAccount(in IAccountManagerResponse response, in Account account);
void invalidateAuthToken(String accountType, String authToken);
String peekAuthToken(in Account account, String authTokenType);
@@ -47,7 +48,7 @@ interface IAccountManager {
void getAuthToken(in IAccountManagerResponse response, in Account account,
String authTokenType, boolean notifyOnAuthFailure, boolean expectActivityLaunch,
in Bundle options);
void addAcount(in IAccountManagerResponse response, String accountType,
void addAccount(in IAccountManagerResponse response, String accountType,
String authTokenType, in String[] requiredFeatures, boolean expectActivityLaunch,
in Bundle options);
void updateCredentials(in IAccountManagerResponse response, in Account account,

View File

@@ -121,6 +121,14 @@ public class UserManager {
*/
public static final String DISALLOW_USB_FILE_TRANSFER = "no_usb_file_transfer";
private static UserManager sInstance = null;
public synchronized static UserManager get(Context context) {
if (sInstance == null) {
sInstance = (UserManager) context.getSystemService(Context.USER_SERVICE);
}
return sInstance;
}
/** @hide */
public UserManager(Context context, IUserManager service) {

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
**
** Copyright 2013, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- Customizable description text -->
<TextView android:id="@+id/description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_gravity="start|center_vertical"
android:paddingTop="16dip"
android:paddingBottom="16dip"
android:paddingStart="16dip"
android:paddingEnd="16dip"
android:text="@string/app_no_restricted_accounts"
/>
<!-- Horizontal divider line -->
<View android:layout_height="1dip"
android:layout_width="match_parent"
android:background="?android:attr/dividerHorizontal" />
<!-- Alert dialog style buttons along the bottom. -->
<LinearLayout android:id="@+id/button_bar"
style="?android:attr/buttonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:measureWithLargestChild="true">
<Button android:id="@android:id/button1"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@android:string/yes"
android:onClick="onCancelButtonClicked" />
</LinearLayout>
</LinearLayout>

View File

@@ -4063,5 +4063,10 @@
<string name="user_switched">Current user <xliff:g id="name" example="Bob">%1$s</xliff:g>.</string>
<!-- Default name of the owner user [CHAR LIMIT=20] -->
<string name="owner_name" msgid="3879126011135546571">Owner</string>
<!-- Error message title [CHAR LIMIT=35] -->
<string name="error_message_title">Error</string>
<!-- Message informing user that app is not permitted to access accounts. [CHAR LIMIT=none] -->
<string name="app_no_restricted_accounts">This application does not support accounts for limited users</string>
<!-- Message informing user that the requested activity could not be found [CHAR LIMIT=none] -->
<string name="app_not_found">No application found to handle this action</string>
</resources>

View File

@@ -869,7 +869,7 @@
<java-symbol type="string" name="config_chooseAccountActivity" />
<java-symbol type="string" name="config_chooseTypeAndAccountActivity" />
<java-symbol type="string" name="config_appsAuthorizedForSharedAccounts" />
<java-symbol type="string" name="error_message_title" />
<java-symbol type="plurals" name="abbrev_in_num_days" />
<java-symbol type="plurals" name="abbrev_in_num_hours" />
@@ -1121,6 +1121,7 @@
<java-symbol type="layout" name="sms_short_code_confirmation_dialog" />
<java-symbol type="layout" name="keyguard_add_widget" />
<java-symbol type="layout" name="action_bar_up_container" />
<java-symbol type="layout" name="app_not_authorized" />
<java-symbol type="anim" name="slide_in_child_bottom" />
<java-symbol type="anim" name="slide_in_right" />

View File

@@ -322,7 +322,7 @@ public class SettingsProvider extends ContentProvider {
@Override
public boolean onCreate() {
mBackupManager = new BackupManager(getContext());
mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
mUserManager = UserManager.get(getContext());
setAppOps(AppOpsManager.OP_NONE, AppOpsManager.OP_WRITE_SETTINGS);
establishDbTracking(UserHandle.USER_OWNER);

View File

@@ -206,8 +206,7 @@ class QuickSettings {
mUserInfoTask = new AsyncTask<Void, Void, Pair<String, Drawable>>() {
@Override
protected Pair<String, Drawable> doInBackground(Void... params) {
final UserManager um =
(UserManager) mContext.getSystemService(Context.USER_SERVICE);
final UserManager um = UserManager.get(mContext);
// Fall back to the UserManager nickname if we can't read the name from the local
// profile below.
@@ -292,8 +291,7 @@ class QuickSettings {
@Override
public void onClick(View v) {
mBar.collapseAllPanels(true);
final UserManager um =
(UserManager) mContext.getSystemService(Context.USER_SERVICE);
final UserManager um = UserManager.get(mContext);
if (um.getUsers(true).size() > 1) {
try {
WindowManagerGlobal.getWindowManagerService().lockNow(null);

View File

@@ -58,6 +58,7 @@ import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -270,7 +271,7 @@ public class AccountManagerService
private UserManager getUserManager() {
if (mUserManager == null) {
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
mUserManager = UserManager.get(mContext);
}
return mUserManager;
}
@@ -542,9 +543,9 @@ public class AccountManagerService
}
@Override
public boolean addAccount(Account account, String password, Bundle extras) {
public boolean addAccountExplicitly(Account account, String password, Bundle extras) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "addAccount: " + account
Log.v(TAG, "addAccountExplicitly: " + account
+ ", caller's uid " + Binder.getCallingUid()
+ ", pid " + Binder.getCallingPid());
}
@@ -1167,7 +1168,7 @@ public class AccountManagerService
final int callingUid = getCallingUid();
clearCallingIdentity();
if (callingUid != android.os.Process.SYSTEM_UID) {
if (callingUid != Process.SYSTEM_UID) {
throw new SecurityException("can only call from system");
}
UserAccounts accounts = getUserAccounts(UserHandle.getUserId(callingUid));
@@ -1395,7 +1396,7 @@ public class AccountManagerService
return id;
}
public void addAcount(final IAccountManagerResponse response, final String accountType,
public void addAccount(final IAccountManagerResponse response, final String accountType,
final String authTokenType, final String[] requiredFeatures,
final boolean expectActivityLaunch, final Bundle optionsIn) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
@@ -1412,7 +1413,7 @@ public class AccountManagerService
checkManageAccountsPermission();
// Is user disallowed from modifying accounts?
if (getUserManager().hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS)) {
if (!canUserModifyAccounts(Binder.getCallingUid())) {
try {
response.onError(AccountManager.ERROR_CODE_USER_RESTRICTED,
"User is not allowed to add an account!");
@@ -1457,7 +1458,7 @@ public class AccountManagerService
int userId) {
// Only allow the system process to read accounts of other users
if (userId != UserHandle.getCallingUserId()
&& Binder.getCallingUid() != android.os.Process.myUid()) {
&& Binder.getCallingUid() != Process.myUid()) {
throw new SecurityException("User " + UserHandle.getCallingUserId()
+ " trying to confirm account credentials for " + userId);
}
@@ -1573,7 +1574,8 @@ public class AccountManagerService
public void run() throws RemoteException {
synchronized (mAccounts.cacheLock) {
mAccountsOfType = getAccountsFromCacheLocked(mAccounts, mAccountType, mCallingUid);
mAccountsOfType = getAccountsFromCacheLocked(mAccounts, mAccountType, mCallingUid,
null);
}
// check whether each account matches the requested features
mAccountsWithFeatures = new ArrayList<Account>(mAccountsOfType.length);
@@ -1662,7 +1664,7 @@ public class AccountManagerService
long identityToken = clearCallingIdentity();
try {
synchronized (accounts.cacheLock) {
return getAccountsFromCacheLocked(accounts, null, callingUid);
return getAccountsFromCacheLocked(accounts, null, callingUid, null);
}
} finally {
restoreCallingIdentity(identityToken);
@@ -1703,7 +1705,7 @@ public class AccountManagerService
if (userAccounts == null) continue;
synchronized (userAccounts.cacheLock) {
Account[] accounts = getAccountsFromCacheLocked(userAccounts, null,
Binder.getCallingUid());
Binder.getCallingUid(), null);
for (int a = 0; a < accounts.length; a++) {
runningAccounts.add(new AccountAndUser(accounts[a], userId));
}
@@ -1717,10 +1719,15 @@ public class AccountManagerService
@Override
public Account[] getAccountsAsUser(String type, int userId) {
final int callingUid = Binder.getCallingUid();
return getAccountsAsUser(type, userId, null, -1);
}
private Account[] getAccountsAsUser(String type, int userId, String callingPackage,
int packageUid) {
int callingUid = Binder.getCallingUid();
// Only allow the system process to read accounts of other users
if (userId != UserHandle.getCallingUserId()
&& callingUid != android.os.Process.myUid()) {
&& callingUid != Process.myUid()) {
throw new SecurityException("User " + UserHandle.getCallingUserId()
+ " trying to get account for " + userId);
}
@@ -1730,12 +1737,17 @@ public class AccountManagerService
+ ", caller's uid " + Binder.getCallingUid()
+ ", pid " + Binder.getCallingPid());
}
// If the original calling app was using the framework account chooser activity, we'll
// be passed in the original caller's uid here, which is what should be used for filtering.
if (packageUid != -1 && UserHandle.isSameApp(callingUid, Process.myUid())) {
callingUid = packageUid;
}
checkReadAccountsPermission();
UserAccounts accounts = getUserAccounts(userId);
long identityToken = clearCallingIdentity();
try {
synchronized (accounts.cacheLock) {
return getAccountsFromCacheLocked(accounts, type, callingUid);
return getAccountsFromCacheLocked(accounts, type, callingUid, callingPackage);
}
} finally {
restoreCallingIdentity(identityToken);
@@ -1806,6 +1818,16 @@ public class AccountManagerService
return getAccountsAsUser(type, UserHandle.getCallingUserId());
}
@Override
public Account[] getAccountsForPackage(String packageName, int uid) {
int callingUid = Binder.getCallingUid();
if (!UserHandle.isSameApp(callingUid, Process.myUid())) {
throw new SecurityException("getAccountsForPackage() called from unauthorized uid "
+ callingUid + " with uid=" + uid);
}
return getAccountsAsUser(null, UserHandle.getCallingUserId(), packageName, uid);
}
public void getAccountsByFeatures(IAccountManagerResponse response,
String type, String[] features) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
@@ -1825,7 +1847,7 @@ public class AccountManagerService
if (features == null || features.length == 0) {
Account[] accounts;
synchronized (userAccounts.cacheLock) {
accounts = getAccountsFromCacheLocked(userAccounts, type, callingUid);
accounts = getAccountsFromCacheLocked(userAccounts, type, callingUid, null);
}
Bundle result = new Bundle();
result.putParcelableArray(AccountManager.KEY_ACCOUNTS, accounts);
@@ -2348,7 +2370,7 @@ public class AccountManagerService
}
} else {
Account[] accounts = getAccountsFromCacheLocked(userAccounts, null /* type */,
android.os.Process.myUid());
Process.myUid(), null);
fout.println("Accounts: " + accounts.length);
for (Account account : accounts) {
fout.println(" " + account);
@@ -2501,7 +2523,7 @@ public class AccountManagerService
private boolean hasExplicitlyGrantedPermission(Account account, String authTokenType,
int callerUid) {
if (callerUid == android.os.Process.SYSTEM_UID) {
if (callerUid == Process.SYSTEM_UID) {
return true;
}
UserAccounts accounts = getUserAccountsForCaller();
@@ -2554,8 +2576,10 @@ public class AccountManagerService
}
private boolean canUserModifyAccounts(int callingUid) {
if (callingUid != android.os.Process.myUid()) {
if (getUserManager().hasUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS)) {
if (callingUid != Process.myUid()) {
if (getUserManager().getUserRestrictions(
new UserHandle(UserHandle.getUserId(callingUid)))
.getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS)) {
return false;
}
}
@@ -2566,7 +2590,7 @@ public class AccountManagerService
throws RemoteException {
final int callingUid = getCallingUid();
if (callingUid != android.os.Process.SYSTEM_UID) {
if (callingUid != Process.SYSTEM_UID) {
throw new SecurityException();
}
@@ -2686,9 +2710,9 @@ public class AccountManagerService
}
private Account[] filterSharedAccounts(UserAccounts userAccounts, Account[] unfiltered,
int callingUid) {
int callingUid, String callingPackage) {
if (getUserManager() == null || userAccounts == null || userAccounts.userId < 0
|| callingUid == android.os.Process.myUid()) {
|| callingUid == Process.myUid()) {
return unfiltered;
}
if (mUserManager.getUserInfo(userAccounts.userId).isRestricted()) {
@@ -2712,6 +2736,10 @@ public class AccountManagerService
PackageInfo pi = mPackageManager.getPackageInfo(packageName, 0);
if (pi != null && pi.restrictedAccountType != null) {
requiredAccountType = pi.restrictedAccountType;
// If it matches the package name of the original caller, use this choice.
if (callingPackage != null && packageName.equals(callingPackage)) {
break;
}
}
}
} catch (NameNotFoundException nnfe) {
@@ -2740,15 +2768,19 @@ public class AccountManagerService
}
}
/*
* packageName can be null. If not null, it should be used to filter out restricted accounts
* that the package is not allowed to access.
*/
protected Account[] getAccountsFromCacheLocked(UserAccounts userAccounts, String accountType,
int callingUid) {
int callingUid, String callingPackage) {
if (accountType != null) {
final Account[] accounts = userAccounts.accountCache.get(accountType);
if (accounts == null) {
return EMPTY_ACCOUNT_ARRAY;
} else {
return filterSharedAccounts(userAccounts, Arrays.copyOf(accounts, accounts.length),
callingUid);
callingUid, callingPackage);
}
} else {
int totalLength = 0;
@@ -2765,7 +2797,7 @@ public class AccountManagerService
accountsOfType.length);
totalLength += accountsOfType.length;
}
return filterSharedAccounts(userAccounts, accounts, callingUid);
return filterSharedAccounts(userAccounts, accounts, callingUid, callingPackage);
}
}