DPMS: password blacklist

Allows admins to blacklist passwords so they cannot be enrolled by the
user or the admin.

Test: bit FrameworksServicesTests:com.android.server.devicepolicy.PasswordBlacklistTest
Test: bit FrameworksServicesTests:com.android.server.devicepolicy.DevicePolicyManagerTest
Test: cts-tradefed run cts -m CtsDevicePolicyManagerTestCases -t com.android.cts.devicepolicy.MixedManagedProfileOwnerTest#testPasswordBlacklist
Test: cts-tradefed run cts -m CtsAdminTestCases -t android.admin.cts.DevicePolicyManagerTest

Bug: 63578054
Change-Id: I8949ac929c760b66dc719cb058a9f88dc9cad727
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 89df421..eabfa96 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2635,10 +2635,121 @@
     }
 
     /**
+     * The maximum number of characters allowed in the password blacklist.
+     */
+    private static final int PASSWORD_BLACKLIST_CHARACTER_LIMIT = 128 * 1000;
+
+    /**
+     * Throws an exception if the password blacklist is too large.
+     *
+     * @hide
+     */
+    public static void enforcePasswordBlacklistSize(List<String> blacklist) {
+        if (blacklist == null) {
+            return;
+        }
+        long characterCount = 0;
+        for (final String item : blacklist) {
+            characterCount += item.length();
+        }
+        if (characterCount > PASSWORD_BLACKLIST_CHARACTER_LIMIT) {
+            throw new IllegalArgumentException("128 thousand blacklist character limit exceeded by "
+                      + (characterCount - PASSWORD_BLACKLIST_CHARACTER_LIMIT) + " characters");
+        }
+    }
+
+    /**
+     * Called by an application that is administering the device to blacklist passwords.
+     * <p>
+     * Any blacklisted password or PIN is prevented from being enrolled by the user or the admin.
+     * Note that the match against the blacklist is case insensitive. The blacklist applies for all
+     * password qualities requested by {@link #setPasswordQuality} however it is not taken into
+     * consideration by {@link #isActivePasswordSufficient}.
+     * <p>
+     * The blacklist can be cleared by passing {@code null} or an empty list. The blacklist is
+     * given a name that is used to track which blacklist is currently set by calling {@link
+     * #getPasswordBlacklistName}. If the blacklist is being cleared, the name is ignored and {@link
+     * #getPasswordBlacklistName} will return {@code null}. The name can only be {@code null} when
+     * the blacklist is being cleared.
+     * <p>
+     * The blacklist is limited to a total of 128 thousand characters rather than limiting to a
+     * number of entries.
+     * <p>
+     * This method can be called on the {@link DevicePolicyManager} instance returned by
+     * {@link #getParentProfileInstance(ComponentName)} in order to set restrictions on the parent
+     * profile.
+     *
+     * @param admin the {@link DeviceAdminReceiver} this request is associated with
+     * @param name name to associate with the blacklist
+     * @param blacklist list of passwords to blacklist or {@code null} to clear the blacklist
+     * @return whether the new blacklist was successfully installed
+     * @throws SecurityException if {@code admin} is not a device or profile owner
+     * @throws IllegalArgumentException if the blacklist surpasses the character limit
+     * @throws NullPointerException if {@code name} is {@code null} when setting a non-empty list
+     *
+     * @see #getPasswordBlacklistName
+     * @see #isActivePasswordSufficient
+     * @see #resetPasswordWithToken
+     *
+     * TODO(63578054): unhide for P
+     * @hide
+     */
+    public boolean setPasswordBlacklist(@NonNull ComponentName admin, @Nullable String name,
+            @Nullable List<String> blacklist) {
+        enforcePasswordBlacklistSize(blacklist);
+
+        try {
+            return mService.setPasswordBlacklist(admin, name, blacklist, mParentInstance);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Get the name of the password blacklist set by the given admin.
+     *
+     * @param admin the {@link DeviceAdminReceiver} this request is associated with
+     * @return the name of the blacklist or {@code null} if no blacklist is set
+     *
+     * @see #setPasswordBlacklist
+     *
+     * TODO(63578054): unhide for P
+     * @hide
+     */
+    public @Nullable String getPasswordBlacklistName(@NonNull ComponentName admin) {
+        try {
+            return mService.getPasswordBlacklistName(admin, myUserId(), mParentInstance);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Test if a given password is blacklisted.
+     *
+     * @param userId the user to valiate for
+     * @param password the password to check against the blacklist
+     * @return whether the password is blacklisted
+     *
+     * @see #setPasswordBlacklist
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.TEST_BLACKLISTED_PASSWORD)
+    public boolean isPasswordBlacklisted(@UserIdInt int userId, @NonNull String password) {
+        try {
+            return mService.isPasswordBlacklisted(userId, password);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Determine whether the current password the user has set is sufficient to meet the policy
      * requirements (e.g. quality, minimum length) that have been requested by the admins of this
      * user and its participating profiles. Restrictions on profiles that have a separate challenge
-     * are not taken into account. The user must be unlocked in order to perform the check.
+     * are not taken into account. The user must be unlocked in order to perform the check. The
+     * password blacklist is not considered when checking sufficiency.
      * <p>
      * The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 9128208..f4cd797 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -78,6 +78,10 @@
 
     long getPasswordExpiration(in ComponentName who, int userHandle, boolean parent);
 
+    boolean setPasswordBlacklist(in ComponentName who, String name, in List<String> blacklist, boolean parent);
+    String getPasswordBlacklistName(in ComponentName who, int userId, boolean parent);
+    boolean isPasswordBlacklisted(int userId, String password);
+
     boolean isActivePasswordSufficient(int userHandle, boolean parent);
     boolean isProfileActivePasswordSufficientForParent(int userHandle);
     boolean isUsingUnifiedPassword(in ComponentName admin);
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 15e439e..13fedfe 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1620,6 +1620,11 @@
     <permission android:name="android.permission.ACCESS_PDB_STATE"
         android:protectionLevel="signature" />
 
+    <!-- Allows testing if a passwords is forbidden by the admins.
+         @hide <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.TEST_BLACKLISTED_PASSWORD"
+        android:protectionLevel="signature" />
+
     <!-- @hide Allows system update service to notify device owner about pending updates.
    <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.NOTIFY_PENDING_SYSTEM_UPDATE"