From c857740f242169f2ca7fd42f0d1268661b399ad6 Mon Sep 17 00:00:00 2001 From: Konstantin Lopyrev Date: Fri, 4 Jun 2010 17:15:02 -0700 Subject: [PATCH] Adding support for minimum number of non letter characters. Change-Id: If54cb7209d65eef826d474d7e0dbbef63d2f2b47 --- api/current.xml | 28 +++++ .../app/admin/DevicePolicyManager.java | 107 +++++++++++++++--- .../app/admin/IDevicePolicyManager.aidl | 6 +- .../internal/widget/LockPatternUtils.java | 16 ++- .../server/DevicePolicyManagerService.java | 71 +++++++++++- 5 files changed, 203 insertions(+), 25 deletions(-) diff --git a/api/current.xml b/api/current.xml index 5a0a61976d3d7..0ffbb1b66107c 100644 --- a/api/current.xml +++ b/api/current.xml @@ -31871,6 +31871,19 @@ + + + + + + + + + + and alphabetic characters, - * and special symbols. Note that quality constants are ordered so - * that higher values are more restrictive. + * password containing at least a letter, a numerical digit and a special + * symbol, by default. With this password quality, passwords can be + * restricted to contain various sets of characters, like at least an + * uppercase letter, etc. These are specified using various methods, + * like {@link #setPasswordMinimumLowerCase(ComponentName, int)}. Note + * that quality constants are ordered so that higher values are more + * restrictive. */ public static final int PASSWORD_QUALITY_COMPLEX = 0x60000; @@ -329,7 +333,8 @@ public class DevicePolicyManager { * not take place immediately. To prompt the user for a new password, use * {@link #ACTION_SET_NEW_PASSWORD} after setting this value. This * constraint is only imposed if the administrator has also requested - * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. + * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The + * default value is 0. *

* The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call @@ -353,7 +358,10 @@ public class DevicePolicyManager { /** * Retrieve the current number of upper case letters required in the - * password for all admins or a particular one. + * password for all admins or a particular one. This is the same value as + * set by {#link {@link #setPasswordMinimumUpperCase(ComponentName, int)} + * and only applies when the password quality is + * {@link #PASSWORD_QUALITY_COMPLEX}. * * @param admin The name of the admin component to check, or null to * aggregate all admins. @@ -380,7 +388,8 @@ public class DevicePolicyManager { * not take place immediately. To prompt the user for a new password, use * {@link #ACTION_SET_NEW_PASSWORD} after setting this value. This * constraint is only imposed if the administrator has also requested - * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. + * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The + * default value is 0. *

* The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call @@ -404,7 +413,10 @@ public class DevicePolicyManager { /** * Retrieve the current number of lower case letters required in the - * password for all admins or a particular one. + * password for all admins or a particular one. This is the same value as + * set by {#link {@link #setPasswordMinimumLowerCase(ComponentName, int)} + * and only applies when the password quality is + * {@link #PASSWORD_QUALITY_COMPLEX}. * * @param admin The name of the admin component to check, or null to * aggregate all admins. @@ -431,7 +443,8 @@ public class DevicePolicyManager { * place immediately. To prompt the user for a new password, use * {@link #ACTION_SET_NEW_PASSWORD} after setting this value. This * constraint is only imposed if the administrator has also requested - * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. + * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The + * default value is 1. *

* The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call @@ -454,7 +467,10 @@ public class DevicePolicyManager { /** * Retrieve the current number of letters required in the password for all - * admins or a particular one. + * admins or a particular one. This is the same value as + * set by {#link {@link #setPasswordMinimumLetters(ComponentName, int)} + * and only applies when the password quality is + * {@link #PASSWORD_QUALITY_COMPLEX}. * * @param admin The name of the admin component to check, or null to * aggregate all admins. @@ -480,7 +496,8 @@ public class DevicePolicyManager { * not take place immediately. To prompt the user for a new password, use * {@link #ACTION_SET_NEW_PASSWORD} after setting this value. This * constraint is only imposed if the administrator has also requested - * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. + * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The + * default value is 1. *

* The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call @@ -503,7 +520,10 @@ public class DevicePolicyManager { /** * Retrieve the current number of numerical digits required in the password - * for all admins or a particular one. + * for all admins or a particular one. This is the same value as + * set by {#link {@link #setPasswordMinimumNumeric(ComponentName, int)} + * and only applies when the password quality is + * {@link #PASSWORD_QUALITY_COMPLEX}. * * @param admin The name of the admin component to check, or null to * aggregate all admins. @@ -529,7 +549,8 @@ public class DevicePolicyManager { * place immediately. To prompt the user for a new password, use * {@link #ACTION_SET_NEW_PASSWORD} after setting this value. This * constraint is only imposed if the administrator has also requested - * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. + * {@link #PASSWORD_QUALITY_COMPLEX} with {@link #setPasswordQuality}. The + * default value is 1. *

* The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call @@ -552,7 +573,10 @@ public class DevicePolicyManager { /** * Retrieve the current number of symbols required in the password for all - * admins or a particular one. + * admins or a particular one. This is the same value as + * set by {#link {@link #setPasswordMinimumSymbols(ComponentName, int)} + * and only applies when the password quality is + * {@link #PASSWORD_QUALITY_COMPLEX}. * * @param admin The name of the admin component to check, or null to * aggregate all admins. @@ -569,6 +593,59 @@ public class DevicePolicyManager { return 0; } + /** + * Called by an application that is administering the device to set the + * minimum number of non-letter characters (numerical digits or symbols) + * required in the password. After setting this, the user will not be able + * to enter a new password that is not at least as restrictive as what has + * been set. Note that the current password will remain until the user has + * set a new one, so the change does not take place immediately. To prompt + * the user for a new password, use {@link #ACTION_SET_NEW_PASSWORD} after + * setting this value. This constraint is only imposed if the administrator + * has also requested {@link #PASSWORD_QUALITY_COMPLEX} with + * {@link #setPasswordQuality}. The default value is 0. + *

+ * The calling device admin must have requested + * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call + * this method; if it has not, a security exception will be thrown. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated + * with. + * @param length The new desired minimum number of letters required in the + * password. A value of 0 means there is no restriction. + */ + public void setPasswordMinimumNonLetter(ComponentName admin, int length) { + if (mService != null) { + try { + mService.setPasswordMinimumNonLetter(admin, length); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + } + + /** + * Retrieve the current number of non-letter characters required in the + * password for all admins or a particular one. This is the same value as + * set by {#link {@link #setPasswordMinimumNonLetter(ComponentName, int)} + * and only applies when the password quality is + * {@link #PASSWORD_QUALITY_COMPLEX}. + * + * @param admin The name of the admin component to check, or null to + * aggregate all admins. + * @return The minimum number of letters required in the password. + */ + public int getPasswordMinimumNonLetter(ComponentName admin) { + if (mService != null) { + try { + return mService.getPasswordMinimumNonLetter(admin); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + return 0; + } + /** * Called by an application that is administering the device to set the length * of the password history. After setting this, the user will not be able to @@ -894,11 +971,11 @@ public class DevicePolicyManager { * @hide */ public void setActivePasswordState(int quality, int length, int letters, int uppercase, - int lowercase, int numbers, int symbols) { + int lowercase, int numbers, int symbols, int nonletter) { if (mService != null) { try { mService.setActivePasswordState(quality, length, letters, uppercase, lowercase, - numbers, symbols); + numbers, symbols, nonletter); } catch (RemoteException e) { Log.w(TAG, "Failed talking with device policy service", e); } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index fa31a373d9f23..3ada95cf62644 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -45,6 +45,9 @@ interface IDevicePolicyManager { void setPasswordMinimumSymbols(in ComponentName who, int length); int getPasswordMinimumSymbols(in ComponentName who); + + void setPasswordMinimumNonLetter(in ComponentName who, int length); + int getPasswordMinimumNonLetter(in ComponentName who); void setPasswordHistoryLength(in ComponentName who, int length); int getPasswordHistoryLength(in ComponentName who); @@ -71,7 +74,8 @@ interface IDevicePolicyManager { void getRemoveWarning(in ComponentName policyReceiver, in RemoteCallback result); void removeActiveAdmin(in ComponentName policyReceiver); - void setActivePasswordState(int quality, int length, int letters, int uppercase, int lowercase, int numbers, int symbols); + void setActivePasswordState(int quality, int length, int letters, int uppercase, int lowercase, + int numbers, int symbols, int nonletter); void reportFailedPasswordAttempt(); void reportSuccessfulPasswordAttempt(); } diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 9983c027f33da..0b62a67e70fec 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -164,6 +164,9 @@ public class LockPatternUtils { return getDevicePolicyManager().getPasswordMinimumSymbols(null); } + public int getRequestedPasswordMinimumNonLetter() { + return getDevicePolicyManager().getPasswordMinimumNonLetter(null); + } /** * Returns the actual password mode, as set by keyguard after updating the password. * @@ -369,10 +372,10 @@ public class LockPatternUtils { setBoolean(PATTERN_EVER_CHOSEN_KEY, true); setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, pattern - .size(), 0, 0, 0, 0, 0); + .size(), 0, 0, 0, 0, 0, 0); } else { dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, - 0, 0, 0, 0); + 0, 0, 0, 0, 0); } } catch (FileNotFoundException fnfe) { // Cant do much, unless we want to fail over to using the settings @@ -441,6 +444,7 @@ public class LockPatternUtils { 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') { @@ -451,16 +455,18 @@ public class LockPatternUtils { 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); + .length(), letters, uppercase, lowercase, numbers, symbols, nonletter); } else { // The password is not anything. dpm.setActivePasswordState( - DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, 0, 0, 0, 0); + DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, 0, 0, 0, 0, 0); } // Add the password to the password history. We assume all // password @@ -483,7 +489,7 @@ public class LockPatternUtils { setString(PASSWORD_HISTORY_KEY, passwordHistory); } else { dpm.setActivePasswordState( - DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, 0, 0, 0, 0); + DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, 0, 0, 0, 0, 0); } } catch (FileNotFoundException fnfe) { // Cant do much, unless we want to fail over to using the settings provider diff --git a/services/java/com/android/server/DevicePolicyManagerService.java b/services/java/com/android/server/DevicePolicyManagerService.java index 1e1c416937608..c0ea68d731904 100644 --- a/services/java/com/android/server/DevicePolicyManagerService.java +++ b/services/java/com/android/server/DevicePolicyManagerService.java @@ -80,6 +80,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { int mActivePasswordLetters = 0; int mActivePasswordNumeric = 0; int mActivePasswordSymbols = 0; + int mActivePasswordNonLetter = 0; int mFailedPasswordAttempts = 0; int mPasswordOwner = -1; @@ -100,6 +101,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { int minimumPasswordLetters = 1; int minimumPasswordNumeric = 1; int minimumPasswordSymbols = 1; + int minimumPasswordNonLetter = 0; long maximumTimeToUnlock = 0; int maximumFailedPasswordsForWipe = 0; @@ -153,6 +155,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { out.attribute(null, "value", Integer.toString(minimumPasswordSymbols)); out.endTag(null, "min-password-symbols"); } + if (minimumPasswordNonLetter > 0) { + out.startTag(null, "min-password-nonletter"); + out.attribute(null, "value", Integer.toString(minimumPasswordNonLetter)); + out.endTag(null, "min-password-nonletter"); + } } if (maximumTimeToUnlock != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) { out.startTag(null, "max-time-to-unlock"); @@ -202,6 +209,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } else if ("min-password-symbols".equals(tag)) { minimumPasswordSymbols = Integer.parseInt( parser.getAttributeValue(null, "value")); + } else if ("min-password-nonletter".equals(tag)) { + minimumPasswordNonLetter = Integer.parseInt( + parser.getAttributeValue(null, "value")); } else if ("max-time-to-unlock".equals(tag)) { maximumTimeToUnlock = Long.parseLong( parser.getAttributeValue(null, "value")); @@ -240,6 +250,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { pw.println(minimumPasswordNumeric); pw.print(prefix); pw.print("minimumPasswordSymbols="); pw.println(minimumPasswordSymbols); + pw.print(prefix); pw.print("minimumPasswordNonLetter="); + pw.println(minimumPasswordNonLetter); pw.print(prefix); pw.print("maximumTimeToUnlock="); pw.println(maximumTimeToUnlock); pw.print(prefix); pw.print("maximumFailedPasswordsForWipe="); @@ -429,7 +441,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (mActivePasswordQuality != 0 || mActivePasswordLength != 0 || mActivePasswordUpperCase != 0 || mActivePasswordLowerCase != 0 || mActivePasswordLetters != 0 || mActivePasswordNumeric != 0 - || mActivePasswordSymbols != 0) { + || mActivePasswordSymbols != 0 || mActivePasswordNonLetter != 0) { out.startTag(null, "active-password"); out.attribute(null, "quality", Integer.toString(mActivePasswordQuality)); out.attribute(null, "length", Integer.toString(mActivePasswordLength)); @@ -439,6 +451,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { out.attribute(null, "numeric", Integer .toString(mActivePasswordNumeric)); out.attribute(null, "symbols", Integer.toString(mActivePasswordSymbols)); + out.attribute(null, "nonletter", Integer.toString(mActivePasswordNonLetter)); out.endTag(null, "active-password"); } @@ -529,6 +542,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { parser.getAttributeValue(null, "numeric")); mActivePasswordSymbols = Integer.parseInt( parser.getAttributeValue(null, "symbols")); + mActivePasswordNonLetter = Integer.parseInt( + parser.getAttributeValue(null, "nonletter")); XmlUtils.skipCurrentTag(parser); } else { Slog.w(TAG, "Unknown tag: " + tag); @@ -571,6 +586,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mActivePasswordLetters = 0; mActivePasswordNumeric = 0; mActivePasswordSymbols = 0; + mActivePasswordNonLetter = 0; } validatePasswordOwnerLocked(); @@ -974,6 +990,40 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + public void setPasswordMinimumNonLetter(ComponentName who, int length) { + synchronized (this) { + if (who == null) { + throw new NullPointerException("ComponentName is null"); + } + ActiveAdmin ap = getActiveAdminForCallerLocked(who, + DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD); + if (ap.minimumPasswordNonLetter != length) { + ap.minimumPasswordNonLetter = length; + saveSettingsLocked(); + } + } + } + + public int getPasswordMinimumNonLetter(ComponentName who) { + synchronized (this) { + int length = 0; + + if (who != null) { + ActiveAdmin admin = getActiveAdminUncheckedLocked(who); + return admin != null ? admin.minimumPasswordNonLetter : length; + } + + final int N = mAdminList.size(); + for (int i=0; i= getPasswordMinimumLowerCase(null) && mActivePasswordLetters >= getPasswordMinimumLetters(null) && mActivePasswordNumeric >= getPasswordMinimumNumeric(null) - && mActivePasswordSymbols >= getPasswordMinimumSymbols(null); + && mActivePasswordSymbols >= getPasswordMinimumSymbols(null) + && mActivePasswordNonLetter >= getPasswordMinimumNonLetter(null); } } @@ -1075,6 +1126,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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') { @@ -1085,8 +1137,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { lowercase++; } else if (c >= '0' && c <= '9') { numbers++; + nonletter++; } else { symbols++; + nonletter++; } } int neededLetters = getPasswordMinimumLetters(null); @@ -1123,6 +1177,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { + " does not meet required number of special symbols " + neededSymbols); return false; } + int neededNonLetter = getPasswordMinimumNonLetter(null); + if (nonletter < neededNonLetter) { + Slog.w(TAG, "resetPassword: number of non-letter characters " + nonletter + + " does not meet required number of non-letter characters " + + neededNonLetter); + return false; + } } LockPatternUtils utils = new LockPatternUtils(mContext); @@ -1282,7 +1343,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public void setActivePasswordState(int quality, int length, int letters, int uppercase, - int lowercase, int numbers, int symbols) { + int lowercase, int numbers, int symbols, int nonletter) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.BIND_DEVICE_ADMIN, null); @@ -1293,7 +1354,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { || mFailedPasswordAttempts != 0 || mActivePasswordLetters != letters || mActivePasswordUpperCase != uppercase || mActivePasswordLowerCase != lowercase || mActivePasswordNumeric != numbers - || mActivePasswordSymbols != symbols) { + || mActivePasswordSymbols != symbols || mActivePasswordNonLetter != nonletter) { long ident = Binder.clearCallingIdentity(); try { mActivePasswordQuality = quality; @@ -1303,6 +1364,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mActivePasswordUpperCase = uppercase; mActivePasswordNumeric = numbers; mActivePasswordSymbols = symbols; + mActivePasswordNonLetter = nonletter; mFailedPasswordAttempts = 0; saveSettingsLocked(); sendAdminCommandLocked(DeviceAdminReceiver.ACTION_PASSWORD_CHANGED, @@ -1391,6 +1453,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { pw.print(" mActivePasswordLetters="); pw.println(mActivePasswordLetters); pw.print(" mActivePasswordNumeric="); pw.println(mActivePasswordNumeric); pw.print(" mActivePasswordSymbols="); pw.println(mActivePasswordSymbols); + pw.print(" mActivePasswordNonLetter="); pw.println(mActivePasswordNonLetter); pw.print(" mFailedPasswordAttempts="); pw.println(mFailedPasswordAttempts); pw.print(" mPasswordOwner="); pw.println(mPasswordOwner); }