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

Commit 85f2c9ce authored by Dianne Hackborn's avatar Dianne Hackborn
Browse files

Fix issue #2530103: ActivePassword data in Device Policy Manager needs to be persisted

Also fixes how the quality vs. mode is handled to be more consistent, which also
required introducing a new "alphabetic" quality since it is possible for the user
to enter such a password.

The current password quality and length is stored in the DPM, since at boot it
couldn't figure this out from the stored password.

Change-Id: I519d9b76dd0b4431bcf42920c34dda38c9f1136e
parent 1a26c9aa
Loading
Loading
Loading
Loading
+12 −1
Original line number Diff line number Diff line
@@ -26873,11 +26873,22 @@
 visibility="public"
>
</field>
<field name="PASSWORD_QUALITY_ALPHABETIC"
 type="int"
 transient="false"
 volatile="false"
 value="262144"
 static="true"
 final="true"
 deprecated="not deprecated"
 visibility="public"
>
</field>
<field name="PASSWORD_QUALITY_ALPHANUMERIC"
 type="int"
 transient="false"
 volatile="false"
 value="196608"
 value="327680"
 static="true"
 final="true"
 deprecated="not deprecated"
+14 −5
Original line number Diff line number Diff line
@@ -20,7 +20,6 @@ import org.xmlpull.v1.XmlPullParserException;

import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.app.admin.IDevicePolicyManager.Stub;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
@@ -190,13 +189,21 @@ public class DevicePolicyManager {
     */
    public static final int PASSWORD_QUALITY_NUMERIC = 0x20000;
    
    /**
     * Constant for {@link #setPasswordQuality}: the user must have entered a
     * password containing at least alphabetic (or other symbol) characters.
     * Note that quality constants are ordered so that higher values are more
     * restrictive.
     */
    public static final int PASSWORD_QUALITY_ALPHABETIC = 0x40000;
    
    /**
     * Constant for {@link #setPasswordQuality}: the user must have entered a
     * password containing at least <em>both></em> numeric <em>and</em>
     * alphabeter (or other symbol) characters.  Note that quality constants are
     * alphabetic (or other symbol) characters.  Note that quality constants are
     * ordered so that higher values are more restrictive.
     */
    public static final int PASSWORD_QUALITY_ALPHANUMERIC = 0x30000;
    public static final int PASSWORD_QUALITY_ALPHANUMERIC = 0x50000;
    
    /**
     * Called by an application that is administering the device to set the
@@ -219,7 +226,8 @@ public class DevicePolicyManager {
     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
     * @param quality The new desired quality.  One of
     * {@link #PASSWORD_QUALITY_UNSPECIFIED}, {@link #PASSWORD_QUALITY_SOMETHING},
     * {@link #PASSWORD_QUALITY_NUMERIC}, or {@link #PASSWORD_QUALITY_ALPHANUMERIC}.
     * {@link #PASSWORD_QUALITY_NUMERIC}, {@link #PASSWORD_QUALITY_ALPHABETIC},
     * or {@link #PASSWORD_QUALITY_ALPHANUMERIC}.
     */
    public void setPasswordQuality(ComponentName admin, int quality) {
        if (mService != null) {
@@ -257,7 +265,8 @@ public class DevicePolicyManager {
     * 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 either
     * {@link #PASSWORD_QUALITY_NUMERIC} or {@link #PASSWORD_QUALITY_ALPHANUMERIC}
     * {@link #PASSWORD_QUALITY_NUMERIC}, {@link #PASSWORD_QUALITY_ALPHABETIC},
     * or {@link #PASSWORD_QUALITY_ALPHANUMERIC}
     * with {@link #setPasswordQuality}.
     * 
     * <p>The calling device admin must have requested
+49 −44
Original line number Diff line number Diff line
@@ -89,7 +89,7 @@ public class LockPatternUtils {
    public static final int MODE_UNSPECIFIED = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
    public static final int MODE_PATTERN = DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
    public static final int MODE_PIN = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
    public static final int MODE_PASSWORD = DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
    public static final int MODE_PASSWORD = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;

    /**
     * The minimum number of dots the user must include in a wrong pattern
@@ -144,12 +144,11 @@ public class LockPatternUtils {
    /**
     * Gets the device policy password mode. If the mode is non-specific, returns
     * MODE_PATTERN which allows the user to choose anything.
     *
     * @return
     */
    public int getRequestedPasswordMode() {
        int policyMode = getDevicePolicyManager().getPasswordQuality(null);
        switch (policyMode) {
            case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
            case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
                return MODE_PASSWORD;
            case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
@@ -269,10 +268,42 @@ public class LockPatternUtils {
        return getBoolean(PATTERN_EVER_CHOSEN_KEY);
    }

    /**
     * Used by device policy manager to validate the current password
     * information it has.
     */
    public int getActivePasswordQuality() {
        switch (getPasswordMode()) {
            case MODE_UNSPECIFIED:
                return DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
            case MODE_PATTERN:
                if (isLockPatternEnabled()) {
                    return DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
                } else {
                    return DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
                }
            case MODE_PIN:
                if (isLockPasswordEnabled()) {
                    return DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
                } else {
                    return DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
                }
            case MODE_PASSWORD:
                if (isLockPasswordEnabled()) {
                    return DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
                } else {
                    return DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
                }
        }
        return DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
    }
    
    /**
     * Clear any lock pattern or password.
     */
    public void clearLock() {
        getDevicePolicyManager().setActivePasswordState(
                DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0);
        saveLockPassword(null, LockPatternUtils.MODE_PATTERN);
        setLockPatternEnabled(false);
        saveLockPattern(null);
@@ -316,15 +347,9 @@ public class LockPatternUtils {
    }

    /**
     * Compare the given password and mode, ensuring that the password meets
     * the mode and returning the minimum mode needed for the given password.
     * @param password The password to be used.
     * @param reqMode The desired password mode.
     * @return Returns {@link #MODE_UNSPECIFIED} if the password is not
     * good enough for the given mode.  Otherwise, returns either the original
     * reqMode or something better if that is needed for the given password.
     * Compute the password quality from the given password string.
     */
    static public int adjustPasswordMode(String password, int reqMode) {
    static public int computePasswordQuality(String password) {
        boolean hasDigit = false;
        boolean hasNonDigit = false;
        final int len = password.length();
@@ -336,39 +361,16 @@ public class LockPatternUtils {
            }
        }

        // First check if it is sufficient.
        switch (reqMode) {
            case MODE_PASSWORD: {
                if (!hasDigit || !hasNonDigit) {
                    return MODE_UNSPECIFIED;
        if (hasNonDigit && hasDigit) {
            return DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
        }
            } break;

            case MODE_PIN:
            case MODE_PATTERN: {
                // Whatever we have is acceptable; we may need to promote the
                // mode later.
            } break;

            default:
                // If it isn't a mode we specifically know, then fail fast.
                Log.w(TAG, "adjustPasswordMode: unknown mode " + reqMode);
                return MODE_UNSPECIFIED;
        }

        // Do we need to promote?
        if (hasNonDigit) {
            if (reqMode < MODE_PASSWORD) {
                reqMode = MODE_PASSWORD;
            }
            return DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
        }
        if (hasDigit) {
            if (reqMode < MODE_PIN) {
                reqMode = MODE_PIN;
            return DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
        }
        }

        return reqMode;
        return DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
    }

    /**
@@ -392,12 +394,15 @@ public class LockPatternUtils {
            raf.close();
            DevicePolicyManager dpm = getDevicePolicyManager();
            if (password != null) {
                int finalMode = adjustPasswordMode(password, mode);
                if (mode < finalMode) {
                    mode = finalMode;
                }
                setLong(PASSWORD_TYPE_KEY, mode);
                dpm.setActivePasswordState(mode, password.length());
                int quality = computePasswordQuality(password);
                if (quality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
                    dpm.setActivePasswordState(quality, password.length());
                } else {
                    // The password is not anything.
                    dpm.setActivePasswordState(
                            DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0);
                }
            } else {
                dpm.setActivePasswordState(
                        DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0);
+89 −40
Original line number Diff line number Diff line
@@ -46,7 +46,7 @@ import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.util.Log;
import android.util.Slog;
import android.util.PrintWriterPrinter;
import android.util.Printer;
import android.util.Xml;
@@ -150,7 +150,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
                    maximumFailedPasswordsForWipe = Integer.parseInt(
                            parser.getAttributeValue(null, "value"));
                } else {
                    Log.w(TAG, "Unknown admin tag: " + tag);
                    Slog.w(TAG, "Unknown admin tag: " + tag);
                }
                XmlUtils.skipCurrentTag(parser);
            }
@@ -165,8 +165,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
                    pw.print(prefix); pw.print("  "); pw.println(pols.get(i).tag);
                }
            }
            pw.print(prefix); pw.print("passwordQuality=");
                    pw.print(passwordQuality);
            pw.print(prefix); pw.print("passwordQuality=0x");
                    pw.print(Integer.toHexString(passwordQuality));
                    pw.print(" minimumPasswordLength=");
                    pw.println(minimumPasswordLength);
            pw.print(prefix); pw.print("maximumTimeToUnlock=");
@@ -185,7 +185,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
                    int change = isPackageDisappearing(aa.info.getPackageName()); 
                    if (change == PACKAGE_PERMANENT_CHANGE
                            || change == PACKAGE_TEMPORARY_CHANGE) {
                        Log.w(TAG, "Admin unexpectedly uninstalled: "
                        Slog.w(TAG, "Admin unexpectedly uninstalled: "
                                + aa.info.getComponent());
                        removed = true;
                        mAdminList.remove(i);
@@ -194,7 +194,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
                            mContext.getPackageManager().getReceiverInfo(
                                    aa.info.getComponent(), 0);
                        } catch (NameNotFoundException e) {
                            Log.w(TAG, "Admin package change removed component: "
                            Slog.w(TAG, "Admin package change removed component: "
                                    + aa.info.getComponent());
                            removed = true;
                            mAdminList.remove(i);
@@ -308,10 +308,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
        try {
            return new DeviceAdminInfo(mContext, infos.get(0));
        } catch (XmlPullParserException e) {
            Log.w(TAG, "Bad device admin requested: " + adminName, e);
            Slog.w(TAG, "Bad device admin requested: " + adminName, e);
            return null;
        } catch (IOException e) {
            Log.w(TAG, "Bad device admin requested: " + adminName, e);
            Slog.w(TAG, "Bad device admin requested: " + adminName, e);
            return null;
        }
    }
@@ -343,8 +343,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
                }
            }
            
            out.endTag(null, "policies");

            if (mPasswordOwner >= 0) {
                out.startTag(null, "password-owner");
                out.attribute(null, "value", Integer.toString(mPasswordOwner));
@@ -357,6 +355,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
                out.endTag(null, "failed-password-attempts");
            }
            
            if (mActivePasswordQuality != 0 || mActivePasswordLength != 0) {
                out.startTag(null, "active-password");
                out.attribute(null, "quality", Integer.toString(mActivePasswordQuality));
                out.attribute(null, "length", Integer.toString(mActivePasswordLength));
                out.endTag(null, "active-password");
            }
            
            out.endTag(null, "policies");

            out.endDocument();
            stream.close();
            journal.commit();
@@ -410,7 +417,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
                            mAdminList.add(ap);
                        }
                    } catch (RuntimeException e) {
                        Log.w(TAG, "Failed loading admin " + name, e);
                        Slog.w(TAG, "Failed loading admin " + name, e);
                    }
                } else if ("failed-password-attempts".equals(tag)) {
                    mFailedPasswordAttempts = Integer.parseInt(
@@ -420,21 +427,27 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
                    mPasswordOwner = Integer.parseInt(
                            parser.getAttributeValue(null, "value"));
                    XmlUtils.skipCurrentTag(parser);
                } else if ("active-password".equals(tag)) {
                    mActivePasswordQuality = Integer.parseInt(
                            parser.getAttributeValue(null, "quality"));
                    mActivePasswordLength = Integer.parseInt(
                            parser.getAttributeValue(null, "length"));
                    XmlUtils.skipCurrentTag(parser);
                } else {
                    Log.w(TAG, "Unknown tag: " + tag);
                    Slog.w(TAG, "Unknown tag: " + tag);
                    XmlUtils.skipCurrentTag(parser);
                }
            }
        } catch (NullPointerException e) {
            Log.w(TAG, "failed parsing " + file + " " + e);
            Slog.w(TAG, "failed parsing " + file + " " + e);
        } catch (NumberFormatException e) {
            Log.w(TAG, "failed parsing " + file + " " + e);
            Slog.w(TAG, "failed parsing " + file + " " + e);
        } catch (XmlPullParserException e) {
            Log.w(TAG, "failed parsing " + file + " " + e);
            Slog.w(TAG, "failed parsing " + file + " " + e);
        } catch (IOException e) {
            Log.w(TAG, "failed parsing " + file + " " + e);
            Slog.w(TAG, "failed parsing " + file + " " + e);
        } catch (IndexOutOfBoundsException e) {
            Log.w(TAG, "failed parsing " + file + " " + e);
            Slog.w(TAG, "failed parsing " + file + " " + e);
        }
        try {
            if (stream != null) {
@@ -444,6 +457,20 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
            // Ignore
        }

        // Validate that what we stored for the password quality matches
        // sufficiently what is currently set.  Note that this is only
        // a sanity check in case the two get out of sync; this should
        // never normally happen.
        LockPatternUtils utils = new LockPatternUtils(mContext);
        if (utils.getActivePasswordQuality() < mActivePasswordQuality) {
            Slog.w(TAG, "Active password quality 0x"
                    + Integer.toHexString(mActivePasswordQuality)
                    + " does not match actual quality 0x"
                    + Integer.toHexString(utils.getActivePasswordQuality()));
            mActivePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
            mActivePasswordLength = 0;
        }
        
        validatePasswordOwnerLocked();
        
        long timeMs = getMaximumTimeToLock(null);
@@ -453,10 +480,23 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
        try {
            getIPowerManager().setMaximumScreenOffTimeount((int)timeMs);
        } catch (RemoteException e) {
            Log.w(TAG, "Failure talking with power manager", e);
            Slog.w(TAG, "Failure talking with power manager", e);
        }
    }

    static void validateQualityConstant(int quality) {
        switch (quality) {
            case DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED:
            case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
            case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
            case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
            case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
                return;
        }
        throw new IllegalArgumentException("Invalid quality constant: 0x"
                + Integer.toHexString(quality));
    }
    
    void validatePasswordOwnerLocked() {
        if (mPasswordOwner >= 0) {
            boolean haveOwner = false;
@@ -467,7 +507,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
                }
            }
            if (!haveOwner) {
                Log.w(TAG, "Previous password owner " + mPasswordOwner
                Slog.w(TAG, "Previous password owner " + mPasswordOwner
                        + " no longer active; disabling");
                mPasswordOwner = -1;
            }
@@ -557,15 +597,17 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
        }
    }
    
    public void setPasswordQuality(ComponentName who, int mode) {
    public void setPasswordQuality(ComponentName who, int quality) {
        validateQualityConstant(quality);
        
        synchronized (this) {
            if (who == null) {
                throw new NullPointerException("ComponentName is null");
            }
            ActiveAdmin ap = getActiveAdminForCallerLocked(who,
                    DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
            if (ap.passwordQuality != mode) {
                ap.passwordQuality = mode;
            if (ap.passwordQuality != quality) {
                ap.passwordQuality = quality;
                saveSettingsLocked();
            }
        }
@@ -693,23 +735,27 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
                    DeviceAdminInfo.USES_POLICY_RESET_PASSWORD);
            quality = getPasswordQuality(null);
            if (quality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
                int adjQuality = LockPatternUtils.adjustPasswordMode(password, quality);
                if (adjQuality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
                    Log.w(TAG, "resetPassword: password does not meet quality " + quality);
                int realQuality = LockPatternUtils.computePasswordQuality(password);
                if (realQuality < quality) {
                    Slog.w(TAG, "resetPassword: password quality 0x"
                            + Integer.toHexString(quality)
                            + " does not meet required quality 0x"
                            + Integer.toHexString(quality));
                    return false;
                }
                quality = adjQuality;
                quality = realQuality;
            }
            int length = getPasswordMinimumLength(null);
            if (password.length() < length) {
                Log.w(TAG, "resetPassword: password does not meet length " + length);
                Slog.w(TAG, "resetPassword: password length " + password.length()
                        + " does not meet required length " + length);
                return false;
            }
        }
        
        int callingUid = Binder.getCallingUid();
        if (mPasswordOwner >= 0 && mPasswordOwner != callingUid) {
            Log.w(TAG, "resetPassword: already set by another uid and not entered by user");
            Slog.w(TAG, "resetPassword: already set by another uid and not entered by user");
            return false;
        }
        
@@ -719,12 +765,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
        try {
            LockPatternUtils utils = new LockPatternUtils(mContext);
            utils.saveLockPassword(password, quality);
            synchronized (this) {
                int newOwner = (flags&DevicePolicyManager.RESET_PASSWORD_REQUIRE_ENTRY)
                        != 0 ? callingUid : -1;
                if (mPasswordOwner != newOwner) {
                    mPasswordOwner = newOwner;
                    saveSettingsLocked();
                }
            }
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
@@ -754,7 +802,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
                    try {
                        getIPowerManager().setMaximumScreenOffTimeount((int)timeMs);
                    } catch (RemoteException e) {
                        Log.w(TAG, "Failure talking with power manager", e);
                        Slog.w(TAG, "Failure talking with power manager", e);
                    }
                } finally {
                    Binder.restoreCallingIdentity(ident);
@@ -807,7 +855,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
        try {
            RecoverySystem.rebootWipeUserData(mContext);
        } catch (IOException e) {
            Log.w(TAG, "Failed requesting data wipe", e);
            Slog.w(TAG, "Failed requesting data wipe", e);
        }
    }
    
@@ -857,6 +905,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
        mContext.enforceCallingOrSelfPermission(
                android.Manifest.permission.BIND_DEVICE_ADMIN, null);
        
        validateQualityConstant(quality);
        
        synchronized (this) {
            if (mActivePasswordQuality != quality || mActivePasswordLength != length
                    || mFailedPasswordAttempts != 0) {
@@ -864,10 +914,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
                try {
                    mActivePasswordQuality = quality;
                    mActivePasswordLength = length;
                    if (mFailedPasswordAttempts != 0) {
                    mFailedPasswordAttempts = 0;
                    saveSettingsLocked();
                    }
                    sendAdminCommandLocked(DeviceAdminReceiver.ACTION_PASSWORD_CHANGED,
                            DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
                } finally {
@@ -946,7 +994,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
            }
            
            pw.println(" ");
            pw.print("  mActivePasswordQuality="); pw.println(mActivePasswordQuality);
            pw.print("  mActivePasswordQuality=0x");
                    pw.println(Integer.toHexString(mActivePasswordQuality));
            pw.print("  mActivePasswordLength="); pw.println(mActivePasswordLength);
            pw.print("  mFailedPasswordAttempts="); pw.println(mFailedPasswordAttempts);
            pw.print("  mPasswordOwner="); pw.println(mPasswordOwner);