Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Backport: Handle "Allow non-provisionable devices" properly

* Backport from master branch
* Send policy key of "0" when validating; this gets us the policies
  even if "Allow..." is enabled (currently, we simply don't see the
  policies)
* If we don't support all of the policies, send back the response
  code indicating support for partial support.  If we get a positive
  response back, then we're good to go - the server allows devices
  with partial support.  Otherwise, we fail as we always have - with
  the toast indicating that the device doesn't support required
  policies
* Remove PolicySet.isSupported() and ensure proper field ranges
  within the constructor
* Update tests as appropriate

Bug: 2759782
Change-Id: Ida5663a9b35c75ecc61a5f442be0bd60b433cb73
  • Loading branch information...
commit c7109be0ea1a33eff438d5e8db270ed4076dd222 1 parent c339d25
Marc Blank authored cyanogen committed
View
79 src/com/android/email/SecurityPolicy.java
@@ -74,15 +74,6 @@
private static final int ACCOUNT_FLAGS_COLUMN_ID = 0;
private static final int ACCOUNT_FLAGS_COLUMN_FLAGS = 1;
- /**
- * 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
*/
@@ -189,33 +180,6 @@ private synchronized DevicePolicyManager getDPM() {
}
/**
- * API: Query used to determine if a given policy is "possible" (irrespective of current
- * device state. This is used when creating new accounts.
- *
- * 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;
- }
-
- /**
* API: Report that policies may have been updated due to rewriting values in an Account.
* @param accountId the account that has been updated, -1 if unknown/deleted
*/
@@ -435,7 +399,6 @@ public void remoteWipe() {
private static final int PASSWORD_LENGTH_MASK = 31;
private static final int PASSWORD_LENGTH_SHIFT = 0;
public static final int PASSWORD_LENGTH_MAX = 30;
- private static final int PASSWORD_LENGTH_EXCEEDED = 31;
// bits 5..8: password mode
private static final int PASSWORD_MODE_SHIFT = 5;
private static final int PASSWORD_MODE_MASK = 15 << PASSWORD_MODE_SHIFT;
@@ -453,11 +416,31 @@ public void remoteWipe() {
// bit 25: remote wipe capability required
private static final int REQUIRE_REMOTE_WIPE = 1 << 25;
- public final int mMinPasswordLength;
- public final int mPasswordMode;
- public final int mMaxPasswordFails;
- public final int mMaxScreenLockTime;
- public final boolean mRequireRemoteWipe;
+ /*package*/ final int mMinPasswordLength;
+ /*package*/ final int mPasswordMode;
+ /*package*/ final int mMaxPasswordFails;
+ /*package*/ final int mMaxScreenLockTime;
+ /*package*/ final boolean mRequireRemoteWipe;
+
+ public int getMinPasswordLength() {
+ return mMinPasswordLength;
+ }
+
+ public int getPasswordMode() {
+ return mPasswordMode;
+ }
+
+ public int getMaxPasswordFails() {
+ return mMaxPasswordFails;
+ }
+
+ public int getMaxScreenLockTime() {
+ return mMaxScreenLockTime;
+ }
+
+ public boolean isRequireRemoteWipe() {
+ return mRequireRemoteWipe;
+ }
/**
* Create from raw values.
@@ -470,15 +453,17 @@ public void remoteWipe() {
*/
public PolicySet(int minPasswordLength, int passwordMode, int maxPasswordFails,
int maxScreenLockTime, boolean requireRemoteWipe) throws IllegalArgumentException {
- // This value has a hard limit which cannot be supported if exceeded. Setting the
- // exceeded value will force isSupported() to return false.
+ // Check against hard limits
+ // EAS doesn't generate values outside these limits anyway
if (minPasswordLength > PASSWORD_LENGTH_MAX) {
- minPasswordLength = PASSWORD_LENGTH_EXCEEDED;
+ throw new IllegalArgumentException("password length");
}
- if (passwordMode < PASSWORD_MODE_NONE
- || passwordMode > PASSWORD_MODE_STRONG) {
+ if (passwordMode < PASSWORD_MODE_NONE || passwordMode > PASSWORD_MODE_STRONG) {
throw new IllegalArgumentException("password mode");
}
+ if (maxScreenLockTime > SCREEN_LOCK_TIME_MAX) {
+ throw new IllegalArgumentException("screen lock time");
+ }
// This value can be reduced (which actually increases security) if necessary
if (maxPasswordFails > PASSWORD_MAX_FAILS_MAX) {
maxPasswordFails = PASSWORD_MAX_FAILS_MAX;
View
69 src/com/android/exchange/EasSyncService.java
@@ -172,12 +172,17 @@
// MSFT's custom HTTP result code indicating the need to provision
static private final int HTTP_NEED_PROVISIONING = 449;
+ // The EAS protocol Provision status for "we implement all of the policies"
+ static private final String PROVISION_STATUS_OK = "1";
+ // The EAS protocol Provision status meaning "we partially implement the policies"
+ static private final String PROVISION_STATUS_PARTIAL = "2";
+
// Reasonable default
public String mProtocolVersion = Eas.DEFAULT_PROTOCOL_VERSION;
public Double mProtocolVersionDouble;
protected String mDeviceId = null;
- private String mDeviceType = "Android";
- private String mAuthString = null;
+ /*package*/ String mDeviceType = "Android";
+ /*package*/ String mAuthString = null;
private String mCmdString = null;
public String mHostAddress;
public String mUserName;
@@ -775,8 +780,7 @@ void parseAutodiscover(XmlPullParser parser, HostAuth hostAuth)
* TODO: make watchdog actually work (it doesn't understand our service w/Mailbox == 0)
* TODO: figure out why sendHttpClientPost() hangs - possibly pool exhaustion
*/
- static public GalResult searchGal(Context context, long accountId, String filter)
- {
+ static public GalResult searchGal(Context context, long accountId, String filter) {
Account acct = SyncManager.getAccountById(accountId);
if (acct != null) {
HostAuth ha = HostAuth.restoreHostAuthWithId(context, acct.mHostAuthKeyRecv);
@@ -1102,18 +1106,21 @@ private String makeUriString(String cmd, String extra) throws IOException {
* @param method the method we are going to send
* @param usePolicyKey whether or not a policy key should be sent in the headers
*/
- private void setHeaders(HttpRequestBase method, boolean usePolicyKey) {
+ /*package*/ void setHeaders(HttpRequestBase method, boolean usePolicyKey) {
method.setHeader("Authorization", mAuthString);
method.setHeader("MS-ASProtocolVersion", mProtocolVersion);
method.setHeader("Connection", "keep-alive");
method.setHeader("User-Agent", mDeviceType + '/' + Eas.VERSION);
- if (usePolicyKey && (mAccount != null)) {
- String key = mAccount.mSecuritySyncKey;
- if (key == null || key.length() == 0) {
- return;
- }
- if (Eas.PARSER_LOG) {
- userLog("Policy key: " , key);
+ if (usePolicyKey) {
+ // If there's an account in existence, use its key; otherwise (we're creating the
+ // account), send "0". The server will respond with code 449 if there are policies
+ // to be enforced
+ String key = "0";
+ if (mAccount != null) {
+ String accountKey = mAccount.mSecuritySyncKey;
+ if (!TextUtils.isEmpty(accountKey)) {
+ key = accountKey;
+ }
}
method.setHeader("X-MS-PolicyKey", key);
}
@@ -1280,7 +1287,7 @@ private boolean tryProvision() throws IOException {
} else if (sp.isActive(ps)) {
// See if the required policies are in force; if they are, acknowledge the policies
// to the server and get the final policy key
- String policyKey = acknowledgeProvision(pp.getPolicyKey());
+ String policyKey = acknowledgeProvision(pp.getPolicyKey(), PROVISION_STATUS_OK);
if (policyKey != null) {
// Write the final policy key to the Account and say we've been successful
ps.writeAccount(mAccount, policyKey, true, mContext);
@@ -1317,15 +1324,20 @@ private ProvisionParser canProvision() throws IOException {
InputStream is = resp.getEntity().getContent();
ProvisionParser pp = new ProvisionParser(is, this);
if (pp.parse()) {
- // If true, we received policies from the server; see if they are supported by
- // the framework; if so, return the ProvisionParser containing the policy set and
- // temporary key
- PolicySet ps = pp.getPolicySet();
- // The PolicySet can be null if there are policies we don't know about (e.g. ones
- // from Exchange 12.1) If we have a PolicySet, then we ask whether the device can
- // support the actual parameters of those policies.
- if ((ps != null) && SecurityPolicy.getInstance(mContext).isSupported(ps)) {
+ // The PolicySet in the ProvisionParser will have the requirements for all KNOWN
+ // policies. If others are required, hasSupportablePolicySet will be false
+ if (pp.hasSupportablePolicySet()) {
+ // If the policies are supportable (in this context, meaning that there are no
+ // completely unimplemented policies required), just return the parser itself
return pp;
+ } else {
+ // Try to acknowledge using the "partial" status (i.e. we can partially
+ // accommodate the required policies). The server will agree to this if the
+ // "allow non-provisionable devices" setting is enabled on the server
+ String policyKey = acknowledgeProvision(pp.getPolicyKey(),
+ PROVISION_STATUS_PARTIAL);
+ // Return either the parser (success) or null (failure)
+ return (policyKey != null) ? pp : null;
}
}
}
@@ -1341,14 +1353,15 @@ private ProvisionParser canProvision() throws IOException {
* @throws IOException
*/
private void acknowledgeRemoteWipe(String tempKey) throws IOException {
- acknowledgeProvisionImpl(tempKey, true);
+ acknowledgeProvisionImpl(tempKey, PROVISION_STATUS_OK, true);
}
- private String acknowledgeProvision(String tempKey) throws IOException {
- return acknowledgeProvisionImpl(tempKey, false);
+ private String acknowledgeProvision(String tempKey, String result) throws IOException {
+ return acknowledgeProvisionImpl(tempKey, result, false);
}
- private String acknowledgeProvisionImpl(String tempKey, boolean remoteWipe) throws IOException {
+ private String acknowledgeProvisionImpl(String tempKey, String status,
+ boolean remoteWipe) throws IOException {
Serializer s = new Serializer();
s.start(Tags.PROVISION_PROVISION).start(Tags.PROVISION_POLICIES);
s.start(Tags.PROVISION_POLICY);
@@ -1357,11 +1370,11 @@ private String acknowledgeProvisionImpl(String tempKey, boolean remoteWipe) thro
s.data(Tags.PROVISION_POLICY_TYPE, getPolicyType());
s.data(Tags.PROVISION_POLICY_KEY, tempKey);
- s.data(Tags.PROVISION_STATUS, "1");
+ s.data(Tags.PROVISION_STATUS, status);
s.end().end(); // PROVISION_POLICY, PROVISION_POLICIES
if (remoteWipe) {
s.start(Tags.PROVISION_REMOTE_WIPE);
- s.data(Tags.PROVISION_STATUS, "1");
+ s.data(Tags.PROVISION_STATUS, PROVISION_STATUS_OK);
s.end();
}
s.end().done(); // PROVISION_PROVISION
@@ -1371,7 +1384,7 @@ private String acknowledgeProvisionImpl(String tempKey, boolean remoteWipe) thro
InputStream is = resp.getEntity().getContent();
ProvisionParser pp = new ProvisionParser(is, this);
if (pp.parse()) {
- // Return the final polic key from the ProvisionParser
+ // Return the final policy key from the ProvisionParser
return pp.getPolicyKey();
}
}
View
25 src/com/android/exchange/adapter/ProvisionParser.java
@@ -37,11 +37,11 @@
PolicySet mPolicySet = null;
String mPolicyKey = null;
boolean mRemoteWipe = false;
+ boolean mIsSupportable = true;
public ProvisionParser(InputStream in, EasSyncService service) throws IOException {
super(in);
mService = service;
- setDebug(true);
}
public PolicySet getPolicySet() {
@@ -56,12 +56,16 @@ public boolean getRemoteWipe() {
return mRemoteWipe;
}
- public void parseProvisionDocWbxml() throws IOException {
+ public boolean hasSupportablePolicySet() {
+ return (mPolicySet != null) && mIsSupportable;
+ }
+
+ private void parseProvisionDocWbxml() throws IOException {
int minPasswordLength = 0;
int passwordMode = PolicySet.PASSWORD_MODE_NONE;
int maxPasswordFails = 0;
int maxScreenLockTime = 0;
- boolean canSupport = true;
+ boolean supported = true;
while (nextTag(Tags.PROVISION_EAS_PROVISION_DOC) != END) {
switch (tag) {
@@ -95,7 +99,7 @@ public void parseProvisionDocWbxml() throws IOException {
// The following policy, if false, can't be supported at the moment
case Tags.PROVISION_ATTACHMENTS_ENABLED:
if (getValueInt() == 0) {
- canSupport = false;
+ supported = false;
}
break;
// The following policies, if true, can't be supported at the moment
@@ -105,18 +109,21 @@ public void parseProvisionDocWbxml() throws IOException {
case Tags.PROVISION_DEVICE_PASSWORD_HISTORY:
case Tags.PROVISION_MAX_ATTACHMENT_SIZE:
if (getValueInt() == 1) {
- canSupport = false;
+ supported = false;
}
break;
default:
skipTag();
}
+
+ if (!supported) {
+ log("Policy not supported: " + tag);
+ mIsSupportable = false;
+ }
}
- if (canSupport) {
- mPolicySet = new SecurityPolicy.PolicySet(minPasswordLength, passwordMode,
+ mPolicySet = new SecurityPolicy.PolicySet(minPasswordLength, passwordMode,
maxPasswordFails, maxScreenLockTime, true);
- }
}
class ShadowPolicySet {
@@ -318,10 +325,10 @@ public boolean parse() throws IOException {
case Tags.PROVISION_STATUS:
int status = getValueInt();
mService.userLog("Provision status: ", status);
+ res = (status == 1);
break;
case Tags.PROVISION_POLICIES:
parsePolicies();
- res = (mPolicySet != null) || (mPolicyKey != null);
break;
case Tags.PROVISION_REMOTE_WIPE:
// Indicate remote wipe command received
View
42 tests/src/com/android/email/SecurityPolicyTests.java
@@ -88,6 +88,27 @@ private SecurityPolicy getSecurityPolicy() {
return sp;
}
+ public void testPolicySetConstructor() {
+ // We know that EMPTY_POLICY_SET doesn't generate an Exception or we wouldn't be here
+ // Try some illegal parameters
+ try {
+ new PolicySet(100, PolicySet.PASSWORD_MODE_NONE, 0, 0, false);
+ fail("Too-long password allowed");
+ } catch (IllegalArgumentException e) {
+ }
+ try {
+ new PolicySet(0, PolicySet.PASSWORD_MODE_STRONG + 1, 0, 0, false);
+ fail("Illegal password mode allowed");
+ } catch (IllegalArgumentException e) {
+ }
+ try {
+ new PolicySet(0, PolicySet.PASSWORD_MODE_NONE, 0,
+ PolicySet.SCREEN_LOCK_TIME_MAX + 1, false);
+ fail("Too-long screen lock time allowed");
+ } catch (IllegalArgumentException e) {
+ }
+ }
+
/**
* Test business logic of aggregating accounts with policies
*/
@@ -215,27 +236,6 @@ public void testFieldIsolation() {
}
/**
- * Test creation of policies with unsupported ranges
- */
- @SmallTest
- public void testFieldRanges() {
- SecurityPolicy sp = getSecurityPolicy();
- // Overlong password length cannot be supported
- PolicySet p = new PolicySet(PolicySet.PASSWORD_LENGTH_MAX + 1, 0, 0, 0, false);
- assertFalse(sp.isSupported(p));
-
- // Too many wipes before reboot can be supported (by reducing to the max)
- p = new PolicySet(0, 0, PolicySet.PASSWORD_MAX_FAILS_MAX + 1, 0, false);
- assertTrue(sp.isSupported(p));
- assertEquals(PolicySet.PASSWORD_MAX_FAILS_MAX, p.mMaxPasswordFails);
-
- // Too long lock time can be supported (by reducing to the max)
- p = new PolicySet(0, 0, 0, PolicySet.SCREEN_LOCK_TIME_MAX + 1, false);
- assertTrue(sp.isSupported(p));
- assertEquals(PolicySet.SCREEN_LOCK_TIME_MAX, p.mMaxScreenLockTime);
- }
-
- /**
* Test encoding into an Account and out again
*/
@SmallTest
View
43 tests/src/com/android/exchange/EasSyncServiceTests.java
@@ -17,6 +17,12 @@
package com.android.exchange;
+import com.android.email.provider.EmailContent.Account;
+
+import org.apache.http.Header;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpRequestBase;
+
import android.content.Context;
import android.test.AndroidTestCase;
@@ -119,4 +125,41 @@ public void testResetHeartbeats() {
assertEquals(100, svc.mPingForceHeartbeat);
assertFalse(svc.mPingHeartbeatDropped);
}
+
+ public void testAddHeaders() {
+ HttpRequestBase method = new HttpPost();
+ EasSyncService svc = new EasSyncService();
+ svc.mAuthString = "auth";
+ svc.mProtocolVersion = "12.1";
+ svc.mDeviceType = "android";
+ svc.mAccount = null;
+ // With second argument false, there should be no header
+ svc.setHeaders(method, false);
+ Header[] headers = method.getHeaders("X-MS-PolicyKey");
+ assertEquals(0, headers.length);
+ // With second argument true, there should always be a header
+ // The value will be "0" without an account
+ method.removeHeaders("X-MS-PolicyKey");
+ svc.setHeaders(method, true);
+ headers = method.getHeaders("X-MS-PolicyKey");
+ assertEquals(1, headers.length);
+ assertEquals("0", headers[0].getValue());
+ // With an account, but null security key, the header's value should be "0"
+ Account account = new Account();
+ account.mSecuritySyncKey = null;
+ svc.mAccount = account;
+ method.removeHeaders("X-MS-PolicyKey");
+ svc.setHeaders(method, true);
+ headers = method.getHeaders("X-MS-PolicyKey");
+ assertEquals(1, headers.length);
+ assertEquals("0", headers[0].getValue());
+ // With an account and security key, the header's value should be the security key
+ account.mSecuritySyncKey = "key";
+ svc.mAccount = account;
+ method.removeHeaders("X-MS-PolicyKey");
+ svc.setHeaders(method, true);
+ headers = method.getHeaders("X-MS-PolicyKey");
+ assertEquals(1, headers.length);
+ assertEquals("key", headers[0].getValue());
+ }
}
View
22 tests/src/com/android/exchange/adapter/ProvisionParserTests.java
@@ -113,11 +113,11 @@ public void testWapProvisionParser1() throws IOException {
PolicySet ps = parser.getPolicySet();
assertNotNull(ps);
// Check the settings to make sure they were parsed correctly
- assertEquals(5*60, ps.mMaxScreenLockTime); // Screen lock time is in seconds
- assertEquals(8, ps.mMinPasswordLength);
- assertEquals(PolicySet.PASSWORD_MODE_STRONG, ps.mPasswordMode);
- assertEquals(20, ps.mMaxPasswordFails);
- assertTrue(ps.mRequireRemoteWipe);
+ assertEquals(5*60, ps.getMaxScreenLockTime()); // Screen lock time is in seconds
+ assertEquals(8, ps.getMinPasswordLength());
+ assertEquals(PolicySet.PASSWORD_MODE_STRONG, ps.getPasswordMode());
+ assertEquals(20, ps.getMaxPasswordFails());
+ assertTrue(ps.isRequireRemoteWipe());
}
public void testWapProvisionParser2() throws IOException {
@@ -126,7 +126,7 @@ public void testWapProvisionParser2() throws IOException {
PolicySet ps = parser.getPolicySet();
assertNotNull(ps);
// Password should be set to none; others are ignored in this case.
- assertEquals(PolicySet.PASSWORD_MODE_NONE, ps.mPasswordMode);
+ assertEquals(PolicySet.PASSWORD_MODE_NONE, ps.getPasswordMode());
}
public void testWapProvisionParser3() throws IOException {
@@ -135,10 +135,10 @@ public void testWapProvisionParser3() throws IOException {
PolicySet ps = parser.getPolicySet();
assertNotNull(ps);
// Password should be set to simple
- assertEquals(2*60, ps.mMaxScreenLockTime); // Screen lock time is in seconds
- assertEquals(4, ps.mMinPasswordLength);
- assertEquals(PolicySet.PASSWORD_MODE_SIMPLE, ps.mPasswordMode);
- assertEquals(5, ps.mMaxPasswordFails);
- assertTrue(ps.mRequireRemoteWipe);
+ assertEquals(2*60, ps.getMaxScreenLockTime()); // Screen lock time is in seconds
+ assertEquals(4, ps.getMinPasswordLength());
+ assertEquals(PolicySet.PASSWORD_MODE_SIMPLE, ps.getPasswordMode());
+ assertEquals(5, ps.getMaxPasswordFails());
+ assertTrue(ps.isRequireRemoteWipe());
}
}
Please sign in to comment.
Something went wrong with that request. Please try again.