Merge "Add API for CaptivePortalData"

This commit is contained in:
Remi NGUYEN VAN
2020-01-22 15:55:17 +00:00
committed by Gerrit Code Review
6 changed files with 691 additions and 49 deletions

View File

@@ -0,0 +1,281 @@
/*
* Copyright (C) 2019 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.net;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Objects;
/**
* Metadata sent by captive portals, see https://www.ietf.org/id/draft-ietf-capport-api-03.txt.
* @hide
*/
@SystemApi
@TestApi
public final class CaptivePortalData implements Parcelable {
private final long mRefreshTimeMillis;
@Nullable
private final Uri mUserPortalUrl;
@Nullable
private final Uri mVenueInfoUrl;
private final boolean mIsSessionExtendable;
private final long mByteLimit;
private final long mExpiryTimeMillis;
private final boolean mCaptive;
private CaptivePortalData(long refreshTimeMillis, Uri userPortalUrl, Uri venueInfoUrl,
boolean isSessionExtendable, long byteLimit, long expiryTimeMillis, boolean captive) {
mRefreshTimeMillis = refreshTimeMillis;
mUserPortalUrl = userPortalUrl;
mVenueInfoUrl = venueInfoUrl;
mIsSessionExtendable = isSessionExtendable;
mByteLimit = byteLimit;
mExpiryTimeMillis = expiryTimeMillis;
mCaptive = captive;
}
private CaptivePortalData(Parcel p) {
this(p.readLong(), p.readParcelable(null), p.readParcelable(null), p.readBoolean(),
p.readLong(), p.readLong(), p.readBoolean());
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeLong(mRefreshTimeMillis);
dest.writeParcelable(mUserPortalUrl, 0);
dest.writeParcelable(mVenueInfoUrl, 0);
dest.writeBoolean(mIsSessionExtendable);
dest.writeLong(mByteLimit);
dest.writeLong(mExpiryTimeMillis);
dest.writeBoolean(mCaptive);
}
/**
* A builder to create new {@link CaptivePortalData}.
*/
public static class Builder {
private long mRefreshTime;
private Uri mUserPortalUrl;
private Uri mVenueInfoUrl;
private boolean mIsSessionExtendable;
private long mBytesRemaining = -1;
private long mExpiryTime = -1;
private boolean mCaptive;
/**
* Create an empty builder.
*/
public Builder() {}
/**
* Create a builder copying all data from existing {@link CaptivePortalData}.
*/
public Builder(@Nullable CaptivePortalData data) {
if (data == null) return;
setRefreshTime(data.mRefreshTimeMillis)
.setUserPortalUrl(data.mUserPortalUrl)
.setVenueInfoUrl(data.mVenueInfoUrl)
.setSessionExtendable(data.mIsSessionExtendable)
.setBytesRemaining(data.mByteLimit)
.setExpiryTime(data.mExpiryTimeMillis)
.setCaptive(data.mCaptive);
}
/**
* Set the time at which data was last refreshed, as per {@link System#currentTimeMillis()}.
*/
@NonNull
public Builder setRefreshTime(long refreshTime) {
mRefreshTime = refreshTime;
return this;
}
/**
* Set the URL to be used for users to login to the portal, if captive.
*/
@NonNull
public Builder setUserPortalUrl(@Nullable Uri userPortalUrl) {
mUserPortalUrl = userPortalUrl;
return this;
}
/**
* Set the URL that can be used by users to view information about the network venue.
*/
@NonNull
public Builder setVenueInfoUrl(@Nullable Uri venueInfoUrl) {
mVenueInfoUrl = venueInfoUrl;
return this;
}
/**
* Set whether the portal supports extending a user session on the portal URL page.
*/
@NonNull
public Builder setSessionExtendable(boolean sessionExtendable) {
mIsSessionExtendable = sessionExtendable;
return this;
}
/**
* Set the number of bytes remaining on the network before the portal closes.
*/
@NonNull
public Builder setBytesRemaining(long bytesRemaining) {
mBytesRemaining = bytesRemaining;
return this;
}
/**
* Set the time at the session will expire, as per {@link System#currentTimeMillis()}.
*/
@NonNull
public Builder setExpiryTime(long expiryTime) {
mExpiryTime = expiryTime;
return this;
}
/**
* Set whether the network is captive (portal closed).
*/
@NonNull
public Builder setCaptive(boolean captive) {
mCaptive = captive;
return this;
}
/**
* Create a new {@link CaptivePortalData}.
*/
@NonNull
public CaptivePortalData build() {
return new CaptivePortalData(mRefreshTime, mUserPortalUrl, mVenueInfoUrl,
mIsSessionExtendable, mBytesRemaining, mExpiryTime, mCaptive);
}
}
/**
* Get the time at which data was last refreshed, as per {@link System#currentTimeMillis()}.
*/
public long getRefreshTimeMillis() {
return mRefreshTimeMillis;
}
/**
* Get the URL to be used for users to login to the portal, or extend their session if
* {@link #isSessionExtendable()} is true.
*/
@Nullable
public Uri getUserPortalUrl() {
return mUserPortalUrl;
}
/**
* Get the URL that can be used by users to view information about the network venue.
*/
@Nullable
public Uri getVenueInfoUrl() {
return mVenueInfoUrl;
}
/**
* Indicates whether the user portal URL can be used to extend sessions, when the user is logged
* in and the session has a time or byte limit.
*/
public boolean isSessionExtendable() {
return mIsSessionExtendable;
}
/**
* Get the remaining bytes on the captive portal session, at the time {@link CaptivePortalData}
* was refreshed. This may be different from the limit currently enforced by the portal.
* @return The byte limit, or -1 if not set.
*/
public long getByteLimit() {
return mByteLimit;
}
/**
* Get the time at the session will expire, as per {@link System#currentTimeMillis()}.
* @return The expiry time, or -1 if unset.
*/
public long getExpiryTimeMillis() {
return mExpiryTimeMillis;
}
/**
* Get whether the network is captive (portal closed).
*/
public boolean isCaptive() {
return mCaptive;
}
@NonNull
public static final Creator<CaptivePortalData> CREATOR = new Creator<CaptivePortalData>() {
@Override
public CaptivePortalData createFromParcel(Parcel source) {
return new CaptivePortalData(source);
}
@Override
public CaptivePortalData[] newArray(int size) {
return new CaptivePortalData[size];
}
};
@Override
public int hashCode() {
return Objects.hash(mRefreshTimeMillis, mUserPortalUrl, mVenueInfoUrl,
mIsSessionExtendable, mByteLimit, mExpiryTimeMillis, mCaptive);
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof CaptivePortalData)) return false;
final CaptivePortalData other = (CaptivePortalData) obj;
return mRefreshTimeMillis == other.mRefreshTimeMillis
&& Objects.equals(mUserPortalUrl, other.mUserPortalUrl)
&& Objects.equals(mVenueInfoUrl, other.mVenueInfoUrl)
&& mIsSessionExtendable == other.mIsSessionExtendable
&& mByteLimit == other.mByteLimit
&& mExpiryTimeMillis == other.mExpiryTimeMillis
&& mCaptive == other.mCaptive;
}
@Override
public String toString() {
return "CaptivePortalData {"
+ "refreshTime: " + mRefreshTimeMillis
+ ", userPortalUrl: " + mUserPortalUrl
+ ", venueInfoUrl: " + mVenueInfoUrl
+ ", isSessionExtendable: " + mIsSessionExtendable
+ ", byteLimit: " + mByteLimit
+ ", expiryTime: " + mExpiryTimeMillis
+ ", captive: " + mCaptive
+ "}";
}
}

