Loading core/java/android/app/admin/PasswordMetrics.java +21 −18 Original line number Original line Diff line number Diff line Loading @@ -41,6 +41,7 @@ import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_SYM import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_UPPER_CASE; import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_UPPER_CASE; import static com.android.internal.widget.PasswordValidationError.TOO_LONG; import static com.android.internal.widget.PasswordValidationError.TOO_LONG; import static com.android.internal.widget.PasswordValidationError.TOO_SHORT; import static com.android.internal.widget.PasswordValidationError.TOO_SHORT; import static com.android.internal.widget.PasswordValidationError.TOO_SHORT_WHEN_ALL_NUMERIC; import static com.android.internal.widget.PasswordValidationError.WEAK_CREDENTIAL_TYPE; import static com.android.internal.widget.PasswordValidationError.WEAK_CREDENTIAL_TYPE; import android.annotation.IntDef; import android.annotation.IntDef; Loading Loading @@ -569,21 +570,15 @@ public final class PasswordMetrics implements Parcelable { result.add(new PasswordValidationError(TOO_LONG, MAX_PASSWORD_LENGTH)); result.add(new PasswordValidationError(TOO_LONG, MAX_PASSWORD_LENGTH)); } } // A flag indicating whether the provided password already has non-numeric characters in final PasswordMetrics minMetrics = applyComplexity(adminMetrics, // it or if the admin imposes the requirement of any non-numeric characters. actualMetrics.credType == CREDENTIAL_TYPE_PIN, bucket); final boolean hasOrWouldNeedNonNumeric = actualMetrics.nonNumeric > 0 || adminMetrics.nonNumeric > 0 || adminMetrics.letters > 0 || adminMetrics.lowerCase > 0 || adminMetrics.upperCase > 0 || adminMetrics.symbols > 0; final PasswordMetrics minMetrics = applyComplexity(adminMetrics, hasOrWouldNeedNonNumeric, bucket); // Clamp required length between maximum and minimum valid values. // Clamp required length between maximum and minimum valid values. minMetrics.length = Math.min(MAX_PASSWORD_LENGTH, minMetrics.length = Math.min(MAX_PASSWORD_LENGTH, Math.max(minMetrics.length, MIN_LOCK_PASSWORD_SIZE)); Math.max(minMetrics.length, MIN_LOCK_PASSWORD_SIZE)); minMetrics.removeOverlapping(); minMetrics.removeOverlapping(); comparePasswordMetrics(minMetrics, actualMetrics, result); comparePasswordMetrics(minMetrics, bucket, actualMetrics, result); return result; return result; } } Loading @@ -591,11 +586,23 @@ public final class PasswordMetrics implements Parcelable { /** /** * TODO: move to PasswordPolicy * TODO: move to PasswordPolicy */ */ private static void comparePasswordMetrics(PasswordMetrics minMetrics, private static void comparePasswordMetrics(PasswordMetrics minMetrics, ComplexityBucket bucket, PasswordMetrics actualMetrics, ArrayList<PasswordValidationError> result) { PasswordMetrics actualMetrics, ArrayList<PasswordValidationError> result) { if (actualMetrics.length < minMetrics.length) { if (actualMetrics.length < minMetrics.length) { result.add(new PasswordValidationError(TOO_SHORT, minMetrics.length)); result.add(new PasswordValidationError(TOO_SHORT, minMetrics.length)); } } if (actualMetrics.nonNumeric == 0 && minMetrics.nonNumeric == 0 && minMetrics.letters == 0 && minMetrics.lowerCase == 0 && minMetrics.upperCase == 0 && minMetrics.symbols == 0) { // When provided password is all numeric and all numeric password is allowed. int allNumericMinimumLength = bucket.getMinimumLength(false); if (allNumericMinimumLength > minMetrics.length && allNumericMinimumLength > minMetrics.numeric && actualMetrics.length < allNumericMinimumLength) { result.add(new PasswordValidationError( TOO_SHORT_WHEN_ALL_NUMERIC, allNumericMinimumLength)); } } if (actualMetrics.letters < minMetrics.letters) { if (actualMetrics.letters < minMetrics.letters) { result.add(new PasswordValidationError(NOT_ENOUGH_LETTERS, minMetrics.letters)); result.add(new PasswordValidationError(NOT_ENOUGH_LETTERS, minMetrics.letters)); } } Loading Loading @@ -668,15 +675,12 @@ public final class PasswordMetrics implements Parcelable { * * * TODO: move to PasswordPolicy * TODO: move to PasswordPolicy */ */ public static PasswordMetrics applyComplexity( public static PasswordMetrics applyComplexity(PasswordMetrics adminMetrics, boolean isPin, PasswordMetrics adminMetrics, boolean withNonNumericCharacters, int complexity) { int complexity) { return applyComplexity(adminMetrics, withNonNumericCharacters, return applyComplexity(adminMetrics, isPin, ComplexityBucket.forComplexity(complexity)); ComplexityBucket.forComplexity(complexity)); } } private static PasswordMetrics applyComplexity( private static PasswordMetrics applyComplexity(PasswordMetrics adminMetrics, boolean isPin, PasswordMetrics adminMetrics, boolean withNonNumericCharacters, ComplexityBucket bucket) { ComplexityBucket bucket) { final PasswordMetrics minMetrics = new PasswordMetrics(adminMetrics); final PasswordMetrics minMetrics = new PasswordMetrics(adminMetrics); Loading @@ -684,8 +688,7 @@ public final class PasswordMetrics implements Parcelable { minMetrics.seqLength = Math.min(minMetrics.seqLength, MAX_ALLOWED_SEQUENCE); minMetrics.seqLength = Math.min(minMetrics.seqLength, MAX_ALLOWED_SEQUENCE); } } minMetrics.length = Math.max(minMetrics.length, minMetrics.length = Math.max(minMetrics.length, bucket.getMinimumLength(!isPin)); bucket.getMinimumLength(withNonNumericCharacters)); return minMetrics; return minMetrics; } } Loading core/java/com/android/internal/widget/PasswordValidationError.java +12 −10 Original line number Original line Diff line number Diff line Loading @@ -24,16 +24,17 @@ public class PasswordValidationError { public static final int WEAK_CREDENTIAL_TYPE = 1; public static final int WEAK_CREDENTIAL_TYPE = 1; public static final int CONTAINS_INVALID_CHARACTERS = 2; public static final int CONTAINS_INVALID_CHARACTERS = 2; public static final int TOO_SHORT = 3; public static final int TOO_SHORT = 3; public static final int TOO_LONG = 4; public static final int TOO_SHORT_WHEN_ALL_NUMERIC = 4; public static final int CONTAINS_SEQUENCE = 5; public static final int TOO_LONG = 5; public static final int NOT_ENOUGH_LETTERS = 6; public static final int CONTAINS_SEQUENCE = 6; public static final int NOT_ENOUGH_UPPER_CASE = 7; public static final int NOT_ENOUGH_LETTERS = 7; public static final int NOT_ENOUGH_LOWER_CASE = 8; public static final int NOT_ENOUGH_UPPER_CASE = 8; public static final int NOT_ENOUGH_DIGITS = 9; public static final int NOT_ENOUGH_LOWER_CASE = 9; public static final int NOT_ENOUGH_SYMBOLS = 10; public static final int NOT_ENOUGH_DIGITS = 10; public static final int NOT_ENOUGH_NON_LETTER = 11; public static final int NOT_ENOUGH_SYMBOLS = 11; public static final int NOT_ENOUGH_NON_DIGITS = 12; public static final int NOT_ENOUGH_NON_LETTER = 12; public static final int RECENTLY_USED = 13; public static final int NOT_ENOUGH_NON_DIGITS = 13; public static final int RECENTLY_USED = 14; // WARNING: if you add a new error, make sure it is presented to the user correctly in Settings. // WARNING: if you add a new error, make sure it is presented to the user correctly in Settings. public final int errorCode; public final int errorCode; Loading Loading @@ -61,6 +62,7 @@ public class PasswordValidationError { case WEAK_CREDENTIAL_TYPE: return "Weak credential type"; case WEAK_CREDENTIAL_TYPE: return "Weak credential type"; case CONTAINS_INVALID_CHARACTERS: return "Contains an invalid character"; case CONTAINS_INVALID_CHARACTERS: return "Contains an invalid character"; case TOO_SHORT: return "Password too short"; case TOO_SHORT: return "Password too short"; case TOO_SHORT_WHEN_ALL_NUMERIC: return "Password too short"; case TOO_LONG: return "Password too long"; case TOO_LONG: return "Password too long"; case CONTAINS_SEQUENCE: return "Sequence too long"; case CONTAINS_SEQUENCE: return "Sequence too long"; case NOT_ENOUGH_LETTERS: return "Too few letters"; case NOT_ENOUGH_LETTERS: return "Too few letters"; Loading core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java +52 −2 Original line number Original line Diff line number Diff line Loading @@ -38,8 +38,8 @@ import static org.junit.Assert.assertTrue; import android.os.Parcel; import android.os.Parcel; import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.Presubmit; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.internal.widget.PasswordValidationError; import com.android.internal.widget.PasswordValidationError; Loading Loading @@ -324,6 +324,56 @@ public class PasswordMetricsTest { PasswordValidationError.WEAK_CREDENTIAL_TYPE, 0); PasswordValidationError.WEAK_CREDENTIAL_TYPE, 0); } } @Test public void testValidatePasswordMetrics_pinAndComplexityHigh() { PasswordMetrics adminMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PIN); PasswordMetrics actualMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PIN); actualMetrics.length = 6; actualMetrics.seqLength = 1; assertValidationErrors( validatePasswordMetrics(adminMetrics, PASSWORD_COMPLEXITY_HIGH, actualMetrics), PasswordValidationError.TOO_SHORT, 8); } @Test public void testValidatePasswordMetrics_nonAllNumberPasswordAndComplexityHigh() { PasswordMetrics adminMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD); PasswordMetrics actualMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD); actualMetrics.length = 5; actualMetrics.nonNumeric = 1; actualMetrics.seqLength = 1; assertValidationErrors( validatePasswordMetrics(adminMetrics, PASSWORD_COMPLEXITY_HIGH, actualMetrics), PasswordValidationError.TOO_SHORT, 6); } @Test public void testValidatePasswordMetrics_allNumberPasswordAndComplexityHigh() { PasswordMetrics adminMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD); PasswordMetrics actualMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD); actualMetrics.length = 6; actualMetrics.seqLength = 1; assertValidationErrors( validatePasswordMetrics(adminMetrics, PASSWORD_COMPLEXITY_HIGH, actualMetrics), PasswordValidationError.TOO_SHORT_WHEN_ALL_NUMERIC, 8); } @Test public void testValidatePasswordMetrics_allNumberPasswordAndRequireNonNumeric() { PasswordMetrics adminMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD); adminMetrics.nonNumeric = 1; PasswordMetrics actualMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD); actualMetrics.length = 6; actualMetrics.seqLength = 1; assertValidationErrors( validatePasswordMetrics(adminMetrics, PASSWORD_COMPLEXITY_HIGH, actualMetrics), PasswordValidationError.NOT_ENOUGH_NON_DIGITS, 1); } /** /** * @param expected sequense of validation error codes followed by requirement values, must have * @param expected sequense of validation error codes followed by requirement values, must have * even number of elements. Empty means no errors. * even number of elements. Empty means no errors. Loading Loading
core/java/android/app/admin/PasswordMetrics.java +21 −18 Original line number Original line Diff line number Diff line Loading @@ -41,6 +41,7 @@ import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_SYM import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_UPPER_CASE; import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_UPPER_CASE; import static com.android.internal.widget.PasswordValidationError.TOO_LONG; import static com.android.internal.widget.PasswordValidationError.TOO_LONG; import static com.android.internal.widget.PasswordValidationError.TOO_SHORT; import static com.android.internal.widget.PasswordValidationError.TOO_SHORT; import static com.android.internal.widget.PasswordValidationError.TOO_SHORT_WHEN_ALL_NUMERIC; import static com.android.internal.widget.PasswordValidationError.WEAK_CREDENTIAL_TYPE; import static com.android.internal.widget.PasswordValidationError.WEAK_CREDENTIAL_TYPE; import android.annotation.IntDef; import android.annotation.IntDef; Loading Loading @@ -569,21 +570,15 @@ public final class PasswordMetrics implements Parcelable { result.add(new PasswordValidationError(TOO_LONG, MAX_PASSWORD_LENGTH)); result.add(new PasswordValidationError(TOO_LONG, MAX_PASSWORD_LENGTH)); } } // A flag indicating whether the provided password already has non-numeric characters in final PasswordMetrics minMetrics = applyComplexity(adminMetrics, // it or if the admin imposes the requirement of any non-numeric characters. actualMetrics.credType == CREDENTIAL_TYPE_PIN, bucket); final boolean hasOrWouldNeedNonNumeric = actualMetrics.nonNumeric > 0 || adminMetrics.nonNumeric > 0 || adminMetrics.letters > 0 || adminMetrics.lowerCase > 0 || adminMetrics.upperCase > 0 || adminMetrics.symbols > 0; final PasswordMetrics minMetrics = applyComplexity(adminMetrics, hasOrWouldNeedNonNumeric, bucket); // Clamp required length between maximum and minimum valid values. // Clamp required length between maximum and minimum valid values. minMetrics.length = Math.min(MAX_PASSWORD_LENGTH, minMetrics.length = Math.min(MAX_PASSWORD_LENGTH, Math.max(minMetrics.length, MIN_LOCK_PASSWORD_SIZE)); Math.max(minMetrics.length, MIN_LOCK_PASSWORD_SIZE)); minMetrics.removeOverlapping(); minMetrics.removeOverlapping(); comparePasswordMetrics(minMetrics, actualMetrics, result); comparePasswordMetrics(minMetrics, bucket, actualMetrics, result); return result; return result; } } Loading @@ -591,11 +586,23 @@ public final class PasswordMetrics implements Parcelable { /** /** * TODO: move to PasswordPolicy * TODO: move to PasswordPolicy */ */ private static void comparePasswordMetrics(PasswordMetrics minMetrics, private static void comparePasswordMetrics(PasswordMetrics minMetrics, ComplexityBucket bucket, PasswordMetrics actualMetrics, ArrayList<PasswordValidationError> result) { PasswordMetrics actualMetrics, ArrayList<PasswordValidationError> result) { if (actualMetrics.length < minMetrics.length) { if (actualMetrics.length < minMetrics.length) { result.add(new PasswordValidationError(TOO_SHORT, minMetrics.length)); result.add(new PasswordValidationError(TOO_SHORT, minMetrics.length)); } } if (actualMetrics.nonNumeric == 0 && minMetrics.nonNumeric == 0 && minMetrics.letters == 0 && minMetrics.lowerCase == 0 && minMetrics.upperCase == 0 && minMetrics.symbols == 0) { // When provided password is all numeric and all numeric password is allowed. int allNumericMinimumLength = bucket.getMinimumLength(false); if (allNumericMinimumLength > minMetrics.length && allNumericMinimumLength > minMetrics.numeric && actualMetrics.length < allNumericMinimumLength) { result.add(new PasswordValidationError( TOO_SHORT_WHEN_ALL_NUMERIC, allNumericMinimumLength)); } } if (actualMetrics.letters < minMetrics.letters) { if (actualMetrics.letters < minMetrics.letters) { result.add(new PasswordValidationError(NOT_ENOUGH_LETTERS, minMetrics.letters)); result.add(new PasswordValidationError(NOT_ENOUGH_LETTERS, minMetrics.letters)); } } Loading Loading @@ -668,15 +675,12 @@ public final class PasswordMetrics implements Parcelable { * * * TODO: move to PasswordPolicy * TODO: move to PasswordPolicy */ */ public static PasswordMetrics applyComplexity( public static PasswordMetrics applyComplexity(PasswordMetrics adminMetrics, boolean isPin, PasswordMetrics adminMetrics, boolean withNonNumericCharacters, int complexity) { int complexity) { return applyComplexity(adminMetrics, withNonNumericCharacters, return applyComplexity(adminMetrics, isPin, ComplexityBucket.forComplexity(complexity)); ComplexityBucket.forComplexity(complexity)); } } private static PasswordMetrics applyComplexity( private static PasswordMetrics applyComplexity(PasswordMetrics adminMetrics, boolean isPin, PasswordMetrics adminMetrics, boolean withNonNumericCharacters, ComplexityBucket bucket) { ComplexityBucket bucket) { final PasswordMetrics minMetrics = new PasswordMetrics(adminMetrics); final PasswordMetrics minMetrics = new PasswordMetrics(adminMetrics); Loading @@ -684,8 +688,7 @@ public final class PasswordMetrics implements Parcelable { minMetrics.seqLength = Math.min(minMetrics.seqLength, MAX_ALLOWED_SEQUENCE); minMetrics.seqLength = Math.min(minMetrics.seqLength, MAX_ALLOWED_SEQUENCE); } } minMetrics.length = Math.max(minMetrics.length, minMetrics.length = Math.max(minMetrics.length, bucket.getMinimumLength(!isPin)); bucket.getMinimumLength(withNonNumericCharacters)); return minMetrics; return minMetrics; } } Loading
core/java/com/android/internal/widget/PasswordValidationError.java +12 −10 Original line number Original line Diff line number Diff line Loading @@ -24,16 +24,17 @@ public class PasswordValidationError { public static final int WEAK_CREDENTIAL_TYPE = 1; public static final int WEAK_CREDENTIAL_TYPE = 1; public static final int CONTAINS_INVALID_CHARACTERS = 2; public static final int CONTAINS_INVALID_CHARACTERS = 2; public static final int TOO_SHORT = 3; public static final int TOO_SHORT = 3; public static final int TOO_LONG = 4; public static final int TOO_SHORT_WHEN_ALL_NUMERIC = 4; public static final int CONTAINS_SEQUENCE = 5; public static final int TOO_LONG = 5; public static final int NOT_ENOUGH_LETTERS = 6; public static final int CONTAINS_SEQUENCE = 6; public static final int NOT_ENOUGH_UPPER_CASE = 7; public static final int NOT_ENOUGH_LETTERS = 7; public static final int NOT_ENOUGH_LOWER_CASE = 8; public static final int NOT_ENOUGH_UPPER_CASE = 8; public static final int NOT_ENOUGH_DIGITS = 9; public static final int NOT_ENOUGH_LOWER_CASE = 9; public static final int NOT_ENOUGH_SYMBOLS = 10; public static final int NOT_ENOUGH_DIGITS = 10; public static final int NOT_ENOUGH_NON_LETTER = 11; public static final int NOT_ENOUGH_SYMBOLS = 11; public static final int NOT_ENOUGH_NON_DIGITS = 12; public static final int NOT_ENOUGH_NON_LETTER = 12; public static final int RECENTLY_USED = 13; public static final int NOT_ENOUGH_NON_DIGITS = 13; public static final int RECENTLY_USED = 14; // WARNING: if you add a new error, make sure it is presented to the user correctly in Settings. // WARNING: if you add a new error, make sure it is presented to the user correctly in Settings. public final int errorCode; public final int errorCode; Loading Loading @@ -61,6 +62,7 @@ public class PasswordValidationError { case WEAK_CREDENTIAL_TYPE: return "Weak credential type"; case WEAK_CREDENTIAL_TYPE: return "Weak credential type"; case CONTAINS_INVALID_CHARACTERS: return "Contains an invalid character"; case CONTAINS_INVALID_CHARACTERS: return "Contains an invalid character"; case TOO_SHORT: return "Password too short"; case TOO_SHORT: return "Password too short"; case TOO_SHORT_WHEN_ALL_NUMERIC: return "Password too short"; case TOO_LONG: return "Password too long"; case TOO_LONG: return "Password too long"; case CONTAINS_SEQUENCE: return "Sequence too long"; case CONTAINS_SEQUENCE: return "Sequence too long"; case NOT_ENOUGH_LETTERS: return "Too few letters"; case NOT_ENOUGH_LETTERS: return "Too few letters"; Loading
core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java +52 −2 Original line number Original line Diff line number Diff line Loading @@ -38,8 +38,8 @@ import static org.junit.Assert.assertTrue; import android.os.Parcel; import android.os.Parcel; import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.Presubmit; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.internal.widget.PasswordValidationError; import com.android.internal.widget.PasswordValidationError; Loading Loading @@ -324,6 +324,56 @@ public class PasswordMetricsTest { PasswordValidationError.WEAK_CREDENTIAL_TYPE, 0); PasswordValidationError.WEAK_CREDENTIAL_TYPE, 0); } } @Test public void testValidatePasswordMetrics_pinAndComplexityHigh() { PasswordMetrics adminMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PIN); PasswordMetrics actualMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PIN); actualMetrics.length = 6; actualMetrics.seqLength = 1; assertValidationErrors( validatePasswordMetrics(adminMetrics, PASSWORD_COMPLEXITY_HIGH, actualMetrics), PasswordValidationError.TOO_SHORT, 8); } @Test public void testValidatePasswordMetrics_nonAllNumberPasswordAndComplexityHigh() { PasswordMetrics adminMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD); PasswordMetrics actualMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD); actualMetrics.length = 5; actualMetrics.nonNumeric = 1; actualMetrics.seqLength = 1; assertValidationErrors( validatePasswordMetrics(adminMetrics, PASSWORD_COMPLEXITY_HIGH, actualMetrics), PasswordValidationError.TOO_SHORT, 6); } @Test public void testValidatePasswordMetrics_allNumberPasswordAndComplexityHigh() { PasswordMetrics adminMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD); PasswordMetrics actualMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD); actualMetrics.length = 6; actualMetrics.seqLength = 1; assertValidationErrors( validatePasswordMetrics(adminMetrics, PASSWORD_COMPLEXITY_HIGH, actualMetrics), PasswordValidationError.TOO_SHORT_WHEN_ALL_NUMERIC, 8); } @Test public void testValidatePasswordMetrics_allNumberPasswordAndRequireNonNumeric() { PasswordMetrics adminMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD); adminMetrics.nonNumeric = 1; PasswordMetrics actualMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD); actualMetrics.length = 6; actualMetrics.seqLength = 1; assertValidationErrors( validatePasswordMetrics(adminMetrics, PASSWORD_COMPLEXITY_HIGH, actualMetrics), PasswordValidationError.NOT_ENOUGH_NON_DIGITS, 1); } /** /** * @param expected sequense of validation error codes followed by requirement values, must have * @param expected sequense of validation error codes followed by requirement values, must have * even number of elements. Empty means no errors. * even number of elements. Empty means no errors. Loading