Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fabf6dcd authored by Chaohui Wang's avatar Chaohui Wang
Browse files

Add validation TOO_SHORT_WHEN_ALL_NUMERIC

To distinguish the requirement between all numeric and not all numeric
when COMPLEXITY_HIGH.

Note:
The second param of applyComplexity() changed back from
withNonNumericCharacters to isPin.
This was changed from isPin to withNonNumericCharacters in Change
I3227d4d8e6825b5c4ea525828d7e09f52702065b without changing the caller.
This currently not causing any issues because applyComplexity() only
used by KeyguardManager.getMinLockLength(), which currently never be
called with COMPLEXITY_HIGH.

Fix: 227149118
Fix: 173167839
Test: manual & robolectric
Change-Id: I7e0d4a1edf42327452b7d9a564258b26865c056e
parent 92d87441
Loading
Loading
Loading
Loading
+21 −18
Original line number Diff line number Diff line
@@ -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.TOO_LONG;
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 android.annotation.IntDef;
@@ -569,21 +570,15 @@ public final class PasswordMetrics implements Parcelable {
            result.add(new PasswordValidationError(TOO_LONG, MAX_PASSWORD_LENGTH));
        }

        // A flag indicating whether the provided password already has non-numeric characters in
        // it or if the admin imposes the requirement of any non-numeric characters.
        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);
        final PasswordMetrics minMetrics = applyComplexity(adminMetrics,
                actualMetrics.credType == CREDENTIAL_TYPE_PIN, bucket);

        // Clamp required length between maximum and minimum valid values.
        minMetrics.length = Math.min(MAX_PASSWORD_LENGTH,
                Math.max(minMetrics.length, MIN_LOCK_PASSWORD_SIZE));
        minMetrics.removeOverlapping();

        comparePasswordMetrics(minMetrics, actualMetrics, result);
        comparePasswordMetrics(minMetrics, bucket, actualMetrics, result);

        return result;
    }
@@ -591,11 +586,23 @@ public final class PasswordMetrics implements Parcelable {
    /**
     * TODO: move to PasswordPolicy
     */
    private static void comparePasswordMetrics(PasswordMetrics minMetrics,
    private static void comparePasswordMetrics(PasswordMetrics minMetrics, ComplexityBucket bucket,
            PasswordMetrics actualMetrics, ArrayList<PasswordValidationError> result) {
        if (actualMetrics.length < 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) {
            result.add(new PasswordValidationError(NOT_ENOUGH_LETTERS, minMetrics.letters));
        }
@@ -668,15 +675,12 @@ public final class PasswordMetrics implements Parcelable {
     *
     * TODO: move to PasswordPolicy
     */
    public static PasswordMetrics applyComplexity(
            PasswordMetrics adminMetrics, boolean withNonNumericCharacters,
    public static PasswordMetrics applyComplexity(PasswordMetrics adminMetrics, boolean isPin,
            int complexity) {
        return applyComplexity(adminMetrics, withNonNumericCharacters,
                ComplexityBucket.forComplexity(complexity));
        return applyComplexity(adminMetrics, isPin, ComplexityBucket.forComplexity(complexity));
    }

    private static PasswordMetrics applyComplexity(
            PasswordMetrics adminMetrics, boolean withNonNumericCharacters,
    private static PasswordMetrics applyComplexity(PasswordMetrics adminMetrics, boolean isPin,
            ComplexityBucket bucket) {
        final PasswordMetrics minMetrics = new PasswordMetrics(adminMetrics);

@@ -684,8 +688,7 @@ public final class PasswordMetrics implements Parcelable {
            minMetrics.seqLength = Math.min(minMetrics.seqLength, MAX_ALLOWED_SEQUENCE);
        }

        minMetrics.length = Math.max(minMetrics.length,
                bucket.getMinimumLength(withNonNumericCharacters));
        minMetrics.length = Math.max(minMetrics.length, bucket.getMinimumLength(!isPin));

        return minMetrics;
    }
+12 −10
Original line number Diff line number Diff line
@@ -24,16 +24,17 @@ public class PasswordValidationError {
    public static final int WEAK_CREDENTIAL_TYPE = 1;
    public static final int CONTAINS_INVALID_CHARACTERS = 2;
    public static final int TOO_SHORT = 3;
    public static final int TOO_LONG = 4;
    public static final int CONTAINS_SEQUENCE = 5;
    public static final int NOT_ENOUGH_LETTERS = 6;
    public static final int NOT_ENOUGH_UPPER_CASE = 7;
    public static final int NOT_ENOUGH_LOWER_CASE = 8;
    public static final int NOT_ENOUGH_DIGITS = 9;
    public static final int NOT_ENOUGH_SYMBOLS = 10;
    public static final int NOT_ENOUGH_NON_LETTER = 11;
    public static final int NOT_ENOUGH_NON_DIGITS = 12;
    public static final int RECENTLY_USED = 13;
    public static final int TOO_SHORT_WHEN_ALL_NUMERIC = 4;
    public static final int TOO_LONG = 5;
    public static final int CONTAINS_SEQUENCE = 6;
    public static final int NOT_ENOUGH_LETTERS = 7;
    public static final int NOT_ENOUGH_UPPER_CASE = 8;
    public static final int NOT_ENOUGH_LOWER_CASE = 9;
    public static final int NOT_ENOUGH_DIGITS = 10;
    public static final int NOT_ENOUGH_SYMBOLS = 11;
    public static final int NOT_ENOUGH_NON_LETTER = 12;
    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.

    public final int errorCode;
@@ -61,6 +62,7 @@ public class PasswordValidationError {
            case WEAK_CREDENTIAL_TYPE: return "Weak credential type";
            case CONTAINS_INVALID_CHARACTERS: return "Contains an invalid character";
            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 CONTAINS_SEQUENCE: return "Sequence too long";
            case NOT_ENOUGH_LETTERS: return "Too few letters";
+52 −2
Original line number Diff line number Diff line
@@ -38,8 +38,8 @@ import static org.junit.Assert.assertTrue;
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import com.android.internal.widget.PasswordValidationError;

@@ -324,6 +324,56 @@ public class PasswordMetricsTest {
                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
     *                 even number of elements. Empty means no errors.