Support nested domain-config elements
Nested domain-config inherit unset parameters from the domain-config they are nested in. This helps avoid copy and pasted configs that are almost the same except a few minor differences for a domain with slightly different requirements. For example: Consider a domain-config for example.com that, among other settings, does not enforce hsts. Now if you want the rules for example.com to apply to secure.example.com except that hsts _is_ enforced you can make a nested domain-config for secure.example.com under example.com that sets hstsEnforced="true" and nothing else. Change-Id: I9e33f7e62127fd7f4f15c3560fff2f2626477bd4
This commit is contained in:
@@ -186,15 +186,21 @@ public class XmlConfigSource implements ConfigSource {
|
||||
return anchors;
|
||||
}
|
||||
|
||||
private Pair<NetworkSecurityConfig.Builder, Set<Domain>> parseConfigEntry(
|
||||
XmlResourceParser parser, Set<String> seenDomains, boolean baseConfig)
|
||||
private List<Pair<NetworkSecurityConfig.Builder, Set<Domain>>> parseConfigEntry(
|
||||
XmlResourceParser parser, Set<String> seenDomains,
|
||||
NetworkSecurityConfig.Builder parentBuilder, boolean baseConfig)
|
||||
throws IOException, XmlPullParserException, ParserException {
|
||||
List<Pair<NetworkSecurityConfig.Builder, Set<Domain>>> builders = new ArrayList<>();
|
||||
NetworkSecurityConfig.Builder builder = new NetworkSecurityConfig.Builder();
|
||||
builder.setParent(parentBuilder);
|
||||
Set<Domain> domains = new ArraySet<>();
|
||||
boolean seenPinSet = false;
|
||||
boolean seenTrustAnchors = false;
|
||||
String configName = parser.getName();
|
||||
int outerDepth = parser.getDepth();
|
||||
// Add this builder now so that this builder occurs before any of its children. This
|
||||
// makes the final build pass easier.
|
||||
builders.add(new Pair<>(builder, domains));
|
||||
// Parse config attributes. Only set values that are present, config inheritence will
|
||||
// handle the rest.
|
||||
for (int i = 0; i < parser.getAttributeCount(); i++) {
|
||||
@@ -212,7 +218,6 @@ public class XmlConfigSource implements ConfigSource {
|
||||
// Parse the config elements.
|
||||
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
|
||||
String tagName = parser.getName();
|
||||
// TODO: Support nested domain-config entries.
|
||||
if ("domain".equals(tagName)) {
|
||||
if (baseConfig) {
|
||||
throw new ParserException(parser, "domain element not allowed in base-config");
|
||||
@@ -236,6 +241,12 @@ public class XmlConfigSource implements ConfigSource {
|
||||
}
|
||||
builder.setPinSet(parsePinSet(parser));
|
||||
seenPinSet = true;
|
||||
} else if ("domain-config".equals(tagName)) {
|
||||
if (baseConfig) {
|
||||
throw new ParserException(parser,
|
||||
"Nested domain-config not allowed in base-config");
|
||||
}
|
||||
builders.addAll(parseConfigEntry(parser, seenDomains, builder, false));
|
||||
} else {
|
||||
XmlUtils.skipCurrentTag(parser);
|
||||
}
|
||||
@@ -243,7 +254,7 @@ public class XmlConfigSource implements ConfigSource {
|
||||
if (!baseConfig && domains.isEmpty()) {
|
||||
throw new ParserException(parser, "No domain elements in domain-config");
|
||||
}
|
||||
return new Pair<>(builder, domains);
|
||||
return builders;
|
||||
}
|
||||
|
||||
private void parseNetworkSecurityConfig(XmlResourceParser parser)
|
||||
@@ -263,9 +274,9 @@ public class XmlConfigSource implements ConfigSource {
|
||||
throw new ParserException(parser, "Only one base-config allowed");
|
||||
}
|
||||
seenBaseConfig = true;
|
||||
baseConfigBuilder = parseConfigEntry(parser, seenDomains, true).first;
|
||||
baseConfigBuilder = parseConfigEntry(parser, seenDomains, null, true).get(0).first;
|
||||
} else if ("domain-config".equals(parser.getName())) {
|
||||
builders.add(parseConfigEntry(parser, seenDomains, false));
|
||||
builders.addAll(parseConfigEntry(parser, seenDomains, baseConfigBuilder, false));
|
||||
} else {
|
||||
XmlUtils.skipCurrentTag(parser);
|
||||
}
|
||||
@@ -286,8 +297,15 @@ public class XmlConfigSource implements ConfigSource {
|
||||
for (Pair<NetworkSecurityConfig.Builder, Set<Domain>> entry : builders) {
|
||||
NetworkSecurityConfig.Builder builder = entry.first;
|
||||
Set<Domain> domains = entry.second;
|
||||
// Use the base-config for inheriting any unset values in the domain-config entry.
|
||||
builder.setParent(baseConfigBuilder);
|
||||
// Set the parent of configs that do not have a parent to the base-config. This can
|
||||
// happen if the base-config comes after a domain-config in the file.
|
||||
// Note that this is safe with regards to children because of the order that
|
||||
// parseConfigEntry returns builders, the parent is always before the children. The
|
||||
// children builders will not have build called until _after_ their parents have their
|
||||
// parent set so everything is consistent.
|
||||
if (builder.getParent() == null) {
|
||||
builder.setParent(baseConfigBuilder);
|
||||
}
|
||||
NetworkSecurityConfig config = builder.build();
|
||||
for (Domain domain : domains) {
|
||||
configs.add(new Pair<>(domain, config));
|
||||
|
||||
18
tests/NetworkSecurityConfigTest/res/xml/nested_domains.xml
Normal file
18
tests/NetworkSecurityConfigTest/res/xml/nested_domains.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
<domain-config>
|
||||
<domain includeSubdomains="true">android.com</domain>
|
||||
<trust-anchors>
|
||||
<certificates src="system" />
|
||||
</trust-anchors>
|
||||
<!-- nested config that adds pins -->
|
||||
<domain-config>
|
||||
<domain>developer.android.com</domain>
|
||||
<pin-set>
|
||||
<pin digest="SHA-256">7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
|
||||
</pin-set>
|
||||
</domain-config>
|
||||
</domain-config>
|
||||
<base-config cleartextTrafficPermitted="false">
|
||||
</base-config>
|
||||
</network-security-config>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
<base-config cleartextTrafficPermitted="false">
|
||||
</base-config>
|
||||
<!-- Nested config that overrides parent -->
|
||||
<domain-config cleartextTrafficPermitted="true">
|
||||
<domain includeSubdomains="true">android.com</domain>
|
||||
<domain-config cleartextTrafficPermitted="false">
|
||||
<domain>developer.android.com</domain>
|
||||
</domain-config>
|
||||
</domain-config>
|
||||
</network-security-config>
|
||||
@@ -245,6 +245,35 @@ public class XmlConfigTests extends AndroidTestCase {
|
||||
TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443);
|
||||
}
|
||||
|
||||
public void testNestedDomainConfigs() throws Exception {
|
||||
XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.nested_domains);
|
||||
ApplicationConfig appConfig = new ApplicationConfig(source);
|
||||
assertTrue(appConfig.hasPerDomainConfigs());
|
||||
NetworkSecurityConfig parent = appConfig.getConfigForHostname("android.com");
|
||||
NetworkSecurityConfig child = appConfig.getConfigForHostname("developer.android.com");
|
||||
MoreAsserts.assertNotEqual(parent, child);
|
||||
MoreAsserts.assertEmpty(parent.getPins().pins);
|
||||
MoreAsserts.assertNotEmpty(child.getPins().pins);
|
||||
// Check that the child inherited the cleartext value and anchors.
|
||||
assertFalse(child.isCleartextTrafficPermitted());
|
||||
MoreAsserts.assertNotEmpty(child.getTrustAnchors());
|
||||
// Test connections.
|
||||
SSLContext context = TestUtils.getSSLContext(source);
|
||||
TestUtils.assertConnectionSucceeds(context, "android.com", 443);
|
||||
TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443);
|
||||
}
|
||||
|
||||
public void testNestedDomainConfigsOverride() throws Exception {
|
||||
XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.nested_domains_override);
|
||||
ApplicationConfig appConfig = new ApplicationConfig(source);
|
||||
assertTrue(appConfig.hasPerDomainConfigs());
|
||||
NetworkSecurityConfig parent = appConfig.getConfigForHostname("android.com");
|
||||
NetworkSecurityConfig child = appConfig.getConfigForHostname("developer.android.com");
|
||||
MoreAsserts.assertNotEqual(parent, child);
|
||||
assertTrue(parent.isCleartextTrafficPermitted());
|
||||
assertFalse(child.isCleartextTrafficPermitted());
|
||||
}
|
||||
|
||||
private void testBadConfig(int configId) throws Exception {
|
||||
try {
|
||||
XmlConfigSource source = new XmlConfigSource(getContext(), configId);
|
||||
|
||||
Reference in New Issue
Block a user