Additional SecurityPolicy functionality
* Begin wiring into system DevicePolicyManager requirements * Semi-real implementations of isSupported() & isActive() * Added new API (placeholder) updatePolicies() * Updated existing unit tests as needed Bug: 2387961
This commit is contained in:
parent
ecb1af8041
commit
d62860821c
|
@ -193,6 +193,20 @@
|
|||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<!-- Support for DeviceAdmin / DevicePolicyManager. See SecurityPolicy class for impl. -->
|
||||
<receiver
|
||||
android:name=".SecurityPolicy$PolicyAdmin"
|
||||
android:label="@string/device_admin_label"
|
||||
android:description="@string/device_admin_description"
|
||||
android:permission="android.permission.BIND_DEVICE_ADMIN" >
|
||||
<meta-data
|
||||
android:name="android.app.device_admin"
|
||||
android:resource="@xml/device_admin" />
|
||||
<intent-filter>
|
||||
<action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<service
|
||||
android:name=".service.MailService"
|
||||
android:enabled="false"
|
||||
|
|
|
@ -577,4 +577,11 @@
|
|||
|
||||
<!-- Message that appears if the AccountManager cannot create the system Account -->
|
||||
<string name="system_account_create_failed">The AccountManager could not create the Account; please try again.</string>
|
||||
|
||||
<!-- Strings that support the DeviceAdmin / DevicePolicyManager API -->
|
||||
<!-- Name of the DeviceAdmin (seen in settings - anywhere else?) -->
|
||||
<string name="device_admin_label">Email Device Administrator</string>
|
||||
<!-- Long-form description of the DeviceAdmin (seen in settings - anywhere else?) -->
|
||||
<string name="device_admin_description">Email Device Administrator - Long Description</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2010 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.
|
||||
-->
|
||||
|
||||
<device-admin xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-policies>
|
||||
<limit-password />
|
||||
<watch-login />
|
||||
<limit-unlock />
|
||||
<wipe-data />
|
||||
</uses-policies>
|
||||
</device-admin>
|
|
@ -19,6 +19,8 @@ package com.android.email;
|
|||
import com.android.email.provider.EmailContent.Account;
|
||||
|
||||
import android.app.DeviceAdmin;
|
||||
import android.app.DevicePolicyManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
|
@ -28,8 +30,18 @@ import android.database.Cursor;
|
|||
*/
|
||||
public class SecurityPolicy {
|
||||
|
||||
/** STOPSHIP - ok to check in true for now, but must be false for shipping */
|
||||
/** DO NOT CHECK IN WHILE 'true' */
|
||||
private static final boolean DEBUG_ALWAYS_ACTIVE = true;
|
||||
|
||||
private static SecurityPolicy sInstance = null;
|
||||
private Context mContext;
|
||||
private DevicePolicyManager mDPM;
|
||||
private ComponentName mAdminName;
|
||||
private PolicySet mAggregatePolicy;
|
||||
|
||||
private static final PolicySet NO_POLICY_SET =
|
||||
new PolicySet(0, PolicySet.PASSWORD_MODE_NONE, 0, 0, false);
|
||||
|
||||
/**
|
||||
* This projection on Account is for scanning/reading
|
||||
|
@ -42,6 +54,15 @@ public class SecurityPolicy {
|
|||
private static final String WHERE_ACCOUNT_SECURITY_NONZERO =
|
||||
Account.SECURITY_FLAGS + " IS NOT NULL AND " + Account.SECURITY_FLAGS + "!=0";
|
||||
|
||||
/**
|
||||
* These are hardcoded limits based on knowledge of the current DevicePolicyManager
|
||||
* and screen lock mechanisms. Wherever possible, these should be replaced with queries of
|
||||
* dynamic capabilities of the device (e.g. what password modes are supported?)
|
||||
*/
|
||||
private static final int LIMIT_MIN_PASSWORD_LENGTH = 16;
|
||||
private static final int LIMIT_PASSWORD_MODE = PolicySet.PASSWORD_MODE_STRONG;
|
||||
private static final int LIMIT_SCREENLOCK_TIME = PolicySet.SCREEN_LOCK_TIME_MAX;
|
||||
|
||||
/**
|
||||
* Get the security policy instance
|
||||
*/
|
||||
|
@ -57,6 +78,9 @@ public class SecurityPolicy {
|
|||
*/
|
||||
private SecurityPolicy(Context context) {
|
||||
mContext = context;
|
||||
mDPM = null;
|
||||
mAdminName = new ComponentName(context, PolicyAdmin.class);
|
||||
mAggregatePolicy = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -76,7 +100,8 @@ public class SecurityPolicy {
|
|||
* max screen lock time take the min
|
||||
* require remote wipe take the max (logical or)
|
||||
*
|
||||
* @return a policy representing the strongest aggregate, or null if none are defined
|
||||
* @return a policy representing the strongest aggregate. If no policy sets are defined,
|
||||
* a lightweight "nothing required" policy will be returned. Never null.
|
||||
*/
|
||||
/* package */ PolicySet computeAggregatePolicy() {
|
||||
boolean policiesFound = false;
|
||||
|
@ -117,39 +142,107 @@ public class SecurityPolicy {
|
|||
return new PolicySet(minPasswordLength, passwordMode, maxPasswordFails,
|
||||
maxScreenLockTime, requireRemoteWipe);
|
||||
} else {
|
||||
return null;
|
||||
return NO_POLICY_SET;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Query used to determine if a given policy is "possible" (irrespective of current
|
||||
* Get the dpm. This mainly allows us to make some utility calls without it, for testing.
|
||||
*/
|
||||
private synchronized DevicePolicyManager getDPM() {
|
||||
if (mDPM == null) {
|
||||
mDPM = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
|
||||
}
|
||||
return mDPM;
|
||||
}
|
||||
|
||||
/**
|
||||
* API: Query used to determine if a given policy is "possible" (irrespective of current
|
||||
* device state. This is used when creating new accounts.
|
||||
*
|
||||
* TO BE IMPLEMENTED
|
||||
* TODO: This is hardcoded based on knowledge of the current DevicePolicyManager
|
||||
* and screen lock mechanisms. It would be nice to replace these tests with something
|
||||
* more dynamic.
|
||||
*
|
||||
* @param policies the policies requested
|
||||
* @return true if the policies are supported, false if not supported
|
||||
*/
|
||||
public boolean isSupported(PolicySet policies) {
|
||||
if (policies.mMinPasswordLength > LIMIT_MIN_PASSWORD_LENGTH) {
|
||||
return false;
|
||||
}
|
||||
if (policies.mPasswordMode > LIMIT_PASSWORD_MODE ) {
|
||||
return false;
|
||||
}
|
||||
// No limit on password fail count
|
||||
if (policies.mMaxScreenLockTime > LIMIT_SCREENLOCK_TIME ) {
|
||||
return false;
|
||||
}
|
||||
// No limit on remote wipe capable
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query used to determine if a given policy is "active" (the device is operating at
|
||||
* the required security level). This is used when creating new accounts.
|
||||
*
|
||||
* TO BE IMPLEMENTED
|
||||
* API: Report that policies may have been updated due to rewriting values in an Account.
|
||||
*/
|
||||
public synchronized void updatePolicies() {
|
||||
mAggregatePolicy = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* API: Query used to determine if a given policy is "active" (the device is operating at
|
||||
* the required security level). This is used when creating new accounts. This method
|
||||
* is for queries only, and does not trigger any change in device state.
|
||||
*
|
||||
* @param policies the policies requested
|
||||
* @return true if the policies are active, false if not active
|
||||
*/
|
||||
public boolean isActive(PolicySet policies) {
|
||||
return true;
|
||||
DevicePolicyManager dpm = getDPM();
|
||||
if (dpm.isAdminActive(mAdminName)) {
|
||||
// check each policy
|
||||
PolicySet aggregate;
|
||||
synchronized (this) {
|
||||
if (mAggregatePolicy == null) {
|
||||
mAggregatePolicy = computeAggregatePolicy();
|
||||
}
|
||||
aggregate = mAggregatePolicy;
|
||||
}
|
||||
// quick check for the "empty set" of no policies
|
||||
if (aggregate == NO_POLICY_SET) {
|
||||
return true;
|
||||
}
|
||||
// check each policy explicitly
|
||||
if (aggregate.mMinPasswordLength > 0) {
|
||||
if (dpm.getPasswordMinimumLength(mAdminName) < aggregate.mMinPasswordLength) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (aggregate.mPasswordMode > 0) {
|
||||
if (dpm.getPasswordQuality(mAdminName) < aggregate.getDPManagerPasswordMode()) {
|
||||
return false;
|
||||
}
|
||||
if (!dpm.isActivePasswordSufficient()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (aggregate.mMaxScreenLockTime > 0) {
|
||||
// Note, we use seconds, dpm uses milliseconds
|
||||
if (dpm.getMaximumTimeToLock(mAdminName) > aggregate.mMaxScreenLockTime * 1000) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// password failures are counted locally - no test required here
|
||||
// no check required for remote wipe (it's supported, if we're the admin)
|
||||
}
|
||||
// return false, not active - unless debugging enabled
|
||||
return DEBUG_ALWAYS_ACTIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync service should call this any time a sync fails due to isActive() returning false.
|
||||
* This will kick off the acquire-admin-state process and/or increase the security level.
|
||||
* This will kick off the notify-acquire-admin-state process and/or increase the security level.
|
||||
* The caller needs to write the required policies into this account before making this call.
|
||||
*
|
||||
* @param accountId the account for which sync cannot proceed
|
||||
|
@ -196,7 +289,7 @@ public class SecurityPolicy {
|
|||
* @param minPasswordLength (0=not enforced)
|
||||
* @param passwordMode
|
||||
* @param maxPasswordFails (0=not enforced)
|
||||
* @param maxScreenLockTime (0=not enforced)
|
||||
* @param maxScreenLockTime in seconds (0=not enforced)
|
||||
* @param requireRemoteWipe
|
||||
*/
|
||||
public PolicySet(int minPasswordLength, int passwordMode, int maxPasswordFails,
|
||||
|
@ -245,6 +338,20 @@ public class SecurityPolicy {
|
|||
mRequireRemoteWipe = 0 != (flags & REQUIRE_REMOTE_WIPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to map DevicePolicyManager password modes to our internal encoding.
|
||||
*/
|
||||
public int getDPManagerPasswordMode() {
|
||||
switch (mPasswordMode) {
|
||||
case PASSWORD_MODE_SIMPLE:
|
||||
return DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
|
||||
case PASSWORD_MODE_STRONG:
|
||||
return DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
|
||||
default:
|
||||
return DevicePolicyManager .PASSWORD_QUALITY_UNSPECIFIED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Record flags (and a sync key for the flags) into an Account
|
||||
* Note: the hash code is defined as the encoding used in Account
|
||||
|
|
|
@ -25,6 +25,7 @@ import com.android.email.provider.EmailContent.AccountColumns;
|
|||
import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.net.Uri;
|
||||
import android.test.ProviderTestCase2;
|
||||
import android.test.suitebuilder.annotation.MediumTest;
|
||||
|
@ -38,6 +39,9 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
|
|||
|
||||
private Context mMockContext;
|
||||
|
||||
private static final PolicySet EMPTY_POLICY_SET =
|
||||
new PolicySet(0, PolicySet.PASSWORD_MODE_NONE, 0, 0, false);
|
||||
|
||||
public SecurityPolicyTests() {
|
||||
super(EmailProvider.class, EmailProvider.EMAIL_AUTHORITY);
|
||||
}
|
||||
|
@ -46,7 +50,7 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
|
|||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
mMockContext = getMockContext();
|
||||
mMockContext = new MockContext2(getMockContext(), this.mContext);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -57,6 +61,24 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
|
|||
super.tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Private context wrapper used to add back getPackageName() for these tests
|
||||
*/
|
||||
private static class MockContext2 extends ContextWrapper {
|
||||
|
||||
private final Context mRealContext;
|
||||
|
||||
public MockContext2(Context mockContext, Context realContext) {
|
||||
super(mockContext);
|
||||
mRealContext = realContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPackageName() {
|
||||
return mRealContext.getPackageName();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the security policy object, and inject the mock context so it works as expected
|
||||
*/
|
||||
|
@ -72,17 +94,17 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
|
|||
public void testAggregator() {
|
||||
SecurityPolicy sp = getSecurityPolicy();
|
||||
|
||||
// with no accounts, should return null
|
||||
assertNull(sp.computeAggregatePolicy());
|
||||
// with no accounts, should return empty set
|
||||
assertTrue(EMPTY_POLICY_SET.equals(sp.computeAggregatePolicy()));
|
||||
|
||||
// with accounts having no security, return null
|
||||
// with accounts having no security, empty set
|
||||
Account a1 = ProviderTestUtils.setupAccount("no-sec-1", false, mMockContext);
|
||||
a1.mSecurityFlags = 0;
|
||||
a1.save(mMockContext);
|
||||
Account a2 = ProviderTestUtils.setupAccount("no-sec-2", false, mMockContext);
|
||||
a2.mSecurityFlags = 0;
|
||||
a2.save(mMockContext);
|
||||
assertNull(sp.computeAggregatePolicy());
|
||||
assertTrue(EMPTY_POLICY_SET.equals(sp.computeAggregatePolicy()));
|
||||
|
||||
// with a single account in security mode, should return same security as in account
|
||||
PolicySet p3in = new PolicySet(10, PolicySet.PASSWORD_MODE_SIMPLE, 15, 16, false);
|
||||
|
@ -142,7 +164,7 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
|
|||
Account a2 = ProviderTestUtils.setupAccount("no-sec-2", false, mMockContext);
|
||||
a2.mSecurityFlags = 0;
|
||||
a2.save(mMockContext);
|
||||
assertNull(sp.computeAggregatePolicy());
|
||||
assertTrue(EMPTY_POLICY_SET.equals(sp.computeAggregatePolicy()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue