DPM API to set the SSID restriction list

Adding API for the admin to set the SSID allowlist or denylist.

Bug: 197558754
Test: atest com.android.server.devicepolicy.DevicePolicyManagerTest
Change-Id: I53617a3e5cac9e5be09467dfe98ac63522931c92
This commit is contained in:
Stephanie Bak
2022-01-04 18:57:15 +00:00
parent 85da5fd5f4
commit a0fc4c0cb9
7 changed files with 426 additions and 0 deletions

View File

@@ -7355,6 +7355,7 @@ package android.app.admin {
method @NonNull public java.util.List<java.lang.String> getUserControlDisabledPackages(@NonNull android.content.ComponentName);
method @NonNull public android.os.Bundle getUserRestrictions(@NonNull android.content.ComponentName);
method @Nullable public String getWifiMacAddress(@NonNull android.content.ComponentName);
method @Nullable public android.app.admin.WifiSsidPolicy getWifiSsidPolicy();
method public boolean grantKeyPairToApp(@Nullable android.content.ComponentName, @NonNull String, @NonNull String);
method public boolean grantKeyPairToWifiAuth(@NonNull String);
method public boolean hasCaCertInstalled(@Nullable android.content.ComponentName, byte[]);
@@ -7508,6 +7509,7 @@ package android.app.admin {
method public void setUsbDataSignalingEnabled(boolean);
method public void setUserControlDisabledPackages(@NonNull android.content.ComponentName, @NonNull java.util.List<java.lang.String>);
method public void setUserIcon(@NonNull android.content.ComponentName, android.graphics.Bitmap);
method public void setWifiSsidPolicy(@Nullable android.app.admin.WifiSsidPolicy);
method public int startUserInBackground(@NonNull android.content.ComponentName, @NonNull android.os.UserHandle);
method public int stopUser(@NonNull android.content.ComponentName, @NonNull android.os.UserHandle);
method public boolean switchUser(@NonNull android.content.ComponentName, @Nullable android.os.UserHandle);
@@ -7869,6 +7871,18 @@ package android.app.admin {
field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.UnsafeStateException> CREATOR;
}
public final class WifiSsidPolicy implements android.os.Parcelable {
method @NonNull public static android.app.admin.WifiSsidPolicy createAllowlistPolicy(@NonNull java.util.Set<java.lang.String>);
method @NonNull public static android.app.admin.WifiSsidPolicy createDenylistPolicy(@NonNull java.util.Set<java.lang.String>);
method public int describeContents();
method public int getPolicyType();
method @NonNull public java.util.Set<java.lang.String> getSsids();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.WifiSsidPolicy> CREATOR;
field public static final int WIFI_SSID_POLICY_TYPE_ALLOWLIST = 0; // 0x0
field public static final int WIFI_SSID_POLICY_TYPE_DENYLIST = 1; // 0x1
}
}
package android.app.assist {

View File

@@ -14670,6 +14670,66 @@ public class DevicePolicyManager {
}
}
/**
* Called by device owner or profile owner of an organization-owned managed profile to
* specify the Wi-Fi SSID policy ({@link WifiSsidPolicy}).
* Wi-Fi SSID policy specifies the SSID restriction the network must satisfy
* in order to be eligible for a connection. Providing a null policy results in the
* deactivation of the SSID restriction
*
* @param policy Wi-Fi SSID policy
* @throws SecurityException if the caller is not a device owner or a profile owner on
* an organization-owned managed profile.
*/
public void setWifiSsidPolicy(@Nullable WifiSsidPolicy policy) {
throwIfParentInstance("setWifiSsidPolicy");
if (mService != null) {
try {
if (policy == null) {
mService.setSsidAllowlist(new ArrayList<>());
} else {
int policyType = policy.getPolicyType();
if (policyType == WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_ALLOWLIST) {
mService.setSsidAllowlist(new ArrayList<>(policy.getSsids()));
} else {
mService.setSsidDenylist(new ArrayList<>(policy.getSsids()));
}
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
/**
* Returns the current Wi-Fi SSID policy.
* If the policy has not been set, it will return NULL.
*
* @see #setWifiSsidPolicy(WifiSsidPolicy)
* @throws SecurityException if the caller is not a device owner or a profile owner on
* an organization-owned managed profile or a system app.
*/
@Nullable
public WifiSsidPolicy getWifiSsidPolicy() {
throwIfParentInstance("getWifiSsidPolicy");
if (mService == null) {
return null;
}
try {
List<String> allowlist = mService.getSsidAllowlist();
if (!allowlist.isEmpty()) {
return WifiSsidPolicy.createAllowlistPolicy(new ArraySet<>(allowlist));
}
List<String> denylist = mService.getSsidDenylist();
if (!denylist.isEmpty()) {
return WifiSsidPolicy.createDenylistPolicy(new ArraySet<>(denylist));
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
return null;
}
/**
* For each {@link DevicePolicyDrawableResource} item in {@code drawables}, if
* {@link DevicePolicyDrawableResource#getDrawableSource()} is not set or is set to

View File

@@ -536,6 +536,11 @@ interface IDevicePolicyManager {
void setMinimumRequiredWifiSecurityLevel(int level);
int getMinimumRequiredWifiSecurityLevel();
void setSsidAllowlist(in List<String> ssids);
List<String> getSsidAllowlist();
void setSsidDenylist(in List<String> ssids);
List<String> getSsidDenylist();
List<UserHandle> listForegroundAffiliatedUsers();
void setDrawables(in List<DevicePolicyDrawableResource> drawables);
void resetDrawables(in int[] drawableIds);

View File

@@ -0,0 +1,153 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app.admin;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArraySet;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Set;
/**
* Used to indicate the Wi-Fi SSID restriction policy the network must satisfy
* in order to be eligible for a connection.
*
* If the policy type is a denylist, the device may not connect to networks on the denylist.
* If the policy type is an allowlist, the device may only connect to networks on the allowlist.
* Admin configured networks are not exempt from this restriction.
* This policy only prohibits connecting to a restricted network and
* does not affect adding a restricted network.
* If the current network is present in the denylist or not present in the allowlist,
* it will be disconnected.
*/
public final class WifiSsidPolicy implements Parcelable {
/**
* SSID policy type indicator for {@link WifiSsidPolicy}.
*
* <p> When returned from {@link WifiSsidPolicy#getPolicyType()}, the constant
* indicates that the SSID policy type is an allowlist.
*
* @see #WIFI_SSID_POLICY_TYPE_DENYLIST
*/
public static final int WIFI_SSID_POLICY_TYPE_ALLOWLIST = 0;
/**
* SSID policy type indicator for {@link WifiSsidPolicy}.
*
* <p> When returned from {@link WifiSsidPolicy#getPolicyType()}, the constant
* indicates that the SSID policy type is a denylist.
*
* @see #WIFI_SSID_POLICY_TYPE_ALLOWLIST
*/
public static final int WIFI_SSID_POLICY_TYPE_DENYLIST = 1;
/**
* Possible SSID policy types
*
* @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"WIFI_SSID_POLICY_TYPE_"}, value = {
WIFI_SSID_POLICY_TYPE_ALLOWLIST,
WIFI_SSID_POLICY_TYPE_DENYLIST})
public @interface WifiSsidPolicyType {}
private @WifiSsidPolicyType int mPolicyType;
private ArraySet<String> mSsids;
private WifiSsidPolicy(@WifiSsidPolicyType int policyType, @NonNull Set<String> ssids) {
mPolicyType = policyType;
mSsids = new ArraySet<>(ssids);
}
private WifiSsidPolicy(Parcel in) {
mPolicyType = in.readInt();
mSsids = (ArraySet<String>) in.readArraySet(null);
}
/**
* Create the allowlist Wi-Fi SSID Policy.
*
* @param ssids allowlist of SSIDs in UTF-8 without double quotes format
* @throws IllegalArgumentException if the input ssids list is empty
*/
@NonNull
public static WifiSsidPolicy createAllowlistPolicy(@NonNull Set<String> ssids) {
if (ssids.isEmpty()) {
throw new IllegalArgumentException("SSID list cannot be empty");
}
return new WifiSsidPolicy(WIFI_SSID_POLICY_TYPE_ALLOWLIST, ssids);
}
/**
* Create the denylist Wi-Fi SSID Policy.
*
* @param ssids denylist of SSIDs in UTF-8 without double quotes format
* @throws IllegalArgumentException if the input ssids list is empty
*/
@NonNull
public static WifiSsidPolicy createDenylistPolicy(@NonNull Set<String> ssids) {
if (ssids.isEmpty()) {
throw new IllegalArgumentException("SSID list cannot be empty");
}
return new WifiSsidPolicy(WIFI_SSID_POLICY_TYPE_DENYLIST, ssids);
}
/**
* Returns the set of SSIDs in UTF-8 without double quotes format.
*/
@NonNull
public Set<String> getSsids() {
return mSsids;
}
/**
* Returns the policy type.
*/
public @WifiSsidPolicyType int getPolicyType() {
return mPolicyType;
}
/**
* @see Parcelable.Creator
*/
@NonNull
public static final Creator<WifiSsidPolicy> CREATOR = new Creator<WifiSsidPolicy>() {
@Override
public WifiSsidPolicy createFromParcel(Parcel source) {
return new WifiSsidPolicy(source);
}
@Override
public WifiSsidPolicy[] newArray(int size) {
return new WifiSsidPolicy[size];
}
};
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mPolicyType);
dest.writeArraySet(mSsids);
}
@Override
public int describeContents() {
return 0;
}
}

View File

@@ -146,6 +146,9 @@ class ActiveAdmin {
"preferential-network-service-enabled";
private static final String TAG_USB_DATA_SIGNALING = "usb-data-signaling";
private static final String TAG_WIFI_MIN_SECURITY = "wifi-min-security";
private static final String TAG_SSID_ALLOWLIST = "ssid-allowlist";
private static final String TAG_SSID_DENYLIST = "ssid-denylist";
private static final String TAG_SSID = "ssid";
private static final String ATTR_VALUE = "value";
private static final String ATTR_LAST_NETWORK_LOGGING_NOTIFICATION = "last-notification";
private static final String ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS = "num-notifications";
@@ -238,6 +241,14 @@ class ActiveAdmin {
// List of package names to keep cached.
List<String> keepUninstalledPackages;
// The allowlist of SSIDs the device may connect to.
// By default, the allowlist restriction is deactivated.
List<String> mSsidAllowlist;
// The denylist of SSIDs the device may not connect to.
// By default, the denylist restriction is deactivated.
List<String> mSsidDenylist;
// TODO: review implementation decisions with frameworks team
boolean specifiesGlobalProxy = false;
String globalProxySpec = null;
@@ -580,6 +591,12 @@ class ActiveAdmin {
if (mWifiMinimumSecurityLevel != DevicePolicyManager.WIFI_SECURITY_OPEN) {
writeAttributeValueToXml(out, TAG_WIFI_MIN_SECURITY, mWifiMinimumSecurityLevel);
}
if (mSsidAllowlist != null && !mSsidAllowlist.isEmpty()) {
writeAttributeValuesToXml(out, TAG_SSID_ALLOWLIST, TAG_SSID, mSsidAllowlist);
}
if (mSsidDenylist != null && !mSsidDenylist.isEmpty()) {
writeAttributeValuesToXml(out, TAG_SSID_DENYLIST, TAG_SSID, mSsidDenylist);
}
}
void writeTextToXml(TypedXmlSerializer out, String tag, String text) throws IOException {
@@ -834,6 +851,12 @@ class ActiveAdmin {
USB_DATA_SIGNALING_ENABLED_DEFAULT);
} else if (TAG_WIFI_MIN_SECURITY.equals(tag)) {
mWifiMinimumSecurityLevel = parser.getAttributeInt(null, ATTR_VALUE);
} else if (TAG_SSID_ALLOWLIST.equals(tag)) {
mSsidAllowlist = new ArrayList<>();
readAttributeValues(parser, TAG_SSID, mSsidAllowlist);
} else if (TAG_SSID_DENYLIST.equals(tag)) {
mSsidDenylist = new ArrayList<>();
readAttributeValues(parser, TAG_SSID, mSsidDenylist);
} else {
Slogf.w(LOG_TAG, "Unknown admin tag: %s", tag);
XmlUtils.skipCurrentTag(parser);
@@ -1195,5 +1218,11 @@ class ActiveAdmin {
pw.print("mWifiMinimumSecurityLevel=");
pw.println(mWifiMinimumSecurityLevel);
pw.print("mSsidAllowlist=");
pw.println(mSsidAllowlist);
pw.print("mSsidDenylist=");
pw.println(mSsidDenylist);
}
}

View File

@@ -18014,6 +18014,82 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
}
@Override
public void setSsidAllowlist(List<String> ssids) {
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(
isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
"SSID allowlist can only be controlled by a device owner or "
+ "a profile owner on an organization-owned device.");
Collections.sort(ssids);
boolean changed = false;
synchronized (getLockObject()) {
final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
if (!ssids.equals(admin.mSsidAllowlist)) {
admin.mSsidAllowlist = ssids;
admin.mSsidDenylist = null;
changed = true;
}
if (changed) saveSettingsLocked(caller.getUserId());
}
if (changed) validateCurrentWifiMeetsAdminRequirements();
}
@Override
public List<String> getSsidAllowlist() {
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(
isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)
|| isSystemUid(caller),
"SSID allowlist can only be retrieved by a device owner or "
+ "a profile owner on an organization-owned device or a system app.");
synchronized (getLockObject()) {
final ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
UserHandle.USER_SYSTEM);
return (admin == null || admin.mSsidAllowlist == null) ? new ArrayList<>()
: admin.mSsidAllowlist;
}
}
@Override
public void setSsidDenylist(List<String> ssids) {
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(
isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
"SSID denylist can only be controlled by a device owner or "
+ "a profile owner on an organization-owned device.");
Collections.sort(ssids);
boolean changed = false;
synchronized (getLockObject()) {
final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
if (!ssids.equals(admin.mSsidDenylist)) {
admin.mSsidDenylist = ssids;
admin.mSsidAllowlist = null;
changed = true;
}
if (changed) saveSettingsLocked(caller.getUserId());
}
if (changed) validateCurrentWifiMeetsAdminRequirements();
}
@Override
public List<String> getSsidDenylist() {
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(
isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)
|| isSystemUid(caller),
"SSID denylist can only be retrieved by a device owner or "
+ "a profile owner on an organization-owned device or a system app.");
synchronized (getLockObject()) {
final ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
UserHandle.USER_SYSTEM);
return (admin == null || admin.mSsidDenylist == null) ? new ArrayList<>()
: admin.mSsidDenylist;
}
}
@Override
public void setDrawables(@NonNull List<DevicePolicyDrawableResource> drawables) {
Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(

View File

@@ -95,6 +95,7 @@ import android.app.admin.DevicePolicyManagerLiteInternal;
import android.app.admin.FactoryResetProtectionPolicy;
import android.app.admin.PasswordMetrics;
import android.app.admin.SystemUpdatePolicy;
import android.app.admin.WifiSsidPolicy;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Intent;
@@ -7866,6 +7867,94 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
}
@Test
public void testSetSsidAllowlist_noDeviceOwnerOrPoOfOrgOwnedDevice() {
final Set<String> ssids = Collections.singleton("ssid1");
WifiSsidPolicy policy = WifiSsidPolicy.createAllowlistPolicy(ssids);
assertThrows(SecurityException.class, () -> dpm.setWifiSsidPolicy(policy));
}
@Test
public void testSetSsidAllowlist_asDeviceOwner() throws Exception {
setDeviceOwner();
final Set<String> ssids = Collections.singleton("ssid1");
WifiSsidPolicy policy = WifiSsidPolicy.createAllowlistPolicy(ssids);
dpm.setWifiSsidPolicy(policy);
assertThat(dpm.getWifiSsidPolicy().getSsids()).isEqualTo(ssids);
assertThat(dpm.getWifiSsidPolicy().getPolicyType()).isEqualTo(
WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_ALLOWLIST);
}
@Test
public void testSetSsidAllowlist_asPoOfOrgOwnedDevice() throws Exception {
final int managedProfileUserId = 15;
final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436);
addManagedProfile(admin1, managedProfileAdminUid, admin1);
configureProfileOwnerOfOrgOwnedDevice(admin1, managedProfileUserId);
mContext.binder.callingUid = managedProfileAdminUid;
final Set<String> ssids = new ArraySet<>(Arrays.asList("ssid1", "ssid2", "ssid3"));
WifiSsidPolicy policy = WifiSsidPolicy.createAllowlistPolicy(ssids);
dpm.setWifiSsidPolicy(policy);
assertThat(dpm.getWifiSsidPolicy().getSsids()).isEqualTo(ssids);
assertThat(dpm.getWifiSsidPolicy().getPolicyType()).isEqualTo(
WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_ALLOWLIST);
}
@Test
public void testSetSsidAllowlist_emptyList() throws Exception {
setDeviceOwner();
final Set<String> ssids = new ArraySet<>();
assertThrows(IllegalArgumentException.class,
() -> WifiSsidPolicy.createAllowlistPolicy(ssids));
}
@Test
public void testSetSsidDenylist_noDeviceOwnerOrPoOfOrgOwnedDevice() {
final Set<String> ssids = Collections.singleton("ssid1");
WifiSsidPolicy policy = WifiSsidPolicy.createDenylistPolicy(ssids);
assertThrows(SecurityException.class, () -> dpm.setWifiSsidPolicy(policy));
}
@Test
public void testSetSsidDenylist_asDeviceOwner() throws Exception {
setDeviceOwner();
final Set<String> ssids = Collections.singleton("ssid1");
WifiSsidPolicy policy = WifiSsidPolicy.createDenylistPolicy(ssids);
dpm.setWifiSsidPolicy(policy);
assertThat(dpm.getWifiSsidPolicy().getSsids()).isEqualTo(ssids);
assertThat(dpm.getWifiSsidPolicy().getPolicyType()).isEqualTo(
WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_DENYLIST);
}
@Test
public void testSetSsidDenylist_asPoOfOrgOwnedDevice() throws Exception {
final int managedProfileUserId = 15;
final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436);
addManagedProfile(admin1, managedProfileAdminUid, admin1);
configureProfileOwnerOfOrgOwnedDevice(admin1, managedProfileUserId);
mContext.binder.callingUid = managedProfileAdminUid;
final Set<String> ssids = new ArraySet<>(Arrays.asList("ssid1", "ssid2", "ssid3"));
WifiSsidPolicy policy = WifiSsidPolicy.createDenylistPolicy(ssids);
dpm.setWifiSsidPolicy(policy);
assertThat(dpm.getWifiSsidPolicy().getSsids()).isEqualTo(ssids);
assertThat(dpm.getWifiSsidPolicy().getPolicyType()).isEqualTo(
WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_DENYLIST);
}
@Test
public void testSetSsidDenylist_emptyList() throws Exception {
setDeviceOwner();
final Set<String> ssids = new ArraySet<>();
assertThrows(IllegalArgumentException.class,
() -> WifiSsidPolicy.createDenylistPolicy(ssids));
}
private void setupVpnAuthorization(String userVpnPackage, int userVpnUid) {
final AppOpsManager.PackageOps vpnOp = new AppOpsManager.PackageOps(userVpnPackage,
userVpnUid, List.of(new AppOpsManager.OpEntry(