summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/StatementService/Android.bp1
-rw-r--r--packages/StatementService/src/com/android/statementservice/network/retriever/StatementParser.kt36
-rw-r--r--packages/StatementService/src/com/android/statementservice/retriever/DynamicAppLinkComponent.java145
-rw-r--r--packages/StatementService/src/com/android/statementservice/retriever/JsonParser.java6
-rw-r--r--packages/StatementService/src/com/android/statementservice/retriever/Statement.java51
-rw-r--r--packages/StatementService/src/com/android/statementservice/utils/StatementUtils.kt30
6 files changed, 257 insertions, 12 deletions
diff --git a/packages/StatementService/Android.bp b/packages/StatementService/Android.bp
index ff1a756479b6..90e1808b7d44 100644
--- a/packages/StatementService/Android.bp
+++ b/packages/StatementService/Android.bp
@@ -35,6 +35,7 @@ android_app {
privileged: true,
certificate: "platform",
static_libs: [
+ "StatementServiceParser",
"androidx.appcompat_appcompat",
"androidx.collection_collection-ktx",
"androidx.work_work-runtime",
diff --git a/packages/StatementService/src/com/android/statementservice/network/retriever/StatementParser.kt b/packages/StatementService/src/com/android/statementservice/network/retriever/StatementParser.kt
index 455e8085af50..ad137400fa86 100644
--- a/packages/StatementService/src/com/android/statementservice/network/retriever/StatementParser.kt
+++ b/packages/StatementService/src/com/android/statementservice/network/retriever/StatementParser.kt
@@ -28,6 +28,8 @@ import java.io.StringReader
import java.util.ArrayList
import com.android.statementservice.retriever.WebAsset
import com.android.statementservice.retriever.AndroidAppAsset
+import com.android.statementservice.retriever.DynamicAppLinkComponent
+import org.json.JSONObject
/**
* Parses JSON from the Digital Asset Links specification. For examples, see [WebAsset],
@@ -97,13 +99,45 @@ object StatementParser {
FIELD_NOT_ARRAY_FORMAT_STRING.format(StatementUtils.ASSET_DESCRIPTOR_FIELD_RELATION)
)
val target = AssetFactory.create(targetObject)
+ val dynamicAppLinkComponents = parseDynamicAppLinkComponents(
+ statement.optJSONObject(StatementUtils.ASSET_DESCRIPTOR_FIELD_RELATION_EXTENSIONS)
+ )
val statements = (0 until relations.length())
.map { relations.getString(it) }
.map(Relation::create)
- .map { Statement.create(source, target, it) }
+ .map { Statement.create(source, target, it, dynamicAppLinkComponents) }
return Result.Success(ParsedStatement(statements, listOfNotNull(delegate)))
}
+ private fun parseDynamicAppLinkComponents(
+ statement: JSONObject?
+ ): List<DynamicAppLinkComponent> {
+ val relationExtensions = statement?.optJSONObject(
+ StatementUtils.ASSET_DESCRIPTOR_FIELD_RELATION_EXTENSIONS
+ ) ?: return emptyList()
+ val handleAllUrlsRelationExtension = relationExtensions.optJSONObject(
+ StatementUtils.RELATION.toString()
+ ) ?: return emptyList()
+ val components = handleAllUrlsRelationExtension.optJSONArray(
+ StatementUtils.RELATION_EXTENSION_FIELD_DAL_COMPONENTS
+ ) ?: return emptyList()
+
+ return (0 until components.length())
+ .map { components.getJSONObject(it) }
+ .map { parseComponent(it) }
+ }
+
+ private fun parseComponent(component: JSONObject): DynamicAppLinkComponent {
+ val query = component.optJSONObject("?")
+ return DynamicAppLinkComponent.create(
+ component.optBoolean("exclude", false),
+ component.optString("#"),
+ component.optString("/"),
+ query?.keys()?.asSequence()?.associateWith { query.getString(it) },
+ component.optString("comments")
+ )
+ }
+
data class ParsedStatement(val statements: List<Statement>, val delegates: List<String>)
}
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/DynamicAppLinkComponent.java b/packages/StatementService/src/com/android/statementservice/retriever/DynamicAppLinkComponent.java
new file mode 100644
index 000000000000..dc27e125e204
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/retriever/DynamicAppLinkComponent.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2024 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 com.android.statementservice.retriever;
+
+import android.annotation.Nullable;
+
+import java.util.Map;
+
+/**
+ * A immutable value type representing a dynamic app link component
+ */
+public final class DynamicAppLinkComponent {
+ private final boolean mExclude;
+ private final String mFragment;
+ private final String mPath;
+ private final Map<String, String> mQuery;
+ private final String mComments;
+
+ private DynamicAppLinkComponent(boolean exclude, String fragment, String path,
+ Map<String, String> query, String comments) {
+ mExclude = exclude;
+ mFragment = fragment;
+ mPath = path;
+ mQuery = query;
+ mComments = comments;
+ }
+
+ /**
+ * Returns true or false indicating whether this rule should be a exclusion rule.
+ */
+ public boolean getExclude() {
+ return mExclude;
+ }
+
+ /**
+ * Returns a optional pattern string for matching URL fragments.
+ */
+ @Nullable
+ public String getFragment() {
+ return mFragment;
+ }
+
+ /**
+ * Returns a optional pattern string for matching URL paths.
+ */
+ @Nullable
+ public String getPath() {
+ return mPath;
+ }
+
+ /**
+ * Returns a optional pattern string for matching a single key-value pair in the URL query
+ * params.
+ */
+ @Nullable
+ public Map<String, String> getQuery() {
+ return mQuery;
+ }
+
+ /**
+ * Returns a optional comment string for this component.
+ */
+ @Nullable
+ public String getComments() {
+ return mComments;
+ }
+
+ /**
+ * Creates a new DynamicAppLinkComponent object.
+ */
+ public static DynamicAppLinkComponent create(boolean exclude, String fragment, String path,
+ Map<String, String> query, String comments) {
+ return new DynamicAppLinkComponent(exclude, fragment, path, query, comments);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ DynamicAppLinkComponent rule = (DynamicAppLinkComponent) o;
+
+ if (mExclude != rule.mExclude) {
+ return false;
+ }
+ if (!mFragment.equals(rule.mFragment)) {
+ return false;
+ }
+ if (!mPath.equals(rule.mPath)) {
+ return false;
+ }
+ if (!mQuery.equals(rule.mQuery)) {
+ return false;
+ }
+ if (!mComments.equals(rule.mComments)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Boolean.hashCode(mExclude);
+ result = 31 * result + mFragment.hashCode();
+ result = 31 * result + mPath.hashCode();
+ result = 31 * result + mQuery.hashCode();
+ result = 31 * result + mComments.hashCode();
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder statement = new StringBuilder();
+ statement.append("HandleAllUriRule: ");
+ statement.append(mExclude);
+ statement.append(", ");
+ statement.append(mFragment);
+ statement.append(", ");
+ statement.append(mPath);
+ statement.append(", ");
+ statement.append(mQuery);
+ statement.append(", ");
+ statement.append(mComments);
+ return statement.toString();
+ }
+}
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/JsonParser.java b/packages/StatementService/src/com/android/statementservice/retriever/JsonParser.java
index ce063ea5c143..7635e8234dc0 100644
--- a/packages/StatementService/src/com/android/statementservice/retriever/JsonParser.java
+++ b/packages/StatementService/src/com/android/statementservice/retriever/JsonParser.java
@@ -46,12 +46,6 @@ public final class JsonParser {
while (reader.hasNext()) {
String fieldName = reader.nextName();
- if (output.has(fieldName)) {
- errorMsg = "Duplicate field name.";
- reader.skipValue();
- continue;
- }
-
JsonToken token = reader.peek();
if (token.equals(JsonToken.BEGIN_ARRAY)) {
output.put(fieldName, new JSONArray(parseArray(reader)));
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/Statement.java b/packages/StatementService/src/com/android/statementservice/retriever/Statement.java
index f8bab3ef170f..b5e204651307 100644
--- a/packages/StatementService/src/com/android/statementservice/retriever/Statement.java
+++ b/packages/StatementService/src/com/android/statementservice/retriever/Statement.java
@@ -23,6 +23,10 @@ import com.android.statementservice.network.retriever.StatementRetriever;
import kotlin.coroutines.Continuation;
+import java.util.Collections;
+import java.util.List;
+
+
/**
* An immutable value type representing a statement, consisting of a source, target, and relation.
* This reflects an assertion that the relation holds for the source, target pair. For example, if a
@@ -32,7 +36,21 @@ import kotlin.coroutines.Continuation;
* {
* "relation": ["delegate_permission/common.handle_all_urls"],
* "target" : {"namespace": "android_app", "package_name": "com.example.app",
- * "sha256_cert_fingerprints": ["00:11:22:33"] }
+ * "sha256_cert_fingerprints": ["00:11:22:33"] },
+ * "relation_extensions": {
+ * "delegate_permission/common_handle_all_urls": {
+ * "dynamic_app_link_components": [
+ * {
+ * "/": "/foo*",
+ * "exclude": true,
+ * "comments": "App should not handle paths that start with foo"
+ * },
+ * {
+ * "/": "*",
+ * "comments": "Catch all other paths"
+ * }
+ * ]
+ * }
* }
* </pre>
*
@@ -40,7 +58,7 @@ import kotlin.coroutines.Continuation;
* return a {@link Statement} with {@link #getSource} equal to the input parameter,
* {@link #getRelation} equal to
*
- * <pre>Relation.create("delegate_permission", "common.get_login_creds");</pre>
+ * <pre>Relation.create("delegate_permission", "common.handle_all_urls");</pre>
*
* and with {@link #getTarget} equal to
*
@@ -48,17 +66,23 @@ import kotlin.coroutines.Continuation;
* + "\"package_name\": \"com.example.app\"}"
* + "\"sha256_cert_fingerprints\": \"[\"00:11:22:33\"]\"}");
* </pre>
+ *
+ * If extensions exist for the handle_all_urls relation then {@link #getDynamicAppLinkComponents}
+ * will return a list of parsed {@link DynamicAppLinkComponent}s.
*/
public final class Statement {
private final AbstractAsset mTarget;
private final Relation mRelation;
private final AbstractAsset mSource;
+ private final List<DynamicAppLinkComponent> mDynamicAppLinkComponents;
- private Statement(AbstractAsset source, AbstractAsset target, Relation relation) {
+ private Statement(AbstractAsset source, AbstractAsset target, Relation relation,
+ List<DynamicAppLinkComponent> components) {
mSource = source;
mTarget = target;
mRelation = relation;
+ mDynamicAppLinkComponents = Collections.unmodifiableList(components);
}
/**
@@ -86,6 +110,14 @@ public final class Statement {
}
/**
+ * Returns the relation matching rules of the statement.
+ */
+ @NonNull
+ public List<DynamicAppLinkComponent> getDynamicAppLinkComponents() {
+ return mDynamicAppLinkComponents;
+ }
+
+ /**
* Creates a new Statement object for the specified target asset and relation. For example:
* <pre>
* Asset asset = Asset.Factory.create(
@@ -95,8 +127,9 @@ public final class Statement {
* </pre>
*/
public static Statement create(@NonNull AbstractAsset source, @NonNull AbstractAsset target,
- @NonNull Relation relation) {
- return new Statement(source, target, relation);
+ @NonNull Relation relation,
+ @NonNull List<DynamicAppLinkComponent> components) {
+ return new Statement(source, target, relation, components);
}
@Override
@@ -119,6 +152,9 @@ public final class Statement {
if (!mSource.equals(statement.mSource)) {
return false;
}
+ if (!mDynamicAppLinkComponents.equals(statement.mDynamicAppLinkComponents)) {
+ return false;
+ }
return true;
}
@@ -128,6 +164,7 @@ public final class Statement {
int result = mTarget.hashCode();
result = 31 * result + mRelation.hashCode();
result = 31 * result + mSource.hashCode();
+ result = 31 * result + mDynamicAppLinkComponents.hashCode();
return result;
}
@@ -140,6 +177,10 @@ public final class Statement {
statement.append(mTarget);
statement.append(", ");
statement.append(mRelation);
+ if (!mDynamicAppLinkComponents.isEmpty()) {
+ statement.append(", ");
+ statement.append(mDynamicAppLinkComponents);
+ }
return statement.toString();
}
}
diff --git a/packages/StatementService/src/com/android/statementservice/utils/StatementUtils.kt b/packages/StatementService/src/com/android/statementservice/utils/StatementUtils.kt
index 4837aad3a025..47c69b4b3a44 100644
--- a/packages/StatementService/src/com/android/statementservice/utils/StatementUtils.kt
+++ b/packages/StatementService/src/com/android/statementservice/utils/StatementUtils.kt
@@ -17,8 +17,17 @@
package com.android.statementservice.utils
import android.content.Context
+import android.content.UriRelativeFilter
+import android.content.UriRelativeFilter.FRAGMENT
+import android.content.UriRelativeFilter.PATH
+import android.content.UriRelativeFilter.QUERY
+import android.content.UriRelativeFilterGroup
+import android.content.UriRelativeFilterGroup.ACTION_ALLOW
+import android.content.UriRelativeFilterGroup.ACTION_BLOCK
import android.content.pm.PackageManager
import android.util.Patterns
+import com.android.statementservice.parser.parseMatchingExpression
+import com.android.statementservice.retriever.DynamicAppLinkComponent
import com.android.statementservice.retriever.Relation
import java.net.URL
import java.security.MessageDigest
@@ -52,7 +61,9 @@ internal object StatementUtils {
*/
const val ASSET_DESCRIPTOR_FIELD_RELATION = "relation"
const val ASSET_DESCRIPTOR_FIELD_TARGET = "target"
+ const val ASSET_DESCRIPTOR_FIELD_RELATION_EXTENSIONS = "relation_extensions"
const val DELEGATE_FIELD_DELEGATE = "include"
+ const val RELATION_EXTENSION_FIELD_DAL_COMPONENTS = "dynamic_app_link_components"
val HEX_DIGITS =
charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F')
@@ -160,4 +171,23 @@ internal object StatementUtils {
// Hosts with *. for wildcard subdomain support are verified against their root domain
fun createWebAssetString(host: String) =
WEB_ASSET_FORMAT.format(URL("https", host.removePrefix("*."), "").toString())
+
+ fun createUriRelativeFilterGroup(component: DynamicAppLinkComponent): UriRelativeFilterGroup {
+ val group = UriRelativeFilterGroup(if (component.exclude) ACTION_BLOCK else ACTION_ALLOW)
+ component.fragment?.let {
+ val (type, filter) = parseMatchingExpression(it)
+ group.addUriRelativeFilter(UriRelativeFilter(FRAGMENT, type, filter))
+ }
+ component.path?.let {
+ val (type, filter) = parseMatchingExpression(it)
+ group.addUriRelativeFilter(UriRelativeFilter(PATH, type, filter))
+ }
+ component.query?.let {
+ for ((k, v) in it) {
+ val (type, filter) = parseMatchingExpression(k + "=" + v)
+ group.addUriRelativeFilter(UriRelativeFilter(QUERY, type, filter))
+ }
+ }
+ return group
+ }
}