View File

@@ -70,6 +70,14 @@ public final class LinkProperties implements Parcelable {
private String mTcpBufferSizes; private String mTcpBufferSizes;
private IpPrefix mNat64Prefix; private IpPrefix mNat64Prefix;
private boolean mWakeOnLanSupported; private boolean mWakeOnLanSupported;
private Uri mCaptivePortalApiUrl;
private CaptivePortalData mCaptivePortalData;
/**
* Indicates whether parceling should preserve fields that are set based on permissions of
* the process receiving the {@link LinkProperties}.
*/
private final transient boolean mParcelSensitiveFields;
private static final int MIN_MTU = 68; private static final int MIN_MTU = 68;
private static final int MIN_MTU_V6 = 1280; private static final int MIN_MTU_V6 = 1280;
@@ -174,6 +182,7 @@ public final class LinkProperties implements Parcelable {
* Constructs a new {@code LinkProperties} with default values. * Constructs a new {@code LinkProperties} with default values.
*/ */
public LinkProperties() { public LinkProperties() {
mParcelSensitiveFields = false;
} }
/** /**
@@ -182,26 +191,32 @@ public final class LinkProperties implements Parcelable {
@SystemApi @SystemApi
@TestApi @TestApi
public LinkProperties(@Nullable LinkProperties source) { public LinkProperties(@Nullable LinkProperties source) {
if (source != null) { this(source, false /* parcelSensitiveFields */);
mIfaceName = source.mIfaceName; }
mLinkAddresses.addAll(source.mLinkAddresses);
mDnses.addAll(source.mDnses); private LinkProperties(@Nullable LinkProperties source, boolean parcelSensitiveFields) {
mValidatedPrivateDnses.addAll(source.mValidatedPrivateDnses); mParcelSensitiveFields = parcelSensitiveFields;
mUsePrivateDns = source.mUsePrivateDns; if (source == null) return;
mPrivateDnsServerName = source.mPrivateDnsServerName; mIfaceName = source.mIfaceName;
mPcscfs.addAll(source.mPcscfs); mLinkAddresses.addAll(source.mLinkAddresses);
mDomains = source.mDomains; mDnses.addAll(source.mDnses);
mRoutes.addAll(source.mRoutes); mValidatedPrivateDnses.addAll(source.mValidatedPrivateDnses);
mHttpProxy = (source.mHttpProxy == null) ? null : new ProxyInfo(source.mHttpProxy); mUsePrivateDns = source.mUsePrivateDns;
for (LinkProperties l: source.mStackedLinks.values()) { mPrivateDnsServerName = source.mPrivateDnsServerName;
addStackedLink(l); mPcscfs.addAll(source.mPcscfs);
} mDomains = source.mDomains;
setMtu(source.mMtu); mRoutes.addAll(source.mRoutes);
setDhcpServerAddress(source.getDhcpServerAddress()); mHttpProxy = (source.mHttpProxy == null) ? null : new ProxyInfo(source.mHttpProxy);
mTcpBufferSizes = source.mTcpBufferSizes; for (LinkProperties l: source.mStackedLinks.values()) {
mNat64Prefix = source.mNat64Prefix; addStackedLink(l);
mWakeOnLanSupported = source.mWakeOnLanSupported;
} }
setMtu(source.mMtu);
setDhcpServerAddress(source.getDhcpServerAddress());
mTcpBufferSizes = source.mTcpBufferSizes;
mNat64Prefix = source.mNat64Prefix;
mWakeOnLanSupported = source.mWakeOnLanSupported;
mCaptivePortalApiUrl = source.mCaptivePortalApiUrl;
mCaptivePortalData = source.mCaptivePortalData;
} }
/** /**
@@ -860,6 +875,11 @@ public final class LinkProperties implements Parcelable {
* Clears this object to its initial state. * Clears this object to its initial state.
*/ */
public void clear() { public void clear() {
if (mParcelSensitiveFields) {
throw new UnsupportedOperationException(
"Cannot clear LinkProperties when parcelSensitiveFields is set");
}
mIfaceName = null; mIfaceName = null;
mLinkAddresses.clear(); mLinkAddresses.clear();
mDnses.clear(); mDnses.clear();
@@ -875,6 +895,8 @@ public final class LinkProperties implements Parcelable {
mTcpBufferSizes = null; mTcpBufferSizes = null;
mNat64Prefix = null; mNat64Prefix = null;
mWakeOnLanSupported = false; mWakeOnLanSupported = false;
mCaptivePortalApiUrl = null;
mCaptivePortalData = null;
} }
/** /**
@@ -945,6 +967,14 @@ public final class LinkProperties implements Parcelable {
resultJoiner.add(mDhcpServerAddress.toString()); resultJoiner.add(mDhcpServerAddress.toString());
} }
if (mCaptivePortalApiUrl != null) {
resultJoiner.add("CaptivePortalApiUrl: " + mCaptivePortalApiUrl);
}
if (mCaptivePortalData != null) {
resultJoiner.add("CaptivePortalData: " + mCaptivePortalData);
}
if (mTcpBufferSizes != null) { if (mTcpBufferSizes != null) {
resultJoiner.add("TcpBufferSizes:"); resultJoiner.add("TcpBufferSizes:");
resultJoiner.add(mTcpBufferSizes); resultJoiner.add(mTcpBufferSizes);
@@ -1478,6 +1508,28 @@ public final class LinkProperties implements Parcelable {
return isWakeOnLanSupported() == target.isWakeOnLanSupported(); return isWakeOnLanSupported() == target.isWakeOnLanSupported();
} }
/**
* Compares this {@code LinkProperties}'s CaptivePortalApiUrl against the target.
*
* @param target LinkProperties to compare.
* @return {@code true} if both are identical, {@code false} otherwise.
* @hide
*/
public boolean isIdenticalCaptivePortalApiUrl(LinkProperties target) {
return Objects.equals(mCaptivePortalApiUrl, target.mCaptivePortalApiUrl);
}
/**
* Compares this {@code LinkProperties}'s CaptivePortalData against the target.
*
* @param target LinkProperties to compare.
* @return {@code true} if both are identical, {@code false} otherwise.
* @hide
*/
public boolean isIdenticalCaptivePortalData(LinkProperties target) {
return Objects.equals(mCaptivePortalData, target.mCaptivePortalData);
}
/** /**
* Set whether the network interface supports WakeOnLAN * Set whether the network interface supports WakeOnLAN
* *
@@ -1498,6 +1550,73 @@ public final class LinkProperties implements Parcelable {
return mWakeOnLanSupported; return mWakeOnLanSupported;
} }
/**
* Set the URL of the captive portal API endpoint to get more information about the network.
* @hide
*/
@SystemApi
@TestApi
public void setCaptivePortalApiUrl(@Nullable Uri url) {
mCaptivePortalApiUrl = url;
}
/**
* Get the URL of the captive portal API endpoint to get more information about the network.
*
* <p>This is null unless the application has
* {@link android.Manifest.permission.NETWORK_SETTINGS} or
* {@link NetworkStack#PERMISSION_MAINLINE_NETWORK_STACK} permissions, and the network provided
* the URL.
* @hide
*/
@SystemApi
@TestApi
@Nullable
public Uri getCaptivePortalApiUrl() {
return mCaptivePortalApiUrl;
}
/**
* Set the CaptivePortalData obtained from the captive portal API (RFC7710bis).
* @hide
*/
@SystemApi
@TestApi
public void setCaptivePortalData(@Nullable CaptivePortalData data) {
mCaptivePortalData = data;
}
/**
* Get the CaptivePortalData obtained from the captive portal API (RFC7710bis).
*
* <p>This is null unless the application has
* {@link android.Manifest.permission.NETWORK_SETTINGS} or
* {@link NetworkStack#PERMISSION_MAINLINE_NETWORK_STACK} permissions.
* @hide
*/
@SystemApi
@TestApi
@Nullable
public CaptivePortalData getCaptivePortalData() {
return mCaptivePortalData;
}
/**
* Create a copy of this {@link LinkProperties} that will preserve fields that were set
* based on the permissions of the process that received this {@link LinkProperties}.
*
* <p>By default {@link LinkProperties} does not preserve such fields during parceling, as
* they should not be shared outside of the process that receives them without appropriate
* checks.
* @hide
*/
@SystemApi
@TestApi
@NonNull
public LinkProperties makeSensitiveFieldsParcelingCopy() {
return new LinkProperties(this, true /* parcelSensitiveFields */);
}
/** /**
* Compares this {@code LinkProperties} instance against the target * Compares this {@code LinkProperties} instance against the target
* LinkProperties in {@code obj}. Two LinkPropertieses are equal if * LinkProperties in {@code obj}. Two LinkPropertieses are equal if
@@ -1537,7 +1656,9 @@ public final class LinkProperties implements Parcelable {
&& isIdenticalMtu(target) && isIdenticalMtu(target)
&& isIdenticalTcpBufferSizes(target) && isIdenticalTcpBufferSizes(target)
&& isIdenticalNat64Prefix(target) && isIdenticalNat64Prefix(target)
&& isIdenticalWakeOnLan(target); && isIdenticalWakeOnLan(target)
&& isIdenticalCaptivePortalApiUrl(target)
&& isIdenticalCaptivePortalData(target);
} }
/** /**
@@ -1655,7 +1776,8 @@ public final class LinkProperties implements Parcelable {
+ mPcscfs.size() * 67 + mPcscfs.size() * 67
+ ((null == mPrivateDnsServerName) ? 0 : mPrivateDnsServerName.hashCode()) + ((null == mPrivateDnsServerName) ? 0 : mPrivateDnsServerName.hashCode())
+ Objects.hash(mNat64Prefix) + Objects.hash(mNat64Prefix)
+ (mWakeOnLanSupported ? 71 : 0); + (mWakeOnLanSupported ? 71 : 0)
+ Objects.hash(mCaptivePortalApiUrl, mCaptivePortalData);
} }
/** /**
@@ -1694,6 +1816,8 @@ public final class LinkProperties implements Parcelable {
dest.writeList(stackedLinks); dest.writeList(stackedLinks);
dest.writeBoolean(mWakeOnLanSupported); dest.writeBoolean(mWakeOnLanSupported);
dest.writeParcelable(mParcelSensitiveFields ? mCaptivePortalApiUrl : null, 0);
dest.writeParcelable(mParcelSensitiveFields ? mCaptivePortalData : null, 0);
} }
private static void writeAddresses(@NonNull Parcel dest, @NonNull List<InetAddress> list) { private static void writeAddresses(@NonNull Parcel dest, @NonNull List<InetAddress> list) {
@@ -1785,6 +1909,9 @@ public final class LinkProperties implements Parcelable {
netProp.addStackedLink(stackedLink); netProp.addStackedLink(stackedLink);
} }
netProp.setWakeOnLanSupported(in.readBoolean()); netProp.setWakeOnLanSupported(in.readBoolean());
netProp.setCaptivePortalApiUrl(in.readParcelable(null));
netProp.setCaptivePortalData(in.readParcelable(null));
return netProp; return netProp;
} }

View File

@@ -1573,48 +1573,49 @@ public class ConnectivityService extends IConnectivityManager.Stub
enforceAccessPermission(); enforceAccessPermission();
final int uid = Binder.getCallingUid(); final int uid = Binder.getCallingUid();
NetworkState state = getUnfilteredActiveNetworkState(uid); NetworkState state = getUnfilteredActiveNetworkState(uid);
return state.linkProperties; if (state.linkProperties == null) return null;
return linkPropertiesRestrictedForCallerPermissions(state.linkProperties,
Binder.getCallingPid(), uid);
} }
@Override @Override
public LinkProperties getLinkPropertiesForType(int networkType) { public LinkProperties getLinkPropertiesForType(int networkType) {
enforceAccessPermission(); enforceAccessPermission();
NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType); NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
if (nai != null) { final LinkProperties lp = getLinkProperties(nai);
synchronized (nai) { if (lp == null) return null;
return new LinkProperties(nai.linkProperties); return linkPropertiesRestrictedForCallerPermissions(
} lp, Binder.getCallingPid(), Binder.getCallingUid());
}
return null;
} }
// TODO - this should be ALL networks // TODO - this should be ALL networks
@Override @Override
public LinkProperties getLinkProperties(Network network) { public LinkProperties getLinkProperties(Network network) {
enforceAccessPermission(); enforceAccessPermission();
return getLinkProperties(getNetworkAgentInfoForNetwork(network)); final LinkProperties lp = getLinkProperties(getNetworkAgentInfoForNetwork(network));
if (lp == null) return null;
return linkPropertiesRestrictedForCallerPermissions(
lp, Binder.getCallingPid(), Binder.getCallingUid());
} }
private LinkProperties getLinkProperties(NetworkAgentInfo nai) { @Nullable
private LinkProperties getLinkProperties(@Nullable NetworkAgentInfo nai) {
if (nai == null) { if (nai == null) {
return null; return null;
} }
synchronized (nai) { synchronized (nai) {
return new LinkProperties(nai.linkProperties); return nai.linkProperties;
} }
} }
private NetworkCapabilities getNetworkCapabilitiesInternal(NetworkAgentInfo nai) { private NetworkCapabilities getNetworkCapabilitiesInternal(NetworkAgentInfo nai) {
if (nai != null) { if (nai == null) return null;
synchronized (nai) { synchronized (nai) {
if (nai.networkCapabilities != null) { if (nai.networkCapabilities == null) return null;
return networkCapabilitiesRestrictedForCallerPermissions( return networkCapabilitiesRestrictedForCallerPermissions(
nai.networkCapabilities, nai.networkCapabilities,
Binder.getCallingPid(), Binder.getCallingUid()); Binder.getCallingPid(), Binder.getCallingUid());
}
}
} }
return null;
} }
@Override @Override
@@ -1636,6 +1637,29 @@ public class ConnectivityService extends IConnectivityManager.Stub
return newNc; return newNc;
} }
private LinkProperties linkPropertiesRestrictedForCallerPermissions(
LinkProperties lp, int callerPid, int callerUid) {
if (lp == null) return new LinkProperties();
// Only do a permission check if sanitization is needed, to avoid unnecessary binder calls.
final boolean needsSanitization =
(lp.getCaptivePortalApiUrl() != null || lp.getCaptivePortalData() != null);
if (!needsSanitization) {
return new LinkProperties(lp);
}
if (checkSettingsPermission(callerPid, callerUid)) {
return lp.makeSensitiveFieldsParcelingCopy();
}
final LinkProperties newLp = new LinkProperties(lp);
// Sensitive fields would not be parceled anyway, but sanitize for consistency before the
// object gets parceled.
newLp.setCaptivePortalApiUrl(null);
newLp.setCaptivePortalData(null);
return newLp;
}
private void restrictRequestUidsForCaller(NetworkCapabilities nc) { private void restrictRequestUidsForCaller(NetworkCapabilities nc) {
if (!checkSettingsPermission()) { if (!checkSettingsPermission()) {
nc.setSingleUid(Binder.getCallingUid()); nc.setSingleUid(Binder.getCallingUid());
@@ -6138,7 +6162,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
case ConnectivityManager.CALLBACK_AVAILABLE: { case ConnectivityManager.CALLBACK_AVAILABLE: {
putParcelable(bundle, networkCapabilitiesRestrictedForCallerPermissions( putParcelable(bundle, networkCapabilitiesRestrictedForCallerPermissions(
networkAgent.networkCapabilities, nri.mPid, nri.mUid)); networkAgent.networkCapabilities, nri.mPid, nri.mUid));
putParcelable(bundle, new LinkProperties(networkAgent.linkProperties)); putParcelable(bundle, linkPropertiesRestrictedForCallerPermissions(
networkAgent.linkProperties, nri.mPid, nri.mUid));
// For this notification, arg1 contains the blocked status. // For this notification, arg1 contains the blocked status.
msg.arg1 = arg1; msg.arg1 = arg1;
break; break;
@@ -6155,7 +6180,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
break; break;
} }
case ConnectivityManager.CALLBACK_IP_CHANGED: { case ConnectivityManager.CALLBACK_IP_CHANGED: {
putParcelable(bundle, new LinkProperties(networkAgent.linkProperties)); putParcelable(bundle, linkPropertiesRestrictedForCallerPermissions(
networkAgent.linkProperties, nri.mPid, nri.mUid));
break; break;
} }
case ConnectivityManager.CALLBACK_BLK_CHANGED: { case ConnectivityManager.CALLBACK_BLK_CHANGED: {

View File

@@ -75,6 +75,9 @@ public class LinkPropertiesTest {
private static final LinkAddress LINKADDRV4 = new LinkAddress(ADDRV4, 32); private static final LinkAddress LINKADDRV4 = new LinkAddress(ADDRV4, 32);
private static final LinkAddress LINKADDRV6 = new LinkAddress(ADDRV6, 128); private static final LinkAddress LINKADDRV6 = new LinkAddress(ADDRV6, 128);
private static final LinkAddress LINKADDRV6LINKLOCAL = new LinkAddress("fe80::1/64"); private static final LinkAddress LINKADDRV6LINKLOCAL = new LinkAddress("fe80::1/64");
private static final Uri CAPPORT_API_URL = Uri.parse("https://test.example.com/capportapi");
private static final CaptivePortalData CAPPORT_DATA = new CaptivePortalData.Builder()
.setVenueInfoUrl(Uri.parse("https://test.example.com/venue")).build();
private static InetAddress address(String addrString) { private static InetAddress address(String addrString) {
return InetAddresses.parseNumericAddress(addrString); return InetAddresses.parseNumericAddress(addrString);
@@ -101,6 +104,8 @@ public class LinkPropertiesTest {
assertFalse(lp.isIpv6Provisioned()); assertFalse(lp.isIpv6Provisioned());
assertFalse(lp.isPrivateDnsActive()); assertFalse(lp.isPrivateDnsActive());
assertFalse(lp.isWakeOnLanSupported()); assertFalse(lp.isWakeOnLanSupported());
assertNull(lp.getCaptivePortalApiUrl());
assertNull(lp.getCaptivePortalData());
} }
private LinkProperties makeTestObject() { private LinkProperties makeTestObject() {
@@ -124,6 +129,8 @@ public class LinkPropertiesTest {
lp.setNat64Prefix(new IpPrefix("2001:db8:0:64::/96")); lp.setNat64Prefix(new IpPrefix("2001:db8:0:64::/96"));
lp.setDhcpServerAddress(DHCPSERVER); lp.setDhcpServerAddress(DHCPSERVER);
lp.setWakeOnLanSupported(true); lp.setWakeOnLanSupported(true);
lp.setCaptivePortalApiUrl(CAPPORT_API_URL);
lp.setCaptivePortalData(CAPPORT_DATA);
return lp; return lp;
} }
@@ -165,6 +172,12 @@ public class LinkPropertiesTest {
assertTrue(source.isIdenticalWakeOnLan(target)); assertTrue(source.isIdenticalWakeOnLan(target));
assertTrue(target.isIdenticalWakeOnLan(source)); assertTrue(target.isIdenticalWakeOnLan(source));
assertTrue(source.isIdenticalCaptivePortalApiUrl(target));
assertTrue(target.isIdenticalCaptivePortalApiUrl(source));
assertTrue(source.isIdenticalCaptivePortalData(target));
assertTrue(target.isIdenticalCaptivePortalData(source));
// Check result of equals(). // Check result of equals().
assertTrue(source.equals(target)); assertTrue(source.equals(target));
assertTrue(target.equals(source)); assertTrue(target.equals(source));
@@ -963,6 +976,8 @@ public class LinkPropertiesTest {
source.setNat64Prefix(new IpPrefix("2001:db8:1:2:64:64::/96")); source.setNat64Prefix(new IpPrefix("2001:db8:1:2:64:64::/96"));
source.setWakeOnLanSupported(true); source.setWakeOnLanSupported(true);
source.setCaptivePortalApiUrl(CAPPORT_API_URL);
source.setCaptivePortalData(CAPPORT_DATA);
source.setDhcpServerAddress((Inet4Address) GATEWAY1); source.setDhcpServerAddress((Inet4Address) GATEWAY1);
@@ -970,7 +985,13 @@ public class LinkPropertiesTest {
stacked.setInterfaceName("test-stacked"); stacked.setInterfaceName("test-stacked");
source.addStackedLink(stacked); source.addStackedLink(stacked);
assertParcelSane(source, 16 /* fieldCount */); assertParcelSane(source.makeSensitiveFieldsParcelingCopy(), 18 /* fieldCount */);
// Verify that without using a sensitiveFieldsParcelingCopy, sensitive fields are cleared.
final LinkProperties sanitized = new LinkProperties(source);
sanitized.setCaptivePortalApiUrl(null);
sanitized.setCaptivePortalData(null);
assertEquals(sanitized, parcelingRoundTrip(source));
} }
@Test @Test
@@ -1113,4 +1134,22 @@ public class LinkPropertiesTest {
lp.clear(); lp.clear();
assertFalse(lp.isWakeOnLanSupported()); assertFalse(lp.isWakeOnLanSupported());
} }
@Test
public void testCaptivePortalApiUrl() {
final LinkProperties lp = makeTestObject();
assertEquals(CAPPORT_API_URL, lp.getCaptivePortalApiUrl());
lp.clear();
assertNull(lp.getCaptivePortalApiUrl());
}
@Test
public void testCaptivePortalData() {
final LinkProperties lp = makeTestObject();
assertEquals(CAPPORT_DATA, lp.getCaptivePortalData());
lp.clear();
assertNull(lp.getCaptivePortalData());
}
} }

