diff options
| author | 2017-01-25 18:38:28 +0000 | |
|---|---|---|
| committer | 2017-01-25 18:38:31 +0000 | |
| commit | e7beedff6e02cf11bce748d1aa73c848c53bbccd (patch) | |
| tree | bfe940d884f23d89993694d3ab61e03128a0bc6b | |
| parent | b3e3100489051ba0ccd286e896605ec5beaf135d (diff) | |
| parent | 0cbc19e4a66f7db51596b57ca91afc6f5b27f3b4 (diff) | |
Merge "Add unit tests for LockSettingsService"
13 files changed, 1420 insertions, 114 deletions
diff --git a/core/java/android/service/gatekeeper/GateKeeperResponse.java b/core/java/android/service/gatekeeper/GateKeeperResponse.java index a512957d6040..287dc76a9b01 100644 --- a/core/java/android/service/gatekeeper/GateKeeperResponse.java +++ b/core/java/android/service/gatekeeper/GateKeeperResponse.java @@ -19,6 +19,8 @@ package android.service.gatekeeper; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.annotations.VisibleForTesting; + /** * Response object for a GateKeeper verification request. * @hide @@ -35,12 +37,28 @@ public final class GateKeeperResponse implements Parcelable { private byte[] mPayload; private boolean mShouldReEnroll; + /** Default constructor for response with generic response code **/ private GateKeeperResponse(int responseCode) { mResponseCode = responseCode; } - private GateKeeperResponse(int responseCode, int timeout) { - mResponseCode = responseCode; + @VisibleForTesting + public static GateKeeperResponse createGenericResponse(int responseCode) { + return new GateKeeperResponse(responseCode); + } + + private static GateKeeperResponse createRetryResponse(int timeout) { + GateKeeperResponse response = new GateKeeperResponse(RESPONSE_RETRY); + response.mTimeout = timeout; + return response; + } + + @VisibleForTesting + public static GateKeeperResponse createOkResponse(byte[] payload, boolean shouldReEnroll) { + GateKeeperResponse response = new GateKeeperResponse(RESPONSE_OK); + response.mPayload = payload; + response.mShouldReEnroll = shouldReEnroll; + return response; } @Override @@ -53,17 +71,20 @@ public final class GateKeeperResponse implements Parcelable { @Override public GateKeeperResponse createFromParcel(Parcel source) { int responseCode = source.readInt(); - GateKeeperResponse response = new GateKeeperResponse(responseCode); + final GateKeeperResponse response; if (responseCode == RESPONSE_RETRY) { - response.setTimeout(source.readInt()); + response = createRetryResponse(source.readInt()); } else if (responseCode == RESPONSE_OK) { - response.setShouldReEnroll(source.readInt() == 1); + final boolean shouldReEnroll = source.readInt() == 1; + byte[] payload = null; int size = source.readInt(); if (size > 0) { - byte[] payload = new byte[size]; + payload = new byte[size]; source.readByteArray(payload); - response.setPayload(payload); } + response = createOkResponse(payload, shouldReEnroll); + } else { + response = createGenericResponse(responseCode); } return response; } @@ -104,17 +125,4 @@ public final class GateKeeperResponse implements Parcelable { public int getResponseCode() { return mResponseCode; } - - private void setTimeout(int timeout) { - mTimeout = timeout; - } - - private void setShouldReEnroll(boolean shouldReEnroll) { - mShouldReEnroll = shouldReEnroll; - } - - private void setPayload(byte[] payload) { - mPayload = payload; - } - } diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index ece5540443ce..58fb145db556 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -43,6 +43,7 @@ import android.text.TextUtils; import android.util.Log; import android.util.SparseIntArray; +import com.android.internal.annotations.VisibleForTesting; import com.google.android.collect.Lists; import libcore.util.HexEncoding; @@ -239,7 +240,8 @@ public class LockPatternUtils { mHandler = looper != null ? new Handler(looper) : null; } - private ILockSettings getLockSettings() { + @VisibleForTesting + public ILockSettings getLockSettings() { if (mLockSettingsService == null) { ILockSettings service = ILockSettings.Stub.asInterface( ServiceManager.getService("lock_settings")); diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java index 2d40e8eebdb5..8ef34dce438b 100644 --- a/services/core/java/com/android/server/LockSettingsService.java +++ b/services/core/java/com/android/server/LockSettingsService.java @@ -23,6 +23,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR import android.annotation.UserIdInt; import android.app.ActivityManager; +import android.app.IActivityManager; import android.app.KeyguardManager; import android.app.Notification; import android.app.NotificationManager; @@ -70,6 +71,7 @@ import android.text.TextUtils; import android.util.Log; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.widget.ICheckCredentialProgressCallback; import com.android.internal.widget.ILockSettings; @@ -123,20 +125,23 @@ public class LockSettingsService extends ILockSettings.Stub { private final Object mSeparateChallengeLock = new Object(); + private final Injector mInjector; private final Context mContext; private final Handler mHandler; - private final LockSettingsStorage mStorage; + @VisibleForTesting + protected final LockSettingsStorage mStorage; private final LockSettingsStrongAuth mStrongAuth; private final SynchronizedStrongAuthTracker mStrongAuthTracker; - private LockPatternUtils mLockPatternUtils; - private boolean mFirstCallToVold; - private IGateKeeperService mGateKeeperService; - private NotificationManager mNotificationManager; - private UserManager mUserManager; + private final LockPatternUtils mLockPatternUtils; + private final NotificationManager mNotificationManager; + private final UserManager mUserManager; + private final IActivityManager mActivityManager; - private final KeyStore mKeyStore = KeyStore.getInstance(); + private final KeyStore mKeyStore; + private boolean mFirstCallToVold; + protected IGateKeeperService mGateKeeperService; /** * The UIDs that are used for system credential storage in keystore. */ @@ -177,7 +182,9 @@ public class LockSettingsService extends ILockSettings.Stub { } } - private class SynchronizedStrongAuthTracker extends LockPatternUtils.StrongAuthTracker { + @VisibleForTesting + protected static class SynchronizedStrongAuthTracker + extends LockPatternUtils.StrongAuthTracker { public SynchronizedStrongAuthTracker(Context context) { super(context); } @@ -196,8 +203,8 @@ public class LockSettingsService extends ILockSettings.Stub { } } - void register() { - mStrongAuth.registerStrongAuthTracker(this.mStub); + void register(LockSettingsStrongAuth strongAuth) { + strongAuth.registerStrongAuthTracker(this.mStub); } } @@ -211,7 +218,7 @@ public class LockSettingsService extends ILockSettings.Stub { public void tieManagedProfileLockIfNecessary(int managedUserId, String managedUserPassword) { if (DEBUG) Slog.v(TAG, "Check child profile lock for user: " + managedUserId); // Only for managed profile - if (!UserManager.get(mContext).getUserInfo(managedUserId).isManagedProfile()) { + if (!mUserManager.getUserInfo(managedUserId).isManagedProfile()) { return; } // Do not tie managed profile when work challenge is enabled @@ -258,38 +265,103 @@ public class LockSettingsService extends ILockSettings.Stub { } } + static class Injector { + + protected Context mContext; + + public Injector(Context context) { + mContext = context; + } + + public Context getContext() { + return mContext; + } + + public Handler getHandler() { + return new Handler(); + } + + public LockSettingsStorage getStorage() { + final LockSettingsStorage storage = new LockSettingsStorage(mContext); + storage.setDatabaseOnCreateCallback(new LockSettingsStorage.Callback() { + @Override + public void initialize(SQLiteDatabase db) { + // Get the lockscreen default from a system property, if available + boolean lockScreenDisable = SystemProperties.getBoolean( + "ro.lockscreen.disable.default", false); + if (lockScreenDisable) { + storage.writeKeyValue(db, LockPatternUtils.DISABLE_LOCKSCREEN_KEY, "1", 0); + } + } + }); + return storage; + } + + public LockSettingsStrongAuth getStrongAuth() { + return new LockSettingsStrongAuth(mContext); + } + + public SynchronizedStrongAuthTracker getStrongAuthTracker() { + return new SynchronizedStrongAuthTracker(mContext); + } + + public IActivityManager getActivityManager() { + return ActivityManager.getService(); + } + + public LockPatternUtils getLockPatternUtils() { + return new LockPatternUtils(mContext); + } + + public NotificationManager getNotificationManager() { + return (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); + } + + public UserManager getUserManager() { + return (UserManager) mContext.getSystemService(Context.USER_SERVICE); + } + + public KeyStore getKeyStore() { + return KeyStore.getInstance(); + } + + public IStorageManager getStorageManager() { + final IBinder service = ServiceManager.getService("mount"); + if (service != null) { + return IStorageManager.Stub.asInterface(service); + } + return null; + } + } + public LockSettingsService(Context context) { - mContext = context; - mHandler = new Handler(); - mStrongAuth = new LockSettingsStrongAuth(context); - // Open the database + this(new Injector(context)); + } - mLockPatternUtils = new LockPatternUtils(context); + @VisibleForTesting + protected LockSettingsService(Injector injector) { + mInjector = injector; + mContext = injector.getContext(); + mKeyStore = injector.getKeyStore(); + mHandler = injector.getHandler(); + mStrongAuth = injector.getStrongAuth(); + mActivityManager = injector.getActivityManager(); + + mLockPatternUtils = injector.getLockPatternUtils(); mFirstCallToVold = true; IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_USER_ADDED); filter.addAction(Intent.ACTION_USER_STARTING); filter.addAction(Intent.ACTION_USER_REMOVED); - mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null); - - mStorage = new LockSettingsStorage(context, new LockSettingsStorage.Callback() { - @Override - public void initialize(SQLiteDatabase db) { - // Get the lockscreen default from a system property, if available - boolean lockScreenDisable = SystemProperties.getBoolean( - "ro.lockscreen.disable.default", false); - if (lockScreenDisable) { - mStorage.writeKeyValue(db, LockPatternUtils.DISABLE_LOCKSCREEN_KEY, "1", 0); - } - } - }); - mNotificationManager = (NotificationManager) - mContext.getSystemService(Context.NOTIFICATION_SERVICE); - mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); - mStrongAuthTracker = new SynchronizedStrongAuthTracker(mContext); - mStrongAuthTracker.register(); + injector.getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, + null, null); + mStorage = injector.getStorage(); + mNotificationManager = injector.getNotificationManager(); + mUserManager = injector.getUserManager(); + mStrongAuthTracker = injector.getStrongAuthTracker(); + mStrongAuthTracker.register(mStrongAuth); } /** @@ -748,7 +820,8 @@ public class LockSettingsService extends ILockSettings.Stub { ks.unlock(userHandle, password); } - private String getDecryptedPasswordForTiedProfile(int userId) + @VisibleForTesting + protected String getDecryptedPasswordForTiedProfile(int userId) throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, @@ -814,7 +887,7 @@ public class LockSettingsService extends ILockSettings.Stub { }; try { - ActivityManager.getService().unlockUser(userId, token, secret, listener); + mActivityManager.unlockUser(userId, token, secret, listener); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } @@ -961,7 +1034,8 @@ public class LockSettingsService extends ILockSettings.Stub { } } - private void tieProfileLockToParent(int userId, String password) { + @VisibleForTesting + protected void tieProfileLockToParent(int userId, String password) { if (DEBUG) Slog.v(TAG, "tieProfileLockToParent for user: " + userId); byte[] randomLockSeed = password.getBytes(StandardCharsets.UTF_8); byte[] encryptionResult; @@ -1085,8 +1159,8 @@ public class LockSettingsService extends ILockSettings.Stub { private void addUserKeyAuth(int userId, byte[] token, byte[] secret) throws RemoteException { - final UserInfo userInfo = UserManager.get(mContext).getUserInfo(userId); - final IStorageManager storageManager = getStorageManager(); + final UserInfo userInfo = mUserManager.getUserInfo(userId); + final IStorageManager storageManager = mInjector.getStorageManager(); final long callingId = Binder.clearCallingIdentity(); try { storageManager.addUserKeyAuth(userId, userInfo.serialNumber, token, secret); @@ -1097,7 +1171,7 @@ public class LockSettingsService extends ILockSettings.Stub { private void fixateNewestUserKeyAuth(int userId) throws RemoteException { - final IStorageManager storageManager = getStorageManager(); + final IStorageManager storageManager = mInjector.getStorageManager(); final long callingId = Binder.clearCallingIdentity(); try { storageManager.fixateNewestUserKeyAuth(userId); @@ -1396,7 +1470,7 @@ public class LockSettingsService extends ILockSettings.Stub { // we should, within the first minute of decrypting the phone if this // service can't connect to vold, it restarts, and then the new instance // does successfully connect. - final IStorageManager service = getStorageManager(); + final IStorageManager service = mInjector.getStorageManager(); String password; long identity = Binder.clearCallingIdentity(); try { @@ -1561,14 +1635,6 @@ public class LockSettingsService extends ILockSettings.Stub { Secure.LOCK_SCREEN_OWNER_INFO }; - private IStorageManager getStorageManager() { - final IBinder service = ServiceManager.getService("mount"); - if (service != null) { - return IStorageManager.Stub.asInterface(service); - } - return null; - } - private class GateKeeperDiedRecipient implements IBinder.DeathRecipient { @Override public void binderDied() { diff --git a/services/core/java/com/android/server/LockSettingsShellCommand.java b/services/core/java/com/android/server/LockSettingsShellCommand.java index f72663aed140..e1312519480f 100644 --- a/services/core/java/com/android/server/LockSettingsShellCommand.java +++ b/services/core/java/com/android/server/LockSettingsShellCommand.java @@ -77,7 +77,8 @@ class LockSettingsShellCommand extends ShellCommand { } return 0; } catch (Exception e) { - getErrPrintWriter().println("Error while executing command: " + e); + getErrPrintWriter().println("Error while executing command: " + cmd); + e.printStackTrace(getErrPrintWriter()); return -1; } } diff --git a/services/core/java/com/android/server/LockSettingsStorage.java b/services/core/java/com/android/server/LockSettingsStorage.java index 3d973a04c212..c858036eb024 100644 --- a/services/core/java/com/android/server/LockSettingsStorage.java +++ b/services/core/java/com/android/server/LockSettingsStorage.java @@ -119,9 +119,13 @@ class LockSettingsStorage { boolean isBaseZeroPattern; } - public LockSettingsStorage(Context context, Callback callback) { + public LockSettingsStorage(Context context) { mContext = context; - mOpenHelper = new DatabaseHelper(context, callback); + mOpenHelper = new DatabaseHelper(context); + } + + public void setDatabaseOnCreateCallback(Callback callback) { + mOpenHelper.setCallback(callback); } public void writeKeyValue(String key, String value, int userId) { @@ -472,11 +476,14 @@ class LockSettingsStorage { private static final int DATABASE_VERSION = 2; - private final Callback mCallback; + private Callback mCallback; - public DatabaseHelper(Context context, Callback callback) { + public DatabaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); setWriteAheadLoggingEnabled(true); + } + + public void setCallback(Callback callback) { mCallback = callback; } @@ -492,7 +499,9 @@ class LockSettingsStorage { @Override public void onCreate(SQLiteDatabase db) { createTable(db); - mCallback.initialize(db); + if (mCallback != null) { + mCallback.initialize(db); + } } @Override diff --git a/services/tests/servicestests/src/com/android/server/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/BaseLockSettingsServiceTests.java new file mode 100644 index 000000000000..c89d35c1d84e --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/BaseLockSettingsServiceTests.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2016 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 + */ + +package com.android.server; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.IActivityManager; +import android.app.NotificationManager; +import android.content.Context; +import android.content.pm.UserInfo; +import android.database.sqlite.SQLiteDatabase; +import android.os.FileUtils; +import android.os.Handler; +import android.os.IProgressListener; +import android.os.RemoteException; +import android.os.UserManager; +import android.os.storage.IStorageManager; +import android.security.KeyStore; +import android.service.gatekeeper.GateKeeperResponse; +import android.service.gatekeeper.IGateKeeperService; +import android.test.AndroidTestCase; + +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.VerifyCredentialResponse; +import com.android.server.LockSettingsService.SynchronizedStrongAuthTracker; +import com.android.server.LockSettingsStorage.CredentialHash; +import com.android.server.MockGateKeeperService.AuthToken; +import com.android.server.MockGateKeeperService.VerifyHandle; + +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.io.File; +import java.util.Arrays; + + +public class BaseLockSettingsServiceTests extends AndroidTestCase { + protected static final int PRIMARY_USER_ID = 0; + protected static final int MANAGED_PROFILE_USER_ID = 12; + protected static final int SECONDARY_USER_ID = 20; + + private static final UserInfo PRIMARY_USER_INFO = new UserInfo(PRIMARY_USER_ID, null, null, + UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY); + private static final UserInfo MANAGED_PROFILE_INFO = new UserInfo(MANAGED_PROFILE_USER_ID, null, + null, UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_MANAGED_PROFILE); + private static final UserInfo SECONDARY_USER_INFO = new UserInfo(SECONDARY_USER_ID, null, null, + UserInfo.FLAG_INITIALIZED); + + LockSettingsService mService; + + MockLockSettingsContext mContext; + LockSettingsStorageTestable mStorage; + + LockPatternUtils mLockPatternUtils; + MockGateKeeperService mGateKeeperService; + NotificationManager mNotificationManager; + UserManager mUserManager; + MockStorageManager mStorageManager; + IActivityManager mActivityManager; + + KeyStore mKeyStore; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + mLockPatternUtils = mock(LockPatternUtils.class); + mGateKeeperService = new MockGateKeeperService(); + mNotificationManager = mock(NotificationManager.class); + mUserManager = mock(UserManager.class); + mStorageManager = new MockStorageManager(); + mActivityManager = mock(IActivityManager.class); + mContext = new MockLockSettingsContext(getContext(), mUserManager, mNotificationManager); + mStorage = new LockSettingsStorageTestable(mContext, + new File(getContext().getFilesDir(), "locksettings")); + File storageDir = mStorage.mStorageDir; + if (storageDir.exists()) { + FileUtils.deleteContents(storageDir); + } else { + storageDir.mkdirs(); + } + + mService = new LockSettingsServiceTestable(mContext, mLockPatternUtils, + mStorage, mGateKeeperService, mKeyStore, mStorageManager, mActivityManager); + when(mUserManager.getUserInfo(eq(PRIMARY_USER_ID))).thenReturn(PRIMARY_USER_INFO); + when(mUserManager.getProfiles(eq(PRIMARY_USER_ID))).thenReturn(Arrays.asList( + new UserInfo[] {PRIMARY_USER_INFO, MANAGED_PROFILE_INFO})); + when(mUserManager.getUserInfo(eq(MANAGED_PROFILE_USER_ID))).thenReturn( + MANAGED_PROFILE_INFO); + when(mUserManager.getProfileParent(eq(MANAGED_PROFILE_USER_ID))).thenReturn( + PRIMARY_USER_INFO); + when(mUserManager.getUserInfo(eq(SECONDARY_USER_ID))).thenReturn(SECONDARY_USER_INFO); + + when(mActivityManager.unlockUser(anyInt(), any(), any(), any())).thenAnswer( + new Answer<Boolean>() { + @Override + public Boolean answer(InvocationOnMock invocation) throws Throwable { + Object[] args = invocation.getArguments(); + mStorageManager.unlockUser((int)args[0], (byte[])args[2], + (IProgressListener) args[3]); + return true; + } + }); + + when(mLockPatternUtils.getLockSettings()).thenReturn(mService); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + mStorage.closeDatabase(); + File db = getContext().getDatabasePath("locksettings.db"); + assertTrue(!db.exists() || db.delete()); + + File storageDir = mStorage.mStorageDir; + assertTrue(FileUtils.deleteContents(storageDir)); + } +} + diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/LockSettingsServiceTestable.java new file mode 100644 index 000000000000..613ec0be0919 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/LockSettingsServiceTestable.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2017 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 + */ + +package com.android.server; + +import static org.mockito.Mockito.mock; + +import android.app.IActivityManager; +import android.content.Context; +import android.os.Handler; +import android.os.storage.IStorageManager; +import android.security.KeyStore; +import android.service.gatekeeper.IGateKeeperService; + +import com.android.internal.widget.LockPatternUtils; + +import java.io.FileNotFoundException; + +public class LockSettingsServiceTestable extends LockSettingsService { + + private static class MockInjector extends LockSettingsService.Injector { + + private LockSettingsStorage mLockSettingsStorage; + private KeyStore mKeyStore; + private IActivityManager mActivityManager; + private LockPatternUtils mLockPatternUtils; + private IStorageManager mStorageManager; + + public MockInjector(Context context, LockSettingsStorage storage, KeyStore keyStore, + IActivityManager activityManager, LockPatternUtils lockPatternUtils, + IStorageManager storageManager) { + super(context); + mLockSettingsStorage = storage; + mKeyStore = keyStore; + mActivityManager = activityManager; + mLockPatternUtils = lockPatternUtils; + mStorageManager = storageManager; + } + + @Override + public Handler getHandler() { + return mock(Handler.class); + } + + @Override + public LockSettingsStorage getStorage() { + return mLockSettingsStorage; + } + + @Override + public LockSettingsStrongAuth getStrongAuth() { + return mock(LockSettingsStrongAuth.class); + } + + @Override + public SynchronizedStrongAuthTracker getStrongAuthTracker() { + return mock(SynchronizedStrongAuthTracker.class); + } + + @Override + public IActivityManager getActivityManager() { + return mActivityManager; + } + + @Override + public LockPatternUtils getLockPatternUtils() { + return mLockPatternUtils; + } + + @Override + public KeyStore getKeyStore() { + return mKeyStore; + } + + @Override + public IStorageManager getStorageManager() { + return mStorageManager; + } + } + + protected LockSettingsServiceTestable(Context context, LockPatternUtils lockPatternUtils, + LockSettingsStorage storage, IGateKeeperService gatekeeper, KeyStore keystore, + IStorageManager storageManager, IActivityManager mActivityManager) { + super(new MockInjector(context, storage, keystore, mActivityManager, lockPatternUtils, + storageManager)); + mGateKeeperService = gatekeeper; + } + + @Override + protected void tieProfileLockToParent(int userId, String password) { + mStorage.writeChildProfileLock(userId, password.getBytes()); + } + + @Override + protected String getDecryptedPasswordForTiedProfile(int userId) throws FileNotFoundException { + byte[] storedData = mStorage.readChildProfileLock(userId); + if (storedData == null) { + throw new FileNotFoundException("Child profile lock file not found"); + } + return new String(storedData); + } + +} diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/LockSettingsServiceTests.java new file mode 100644 index 000000000000..4c2e17172e76 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/LockSettingsServiceTests.java @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2016 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 + */ + +package com.android.server; + +import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE; +import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD; +import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN; + +import android.os.RemoteException; +import android.service.gatekeeper.GateKeeperResponse; + +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.VerifyCredentialResponse; +import com.android.server.LockSettingsStorage.CredentialHash; +import com.android.server.MockGateKeeperService.VerifyHandle; + +/** + * runtest frameworks-services -c com.android.server.LockSettingsServiceTests + */ +public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { + + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + public void testCreatePasswordPrimaryUser() throws RemoteException { + testCreateCredential(PRIMARY_USER_ID, "password", CREDENTIAL_TYPE_PASSWORD); + } + + public void testCreatePatternPrimaryUser() throws RemoteException { + testCreateCredential(PRIMARY_USER_ID, "123456789", CREDENTIAL_TYPE_PATTERN); + } + + public void testChangePasswordPrimaryUser() throws RemoteException { + testChangeCredentials(PRIMARY_USER_ID, "78963214", CREDENTIAL_TYPE_PATTERN, + "asdfghjk", CREDENTIAL_TYPE_PASSWORD); + } + + public void testChangePatternPrimaryUser() throws RemoteException { + testChangeCredentials(PRIMARY_USER_ID, "!£$%^&*(())", CREDENTIAL_TYPE_PASSWORD, + "1596321", CREDENTIAL_TYPE_PATTERN); + } + + public void testChangePasswordFailPrimaryUser() throws RemoteException { + final long sid = 1234; + final String FAILED_MESSAGE = "Failed to enroll password"; + initializeStorageWithCredential(PRIMARY_USER_ID, "password", CREDENTIAL_TYPE_PASSWORD, sid); + + try { + mService.setLockCredential("newpwd", CREDENTIAL_TYPE_PASSWORD, "badpwd", + PRIMARY_USER_ID); + fail("Did not fail when enrolling using incorrect credential"); + } catch (RemoteException expected) { + assertTrue(expected.getMessage().equals(FAILED_MESSAGE)); + } + try { + mService.setLockCredential("newpwd", CREDENTIAL_TYPE_PASSWORD, null, PRIMARY_USER_ID); + fail("Did not fail when enrolling using incorrect credential"); + } catch (RemoteException expected) { + assertTrue(expected.getMessage().equals(FAILED_MESSAGE)); + } + assertVerifyCredentials(PRIMARY_USER_ID, "password", CREDENTIAL_TYPE_PASSWORD, sid); + } + + public void testClearPasswordPrimaryUser() throws RemoteException { + final String PASSWORD = "password"; + initializeStorageWithCredential(PRIMARY_USER_ID, PASSWORD, CREDENTIAL_TYPE_PASSWORD, 1234); + mService.setLockCredential(null, CREDENTIAL_TYPE_NONE, PASSWORD, PRIMARY_USER_ID); + assertFalse(mService.havePassword(PRIMARY_USER_ID)); + assertFalse(mService.havePattern(PRIMARY_USER_ID)); + assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID)); + } + + public void testManagedProfileUnifiedChallenge() throws RemoteException { + final String UnifiedPassword = "testManagedProfileUnifiedChallenge-pwd"; + mService.setLockCredential(UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, + PRIMARY_USER_ID); + mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null); + final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID); + final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID); + assertTrue(primarySid != 0); + assertTrue(profileSid != 0); + assertTrue(profileSid != primarySid); + + // clear auth token and wait for verify challenge from primary user to re-generate it. + mGateKeeperService.clearAuthToken(MANAGED_PROFILE_USER_ID); + // verify credential + assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential( + UnifiedPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID) + .getResponseCode()); + + // Verify that we have a new auth token for the profile + assertNotNull(mGateKeeperService.getAuthToken(MANAGED_PROFILE_USER_ID)); + assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID)); + + /* Currently in LockSettingsService.setLockCredential, unlockUser() is called with the new + * credential as part of verifyCredential() before the new credential is committed in + * StorageManager. So we relax the check in our mock StorageManager to allow that. + */ + mStorageManager.setIgnoreBadUnlock(true); + // Change primary password and verify that profile SID remains + mService.setLockCredential("pwd", LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, + UnifiedPassword, PRIMARY_USER_ID); + mStorageManager.setIgnoreBadUnlock(false); + assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID)); + } + + public void testManagedProfileSeparateChallenge() throws RemoteException { + final String primaryPassword = "testManagedProfileSeparateChallenge-primary"; + final String profilePassword = "testManagedProfileSeparateChallenge-profile"; + mService.setLockCredential(primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, + PRIMARY_USER_ID); + /* Currently in LockSettingsService.setLockCredential, unlockUser() is called with the new + * credential as part of verifyCredential() before the new credential is committed in + * StorageManager. So we relax the check in our mock StorageManager to allow that. + */ + mStorageManager.setIgnoreBadUnlock(true); + mService.setLockCredential(profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null, + MANAGED_PROFILE_USER_ID); + mStorageManager.setIgnoreBadUnlock(false); + + final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID); + final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID); + assertTrue(primarySid != 0); + assertTrue(profileSid != 0); + assertTrue(profileSid != primarySid); + + // clear auth token and make sure verify challenge from primary user does not regenerate it. + mGateKeeperService.clearAuthToken(MANAGED_PROFILE_USER_ID); + // verify primary credential + assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential( + primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID) + .getResponseCode()); + assertNull(mGateKeeperService.getAuthToken(MANAGED_PROFILE_USER_ID)); + + // verify profile credential + assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential( + profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, + MANAGED_PROFILE_USER_ID).getResponseCode()); + assertNotNull(mGateKeeperService.getAuthToken(MANAGED_PROFILE_USER_ID)); + assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID)); + + // Change primary credential and make sure we don't affect profile + mStorageManager.setIgnoreBadUnlock(true); + mService.setLockCredential("pwd", LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, + primaryPassword, PRIMARY_USER_ID); + mStorageManager.setIgnoreBadUnlock(false); + assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential( + profilePassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, + MANAGED_PROFILE_USER_ID).getResponseCode()); + assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID)); + } + + private void testCreateCredential(int userId, String credential, int type) + throws RemoteException { + mService.setLockCredential(credential, type, null, userId); + assertVerifyCredentials(userId, credential, type, -1); + } + + private void testChangeCredentials(int userId, String newCredential, int newType, + String oldCredential, int oldType) throws RemoteException { + final long sid = 1234; + initializeStorageWithCredential(userId, oldCredential, oldType, sid); + mService.setLockCredential(newCredential, newType, oldCredential, userId); + assertVerifyCredentials(userId, newCredential, newType, sid); + } + + private void assertVerifyCredentials(int userId, String credential, int type, long sid) + throws RemoteException{ + final long challenge = 54321; + VerifyCredentialResponse response = mService.verifyCredential(credential, type, challenge, + userId); + + assertEquals(GateKeeperResponse.RESPONSE_OK, response.getResponseCode()); + if (sid != -1) assertEquals(sid, mGateKeeperService.getSecureUserId(userId)); + final int incorrectType; + if (type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD) { + assertTrue(mService.havePassword(userId)); + assertFalse(mService.havePattern(userId)); + incorrectType = LockPatternUtils.CREDENTIAL_TYPE_PATTERN; + } else if (type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN){ + assertFalse(mService.havePassword(userId)); + assertTrue(mService.havePattern(userId)); + incorrectType = LockPatternUtils.CREDENTIAL_TYPE_PASSWORD; + } else { + assertFalse(mService.havePassword(userId)); + assertFalse(mService.havePassword(userId)); + incorrectType = LockPatternUtils.CREDENTIAL_TYPE_PASSWORD; + } + // check for bad type + assertEquals(GateKeeperResponse.RESPONSE_ERROR, mService.verifyCredential(credential, + incorrectType, challenge, userId).getResponseCode()); + // check for bad credential + assertEquals(GateKeeperResponse.RESPONSE_ERROR, mService.verifyCredential("0" + credential, + type, challenge, userId).getResponseCode()); + } + + private void initializeStorageWithCredential(int userId, String credential, int type, long sid) { + byte[] oldHash = new VerifyHandle(credential.getBytes(), sid).toBytes(); + if (type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD) { + mStorage.writeCredentialHash(CredentialHash.create(oldHash, + LockPatternUtils.CREDENTIAL_TYPE_PASSWORD), userId); + } else { + mStorage.writeCredentialHash(CredentialHash.create(oldHash, + LockPatternUtils.CREDENTIAL_TYPE_PATTERN), userId); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTestable.java b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTestable.java new file mode 100644 index 000000000000..e81b02f071a8 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTestable.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2017 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 + */ + +package com.android.server; + +import android.content.Context; + +import java.io.File; + +public class LockSettingsStorageTestable extends LockSettingsStorage { + + public File mStorageDir; + + public LockSettingsStorageTestable(Context context, File storageDir) { + super(context); + mStorageDir = storageDir; + } + + @Override + String getLockPatternFilename(int userId) { + return new File(mStorageDir, + super.getLockPatternFilename(userId).replace('/', '-')).getAbsolutePath(); + } + + @Override + String getLockPasswordFilename(int userId) { + return new File(mStorageDir, + super.getLockPasswordFilename(userId).replace('/', '-')).getAbsolutePath(); + } + + @Override + String getChildProfileLockFile(int userId) { + return new File(mStorageDir, + super.getChildProfileLockFile(userId).replace('/', '-')).getAbsolutePath(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java index 9d521533d773..d110feaab3c9 100644 --- a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java +++ b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java @@ -20,6 +20,7 @@ import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import android.app.NotificationManager; import android.content.Context; import android.content.ContextWrapper; import android.content.pm.UserInfo; @@ -60,46 +61,22 @@ public class LockSettingsStorageTests extends AndroidTestCase { assertTrue(FileUtils.deleteContents(mStorageDir)); assertTrue(!mDb.exists() || mDb.delete()); - final Context ctx = getContext(); final UserManager mockUserManager = mock(UserManager.class); // User 2 is a profile of user 1. when(mockUserManager.getProfileParent(eq(2))).thenReturn(new UserInfo(1, "name", 0)); // User 3 is a profile of user 0. when(mockUserManager.getProfileParent(eq(3))).thenReturn(new UserInfo(0, "name", 0)); - setContext(new ContextWrapper(ctx) { - @Override - public Object getSystemService(String name) { - if (USER_SERVICE.equals(name)) { - return mockUserManager; - } - return super.getSystemService(name); - } - }); - - mStorage = new LockSettingsStorage(getContext(), new LockSettingsStorage.Callback() { - @Override - public void initialize(SQLiteDatabase db) { - mStorage.writeKeyValue(db, "initializedKey", "initialValue", 0); - } - }) { - @Override - String getLockPatternFilename(int userId) { - return new File(mStorageDir, - super.getLockPatternFilename(userId).replace('/', '-')).getAbsolutePath(); - } - @Override - String getLockPasswordFilename(int userId) { - return new File(mStorageDir, - super.getLockPasswordFilename(userId).replace('/', '-')).getAbsolutePath(); - } - - @Override - String getChildProfileLockFile(int userId) { - return new File(mStorageDir, - super.getChildProfileLockFile(userId).replace('/', '-')).getAbsolutePath(); - } - }; + MockLockSettingsContext context = new MockLockSettingsContext(getContext(), mockUserManager, + mock(NotificationManager.class)); + mStorage = new LockSettingsStorageTestable(context, + new File(getContext().getFilesDir(), "locksettings")); + mStorage.setDatabaseOnCreateCallback(new LockSettingsStorage.Callback() { + @Override + public void initialize(SQLiteDatabase db) { + mStorage.writeKeyValue(db, "initializedKey", "initialValue", 0); + } + }); } @Override @@ -323,7 +300,7 @@ public class LockSettingsStorageTests extends AndroidTestCase { } public void testFileLocation_Owner() { - LockSettingsStorage storage = new LockSettingsStorage(getContext(), null); + LockSettingsStorage storage = new LockSettingsStorage(getContext()); assertEquals("/data/system/gesture.key", storage.getLegacyLockPatternFilename(0)); assertEquals("/data/system/password.key", storage.getLegacyLockPasswordFilename(0)); @@ -332,21 +309,21 @@ public class LockSettingsStorageTests extends AndroidTestCase { } public void testFileLocation_SecondaryUser() { - LockSettingsStorage storage = new LockSettingsStorage(getContext(), null); + LockSettingsStorage storage = new LockSettingsStorage(getContext()); assertEquals("/data/system/users/1/gatekeeper.pattern.key", storage.getLockPatternFilename(1)); assertEquals("/data/system/users/1/gatekeeper.password.key", storage.getLockPasswordFilename(1)); } public void testFileLocation_ProfileToSecondary() { - LockSettingsStorage storage = new LockSettingsStorage(getContext(), null); + LockSettingsStorage storage = new LockSettingsStorage(getContext()); assertEquals("/data/system/users/2/gatekeeper.pattern.key", storage.getLockPatternFilename(2)); assertEquals("/data/system/users/2/gatekeeper.password.key", storage.getLockPasswordFilename(2)); } public void testFileLocation_ProfileToOwner() { - LockSettingsStorage storage = new LockSettingsStorage(getContext(), null); + LockSettingsStorage storage = new LockSettingsStorage(getContext()); assertEquals("/data/system/users/3/gatekeeper.pattern.key", storage.getLockPatternFilename(3)); assertEquals("/data/system/users/3/gatekeeper.password.key", storage.getLockPasswordFilename(3)); diff --git a/services/tests/servicestests/src/com/android/server/MockGateKeeperService.java b/services/tests/servicestests/src/com/android/server/MockGateKeeperService.java new file mode 100644 index 000000000000..15983cada9c2 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/MockGateKeeperService.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2017 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 + */ + +package com.android.server; + +import android.os.IBinder; +import android.os.RemoteException; +import android.service.gatekeeper.GateKeeperResponse; +import android.service.gatekeeper.IGateKeeperService; +import android.util.ArrayMap; + +import junit.framework.AssertionFailedError; + +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Random; + +public class MockGateKeeperService implements IGateKeeperService { + static class VerifyHandle { + public byte[] password; + public long sid; + + public VerifyHandle(byte[] password, long sid) { + this.password = password; + this.sid = sid; + } + + public VerifyHandle(byte[] handle) { + ByteBuffer buffer = ByteBuffer.allocate(handle.length); + buffer.put(handle, 0, handle.length); + buffer.flip(); + int version = buffer.get(); + sid = buffer.getLong(); + password = new byte[buffer.remaining()]; + buffer.get(password); + } + + public byte[] toBytes() { + ByteBuffer buffer = ByteBuffer.allocate(1 + Long.BYTES + password.length); + buffer.put((byte)0); + buffer.putLong(sid); + buffer.put(password); + return buffer.array(); + } + } + + static class AuthToken { + public long challenge; + public long sid; + + public AuthToken(long challenge, long sid) { + this.challenge = challenge; + this.sid = sid; + } + + public AuthToken(byte[] handle) { + ByteBuffer buffer = ByteBuffer.allocate(handle.length); + buffer.put(handle, 0, handle.length); + buffer.flip(); + int version = buffer.get(); + challenge = buffer.getLong(); + sid = buffer.getLong(); + } + + public byte[] toBytes() { + ByteBuffer buffer = ByteBuffer.allocate(1 + Long.BYTES + Long.BYTES); + buffer.put((byte)0); + buffer.putLong(challenge); + buffer.putLong(sid); + return buffer.array(); + } + } + + private ArrayMap<Integer, Long> sidMap = new ArrayMap<>(); + private ArrayMap<Integer, AuthToken> authTokenMap = new ArrayMap<>(); + + private ArrayMap<Integer, byte[]> handleMap = new ArrayMap<>(); + + @Override + public GateKeeperResponse enroll(int uid, byte[] currentPasswordHandle, byte[] currentPassword, + byte[] desiredPassword) throws android.os.RemoteException { + + if (currentPasswordHandle != null) { + VerifyHandle handle = new VerifyHandle(currentPasswordHandle); + if (Arrays.equals(currentPassword, handle.password)) { + // Trusted enroll + VerifyHandle newHandle = new VerifyHandle(desiredPassword, handle.sid); + refreshSid(uid, handle.sid, false); + handleMap.put(uid, newHandle.toBytes()); + return GateKeeperResponse.createOkResponse(newHandle.toBytes(), false); + } else { + return null; + } + } else { + // Untrusted enroll + long newSid = new Random().nextLong(); + VerifyHandle newHandle = new VerifyHandle(desiredPassword, newSid); + refreshSid(uid, newSid, true); + handleMap.put(uid, newHandle.toBytes()); + return GateKeeperResponse.createOkResponse(newHandle.toBytes(), false); + } + } + + @Override + public GateKeeperResponse verify(int uid, byte[] enrolledPasswordHandle, + byte[] providedPassword) throws android.os.RemoteException { + return verifyChallenge(uid, 0, enrolledPasswordHandle, providedPassword); + } + + @Override + public GateKeeperResponse verifyChallenge(int uid, long challenge, + byte[] enrolledPasswordHandle, byte[] providedPassword) throws RemoteException { + + VerifyHandle handle = new VerifyHandle(enrolledPasswordHandle); + if (Arrays.equals(handle.password, providedPassword)) { + byte[] knownHandle = handleMap.get(uid); + if (knownHandle != null) { + if (!Arrays.equals(knownHandle, enrolledPasswordHandle)) { + throw new AssertionFailedError("Got correct but obsolete handle"); + } + } + refreshSid(uid, handle.sid, false); + AuthToken token = new AuthToken(challenge, handle.sid); + refreshAuthToken(uid, token); + return GateKeeperResponse.createOkResponse(token.toBytes(), false); + } else { + return GateKeeperResponse.createGenericResponse(GateKeeperResponse.RESPONSE_ERROR); + } + } + + private void refreshAuthToken(int uid, AuthToken token) { + authTokenMap.put(uid, token); + } + + public AuthToken getAuthToken(int uid) { + return authTokenMap.get(uid); + } + + public void clearAuthToken(int uid) { + authTokenMap.remove(uid); + } + + @Override + public IBinder asBinder() { + throw new UnsupportedOperationException(); + } + + @Override + public void clearSecureUserId(int userId) throws RemoteException { + sidMap.remove(userId); + } + + @Override + public long getSecureUserId(int userId) throws RemoteException { + if (sidMap.containsKey(userId)) { + return sidMap.get(userId); + } else { + return 0L; + } + } + + private void refreshSid(int uid, long sid, boolean force) { + if (!sidMap.containsKey(uid) || force) { + sidMap.put(uid, sid); + } else{ + if (sidMap.get(uid) != sid) { + throw new AssertionFailedError("Inconsistent SID"); + } + } + } + +} diff --git a/services/tests/servicestests/src/com/android/server/MockLockSettingsContext.java b/services/tests/servicestests/src/com/android/server/MockLockSettingsContext.java new file mode 100644 index 000000000000..b63936fdffdf --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/MockLockSettingsContext.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2017 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 + */ + +package com.android.server; + +import android.app.NotificationManager; +import android.content.Context; +import android.content.ContextWrapper; +import android.os.UserManager; + +public class MockLockSettingsContext extends ContextWrapper { + + private UserManager mUserManager; + private NotificationManager mNotificationManager; + + public MockLockSettingsContext(Context base, UserManager userManager, + NotificationManager notificationManager) { + super(base); + mUserManager = userManager; + mNotificationManager = notificationManager; + } + + @Override + public Object getSystemService(String name) { + if (USER_SERVICE.equals(name)) { + return mUserManager; + } else if (NOTIFICATION_SERVICE.equals(name)) { + return mNotificationManager; + } else { + throw new RuntimeException("System service not mocked: " + name); + } + } + + @Override + public void enforceCallingOrSelfPermission(String permission, String message) { + // Skip permission checks for unit tests. + } + +} diff --git a/services/tests/servicestests/src/com/android/server/MockStorageManager.java b/services/tests/servicestests/src/com/android/server/MockStorageManager.java new file mode 100644 index 000000000000..931651603610 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/MockStorageManager.java @@ -0,0 +1,475 @@ +/* + * Copyright (C) 2017 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 + */ + +package com.android.server; + +import android.content.pm.IPackageMoveObserver; +import android.os.IBinder; +import android.os.IProgressListener; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.os.storage.DiskInfo; +import android.os.storage.IObbActionListener; +import android.os.storage.IStorageEventListener; +import android.os.storage.IStorageManager; +import android.os.storage.IStorageShutdownObserver; +import android.os.storage.StorageVolume; +import android.os.storage.VolumeInfo; +import android.os.storage.VolumeRecord; +import android.util.ArrayMap; +import android.util.Pair; + +import junit.framework.AssertionFailedError; + +import java.util.ArrayList; +import java.util.Arrays; + +public class MockStorageManager implements IStorageManager { + + private ArrayMap<Integer, ArrayList<Pair<byte[], byte[]>>> mAuth = new ArrayMap<>(); + private boolean mIgnoreBadUnlock; + + @Override + public void addUserKeyAuth(int userId, int serialNumber, byte[] token, byte[] secret) + throws RemoteException { + getUserAuth(userId).add(new Pair<>(token, secret)); + } + + @Override + public void fixateNewestUserKeyAuth(int userId) throws RemoteException { + ArrayList<Pair<byte[], byte[]>> auths = mAuth.get(userId); + Pair<byte[], byte[]> latest = auths.get(auths.size() - 1); + auths.clear(); + auths.add(latest); + } + + private ArrayList<Pair<byte[], byte[]>> getUserAuth(int userId) { + if (!mAuth.containsKey(userId)) { + ArrayList<Pair<byte[], byte[]>> auths = new ArrayList<Pair<byte[], byte[]>>(); + auths.add(new Pair(null, null)); + mAuth.put(userId, auths); + } + return mAuth.get(userId); + } + + public byte[] getUserUnlockToken(int userId) { + ArrayList<Pair<byte[], byte[]>> auths = getUserAuth(userId); + if (auths.size() != 1) { + throw new AssertionFailedError("More than one secret exists"); + } + return auths.get(0).second; + } + + public void unlockUser(int userId, byte[] secret, IProgressListener listener) + throws RemoteException { + listener.onStarted(userId, null); + listener.onFinished(userId, null); + ArrayList<Pair<byte[], byte[]>> auths = getUserAuth(userId); + if (secret != null) { + if (auths.size() > 1) { + throw new AssertionFailedError("More than one secret exists"); + } + Pair<byte[], byte[]> auth = auths.get(0); + if ((!mIgnoreBadUnlock) && auth.second != null && !Arrays.equals(secret, auth.second)) { + throw new AssertionFailedError("Invalid secret to unlock user"); + } + } else { + if (auths != null && auths.size() > 0) { + throw new AssertionFailedError("Cannot unlock encrypted user with empty token"); + } + } + } + + public void setIgnoreBadUnlock(boolean ignore) { + mIgnoreBadUnlock = ignore; + } + + @Override + public IBinder asBinder() { + throw new UnsupportedOperationException(); + } + + @Override + public void registerListener(IStorageEventListener listener) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public void unregisterListener(IStorageEventListener listener) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isUsbMassStorageConnected() throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public void setUsbMassStorageEnabled(boolean enable) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isUsbMassStorageEnabled() throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public int mountVolume(String mountPoint) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public void unmountVolume(String mountPoint, boolean force, boolean removeEncryption) + throws RemoteException { + throw new UnsupportedOperationException(); + + } + + @Override + public int formatVolume(String mountPoint) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public int[] getStorageUsers(String path) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public String getVolumeState(String mountPoint) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public int createSecureContainer(String id, int sizeMb, String fstype, String key, int ownerUid, + boolean external) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public int finalizeSecureContainer(String id) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public int destroySecureContainer(String id, boolean force) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public int mountSecureContainer(String id, String key, int ownerUid, boolean readOnly) + throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public int unmountSecureContainer(String id, boolean force) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isSecureContainerMounted(String id) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public int renameSecureContainer(String oldId, String newId) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public String getSecureContainerPath(String id) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public String[] getSecureContainerList() throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public void shutdown(IStorageShutdownObserver observer) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public void finishMediaUpdate() throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public void mountObb(String rawPath, String canonicalPath, String key, IObbActionListener token, + int nonce) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public void unmountObb(String rawPath, boolean force, IObbActionListener token, int nonce) + throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isObbMounted(String rawPath) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public String getMountedObbPath(String rawPath) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isExternalStorageEmulated() throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public int decryptStorage(String password) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public int encryptStorage(int type, String password) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public int changeEncryptionPassword(int type, String password) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public StorageVolume[] getVolumeList(int uid, String packageName, int flags) + throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public String getSecureContainerFilesystemPath(String cid) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public int getEncryptionState() throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public int verifyEncryptionPassword(String password) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public int fixPermissionsSecureContainer(String id, int gid, String filename) + throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public int mkdirs(String callingPkg, String path) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public int getPasswordType() throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public String getPassword() throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public void clearPassword() throws RemoteException { + throw new UnsupportedOperationException(); + + } + + @Override + public void setField(String field, String contents) throws RemoteException { + throw new UnsupportedOperationException(); + + } + + @Override + public String getField(String field) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public int resizeSecureContainer(String id, int sizeMb, String key) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public long lastMaintenance() throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public void runMaintenance() throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public void waitForAsecScan() throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public DiskInfo[] getDisks() throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public VolumeInfo[] getVolumes(int flags) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public VolumeRecord[] getVolumeRecords(int flags) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public void mount(String volId) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public void unmount(String volId) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public void format(String volId) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public void partitionPublic(String diskId) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public void partitionPrivate(String diskId) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public void partitionMixed(String diskId, int ratio) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public void setVolumeNickname(String fsUuid, String nickname) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public void setVolumeUserFlags(String fsUuid, int flags, int mask) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public void forgetVolume(String fsUuid) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public void forgetAllVolumes() throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public String getPrimaryStorageUuid() throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public void setPrimaryStorageUuid(String volumeUuid, IPackageMoveObserver callback) + throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public long benchmark(String volId) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public void setDebugFlags(int flags, int mask) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public void createUserKey(int userId, int serialNumber, boolean ephemeral) + throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public void destroyUserKey(int userId) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public void unlockUserKey(int userId, int serialNumber, byte[] token, byte[] secret) + throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public void lockUserKey(int userId) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isUserKeyUnlocked(int userId) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public void prepareUserStorage(String volumeUuid, int userId, int serialNumber, int flags) + throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public void destroyUserStorage(String volumeUuid, int userId, int flags) + throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isConvertibleToFBE() throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public ParcelFileDescriptor mountAppFuse(String name) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public void fstrim(int flags) throws RemoteException { + throw new UnsupportedOperationException(); + } + +} |