From 5f9e6f37b276fac7be6db982c9cb7fbd93150c4a Mon Sep 17 00:00:00 2001 From: Andrew Scull Date: Tue, 2 Aug 2016 14:22:17 +0100 Subject: [PATCH] Refactor password metric computation. This unifies previously duplicated code. Bug: 30558331 Test: runtest -x frameworks/base/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java Change-Id: I37678b83a03d4f05b9894d4b00e9a741d1a8df27 --- Android.mk | 1 + .../app/admin/DevicePolicyManager.java | 7 +- .../app/admin/IDevicePolicyManager.aidl | 5 +- .../android/app/admin/PasswordMetrics.aidl | 20 ++ .../android/app/admin/PasswordMetrics.java | 238 ++++++++++++ .../internal/widget/LockPatternUtils.java | 136 +------ .../app/admin/PasswordMetricsTest.java | 138 +++++++ .../DevicePolicyManagerService.java | 339 ++++++++---------- 8 files changed, 557 insertions(+), 327 deletions(-) create mode 100644 core/java/android/app/admin/PasswordMetrics.aidl create mode 100644 core/java/android/app/admin/PasswordMetrics.java create mode 100644 core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java diff --git a/Android.mk b/Android.mk index 4fea0d402d58c..31b1276ea71a6 100644 --- a/Android.mk +++ b/Android.mk @@ -574,6 +574,7 @@ aidl_files := \ frameworks/base/core/java/android/accounts/AuthenticatorDescription.aidl \ frameworks/base/core/java/android/accounts/Account.aidl \ frameworks/base/core/java/android/app/admin/SystemUpdatePolicy.aidl \ + frameworks/base/core/java/android/app/admin/PasswordMetrics.aidl \ frameworks/base/core/java/android/print/PrintDocumentInfo.aidl \ frameworks/base/core/java/android/print/PageRange.aidl \ frameworks/base/core/java/android/print/PrintAttributes.aidl \ diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 4ddcfe546f95c..9ce9decaab35e 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -26,6 +26,7 @@ import android.annotation.SystemApi; import android.annotation.UserIdInt; import android.annotation.WorkerThread; import android.app.Activity; +import android.app.admin.PasswordMetrics; import android.app.admin.SecurityLog.SecurityEvent; import android.content.ComponentName; import android.content.Context; @@ -3523,12 +3524,10 @@ public class DevicePolicyManager { /** * @hide */ - public void setActivePasswordState(int quality, int length, int letters, int uppercase, - int lowercase, int numbers, int symbols, int nonletter, int userHandle) { + public void setActivePasswordState(PasswordMetrics metrics, int userHandle) { if (mService != null) { try { - mService.setActivePasswordState(quality, length, letters, uppercase, lowercase, - numbers, symbols, nonletter, userHandle); + mService.setActivePasswordState(metrics, userHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 8c376bbb670fa..22219d789799d 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -18,6 +18,7 @@ package android.app.admin; import android.app.admin.SystemUpdatePolicy; +import android.app.admin.PasswordMetrics; import android.content.ComponentName; import android.content.Intent; import android.content.IntentFilter; @@ -29,6 +30,7 @@ import android.os.Bundle; import android.os.PersistableBundle; import android.os.RemoteCallback; import android.os.UserHandle; + import java.util.List; /** @@ -117,8 +119,7 @@ interface IDevicePolicyManager { void forceRemoveActiveAdmin(in ComponentName policyReceiver, int userHandle); boolean hasGrantedPolicy(in ComponentName policyReceiver, int usesPolicy, int userHandle); - void setActivePasswordState(int quality, int length, int letters, int uppercase, int lowercase, - int numbers, int symbols, int nonletter, int userHandle); + void setActivePasswordState(in PasswordMetrics metrics, int userHandle); void reportFailedPasswordAttempt(int userHandle); void reportSuccessfulPasswordAttempt(int userHandle); void reportFailedFingerprintAttempt(int userHandle); diff --git a/core/java/android/app/admin/PasswordMetrics.aidl b/core/java/android/app/admin/PasswordMetrics.aidl new file mode 100644 index 0000000000000..90d7c6974f1ba --- /dev/null +++ b/core/java/android/app/admin/PasswordMetrics.aidl @@ -0,0 +1,20 @@ +/* +** +** Copyright 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 android.app.admin; + +parcelable PasswordMetrics; diff --git a/core/java/android/app/admin/PasswordMetrics.java b/core/java/android/app/admin/PasswordMetrics.java new file mode 100644 index 0000000000000..ea3f560d02db2 --- /dev/null +++ b/core/java/android/app/admin/PasswordMetrics.java @@ -0,0 +1,238 @@ +/* + * 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 android.app.admin; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.app.admin.DevicePolicyManager; +import android.os.Parcelable; +import android.os.Parcel; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.io.IOException; + +/** + * A class that represents the metrics of a password that are used to decide whether or not a + * password meets the requirements. + * + * {@hide} + */ +public class PasswordMetrics implements Parcelable { + // Maximum allowed number of repeated or ordered characters in a sequence before we'll + // consider it a complex PIN/password. + public static final int MAX_ALLOWED_SEQUENCE = 3; + + public int quality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; + public int length = 0; + public int letters = 0; + public int upperCase = 0; + public int lowerCase = 0; + public int numeric = 0; + public int symbols = 0; + public int nonLetter = 0; + + public PasswordMetrics() {} + + public PasswordMetrics(int quality, int length) { + this.quality = quality; + this.length = length; + } + + public PasswordMetrics(int quality, int length, int letters, int upperCase, int lowerCase, + int numeric, int symbols, int nonLetter) { + this(quality, length); + this.letters = letters; + this.upperCase = upperCase; + this.lowerCase = lowerCase; + this.numeric = numeric; + this.symbols = symbols; + this.nonLetter = nonLetter; + } + + private PasswordMetrics(Parcel in) { + quality = in.readInt(); + length = in.readInt(); + letters = in.readInt(); + upperCase = in.readInt(); + lowerCase = in.readInt(); + numeric = in.readInt(); + symbols = in.readInt(); + nonLetter = in.readInt(); + } + + public boolean isDefault() { + return quality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED + && length == 0 && letters == 0 && upperCase == 0 && lowerCase == 0 + && numeric == 0 && symbols == 0 && nonLetter == 0; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(quality); + dest.writeInt(length); + dest.writeInt(letters); + dest.writeInt(upperCase); + dest.writeInt(lowerCase); + dest.writeInt(numeric); + dest.writeInt(symbols); + dest.writeInt(nonLetter); + } + + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + public PasswordMetrics createFromParcel(Parcel in) { + return new PasswordMetrics(in); + } + + public PasswordMetrics[] newArray(int size) { + return new PasswordMetrics[size]; + } + }; + + public static PasswordMetrics computeForPassword(@NonNull String password) { + // Analyse the characters used + int letters = 0; + int upperCase = 0; + int lowerCase = 0; + int numeric = 0; + int symbols = 0; + int nonLetter = 0; + final int length = password.length(); + for (int i = 0; i < length; i++) { + switch (categoryChar(password.charAt(i))) { + case CHAR_LOWER_CASE: + letters++; + lowerCase++; + break; + case CHAR_UPPER_CASE: + letters++; + upperCase++; + break; + case CHAR_DIGIT: + numeric++; + nonLetter++; + break; + case CHAR_SYMBOL: + symbols++; + nonLetter++; + break; + } + } + + // Determine the quality of the password + final boolean hasNumeric = numeric > 0; + final boolean hasNonNumeric = (letters + symbols) > 0; + final int quality; + if (hasNonNumeric && hasNumeric) { + quality = DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC; + } else if (hasNonNumeric) { + quality = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC; + } else if (hasNumeric) { + quality = maxLengthSequence(password) > MAX_ALLOWED_SEQUENCE + ? DevicePolicyManager.PASSWORD_QUALITY_NUMERIC + : DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX; + } else { + quality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; + } + + return new PasswordMetrics( + quality, length, letters, upperCase, lowerCase, numeric, symbols, nonLetter); + } + + /* + * Returns the maximum length of a sequential characters. A sequence is defined as + * monotonically increasing characters with a constant interval or the same character repeated. + * + * For example: + * maxLengthSequence("1234") == 4 + * maxLengthSequence("13579") == 5 + * maxLengthSequence("1234abc") == 4 + * maxLengthSequence("aabc") == 3 + * maxLengthSequence("qwertyuio") == 1 + * maxLengthSequence("@ABC") == 3 + * maxLengthSequence(";;;;") == 4 (anything that repeats) + * maxLengthSequence(":;<=>") == 1 (ordered, but not composed of alphas or digits) + * + * @param string the pass + * @return the number of sequential letters or digits + */ + public static int maxLengthSequence(@NonNull String string) { + if (string.length() == 0) return 0; + char previousChar = string.charAt(0); + @CharacterCatagory int category = categoryChar(previousChar); //current sequence category + int diff = 0; //difference between two consecutive characters + boolean hasDiff = false; //if we are currently targeting a sequence + int maxLength = 0; //maximum length of a sequence already found + int startSequence = 0; //where the current sequence started + for (int current = 1; current < string.length(); current++) { + char currentChar = string.charAt(current); + @CharacterCatagory int categoryCurrent = categoryChar(currentChar); + int currentDiff = (int) currentChar - (int) previousChar; + if (categoryCurrent != category || Math.abs(currentDiff) > maxDiffCategory(category)) { + maxLength = Math.max(maxLength, current - startSequence); + startSequence = current; + hasDiff = false; + category = categoryCurrent; + } + else { + if(hasDiff && currentDiff != diff) { + maxLength = Math.max(maxLength, current - startSequence); + startSequence = current - 1; + } + diff = currentDiff; + hasDiff = true; + } + previousChar = currentChar; + } + maxLength = Math.max(maxLength, string.length() - startSequence); + return maxLength; + } + + @Retention(RetentionPolicy.SOURCE) + @IntDef({CHAR_UPPER_CASE, CHAR_LOWER_CASE, CHAR_DIGIT, CHAR_SYMBOL}) + private @interface CharacterCatagory {} + private static final int CHAR_LOWER_CASE = 0; + private static final int CHAR_UPPER_CASE = 1; + private static final int CHAR_DIGIT = 2; + private static final int CHAR_SYMBOL = 3; + + @CharacterCatagory + private static int categoryChar(char c) { + if ('a' <= c && c <= 'z') return CHAR_LOWER_CASE; + if ('A' <= c && c <= 'Z') return CHAR_UPPER_CASE; + if ('0' <= c && c <= '9') return CHAR_DIGIT; + return CHAR_SYMBOL; + } + + private static int maxDiffCategory(@CharacterCatagory int category) { + switch (category) { + case CHAR_LOWER_CASE: + case CHAR_UPPER_CASE: + return 1; + case CHAR_DIGIT: + return 10; + default: + return 0; + } + } +} diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 0e07bf8c47a82..71252fb06f2fd 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -19,6 +19,7 @@ package com.android.internal.widget; import android.annotation.IntDef; import android.annotation.Nullable; import android.app.admin.DevicePolicyManager; +import android.app.admin.PasswordMetrics; import android.app.trust.IStrongAuthTracker; import android.app.trust.TrustManager; import android.content.ComponentName; @@ -137,10 +138,6 @@ public class LockPatternUtils { private static final String ENABLED_TRUST_AGENTS = "lockscreen.enabledtrustagents"; private static final String IS_TRUST_USUALLY_MANAGED = "lockscreen.istrustusuallymanaged"; - // Maximum allowed number of repeated or ordered characters in a sequence before we'll - // consider it a complex PIN/password. - public static final int MAX_ALLOWED_SEQUENCE = 3; - public static final String PROFILE_KEY_NAME_ENCRYPT = "profile_key_name_encrypt_"; public static final String PROFILE_KEY_NAME_DECRYPT = "profile_key_name_decrypt_"; @@ -593,8 +590,7 @@ public class LockPatternUtils { setCredentialRequiredToDecrypt(false); } - getDevicePolicyManager().setActivePasswordState( - DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, 0, 0, 0, 0, 0, userHandle); + getDevicePolicyManager().setActivePasswordState(new PasswordMetrics(), userHandle); onAfterChangingPassword(userHandle); } @@ -665,8 +661,8 @@ public class LockPatternUtils { setBoolean(PATTERN_EVER_CHOSEN_KEY, true, userId); setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, userId); - dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, - pattern.size(), 0, 0, 0, 0, 0, 0, userId); + dpm.setActivePasswordState(new PasswordMetrics( + DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, pattern.size()), userId); onAfterChangingPassword(userId); } catch (RemoteException re) { Log.e(TAG, "Couldn't save lock pattern " + re); @@ -736,96 +732,6 @@ public class LockPatternUtils { return getDeviceOwnerInfo() != null; } - /** - * Compute the password quality from the given password string. - */ - static public int computePasswordQuality(String password) { - boolean hasDigit = false; - boolean hasNonDigit = false; - final int len = password.length(); - for (int i = 0; i < len; i++) { - if (Character.isDigit(password.charAt(i))) { - hasDigit = true; - } else { - hasNonDigit = true; - } - } - - if (hasNonDigit && hasDigit) { - return DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC; - } - if (hasNonDigit) { - return DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC; - } - if (hasDigit) { - return maxLengthSequence(password) > MAX_ALLOWED_SEQUENCE - ? DevicePolicyManager.PASSWORD_QUALITY_NUMERIC - : DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX; - } - return DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; - } - - private static int categoryChar(char c) { - if ('a' <= c && c <= 'z') return 0; - if ('A' <= c && c <= 'Z') return 1; - if ('0' <= c && c <= '9') return 2; - return 3; - } - - private static int maxDiffCategory(int category) { - if (category == 0 || category == 1) return 1; - else if (category == 2) return 10; - return 0; - } - - /* - * Returns the maximum length of a sequential characters. A sequence is defined as - * monotonically increasing characters with a constant interval or the same character repeated. - * - * For example: - * maxLengthSequence("1234") == 4 - * maxLengthSequence("1234abc") == 4 - * maxLengthSequence("aabc") == 3 - * maxLengthSequence("qwertyuio") == 1 - * maxLengthSequence("@ABC") == 3 - * maxLengthSequence(";;;;") == 4 (anything that repeats) - * maxLengthSequence(":;<=>") == 1 (ordered, but not composed of alphas or digits) - * - * @param string the pass - * @return the number of sequential letters or digits - */ - public static int maxLengthSequence(String string) { - if (string.length() == 0) return 0; - char previousChar = string.charAt(0); - int category = categoryChar(previousChar); //current category of the sequence - int diff = 0; //difference between two consecutive characters - boolean hasDiff = false; //if we are currently targeting a sequence - int maxLength = 0; //maximum length of a sequence already found - int startSequence = 0; //where the current sequence started - for (int current = 1; current < string.length(); current++) { - char currentChar = string.charAt(current); - int categoryCurrent = categoryChar(currentChar); - int currentDiff = (int) currentChar - (int) previousChar; - if (categoryCurrent != category || Math.abs(currentDiff) > maxDiffCategory(category)) { - maxLength = Math.max(maxLength, current - startSequence); - startSequence = current; - hasDiff = false; - category = categoryCurrent; - } - else { - if(hasDiff && currentDiff != diff) { - maxLength = Math.max(maxLength, current - startSequence); - startSequence = current - 1; - } - diff = currentDiff; - hasDiff = true; - } - previousChar = currentChar; - } - maxLength = Math.max(maxLength, string.length() - startSequence); - return maxLength; - } - /** Update the encryption password if it is enabled **/ private void updateEncryptionPassword(final int type, final String password) { if (!isDeviceEncryptionEnabled()) { @@ -871,7 +777,8 @@ public class LockPatternUtils { getLockSettings().setLockPassword(password, savedPassword, userHandle); getLockSettings().setSeparateProfileChallengeEnabled(userHandle, true, null); - int computedQuality = computePasswordQuality(password); + final PasswordMetrics metrics = PasswordMetrics.computeForPassword(password); + final int computedQuality = metrics.quality; // Update the device encryption password. if (userHandle == UserHandle.USER_SYSTEM @@ -891,36 +798,11 @@ public class LockPatternUtils { setLong(PASSWORD_TYPE_KEY, Math.max(quality, computedQuality), userHandle); if (computedQuality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) { - int letters = 0; - int uppercase = 0; - int lowercase = 0; - int numbers = 0; - int symbols = 0; - int nonletter = 0; - for (int i = 0; i < password.length(); i++) { - char c = password.charAt(i); - if (c >= 'A' && c <= 'Z') { - letters++; - uppercase++; - } else if (c >= 'a' && c <= 'z') { - letters++; - lowercase++; - } else if (c >= '0' && c <= '9') { - numbers++; - nonletter++; - } else { - symbols++; - nonletter++; - } - } - dpm.setActivePasswordState(Math.max(quality, computedQuality), - password.length(), letters, uppercase, lowercase, - numbers, symbols, nonletter, userHandle); + metrics.quality = Math.max(quality, metrics.quality); + dpm.setActivePasswordState(metrics, userHandle); } else { // The password is not anything. - dpm.setActivePasswordState( - DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, - 0, 0, 0, 0, 0, 0, 0, userHandle); + dpm.setActivePasswordState(new PasswordMetrics(), userHandle); } // Add the password to the password history. We assume all diff --git a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java new file mode 100644 index 0000000000000..e9e3a18e80b26 --- /dev/null +++ b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.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 android.app.admin; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import android.os.Parcel; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** Unit tests for {@link PasswordMetrics}. */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class PasswordMetricsTest { + + @Test + public void testIsDefault() { + final PasswordMetrics metrics = new PasswordMetrics(); + assertEquals(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, metrics.quality); + assertEquals(0, metrics.length); + assertEquals(0, metrics.letters); + assertEquals(0, metrics.upperCase); + assertEquals(0, metrics.lowerCase); + assertEquals(0, metrics.numeric); + assertEquals(0, metrics.symbols); + assertEquals(0, metrics.nonLetter); + assertTrue("default constructor does not produce default metrics", metrics.isDefault()); + } + + @Test + public void testIsNotDefault() { + final PasswordMetrics metrics = new PasswordMetrics( + DevicePolicyManager.PASSWORD_QUALITY_NUMERIC, 12); + assertFalse("non-default metrics are repoted as default", metrics.isDefault()); + } + + @Test + public void testComputeForEmptyPassword() { + final PasswordMetrics metrics = PasswordMetrics.computeForPassword(""); + assertTrue("empty password has default metrics", metrics.isDefault()); + } + + @Test + public void testParceling() { + final int quality = 0; + final int length = 1; + final int letters = 2; + final int upperCase = 3; + final int lowerCase = 4; + final int numeric = 5; + final int symbols = 6; + final int nonLetter = 7; + + final Parcel parcel = Parcel.obtain(); + final PasswordMetrics metrics; + try { + new PasswordMetrics( + quality, length, letters, upperCase, lowerCase, numeric, symbols, nonLetter) + .writeToParcel(parcel, 0); + parcel.setDataPosition(0); + metrics = PasswordMetrics.CREATOR.createFromParcel(parcel); + } finally { + parcel.recycle(); + } + + assertEquals(quality, metrics.quality); + assertEquals(length, metrics.length); + assertEquals(letters, metrics.letters); + assertEquals(upperCase, metrics.upperCase); + assertEquals(lowerCase, metrics.lowerCase); + assertEquals(numeric, metrics.numeric); + assertEquals(symbols, metrics.symbols); + assertEquals(nonLetter, metrics.nonLetter); + + } + + @Test + public void testComputeForPassword_metrics() { + final PasswordMetrics metrics = PasswordMetrics.computeForPassword("6B~0z1Z3*8A"); + assertEquals(11, metrics.length); + assertEquals(4, metrics.letters); + assertEquals(3, metrics.upperCase); + assertEquals(1, metrics.lowerCase); + assertEquals(5, metrics.numeric); + assertEquals(2, metrics.symbols); + assertEquals(7, metrics.nonLetter); + } + + @Test + public void testComputeForPassword_quality() { + assertEquals(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, + PasswordMetrics.computeForPassword("a1").quality); + assertEquals(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, + PasswordMetrics.computeForPassword("a").quality); + assertEquals(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, + PasswordMetrics.computeForPassword("*~&%$").quality); + assertEquals(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, + PasswordMetrics.computeForPassword("1").quality); + // contains a long sequence so isn't complex + assertEquals(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC, + PasswordMetrics.computeForPassword("1234").quality); + assertEquals(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, + PasswordMetrics.computeForPassword("").quality); + } + + @Test + public void testMaxLengthSequence() { + assertEquals(4, PasswordMetrics.maxLengthSequence("1234")); + assertEquals(5, PasswordMetrics.maxLengthSequence("13579")); + assertEquals(4, PasswordMetrics.maxLengthSequence("1234abd")); + assertEquals(3, PasswordMetrics.maxLengthSequence("aabc")); + assertEquals(1, PasswordMetrics.maxLengthSequence("qwertyuio")); + assertEquals(3, PasswordMetrics.maxLengthSequence("@ABC")); + // anything that repeats + assertEquals(4, PasswordMetrics.maxLengthSequence(";;;;")); + // ordered, but not composed of alphas or digits + assertEquals(1, PasswordMetrics.maxLengthSequence(":;<=>")); + } +} diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 5106c32b40468..eb85e899dcd82 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -51,6 +51,7 @@ import android.app.admin.DeviceAdminReceiver; import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManagerInternal; import android.app.admin.IDevicePolicyManager; +import android.app.admin.PasswordMetrics; import android.app.admin.SecurityLog; import android.app.admin.SecurityLog.SecurityEvent; import android.app.admin.SystemUpdatePolicy; @@ -425,14 +426,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public static class DevicePolicyData { - int mActivePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; - int mActivePasswordLength = 0; - int mActivePasswordUpperCase = 0; - int mActivePasswordLowerCase = 0; - int mActivePasswordLetters = 0; - int mActivePasswordNumeric = 0; - int mActivePasswordSymbols = 0; - int mActivePasswordNonLetter = 0; + @NonNull PasswordMetrics mActivePasswordMetrics = new PasswordMetrics(); int mFailedPasswordAttempts = 0; int mUserHandle; @@ -598,31 +592,23 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final DeviceAdminInfo info; - int passwordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; - - static final int DEF_MINIMUM_PASSWORD_LENGTH = 0; - int minimumPasswordLength = DEF_MINIMUM_PASSWORD_LENGTH; static final int DEF_PASSWORD_HISTORY_LENGTH = 0; int passwordHistoryLength = DEF_PASSWORD_HISTORY_LENGTH; - static final int DEF_MINIMUM_PASSWORD_UPPER_CASE = 0; - int minimumPasswordUpperCase = DEF_MINIMUM_PASSWORD_UPPER_CASE; - - static final int DEF_MINIMUM_PASSWORD_LOWER_CASE = 0; - int minimumPasswordLowerCase = DEF_MINIMUM_PASSWORD_LOWER_CASE; - + static final int DEF_MINIMUM_PASSWORD_LENGTH = 0; static final int DEF_MINIMUM_PASSWORD_LETTERS = 1; - int minimumPasswordLetters = DEF_MINIMUM_PASSWORD_LETTERS; - + static final int DEF_MINIMUM_PASSWORD_UPPER_CASE = 0; + static final int DEF_MINIMUM_PASSWORD_LOWER_CASE = 0; static final int DEF_MINIMUM_PASSWORD_NUMERIC = 1; - int minimumPasswordNumeric = DEF_MINIMUM_PASSWORD_NUMERIC; - static final int DEF_MINIMUM_PASSWORD_SYMBOLS = 1; - int minimumPasswordSymbols = DEF_MINIMUM_PASSWORD_SYMBOLS; - static final int DEF_MINIMUM_PASSWORD_NON_LETTER = 0; - int minimumPasswordNonLetter = DEF_MINIMUM_PASSWORD_NON_LETTER; + @NonNull + PasswordMetrics minimumPasswordMetrics = new PasswordMetrics( + DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, DEF_MINIMUM_PASSWORD_LENGTH, + DEF_MINIMUM_PASSWORD_LETTERS, DEF_MINIMUM_PASSWORD_UPPER_CASE, + DEF_MINIMUM_PASSWORD_LOWER_CASE, DEF_MINIMUM_PASSWORD_NUMERIC, + DEF_MINIMUM_PASSWORD_SYMBOLS, DEF_MINIMUM_PASSWORD_NON_LETTER); static final long DEF_MAXIMUM_TIME_TO_UNLOCK = 0; long maximumTimeToUnlock = DEF_MAXIMUM_TIME_TO_UNLOCK; @@ -728,13 +714,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { out.startTag(null, TAG_POLICIES); info.writePoliciesToXml(out); out.endTag(null, TAG_POLICIES); - if (passwordQuality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) { + if (minimumPasswordMetrics.quality + != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) { out.startTag(null, TAG_PASSWORD_QUALITY); - out.attribute(null, ATTR_VALUE, Integer.toString(passwordQuality)); + out.attribute(null, ATTR_VALUE, Integer.toString(minimumPasswordMetrics.quality)); out.endTag(null, TAG_PASSWORD_QUALITY); - if (minimumPasswordLength != DEF_MINIMUM_PASSWORD_LENGTH) { + if (minimumPasswordMetrics.length != DEF_MINIMUM_PASSWORD_LENGTH) { out.startTag(null, TAG_MIN_PASSWORD_LENGTH); - out.attribute(null, ATTR_VALUE, Integer.toString(minimumPasswordLength)); + out.attribute( + null, ATTR_VALUE, Integer.toString(minimumPasswordMetrics.length)); out.endTag(null, TAG_MIN_PASSWORD_LENGTH); } if(passwordHistoryLength != DEF_PASSWORD_HISTORY_LENGTH) { @@ -742,34 +730,40 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { out.attribute(null, ATTR_VALUE, Integer.toString(passwordHistoryLength)); out.endTag(null, TAG_PASSWORD_HISTORY_LENGTH); } - if (minimumPasswordUpperCase != DEF_MINIMUM_PASSWORD_UPPER_CASE) { + if (minimumPasswordMetrics.upperCase != DEF_MINIMUM_PASSWORD_UPPER_CASE) { out.startTag(null, TAG_MIN_PASSWORD_UPPERCASE); - out.attribute(null, ATTR_VALUE, Integer.toString(minimumPasswordUpperCase)); + out.attribute( + null, ATTR_VALUE, Integer.toString(minimumPasswordMetrics.upperCase)); out.endTag(null, TAG_MIN_PASSWORD_UPPERCASE); } - if (minimumPasswordLowerCase != DEF_MINIMUM_PASSWORD_LOWER_CASE) { + if (minimumPasswordMetrics.lowerCase != DEF_MINIMUM_PASSWORD_LOWER_CASE) { out.startTag(null, TAG_MIN_PASSWORD_LOWERCASE); - out.attribute(null, ATTR_VALUE, Integer.toString(minimumPasswordLowerCase)); + out.attribute( + null, ATTR_VALUE, Integer.toString(minimumPasswordMetrics.lowerCase)); out.endTag(null, TAG_MIN_PASSWORD_LOWERCASE); } - if (minimumPasswordLetters != DEF_MINIMUM_PASSWORD_LETTERS) { + if (minimumPasswordMetrics.letters != DEF_MINIMUM_PASSWORD_LETTERS) { out.startTag(null, TAG_MIN_PASSWORD_LETTERS); - out.attribute(null, ATTR_VALUE, Integer.toString(minimumPasswordLetters)); + out.attribute( + null, ATTR_VALUE, Integer.toString(minimumPasswordMetrics.letters)); out.endTag(null, TAG_MIN_PASSWORD_LETTERS); } - if (minimumPasswordNumeric != DEF_MINIMUM_PASSWORD_NUMERIC) { + if (minimumPasswordMetrics.numeric != DEF_MINIMUM_PASSWORD_NUMERIC) { out.startTag(null, TAG_MIN_PASSWORD_NUMERIC); - out.attribute(null, ATTR_VALUE, Integer.toString(minimumPasswordNumeric)); + out.attribute( + null, ATTR_VALUE, Integer.toString(minimumPasswordMetrics.numeric)); out.endTag(null, TAG_MIN_PASSWORD_NUMERIC); } - if (minimumPasswordSymbols != DEF_MINIMUM_PASSWORD_SYMBOLS) { + if (minimumPasswordMetrics.symbols != DEF_MINIMUM_PASSWORD_SYMBOLS) { out.startTag(null, TAG_MIN_PASSWORD_SYMBOLS); - out.attribute(null, ATTR_VALUE, Integer.toString(minimumPasswordSymbols)); + out.attribute( + null, ATTR_VALUE, Integer.toString(minimumPasswordMetrics.symbols)); out.endTag(null, TAG_MIN_PASSWORD_SYMBOLS); } - if (minimumPasswordNonLetter > DEF_MINIMUM_PASSWORD_NON_LETTER) { + if (minimumPasswordMetrics.nonLetter > DEF_MINIMUM_PASSWORD_NON_LETTER) { out.startTag(null, TAG_MIN_PASSWORD_NONLETTER); - out.attribute(null, ATTR_VALUE, Integer.toString(minimumPasswordNonLetter)); + out.attribute( + null, ATTR_VALUE, Integer.toString(minimumPasswordMetrics.nonLetter)); out.endTag(null, TAG_MIN_PASSWORD_NONLETTER); } } @@ -968,31 +962,31 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (TAG_POLICIES.equals(tag)) { info.readPoliciesFromXml(parser); } else if (TAG_PASSWORD_QUALITY.equals(tag)) { - passwordQuality = Integer.parseInt( + minimumPasswordMetrics.quality = Integer.parseInt( parser.getAttributeValue(null, ATTR_VALUE)); } else if (TAG_MIN_PASSWORD_LENGTH.equals(tag)) { - minimumPasswordLength = Integer.parseInt( + minimumPasswordMetrics.length = Integer.parseInt( parser.getAttributeValue(null, ATTR_VALUE)); } else if (TAG_PASSWORD_HISTORY_LENGTH.equals(tag)) { passwordHistoryLength = Integer.parseInt( parser.getAttributeValue(null, ATTR_VALUE)); } else if (TAG_MIN_PASSWORD_UPPERCASE.equals(tag)) { - minimumPasswordUpperCase = Integer.parseInt( + minimumPasswordMetrics.upperCase = Integer.parseInt( parser.getAttributeValue(null, ATTR_VALUE)); } else if (TAG_MIN_PASSWORD_LOWERCASE.equals(tag)) { - minimumPasswordLowerCase = Integer.parseInt( + minimumPasswordMetrics.lowerCase = Integer.parseInt( parser.getAttributeValue(null, ATTR_VALUE)); } else if (TAG_MIN_PASSWORD_LETTERS.equals(tag)) { - minimumPasswordLetters = Integer.parseInt( + minimumPasswordMetrics.letters = Integer.parseInt( parser.getAttributeValue(null, ATTR_VALUE)); } else if (TAG_MIN_PASSWORD_NUMERIC.equals(tag)) { - minimumPasswordNumeric = Integer.parseInt( + minimumPasswordMetrics.numeric = Integer.parseInt( parser.getAttributeValue(null, ATTR_VALUE)); } else if (TAG_MIN_PASSWORD_SYMBOLS.equals(tag)) { - minimumPasswordSymbols = Integer.parseInt( + minimumPasswordMetrics.symbols = Integer.parseInt( parser.getAttributeValue(null, ATTR_VALUE)); } else if (TAG_MIN_PASSWORD_NONLETTER.equals(tag)) { - minimumPasswordNonLetter = Integer.parseInt( + minimumPasswordMetrics.nonLetter = Integer.parseInt( parser.getAttributeValue(null, ATTR_VALUE)); } else if (TAG_MAX_TIME_TO_UNLOCK.equals(tag)) { maximumTimeToUnlock = Long.parseLong( @@ -1232,23 +1226,23 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } pw.print(prefix); pw.print("passwordQuality=0x"); - pw.println(Integer.toHexString(passwordQuality)); + pw.println(Integer.toHexString(minimumPasswordMetrics.quality)); pw.print(prefix); pw.print("minimumPasswordLength="); - pw.println(minimumPasswordLength); + pw.println(minimumPasswordMetrics.length); pw.print(prefix); pw.print("passwordHistoryLength="); pw.println(passwordHistoryLength); pw.print(prefix); pw.print("minimumPasswordUpperCase="); - pw.println(minimumPasswordUpperCase); + pw.println(minimumPasswordMetrics.upperCase); pw.print(prefix); pw.print("minimumPasswordLowerCase="); - pw.println(minimumPasswordLowerCase); + pw.println(minimumPasswordMetrics.lowerCase); pw.print(prefix); pw.print("minimumPasswordLetters="); - pw.println(minimumPasswordLetters); + pw.println(minimumPasswordMetrics.letters); pw.print(prefix); pw.print("minimumPasswordNumeric="); - pw.println(minimumPasswordNumeric); + pw.println(minimumPasswordMetrics.numeric); pw.print(prefix); pw.print("minimumPasswordSymbols="); - pw.println(minimumPasswordSymbols); + pw.println(minimumPasswordMetrics.symbols); pw.print(prefix); pw.print("minimumPasswordNonLetter="); - pw.println(minimumPasswordNonLetter); + pw.println(minimumPasswordMetrics.nonLetter); pw.print(prefix); pw.print("maximumTimeToUnlock="); pw.println(maximumTimeToUnlock); pw.print(prefix); pw.print("strongAuthUnlockTimeout="); @@ -2281,20 +2275,17 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { out.endTag(null, "failed-password-attempts"); } - if (policy.mActivePasswordQuality != 0 || policy.mActivePasswordLength != 0 - || policy.mActivePasswordUpperCase != 0 || policy.mActivePasswordLowerCase != 0 - || policy.mActivePasswordLetters != 0 || policy.mActivePasswordNumeric != 0 - || policy.mActivePasswordSymbols != 0 || policy.mActivePasswordNonLetter != 0) { + final PasswordMetrics metrics = policy.mActivePasswordMetrics; + if (!metrics.isDefault()) { out.startTag(null, "active-password"); - out.attribute(null, "quality", Integer.toString(policy.mActivePasswordQuality)); - out.attribute(null, "length", Integer.toString(policy.mActivePasswordLength)); - out.attribute(null, "uppercase", Integer.toString(policy.mActivePasswordUpperCase)); - out.attribute(null, "lowercase", Integer.toString(policy.mActivePasswordLowerCase)); - out.attribute(null, "letters", Integer.toString(policy.mActivePasswordLetters)); - out.attribute(null, "numeric", Integer - .toString(policy.mActivePasswordNumeric)); - out.attribute(null, "symbols", Integer.toString(policy.mActivePasswordSymbols)); - out.attribute(null, "nonletter", Integer.toString(policy.mActivePasswordNonLetter)); + out.attribute(null, "quality", Integer.toString(metrics.quality)); + out.attribute(null, "length", Integer.toString(metrics.length)); + out.attribute(null, "uppercase", Integer.toString(metrics.upperCase)); + out.attribute(null, "lowercase", Integer.toString(metrics.lowerCase)); + out.attribute(null, "letters", Integer.toString(metrics.letters)); + out.attribute(null, "numeric", Integer.toString(metrics.numeric)); + out.attribute(null, "symbols", Integer.toString(metrics.symbols)); + out.attribute(null, "nonletter", Integer.toString(metrics.nonLetter)); out.endTag(null, "active-password"); } @@ -2464,22 +2455,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { policy.mPasswordOwner = Integer.parseInt( parser.getAttributeValue(null, "value")); } else if ("active-password".equals(tag)) { - policy.mActivePasswordQuality = Integer.parseInt( - parser.getAttributeValue(null, "quality")); - policy.mActivePasswordLength = Integer.parseInt( - parser.getAttributeValue(null, "length")); - policy.mActivePasswordUpperCase = Integer.parseInt( - parser.getAttributeValue(null, "uppercase")); - policy.mActivePasswordLowerCase = Integer.parseInt( - parser.getAttributeValue(null, "lowercase")); - policy.mActivePasswordLetters = Integer.parseInt( - parser.getAttributeValue(null, "letters")); - policy.mActivePasswordNumeric = Integer.parseInt( - parser.getAttributeValue(null, "numeric")); - policy.mActivePasswordSymbols = Integer.parseInt( - parser.getAttributeValue(null, "symbols")); - policy.mActivePasswordNonLetter = Integer.parseInt( - parser.getAttributeValue(null, "nonletter")); + final PasswordMetrics m = policy.mActivePasswordMetrics; + m.quality = Integer.parseInt(parser.getAttributeValue(null, "quality")); + m.length = Integer.parseInt(parser.getAttributeValue(null, "length")); + m.upperCase = Integer.parseInt(parser.getAttributeValue(null, "uppercase")); + m.lowerCase = Integer.parseInt(parser.getAttributeValue(null, "lowercase")); + m.letters = Integer.parseInt(parser.getAttributeValue(null, "letters")); + m.numeric = Integer.parseInt(parser.getAttributeValue(null, "numeric")); + m.symbols = Integer.parseInt(parser.getAttributeValue(null, "symbols")); + m.nonLetter = Integer.parseInt(parser.getAttributeValue(null, "nonletter")); } else if (TAG_ACCEPTED_CA_CERTIFICATES.equals(tag)) { policy.mAcceptedCaCertificates.add(parser.getAttributeValue(null, ATTR_NAME)); } else if (TAG_LOCK_TASK_COMPONENTS.equals(tag)) { @@ -2525,19 +2509,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final long identity = mInjector.binderClearCallingIdentity(); try { int actualPasswordQuality = mLockPatternUtils.getActivePasswordQuality(userHandle); - if (actualPasswordQuality < policy.mActivePasswordQuality) { + if (actualPasswordQuality < policy.mActivePasswordMetrics.quality) { Slog.w(LOG_TAG, "Active password quality 0x" - + Integer.toHexString(policy.mActivePasswordQuality) + + Integer.toHexString(policy.mActivePasswordMetrics.quality) + " does not match actual quality 0x" + Integer.toHexString(actualPasswordQuality)); - policy.mActivePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; - policy.mActivePasswordLength = 0; - policy.mActivePasswordUpperCase = 0; - policy.mActivePasswordLowerCase = 0; - policy.mActivePasswordLetters = 0; - policy.mActivePasswordNumeric = 0; - policy.mActivePasswordSymbols = 0; - policy.mActivePasswordNonLetter = 0; + policy.mActivePasswordMetrics = new PasswordMetrics(); } } finally { mInjector.binderRestoreCallingIdentity(identity); @@ -3127,8 +3104,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { synchronized (this) { ActiveAdmin ap = getActiveAdminForCallerLocked( who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent); - if (ap.passwordQuality != quality) { - ap.passwordQuality = quality; + if (ap.minimumPasswordMetrics.quality != quality) { + ap.minimumPasswordMetrics.quality = quality; saveSettingsLocked(mInjector.userHandleGetCallingUserId()); } } @@ -3145,7 +3122,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (who != null) { ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle, parent); - return admin != null ? admin.passwordQuality : mode; + return admin != null ? admin.minimumPasswordMetrics.quality : mode; } // Return the strictest policy across all participating admins. @@ -3154,8 +3131,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final int N = admins.size(); for (int i = 0; i < N; i++) { ActiveAdmin admin = admins.get(i); - if (mode < admin.passwordQuality) { - mode = admin.passwordQuality; + if (mode < admin.minimumPasswordMetrics.quality) { + mode = admin.minimumPasswordMetrics.quality; } } return mode; @@ -3214,8 +3191,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { synchronized (this) { ActiveAdmin ap = getActiveAdminForCallerLocked( who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent); - if (ap.minimumPasswordLength != length) { - ap.minimumPasswordLength = length; + if (ap.minimumPasswordMetrics.length != length) { + ap.minimumPasswordMetrics.length = length; saveSettingsLocked(mInjector.userHandleGetCallingUserId()); } } @@ -3232,7 +3209,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (who != null) { ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle, parent); - return admin != null ? admin.minimumPasswordLength : length; + return admin != null ? admin.minimumPasswordMetrics.length : length; } // Return the strictest policy across all participating admins. @@ -3241,8 +3218,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final int N = admins.size(); for (int i = 0; i < N; i++) { ActiveAdmin admin = admins.get(i); - if (length < admin.minimumPasswordLength) { - length = admin.minimumPasswordLength; + if (length < admin.minimumPasswordMetrics.length) { + length = admin.minimumPasswordMetrics.length; } } return length; @@ -3469,8 +3446,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { synchronized (this) { ActiveAdmin ap = getActiveAdminForCallerLocked( who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent); - if (ap.minimumPasswordUpperCase != length) { - ap.minimumPasswordUpperCase = length; + if (ap.minimumPasswordMetrics.upperCase != length) { + ap.minimumPasswordMetrics.upperCase = length; saveSettingsLocked(mInjector.userHandleGetCallingUserId()); } } @@ -3487,7 +3464,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (who != null) { ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle, parent); - return admin != null ? admin.minimumPasswordUpperCase : length; + return admin != null ? admin.minimumPasswordMetrics.upperCase : length; } // Return the strictest policy across all participating admins. @@ -3496,8 +3473,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final int N = admins.size(); for (int i = 0; i < N; i++) { ActiveAdmin admin = admins.get(i); - if (length < admin.minimumPasswordUpperCase) { - length = admin.minimumPasswordUpperCase; + if (length < admin.minimumPasswordMetrics.upperCase) { + length = admin.minimumPasswordMetrics.upperCase; } } return length; @@ -3510,8 +3487,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { synchronized (this) { ActiveAdmin ap = getActiveAdminForCallerLocked( who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent); - if (ap.minimumPasswordLowerCase != length) { - ap.minimumPasswordLowerCase = length; + if (ap.minimumPasswordMetrics.lowerCase != length) { + ap.minimumPasswordMetrics.lowerCase = length; saveSettingsLocked(mInjector.userHandleGetCallingUserId()); } } @@ -3528,7 +3505,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (who != null) { ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle, parent); - return admin != null ? admin.minimumPasswordLowerCase : length; + return admin != null ? admin.minimumPasswordMetrics.lowerCase : length; } // Return the strictest policy across all participating admins. @@ -3537,8 +3514,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final int N = admins.size(); for (int i = 0; i < N; i++) { ActiveAdmin admin = admins.get(i); - if (length < admin.minimumPasswordLowerCase) { - length = admin.minimumPasswordLowerCase; + if (length < admin.minimumPasswordMetrics.lowerCase) { + length = admin.minimumPasswordMetrics.lowerCase; } } return length; @@ -3554,8 +3531,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { synchronized (this) { ActiveAdmin ap = getActiveAdminForCallerLocked( who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent); - if (ap.minimumPasswordLetters != length) { - ap.minimumPasswordLetters = length; + if (ap.minimumPasswordMetrics.letters != length) { + ap.minimumPasswordMetrics.letters = length; saveSettingsLocked(mInjector.userHandleGetCallingUserId()); } } @@ -3572,7 +3549,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (who != null) { ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle, parent); - return admin != null ? admin.minimumPasswordLetters : length; + return admin != null ? admin.minimumPasswordMetrics.letters : length; } // Return the strictest policy across all participating admins. @@ -3584,8 +3561,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!isLimitPasswordAllowed(admin, PASSWORD_QUALITY_COMPLEX)) { continue; } - if (length < admin.minimumPasswordLetters) { - length = admin.minimumPasswordLetters; + if (length < admin.minimumPasswordMetrics.letters) { + length = admin.minimumPasswordMetrics.letters; } } return length; @@ -3601,8 +3578,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { synchronized (this) { ActiveAdmin ap = getActiveAdminForCallerLocked( who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent); - if (ap.minimumPasswordNumeric != length) { - ap.minimumPasswordNumeric = length; + if (ap.minimumPasswordMetrics.numeric != length) { + ap.minimumPasswordMetrics.numeric = length; saveSettingsLocked(mInjector.userHandleGetCallingUserId()); } } @@ -3619,7 +3596,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (who != null) { ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle, parent); - return admin != null ? admin.minimumPasswordNumeric : length; + return admin != null ? admin.minimumPasswordMetrics.numeric : length; } // Return the strictest policy across all participating admins. @@ -3631,8 +3608,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!isLimitPasswordAllowed(admin, PASSWORD_QUALITY_COMPLEX)) { continue; } - if (length < admin.minimumPasswordNumeric) { - length = admin.minimumPasswordNumeric; + if (length < admin.minimumPasswordMetrics.numeric) { + length = admin.minimumPasswordMetrics.numeric; } } return length; @@ -3648,8 +3625,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { synchronized (this) { ActiveAdmin ap = getActiveAdminForCallerLocked( who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent); - if (ap.minimumPasswordSymbols != length) { - ap.minimumPasswordSymbols = length; + if (ap.minimumPasswordMetrics.symbols != length) { + ap.minimumPasswordMetrics.symbols = length; saveSettingsLocked(mInjector.userHandleGetCallingUserId()); } } @@ -3666,7 +3643,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (who != null) { ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle, parent); - return admin != null ? admin.minimumPasswordSymbols : length; + return admin != null ? admin.minimumPasswordMetrics.symbols : length; } // Return the strictest policy across all participating admins. @@ -3678,8 +3655,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!isLimitPasswordAllowed(admin, PASSWORD_QUALITY_COMPLEX)) { continue; } - if (length < admin.minimumPasswordSymbols) { - length = admin.minimumPasswordSymbols; + if (length < admin.minimumPasswordMetrics.symbols) { + length = admin.minimumPasswordMetrics.symbols; } } return length; @@ -3695,8 +3672,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { synchronized (this) { ActiveAdmin ap = getActiveAdminForCallerLocked( who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent); - if (ap.minimumPasswordNonLetter != length) { - ap.minimumPasswordNonLetter = length; + if (ap.minimumPasswordMetrics.nonLetter != length) { + ap.minimumPasswordMetrics.nonLetter = length; saveSettingsLocked(mInjector.userHandleGetCallingUserId()); } } @@ -3713,7 +3690,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (who != null) { ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle, parent); - return admin != null ? admin.minimumPasswordNonLetter : length; + return admin != null ? admin.minimumPasswordMetrics.nonLetter : length; } // Return the strictest policy across all participating admins. @@ -3725,8 +3702,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!isLimitPasswordAllowed(admin, PASSWORD_QUALITY_COMPLEX)) { continue; } - if (length < admin.minimumPasswordNonLetter) { - length = admin.minimumPasswordNonLetter; + if (length < admin.minimumPasswordMetrics.nonLetter) { + length = admin.minimumPasswordMetrics.nonLetter; } } return length; @@ -3767,28 +3744,28 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private boolean isActivePasswordSufficientForUserLocked( DevicePolicyData policy, int userHandle, boolean parent) { final int requiredPasswordQuality = getPasswordQuality(null, userHandle, parent); - if (policy.mActivePasswordQuality < requiredPasswordQuality) { + if (policy.mActivePasswordMetrics.quality < requiredPasswordQuality) { return false; } if (requiredPasswordQuality >= DevicePolicyManager.PASSWORD_QUALITY_NUMERIC - && policy.mActivePasswordLength < getPasswordMinimumLength( + && policy.mActivePasswordMetrics.length < getPasswordMinimumLength( null, userHandle, parent)) { return false; } if (requiredPasswordQuality != DevicePolicyManager.PASSWORD_QUALITY_COMPLEX) { return true; } - return policy.mActivePasswordUpperCase >= getPasswordMinimumUpperCase( + return policy.mActivePasswordMetrics.upperCase >= getPasswordMinimumUpperCase( null, userHandle, parent) - && policy.mActivePasswordLowerCase >= getPasswordMinimumLowerCase( + && policy.mActivePasswordMetrics.lowerCase >= getPasswordMinimumLowerCase( null, userHandle, parent) - && policy.mActivePasswordLetters >= getPasswordMinimumLetters( + && policy.mActivePasswordMetrics.letters >= getPasswordMinimumLetters( null, userHandle, parent) - && policy.mActivePasswordNumeric >= getPasswordMinimumNumeric( + && policy.mActivePasswordMetrics.numeric >= getPasswordMinimumNumeric( null, userHandle, parent) - && policy.mActivePasswordSymbols >= getPasswordMinimumSymbols( + && policy.mActivePasswordMetrics.symbols >= getPasswordMinimumSymbols( null, userHandle, parent) - && policy.mActivePasswordNonLetter >= getPasswordMinimumNonLetter( + && policy.mActivePasswordMetrics.nonLetter >= getPasswordMinimumNonLetter( null, userHandle, parent); } @@ -3982,8 +3959,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (quality == DevicePolicyManager.PASSWORD_QUALITY_MANAGED) { quality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; } + final PasswordMetrics metrics = PasswordMetrics.computeForPassword(password); if (quality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) { - int realQuality = LockPatternUtils.computePasswordQuality(password); + final int realQuality = metrics.quality; if (realQuality < quality && quality != DevicePolicyManager.PASSWORD_QUALITY_COMPLEX) { Slog.w(LOG_TAG, "resetPassword: password quality 0x" @@ -4001,67 +3979,48 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return false; } if (quality == DevicePolicyManager.PASSWORD_QUALITY_COMPLEX) { - int letters = 0; - int uppercase = 0; - int lowercase = 0; - int numbers = 0; - int symbols = 0; - int nonletter = 0; - for (int i = 0; i < password.length(); i++) { - char c = password.charAt(i); - if (c >= 'A' && c <= 'Z') { - letters++; - uppercase++; - } else if (c >= 'a' && c <= 'z') { - letters++; - lowercase++; - } else if (c >= '0' && c <= '9') { - numbers++; - nonletter++; - } else { - symbols++; - nonletter++; - } - } int neededLetters = getPasswordMinimumLetters(null, userHandle, /* parent */ false); - if(letters < neededLetters) { - Slog.w(LOG_TAG, "resetPassword: number of letters " + letters + if(metrics.letters < neededLetters) { + Slog.w(LOG_TAG, "resetPassword: number of letters " + metrics.letters + " does not meet required number of letters " + neededLetters); return false; } - int neededNumbers = getPasswordMinimumNumeric(null, userHandle, /* parent */ false); - if (numbers < neededNumbers) { - Slog.w(LOG_TAG, "resetPassword: number of numerical digits " + numbers + int neededNumeric = getPasswordMinimumNumeric(null, userHandle, /* parent */ false); + if (metrics.numeric < neededNumeric) { + Slog.w(LOG_TAG, "resetPassword: number of numerical digits " + metrics.numeric + " does not meet required number of numerical digits " - + neededNumbers); + + neededNumeric); return false; } int neededLowerCase = getPasswordMinimumLowerCase( null, userHandle, /* parent */ false); - if (lowercase < neededLowerCase) { - Slog.w(LOG_TAG, "resetPassword: number of lowercase letters " + lowercase + if (metrics.lowerCase < neededLowerCase) { + Slog.w(LOG_TAG, "resetPassword: number of lowercase letters " + + metrics.lowerCase + " does not meet required number of lowercase letters " + neededLowerCase); return false; } int neededUpperCase = getPasswordMinimumUpperCase( null, userHandle, /* parent */ false); - if (uppercase < neededUpperCase) { - Slog.w(LOG_TAG, "resetPassword: number of uppercase letters " + uppercase + if (metrics.upperCase < neededUpperCase) { + Slog.w(LOG_TAG, "resetPassword: number of uppercase letters " + + metrics.upperCase + " does not meet required number of uppercase letters " + neededUpperCase); return false; } int neededSymbols = getPasswordMinimumSymbols(null, userHandle, /* parent */ false); - if (symbols < neededSymbols) { - Slog.w(LOG_TAG, "resetPassword: number of special symbols " + symbols + if (metrics.symbols < neededSymbols) { + Slog.w(LOG_TAG, "resetPassword: number of special symbols " + metrics.symbols + " does not meet required number of special symbols " + neededSymbols); return false; } int neededNonLetter = getPasswordMinimumNonLetter( null, userHandle, /* parent */ false); - if (nonletter < neededNonLetter) { - Slog.w(LOG_TAG, "resetPassword: number of non-letter characters " + nonletter + if (metrics.nonLetter < neededNonLetter) { + Slog.w(LOG_TAG, "resetPassword: number of non-letter characters " + + metrics.nonLetter + " does not meet required number of non-letter characters " + neededNonLetter); return false; @@ -4850,8 +4809,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } @Override - public void setActivePasswordState(int quality, int length, int letters, int uppercase, - int lowercase, int numbers, int symbols, int nonletter, int userHandle) { + public void setActivePasswordState(PasswordMetrics metrics, int userHandle) { if (!mHasFeature) { return; } @@ -4864,21 +4822,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.BIND_DEVICE_ADMIN, null); - validateQualityConstant(quality); + validateQualityConstant(metrics.quality); DevicePolicyData policy = getUserData(userHandle); long ident = mInjector.binderClearCallingIdentity(); try { synchronized (this) { - policy.mActivePasswordQuality = quality; - policy.mActivePasswordLength = length; - policy.mActivePasswordLetters = letters; - policy.mActivePasswordLowerCase = lowercase; - policy.mActivePasswordUpperCase = uppercase; - policy.mActivePasswordNumeric = numbers; - policy.mActivePasswordSymbols = symbols; - policy.mActivePasswordNonLetter = nonletter; + policy.mActivePasswordMetrics = metrics; policy.mFailedPasswordAttempts = 0; saveSettingsLocked(userHandle); updatePasswordExpirationsLocked(userHandle); @@ -8437,10 +8388,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { /** * Returns true if specified admin is allowed to limit passwords and has a - * {@code passwordQuality} of at least {@code minPasswordQuality} + * {@code minimumPasswordMetrics.quality} of at least {@code minPasswordQuality} */ private static boolean isLimitPasswordAllowed(ActiveAdmin admin, int minPasswordQuality) { - if (admin.passwordQuality < minPasswordQuality) { + if (admin.minimumPasswordMetrics.quality < minPasswordQuality) { return false; } return admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);