View File

@@ -0,0 +1,72 @@
/*
* Copyright (C) 2019 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.net
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.testutils.assertParcelSane
import com.android.testutils.assertParcelingIsLossless
import org.junit.Test
import org.junit.runner.RunWith
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
@SmallTest
@RunWith(AndroidJUnit4::class)
class CaptivePortalDataTest {
private val data = CaptivePortalData.Builder()
.setRefreshTime(123L)
.setUserPortalUrl(Uri.parse("https://portal.example.com/test"))
.setVenueInfoUrl(Uri.parse("https://venue.example.com/test"))
.setSessionExtendable(true)
.setBytesRemaining(456L)
.setExpiryTime(789L)
.setCaptive(true)
.build()
private fun makeBuilder() = CaptivePortalData.Builder(data)
@Test
fun testParcelUnparcel() {
assertParcelSane(data, fieldCount = 7)
assertParcelingIsLossless(makeBuilder().setUserPortalUrl(null).build())
assertParcelingIsLossless(makeBuilder().setVenueInfoUrl(null).build())
}
@Test
fun testEquals() {
assertEquals(data, makeBuilder().build())
assertNotEqualsAfterChange { it.setRefreshTime(456L) }
assertNotEqualsAfterChange { it.setUserPortalUrl(Uri.parse("https://example.com/")) }
assertNotEqualsAfterChange { it.setUserPortalUrl(null) }
assertNotEqualsAfterChange { it.setVenueInfoUrl(Uri.parse("https://example.com/")) }
assertNotEqualsAfterChange { it.setVenueInfoUrl(null) }
assertNotEqualsAfterChange { it.setSessionExtendable(false) }
assertNotEqualsAfterChange { it.setBytesRemaining(789L) }
assertNotEqualsAfterChange { it.setExpiryTime(12L) }
assertNotEqualsAfterChange { it.setCaptive(false) }
}
private fun CaptivePortalData.mutate(mutator: (CaptivePortalData.Builder) -> Unit) =
CaptivePortalData.Builder(this).apply { mutator(this) }.build()
private fun assertNotEqualsAfterChange(mutator: (CaptivePortalData.Builder) -> Unit) {
assertNotEquals(data, data.mutate(mutator))
}
}

View File

@@ -21,6 +21,8 @@ import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS;
import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED; import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
import static android.content.pm.PackageManager.GET_PERMISSIONS; import static android.content.pm.PackageManager.GET_PERMISSIONS;
import static android.content.pm.PackageManager.MATCH_ANY_USER; import static android.content.pm.PackageManager.MATCH_ANY_USER;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.net.ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN; import static android.net.ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_SUPL; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_SUPL;
@@ -114,6 +116,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import android.Manifest;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.app.AlarmManager; import android.app.AlarmManager;
import android.app.NotificationManager; import android.app.NotificationManager;
@@ -129,6 +132,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.UserInfo; import android.content.pm.UserInfo;
import android.content.res.Resources; import android.content.res.Resources;
import android.net.CaptivePortalData;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback; import android.net.ConnectivityManager.NetworkCallback;
import android.net.ConnectivityManager.PacketKeepalive; import android.net.ConnectivityManager.PacketKeepalive;
@@ -165,6 +169,7 @@ import android.net.ResolverParamsParcel;
import android.net.RouteInfo; import android.net.RouteInfo;
import android.net.SocketKeepalive; import android.net.SocketKeepalive;
import android.net.UidRange; import android.net.UidRange;
import android.net.Uri;
import android.net.metrics.IpConnectivityLog; import android.net.metrics.IpConnectivityLog;
import android.net.shared.NetworkMonitorUtils; import android.net.shared.NetworkMonitorUtils;
import android.net.shared.PrivateDnsConfig; import android.net.shared.PrivateDnsConfig;
@@ -243,8 +248,10 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@@ -347,6 +354,8 @@ public class ConnectivityServiceTest {
@Spy private Resources mResources; @Spy private Resources mResources;
private final LinkedBlockingQueue<Intent> mStartedActivities = new LinkedBlockingQueue<>(); private final LinkedBlockingQueue<Intent> mStartedActivities = new LinkedBlockingQueue<>();
// Map of permission name -> PermissionManager.Permission_{GRANTED|DENIED} constant
private final HashMap<String, Integer> mMockedPermissions = new HashMap<>();
MockContext(Context base, ContentProvider settingsProvider) { MockContext(Context base, ContentProvider settingsProvider) {
super(base); super(base);
@@ -416,14 +425,40 @@ public class ConnectivityServiceTest {
return mPackageManager; return mPackageManager;
} }
@Override
public int checkPermission(String permission, int pid, int uid) {
final Integer granted = mMockedPermissions.get(permission);
if (granted == null) {
// All non-mocked permissions should be held by the test or unnecessary: check as
// normal to make sure the code does not rely on unexpected permissions.
return super.checkPermission(permission, pid, uid);
}
return granted;
}
@Override @Override
public void enforceCallingOrSelfPermission(String permission, String message) { public void enforceCallingOrSelfPermission(String permission, String message) {
// The mainline permission can only be held if signed with the network stack certificate final Integer granted = mMockedPermissions.get(permission);
// Skip testing for this permission. if (granted == null) {
if (NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK.equals(permission)) return; super.enforceCallingOrSelfPermission(permission, message);
// All other permissions should be held by the test or unnecessary: check as normal to return;
// make sure the code does not rely on unexpected permissions. }
super.enforceCallingOrSelfPermission(permission, message);
if (!granted.equals(PERMISSION_GRANTED)) {
throw new SecurityException("[Test] permission denied: " + permission);
}
}
/**
* Mock checks for the specified permission, and have them behave as per {@code granted}.
*
* <p>Passing null reverts to default behavior, which does a real permission check on the
* test package.
* @param granted One of {@link PackageManager#PERMISSION_GRANTED} or
* {@link PackageManager#PERMISSION_DENIED}.
*/
public void setPermission(String permission, Integer granted) {
mMockedPermissions.put(permission, granted);
} }
@Override @Override
@@ -1750,6 +1785,66 @@ public class ConnectivityServiceTest {
assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
} }
private void doNetworkCallbacksSanitizationTest(boolean sanitized) throws Exception {
final TestNetworkCallback callback = new TestNetworkCallback();
final TestNetworkCallback defaultCallback = new TestNetworkCallback();
final NetworkRequest wifiRequest = new NetworkRequest.Builder()
.addTransportType(TRANSPORT_WIFI).build();
mCm.registerNetworkCallback(wifiRequest, callback);
mCm.registerDefaultNetworkCallback(defaultCallback);
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(false);
callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
final LinkProperties newLp = new LinkProperties();
final Uri capportUrl = Uri.parse("https://capport.example.com/api");
final CaptivePortalData capportData = new CaptivePortalData.Builder()
.setCaptive(true).build();
newLp.setCaptivePortalApiUrl(capportUrl);
newLp.setCaptivePortalData(capportData);
mWiFiNetworkAgent.sendLinkProperties(newLp);
final Uri expectedCapportUrl = sanitized ? null : capportUrl;
final CaptivePortalData expectedCapportData = sanitized ? null : capportData;
callback.expectLinkPropertiesThat(mWiFiNetworkAgent, lp ->
Objects.equals(expectedCapportUrl, lp.getCaptivePortalApiUrl())
&& Objects.equals(expectedCapportData, lp.getCaptivePortalData()));
defaultCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, lp ->
Objects.equals(expectedCapportUrl, lp.getCaptivePortalApiUrl())
&& Objects.equals(expectedCapportData, lp.getCaptivePortalData()));
final LinkProperties lp = mCm.getLinkProperties(mWiFiNetworkAgent.getNetwork());
assertEquals(expectedCapportUrl, lp.getCaptivePortalApiUrl());
assertEquals(expectedCapportData, lp.getCaptivePortalData());
}
@Test
public void networkCallbacksSanitizationTest_Sanitize() throws Exception {
mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
PERMISSION_DENIED);
mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS,
PERMISSION_DENIED);
doNetworkCallbacksSanitizationTest(true /* sanitized */);
}
@Test
public void networkCallbacksSanitizationTest_NoSanitize_NetworkStack() throws Exception {
mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
PERMISSION_GRANTED);
mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS, PERMISSION_DENIED);
doNetworkCallbacksSanitizationTest(false /* sanitized */);
}
@Test
public void networkCallbacksSanitizationTest_NoSanitize_Settings() throws Exception {
mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
PERMISSION_DENIED);
mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED);
doNetworkCallbacksSanitizationTest(false /* sanitized */);
}
@Test @Test
public void testMultipleLingering() throws Exception { public void testMultipleLingering() throws Exception {
// This test would be flaky with the default 120ms timer: that is short enough that // This test would be flaky with the default 120ms timer: that is short enough that
@@ -2628,6 +2723,8 @@ public class ConnectivityServiceTest {
final String testKey = "testkey"; final String testKey = "testkey";
final String testValue = "testvalue"; final String testValue = "testvalue";
testBundle.putString(testKey, testValue); testBundle.putString(testKey, testValue);
mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
PERMISSION_GRANTED);
mCm.startCaptivePortalApp(wifiNetwork, testBundle); mCm.startCaptivePortalApp(wifiNetwork, testBundle);
final Intent signInIntent = mServiceContext.expectStartActivityIntent(TIMEOUT_MS); final Intent signInIntent = mServiceContext.expectStartActivityIntent(TIMEOUT_MS);
assertEquals(ACTION_CAPTIVE_PORTAL_SIGN_IN, signInIntent.getAction()); assertEquals(ACTION_CAPTIVE_PORTAL_SIGN_IN, signInIntent.getAction());