1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package android.app.admin; 18 19 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH; 20 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW; 21 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM; 22 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE; 23 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX; 24 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING; 25 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; 26 import static android.app.admin.PasswordMetrics.complexityLevelToMinQuality; 27 import static android.app.admin.PasswordMetrics.sanitizeComplexityLevel; 28 import static android.app.admin.PasswordMetrics.validateCredential; 29 import static android.app.admin.PasswordMetrics.validatePasswordMetrics; 30 31 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE; 32 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD; 33 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN; 34 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN; 35 36 import static org.junit.Assert.assertEquals; 37 import static org.junit.Assert.assertTrue; 38 39 import android.os.Parcel; 40 import android.platform.test.annotations.Presubmit; 41 42 import androidx.test.ext.junit.runners.AndroidJUnit4; 43 import androidx.test.filters.SmallTest; 44 45 import com.android.internal.widget.LockPatternUtils; 46 import com.android.internal.widget.LockscreenCredential; 47 import com.android.internal.widget.PasswordValidationError; 48 49 import org.junit.Test; 50 import org.junit.runner.RunWith; 51 52 import java.util.Arrays; 53 import java.util.Collections; 54 import java.util.HashMap; 55 import java.util.List; 56 57 /** Unit tests for {@link PasswordMetrics}. */ 58 @RunWith(AndroidJUnit4.class) 59 @SmallTest 60 @Presubmit 61 public class PasswordMetricsTest { 62 @Test testParceling()63 public void testParceling() { 64 final int credType = CREDENTIAL_TYPE_PASSWORD; 65 final int length = 1; 66 final int letters = 2; 67 final int upperCase = 3; 68 final int lowerCase = 4; 69 final int numeric = 5; 70 final int symbols = 6; 71 final int nonLetter = 7; 72 final int nonNumeric = 8; 73 final int seqLength = 9; 74 75 final Parcel parcel = Parcel.obtain(); 76 PasswordMetrics metrics = new PasswordMetrics(credType, length, letters, upperCase, 77 lowerCase, numeric, symbols, nonLetter, nonNumeric, seqLength); 78 try { 79 metrics.writeToParcel(parcel, 0); 80 parcel.setDataPosition(0); 81 metrics = PasswordMetrics.CREATOR.createFromParcel(parcel); 82 } finally { 83 parcel.recycle(); 84 } 85 86 assertEquals(credType, metrics.credType); 87 assertEquals(length, metrics.length); 88 assertEquals(letters, metrics.letters); 89 assertEquals(upperCase, metrics.upperCase); 90 assertEquals(lowerCase, metrics.lowerCase); 91 assertEquals(numeric, metrics.numeric); 92 assertEquals(symbols, metrics.symbols); 93 assertEquals(nonLetter, metrics.nonLetter); 94 assertEquals(nonNumeric, metrics.nonNumeric); 95 assertEquals(seqLength, metrics.seqLength); 96 } 97 98 @Test testComputeForPassword_metrics()99 public void testComputeForPassword_metrics() { 100 final PasswordMetrics metrics = metricsForPassword("6B~0z1Z3*8A"); 101 assertEquals(11, metrics.length); 102 assertEquals(4, metrics.letters); 103 assertEquals(3, metrics.upperCase); 104 assertEquals(1, metrics.lowerCase); 105 assertEquals(5, metrics.numeric); 106 assertEquals(2, metrics.symbols); 107 assertEquals(7, metrics.nonLetter); 108 } 109 110 @Test testMaxLengthSequence()111 public void testMaxLengthSequence() { 112 assertEquals(4, PasswordMetrics.maxLengthSequence("1234".getBytes())); 113 assertEquals(5, PasswordMetrics.maxLengthSequence("13579".getBytes())); 114 assertEquals(4, PasswordMetrics.maxLengthSequence("1234abd".getBytes())); 115 assertEquals(3, PasswordMetrics.maxLengthSequence("aabc".getBytes())); 116 assertEquals(1, PasswordMetrics.maxLengthSequence("qwertyuio".getBytes())); 117 assertEquals(3, PasswordMetrics.maxLengthSequence("@ABC".getBytes())); 118 // anything that repeats 119 assertEquals(4, PasswordMetrics.maxLengthSequence(";;;;".getBytes())); 120 // ordered, but not composed of alphas or digits 121 assertEquals(1, PasswordMetrics.maxLengthSequence(":;<=>".getBytes())); 122 } 123 124 @Test testDetermineComplexity_none()125 public void testDetermineComplexity_none() { 126 assertEquals(PASSWORD_COMPLEXITY_NONE, 127 new PasswordMetrics(CREDENTIAL_TYPE_NONE).determineComplexity()); 128 } 129 130 @Test testDetermineComplexity_lowSomething()131 public void testDetermineComplexity_lowSomething() { 132 assertEquals(PASSWORD_COMPLEXITY_LOW, 133 new PasswordMetrics(CREDENTIAL_TYPE_PATTERN).determineComplexity()); 134 } 135 136 @Test testDetermineComplexity_lowNumeric()137 public void testDetermineComplexity_lowNumeric() { 138 assertEquals(PASSWORD_COMPLEXITY_LOW, metricsForPin("1234").determineComplexity()); 139 } 140 141 @Test testDetermineComplexity_lowNumericComplex()142 public void testDetermineComplexity_lowNumericComplex() { 143 assertEquals(PASSWORD_COMPLEXITY_LOW, metricsForPin("124").determineComplexity()); 144 } 145 146 @Test testDetermineComplexity_lowAlphabetic()147 public void testDetermineComplexity_lowAlphabetic() { 148 assertEquals(PASSWORD_COMPLEXITY_LOW, metricsForPassword("a!").determineComplexity()); 149 } 150 151 @Test testDetermineComplexity_lowAlphanumeric()152 public void testDetermineComplexity_lowAlphanumeric() { 153 assertEquals(PASSWORD_COMPLEXITY_LOW, metricsForPassword("a!1").determineComplexity()); 154 } 155 156 @Test testDetermineComplexity_mediumNumericComplex()157 public void testDetermineComplexity_mediumNumericComplex() { 158 assertEquals(PASSWORD_COMPLEXITY_MEDIUM, metricsForPin("1238").determineComplexity()); 159 } 160 161 @Test testDetermineComplexity_mediumAlphabetic()162 public void testDetermineComplexity_mediumAlphabetic() { 163 assertEquals(PASSWORD_COMPLEXITY_MEDIUM, metricsForPassword("ab!c").determineComplexity()); 164 } 165 166 @Test testDetermineComplexity_mediumAlphanumeric()167 public void testDetermineComplexity_mediumAlphanumeric() { 168 assertEquals(PASSWORD_COMPLEXITY_MEDIUM, metricsForPassword("ab!1").determineComplexity()); 169 } 170 171 @Test testDetermineComplexity_highNumericComplex()172 public void testDetermineComplexity_highNumericComplex() { 173 assertEquals(PASSWORD_COMPLEXITY_HIGH, metricsForPin("12389647!").determineComplexity()); 174 } 175 176 @Test testDetermineComplexity_highAlphabetic()177 public void testDetermineComplexity_highAlphabetic() { 178 assertEquals(PASSWORD_COMPLEXITY_HIGH, 179 metricsForPassword("alphabetic!").determineComplexity()); 180 } 181 182 @Test testDetermineComplexity_highAlphanumeric()183 public void testDetermineComplexity_highAlphanumeric() { 184 assertEquals(PASSWORD_COMPLEXITY_HIGH, 185 metricsForPassword("alphanumeric123!").determineComplexity()); 186 } 187 188 @Test testSanitizeComplexityLevel_none()189 public void testSanitizeComplexityLevel_none() { 190 assertEquals(PASSWORD_COMPLEXITY_NONE, sanitizeComplexityLevel(PASSWORD_COMPLEXITY_NONE)); 191 192 } 193 194 @Test testSanitizeComplexityLevel_low()195 public void testSanitizeComplexityLevel_low() { 196 assertEquals(PASSWORD_COMPLEXITY_LOW, sanitizeComplexityLevel(PASSWORD_COMPLEXITY_LOW)); 197 } 198 199 @Test testSanitizeComplexityLevel_medium()200 public void testSanitizeComplexityLevel_medium() { 201 assertEquals( 202 PASSWORD_COMPLEXITY_MEDIUM, sanitizeComplexityLevel(PASSWORD_COMPLEXITY_MEDIUM)); 203 } 204 205 @Test testSanitizeComplexityLevel_high()206 public void testSanitizeComplexityLevel_high() { 207 assertEquals(PASSWORD_COMPLEXITY_HIGH, sanitizeComplexityLevel(PASSWORD_COMPLEXITY_HIGH)); 208 } 209 210 @Test testSanitizeComplexityLevel_invalid()211 public void testSanitizeComplexityLevel_invalid() { 212 assertEquals(PASSWORD_COMPLEXITY_NONE, sanitizeComplexityLevel(-1)); 213 } 214 215 @Test testComplexityLevelToMinQuality_none()216 public void testComplexityLevelToMinQuality_none() { 217 assertEquals(PASSWORD_QUALITY_UNSPECIFIED, 218 complexityLevelToMinQuality(PASSWORD_COMPLEXITY_NONE)); 219 } 220 221 @Test testComplexityLevelToMinQuality_low()222 public void testComplexityLevelToMinQuality_low() { 223 assertEquals(PASSWORD_QUALITY_SOMETHING, 224 complexityLevelToMinQuality(PASSWORD_COMPLEXITY_LOW)); 225 } 226 227 @Test testComplexityLevelToMinQuality_medium()228 public void testComplexityLevelToMinQuality_medium() { 229 assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, 230 complexityLevelToMinQuality(PASSWORD_COMPLEXITY_MEDIUM)); 231 } 232 233 @Test testComplexityLevelToMinQuality_high()234 public void testComplexityLevelToMinQuality_high() { 235 assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, 236 complexityLevelToMinQuality(PASSWORD_COMPLEXITY_HIGH)); 237 } 238 239 @Test testComplexityLevelToMinQuality_invalid()240 public void testComplexityLevelToMinQuality_invalid() { 241 assertEquals(PASSWORD_QUALITY_UNSPECIFIED, complexityLevelToMinQuality(-1)); 242 } 243 244 @Test testMerge_single()245 public void testMerge_single() { 246 PasswordMetrics metrics = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD); 247 assertEquals(CREDENTIAL_TYPE_PASSWORD, 248 PasswordMetrics.merge(Collections.singletonList(metrics)).credType); 249 } 250 251 @Test testMerge_credentialTypes()252 public void testMerge_credentialTypes() { 253 PasswordMetrics none = new PasswordMetrics(CREDENTIAL_TYPE_NONE); 254 PasswordMetrics pattern = new PasswordMetrics(CREDENTIAL_TYPE_PATTERN); 255 PasswordMetrics password = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD); 256 assertEquals(CREDENTIAL_TYPE_PATTERN, 257 PasswordMetrics.merge(Arrays.asList(new PasswordMetrics[]{none, pattern})) 258 .credType); 259 assertEquals(CREDENTIAL_TYPE_PASSWORD, 260 PasswordMetrics.merge(Arrays.asList(new PasswordMetrics[]{none, password})) 261 .credType); 262 assertEquals(CREDENTIAL_TYPE_PASSWORD, 263 PasswordMetrics.merge(Arrays.asList(new PasswordMetrics[]{password, pattern})) 264 .credType); 265 } 266 267 @Test testValidatePasswordMetrics_credentialTypes()268 public void testValidatePasswordMetrics_credentialTypes() { 269 PasswordMetrics none = new PasswordMetrics(CREDENTIAL_TYPE_NONE); 270 PasswordMetrics pattern = new PasswordMetrics(CREDENTIAL_TYPE_PATTERN); 271 PasswordMetrics password = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD); 272 PasswordMetrics pin = new PasswordMetrics(CREDENTIAL_TYPE_PIN); 273 274 // To pass minimal length check. 275 password.length = 4; 276 pin.length = 4; 277 278 // No errors expected, credential is of stronger or equal type. 279 assertValidationErrors( 280 validatePasswordMetrics(none, PASSWORD_COMPLEXITY_NONE, none)); 281 assertValidationErrors( 282 validatePasswordMetrics(none, PASSWORD_COMPLEXITY_NONE, pattern)); 283 assertValidationErrors( 284 validatePasswordMetrics(none, PASSWORD_COMPLEXITY_NONE, password)); 285 assertValidationErrors( 286 validatePasswordMetrics(none, PASSWORD_COMPLEXITY_NONE, pin)); 287 assertValidationErrors( 288 validatePasswordMetrics(pattern, PASSWORD_COMPLEXITY_NONE, pattern)); 289 assertValidationErrors( 290 validatePasswordMetrics(pattern, PASSWORD_COMPLEXITY_NONE, password)); 291 assertValidationErrors( 292 validatePasswordMetrics(password, PASSWORD_COMPLEXITY_NONE, password)); 293 assertValidationErrors( 294 validatePasswordMetrics(pin, PASSWORD_COMPLEXITY_NONE, pin)); 295 296 // Now actual credential type is weaker than required: 297 assertValidationErrors( 298 validatePasswordMetrics(pattern, PASSWORD_COMPLEXITY_NONE, none), 299 PasswordValidationError.WEAK_CREDENTIAL_TYPE, 0); 300 assertValidationErrors( 301 validatePasswordMetrics(password, PASSWORD_COMPLEXITY_NONE, none), 302 PasswordValidationError.WEAK_CREDENTIAL_TYPE, 0); 303 assertValidationErrors( 304 validatePasswordMetrics(password, PASSWORD_COMPLEXITY_NONE, pattern), 305 PasswordValidationError.WEAK_CREDENTIAL_TYPE, 0); 306 assertValidationErrors( 307 validatePasswordMetrics(password, PASSWORD_COMPLEXITY_NONE, pin), 308 PasswordValidationError.WEAK_CREDENTIAL_TYPE, 0); 309 } 310 311 @Test testValidatePasswordMetrics_pinAndComplexityHigh()312 public void testValidatePasswordMetrics_pinAndComplexityHigh() { 313 PasswordMetrics adminMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PIN); 314 PasswordMetrics actualMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PIN); 315 actualMetrics.length = 6; 316 actualMetrics.seqLength = 1; 317 318 assertValidationErrors( 319 validatePasswordMetrics(adminMetrics, PASSWORD_COMPLEXITY_HIGH, actualMetrics), 320 PasswordValidationError.TOO_SHORT, 8); 321 } 322 323 @Test testValidatePasswordMetrics_nonAllNumberPasswordAndComplexityHigh()324 public void testValidatePasswordMetrics_nonAllNumberPasswordAndComplexityHigh() { 325 PasswordMetrics adminMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD); 326 PasswordMetrics actualMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD); 327 actualMetrics.length = 5; 328 actualMetrics.nonNumeric = 1; 329 actualMetrics.seqLength = 1; 330 331 assertValidationErrors( 332 validatePasswordMetrics(adminMetrics, PASSWORD_COMPLEXITY_HIGH, actualMetrics), 333 PasswordValidationError.TOO_SHORT, 6); 334 } 335 336 @Test testValidatePasswordMetrics_allNumberPasswordAndComplexityHigh()337 public void testValidatePasswordMetrics_allNumberPasswordAndComplexityHigh() { 338 PasswordMetrics adminMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD); 339 PasswordMetrics actualMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD); 340 actualMetrics.length = 6; 341 actualMetrics.seqLength = 1; 342 343 assertValidationErrors( 344 validatePasswordMetrics(adminMetrics, PASSWORD_COMPLEXITY_HIGH, actualMetrics), 345 PasswordValidationError.TOO_SHORT_WHEN_ALL_NUMERIC, 8); 346 } 347 348 @Test testValidatePasswordMetrics_allNumberPasswordAndRequireNonNumeric()349 public void testValidatePasswordMetrics_allNumberPasswordAndRequireNonNumeric() { 350 PasswordMetrics adminMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD); 351 adminMetrics.nonNumeric = 1; 352 PasswordMetrics actualMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD); 353 actualMetrics.length = 6; 354 actualMetrics.seqLength = 1; 355 356 assertValidationErrors( 357 validatePasswordMetrics(adminMetrics, PASSWORD_COMPLEXITY_HIGH, actualMetrics), 358 PasswordValidationError.NOT_ENOUGH_NON_DIGITS, 1); 359 } 360 361 @Test testValidateCredential_none()362 public void testValidateCredential_none() { 363 PasswordMetrics adminMetrics; 364 LockscreenCredential none = LockscreenCredential.createNone(); 365 366 adminMetrics = new PasswordMetrics(CREDENTIAL_TYPE_NONE); 367 assertValidationErrors( 368 validateCredential(adminMetrics, PASSWORD_COMPLEXITY_NONE, none)); 369 370 adminMetrics = new PasswordMetrics(CREDENTIAL_TYPE_PIN); 371 assertValidationErrors( 372 validateCredential(adminMetrics, PASSWORD_COMPLEXITY_NONE, none), 373 PasswordValidationError.WEAK_CREDENTIAL_TYPE, 0); 374 } 375 376 @Test testValidateCredential_password()377 public void testValidateCredential_password() { 378 PasswordMetrics adminMetrics; 379 LockscreenCredential password; 380 381 adminMetrics = new PasswordMetrics(CREDENTIAL_TYPE_NONE); 382 password = LockscreenCredential.createPassword("password"); 383 assertValidationErrors( 384 validateCredential(adminMetrics, PASSWORD_COMPLEXITY_LOW, password)); 385 386 // Test that validateCredential() checks LockscreenCredential#hasInvalidChars(). 387 adminMetrics = new PasswordMetrics(CREDENTIAL_TYPE_NONE); 388 password = LockscreenCredential.createPassword("™™™™"); 389 assertTrue(password.hasInvalidChars()); 390 assertValidationErrors( 391 validateCredential(adminMetrics, PASSWORD_COMPLEXITY_LOW, password), 392 PasswordValidationError.CONTAINS_INVALID_CHARACTERS, 0); 393 394 // Test one more case where validateCredential() should reject the password. Beyond this, 395 // the unit tests for the lower-level method validatePasswordMetrics() should be sufficient. 396 adminMetrics = new PasswordMetrics(CREDENTIAL_TYPE_NONE); 397 adminMetrics.length = 6; 398 password = LockscreenCredential.createPassword("pass"); 399 assertValidationErrors( 400 validateCredential(adminMetrics, PASSWORD_COMPLEXITY_LOW, password), 401 PasswordValidationError.TOO_SHORT, 6); 402 } 403 createPattern(String patternString)404 private LockscreenCredential createPattern(String patternString) { 405 return LockscreenCredential.createPattern(LockPatternUtils.byteArrayToPattern( 406 patternString.getBytes())); 407 } 408 metricsForPassword(String password)409 private static PasswordMetrics metricsForPassword(String password) { 410 return PasswordMetrics.computeForCredential(LockscreenCredential.createPassword(password)); 411 } 412 metricsForPin(String pin)413 private static PasswordMetrics metricsForPin(String pin) { 414 return PasswordMetrics.computeForCredential(LockscreenCredential.createPin(pin)); 415 } 416 417 @Test testValidateCredential_pattern()418 public void testValidateCredential_pattern() { 419 PasswordMetrics adminMetrics = new PasswordMetrics(CREDENTIAL_TYPE_NONE); 420 assertValidationErrors( 421 validateCredential(adminMetrics, PASSWORD_COMPLEXITY_NONE, createPattern("123")), 422 PasswordValidationError.TOO_SHORT, 4); 423 assertValidationErrors( 424 validateCredential(adminMetrics, PASSWORD_COMPLEXITY_NONE, createPattern("1234"))); 425 } 426 427 /** 428 * @param expected sequence of validation error codes followed by requirement values, must have 429 * even number of elements. Empty means no errors. 430 */ assertValidationErrors( List<PasswordValidationError> actualErrors, int... expected)431 private void assertValidationErrors( 432 List<PasswordValidationError> actualErrors, int... expected) { 433 assertEquals("Test programming error: content shoud have even number of elements", 434 0, expected.length % 2); 435 assertEquals("wrong number of validation errors", expected.length / 2, actualErrors.size()); 436 HashMap<Integer, Integer> errorMap = new HashMap<>(); 437 for (PasswordValidationError error : actualErrors) { 438 errorMap.put(error.errorCode, error.requirement); 439 } 440 441 for (int i = 0; i < expected.length / 2; i++) { 442 final int expectedError = expected[i * 2]; 443 final int expectedRequirement = expected[i * 2 + 1]; 444 assertTrue("error expected but not reported: " + expectedError, 445 errorMap.containsKey(expectedError)); 446 assertEquals("unexpected requirement for error: " + expectedError, 447 Integer.valueOf(expectedRequirement), errorMap.get(expectedError)); 448 } 449 } 450 } 451