Add ValidatedAttributionSourceState

Add type which encapsulates a validated attribution source, either via
binder context, or if it is passed from a trusted source.

This type will be used by the controller for permission validation.

Test: atest audiopermissioncontroller_test --host
Fixes: 259493676
Bug: 338089555
Flag: EXEMPT safe, adding utilities
Change-Id: I80af36b4b766b7c876f3d7cdb50257dc4d1c7dcd
diff --git a/services/audiopolicy/permission/Android.bp b/services/audiopolicy/permission/Android.bp
index 1aa29e0..ce7b43c 100644
--- a/services/audiopolicy/permission/Android.bp
+++ b/services/audiopolicy/permission/Android.bp
@@ -8,6 +8,7 @@
 
     srcs: [
         "NativePermissionController.cpp",
+        "ValidatedAttributionSourceState.cpp",
     ],
     export_include_dirs: [
         "include",
@@ -22,6 +23,7 @@
     ],
     static_libs: [
         "audio-permission-aidl-cpp",
+        "framework-permission-aidl-cpp",
     ],
     shared_libs: [
         "libbase",
@@ -94,6 +96,7 @@
     ],
     srcs: [
         "tests/NativePermissionControllerTest.cpp",
+        "tests/ValidatedAttributionSourceStateTest.cpp",
     ],
     test_options: {
         unit_test: true,
diff --git a/services/audiopolicy/permission/ValidatedAttributionSourceState.cpp b/services/audiopolicy/permission/ValidatedAttributionSourceState.cpp
new file mode 100644
index 0000000..2c32289
--- /dev/null
+++ b/services/audiopolicy/permission/ValidatedAttributionSourceState.cpp
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+#include <media/ValidatedAttributionSourceState.h>
+
+#include <binder/IPCThreadState.h>
+#include <error/expected_utils.h>
+#include <utils/Log.h>
+
+namespace com::android::media::permission {
+
+using ::android::base::unexpected;
+
+Result<ValidatedAttributionSourceState> ValidatedAttributionSourceState::createFromBinderContext(
+        AttributionSourceState attr, const IPermissionProvider& provider) {
+    attr.pid = ::android::IPCThreadState::self()->getCallingPid();
+    attr.uid = ::android::IPCThreadState::self()->getCallingUid();
+    return createFromTrustedUidNoPackage(std::move(attr), provider);
+}
+
+Result<ValidatedAttributionSourceState>
+ValidatedAttributionSourceState::createFromTrustedUidNoPackage(
+        AttributionSourceState attr, const IPermissionProvider& provider) {
+    if (attr.packageName.has_value() && attr.packageName->size() != 0) {
+        if (VALUE_OR_RETURN(provider.validateUidPackagePair(attr.uid, attr.packageName.value()))) {
+            return ValidatedAttributionSourceState{std::move(attr)};
+        } else {
+            return unexpected{::android::PERMISSION_DENIED};
+        }
+    } else {
+        // For APIs which don't appropriately pass attribution sources or packages, we need
+        // to populate the package name with our best guess.
+        const auto packageNames = VALUE_OR_RETURN(provider.getPackagesForUid(attr.uid));
+        LOG_ALWAYS_FATAL_IF(packageNames.empty());
+        attr.packageName = std::move(packageNames[0]);
+        return ValidatedAttributionSourceState{std::move(attr)};
+    }
+}
+
+}  // namespace com::android::media::permission
diff --git a/services/audiopolicy/permission/include/media/ValidatedAttributionSourceState.h b/services/audiopolicy/permission/include/media/ValidatedAttributionSourceState.h
new file mode 100644
index 0000000..8d9da05
--- /dev/null
+++ b/services/audiopolicy/permission/include/media/ValidatedAttributionSourceState.h
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <android/content/AttributionSourceState.h>
+#include <error/Result.h>
+
+#include "IPermissionProvider.h"
+
+namespace com::android::media::permission {
+
+using ::android::content::AttributionSourceState;
+using ::android::error::Result;
+
+class ValidatedAttributionSourceState {
+  public:
+    /**
+     * Validates an attribution source from within the context of a binder transaction.
+     * Overwrites the uid/pid and validates the packageName
+     */
+    static Result<ValidatedAttributionSourceState> createFromBinderContext(
+            AttributionSourceState attr, const IPermissionProvider& provider);
+
+    /**
+     * Creates a ValidatedAttributionSourceState in cases where the source is passed from a
+     * trusted entity which already performed validation.
+     */
+    static ValidatedAttributionSourceState createFromTrustedSource(AttributionSourceState attr) {
+        return ValidatedAttributionSourceState(attr);
+    }
+
+    /**
+     * Create a ValidatedAttribubtionSourceState in cases where the uid/pid is trusted, but the
+     * packages have not been validated. Proper use of the previous two methods should avoid the
+     * necessity of this, but it is useful for migration purposes as well as testing this class.
+     */
+    static Result<ValidatedAttributionSourceState> createFromTrustedUidNoPackage(
+            AttributionSourceState attr, const IPermissionProvider& provider);
+
+    operator AttributionSourceState() const { return state_; }
+
+    operator const AttributionSourceState&() const { return state_; }
+
+    AttributionSourceState unwrapInto() && { return std::move(state_); }
+
+    bool operator==(const ValidatedAttributionSourceState& other) const {
+        return operator==(other.state_);
+    }
+
+    bool operator==(const AttributionSourceState& other) const { return state_ == other; }
+
+  private:
+    ValidatedAttributionSourceState(AttributionSourceState attr) : state_(attr) {}
+
+    AttributionSourceState state_;
+};
+}  // namespace com::android::media::permission
diff --git a/services/audiopolicy/permission/tests/ValidatedAttributionSourceStateTest.cpp b/services/audiopolicy/permission/tests/ValidatedAttributionSourceStateTest.cpp
new file mode 100644
index 0000000..f4d6556
--- /dev/null
+++ b/services/audiopolicy/permission/tests/ValidatedAttributionSourceStateTest.cpp
@@ -0,0 +1,125 @@
+/*
+ * 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.
+ */
+
+#include <media/ValidatedAttributionSourceState.h>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <android-base/expected.h>
+#include <media/IPermissionProvider.h>
+
+using ::android::base::unexpected;
+using ::android::binder::Status;
+using ::android::content::AttributionSourceState;
+using ::android::error::Result;
+using ::com::android::media::permission::IPermissionProvider;
+using ::com::android::media::permission::ValidatedAttributionSourceState;
+using ::testing::Return;
+
+class MockPermissionProvider : public IPermissionProvider {
+  public:
+    MOCK_METHOD(Result<std::vector<std::string>>, getPackagesForUid, (uid_t uid),
+                (override, const));
+    MOCK_METHOD(Result<bool>, validateUidPackagePair, (uid_t uid, const std::string&),
+                (override, const));
+};
+
+class ValidatedAttributionSourceStateTest : public ::testing::Test {
+  protected:
+    MockPermissionProvider mMockProvider;
+    const uid_t mUid = 10001;
+    const std::vector<std::string> mPackageList{"com.package1", "com.package2"};
+};
+
+#define UNWRAP_EQ(expr, desired_expr)                         \
+    do {                                                      \
+        auto tmp_ = (expr);                                   \
+        EXPECT_TRUE(tmp_.has_value());                        \
+        if (tmp_.has_value()) EXPECT_EQ(*tmp_, desired_expr); \
+    } while (0)
+
+TEST_F(ValidatedAttributionSourceStateTest, providedPackageValid) {
+    const std::string package = "com.package1";
+    EXPECT_CALL(mMockProvider, validateUidPackagePair(mUid, package)).WillOnce(Return(true));
+    AttributionSourceState attr;
+    attr.uid = mUid;
+    attr.packageName = package;
+    UNWRAP_EQ(ValidatedAttributionSourceState::createFromTrustedUidNoPackage(attr, mMockProvider),
+              attr);
+}
+
+TEST_F(ValidatedAttributionSourceStateTest, providedPackageInvalid) {
+    const std::string package = "com.package.spoof";
+    EXPECT_CALL(mMockProvider, validateUidPackagePair(mUid, package)).WillOnce(Return(false));
+    AttributionSourceState attr;
+    attr.uid = mUid;
+    attr.packageName = package;
+    const auto res =
+            ValidatedAttributionSourceState::createFromTrustedUidNoPackage(attr, mMockProvider);
+    ASSERT_FALSE(res.has_value());
+    EXPECT_EQ(res.error(), ::android::PERMISSION_DENIED);
+}
+
+TEST_F(ValidatedAttributionSourceStateTest, packageLookup_whenMissingPackage) {
+    EXPECT_CALL(mMockProvider, getPackagesForUid(mUid)).WillOnce(Return(mPackageList));
+    AttributionSourceState attr;
+    attr.uid = mUid;
+    AttributionSourceState expectedAttr;
+    expectedAttr.uid = mUid;
+    expectedAttr.packageName = "com.package1";
+    UNWRAP_EQ(ValidatedAttributionSourceState::createFromTrustedUidNoPackage(attr, mMockProvider),
+              expectedAttr);
+}
+
+TEST_F(ValidatedAttributionSourceStateTest, packageLookup_whenEmptyPackage) {
+    EXPECT_CALL(mMockProvider, getPackagesForUid(mUid)).WillOnce(Return(mPackageList));
+    AttributionSourceState attr;
+    attr.uid = mUid;
+    attr.packageName = std::string{};
+    AttributionSourceState expectedAttr;
+    expectedAttr.uid = mUid;
+    expectedAttr.packageName = "com.package1";
+    UNWRAP_EQ(ValidatedAttributionSourceState::createFromTrustedUidNoPackage(attr, mMockProvider),
+              expectedAttr);
+}
+
+TEST_F(ValidatedAttributionSourceStateTest, controllerNotInitialized) {
+    EXPECT_CALL(mMockProvider, getPackagesForUid(mUid))
+            .WillOnce(Return(unexpected{::android::NO_INIT}));
+    AttributionSourceState attr;
+    attr.uid = mUid;
+    attr.packageName = std::string{};
+    AttributionSourceState expectedAttr;
+    expectedAttr.uid = mUid;
+    expectedAttr.packageName = "com.package1";
+    const auto res =
+            ValidatedAttributionSourceState::createFromTrustedUidNoPackage(attr, mMockProvider);
+    ASSERT_FALSE(res.has_value());
+    EXPECT_EQ(res.error(), ::android::NO_INIT);
+}
+
+TEST_F(ValidatedAttributionSourceStateTest, uidNotFound) {
+    EXPECT_CALL(mMockProvider, getPackagesForUid(mUid))
+            .WillOnce(Return(unexpected{::android::BAD_VALUE}));
+    AttributionSourceState attr;
+    attr.uid = mUid;
+    attr.packageName = std::string{};
+    const auto res =
+            ValidatedAttributionSourceState::createFromTrustedUidNoPackage(attr, mMockProvider);
+    ASSERT_FALSE(res.has_value());
+    EXPECT_EQ(res.error(), ::android::BAD_VALUE);
+}