Andrew Scull | 5f9e6f3 | 2016-08-02 14:22:17 +0100 | [diff] [blame] | 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 | |
Bernard Chau | e958655 | 2018-11-29 10:59:31 +0000 | [diff] [blame] | 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; |
Bernard Chau | 60e0f7f | 2018-11-29 16:36:39 +0000 | [diff] [blame] | 23 | import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC; |
| 24 | import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC; |
| 25 | import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX; |
| 26 | import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; |
| 27 | import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; |
Bernard Chau | e958655 | 2018-11-29 10:59:31 +0000 | [diff] [blame] | 28 | |
Andrew Scull | 5f9e6f3 | 2016-08-02 14:22:17 +0100 | [diff] [blame] | 29 | import android.annotation.IntDef; |
| 30 | import android.annotation.NonNull; |
Bernard Chau | e958655 | 2018-11-29 10:59:31 +0000 | [diff] [blame] | 31 | import android.app.admin.DevicePolicyManager.PasswordComplexity; |
Andrew Scull | 5f9e6f3 | 2016-08-02 14:22:17 +0100 | [diff] [blame] | 32 | import android.os.Parcel; |
Rubin Xu | 7cf4509 | 2017-08-28 11:47:35 +0100 | [diff] [blame] | 33 | import android.os.Parcelable; |
Andrew Scull | 5f9e6f3 | 2016-08-02 14:22:17 +0100 | [diff] [blame] | 34 | |
Bernard Chau | 60e0f7f | 2018-11-29 16:36:39 +0000 | [diff] [blame] | 35 | import com.android.internal.annotations.VisibleForTesting; |
| 36 | |
Andrew Scull | 5f9e6f3 | 2016-08-02 14:22:17 +0100 | [diff] [blame] | 37 | import java.lang.annotation.Retention; |
| 38 | import java.lang.annotation.RetentionPolicy; |
Andrew Scull | 5f9e6f3 | 2016-08-02 14:22:17 +0100 | [diff] [blame] | 39 | |
| 40 | /** |
| 41 | * A class that represents the metrics of a password that are used to decide whether or not a |
| 42 | * password meets the requirements. |
| 43 | * |
| 44 | * {@hide} |
| 45 | */ |
| 46 | public class PasswordMetrics implements Parcelable { |
| 47 | // Maximum allowed number of repeated or ordered characters in a sequence before we'll |
| 48 | // consider it a complex PIN/password. |
| 49 | public static final int MAX_ALLOWED_SEQUENCE = 3; |
| 50 | |
Bernard Chau | e958655 | 2018-11-29 10:59:31 +0000 | [diff] [blame] | 51 | // TODO(b/120536847): refactor isActivePasswordSufficient logic so that the actual password |
| 52 | // quality is not overwritten |
Andrew Scull | 5f9e6f3 | 2016-08-02 14:22:17 +0100 | [diff] [blame] | 53 | public int quality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; |
| 54 | public int length = 0; |
| 55 | public int letters = 0; |
| 56 | public int upperCase = 0; |
| 57 | public int lowerCase = 0; |
| 58 | public int numeric = 0; |
| 59 | public int symbols = 0; |
| 60 | public int nonLetter = 0; |
| 61 | |
| 62 | public PasswordMetrics() {} |
| 63 | |
Bernard Chau | e958655 | 2018-11-29 10:59:31 +0000 | [diff] [blame] | 64 | public PasswordMetrics(int quality) { |
| 65 | this.quality = quality; |
| 66 | } |
| 67 | |
Andrew Scull | 5f9e6f3 | 2016-08-02 14:22:17 +0100 | [diff] [blame] | 68 | public PasswordMetrics(int quality, int length) { |
| 69 | this.quality = quality; |
| 70 | this.length = length; |
| 71 | } |
| 72 | |
| 73 | public PasswordMetrics(int quality, int length, int letters, int upperCase, int lowerCase, |
| 74 | int numeric, int symbols, int nonLetter) { |
| 75 | this(quality, length); |
| 76 | this.letters = letters; |
| 77 | this.upperCase = upperCase; |
| 78 | this.lowerCase = lowerCase; |
| 79 | this.numeric = numeric; |
| 80 | this.symbols = symbols; |
| 81 | this.nonLetter = nonLetter; |
| 82 | } |
| 83 | |
| 84 | private PasswordMetrics(Parcel in) { |
| 85 | quality = in.readInt(); |
| 86 | length = in.readInt(); |
| 87 | letters = in.readInt(); |
| 88 | upperCase = in.readInt(); |
| 89 | lowerCase = in.readInt(); |
| 90 | numeric = in.readInt(); |
| 91 | symbols = in.readInt(); |
| 92 | nonLetter = in.readInt(); |
| 93 | } |
| 94 | |
Bernard Chau | 60e0f7f | 2018-11-29 16:36:39 +0000 | [diff] [blame] | 95 | /** Returns the min quality allowed by {@code complexityLevel}. */ |
| 96 | public static int complexityLevelToMinQuality(@PasswordComplexity int complexityLevel) { |
| 97 | // this would be the quality of the first metrics since mMetrics is sorted in ascending |
| 98 | // order of quality |
| 99 | return PasswordComplexityBucket |
| 100 | .complexityLevelToBucket(complexityLevel).mMetrics[0].quality; |
| 101 | } |
| 102 | |
| 103 | /** |
| 104 | * Returns a merged minimum {@link PasswordMetrics} requirements that a new password must meet |
| 105 | * to fulfil {@code requestedQuality}, {@code requiresNumeric} and {@code |
| 106 | * requiresLettersOrSymbols}, which are derived from {@link DevicePolicyManager} requirements, |
| 107 | * and {@code complexityLevel}. |
| 108 | * |
| 109 | * <p>Note that we are taking {@code userEnteredPasswordQuality} into account because there are |
| 110 | * more than one set of metrics to meet the minimum complexity requirement and inspecting what |
| 111 | * the user has entered can help determine whether the alphabetic or alphanumeric set of metrics |
| 112 | * should be used. For example, suppose minimum complexity requires either ALPHABETIC(8+), or |
| 113 | * ALPHANUMERIC(6+). If the user has entered "a", the length requirement displayed on the UI |
| 114 | * would be 8. Then the user appends "1" to make it "a1". We now know the user is entering |
| 115 | * an alphanumeric password so we would update the min complexity required min length to 6. |
| 116 | */ |
| 117 | public static PasswordMetrics getMinimumMetrics(@PasswordComplexity int complexityLevel, |
| 118 | int userEnteredPasswordQuality, int requestedQuality, boolean requiresNumeric, |
| 119 | boolean requiresLettersOrSymbols) { |
| 120 | int targetQuality = Math.max( |
| 121 | userEnteredPasswordQuality, |
| 122 | getActualRequiredQuality( |
| 123 | requestedQuality, requiresNumeric, requiresLettersOrSymbols)); |
| 124 | return getTargetQualityMetrics(complexityLevel, targetQuality); |
| 125 | } |
| 126 | |
| 127 | /** |
| 128 | * Returns the {@link PasswordMetrics} at {@code complexityLevel} which the metrics quality |
| 129 | * is the same as {@code targetQuality}. |
| 130 | * |
| 131 | * <p>If {@code complexityLevel} does not allow {@code targetQuality}, returns the metrics |
| 132 | * with the min quality at {@code complexityLevel}. |
| 133 | */ |
| 134 | // TODO(bernardchau): update tests to test getMinimumMetrics and change this to be private |
| 135 | @VisibleForTesting |
| 136 | public static PasswordMetrics getTargetQualityMetrics( |
| 137 | @PasswordComplexity int complexityLevel, int targetQuality) { |
| 138 | PasswordComplexityBucket targetBucket = |
| 139 | PasswordComplexityBucket.complexityLevelToBucket(complexityLevel); |
| 140 | for (PasswordMetrics metrics : targetBucket.mMetrics) { |
| 141 | if (targetQuality == metrics.quality) { |
| 142 | return metrics; |
| 143 | } |
| 144 | } |
| 145 | // none of the metrics at complexityLevel has targetQuality, return metrics with min quality |
| 146 | // see test case testGetMinimumMetrics_actualRequiredQualityStricter for an example, where |
| 147 | // min complexity allows at least NUMERIC_COMPLEX, user has not entered anything yet, and |
| 148 | // requested quality is NUMERIC |
| 149 | return targetBucket.mMetrics[0]; |
| 150 | } |
| 151 | |
| 152 | /** |
| 153 | * Finds out the actual quality requirement based on whether quality is {@link |
| 154 | * DevicePolicyManager#PASSWORD_QUALITY_COMPLEX} and whether digits, letters or symbols are |
| 155 | * required. |
| 156 | */ |
| 157 | @VisibleForTesting |
| 158 | // TODO(bernardchau): update tests to test getMinimumMetrics and change this to be private |
| 159 | public static int getActualRequiredQuality( |
| 160 | int requestedQuality, boolean requiresNumeric, boolean requiresLettersOrSymbols) { |
| 161 | if (requestedQuality != PASSWORD_QUALITY_COMPLEX) { |
| 162 | return requestedQuality; |
| 163 | } |
| 164 | |
| 165 | // find out actual password quality from complex requirements |
| 166 | if (requiresNumeric && requiresLettersOrSymbols) { |
| 167 | return PASSWORD_QUALITY_ALPHANUMERIC; |
| 168 | } |
| 169 | if (requiresLettersOrSymbols) { |
| 170 | return PASSWORD_QUALITY_ALPHABETIC; |
| 171 | } |
| 172 | if (requiresNumeric) { |
| 173 | // cannot specify numeric complex using complex quality so this must be numeric |
| 174 | return PASSWORD_QUALITY_NUMERIC; |
| 175 | } |
| 176 | |
| 177 | // reaching here means dpm sets quality to complex without specifying any requirements |
| 178 | return PASSWORD_QUALITY_UNSPECIFIED; |
| 179 | } |
| 180 | |
| 181 | /** |
| 182 | * Returns {@code complexityLevel} or {@link DevicePolicyManager#PASSWORD_COMPLEXITY_NONE} |
| 183 | * if {@code complexityLevel} is not valid. |
| 184 | */ |
| 185 | @PasswordComplexity |
| 186 | public static int sanitizeComplexityLevel(@PasswordComplexity int complexityLevel) { |
| 187 | return PasswordComplexityBucket.complexityLevelToBucket(complexityLevel).mComplexityLevel; |
| 188 | } |
| 189 | |
Andrew Scull | 5f9e6f3 | 2016-08-02 14:22:17 +0100 | [diff] [blame] | 190 | public boolean isDefault() { |
| 191 | return quality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED |
| 192 | && length == 0 && letters == 0 && upperCase == 0 && lowerCase == 0 |
| 193 | && numeric == 0 && symbols == 0 && nonLetter == 0; |
| 194 | } |
| 195 | |
| 196 | @Override |
| 197 | public int describeContents() { |
| 198 | return 0; |
| 199 | } |
| 200 | |
| 201 | @Override |
| 202 | public void writeToParcel(Parcel dest, int flags) { |
| 203 | dest.writeInt(quality); |
| 204 | dest.writeInt(length); |
| 205 | dest.writeInt(letters); |
| 206 | dest.writeInt(upperCase); |
| 207 | dest.writeInt(lowerCase); |
| 208 | dest.writeInt(numeric); |
| 209 | dest.writeInt(symbols); |
| 210 | dest.writeInt(nonLetter); |
| 211 | } |
| 212 | |
| 213 | public static final Parcelable.Creator<PasswordMetrics> CREATOR |
| 214 | = new Parcelable.Creator<PasswordMetrics>() { |
| 215 | public PasswordMetrics createFromParcel(Parcel in) { |
| 216 | return new PasswordMetrics(in); |
| 217 | } |
| 218 | |
| 219 | public PasswordMetrics[] newArray(int size) { |
| 220 | return new PasswordMetrics[size]; |
| 221 | } |
| 222 | }; |
| 223 | |
| 224 | public static PasswordMetrics computeForPassword(@NonNull String password) { |
| 225 | // Analyse the characters used |
| 226 | int letters = 0; |
| 227 | int upperCase = 0; |
| 228 | int lowerCase = 0; |
| 229 | int numeric = 0; |
| 230 | int symbols = 0; |
| 231 | int nonLetter = 0; |
| 232 | final int length = password.length(); |
| 233 | for (int i = 0; i < length; i++) { |
| 234 | switch (categoryChar(password.charAt(i))) { |
| 235 | case CHAR_LOWER_CASE: |
| 236 | letters++; |
| 237 | lowerCase++; |
| 238 | break; |
| 239 | case CHAR_UPPER_CASE: |
| 240 | letters++; |
| 241 | upperCase++; |
| 242 | break; |
| 243 | case CHAR_DIGIT: |
| 244 | numeric++; |
| 245 | nonLetter++; |
| 246 | break; |
| 247 | case CHAR_SYMBOL: |
| 248 | symbols++; |
| 249 | nonLetter++; |
| 250 | break; |
| 251 | } |
| 252 | } |
| 253 | |
| 254 | // Determine the quality of the password |
| 255 | final boolean hasNumeric = numeric > 0; |
| 256 | final boolean hasNonNumeric = (letters + symbols) > 0; |
| 257 | final int quality; |
| 258 | if (hasNonNumeric && hasNumeric) { |
| 259 | quality = DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC; |
| 260 | } else if (hasNonNumeric) { |
| 261 | quality = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC; |
| 262 | } else if (hasNumeric) { |
| 263 | quality = maxLengthSequence(password) > MAX_ALLOWED_SEQUENCE |
| 264 | ? DevicePolicyManager.PASSWORD_QUALITY_NUMERIC |
| 265 | : DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX; |
| 266 | } else { |
| 267 | quality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; |
| 268 | } |
| 269 | |
| 270 | return new PasswordMetrics( |
| 271 | quality, length, letters, upperCase, lowerCase, numeric, symbols, nonLetter); |
| 272 | } |
| 273 | |
Rubin Xu | 7cf4509 | 2017-08-28 11:47:35 +0100 | [diff] [blame] | 274 | @Override |
| 275 | public boolean equals(Object other) { |
| 276 | if (!(other instanceof PasswordMetrics)) { |
| 277 | return false; |
| 278 | } |
| 279 | PasswordMetrics o = (PasswordMetrics) other; |
| 280 | return this.quality == o.quality |
| 281 | && this.length == o.length |
| 282 | && this.letters == o.letters |
| 283 | && this.upperCase == o.upperCase |
| 284 | && this.lowerCase == o.lowerCase |
| 285 | && this.numeric == o.numeric |
| 286 | && this.symbols == o.symbols |
| 287 | && this.nonLetter == o.nonLetter; |
| 288 | } |
| 289 | |
Bernard Chau | e958655 | 2018-11-29 10:59:31 +0000 | [diff] [blame] | 290 | private boolean satisfiesBucket(PasswordMetrics... bucket) { |
| 291 | for (PasswordMetrics metrics : bucket) { |
| 292 | if (this.quality == metrics.quality) { |
| 293 | return this.length >= metrics.length; |
| 294 | } |
| 295 | } |
| 296 | return false; |
| 297 | } |
| 298 | |
Andrew Scull | 5f9e6f3 | 2016-08-02 14:22:17 +0100 | [diff] [blame] | 299 | /* |
| 300 | * Returns the maximum length of a sequential characters. A sequence is defined as |
| 301 | * monotonically increasing characters with a constant interval or the same character repeated. |
| 302 | * |
| 303 | * For example: |
| 304 | * maxLengthSequence("1234") == 4 |
| 305 | * maxLengthSequence("13579") == 5 |
| 306 | * maxLengthSequence("1234abc") == 4 |
| 307 | * maxLengthSequence("aabc") == 3 |
| 308 | * maxLengthSequence("qwertyuio") == 1 |
| 309 | * maxLengthSequence("@ABC") == 3 |
| 310 | * maxLengthSequence(";;;;") == 4 (anything that repeats) |
| 311 | * maxLengthSequence(":;<=>") == 1 (ordered, but not composed of alphas or digits) |
| 312 | * |
| 313 | * @param string the pass |
| 314 | * @return the number of sequential letters or digits |
| 315 | */ |
| 316 | public static int maxLengthSequence(@NonNull String string) { |
| 317 | if (string.length() == 0) return 0; |
| 318 | char previousChar = string.charAt(0); |
| 319 | @CharacterCatagory int category = categoryChar(previousChar); //current sequence category |
| 320 | int diff = 0; //difference between two consecutive characters |
| 321 | boolean hasDiff = false; //if we are currently targeting a sequence |
| 322 | int maxLength = 0; //maximum length of a sequence already found |
| 323 | int startSequence = 0; //where the current sequence started |
| 324 | for (int current = 1; current < string.length(); current++) { |
| 325 | char currentChar = string.charAt(current); |
| 326 | @CharacterCatagory int categoryCurrent = categoryChar(currentChar); |
| 327 | int currentDiff = (int) currentChar - (int) previousChar; |
| 328 | if (categoryCurrent != category || Math.abs(currentDiff) > maxDiffCategory(category)) { |
| 329 | maxLength = Math.max(maxLength, current - startSequence); |
| 330 | startSequence = current; |
| 331 | hasDiff = false; |
| 332 | category = categoryCurrent; |
| 333 | } |
| 334 | else { |
| 335 | if(hasDiff && currentDiff != diff) { |
| 336 | maxLength = Math.max(maxLength, current - startSequence); |
| 337 | startSequence = current - 1; |
| 338 | } |
| 339 | diff = currentDiff; |
| 340 | hasDiff = true; |
| 341 | } |
| 342 | previousChar = currentChar; |
| 343 | } |
| 344 | maxLength = Math.max(maxLength, string.length() - startSequence); |
| 345 | return maxLength; |
| 346 | } |
| 347 | |
| 348 | @Retention(RetentionPolicy.SOURCE) |
Jeff Sharkey | ce8db99 | 2017-12-13 20:05:05 -0700 | [diff] [blame] | 349 | @IntDef(prefix = { "CHAR_" }, value = { |
| 350 | CHAR_UPPER_CASE, |
| 351 | CHAR_LOWER_CASE, |
| 352 | CHAR_DIGIT, |
| 353 | CHAR_SYMBOL |
| 354 | }) |
Andrew Scull | 5f9e6f3 | 2016-08-02 14:22:17 +0100 | [diff] [blame] | 355 | private @interface CharacterCatagory {} |
| 356 | private static final int CHAR_LOWER_CASE = 0; |
| 357 | private static final int CHAR_UPPER_CASE = 1; |
| 358 | private static final int CHAR_DIGIT = 2; |
| 359 | private static final int CHAR_SYMBOL = 3; |
| 360 | |
| 361 | @CharacterCatagory |
| 362 | private static int categoryChar(char c) { |
| 363 | if ('a' <= c && c <= 'z') return CHAR_LOWER_CASE; |
| 364 | if ('A' <= c && c <= 'Z') return CHAR_UPPER_CASE; |
| 365 | if ('0' <= c && c <= '9') return CHAR_DIGIT; |
| 366 | return CHAR_SYMBOL; |
| 367 | } |
| 368 | |
| 369 | private static int maxDiffCategory(@CharacterCatagory int category) { |
| 370 | switch (category) { |
| 371 | case CHAR_LOWER_CASE: |
| 372 | case CHAR_UPPER_CASE: |
| 373 | return 1; |
| 374 | case CHAR_DIGIT: |
| 375 | return 10; |
| 376 | default: |
| 377 | return 0; |
| 378 | } |
| 379 | } |
Bernard Chau | e958655 | 2018-11-29 10:59:31 +0000 | [diff] [blame] | 380 | |
| 381 | /** Determines the {@link PasswordComplexity} of this {@link PasswordMetrics}. */ |
| 382 | @PasswordComplexity |
| 383 | public int determineComplexity() { |
| 384 | for (PasswordComplexityBucket bucket : PasswordComplexityBucket.BUCKETS) { |
Bernard Chau | 60e0f7f | 2018-11-29 16:36:39 +0000 | [diff] [blame] | 385 | if (satisfiesBucket(bucket.mMetrics)) { |
Bernard Chau | e958655 | 2018-11-29 10:59:31 +0000 | [diff] [blame] | 386 | return bucket.mComplexityLevel; |
| 387 | } |
| 388 | } |
| 389 | return PASSWORD_COMPLEXITY_NONE; |
| 390 | } |
| 391 | |
| 392 | /** |
| 393 | * Requirements in terms of {@link PasswordMetrics} for each {@link PasswordComplexity}. |
| 394 | */ |
Bernard Chau | 60e0f7f | 2018-11-29 16:36:39 +0000 | [diff] [blame] | 395 | private static class PasswordComplexityBucket { |
Bernard Chau | e958655 | 2018-11-29 10:59:31 +0000 | [diff] [blame] | 396 | /** |
| 397 | * Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_HIGH} in terms of |
| 398 | * {@link PasswordMetrics}. |
| 399 | */ |
| 400 | private static final PasswordComplexityBucket HIGH = |
| 401 | new PasswordComplexityBucket( |
| 402 | PASSWORD_COMPLEXITY_HIGH, |
| 403 | new PasswordMetrics( |
Bernard Chau | 60e0f7f | 2018-11-29 16:36:39 +0000 | [diff] [blame] | 404 | DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */ |
| 405 | 8), |
Bernard Chau | e958655 | 2018-11-29 10:59:31 +0000 | [diff] [blame] | 406 | new PasswordMetrics( |
| 407 | DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, /* length= */ 6), |
| 408 | new PasswordMetrics( |
Bernard Chau | 60e0f7f | 2018-11-29 16:36:39 +0000 | [diff] [blame] | 409 | DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */ |
| 410 | 6)); |
Bernard Chau | e958655 | 2018-11-29 10:59:31 +0000 | [diff] [blame] | 411 | |
| 412 | /** |
| 413 | * Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_MEDIUM} in terms of |
| 414 | * {@link PasswordMetrics}. |
| 415 | */ |
| 416 | private static final PasswordComplexityBucket MEDIUM = |
| 417 | new PasswordComplexityBucket( |
| 418 | PASSWORD_COMPLEXITY_MEDIUM, |
| 419 | new PasswordMetrics( |
Bernard Chau | 60e0f7f | 2018-11-29 16:36:39 +0000 | [diff] [blame] | 420 | DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */ |
| 421 | 4), |
Bernard Chau | e958655 | 2018-11-29 10:59:31 +0000 | [diff] [blame] | 422 | new PasswordMetrics( |
| 423 | DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, /* length= */ 4), |
| 424 | new PasswordMetrics( |
Bernard Chau | 60e0f7f | 2018-11-29 16:36:39 +0000 | [diff] [blame] | 425 | DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */ |
Bernard Chau | e958655 | 2018-11-29 10:59:31 +0000 | [diff] [blame] | 426 | 4)); |
| 427 | |
| 428 | /** |
| 429 | * Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_LOW} in terms of |
| 430 | * {@link PasswordMetrics}. |
| 431 | */ |
| 432 | private static final PasswordComplexityBucket LOW = |
| 433 | new PasswordComplexityBucket( |
| 434 | PASSWORD_COMPLEXITY_LOW, |
Bernard Chau | 60e0f7f | 2018-11-29 16:36:39 +0000 | [diff] [blame] | 435 | new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING), |
Bernard Chau | e958655 | 2018-11-29 10:59:31 +0000 | [diff] [blame] | 436 | new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC), |
Bernard Chau | 60e0f7f | 2018-11-29 16:36:39 +0000 | [diff] [blame] | 437 | new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX), |
| 438 | new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC), |
| 439 | new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC)); |
Bernard Chau | e958655 | 2018-11-29 10:59:31 +0000 | [diff] [blame] | 440 | |
| 441 | /** |
| 442 | * A special bucket to represent {@link DevicePolicyManager#PASSWORD_COMPLEXITY_NONE}. |
| 443 | */ |
| 444 | private static final PasswordComplexityBucket NONE = |
| 445 | new PasswordComplexityBucket(PASSWORD_COMPLEXITY_NONE, new PasswordMetrics()); |
| 446 | |
| 447 | /** Array containing all buckets from high to low. */ |
| 448 | private static final PasswordComplexityBucket[] BUCKETS = |
| 449 | new PasswordComplexityBucket[] {HIGH, MEDIUM, LOW}; |
| 450 | |
| 451 | @PasswordComplexity |
| 452 | private final int mComplexityLevel; |
| 453 | private final PasswordMetrics[] mMetrics; |
| 454 | |
Bernard Chau | 60e0f7f | 2018-11-29 16:36:39 +0000 | [diff] [blame] | 455 | /** |
| 456 | * @param metricsArray must be sorted in ascending order of {@link #quality}. |
| 457 | */ |
Bernard Chau | e958655 | 2018-11-29 10:59:31 +0000 | [diff] [blame] | 458 | private PasswordComplexityBucket(@PasswordComplexity int complexityLevel, |
Bernard Chau | 60e0f7f | 2018-11-29 16:36:39 +0000 | [diff] [blame] | 459 | PasswordMetrics... metricsArray) { |
| 460 | int previousQuality = PASSWORD_QUALITY_UNSPECIFIED; |
| 461 | for (PasswordMetrics metrics : metricsArray) { |
| 462 | if (metrics.quality < previousQuality) { |
| 463 | throw new IllegalArgumentException("metricsArray must be sorted in ascending" |
| 464 | + " order of quality"); |
| 465 | } |
| 466 | previousQuality = metrics.quality; |
| 467 | } |
Bernard Chau | e958655 | 2018-11-29 10:59:31 +0000 | [diff] [blame] | 468 | |
Bernard Chau | 60e0f7f | 2018-11-29 16:36:39 +0000 | [diff] [blame] | 469 | this.mMetrics = metricsArray; |
| 470 | this.mComplexityLevel = complexityLevel; |
| 471 | |
Bernard Chau | e958655 | 2018-11-29 10:59:31 +0000 | [diff] [blame] | 472 | } |
| 473 | |
| 474 | /** Returns the bucket that {@code complexityLevel} represents. */ |
Bernard Chau | 60e0f7f | 2018-11-29 16:36:39 +0000 | [diff] [blame] | 475 | private static PasswordComplexityBucket complexityLevelToBucket( |
Bernard Chau | e958655 | 2018-11-29 10:59:31 +0000 | [diff] [blame] | 476 | @PasswordComplexity int complexityLevel) { |
| 477 | for (PasswordComplexityBucket bucket : BUCKETS) { |
| 478 | if (bucket.mComplexityLevel == complexityLevel) { |
| 479 | return bucket; |
| 480 | } |
| 481 | } |
| 482 | return NONE; |
| 483 | } |
| 484 | } |
Andrew Scull | 5f9e6f3 | 2016-08-02 14:22:17 +0100 | [diff] [blame] | 485 | } |