From c2fdd060d34ebc14349aff3b0bfa0083c1e24f7c Mon Sep 17 00:00:00 2001 From: Thomas Lea <thomas_lea@comcast.com> Date: Tue, 30 Jul 2024 10:32:57 -0500 Subject: [PATCH 01/22] Add AccessRestrictionList support --- examples/network-manager-app/linux/args.gni | 1 + .../network-manager-app.matter | 9 +- .../network-manager-app.zap | 66 ++- examples/platform/linux/AppMain.cpp | 18 + examples/platform/linux/BUILD.gn | 2 + .../platform/linux/ExampleAccessRestriction.h | 55 +++ examples/platform/linux/Options.cpp | 63 +++ examples/platform/linux/Options.h | 8 + scripts/tools/check_includes_config.py | 1 + src/access/AccessConfig.h | 22 + src/access/AccessControl.cpp | 19 +- src/access/AccessControl.h | 19 + src/access/AccessRestriction.cpp | 280 +++++++++++ src/access/AccessRestriction.h | 288 +++++++++++ src/access/BUILD.gn | 28 ++ src/access/access.gni | 18 + src/access/tests/BUILD.gn | 5 + src/access/tests/TestAccessRestriction.cpp | 463 ++++++++++++++++++ .../access-control-server.cpp | 216 +++++++- src/app/server/ArlStorage.cpp | 158 ++++++ src/app/server/ArlStorage.h | 148 ++++++ src/app/server/BUILD.gn | 10 + src/app/server/DefaultArlStorage.cpp | 203 ++++++++ src/app/server/DefaultArlStorage.h | 37 ++ src/app/server/Server.cpp | 11 + src/app/server/Server.h | 18 + src/lib/core/CHIPConfig.h | 21 + src/lib/support/DefaultStorageKeyAllocator.h | 10 + 28 files changed, 2188 insertions(+), 9 deletions(-) create mode 100644 examples/platform/linux/ExampleAccessRestriction.h create mode 100644 src/access/AccessConfig.h create mode 100644 src/access/AccessRestriction.cpp create mode 100644 src/access/AccessRestriction.h create mode 100644 src/access/access.gni create mode 100644 src/access/tests/TestAccessRestriction.cpp create mode 100644 src/app/server/ArlStorage.cpp create mode 100644 src/app/server/ArlStorage.h create mode 100644 src/app/server/DefaultArlStorage.cpp create mode 100644 src/app/server/DefaultArlStorage.h diff --git a/examples/network-manager-app/linux/args.gni b/examples/network-manager-app/linux/args.gni index 53bb53d2b1b2a1..97661ce32a8bf7 100644 --- a/examples/network-manager-app/linux/args.gni +++ b/examples/network-manager-app/linux/args.gni @@ -22,3 +22,4 @@ chip_project_config_include_dirs = [ ] chip_config_network_layer_ble = false +chip_enable_access_restrictions = true diff --git a/examples/network-manager-app/network-manager-common/network-manager-app.matter b/examples/network-manager-app/network-manager-common/network-manager-app.matter index 627838288db0d1..57118d365577f1 100644 --- a/examples/network-manager-app/network-manager-common/network-manager-app.matter +++ b/examples/network-manager-app/network-manager-common/network-manager-app.matter @@ -1623,16 +1623,23 @@ endpoint 0 { server cluster AccessControl { emits event AccessControlEntryChanged; emits event AccessControlExtensionChanged; + emits event AccessRestrictionEntryChanged; + emits event FabricRestrictionReviewUpdate; callback attribute acl; callback attribute extension; callback attribute subjectsPerAccessControlEntry; callback attribute targetsPerAccessControlEntry; callback attribute accessControlEntriesPerFabric; + callback attribute commissioningARL; + callback attribute arl; callback attribute generatedCommandList; callback attribute acceptedCommandList; callback attribute attributeList; - ram attribute featureMap default = 0; + ram attribute featureMap default = 1; callback attribute clusterRevision; + + handle command ReviewFabricRestrictions; + handle command ReviewFabricRestrictionsResponse; } server cluster BasicInformation { diff --git a/examples/network-manager-app/network-manager-common/network-manager-app.zap b/examples/network-manager-app/network-manager-common/network-manager-app.zap index 1d27e3c346f320..240cd495a7fb4f 100644 --- a/examples/network-manager-app/network-manager-common/network-manager-app.zap +++ b/examples/network-manager-app/network-manager-common/network-manager-app.zap @@ -314,6 +314,24 @@ "define": "ACCESS_CONTROL_CLUSTER", "side": "server", "enabled": 1, + "commands": [ + { + "name": "ReviewFabricRestrictions", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "ReviewFabricRestrictionsResponse", + "code": 1, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + } + ], "attributes": [ { "name": "ACL", @@ -395,6 +413,38 @@ "maxInterval": 65534, "reportableChange": 0 }, + { + "name": "CommissioningARL", + "code": 5, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ARL", + "code": 6, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, { "name": "GeneratedCommandList", "code": 65528, @@ -453,7 +503,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0", + "defaultValue": "1", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -490,6 +540,20 @@ "mfgCode": null, "side": "server", "included": 1 + }, + { + "name": "AccessRestrictionEntryChanged", + "code": 2, + "mfgCode": null, + "side": "server", + "included": 1 + }, + { + "name": "FabricRestrictionReviewUpdate", + "code": 3, + "mfgCode": null, + "side": "server", + "included": 1 } ] }, diff --git a/examples/platform/linux/AppMain.cpp b/examples/platform/linux/AppMain.cpp index 307b3428126db2..147a970124c0f2 100644 --- a/examples/platform/linux/AppMain.cpp +++ b/examples/platform/linux/AppMain.cpp @@ -103,6 +103,11 @@ #include "AppMain.h" #include "CommissionableInit.h" +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS +#include "ExampleAccessRestriction.h" +#include <app/server/DefaultArlStorage.h> +#endif + #if CHIP_DEVICE_LAYER_TARGET_DARWIN #include <platform/Darwin/NetworkCommissioningDriver.h> #if CHIP_DEVICE_CONFIG_ENABLE_WIFI @@ -121,6 +126,7 @@ using namespace chip::DeviceLayer; using namespace chip::Inet; using namespace chip::Transport; using namespace chip::app::Clusters; +using namespace chip::Access; // Network comissioning implementation namespace { @@ -593,6 +599,18 @@ void ChipLinuxAppMainLoop(AppMainLoopImplementation * impl) chip::app::RuntimeOptionsProvider::Instance().SetSimulateNoInternalTime( LinuxDeviceOptions::GetInstance().mSimulateNoInternalTime); +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + if (LinuxDeviceOptions::GetInstance().accessRestrictionEntries.HasValue()) + { + initParams.accessRestriction = new ExampleAccessRestriction(); + initParams.arlStorage = new app::DefaultArlStorage(); + for (const auto & entry : LinuxDeviceOptions::GetInstance().accessRestrictionEntries.Value()) + { + VerifyOrDie(AccessRestriction::CreateCommissioningEntry(entry) == CHIP_NO_ERROR); + } + } +#endif + // Init ZCL Data Model and CHIP App Server Server::GetInstance().Init(initParams); diff --git a/examples/platform/linux/BUILD.gn b/examples/platform/linux/BUILD.gn index 1fcee183f131b3..b37e73f6debb9c 100644 --- a/examples/platform/linux/BUILD.gn +++ b/examples/platform/linux/BUILD.gn @@ -13,6 +13,7 @@ # limitations under the License. import("//build_overrides/chip.gni") +import("//build_overrides/jsoncpp.gni") import("${chip_root}/examples/common/pigweed/pigweed_rpcs.gni") import("${chip_root}/src/app/common_flags.gni") import("${chip_root}/src/lib/core/core.gni") @@ -94,6 +95,7 @@ source_set("app-main") { "${chip_root}/src/controller:gen_check_chip_controller_headers", "${chip_root}/src/lib", "${chip_root}/src/platform/logging:default", + jsoncpp_root, ] deps = [ ":ota-test-event-trigger", diff --git a/examples/platform/linux/ExampleAccessRestriction.h b/examples/platform/linux/ExampleAccessRestriction.h new file mode 100644 index 00000000000000..0262e42bab2d57 --- /dev/null +++ b/examples/platform/linux/ExampleAccessRestriction.h @@ -0,0 +1,55 @@ +/* + * + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * 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. + */ + +/* + * AccessRestriction implementation for Linux examples. + */ + +#pragma once + +#include <access/AccessRestriction.h> +#include <app-common/zap-generated/cluster-objects.h> +#include <app/EventLogging.h> + +namespace chip { +namespace Access { + +class ExampleAccessRestriction : public AccessRestriction +{ +public: + ExampleAccessRestriction() : AccessRestriction() {} + + ~ExampleAccessRestriction() {} + +protected: + CHIP_ERROR DoRequestFabricRestrictionReview(const FabricIndex fabricIndex, uint64_t token, const std::vector<Entry> & arl) + { + // this example simply removes all restrictions and will generate AccessRestrictionEntryChanged events + while (Access::GetAccessControl().GetAccessRestriction()->DeleteEntry(0, fabricIndex) == CHIP_NO_ERROR) + ; + + chip::app::Clusters::AccessControl::Events::FabricRestrictionReviewUpdate::Type event{ .fabricIndex = fabricIndex }; + EventNumber eventNumber; + ReturnErrorOnFailure(chip::app::LogEvent(event, 0, eventNumber)); + + return CHIP_NO_ERROR; + } +}; + +} // namespace Access +} // namespace chip diff --git a/examples/platform/linux/Options.cpp b/examples/platform/linux/Options.cpp index 9b83d126c1f495..85da5f07397d4d 100644 --- a/examples/platform/linux/Options.cpp +++ b/examples/platform/linux/Options.cpp @@ -26,6 +26,7 @@ #include <app/server/OnboardingCodesUtil.h> #include <crypto/CHIPCryptoPAL.h> +#include <json/json.h> #include <lib/core/CHIPError.h> #include <lib/support/Base64.h> #include <lib/support/BytesToHex.h> @@ -47,6 +48,11 @@ using namespace chip; using namespace chip::ArgParser; +using namespace chip::Platform; + +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS +using namespace chip::Access; +#endif namespace { LinuxDeviceOptions gDeviceOptions; @@ -82,6 +88,9 @@ enum kDeviceOption_TraceFile, kDeviceOption_TraceLog, kDeviceOption_TraceDecode, +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + kDeviceOption_UseAccessRestrictions, +#endif kOptionCSRResponseCSRIncorrectType, kOptionCSRResponseCSRNonceIncorrectType, kOptionCSRResponseCSRNonceTooLong, @@ -154,6 +163,9 @@ OptionDef sDeviceOptionDefs[] = { { "trace_log", kArgumentRequired, kDeviceOption_TraceLog }, { "trace_decode", kArgumentRequired, kDeviceOption_TraceDecode }, #endif // CHIP_CONFIG_TRANSPORT_TRACE_ENABLED +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + { "enable-access-restrictions", kArgumentRequired, kDeviceOption_UseAccessRestrictions }, +#endif // CHIP_CONFIG_USE_ACCESS_RESTRICTIONS { "cert_error_csr_incorrect_type", kNoArgument, kOptionCSRResponseCSRIncorrectType }, { "cert_error_csr_existing_keypair", kNoArgument, kOptionCSRResponseCSRExistingKeyPair }, { "cert_error_csr_nonce_incorrect_type", kNoArgument, kOptionCSRResponseCSRNonceIncorrectType }, @@ -280,6 +292,11 @@ const char * sDeviceOptionHelp = " --trace_decode <1/0>\n" " A value of 1 enables traces decoding, 0 disables this (default 0).\n" #endif // CHIP_CONFIG_TRANSPORT_TRACE_ENABLED +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + " --enable-access-restrictions <CommissioningARL JSON>\n" + " Enable ACL cluster access restrictions with the provided JSON CommissioningARL. Example:\n" + " \"[{\\\"endpoint\\\": 1,\\\"cluster\\\": 2,\\\"restrictions\\\": [{\\\"type\\\": 0,\\\"id\\\": 3}]}]\"\n" +#endif // CHIP_CONFIG_USE_ACCESS_RESTRICTIONS " --cert_error_csr_incorrect_type\n" " Configure the CSRResponse to be built with an invalid CSR type.\n" " --cert_error_csr_existing_keypair\n" @@ -320,6 +337,40 @@ const char * sDeviceOptionHelp = #endif "\n"; +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS +bool ParseAccessRestrictionEntriesFromJson(const char * jsonString, + std::vector<Platform::SharedPtr<AccessRestriction::Entry>> & entries) +{ + Json::Value root; + Json::Reader reader; + VerifyOrReturnValue(reader.parse(jsonString, root), false); + + for (Json::Value::const_iterator eIt = root.begin(); eIt != root.end(); eIt++) + { + auto entry = MakeShared<AccessRestriction::Entry>(); + + entry->endpointNumber = static_cast<EndpointId>((*eIt)["endpoint"].asUInt()); + entry->clusterId = static_cast<ClusterId>((*eIt)["cluster"].asUInt()); + + Json::Value restrictions = (*eIt)["restrictions"]; + for (Json::Value::const_iterator rIt = restrictions.begin(); rIt != restrictions.end(); rIt++) + { + AccessRestriction::Restriction restriction; + restriction.restrictionType = static_cast<AccessRestriction::Type>((*rIt)["type"].asInt()); + if ((*rIt).isMember("id")) + { + restriction.id.SetValue((*rIt)["id"].asUInt()); + } + entry->restrictions.push_back(restriction); + } + + entries.push_back(entry); + } + + return true; +} +#endif // CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + bool Base64ArgToVector(const char * arg, size_t maxSize, std::vector<uint8_t> & outVector) { size_t maxBase64Size = BASE64_ENCODED_LEN(maxSize); @@ -529,6 +580,18 @@ bool HandleOption(const char * aProgram, OptionSet * aOptions, int aIdentifier, break; #endif // CHIP_CONFIG_TRANSPORT_TRACE_ENABLED +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + case kDeviceOption_UseAccessRestrictions: { + std::vector<Platform::SharedPtr<AccessRestriction::Entry>> accessRestrictionEntries; + retval = ParseAccessRestrictionEntriesFromJson(aValue, accessRestrictionEntries); + if (retval) + { + LinuxDeviceOptions::GetInstance().accessRestrictionEntries.SetValue(std::move(accessRestrictionEntries)); + } + } + break; +#endif // CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + case kOptionCSRResponseCSRIncorrectType: LinuxDeviceOptions::GetInstance().mCSRResponseOptions.csrIncorrectType = true; break; diff --git a/examples/platform/linux/Options.h b/examples/platform/linux/Options.h index f921bee4ced554..895037664232c3 100644 --- a/examples/platform/linux/Options.h +++ b/examples/platform/linux/Options.h @@ -28,6 +28,7 @@ #include <string> #include <vector> +#include <access/AccessConfig.h> #include <inet/InetInterface.h> #include <lib/core/CHIPError.h> #include <lib/core/Optional.h> @@ -38,6 +39,10 @@ #include <credentials/DeviceAttestationCredsProvider.h> #include <testing/CustomCSRResponse.h> +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS +#include <access/AccessRestriction.h> +#endif + struct LinuxDeviceOptions { chip::PayloadContents payload; @@ -81,6 +86,9 @@ struct LinuxDeviceOptions #if CONFIG_BUILD_FOR_HOST_UNIT_TEST int32_t subscriptionCapacity = CHIP_IM_MAX_NUM_SUBSCRIPTIONS; int32_t subscriptionResumptionRetryIntervalSec = -1; +#endif +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + chip::Optional<std::vector<chip::Platform::SharedPtr<chip::Access::AccessRestriction::Entry>>> accessRestrictionEntries; #endif static LinuxDeviceOptions & GetInstance(); }; diff --git a/scripts/tools/check_includes_config.py b/scripts/tools/check_includes_config.py index 2af375d7c4bdfd..efe66d57caed04 100644 --- a/scripts/tools/check_includes_config.py +++ b/scripts/tools/check_includes_config.py @@ -185,4 +185,5 @@ 'src/app/icd/client/DefaultICDStorageKey.h': {'vector'}, 'src/controller/CHIPDeviceController.cpp': {'string'}, 'src/qrcodetool/setup_payload_commands.cpp': {'string'}, + 'src/access/AccessRestriction.h': {'vector', 'map'}, } diff --git a/src/access/AccessConfig.h b/src/access/AccessConfig.h new file mode 100644 index 00000000000000..c4cb6f51f3114d --- /dev/null +++ b/src/access/AccessConfig.h @@ -0,0 +1,22 @@ +/* + * + * Copyright (c) 2020-2024 Project CHIP Authors + * + * 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 + +#if CHIP_HAVE_CONFIG_H +#include <access/AccessBuildConfig.h> +#endif diff --git a/src/access/AccessControl.cpp b/src/access/AccessControl.cpp index fcb5a43d975f8e..6d519f9a227adf 100644 --- a/src/access/AccessControl.cpp +++ b/src/access/AccessControl.cpp @@ -325,7 +325,11 @@ void AccessControl::RemoveEntryListener(EntryListener & listener) bool AccessControl::IsAccessRestrictionListSupported() const { - return false; // not yet supported +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + return mAccessRestriction != nullptr; +#else + return false; +#endif } CHIP_ERROR AccessControl::Check(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath, @@ -352,6 +356,19 @@ CHIP_ERROR AccessControl::Check(const SubjectDescriptor & subjectDescriptor, con VerifyOrReturnError(requestPath.requestType != RequestType::kRequestTypeUnknown, CHIP_ERROR_INVALID_ARGUMENT); } +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + if (mAccessRestriction != nullptr) + { + CHIP_ERROR result = mAccessRestriction->Check(subjectDescriptor, requestPath); + if (result != CHIP_NO_ERROR) + { + ChipLogProgress(DataManagement, "AccessControl: %s", + (result == CHIP_ERROR_ACCESS_DENIED) ? "denied (restricted)" : "denied (restriction error)"); + return result; + } + } +#endif + { CHIP_ERROR result = mDelegate->Check(subjectDescriptor, requestPath, requestPrivilege); if (result != CHIP_ERROR_NOT_IMPLEMENTED) diff --git a/src/access/AccessControl.h b/src/access/AccessControl.h index a7c3472f5d99b4..6f2f29803f9cdf 100644 --- a/src/access/AccessControl.h +++ b/src/access/AccessControl.h @@ -18,6 +18,12 @@ #pragma once +#include <access/AccessConfig.h> + +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS +#include "AccessRestriction.h" +#endif + #include "Privilege.h" #include "RequestPath.h" #include "SubjectDescriptor.h" @@ -627,6 +633,13 @@ class AccessControl // Removes a listener from the listener list, if in the list. void RemoveEntryListener(EntryListener & listener); +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + // Set an optional AcceessRestriction object for MNGD feature. + void SetAccessRestriction(AccessRestriction * accessRestriction) { mAccessRestriction = accessRestriction; } + + AccessRestriction * GetAccessRestriction() { return mAccessRestriction; } +#endif + /** * Check whether or not Access Restriction List is supported. * @@ -638,6 +651,8 @@ class AccessControl * Check whether access (by a subject descriptor, to a request path, * requiring a privilege) should be allowed or denied. * + * If an AccessRestriction object is set, it will be checked for additional access restrictions. + * * @retval #CHIP_ERROR_ACCESS_DENIED if denied. * @retval other errors should also be treated as denied. * @retval #CHIP_NO_ERROR if allowed. @@ -662,6 +677,10 @@ class AccessControl DeviceTypeResolver * mDeviceTypeResolver = nullptr; EntryListener * mEntryListener = nullptr; + +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + AccessRestriction * mAccessRestriction; +#endif }; /** diff --git a/src/access/AccessRestriction.cpp b/src/access/AccessRestriction.cpp new file mode 100644 index 00000000000000..cadb70a0ba0088 --- /dev/null +++ b/src/access/AccessRestriction.cpp @@ -0,0 +1,280 @@ +/* + * + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * 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 "AccessRestriction.h" + +#include <algorithm> +#include <lib/core/Global.h> + +using namespace chip::Platform; + +namespace chip { +namespace Access { + +std::vector<SharedPtr<AccessRestriction::Entry>> AccessRestriction::sCommissioningEntries; + +CHIP_ERROR AccessRestriction::CreateFabricEntries(const FabricIndex fabricIndex) +{ + for (auto & entry : sCommissioningEntries) + { + CreateEntry(nullptr, *entry, fabricIndex); + } + + return CHIP_NO_ERROR; +} + +void AccessRestriction::AddListener(EntryListener & listener) +{ + if (mListeners == nullptr) + { + mListeners = &listener; + listener.mNext = nullptr; + return; + } + + for (EntryListener * l = mListeners; /**/; l = l->mNext) + { + if (l == &listener) + { + return; + } + + if (l->mNext == nullptr) + { + l->mNext = &listener; + listener.mNext = nullptr; + return; + } + } +} + +void AccessRestriction::RemoveListener(EntryListener & listener) +{ + if (mListeners == &listener) + { + mListeners = listener.mNext; + listener.mNext = nullptr; + return; + } + + for (EntryListener * l = mListeners; l != nullptr; l = l->mNext) + { + if (l->mNext == &listener) + { + l->mNext = listener.mNext; + listener.mNext = nullptr; + return; + } + } +} + +SharedPtr<AccessRestriction::Entry> AccessRestriction::GetEntry(FabricIndex fabricIndex, size_t index) +{ + if (mFabricEntries.find(fabricIndex) != mFabricEntries.end() && index < mFabricEntries[fabricIndex].size()) + { + return mFabricEntries[fabricIndex][index]; + } + + return nullptr; +} + +CHIP_ERROR AccessRestriction::CreateCommissioningEntry(SharedPtr<Entry> entry) +{ + if (!entry->IsValid()) + { + ChipLogError(DataManagement, "AccessRestriction: invalid restriction entry"); + return CHIP_ERROR_INVALID_ARGUMENT; + } + + sCommissioningEntries.push_back(entry); + return CHIP_NO_ERROR; +} + +CHIP_ERROR AccessRestriction::CreateEntry(size_t * index, const Entry & entry, FabricIndex fabricIndex) +{ + if (!entry.IsValid()) + { + ChipLogError(DataManagement, "AccessRestriction: invalid restriction entry"); + return CHIP_ERROR_INVALID_ARGUMENT; + } + + auto localEntry = MakeShared<Entry>(entry); + size_t newIndex; + + localEntry->fabricIndex = fabricIndex; + + if (mFabricEntries.find(fabricIndex) == mFabricEntries.end()) + { + mFabricEntries[fabricIndex] = std::vector<SharedPtr<Entry>>(); + mFabricEntries[fabricIndex].push_back(localEntry); + } + else + { + mFabricEntries[fabricIndex].push_back(localEntry); + } + + newIndex = mFabricEntries[fabricIndex].size() - 1; + + for (EntryListener * listener = mListeners; listener != nullptr; listener = listener->mNext) + { + listener->OnEntryChanged(fabricIndex, newIndex, localEntry, EntryListener::ChangeType::kAdded); + } + + if (index != nullptr) + { + *index = newIndex; + } + + ChipLogProgress(DataManagement, "AccessRestriction: update entry f=%u i=%lu", fabricIndex, newIndex); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR AccessRestriction::DeleteEntry(size_t index, const FabricIndex fabricIndex) +{ + ChipLogProgress(DataManagement, "AccessRestriction: delete entry f=%u i=%lu", fabricIndex, index); + + auto entry = GetEntry(fabricIndex, index); + if (entry == nullptr) + { + return CHIP_ERROR_NOT_FOUND; + } + else + { + mFabricEntries[fabricIndex].erase(mFabricEntries[fabricIndex].begin() + static_cast<int>(index)); + + for (EntryListener * listener = mListeners; listener != nullptr; listener = listener->mNext) + { + listener->OnEntryChanged(fabricIndex, index, entry, EntryListener::ChangeType::kRemoved); + } + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR AccessRestriction::UpdateEntry(size_t index, const Entry & entry, const FabricIndex fabricIndex) +{ + ChipLogProgress(DataManagement, "AccessRestriction: update entry f=%u i=%lu", fabricIndex, index); + + auto localEntry = GetEntry(fabricIndex, index); + if (localEntry != nullptr) + { + *localEntry = entry; + + for (EntryListener * listener = mListeners; listener != nullptr; listener = listener->mNext) + { + listener->OnEntryChanged(fabricIndex, index, localEntry, EntryListener::ChangeType::kUpdated); + } + + return CHIP_NO_ERROR; + } + + return CHIP_ERROR_NOT_FOUND; +} + +CHIP_ERROR AccessRestriction::Check(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath) +{ + ChipLogProgress(DataManagement, "AccessRestriction: action %d", static_cast<int>(requestPath.requestType)); + + if (requestPath.requestType == RequestType::kRequestTypeUnknown) + { + ChipLogError(DataManagement, "AccessRestriction: RequestPath type is unknown"); + return CHIP_ERROR_INVALID_ARGUMENT; + } + + // wildcard event subscriptions are allowed since wildcard is only used when setting up the subscription and + // we want that request to succeed (when generating the report, this method will be called with the specific + // event id). All other requests require an entity id + if (!requestPath.entityId.has_value()) + { + if (requestPath.requestType == RequestType::kEventReadOrSubscribeRequest) + { + return CHIP_NO_ERROR; + } + else + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + } + + for (auto & entry : mFabricEntries[subjectDescriptor.fabricIndex]) + { + if (entry->endpointNumber != requestPath.endpoint || entry->clusterId != requestPath.cluster) + { + continue; + } + + for (auto & restriction : entry->restrictions) + { + // a missing id is a wildcard + bool idMatch = !restriction.id.HasValue() || restriction.id.Value() == requestPath.entityId.value(); + if (!idMatch) + { + continue; + } + + switch (restriction.restrictionType) + { + case Type::kAttributeAccessForbidden: + if (requestPath.requestType == RequestType::kAttributeReadRequest || + requestPath.requestType == RequestType::kAttributeWriteRequest) + { + return CHIP_ERROR_ACCESS_DENIED; + } + break; + case Type::kAttributeWriteForbidden: + if (requestPath.requestType == RequestType::kAttributeWriteRequest) + { + return CHIP_ERROR_ACCESS_DENIED; + } + break; + case Type::kCommandForbidden: + if (requestPath.requestType == RequestType::kCommandInvokeRequest) + { + return CHIP_ERROR_ACCESS_DENIED; + } + break; + case Type::kEventForbidden: + if (requestPath.requestType == RequestType::kEventReadOrSubscribeRequest) + { + return CHIP_ERROR_ACCESS_DENIED; + } + break; + } + } + } + + return CHIP_NO_ERROR; +} + +void AccessRestriction::ClearData() +{ + // iterate over mFabricEntries and call DeleteEntry for each entry + for (auto & fabricEntry : mFabricEntries) + { + for (size_t i = 0; i < fabricEntry.second.size(); i++) + { + DeleteEntry(i, fabricEntry.first); + } + } + + sCommissioningEntries.clear(); +} + +} // namespace Access +} // namespace chip diff --git a/src/access/AccessRestriction.h b/src/access/AccessRestriction.h new file mode 100644 index 00000000000000..34d1f27562767b --- /dev/null +++ b/src/access/AccessRestriction.h @@ -0,0 +1,288 @@ +/* + * + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * 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 "RequestPath.h" +#include "SubjectDescriptor.h" +#include <algorithm> +#include <app-common/zap-generated/cluster-objects.h> +#include <cstdint> +#include <lib/core/CHIPError.h> +#include <lib/core/DataModelTypes.h> +#include <lib/core/Optional.h> +#include <lib/support/CHIPMem.h> +#include <map> +#include <memory> +#include <protocols/interaction_model/Constants.h> +#include <vector> + +namespace chip { +namespace Access { + +template <typename T> +using SharedPtr = chip::Platform::SharedPtr<T>; + +class AccessRestriction +{ +public: + static constexpr size_t kNumberOfFabrics = CHIP_CONFIG_MAX_FABRICS; + static constexpr size_t kEntriesPerFabric = CHIP_CONFIG_ACCESS_RESTRICTION_MAX_ENTRIES_PER_FABRIC; + static constexpr size_t kRestrictionsPerEntry = CHIP_CONFIG_ACCESS_RESTRICTION_MAX_RESTRICTIONS_PER_ENTRY; + + /** + * Defines the type of access restriction, which is used to determine the meaning of the restriction's id. + */ + enum class Type : uint8_t + { + kAttributeAccessForbidden = 0, + kAttributeWriteForbidden = 1, + kCommandForbidden = 2, + kEventForbidden = 3 + }; + + /** + * Defines a single restriction on an attribute, command, or event. + * + * If id is not set, the restriction applies to all attributes, commands, or events of the given type (wildcard). + */ + struct Restriction + { + Type restrictionType; + Optional<uint32_t> id; + }; + + /** + * Defines a single entry in the access restriction list, which contains a list of restrictions + * for a cluster on an endpoint. + */ + struct Entry + { + FabricIndex fabricIndex; + EndpointId endpointNumber; + ClusterId clusterId; + std::vector<Restriction> restrictions; + + bool IsValid() const + { + return endpointNumber != 0 && clusterId != app::Clusters::NetworkCommissioning::Id && + clusterId != app::Clusters::Descriptor::Id; + } + }; + + /** + * Used to notify of changes in the access restriction list and active reviews. + */ + class EntryListener + { + public: + enum class ChangeType + { + kAdded = 1, + kRemoved = 2, + kUpdated = 3 + }; + + virtual ~EntryListener() = default; + + /** + * Notifies of a change in the access restriction list. + * + * @param [in] fabricIndex The index of the fabric in which the entry has changed. + * @param [in] index Index of entry to which has changed (relative to fabric). + * @param [in] entry The latest value of the entry which changed. + * @param [in] changeType The type of change that occurred. + */ + virtual void OnEntryChanged(FabricIndex fabricIndex, size_t index, SharedPtr<Entry> entry, ChangeType changeType) = 0; + + /** + * Notifies of an update to an active review with instructions and an optional redirect URL. + * + * @param [in] fabricIndex The index of the fabric in which the entry has changed. + * @param [in] token The token of the review being updated (obtained from ReviewFabricRestrictionsResponse) + * @param [in] instruction The instructions to be displayed to the user. + * @param [in] redirectUrl An optional URL to redirect the user to for more information. May be null. + */ + virtual void OnFabricRestrictionReviewUpdate(FabricIndex fabricIndex, uint64_t token, const char * instruction, + const char * redirectUrl) = 0; + + private: + EntryListener * mNext = nullptr; + + friend class AccessRestriction; + }; + + AccessRestriction() = default; + virtual ~AccessRestriction() = default; + + AccessRestriction(const AccessRestriction &) = delete; + AccessRestriction & operator=(const AccessRestriction &) = delete; + + /** + * Create restriction entries for a fabric by populating from the commissioning entries. + * This should be called when the device is commissioned to a new fabric. + * + * @param [in] fabricIndex The index of the fabric for which to create entries. + */ + CHIP_ERROR CreateFabricEntries(const FabricIndex fabricIndex); + + /** + * Add a listener to be notified of changes in the access restriction list and active reviews. + * + * @param [in] listener The listener to add. + */ + void AddListener(EntryListener & listener); + + /** + * Remove a listener from being notified of changes in the access restriction list and active reviews. + * + * @param [in] listener The listener to remove. + */ + void RemoveListener(EntryListener & listener); + + /** + * Check whether access by a subject descriptor to a request path should be restricted (denied) for the given action. + * These restrictions are are only a part of overall access evaluation. + * + * If access is not restricted, CHIP_NO_ERROR will be returned. + * + * @retval #CHIP_ERROR_ACCESS_DENIED if access is denied. + * @retval other errors should also be treated as restricted/denied. + * @retval #CHIP_NO_ERROR if access is not restricted/denied. + */ + CHIP_ERROR Check(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath); + + /** + * Request a review of the access restrictions for a fabric. + * + * @param [in] fabricIndex The index of the fabric requesting a review. + * @param [in] arl An optinal list of access restriction entries to review. If null, all entries will be reviewed. + * @param [out] token The unique token for the review, which can be matched to a review update event. + */ + CHIP_ERROR RequestFabricRestrictionReview(FabricIndex fabricIndex, const std::vector<Entry> & arl, uint64_t & token) + { + token = ++mNextToken; + return DoRequestFabricRestrictionReview(fabricIndex, token, arl); + } + + using EntryIterator = std::vector<SharedPtr<Entry>>::iterator; + + /** + * Get iterator over the commissioning entries, which are used during commissioning + * and also installed as defaults for a fabric upon completion of commissioning. + * + * @param [out] begin iterator pointing to the beginning of the entries + * @param [out] end iterator pointing to the end of the entries + */ + static CHIP_ERROR CommissioningEntries(EntryIterator & begin, EntryIterator & end) + { + begin = sCommissioningEntries.begin(); + end = sCommissioningEntries.end(); + + return CHIP_NO_ERROR; + } + + /** + * Get iterator over entries in the access restriction list by fabric. + * + * @param [in] fabricIndex Iteration is confined to fabric + * @param [out] begin iterator pointing to the beginning of the entries + * @param [out] end iterator pointing to the end of the entries + */ + CHIP_ERROR Entries(const FabricIndex fabricIndex, EntryIterator & begin, EntryIterator & end) + { + if (mFabricEntries.find(fabricIndex) == mFabricEntries.end()) + { + return CHIP_ERROR_NOT_FOUND; + } + + begin = mFabricEntries[fabricIndex].begin(); + end = mFabricEntries[fabricIndex].end(); + + return CHIP_NO_ERROR; + } + + /** + * Add a restriction entry to the commissioning access restriction list. This list is automatically + * applied to fabrics upon completion of commissioning. This request will fail if there is already + * an entry with the same cluster and endpoint. + * + * @param [in] entry The entry to add to the commissioning access restriction list. + * @return CHIP_NO_ERROR if the entry was successfully created, or an error code. + */ + static CHIP_ERROR CreateCommissioningEntry(SharedPtr<Entry> entry); + + /** + * Add a restriction entry to the access restriction list and notify any listeners. This request + * will fail if there is already an entry with the same cluster and endpoint. + * + * @param [out] index (If not nullptr) index of created entry (relative to fabric). + * @param [in] entry The entry to add to the access restriction list. + * @param [in] fabricIndex The index of the fabric in which the entry should be added. + * @return CHIP_NO_ERROR if the entry was successfully created, or an error code. + */ + CHIP_ERROR CreateEntry(size_t * index, const Entry & entry, const FabricIndex fabricIndex); + + /** + * Delete a restriction entry from the access restriction list and notify any listeners. + * + * @param [in] index Index of entry to delete. May be relative to fabric. + * @param [in] fabricIndex Fabric to which entry `index` is relative. + * @return CHIP_NO_ERROR if the entry was successfully deleted, or an error code. + */ + CHIP_ERROR DeleteEntry(size_t index, const FabricIndex fabricIndex); + + /** + * Update a restriction entry in the access restriction list and notify any listeners. + * + * @param [in] index Index of entry to delete. May be relative to fabric. + * @param [in] entry The updated entry to replace the existing entry. + * @param [in] fabricIndex The index of the fabric in which the entry should be updated. + * @return CHIP_NO_ERROR if the entry was successfully updated, or an error code. + */ + CHIP_ERROR UpdateEntry(size_t index, const Entry & entry, const FabricIndex fabricIndex); + +protected: + /** + * Initiate a review of the access restrictions for a fabric. This method should be implemented by the platform and be + * non-blocking. + * + * @param [in] fabricIndex The index of the fabric requesting a review. + * @param [in] token The unique token for the review, which can be matched to a review update event. + * @param [in] arl An optinal list of access restriction entries to review. If null, all entries will be reviewed. + * @return CHIP_NO_ERROR if the review was successfully requested, or an error code if the request failed. + */ + virtual CHIP_ERROR DoRequestFabricRestrictionReview(const FabricIndex fabricIndex, uint64_t token, + const std::vector<Entry> & arl) = 0; + + /** + * Clear all access restriction data. + */ + void ClearData(); + +private: + SharedPtr<Entry> GetEntry(FabricIndex fabricIndex, size_t index); + + static std::vector<Platform::SharedPtr<Entry>> sCommissioningEntries; + uint64_t mNextToken = 1; + EntryListener * mListeners = nullptr; + std::map<FabricIndex, std::vector<Platform::SharedPtr<Entry>>> mFabricEntries; +}; + +} // namespace Access +} // namespace chip diff --git a/src/access/BUILD.gn b/src/access/BUILD.gn index 8d2c4b504975ee..ae2a64d70906f8 100644 --- a/src/access/BUILD.gn +++ b/src/access/BUILD.gn @@ -13,6 +13,25 @@ # limitations under the License. import("//build_overrides/chip.gni") +import("${chip_root}/build/chip/buildconfig_header.gni") +import("${chip_root}/src/access/access.gni") + +buildconfig_header("access_buildconfig") { + header = "AccessBuildConfig.h" + header_dir = "access" + + defines = [ + "CHIP_CONFIG_USE_ACCESS_RESTRICTIONS=${chip_enable_access_restrictions}", + ] + + visibility = [ ":access_config" ] +} + +source_set("access_config") { + sources = [ "AccessConfig.h" ] + + deps = [ ":access_buildconfig" ] +} source_set("types") { sources = [ @@ -23,6 +42,7 @@ source_set("types") { ] public_deps = [ + ":access_config", "${chip_root}/src/lib/core", "${chip_root}/src/lib/core:types", ] @@ -43,10 +63,18 @@ static_library("access") { cflags = [ "-Wconversion" ] public_deps = [ + ":access_config", ":types", "${chip_root}/src/lib/core", "${chip_root}/src/lib/core:types", "${chip_root}/src/lib/support", "${chip_root}/src/platform", ] + + if (chip_enable_access_restrictions) { + sources += [ + "AccessRestriction.cpp", + "AccessRestriction.h", + ] + } } diff --git a/src/access/access.gni b/src/access/access.gni new file mode 100644 index 00000000000000..bd18f1387b66b9 --- /dev/null +++ b/src/access/access.gni @@ -0,0 +1,18 @@ +# Copyright (c) 2024 Project CHIP Authors +# +# 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. + +declare_args() { + # Enable ARL features of Access Control + chip_enable_access_restrictions = false +} diff --git a/src/access/tests/BUILD.gn b/src/access/tests/BUILD.gn index d8b43e6a17a01d..c790ad4066be73 100644 --- a/src/access/tests/BUILD.gn +++ b/src/access/tests/BUILD.gn @@ -15,6 +15,7 @@ import("//build_overrides/build.gni") import("//build_overrides/chip.gni") import("//build_overrides/pigweed.gni") +import("${chip_root}/src/access/access.gni") import("${chip_root}/build/chip/chip_test_suite.gni") @@ -29,4 +30,8 @@ chip_test_suite("tests") { "${chip_root}/src/lib/support:test_utils", "${dir_pw_unit_test}", ] + + if (chip_enable_access_restrictions) { + test_sources += [ "TestAccessRestriction.cpp" ] + } } diff --git a/src/access/tests/TestAccessRestriction.cpp b/src/access/tests/TestAccessRestriction.cpp new file mode 100644 index 00000000000000..3a3db93be3f640 --- /dev/null +++ b/src/access/tests/TestAccessRestriction.cpp @@ -0,0 +1,463 @@ +/* + * + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * 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 "access/AccessControl.h" +#include "access/AccessRestriction.h" +#include "access/examples/ExampleAccessControlDelegate.h" + +#include <pw_unit_test/framework.h> + +#include <lib/core/CHIPCore.h> +#include <lib/core/StringBuilderAdapters.h> + +namespace chip { +namespace Access { + +class TestAccessRestrictionImpl : public AccessRestriction +{ + CHIP_ERROR DoRequestFabricRestrictionReview(const FabricIndex fabricIndex, uint64_t token, const std::vector<Entry> & arl) + { + return CHIP_NO_ERROR; + } + +public: + void Clear() { ClearData(); } +}; + +AccessControl accessControl; +TestAccessRestrictionImpl accessRestriction; + +constexpr ClusterId kNetworkCommissioningCluster = 0x0000'0031; // must not be blocked by access restrictions on any endpoint +constexpr ClusterId kDescriptorCluster = 0x0000'001d; // must not be blocked by access restrictions on any endpoint +constexpr ClusterId kOnOffCluster = 0x0000'0006; + +constexpr NodeId kOperationalNodeId1 = 0x1111111111111111; +constexpr NodeId kOperationalNodeId2 = 0x2222222222222222; +constexpr NodeId kOperationalNodeId3 = 0x3333333333333333; + +struct AclEntryData +{ + FabricIndex fabricIndex = kUndefinedFabricIndex; + Privilege privilege = Privilege::kView; + AuthMode authMode = AuthMode::kNone; + NodeId subject; +}; + +constexpr AclEntryData aclEntryData[] = { + { + .fabricIndex = 1, + .privilege = Privilege::kAdminister, + .authMode = AuthMode::kCase, + .subject = kOperationalNodeId1, + }, + { + .fabricIndex = 2, + .privilege = Privilege::kAdminister, + .authMode = AuthMode::kCase, + .subject = kOperationalNodeId2, + }, +}; +constexpr size_t aclEntryDataCount = ArraySize(aclEntryData); + +struct CheckData +{ + SubjectDescriptor subjectDescriptor; + RequestPath requestPath; + Privilege privilege; + bool allow; +}; + +constexpr CheckData checkDataNoRestrictions[] = { + // Checks for implicit PASE + { .subjectDescriptor = { .fabricIndex = 0, .authMode = AuthMode::kPase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 0, .authMode = AuthMode::kPase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kPase, .subject = kOperationalNodeId2 }, + .requestPath = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kPase, .subject = kOperationalNodeId3 }, + .requestPath = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kEventReadOrSubscribeRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + // Checks for entry 0 + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kEventReadOrSubscribeRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + // Checks for entry 1 + { .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subject = kOperationalNodeId2 }, + .requestPath = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subject = kOperationalNodeId2 }, + .requestPath = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subject = kOperationalNodeId2 }, + .requestPath = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subject = kOperationalNodeId2 }, + .requestPath = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kEventReadOrSubscribeRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, +}; + +CHIP_ERROR LoadEntry(AccessControl::Entry & entry, const AclEntryData & entryData) +{ + ReturnErrorOnFailure(entry.SetAuthMode(entryData.authMode)); + ReturnErrorOnFailure(entry.SetFabricIndex(entryData.fabricIndex)); + ReturnErrorOnFailure(entry.SetPrivilege(entryData.privilege)); + ReturnErrorOnFailure(entry.AddSubject(nullptr, entryData.subject)); + return CHIP_NO_ERROR; +} + +CHIP_ERROR LoadAccessControl(AccessControl & ac, const AclEntryData * entryData, size_t count) +{ + AccessControl::Entry entry; + for (size_t i = 0; i < count; ++i, ++entryData) + { + ReturnErrorOnFailure(ac.PrepareEntry(entry)); + ReturnErrorOnFailure(LoadEntry(entry, *entryData)); + ReturnErrorOnFailure(ac.CreateEntry(nullptr, entry)); + } + return CHIP_NO_ERROR; +} + +void RunChecks(const CheckData * checkData, size_t count) +{ + for (size_t i = 0; i < count; i++) + { + CHIP_ERROR expectedResult = checkData[i].allow ? CHIP_NO_ERROR : CHIP_ERROR_ACCESS_DENIED; + EXPECT_EQ(accessControl.Check(checkData[i].subjectDescriptor, checkData[i].requestPath, checkData[i].privilege), + expectedResult); + } +} + +class DeviceTypeResolver : public AccessControl::DeviceTypeResolver +{ +public: + bool IsDeviceTypeOnEndpoint(DeviceTypeId deviceType, EndpointId endpoint) override { return false; } +} testDeviceTypeResolver; + +class TestAccessRestriction : public ::testing::Test +{ +public: // protected + void SetUp() override { accessRestriction.Clear(); } + static void SetUpTestSuite() + { + ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR); + AccessControl::Delegate * delegate = Examples::GetAccessControlDelegate(); + SetAccessControl(accessControl); + GetAccessControl().SetAccessRestriction(&accessRestriction); + VerifyOrDie(GetAccessControl().Init(delegate, testDeviceTypeResolver) == CHIP_NO_ERROR); + EXPECT_EQ(LoadAccessControl(accessControl, aclEntryData, aclEntryDataCount), CHIP_NO_ERROR); + } + static void TearDownTestSuite() + { + GetAccessControl().Finish(); + ResetAccessControlToDefault(); + } +}; + +// basic data check without restrictions +TEST_F(TestAccessRestriction, MetaTest) +{ + for (const auto & checkData : checkDataNoRestrictions) + { + CHIP_ERROR expectedResult = checkData.allow ? CHIP_NO_ERROR : CHIP_ERROR_ACCESS_DENIED; + EXPECT_EQ(accessControl.Check(checkData.subjectDescriptor, checkData.requestPath, checkData.privilege), expectedResult); + } +} + +// ensure adding restrictons on endpoint 0 (any cluster) or for network commissioning and descriptor clusters fail +TEST_F(TestAccessRestriction, InvalidRestrictionsTest) +{ + AccessRestriction::Entry entry; + entry.fabricIndex = 1; + entry.clusterId = kOnOffCluster; + entry.restrictions.push_back({ .restrictionType = AccessRestriction::Type::kAttributeAccessForbidden }); + + // must not restrict endpoint 0 + entry.endpointNumber = 0; + EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_ERROR_INVALID_ARGUMENT); + + // must not restrict network commissioning cluster + entry.endpointNumber = 1; + entry.clusterId = kNetworkCommissioningCluster; + EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_ERROR_INVALID_ARGUMENT); + + // must not restrict descriptor cluster + entry.clusterId = kDescriptorCluster; + EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_ERROR_INVALID_ARGUMENT); +} + +constexpr CheckData accessAttributeRestrictionTestData[] = { + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = false }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = false }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kOnOffCluster, + .endpoint = 1, + .requestType = RequestType::kEventReadOrSubscribeRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, +}; + +TEST_F(TestAccessRestriction, AccessAttributeRestrictionTest) +{ + AccessRestriction::Entry entry; + entry.fabricIndex = 1; + entry.endpointNumber = 1; + entry.clusterId = kOnOffCluster; + entry.restrictions.push_back({ .restrictionType = AccessRestriction::Type::kAttributeAccessForbidden }); + + // test wildcarded entity id + EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_NO_ERROR); + RunChecks(accessAttributeRestrictionTestData, ArraySize(accessAttributeRestrictionTestData)); + + // test specific entity id + accessRestriction.Clear(); + entry.restrictions[0].id.SetValue(1); + EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_NO_ERROR); + RunChecks(accessAttributeRestrictionTestData, ArraySize(accessAttributeRestrictionTestData)); +} + +constexpr CheckData writeAttributeRestrictionTestData[] = { + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = false }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kOnOffCluster, + .endpoint = 1, + .requestType = RequestType::kEventReadOrSubscribeRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, +}; + +TEST_F(TestAccessRestriction, WriteAttributeRestrictionTest) +{ + AccessRestriction::Entry entry; + entry.fabricIndex = 1; + entry.endpointNumber = 1; + entry.clusterId = kOnOffCluster; + entry.restrictions.push_back({ .restrictionType = AccessRestriction::Type::kAttributeWriteForbidden }); + + // test wildcarded entity id + EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_NO_ERROR); + RunChecks(writeAttributeRestrictionTestData, ArraySize(writeAttributeRestrictionTestData)); + + // test specific entity id + accessRestriction.Clear(); + entry.restrictions[0].id.SetValue(1); + EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_NO_ERROR); + RunChecks(writeAttributeRestrictionTestData, ArraySize(writeAttributeRestrictionTestData)); +} + +constexpr CheckData commandAttributeRestrictionTestData[] = { + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = false }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kOnOffCluster, + .endpoint = 1, + .requestType = RequestType::kEventReadOrSubscribeRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, +}; + +TEST_F(TestAccessRestriction, CommandRestrictionTest) +{ + AccessRestriction::Entry entry; + entry.fabricIndex = 1; + entry.endpointNumber = 1; + entry.clusterId = kOnOffCluster; + entry.restrictions.push_back({ .restrictionType = AccessRestriction::Type::kCommandForbidden }); + + // test wildcarded entity id + EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_NO_ERROR); + RunChecks(commandAttributeRestrictionTestData, ArraySize(commandAttributeRestrictionTestData)); + + // test specific entity id + accessRestriction.Clear(); + entry.restrictions[0].id.SetValue(1); + EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_NO_ERROR); + RunChecks(commandAttributeRestrictionTestData, ArraySize(commandAttributeRestrictionTestData)); +} + +constexpr CheckData eventAttributeRestrictionTestData[] = { + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kOnOffCluster, + .endpoint = 1, + .requestType = RequestType::kEventReadOrSubscribeRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = false }, +}; + +TEST_F(TestAccessRestriction, EventRestrictionTest) +{ + AccessRestriction::Entry entry; + entry.fabricIndex = 1; + entry.endpointNumber = 1; + entry.clusterId = kOnOffCluster; + entry.restrictions.push_back({ .restrictionType = AccessRestriction::Type::kEventForbidden }); + + // test wildcarded entity id + EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_NO_ERROR); + RunChecks(eventAttributeRestrictionTestData, ArraySize(eventAttributeRestrictionTestData)); + + // test specific entity id + accessRestriction.Clear(); + entry.restrictions[0].id.SetValue(1); + EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_NO_ERROR); + RunChecks(eventAttributeRestrictionTestData, ArraySize(eventAttributeRestrictionTestData)); +} + +constexpr CheckData combinedRestrictionTestData[] = { + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = false }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 2 }, + .privilege = Privilege::kAdminister, + .allow = false }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 3 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 4 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 3 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 4 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, + .requestPath = { .cluster = kOnOffCluster, + .endpoint = 1, + .requestType = RequestType::kEventReadOrSubscribeRequest, + .entityId = 5 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subject = kOperationalNodeId2 }, + .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = false }, + { .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subject = kOperationalNodeId2 }, + .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 2 }, + .privilege = Privilege::kAdminister, + .allow = true }, +}; + +TEST_F(TestAccessRestriction, CombinedRestrictionTest) +{ + // a restriction for all access to attribute 1 and 2, attributes 3 and 4 are allowed + AccessRestriction::Entry entry1; + entry1.fabricIndex = 1; + entry1.endpointNumber = 1; + entry1.clusterId = kOnOffCluster; + entry1.restrictions.push_back({ .restrictionType = AccessRestriction::Type::kAttributeWriteForbidden }); + entry1.restrictions[0].id.SetValue(1); + entry1.restrictions.push_back({ .restrictionType = AccessRestriction::Type::kAttributeAccessForbidden }); + entry1.restrictions[1].id.SetValue(2); + EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry1, 1), CHIP_NO_ERROR); + + // a restriction for fabric 2 that forbids command 1 and 2. Check that command 1 is blocked on invoke, but attribute 2 write is + // allowed + AccessRestriction::Entry entry2; + entry2.fabricIndex = 2; + entry2.endpointNumber = 1; + entry2.clusterId = kOnOffCluster; + entry2.restrictions.push_back({ .restrictionType = AccessRestriction::Type::kCommandForbidden }); + entry2.restrictions[0].id.SetValue(1); + entry2.restrictions.push_back({ .restrictionType = AccessRestriction::Type::kCommandForbidden }); + entry2.restrictions[1].id.SetValue(2); + EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry2, 2), CHIP_NO_ERROR); + + RunChecks(combinedRestrictionTestData, ArraySize(combinedRestrictionTestData)); +} + +} // namespace Access +} // namespace chip diff --git a/src/app/clusters/access-control-server/access-control-server.cpp b/src/app/clusters/access-control-server/access-control-server.cpp index 321f7aa92a483a..c8eda434079168 100644 --- a/src/app/clusters/access-control-server/access-control-server.cpp +++ b/src/app/clusters/access-control-server/access-control-server.cpp @@ -17,6 +17,11 @@ #include <access/AccessControl.h> +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS +#include <access/AccessRestriction.h> +#include <app/server/ArlStorage.h> +#endif + #include <app-common/zap-generated/cluster-objects.h> #include <app/AttributeAccessInterface.h> @@ -41,6 +46,15 @@ using Entry = AccessControl::Entry; using EntryListener = AccessControl::EntryListener; using ExtensionEvent = Clusters::AccessControl::Events::AccessControlExtensionChanged::Type; +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS +using ArlChangedEvent = Clusters::AccessControl::Events::AccessRestrictionEntryChanged::Type; +using ArlReviewEvent = Clusters::AccessControl::Events::FabricRestrictionReviewUpdate::Type; + +constexpr uint16_t kMaxInstructionStringLength = 512; + +constexpr uint16_t kMaxRedirectUrlStringLength = 256; +#endif + // TODO(#13590): generated code doesn't automatically handle max length so do it manually constexpr int kExtensionDataMaxLength = 128; @@ -48,7 +62,12 @@ constexpr uint16_t kClusterRevision = 1; namespace { -class AccessControlAttribute : public AttributeAccessInterface, public EntryListener +class AccessControlAttribute : public AttributeAccessInterface, + public AccessControl::EntryListener +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + , + public AccessRestriction::EntryListener +#endif { public: AccessControlAttribute() : AttributeAccessInterface(Optional<EndpointId>(0), AccessControlCluster::Id) {} @@ -64,8 +83,16 @@ class AccessControlAttribute : public AttributeAccessInterface, public EntryList CHIP_ERROR Write(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder) override; public: - void OnEntryChanged(const SubjectDescriptor * subjectDescriptor, FabricIndex fabric, size_t index, const Entry * entry, - ChangeType changeType) override; + void OnEntryChanged(const SubjectDescriptor * subjectDescriptor, FabricIndex fabric, size_t index, + const AccessControl::Entry * entry, AccessControl::EntryListener::ChangeType changeType) override; + +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + void OnEntryChanged(FabricIndex fabricIndex, size_t index, Platform::SharedPtr<AccessRestriction::Entry> entry, + AccessRestriction::EntryListener::ChangeType changeType) override; + + void OnFabricRestrictionReviewUpdate(FabricIndex fabricIndex, uint64_t token, const char * instruction, + const char * redirectUrl) override; +#endif private: /// Business logic implementation of write, returns generic CHIP_ERROR. @@ -78,6 +105,10 @@ class AccessControlAttribute : public AttributeAccessInterface, public EntryList CHIP_ERROR ReadExtension(AttributeValueEncoder & aEncoder); CHIP_ERROR WriteAcl(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder); CHIP_ERROR WriteExtension(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder); +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + CHIP_ERROR ReadCommissioningArl(AttributeValueEncoder & aEncoder); + CHIP_ERROR ReadArl(AttributeValueEncoder & aEncoder); +#endif } sAttribute; CHIP_ERROR LogExtensionChangedEvent(const AccessControlCluster::Structs::AccessControlExtensionStruct::Type & item, @@ -159,6 +190,12 @@ CHIP_ERROR AccessControlAttribute::ReadImpl(const ConcreteReadAttributePath & aP ReturnErrorOnFailure(GetAccessControl().GetMaxEntriesPerFabric(value)); return aEncoder.Encode(static_cast<uint16_t>(value)); } +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + case AccessControlCluster::Attributes::CommissioningARL::Id: + return ReadCommissioningArl(aEncoder); + case AccessControlCluster::Attributes::Arl::Id: + return ReadArl(aEncoder); +#endif case AccessControlCluster::Attributes::ClusterRevision::Id: return aEncoder.Encode(kClusterRevision); } @@ -375,7 +412,7 @@ CHIP_ERROR AccessControlAttribute::WriteExtension(const ConcreteDataAttributePat } void AccessControlAttribute::OnEntryChanged(const SubjectDescriptor * subjectDescriptor, FabricIndex fabric, size_t index, - const Entry * entry, ChangeType changeType) + const AccessControl::Entry * entry, AccessControl::EntryListener::ChangeType changeType) { // NOTE: If the entry was changed internally by the system (e.g. creating // entries at startup from persistent storage, or deleting entries when a @@ -389,11 +426,11 @@ void AccessControlAttribute::OnEntryChanged(const SubjectDescriptor * subjectDes CHIP_ERROR err; AclEvent event{ .changeType = ChangeTypeEnum::kChanged, .fabricIndex = subjectDescriptor->fabricIndex }; - if (changeType == ChangeType::kAdded) + if (changeType == AccessControl::EntryListener::ChangeType::kAdded) { event.changeType = ChangeTypeEnum::kAdded; } - else if (changeType == ChangeType::kRemoved) + else if (changeType == AccessControl::EntryListener::ChangeType::kRemoved) { event.changeType = ChangeTypeEnum::kRemoved; } @@ -428,6 +465,97 @@ void AccessControlAttribute::OnEntryChanged(const SubjectDescriptor * subjectDes ChipLogError(DataManagement, "AccessControlCluster: event failed %" CHIP_ERROR_FORMAT, err.Format()); } +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS +CHIP_ERROR AccessControlAttribute::ReadCommissioningArl(AttributeValueEncoder & aEncoder) +{ + auto accessRestriction = GetAccessControl().GetAccessRestriction(); + if (accessRestriction == nullptr) + { + return CHIP_ERROR_NOT_IMPLEMENTED; + } + + return aEncoder.EncodeList([&](const auto & encoder) -> CHIP_ERROR { + AccessRestriction::EntryIterator begin; + AccessRestriction::EntryIterator end; + ReturnErrorOnFailure(accessRestriction->CommissioningEntries(begin, end)); + + for (AccessRestriction::EntryIterator it = begin; it != end; ++it) + { + ArlStorage::EncodableEntry encodableEntry(*it); + ReturnErrorOnFailure(encoder.Encode(encodableEntry)); + } + return CHIP_NO_ERROR; + }); +} + +CHIP_ERROR AccessControlAttribute::ReadArl(AttributeValueEncoder & aEncoder) +{ + auto accessRestriction = GetAccessControl().GetAccessRestriction(); + if (accessRestriction == nullptr) + { + return CHIP_ERROR_NOT_IMPLEMENTED; + } + + return aEncoder.EncodeList([&](const auto & encoder) -> CHIP_ERROR { + for (auto & info : Server::GetInstance().GetFabricTable()) + { + auto fabric = info.GetFabricIndex(); + AccessRestriction::EntryIterator begin; + AccessRestriction::EntryIterator end; + ReturnErrorOnFailure(accessRestriction->Entries(fabric, begin, end)); + + for (AccessRestriction::EntryIterator it = begin; it != end; ++it) + { + ArlStorage::EncodableEntry encodableEntry(*it); + ReturnErrorOnFailure(encoder.Encode(encodableEntry)); + } + } + return CHIP_NO_ERROR; + }); +} + +void AccessControlAttribute::OnEntryChanged(FabricIndex fabricIndex, size_t index, + Platform::SharedPtr<AccessRestriction::Entry> entry, + AccessRestriction::EntryListener::ChangeType changeType) +{ + CHIP_ERROR err; + ArlChangedEvent event{ .fabricIndex = fabricIndex }; + + EventNumber eventNumber; + SuccessOrExit(err = LogEvent(event, 0, eventNumber)); + + return; + +exit: + ChipLogError(DataManagement, "AccessControlCluster: restriction event failed %" CHIP_ERROR_FORMAT, err.Format()); +} + +void AccessControlAttribute::OnFabricRestrictionReviewUpdate(FabricIndex fabricIndex, uint64_t token, const char * instruction, + const char * redirectUrl) +{ + CHIP_ERROR err; + ArlReviewEvent event{ .token = token, .fabricIndex = fabricIndex }; + + if (instruction != nullptr) + { + event.instruction.SetNonNull(chip::CharSpan(instruction, strnlen(instruction, kMaxInstructionStringLength))); + } + + if (redirectUrl != nullptr) + { + event.redirectURL.SetNonNull(chip::CharSpan(redirectUrl, strnlen(redirectUrl, kMaxRedirectUrlStringLength))); + } + + EventNumber eventNumber; + SuccessOrExit(err = LogEvent(event, 0, eventNumber)); + + return; + +exit: + ChipLogError(DataManagement, "AccessControlCluster: review event failed %" CHIP_ERROR_FORMAT, err.Format()); +} +#endif + CHIP_ERROR ChipErrorToImErrorMap(CHIP_ERROR err) { // Map some common errors into an underlying IM error @@ -473,6 +601,16 @@ CHIP_ERROR AccessControlAttribute::Write(const ConcreteDataAttributePath & aPath return ChipErrorToImErrorMap(WriteImpl(aPath, aDecoder)); } +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS +void OnPlatformEventHandler(const DeviceLayer::ChipDeviceEvent * event, intptr_t arg) +{ + if (event->Type == DeviceLayer::DeviceEventType::kCommissioningComplete) + { + Access::GetAccessControl().GetAccessRestriction()->CreateFabricEntries(event->CommissioningComplete.fabricIndex); + } +} +#endif + } // namespace void MatterAccessControlPluginServerInitCallback() @@ -481,4 +619,70 @@ void MatterAccessControlPluginServerInitCallback() AttributeAccessInterfaceRegistry::Instance().Register(&sAttribute); GetAccessControl().AddEntryListener(sAttribute); + +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + auto accessRestriction = GetAccessControl().GetAccessRestriction(); + if (accessRestriction != nullptr) + { + accessRestriction->AddListener(sAttribute); + } + + DeviceLayer::PlatformMgrImpl().AddEventHandler(OnPlatformEventHandler); +#endif +} + +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS +bool emberAfAccessControlClusterReviewFabricRestrictionsCallback( + CommandHandler * commandObj, const ConcreteCommandPath & commandPath, + const Clusters::AccessControl::Commands::ReviewFabricRestrictions::DecodableType & commandData) +{ + if (commandPath.mEndpointId != 0) + { + ChipLogError(DataManagement, "AccessControlCluster: invalid endpoint in ReviewFabricRestrictions request"); + return true; + } + + uint64_t token; + std::vector<AccessRestriction::Entry> entries; + auto entryIter = commandData.arl.begin(); + while (entryIter.Next()) + { + AccessRestriction::Entry entry; + entry.fabricIndex = commandObj->GetAccessingFabricIndex(); + entry.endpointNumber = entryIter.GetValue().endpoint; + entry.clusterId = entryIter.GetValue().cluster; + + auto restrictionIter = entryIter.GetValue().restrictions.begin(); + while (restrictionIter.Next()) + { + AccessRestriction::Restriction restriction; + restriction.restrictionType = static_cast<AccessRestriction::Type>(restrictionIter.GetValue().type); + if (!restrictionIter.GetValue().id.IsNull()) + { + restriction.id.SetValue(restrictionIter.GetValue().id.Value()); + } + entry.restrictions.push_back(restriction); + } + + entries.push_back(entry); + } + + CHIP_ERROR err = GetAccessControl().GetAccessRestriction()->RequestFabricRestrictionReview( + commandObj->GetAccessingFabricIndex(), entries, token); + + if (err == CHIP_NO_ERROR) + { + Clusters::AccessControl::Commands::ReviewFabricRestrictionsResponse::Type response; + response.token = token; + commandObj->AddResponse(commandPath, response); + } + else + { + ChipLogError(DataManagement, "AccessControlCluster: restriction check failed: %" CHIP_ERROR_FORMAT, err.Format()); + + // return error to client? + } + + return true; } +#endif diff --git a/src/app/server/ArlStorage.cpp b/src/app/server/ArlStorage.cpp new file mode 100644 index 00000000000000..983f9a16ad8cd6 --- /dev/null +++ b/src/app/server/ArlStorage.cpp @@ -0,0 +1,158 @@ +/* + * + * Copyright (c) 2024 Project CHIP Authors + * + * 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 <app/server/ArlStorage.h> + +#include <lib/support/DefaultStorageKeyAllocator.h> + +using namespace chip; +using namespace chip::app; +using namespace chip::Access; + +using Entry = AccessRestriction::Entry; +using EntryListener = AccessRestriction::EntryListener; +using StagingRestrictionType = Clusters::AccessControl::AccessRestrictionTypeEnum; +using StagingRestriction = Clusters::AccessControl::Structs::AccessRestrictionStruct::Type; + +namespace { + +CHIP_ERROR Convert(StagingRestrictionType from, AccessRestriction::Type & to) +{ + switch (from) + { + case StagingRestrictionType::kAttributeAccessForbidden: + to = AccessRestriction::Type::kAttributeAccessForbidden; + break; + case StagingRestrictionType::kAttributeWriteForbidden: + to = AccessRestriction::Type::kAttributeWriteForbidden; + break; + case StagingRestrictionType::kCommandForbidden: + to = AccessRestriction::Type::kCommandForbidden; + break; + case StagingRestrictionType::kEventForbidden: + to = AccessRestriction::Type::kEventForbidden; + break; + default: + return CHIP_ERROR_INVALID_ARGUMENT; + } + return CHIP_NO_ERROR; +} + +CHIP_ERROR Convert(AccessRestriction::Type from, StagingRestrictionType & to) +{ + switch (from) + { + case AccessRestriction::Type::kAttributeAccessForbidden: + to = StagingRestrictionType::kAttributeAccessForbidden; + break; + case AccessRestriction::Type::kAttributeWriteForbidden: + to = StagingRestrictionType::kAttributeWriteForbidden; + break; + case AccessRestriction::Type::kCommandForbidden: + to = StagingRestrictionType::kCommandForbidden; + break; + case AccessRestriction::Type::kEventForbidden: + to = StagingRestrictionType::kEventForbidden; + break; + default: + return CHIP_ERROR_INVALID_ARGUMENT; + } + return CHIP_NO_ERROR; +} + +} // namespace + +namespace chip { +namespace app { + +CHIP_ERROR ArlStorage::DecodableEntry::Decode(TLV::TLVReader & reader) +{ + ReturnErrorOnFailure(mStagingEntry.Decode(reader)); + + mEntry.fabricIndex = mStagingEntry.GetFabricIndex(); + mEntry.endpointNumber = mStagingEntry.endpoint; + mEntry.clusterId = mStagingEntry.cluster; + + auto iterator = mStagingEntry.restrictions.begin(); + while (iterator.Next()) + { + auto & tmp = iterator.GetValue(); + AccessRestriction::Restriction restriction; + ReturnErrorOnFailure(Convert(tmp.type, restriction.restrictionType)); + + if (!tmp.id.IsNull()) + { + restriction.id.SetValue(tmp.id.Value()); + } + + mEntry.restrictions.push_back(restriction); + } + ReturnErrorOnFailure(iterator.GetStatus()); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR ArlStorage::EncodableEntry::EncodeForRead(TLV::TLVWriter & writer, TLV::Tag tag, FabricIndex fabric) const +{ + ReturnErrorOnFailure(Stage()); + ReturnErrorOnFailure(mStagingEntry.EncodeForRead(writer, tag, fabric)); + return CHIP_NO_ERROR; +} + +CHIP_ERROR ArlStorage::EncodableEntry::EncodeForWrite(TLV::TLVWriter & writer, TLV::Tag tag) const +{ + ReturnErrorOnFailure(Stage()); + ReturnErrorOnFailure(mStagingEntry.EncodeForWrite(writer, tag)); + return CHIP_NO_ERROR; +} + +CHIP_ERROR ArlStorage::EncodableEntry::Stage() const +{ + mStagingEntry.fabricIndex = mEntry->fabricIndex; + mStagingEntry.endpoint = mEntry->endpointNumber; + mStagingEntry.cluster = mEntry->clusterId; + + { + size_t count = mEntry->restrictions.size(); + if (count > 0 && count <= CHIP_CONFIG_ACCESS_RESTRICTION_MAX_RESTRICTIONS_PER_ENTRY) + { + for (size_t i = 0; i < count; i++) + { + auto restriction = mEntry->restrictions[i]; + StagingRestriction tmp; + ReturnErrorOnFailure(Convert(restriction.restrictionType, tmp.type)); + + if (restriction.id.HasValue()) + { + tmp.id.SetNonNull(restriction.id.Value()); + } + + mStagingRestrictions[i] = tmp; + } + mStagingEntry.restrictions = Span<StagingRestriction>(mStagingRestrictions, count); + } + else + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + } + + return CHIP_NO_ERROR; +} + +} // namespace app +} // namespace chip diff --git a/src/app/server/ArlStorage.h b/src/app/server/ArlStorage.h new file mode 100644 index 00000000000000..34fb616d226e4f --- /dev/null +++ b/src/app/server/ArlStorage.h @@ -0,0 +1,148 @@ +/* + * + * Copyright (c) 2024 Project CHIP Authors + * + * 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 <access/AccessRestriction.h> +#include <credentials/FabricTable.h> +#include <lib/core/CHIPPersistentStorageDelegate.h> + +#include <app-common/zap-generated/cluster-objects.h> + +namespace chip { +namespace app { + +/** + * Storage specifically for access restriction entries, which correspond to + * the ARL attribute of the access control cluster. + * + * An object of this class should be initialized directly after the access + * control module is initialized, as it will populate entries in the system + * module from storage, and also install a listener in the system module to + * keep storage up to date as entries change. + * + * This class also provides facilities for converting between access restriction + * entries (as used by the system module) and access restriction entries (as used + * by the generated cluster code). + */ +class ArlStorage +{ +public: + /** + * Used for decoding access restriction entries. + * + * Typically used temporarily on the stack to decode: + * - source: TLV + * - staging: generated cluster level code + * - destination: system level access restriction entry + */ + class DecodableEntry + { + using Entry = Access::AccessRestriction::Entry; + using StagingEntry = Clusters::AccessControl::Structs::AccessRestrictionEntryStruct::DecodableType; + + public: + DecodableEntry() = default; + + /** + * Reader decodes into a staging entry, which is then unstaged + * into a member entry. + */ + CHIP_ERROR Decode(TLV::TLVReader & reader); + + Entry & GetEntry() { return mEntry; } + + const Entry & GetEntry() const { return mEntry; } + + public: + static constexpr bool kIsFabricScoped = true; + + void SetFabricIndex(FabricIndex fabricIndex) { mEntry.fabricIndex = fabricIndex; } + + private: + Entry mEntry; + + StagingEntry mStagingEntry; + }; + + /** + * Used for encoding access restriction entries. + * + * Typically used temporarily on the stack to encode: + * - source: system level access restriction entry + * - staging: generated cluster level code + * - destination: TLV + */ + class EncodableEntry + { + using Entry = Access::AccessRestriction::Entry; + using StagingEntry = Clusters::AccessControl::Structs::AccessRestrictionEntryStruct::Type; + using StagingRestriction = Clusters::AccessControl::Structs::AccessRestrictionStruct::Type; + + public: + EncodableEntry(std::shared_ptr<Entry> entry) : mEntry(entry) {} + + /** + * Constructor-provided entry is staged into a staging entry, + * which is then encoded into a writer. + */ + CHIP_ERROR EncodeForRead(TLV::TLVWriter & writer, TLV::Tag tag, FabricIndex fabric) const; + + /** + * Constructor-provided entry is staged into a staging entry, + * which is then encoded into a writer. + */ + CHIP_ERROR EncodeForWrite(TLV::TLVWriter & writer, TLV::Tag tag) const; + + /** + * Constructor-provided entry is staged into a staging entry. + */ + CHIP_ERROR Stage() const; + + StagingEntry & GetStagingEntry() { return mStagingEntry; } + + const StagingEntry & GetStagingEntry() const { return mStagingEntry; } + + public: + static constexpr bool kIsFabricScoped = true; + + FabricIndex GetFabricIndex() const { return mEntry->fabricIndex; } + + private: + std::shared_ptr<Entry> mEntry; + + mutable StagingEntry mStagingEntry; + mutable StagingRestriction mStagingRestrictions[CHIP_CONFIG_ACCESS_RESTRICTION_MAX_RESTRICTIONS_PER_ENTRY]; + }; + + virtual ~ArlStorage() = default; + + /** + * Initialize should be called after chip::Access::AccessControl is initialized. + * + * Implementations should take this opportunity to populate AccessControl with ARL entries + * loaded from persistent storage. A half-open range of fabrics [first, last) is provided + * so this can be done on a per-fabric basis. + * + * Implementations should also install an entry change listener on AccessControl to maintain + * ARL entries in persistent storage as they are changed. + */ + virtual CHIP_ERROR Init(PersistentStorageDelegate & persistentStorage, ConstFabricIterator first, ConstFabricIterator last) = 0; +}; + +} // namespace app +} // namespace chip diff --git a/src/app/server/BUILD.gn b/src/app/server/BUILD.gn index 401356d7b753a4..1a82cd911f0c51 100644 --- a/src/app/server/BUILD.gn +++ b/src/app/server/BUILD.gn @@ -13,6 +13,7 @@ # limitations under the License. import("//build_overrides/chip.gni") +import("${chip_root}/src/access/access.gni") import("${chip_root}/src/app/common_flags.gni") import("${chip_root}/src/app/icd/icd.gni") @@ -79,4 +80,13 @@ static_library("server") { [ "${chip_root}/src/app/icd/server:default-check-in-back-off" ] } } + + if (chip_enable_access_restrictions) { + sources += [ + "ArlStorage.cpp", + "ArlStorage.h", + "DefaultArlStorage.cpp", + "DefaultArlStorage.h", + ] + } } diff --git a/src/app/server/DefaultArlStorage.cpp b/src/app/server/DefaultArlStorage.cpp new file mode 100644 index 00000000000000..b7ab1aff23e4f0 --- /dev/null +++ b/src/app/server/DefaultArlStorage.cpp @@ -0,0 +1,203 @@ +/* + * + * Copyright (c) 2024 Project CHIP Authors + * + * 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 <app/server/DefaultArlStorage.h> + +#include <access/AccessControl.h> +#include <lib/support/DefaultStorageKeyAllocator.h> + +using namespace chip; +using namespace chip::app; +using namespace chip::Access; + +using EncodableEntry = ArlStorage::EncodableEntry; +using Entry = AccessRestriction::Entry; +using EntryListener = AccessRestriction::EntryListener; +using StagingRestriction = Clusters::AccessControl::Structs::AccessRestrictionEntryStruct::Type; +using Restriction = AccessRestriction::Restriction; + +namespace { + +/* +Size calculation for TLV encoded entry. + +Because EncodeForWrite is used without an accessing fabric, the fabric index is +not encoded. However, let's assume it is. This yields 17 bytes total overhead, +but it's wise to add a few more for safety. + +Each restriction requires 11 bytes. + +DATA C T L V NOTES +structure (anonymous) 1 0x15 + field 0 endpoint 1 1 1 + field 1 cluster 1 1 4 + field 2 restrictions 1 1 + structure (anonymous) 1 per restriction + field 0 type 1 1 1 + field 1 id 1 1 4 + end structure 1 + end list 1 0x18 + field 254 fabric index 1 1 1 not written +end structure 1 0x18 +*/ + +constexpr int kEncodedEntryOverheadBytes = 17 + 8; +constexpr int kEncodedEntryRestrictionBytes = 11 * CHIP_CONFIG_ACCESS_RESTRICTION_MAX_RESTRICTIONS_PER_ENTRY; +constexpr int kEncodedEntryTotalBytes = kEncodedEntryOverheadBytes + kEncodedEntryRestrictionBytes; + +class : public EntryListener +{ +public: + void OnEntryChanged(FabricIndex fabricIndex, size_t index, SharedPtr<Entry> entry, ChangeType changeType) override + { + CHIP_ERROR err; + + uint8_t buffer[kEncodedEntryTotalBytes] = { 0 }; + + VerifyOrExit(mPersistentStorage != nullptr, err = CHIP_ERROR_INCORRECT_STATE); + + if (changeType == ChangeType::kRemoved) + { + // Shuffle down entries past index, then delete entry at last index. + while (true) + { + uint16_t size = static_cast<uint16_t>(sizeof(buffer)); + err = mPersistentStorage->SyncGetKeyValue( + DefaultStorageKeyAllocator::AccessControlArlEntry(fabricIndex, index + 1).KeyName(), buffer, size); + if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) + { + break; + } + SuccessOrExit(err); + SuccessOrExit(err = mPersistentStorage->SyncSetKeyValue( + DefaultStorageKeyAllocator::AccessControlArlEntry(fabricIndex, index).KeyName(), buffer, size)); + index++; + } + SuccessOrExit(err = mPersistentStorage->SyncDeleteKeyValue( + DefaultStorageKeyAllocator::AccessControlArlEntry(fabricIndex, index).KeyName())); + } + else + { + // Write added/updated entry at index. + VerifyOrExit(entry != nullptr, err = CHIP_ERROR_INCORRECT_STATE); + TLV::TLVWriter writer; + writer.Init(buffer); + EncodableEntry encodableEntry(entry); + SuccessOrExit(err = encodableEntry.EncodeForWrite(writer, TLV::AnonymousTag())); + SuccessOrExit(err = mPersistentStorage->SyncSetKeyValue( + DefaultStorageKeyAllocator::AccessControlArlEntry(fabricIndex, index).KeyName(), buffer, + static_cast<uint16_t>(writer.GetLengthWritten()))); + } + + return; + + exit: + ChipLogError(DataManagement, "DefaultArlStorage: failed %" CHIP_ERROR_FORMAT, err.Format()); + } + + void OnFabricRestrictionReviewUpdate(FabricIndex fabricIndex, uint64_t token, const char * instruction, + const char * redirectUrl) override + {} + + // Must initialize before use. + void Init(PersistentStorageDelegate & persistentStorage) { mPersistentStorage = &persistentStorage; } + +private: + PersistentStorageDelegate * mPersistentStorage = nullptr; + +} sEntryListener; + +} // namespace + +namespace chip { +namespace app { + +CHIP_ERROR DefaultArlStorage::Init(PersistentStorageDelegate & persistentStorage, ConstFabricIterator first, + ConstFabricIterator last) +{ + ChipLogProgress(DataManagement, "DefaultArlStorage: initializing"); + + CHIP_ERROR err; + + [[maybe_unused]] size_t count = 0; + + for (size_t index = 0; /**/; ++index) + { + uint8_t buffer[kEncodedEntryTotalBytes] = { 0 }; + uint16_t size = static_cast<uint16_t>(sizeof(buffer)); + err = persistentStorage.SyncGetKeyValue(DefaultStorageKeyAllocator::AccessControlCommissioningArlEntry(index).KeyName(), + buffer, size); + if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) + { + break; + } + SuccessOrExit(err); + + TLV::TLVReader reader; + reader.Init(buffer, size); + SuccessOrExit(err = reader.Next()); + + DecodableEntry decodableEntry; + SuccessOrExit(err = decodableEntry.Decode(reader)); + + SuccessOrExit(err = GetAccessControl().GetAccessRestriction()->CreateCommissioningEntry( + Platform::MakeShared<Entry>(decodableEntry.GetEntry()))); + count++; + } + + for (auto it = first; it != last; ++it) + { + auto fabric = it->GetFabricIndex(); + for (size_t index = 0; /**/; ++index) + { + uint8_t buffer[kEncodedEntryTotalBytes] = { 0 }; + uint16_t size = static_cast<uint16_t>(sizeof(buffer)); + err = persistentStorage.SyncGetKeyValue(DefaultStorageKeyAllocator::AccessControlArlEntry(fabric, index).KeyName(), + buffer, size); + if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) + { + break; + } + SuccessOrExit(err); + + TLV::TLVReader reader; + reader.Init(buffer, size); + SuccessOrExit(err = reader.Next()); + + DecodableEntry decodableEntry; + SuccessOrExit(err = decodableEntry.Decode(reader)); + + Entry & entry = decodableEntry.GetEntry(); + SuccessOrExit(err = GetAccessControl().GetAccessRestriction()->CreateEntry(nullptr, entry, fabric)); + count++; + } + } + + ChipLogProgress(DataManagement, "DefaultArlStorage: %u entries loaded", (unsigned) count); + + sEntryListener.Init(persistentStorage); + GetAccessControl().GetAccessRestriction()->AddListener(sEntryListener); + + return CHIP_NO_ERROR; + +exit: + ChipLogError(DataManagement, "DefaultArlStorage: failed %" CHIP_ERROR_FORMAT, err.Format()); + return err; +} + +} // namespace app +} // namespace chip diff --git a/src/app/server/DefaultArlStorage.h b/src/app/server/DefaultArlStorage.h new file mode 100644 index 00000000000000..ebdc23bbf65913 --- /dev/null +++ b/src/app/server/DefaultArlStorage.h @@ -0,0 +1,37 @@ +/* + * + * Copyright (c) 2024 Project CHIP Authors + * + * 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 <app/server/ArlStorage.h> + +namespace chip { +namespace app { + +class DefaultArlStorage : public ArlStorage +{ +public: + /** + * Initialize must be called. It loads ARL entries for all fabrics from persistent storage, + * then installs a listener for the access restriction system module to maintain ARL entries in + * persistent storage so they remain in sync with entries in the access restriction system module. + */ + CHIP_ERROR Init(PersistentStorageDelegate & persistentStorage, ConstFabricIterator first, ConstFabricIterator last) override; +}; + +} // namespace app +} // namespace chip diff --git a/src/app/server/Server.cpp b/src/app/server/Server.cpp index 22cd274ba87e39..fbdae0ccb213f8 100644 --- a/src/app/server/Server.cpp +++ b/src/app/server/Server.cpp @@ -180,6 +180,17 @@ CHIP_ERROR Server::Init(const ServerInitParams & initParams) mAclStorage = initParams.aclStorage; SuccessOrExit(err = mAclStorage->Init(*mDeviceStorage, mFabrics.begin(), mFabrics.end())); +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + if (initParams.accessRestriction != nullptr && initParams.arlStorage != nullptr) + { + mAccessRestriction = initParams.accessRestriction; + mArlStorage = initParams.arlStorage; + + mAccessControl.SetAccessRestriction(mAccessRestriction); + SuccessOrExit(err = mArlStorage->Init(*mDeviceStorage, mFabrics.begin(), mFabrics.end())); + } +#endif + mGroupsProvider = initParams.groupDataProvider; SetGroupDataProvider(mGroupsProvider); diff --git a/src/app/server/Server.h b/src/app/server/Server.h index 2f6126a4ace635..b8f07e526f80d4 100644 --- a/src/app/server/Server.h +++ b/src/app/server/Server.h @@ -31,6 +31,9 @@ #include <app/TestEventTriggerDelegate.h> #include <app/server/AclStorage.h> #include <app/server/AppDelegate.h> +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS +#include <app/server/ArlStorage.h> +#endif #include <app/server/CommissioningWindowManager.h> #include <app/server/DefaultAclStorage.h> #include <credentials/CertificateValidityPolicy.h> @@ -163,6 +166,16 @@ struct ServerInitParams // ACL storage: MUST be injected. Used to store ACL entries in persistent storage. Must NOT // be initialized before being provided. app::AclStorage * aclStorage = nullptr; + +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + // Access Restriction implementation: MUST be injected if MNGD feature enabled. Used to enforce + // access restrictions that are managed by the device. + Access::AccessRestriction * accessRestriction = nullptr; + // ARL storage: MUST be injected if MNGD feature enabled. Used to store ACL entries in + // persistent storage. Must NOT be initialized before being provided. + app::ArlStorage * arlStorage = nullptr; +#endif + // Network native params can be injected depending on the // selected Endpoint implementation void * endpointNativeParams = nullptr; @@ -679,6 +692,11 @@ class Server Access::AccessControl mAccessControl; app::AclStorage * mAclStorage; +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + Access::AccessRestriction * mAccessRestriction; + app::ArlStorage * mArlStorage; +#endif + TestEventTriggerDelegate * mTestEventTriggerDelegate; Crypto::OperationalKeystore * mOperationalKeystore; Credentials::OperationalCertificateStore * mOpCertStore; diff --git a/src/lib/core/CHIPConfig.h b/src/lib/core/CHIPConfig.h index e9c317dfc79d5c..a811f8b6012394 100644 --- a/src/lib/core/CHIPConfig.h +++ b/src/lib/core/CHIPConfig.h @@ -1206,6 +1206,27 @@ extern const char CHIP_NON_PRODUCTION_MARKER[]; "Please enable at least one of CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_FAST_COPY_SUPPORT or CHIP_CONFIG_EXAMPLE_ACCESS_CONTROL_FLEXIBLE_COPY_SUPPORT" #endif +/** + * @def CHIP_CONFIG_ACCESS_RESTRICTION_MAX_ENTRIES_PER_FABRIC + * + * Defines the maximum number of access restriction list entries per + * fabric in the access control code's ARL attribute. + */ +#ifndef CHIP_CONFIG_ACCESS_RESTRICTION_MAX_ENTRIES_PER_FABRIC +#define CHIP_CONFIG_ACCESS_RESTRICTION_MAX_ENTRIES_PER_FABRIC 10 +#endif + +/** + * @def CHIP_CONFIG_ACCESS_RESTRICTION_MAX_RESTRICTIONS_PER_ENTRY + * + * Defines the maximum number of access restrictions for each entry + * in the ARL attribute (each entry is for a specific cluster on an + * endpoint on a fabric). + */ +#ifndef CHIP_CONFIG_ACCESS_RESTRICTION_MAX_RESTRICTIONS_PER_ENTRY +#define CHIP_CONFIG_ACCESS_RESTRICTION_MAX_RESTRICTIONS_PER_ENTRY 10 +#endif + /** * @def CHIP_CONFIG_CASE_SESSION_RESUME_CACHE_SIZE * diff --git a/src/lib/support/DefaultStorageKeyAllocator.h b/src/lib/support/DefaultStorageKeyAllocator.h index 9ed8a2f56cfd77..dce9975cd35f2b 100644 --- a/src/lib/support/DefaultStorageKeyAllocator.h +++ b/src/lib/support/DefaultStorageKeyAllocator.h @@ -130,6 +130,16 @@ class DefaultStorageKeyAllocator static StorageKeyName AccessControlExtensionEntry(FabricIndex fabric) { return StorageKeyName::Formatted("f/%x/ac/1", fabric); } + static StorageKeyName AccessControlCommissioningArlEntry(size_t index) + { + return StorageKeyName::Formatted("g/car/0/%x", static_cast<unsigned>(index)); + } + + static StorageKeyName AccessControlArlEntry(FabricIndex fabric, size_t index) + { + return StorageKeyName::Formatted("f/%x/ar/0/%x", fabric, static_cast<unsigned>(index)); + } + // Group Message Counters static StorageKeyName GroupDataCounter() { return StorageKeyName::FromConst("g/gdc"); } static StorageKeyName GroupControlCounter() { return StorageKeyName::FromConst("g/gcc"); } From 20f0b6a9f729c057239f136d7cc6831044b3ca8c Mon Sep 17 00:00:00 2001 From: Thomas Lea <35579828+tleacmcsa@users.noreply.github.com> Date: Mon, 19 Aug 2024 09:50:36 -0500 Subject: [PATCH 02/22] Update src/access/AccessConfig.h Co-authored-by: C Freeman <cecille@google.com> --- src/access/AccessConfig.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/access/AccessConfig.h b/src/access/AccessConfig.h index c4cb6f51f3114d..b9318a10d9e4d5 100644 --- a/src/access/AccessConfig.h +++ b/src/access/AccessConfig.h @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2020-2024 Project CHIP Authors + * Copyright (c) 2024 Project CHIP Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From 04651f9d6d08202d77eedb221b6b212f3fc51201 Mon Sep 17 00:00:00 2001 From: Thomas Lea <thomas_lea@comcast.com> Date: Tue, 20 Aug 2024 22:22:18 +0000 Subject: [PATCH 03/22] Reworked data manipulators and other cleanup --- examples/network-manager-app/linux/args.gni | 2 + examples/platform/linux/AppMain.cpp | 14 +- ...n.h => ExampleAccessRestrictionProvider.h} | 11 +- examples/platform/linux/Options.cpp | 17 +- examples/platform/linux/Options.h | 4 +- src/access/AccessControl.cpp | 6 +- src/access/AccessControl.h | 13 +- src/access/AccessRestriction.cpp | 280 ------------------ src/access/AccessRestrictionProvider.cpp | 193 ++++++++++++ ...triction.h => AccessRestrictionProvider.h} | 151 +++------- src/access/BUILD.gn | 4 +- src/access/tests/BUILD.gn | 2 +- ....cpp => TestAccessRestrictionProvider.cpp} | 108 ++++--- .../access-control-server.cpp | 77 +++-- src/app/server/ArlStorage.cpp | 36 +-- src/app/server/ArlStorage.h | 12 +- src/app/server/DefaultArlStorage.cpp | 98 +++--- src/app/server/Server.cpp | 8 +- src/app/server/Server.h | 4 +- 19 files changed, 469 insertions(+), 571 deletions(-) rename examples/platform/linux/{ExampleAccessRestriction.h => ExampleAccessRestrictionProvider.h} (80%) delete mode 100644 src/access/AccessRestriction.cpp create mode 100644 src/access/AccessRestrictionProvider.cpp rename src/access/{AccessRestriction.h => AccessRestrictionProvider.h} (52%) rename src/access/tests/{TestAccessRestriction.cpp => TestAccessRestrictionProvider.cpp} (87%) diff --git a/examples/network-manager-app/linux/args.gni b/examples/network-manager-app/linux/args.gni index 97661ce32a8bf7..e97ddb13e7c46e 100644 --- a/examples/network-manager-app/linux/args.gni +++ b/examples/network-manager-app/linux/args.gni @@ -22,4 +22,6 @@ chip_project_config_include_dirs = [ ] chip_config_network_layer_ble = false + +# This enables AccessRestrictionList (ARL) support used by the NIM sample app chip_enable_access_restrictions = true diff --git a/examples/platform/linux/AppMain.cpp b/examples/platform/linux/AppMain.cpp index 147a970124c0f2..df7fa6081331d4 100644 --- a/examples/platform/linux/AppMain.cpp +++ b/examples/platform/linux/AppMain.cpp @@ -104,7 +104,7 @@ #include "CommissionableInit.h" #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS -#include "ExampleAccessRestriction.h" +#include "ExampleAccessRestrictionProvider.h" #include <app/server/DefaultArlStorage.h> #endif @@ -602,12 +602,12 @@ void ChipLinuxAppMainLoop(AppMainLoopImplementation * impl) #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS if (LinuxDeviceOptions::GetInstance().accessRestrictionEntries.HasValue()) { - initParams.accessRestriction = new ExampleAccessRestriction(); - initParams.arlStorage = new app::DefaultArlStorage(); - for (const auto & entry : LinuxDeviceOptions::GetInstance().accessRestrictionEntries.Value()) - { - VerifyOrDie(AccessRestriction::CreateCommissioningEntry(entry) == CHIP_NO_ERROR); - } + auto exampleAccessRestrictionProvider = new ExampleAccessRestrictionProvider(); + exampleAccessRestrictionProvider->SetCommissioningEntries( + LinuxDeviceOptions::GetInstance().accessRestrictionEntries.Value()); + + initParams.accessRestrictionProvider = exampleAccessRestrictionProvider; + initParams.arlStorage = new app::DefaultArlStorage(); } #endif diff --git a/examples/platform/linux/ExampleAccessRestriction.h b/examples/platform/linux/ExampleAccessRestrictionProvider.h similarity index 80% rename from examples/platform/linux/ExampleAccessRestriction.h rename to examples/platform/linux/ExampleAccessRestrictionProvider.h index 0262e42bab2d57..28ca38a327a555 100644 --- a/examples/platform/linux/ExampleAccessRestriction.h +++ b/examples/platform/linux/ExampleAccessRestrictionProvider.h @@ -22,26 +22,25 @@ #pragma once -#include <access/AccessRestriction.h> +#include <access/AccessRestrictionProvider.h> #include <app-common/zap-generated/cluster-objects.h> #include <app/EventLogging.h> namespace chip { namespace Access { -class ExampleAccessRestriction : public AccessRestriction +class ExampleAccessRestrictionProvider : public AccessRestrictionProvider { public: - ExampleAccessRestriction() : AccessRestriction() {} + ExampleAccessRestrictionProvider() : AccessRestrictionProvider() {} - ~ExampleAccessRestriction() {} + ~ExampleAccessRestrictionProvider() {} protected: CHIP_ERROR DoRequestFabricRestrictionReview(const FabricIndex fabricIndex, uint64_t token, const std::vector<Entry> & arl) { // this example simply removes all restrictions and will generate AccessRestrictionEntryChanged events - while (Access::GetAccessControl().GetAccessRestriction()->DeleteEntry(0, fabricIndex) == CHIP_NO_ERROR) - ; + Access::GetAccessControl().GetAccessRestrictionProvider()->SetEntries(fabricIndex, std::vector<Entry>{}); chip::app::Clusters::AccessControl::Events::FabricRestrictionReviewUpdate::Type event{ .fabricIndex = fabricIndex }; EventNumber eventNumber; diff --git a/examples/platform/linux/Options.cpp b/examples/platform/linux/Options.cpp index 85da5f07397d4d..abea582d1f1b43 100644 --- a/examples/platform/linux/Options.cpp +++ b/examples/platform/linux/Options.cpp @@ -338,8 +338,7 @@ const char * sDeviceOptionHelp = "\n"; #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS -bool ParseAccessRestrictionEntriesFromJson(const char * jsonString, - std::vector<Platform::SharedPtr<AccessRestriction::Entry>> & entries) +bool ParseAccessRestrictionEntriesFromJson(const char * jsonString, std::vector<AccessRestrictionProvider::Entry> & entries) { Json::Value root; Json::Reader reader; @@ -347,21 +346,21 @@ bool ParseAccessRestrictionEntriesFromJson(const char * jsonString, for (Json::Value::const_iterator eIt = root.begin(); eIt != root.end(); eIt++) { - auto entry = MakeShared<AccessRestriction::Entry>(); + AccessRestrictionProvider::Entry entry; - entry->endpointNumber = static_cast<EndpointId>((*eIt)["endpoint"].asUInt()); - entry->clusterId = static_cast<ClusterId>((*eIt)["cluster"].asUInt()); + entry.endpointNumber = static_cast<EndpointId>((*eIt)["endpoint"].asUInt()); + entry.clusterId = static_cast<ClusterId>((*eIt)["cluster"].asUInt()); Json::Value restrictions = (*eIt)["restrictions"]; for (Json::Value::const_iterator rIt = restrictions.begin(); rIt != restrictions.end(); rIt++) { - AccessRestriction::Restriction restriction; - restriction.restrictionType = static_cast<AccessRestriction::Type>((*rIt)["type"].asInt()); + AccessRestrictionProvider::Restriction restriction; + restriction.restrictionType = static_cast<AccessRestrictionProvider::Type>((*rIt)["type"].asInt()); if ((*rIt).isMember("id")) { restriction.id.SetValue((*rIt)["id"].asUInt()); } - entry->restrictions.push_back(restriction); + entry.restrictions.push_back(restriction); } entries.push_back(entry); @@ -582,7 +581,7 @@ bool HandleOption(const char * aProgram, OptionSet * aOptions, int aIdentifier, #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS case kDeviceOption_UseAccessRestrictions: { - std::vector<Platform::SharedPtr<AccessRestriction::Entry>> accessRestrictionEntries; + std::vector<AccessRestrictionProvider::Entry> accessRestrictionEntries; retval = ParseAccessRestrictionEntriesFromJson(aValue, accessRestrictionEntries); if (retval) { diff --git a/examples/platform/linux/Options.h b/examples/platform/linux/Options.h index 895037664232c3..8fdacd8b64ea5c 100644 --- a/examples/platform/linux/Options.h +++ b/examples/platform/linux/Options.h @@ -40,7 +40,7 @@ #include <testing/CustomCSRResponse.h> #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS -#include <access/AccessRestriction.h> +#include <access/AccessRestrictionProvider.h> #endif struct LinuxDeviceOptions @@ -88,7 +88,7 @@ struct LinuxDeviceOptions int32_t subscriptionResumptionRetryIntervalSec = -1; #endif #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS - chip::Optional<std::vector<chip::Platform::SharedPtr<chip::Access::AccessRestriction::Entry>>> accessRestrictionEntries; + chip::Optional<std::vector<chip::Access::AccessRestrictionProvider::Entry>> accessRestrictionEntries; #endif static LinuxDeviceOptions & GetInstance(); }; diff --git a/src/access/AccessControl.cpp b/src/access/AccessControl.cpp index 6d519f9a227adf..d7d597f46b2665 100644 --- a/src/access/AccessControl.cpp +++ b/src/access/AccessControl.cpp @@ -326,7 +326,7 @@ void AccessControl::RemoveEntryListener(EntryListener & listener) bool AccessControl::IsAccessRestrictionListSupported() const { #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS - return mAccessRestriction != nullptr; + return mAccessRestrictionProvider != nullptr; #else return false; #endif @@ -357,9 +357,9 @@ CHIP_ERROR AccessControl::Check(const SubjectDescriptor & subjectDescriptor, con } #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS - if (mAccessRestriction != nullptr) + if (mAccessRestrictionProvider != nullptr) { - CHIP_ERROR result = mAccessRestriction->Check(subjectDescriptor, requestPath); + CHIP_ERROR result = mAccessRestrictionProvider->Check(subjectDescriptor, requestPath); if (result != CHIP_NO_ERROR) { ChipLogProgress(DataManagement, "AccessControl: %s", diff --git a/src/access/AccessControl.h b/src/access/AccessControl.h index 6f2f29803f9cdf..855efa04346a3a 100644 --- a/src/access/AccessControl.h +++ b/src/access/AccessControl.h @@ -21,7 +21,7 @@ #include <access/AccessConfig.h> #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS -#include "AccessRestriction.h" +#include "AccessRestrictionProvider.h" #endif #include "Privilege.h" @@ -635,9 +635,12 @@ class AccessControl #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS // Set an optional AcceessRestriction object for MNGD feature. - void SetAccessRestriction(AccessRestriction * accessRestriction) { mAccessRestriction = accessRestriction; } + void SetAccessRestrictionProvider(AccessRestrictionProvider * accessRestrictionProvider) + { + mAccessRestrictionProvider = accessRestrictionProvider; + } - AccessRestriction * GetAccessRestriction() { return mAccessRestriction; } + AccessRestrictionProvider * GetAccessRestrictionProvider() { return mAccessRestrictionProvider; } #endif /** @@ -651,7 +654,7 @@ class AccessControl * Check whether access (by a subject descriptor, to a request path, * requiring a privilege) should be allowed or denied. * - * If an AccessRestriction object is set, it will be checked for additional access restrictions. + * If an AccessRestrictionProvider object is set, it will be checked for additional access restrictions. * * @retval #CHIP_ERROR_ACCESS_DENIED if denied. * @retval other errors should also be treated as denied. @@ -679,7 +682,7 @@ class AccessControl EntryListener * mEntryListener = nullptr; #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS - AccessRestriction * mAccessRestriction; + AccessRestrictionProvider * mAccessRestrictionProvider; #endif }; diff --git a/src/access/AccessRestriction.cpp b/src/access/AccessRestriction.cpp deleted file mode 100644 index cadb70a0ba0088..00000000000000 --- a/src/access/AccessRestriction.cpp +++ /dev/null @@ -1,280 +0,0 @@ -/* - * - * Copyright (c) 2024 Project CHIP Authors - * All rights reserved. - * - * 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 "AccessRestriction.h" - -#include <algorithm> -#include <lib/core/Global.h> - -using namespace chip::Platform; - -namespace chip { -namespace Access { - -std::vector<SharedPtr<AccessRestriction::Entry>> AccessRestriction::sCommissioningEntries; - -CHIP_ERROR AccessRestriction::CreateFabricEntries(const FabricIndex fabricIndex) -{ - for (auto & entry : sCommissioningEntries) - { - CreateEntry(nullptr, *entry, fabricIndex); - } - - return CHIP_NO_ERROR; -} - -void AccessRestriction::AddListener(EntryListener & listener) -{ - if (mListeners == nullptr) - { - mListeners = &listener; - listener.mNext = nullptr; - return; - } - - for (EntryListener * l = mListeners; /**/; l = l->mNext) - { - if (l == &listener) - { - return; - } - - if (l->mNext == nullptr) - { - l->mNext = &listener; - listener.mNext = nullptr; - return; - } - } -} - -void AccessRestriction::RemoveListener(EntryListener & listener) -{ - if (mListeners == &listener) - { - mListeners = listener.mNext; - listener.mNext = nullptr; - return; - } - - for (EntryListener * l = mListeners; l != nullptr; l = l->mNext) - { - if (l->mNext == &listener) - { - l->mNext = listener.mNext; - listener.mNext = nullptr; - return; - } - } -} - -SharedPtr<AccessRestriction::Entry> AccessRestriction::GetEntry(FabricIndex fabricIndex, size_t index) -{ - if (mFabricEntries.find(fabricIndex) != mFabricEntries.end() && index < mFabricEntries[fabricIndex].size()) - { - return mFabricEntries[fabricIndex][index]; - } - - return nullptr; -} - -CHIP_ERROR AccessRestriction::CreateCommissioningEntry(SharedPtr<Entry> entry) -{ - if (!entry->IsValid()) - { - ChipLogError(DataManagement, "AccessRestriction: invalid restriction entry"); - return CHIP_ERROR_INVALID_ARGUMENT; - } - - sCommissioningEntries.push_back(entry); - return CHIP_NO_ERROR; -} - -CHIP_ERROR AccessRestriction::CreateEntry(size_t * index, const Entry & entry, FabricIndex fabricIndex) -{ - if (!entry.IsValid()) - { - ChipLogError(DataManagement, "AccessRestriction: invalid restriction entry"); - return CHIP_ERROR_INVALID_ARGUMENT; - } - - auto localEntry = MakeShared<Entry>(entry); - size_t newIndex; - - localEntry->fabricIndex = fabricIndex; - - if (mFabricEntries.find(fabricIndex) == mFabricEntries.end()) - { - mFabricEntries[fabricIndex] = std::vector<SharedPtr<Entry>>(); - mFabricEntries[fabricIndex].push_back(localEntry); - } - else - { - mFabricEntries[fabricIndex].push_back(localEntry); - } - - newIndex = mFabricEntries[fabricIndex].size() - 1; - - for (EntryListener * listener = mListeners; listener != nullptr; listener = listener->mNext) - { - listener->OnEntryChanged(fabricIndex, newIndex, localEntry, EntryListener::ChangeType::kAdded); - } - - if (index != nullptr) - { - *index = newIndex; - } - - ChipLogProgress(DataManagement, "AccessRestriction: update entry f=%u i=%lu", fabricIndex, newIndex); - - return CHIP_NO_ERROR; -} - -CHIP_ERROR AccessRestriction::DeleteEntry(size_t index, const FabricIndex fabricIndex) -{ - ChipLogProgress(DataManagement, "AccessRestriction: delete entry f=%u i=%lu", fabricIndex, index); - - auto entry = GetEntry(fabricIndex, index); - if (entry == nullptr) - { - return CHIP_ERROR_NOT_FOUND; - } - else - { - mFabricEntries[fabricIndex].erase(mFabricEntries[fabricIndex].begin() + static_cast<int>(index)); - - for (EntryListener * listener = mListeners; listener != nullptr; listener = listener->mNext) - { - listener->OnEntryChanged(fabricIndex, index, entry, EntryListener::ChangeType::kRemoved); - } - } - - return CHIP_NO_ERROR; -} - -CHIP_ERROR AccessRestriction::UpdateEntry(size_t index, const Entry & entry, const FabricIndex fabricIndex) -{ - ChipLogProgress(DataManagement, "AccessRestriction: update entry f=%u i=%lu", fabricIndex, index); - - auto localEntry = GetEntry(fabricIndex, index); - if (localEntry != nullptr) - { - *localEntry = entry; - - for (EntryListener * listener = mListeners; listener != nullptr; listener = listener->mNext) - { - listener->OnEntryChanged(fabricIndex, index, localEntry, EntryListener::ChangeType::kUpdated); - } - - return CHIP_NO_ERROR; - } - - return CHIP_ERROR_NOT_FOUND; -} - -CHIP_ERROR AccessRestriction::Check(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath) -{ - ChipLogProgress(DataManagement, "AccessRestriction: action %d", static_cast<int>(requestPath.requestType)); - - if (requestPath.requestType == RequestType::kRequestTypeUnknown) - { - ChipLogError(DataManagement, "AccessRestriction: RequestPath type is unknown"); - return CHIP_ERROR_INVALID_ARGUMENT; - } - - // wildcard event subscriptions are allowed since wildcard is only used when setting up the subscription and - // we want that request to succeed (when generating the report, this method will be called with the specific - // event id). All other requests require an entity id - if (!requestPath.entityId.has_value()) - { - if (requestPath.requestType == RequestType::kEventReadOrSubscribeRequest) - { - return CHIP_NO_ERROR; - } - else - { - return CHIP_ERROR_INVALID_ARGUMENT; - } - } - - for (auto & entry : mFabricEntries[subjectDescriptor.fabricIndex]) - { - if (entry->endpointNumber != requestPath.endpoint || entry->clusterId != requestPath.cluster) - { - continue; - } - - for (auto & restriction : entry->restrictions) - { - // a missing id is a wildcard - bool idMatch = !restriction.id.HasValue() || restriction.id.Value() == requestPath.entityId.value(); - if (!idMatch) - { - continue; - } - - switch (restriction.restrictionType) - { - case Type::kAttributeAccessForbidden: - if (requestPath.requestType == RequestType::kAttributeReadRequest || - requestPath.requestType == RequestType::kAttributeWriteRequest) - { - return CHIP_ERROR_ACCESS_DENIED; - } - break; - case Type::kAttributeWriteForbidden: - if (requestPath.requestType == RequestType::kAttributeWriteRequest) - { - return CHIP_ERROR_ACCESS_DENIED; - } - break; - case Type::kCommandForbidden: - if (requestPath.requestType == RequestType::kCommandInvokeRequest) - { - return CHIP_ERROR_ACCESS_DENIED; - } - break; - case Type::kEventForbidden: - if (requestPath.requestType == RequestType::kEventReadOrSubscribeRequest) - { - return CHIP_ERROR_ACCESS_DENIED; - } - break; - } - } - } - - return CHIP_NO_ERROR; -} - -void AccessRestriction::ClearData() -{ - // iterate over mFabricEntries and call DeleteEntry for each entry - for (auto & fabricEntry : mFabricEntries) - { - for (size_t i = 0; i < fabricEntry.second.size(); i++) - { - DeleteEntry(i, fabricEntry.first); - } - } - - sCommissioningEntries.clear(); -} - -} // namespace Access -} // namespace chip diff --git a/src/access/AccessRestrictionProvider.cpp b/src/access/AccessRestrictionProvider.cpp new file mode 100644 index 00000000000000..0ef962ca9f939f --- /dev/null +++ b/src/access/AccessRestrictionProvider.cpp @@ -0,0 +1,193 @@ +/* + * + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * 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 "AccessRestrictionProvider.h" + +#include <algorithm> +#include <lib/core/Global.h> + +using namespace chip::Platform; + +namespace chip { +namespace Access { + +void AccessRestrictionProvider::AddListener(Listener & listener) +{ + if (mListeners == nullptr) + { + mListeners = &listener; + listener.mNext = nullptr; + return; + } + + for (Listener * l = mListeners; /**/; l = l->mNext) + { + if (l == &listener) + { + return; + } + + if (l->mNext == nullptr) + { + l->mNext = &listener; + listener.mNext = nullptr; + return; + } + } +} + +void AccessRestrictionProvider::RemoveListener(Listener & listener) +{ + if (mListeners == &listener) + { + mListeners = listener.mNext; + listener.mNext = nullptr; + return; + } + + for (Listener * l = mListeners; l != nullptr; l = l->mNext) + { + if (l->mNext == &listener) + { + l->mNext = listener.mNext; + listener.mNext = nullptr; + return; + } + } +} + +CHIP_ERROR AccessRestrictionProvider::SetCommissioningEntries(const std::vector<Entry> & entries) +{ + // check that the input entries are valid + for (auto & entry : entries) + { + if (!IsEntryValid(entry)) + { + ChipLogError(DataManagement, "AccessRestrictionProvider: invalid entry"); + return CHIP_ERROR_INVALID_ARGUMENT; + } + } + + mCommissioningEntries = entries; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR AccessRestrictionProvider::SetEntries(const FabricIndex fabricIndex, const std::vector<Entry> & entries) +{ + // check that the input entries are valid + for (auto & entry : entries) + { + if (!IsEntryValid(entry)) + { + ChipLogError(DataManagement, "AccessRestrictionProvider: invalid entry"); + return CHIP_ERROR_INVALID_ARGUMENT; + } + } + + for (Listener * listener = mListeners; listener != nullptr; listener = listener->mNext) + { + listener->RestrictionListChanged(fabricIndex); + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR AccessRestrictionProvider::Check(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath) +{ + ChipLogProgress(DataManagement, "AccessRestrictionProvider: action %d", static_cast<int>(requestPath.requestType)); + + if (requestPath.requestType == RequestType::kRequestTypeUnknown) + { + ChipLogError(DataManagement, "AccessRestrictionProvider: RequestPath type is unknown"); + return CHIP_ERROR_INVALID_ARGUMENT; + } + + // wildcard event subscriptions are allowed since wildcard is only used when setting up the subscription and + // we want that request to succeed (when generating the report, this method will be called with the specific + // event id). All other requests require an entity id + if (!requestPath.entityId.has_value()) + { + if (requestPath.requestType == RequestType::kEventReadOrSubscribeRequest) + { + return CHIP_NO_ERROR; + } + else + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + } + + for (auto & entry : mFabricEntries[subjectDescriptor.fabricIndex]) + { + if (entry.endpointNumber != requestPath.endpoint || entry.clusterId != requestPath.cluster) + { + continue; + } + + for (auto & restriction : entry.restrictions) + { + // a missing id is a wildcard + bool idMatch = !restriction.id.HasValue() || restriction.id.Value() == requestPath.entityId.value(); + if (!idMatch) + { + continue; + } + + switch (restriction.restrictionType) + { + case Type::kAttributeAccessForbidden: + if (requestPath.requestType == RequestType::kAttributeReadRequest || + requestPath.requestType == RequestType::kAttributeWriteRequest) + { + return CHIP_ERROR_ACCESS_DENIED; + } + break; + case Type::kAttributeWriteForbidden: + if (requestPath.requestType == RequestType::kAttributeWriteRequest) + { + return CHIP_ERROR_ACCESS_DENIED; + } + break; + case Type::kCommandForbidden: + if (requestPath.requestType == RequestType::kCommandInvokeRequest) + { + return CHIP_ERROR_ACCESS_DENIED; + } + break; + case Type::kEventForbidden: + if (requestPath.requestType == RequestType::kEventReadOrSubscribeRequest) + { + return CHIP_ERROR_ACCESS_DENIED; + } + break; + } + } + } + + return CHIP_NO_ERROR; +} + +bool AccessRestrictionProvider::IsEntryValid(const Entry & entry) const +{ + return entry.endpointNumber != 0 && entry.clusterId != app::Clusters::NetworkCommissioning::Id && + entry.clusterId != app::Clusters::Descriptor::Id; +} + +} // namespace Access +} // namespace chip diff --git a/src/access/AccessRestriction.h b/src/access/AccessRestrictionProvider.h similarity index 52% rename from src/access/AccessRestriction.h rename to src/access/AccessRestrictionProvider.h index 34d1f27562767b..9178d8685a42f0 100644 --- a/src/access/AccessRestriction.h +++ b/src/access/AccessRestrictionProvider.h @@ -35,10 +35,7 @@ namespace chip { namespace Access { -template <typename T> -using SharedPtr = chip::Platform::SharedPtr<T>; - -class AccessRestriction +class AccessRestrictionProvider { public: static constexpr size_t kNumberOfFabrics = CHIP_CONFIG_MAX_FABRICS; @@ -77,38 +74,27 @@ class AccessRestriction EndpointId endpointNumber; ClusterId clusterId; std::vector<Restriction> restrictions; - - bool IsValid() const - { - return endpointNumber != 0 && clusterId != app::Clusters::NetworkCommissioning::Id && - clusterId != app::Clusters::Descriptor::Id; - } }; /** * Used to notify of changes in the access restriction list and active reviews. */ - class EntryListener + class Listener { public: - enum class ChangeType - { - kAdded = 1, - kRemoved = 2, - kUpdated = 3 - }; + virtual ~Listener() = default; - virtual ~EntryListener() = default; + /** + * Notifies of a change in the commissioning access restriction list. + */ + virtual void CommissioningRestrictionListChanged() = 0; /** * Notifies of a change in the access restriction list. * - * @param [in] fabricIndex The index of the fabric in which the entry has changed. - * @param [in] index Index of entry to which has changed (relative to fabric). - * @param [in] entry The latest value of the entry which changed. - * @param [in] changeType The type of change that occurred. + * @param [in] fabricIndex The index of the fabric in which the list has changed. */ - virtual void OnEntryChanged(FabricIndex fabricIndex, size_t index, SharedPtr<Entry> entry, ChangeType changeType) = 0; + virtual void RestrictionListChanged(FabricIndex fabricIndex) = 0; /** * Notifies of an update to an active review with instructions and an optional redirect URL. @@ -122,38 +108,45 @@ class AccessRestriction const char * redirectUrl) = 0; private: - EntryListener * mNext = nullptr; + Listener * mNext = nullptr; - friend class AccessRestriction; + friend class AccessRestrictionProvider; }; - AccessRestriction() = default; - virtual ~AccessRestriction() = default; + AccessRestrictionProvider() = default; + virtual ~AccessRestrictionProvider() = default; + + AccessRestrictionProvider(const AccessRestrictionProvider &) = delete; + AccessRestrictionProvider & operator=(const AccessRestrictionProvider &) = delete; - AccessRestriction(const AccessRestriction &) = delete; - AccessRestriction & operator=(const AccessRestriction &) = delete; + /** + * Set the restriction entries that are to be used during commissioning when there is no accessing fabric. + * + * @param [in] entries The entries to set. + */ + CHIP_ERROR SetCommissioningEntries(const std::vector<Entry> & entries); /** - * Create restriction entries for a fabric by populating from the commissioning entries. - * This should be called when the device is commissioned to a new fabric. + * Set the restriction entries for a fabric. * * @param [in] fabricIndex The index of the fabric for which to create entries. + * @param [in] entries The entries to set for the fabric. */ - CHIP_ERROR CreateFabricEntries(const FabricIndex fabricIndex); + CHIP_ERROR SetEntries(const FabricIndex, const std::vector<Entry> & entries); /** * Add a listener to be notified of changes in the access restriction list and active reviews. * * @param [in] listener The listener to add. */ - void AddListener(EntryListener & listener); + void AddListener(Listener & listener); /** * Remove a listener from being notified of changes in the access restriction list and active reviews. * * @param [in] listener The listener to remove. */ - void RemoveListener(EntryListener & listener); + void RemoveListener(Listener & listener); /** * Check whether access by a subject descriptor to a request path should be restricted (denied) for the given action. @@ -161,9 +154,9 @@ class AccessRestriction * * If access is not restricted, CHIP_NO_ERROR will be returned. * - * @retval #CHIP_ERROR_ACCESS_DENIED if access is denied. + * @retval CHIP_ERROR_ACCESS_DENIED if access is denied. * @retval other errors should also be treated as restricted/denied. - * @retval #CHIP_NO_ERROR if access is not restricted/denied. + * @retval CHIP_NO_ERROR if access is not restricted/denied. */ CHIP_ERROR Check(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath); @@ -180,83 +173,32 @@ class AccessRestriction return DoRequestFabricRestrictionReview(fabricIndex, token, arl); } - using EntryIterator = std::vector<SharedPtr<Entry>>::iterator; - /** - * Get iterator over the commissioning entries, which are used during commissioning - * and also installed as defaults for a fabric upon completion of commissioning. + * Get the commissioning restriction entries. * - * @param [out] begin iterator pointing to the beginning of the entries - * @param [out] end iterator pointing to the end of the entries + * @retval the commissioning restriction entries. */ - static CHIP_ERROR CommissioningEntries(EntryIterator & begin, EntryIterator & end) - { - begin = sCommissioningEntries.begin(); - end = sCommissioningEntries.end(); - - return CHIP_NO_ERROR; - } + const std::vector<Entry> & GetCommissioningEntries() const { return mCommissioningEntries; } /** - * Get iterator over entries in the access restriction list by fabric. + * Get the restriction entries for a fabric. * - * @param [in] fabricIndex Iteration is confined to fabric - * @param [out] begin iterator pointing to the beginning of the entries - * @param [out] end iterator pointing to the end of the entries + * @param [in] fabricIndex the index of the fabric for which to get entries. + * @param [out] entries reference to a const vector to hold the entries. */ - CHIP_ERROR Entries(const FabricIndex fabricIndex, EntryIterator & begin, EntryIterator & end) + CHIP_ERROR GetEntries(const FabricIndex fabricIndex, const std::vector<Entry> *& entries) const { - if (mFabricEntries.find(fabricIndex) == mFabricEntries.end()) + auto it = mFabricEntries.find(fabricIndex); + if (it == mFabricEntries.end()) { return CHIP_ERROR_NOT_FOUND; } - begin = mFabricEntries[fabricIndex].begin(); - end = mFabricEntries[fabricIndex].end(); + entries = &(it->second); return CHIP_NO_ERROR; } - /** - * Add a restriction entry to the commissioning access restriction list. This list is automatically - * applied to fabrics upon completion of commissioning. This request will fail if there is already - * an entry with the same cluster and endpoint. - * - * @param [in] entry The entry to add to the commissioning access restriction list. - * @return CHIP_NO_ERROR if the entry was successfully created, or an error code. - */ - static CHIP_ERROR CreateCommissioningEntry(SharedPtr<Entry> entry); - - /** - * Add a restriction entry to the access restriction list and notify any listeners. This request - * will fail if there is already an entry with the same cluster and endpoint. - * - * @param [out] index (If not nullptr) index of created entry (relative to fabric). - * @param [in] entry The entry to add to the access restriction list. - * @param [in] fabricIndex The index of the fabric in which the entry should be added. - * @return CHIP_NO_ERROR if the entry was successfully created, or an error code. - */ - CHIP_ERROR CreateEntry(size_t * index, const Entry & entry, const FabricIndex fabricIndex); - - /** - * Delete a restriction entry from the access restriction list and notify any listeners. - * - * @param [in] index Index of entry to delete. May be relative to fabric. - * @param [in] fabricIndex Fabric to which entry `index` is relative. - * @return CHIP_NO_ERROR if the entry was successfully deleted, or an error code. - */ - CHIP_ERROR DeleteEntry(size_t index, const FabricIndex fabricIndex); - - /** - * Update a restriction entry in the access restriction list and notify any listeners. - * - * @param [in] index Index of entry to delete. May be relative to fabric. - * @param [in] entry The updated entry to replace the existing entry. - * @param [in] fabricIndex The index of the fabric in which the entry should be updated. - * @return CHIP_NO_ERROR if the entry was successfully updated, or an error code. - */ - CHIP_ERROR UpdateEntry(size_t index, const Entry & entry, const FabricIndex fabricIndex); - protected: /** * Initiate a review of the access restrictions for a fabric. This method should be implemented by the platform and be @@ -270,18 +212,13 @@ class AccessRestriction virtual CHIP_ERROR DoRequestFabricRestrictionReview(const FabricIndex fabricIndex, uint64_t token, const std::vector<Entry> & arl) = 0; - /** - * Clear all access restriction data. - */ - void ClearData(); - private: - SharedPtr<Entry> GetEntry(FabricIndex fabricIndex, size_t index); + bool IsEntryValid(const Entry & entry) const; - static std::vector<Platform::SharedPtr<Entry>> sCommissioningEntries; - uint64_t mNextToken = 1; - EntryListener * mListeners = nullptr; - std::map<FabricIndex, std::vector<Platform::SharedPtr<Entry>>> mFabricEntries; + uint64_t mNextToken = 1; + Listener * mListeners = nullptr; + std::vector<Entry> mCommissioningEntries; + std::map<FabricIndex, std::vector<Entry>> mFabricEntries; }; } // namespace Access diff --git a/src/access/BUILD.gn b/src/access/BUILD.gn index ae2a64d70906f8..3f3aaafe8a6a9b 100644 --- a/src/access/BUILD.gn +++ b/src/access/BUILD.gn @@ -73,8 +73,8 @@ static_library("access") { if (chip_enable_access_restrictions) { sources += [ - "AccessRestriction.cpp", - "AccessRestriction.h", + "AccessRestrictionProvider.cpp", + "AccessRestrictionProvider.h", ] } } diff --git a/src/access/tests/BUILD.gn b/src/access/tests/BUILD.gn index c790ad4066be73..64226adfacc260 100644 --- a/src/access/tests/BUILD.gn +++ b/src/access/tests/BUILD.gn @@ -32,6 +32,6 @@ chip_test_suite("tests") { ] if (chip_enable_access_restrictions) { - test_sources += [ "TestAccessRestriction.cpp" ] + test_sources += [ "TestAccessRestrictionProvider.cpp" ] } } diff --git a/src/access/tests/TestAccessRestriction.cpp b/src/access/tests/TestAccessRestrictionProvider.cpp similarity index 87% rename from src/access/tests/TestAccessRestriction.cpp rename to src/access/tests/TestAccessRestrictionProvider.cpp index 3a3db93be3f640..073d3e6081a47c 100644 --- a/src/access/tests/TestAccessRestriction.cpp +++ b/src/access/tests/TestAccessRestrictionProvider.cpp @@ -17,7 +17,7 @@ */ #include "access/AccessControl.h" -#include "access/AccessRestriction.h" +#include "access/AccessRestrictionProvider.h" #include "access/examples/ExampleAccessControlDelegate.h" #include <pw_unit_test/framework.h> @@ -28,19 +28,16 @@ namespace chip { namespace Access { -class TestAccessRestrictionImpl : public AccessRestriction +class TestAccessRestrictionProvider : public AccessRestrictionProvider { CHIP_ERROR DoRequestFabricRestrictionReview(const FabricIndex fabricIndex, uint64_t token, const std::vector<Entry> & arl) { return CHIP_NO_ERROR; } - -public: - void Clear() { ClearData(); } }; AccessControl accessControl; -TestAccessRestrictionImpl accessRestriction; +TestAccessRestrictionProvider accessRestrictionProvider; constexpr ClusterId kNetworkCommissioningCluster = 0x0000'0031; // must not be blocked by access restrictions on any endpoint constexpr ClusterId kDescriptorCluster = 0x0000'001d; // must not be blocked by access restrictions on any endpoint @@ -176,13 +173,20 @@ class DeviceTypeResolver : public AccessControl::DeviceTypeResolver class TestAccessRestriction : public ::testing::Test { public: // protected - void SetUp() override { accessRestriction.Clear(); } + void SetUp() override + { + accessRestrictionProvider.SetCommissioningEntries(std::vector<AccessRestrictionProvider::Entry>()); + accessRestrictionProvider.SetEntries(0, std::vector<AccessRestrictionProvider::Entry>()); + accessRestrictionProvider.SetEntries(1, std::vector<AccessRestrictionProvider::Entry>()); + accessRestrictionProvider.SetEntries(2, std::vector<AccessRestrictionProvider::Entry>()); + } + static void SetUpTestSuite() { ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR); AccessControl::Delegate * delegate = Examples::GetAccessControlDelegate(); SetAccessControl(accessControl); - GetAccessControl().SetAccessRestriction(&accessRestriction); + GetAccessControl().SetAccessRestrictionProvider(&accessRestrictionProvider); VerifyOrDie(GetAccessControl().Init(delegate, testDeviceTypeResolver) == CHIP_NO_ERROR); EXPECT_EQ(LoadAccessControl(accessControl, aclEntryData, aclEntryDataCount), CHIP_NO_ERROR); } @@ -206,23 +210,29 @@ TEST_F(TestAccessRestriction, MetaTest) // ensure adding restrictons on endpoint 0 (any cluster) or for network commissioning and descriptor clusters fail TEST_F(TestAccessRestriction, InvalidRestrictionsTest) { - AccessRestriction::Entry entry; + std::vector<AccessRestrictionProvider::Entry> entries; + AccessRestrictionProvider::Entry entry; entry.fabricIndex = 1; entry.clusterId = kOnOffCluster; - entry.restrictions.push_back({ .restrictionType = AccessRestriction::Type::kAttributeAccessForbidden }); + entry.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kAttributeAccessForbidden }); // must not restrict endpoint 0 entry.endpointNumber = 0; - EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_ERROR_INVALID_ARGUMENT); + entries.push_back(entry); + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_ERROR_INVALID_ARGUMENT); // must not restrict network commissioning cluster + entries.clear(); entry.endpointNumber = 1; entry.clusterId = kNetworkCommissioningCluster; - EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_ERROR_INVALID_ARGUMENT); + entries.push_back(entry); + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_ERROR_INVALID_ARGUMENT); // must not restrict descriptor cluster + entries.clear(); entry.clusterId = kDescriptorCluster; - EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_ERROR_INVALID_ARGUMENT); + entries.push_back(entry); + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_ERROR_INVALID_ARGUMENT); } constexpr CheckData accessAttributeRestrictionTestData[] = { @@ -249,20 +259,23 @@ constexpr CheckData accessAttributeRestrictionTestData[] = { TEST_F(TestAccessRestriction, AccessAttributeRestrictionTest) { - AccessRestriction::Entry entry; + std::vector<AccessRestrictionProvider::Entry> entries; + AccessRestrictionProvider::Entry entry; entry.fabricIndex = 1; entry.endpointNumber = 1; entry.clusterId = kOnOffCluster; - entry.restrictions.push_back({ .restrictionType = AccessRestriction::Type::kAttributeAccessForbidden }); + entry.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kAttributeAccessForbidden }); // test wildcarded entity id - EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_NO_ERROR); + entries.push_back(entry); + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_NO_ERROR); RunChecks(accessAttributeRestrictionTestData, ArraySize(accessAttributeRestrictionTestData)); // test specific entity id - accessRestriction.Clear(); + entries.clear(); entry.restrictions[0].id.SetValue(1); - EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_NO_ERROR); + entries.push_back(entry); + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_NO_ERROR); RunChecks(accessAttributeRestrictionTestData, ArraySize(accessAttributeRestrictionTestData)); } @@ -290,20 +303,23 @@ constexpr CheckData writeAttributeRestrictionTestData[] = { TEST_F(TestAccessRestriction, WriteAttributeRestrictionTest) { - AccessRestriction::Entry entry; + std::vector<AccessRestrictionProvider::Entry> entries; + AccessRestrictionProvider::Entry entry; entry.fabricIndex = 1; entry.endpointNumber = 1; entry.clusterId = kOnOffCluster; - entry.restrictions.push_back({ .restrictionType = AccessRestriction::Type::kAttributeWriteForbidden }); + entry.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kAttributeWriteForbidden }); // test wildcarded entity id - EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_NO_ERROR); + entries.push_back(entry); + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_NO_ERROR); RunChecks(writeAttributeRestrictionTestData, ArraySize(writeAttributeRestrictionTestData)); // test specific entity id - accessRestriction.Clear(); + entries.clear(); entry.restrictions[0].id.SetValue(1); - EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_NO_ERROR); + entries.push_back(entry); + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_NO_ERROR); RunChecks(writeAttributeRestrictionTestData, ArraySize(writeAttributeRestrictionTestData)); } @@ -331,20 +347,23 @@ constexpr CheckData commandAttributeRestrictionTestData[] = { TEST_F(TestAccessRestriction, CommandRestrictionTest) { - AccessRestriction::Entry entry; + std::vector<AccessRestrictionProvider::Entry> entries; + AccessRestrictionProvider::Entry entry; entry.fabricIndex = 1; entry.endpointNumber = 1; entry.clusterId = kOnOffCluster; - entry.restrictions.push_back({ .restrictionType = AccessRestriction::Type::kCommandForbidden }); + entry.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kCommandForbidden }); // test wildcarded entity id - EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_NO_ERROR); + entries.push_back(entry); + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_NO_ERROR); RunChecks(commandAttributeRestrictionTestData, ArraySize(commandAttributeRestrictionTestData)); // test specific entity id - accessRestriction.Clear(); + entries.clear(); entry.restrictions[0].id.SetValue(1); - EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_NO_ERROR); + entries.push_back(entry); + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_NO_ERROR); RunChecks(commandAttributeRestrictionTestData, ArraySize(commandAttributeRestrictionTestData)); } @@ -372,20 +391,23 @@ constexpr CheckData eventAttributeRestrictionTestData[] = { TEST_F(TestAccessRestriction, EventRestrictionTest) { - AccessRestriction::Entry entry; + std::vector<AccessRestrictionProvider::Entry> entries; + AccessRestrictionProvider::Entry entry; entry.fabricIndex = 1; entry.endpointNumber = 1; entry.clusterId = kOnOffCluster; - entry.restrictions.push_back({ .restrictionType = AccessRestriction::Type::kEventForbidden }); + entry.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kEventForbidden }); // test wildcarded entity id - EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_NO_ERROR); + entries.push_back(entry); + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_NO_ERROR); RunChecks(eventAttributeRestrictionTestData, ArraySize(eventAttributeRestrictionTestData)); // test specific entity id - accessRestriction.Clear(); + entries.clear(); entry.restrictions[0].id.SetValue(1); - EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry, 1), CHIP_NO_ERROR); + entries.push_back(entry); + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_NO_ERROR); RunChecks(eventAttributeRestrictionTestData, ArraySize(eventAttributeRestrictionTestData)); } @@ -434,27 +456,31 @@ constexpr CheckData combinedRestrictionTestData[] = { TEST_F(TestAccessRestriction, CombinedRestrictionTest) { // a restriction for all access to attribute 1 and 2, attributes 3 and 4 are allowed - AccessRestriction::Entry entry1; + std::vector<AccessRestrictionProvider::Entry> entries1; + AccessRestrictionProvider::Entry entry1; entry1.fabricIndex = 1; entry1.endpointNumber = 1; entry1.clusterId = kOnOffCluster; - entry1.restrictions.push_back({ .restrictionType = AccessRestriction::Type::kAttributeWriteForbidden }); + entry1.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kAttributeWriteForbidden }); entry1.restrictions[0].id.SetValue(1); - entry1.restrictions.push_back({ .restrictionType = AccessRestriction::Type::kAttributeAccessForbidden }); + entry1.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kAttributeAccessForbidden }); entry1.restrictions[1].id.SetValue(2); - EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry1, 1), CHIP_NO_ERROR); + entries1.push_back(entry1); + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries1), CHIP_NO_ERROR); // a restriction for fabric 2 that forbids command 1 and 2. Check that command 1 is blocked on invoke, but attribute 2 write is // allowed - AccessRestriction::Entry entry2; + std::vector<AccessRestrictionProvider::Entry> entries2; + AccessRestrictionProvider::Entry entry2; entry2.fabricIndex = 2; entry2.endpointNumber = 1; entry2.clusterId = kOnOffCluster; - entry2.restrictions.push_back({ .restrictionType = AccessRestriction::Type::kCommandForbidden }); + entry2.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kCommandForbidden }); entry2.restrictions[0].id.SetValue(1); - entry2.restrictions.push_back({ .restrictionType = AccessRestriction::Type::kCommandForbidden }); + entry2.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kCommandForbidden }); entry2.restrictions[1].id.SetValue(2); - EXPECT_EQ(accessRestriction.CreateEntry(nullptr, entry2, 2), CHIP_NO_ERROR); + entries2.push_back(entry2); + EXPECT_EQ(accessRestrictionProvider.SetEntries(2, entries2), CHIP_NO_ERROR); RunChecks(combinedRestrictionTestData, ArraySize(combinedRestrictionTestData)); } diff --git a/src/app/clusters/access-control-server/access-control-server.cpp b/src/app/clusters/access-control-server/access-control-server.cpp index c8eda434079168..99c460d4a49b38 100644 --- a/src/app/clusters/access-control-server/access-control-server.cpp +++ b/src/app/clusters/access-control-server/access-control-server.cpp @@ -18,7 +18,7 @@ #include <access/AccessControl.h> #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS -#include <access/AccessRestriction.h> +#include <access/AccessRestrictionProvider.h> #include <app/server/ArlStorage.h> #endif @@ -30,6 +30,7 @@ #include <app/ConcreteCommandPath.h> #include <app/EventLogging.h> #include <app/data-model/Encode.h> +#include <app/reporting/reporting.h> #include <app/server/AclStorage.h> #include <app/server/Server.h> #include <app/util/attribute-storage.h> @@ -66,7 +67,7 @@ class AccessControlAttribute : public AttributeAccessInterface, public AccessControl::EntryListener #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS , - public AccessRestriction::EntryListener + public AccessRestrictionProvider::Listener #endif { public: @@ -87,8 +88,9 @@ class AccessControlAttribute : public AttributeAccessInterface, const AccessControl::Entry * entry, AccessControl::EntryListener::ChangeType changeType) override; #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS - void OnEntryChanged(FabricIndex fabricIndex, size_t index, Platform::SharedPtr<AccessRestriction::Entry> entry, - AccessRestriction::EntryListener::ChangeType changeType) override; + void CommissioningRestrictionListChanged() override; + + void RestrictionListChanged(FabricIndex fabricIndex) override; void OnFabricRestrictionReviewUpdate(FabricIndex fabricIndex, uint64_t token, const char * instruction, const char * redirectUrl) override; @@ -468,20 +470,18 @@ void AccessControlAttribute::OnEntryChanged(const SubjectDescriptor * subjectDes #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS CHIP_ERROR AccessControlAttribute::ReadCommissioningArl(AttributeValueEncoder & aEncoder) { - auto accessRestriction = GetAccessControl().GetAccessRestriction(); - if (accessRestriction == nullptr) + auto accessRestrictionProvider = GetAccessControl().GetAccessRestrictionProvider(); + if (accessRestrictionProvider == nullptr) { return CHIP_ERROR_NOT_IMPLEMENTED; } return aEncoder.EncodeList([&](const auto & encoder) -> CHIP_ERROR { - AccessRestriction::EntryIterator begin; - AccessRestriction::EntryIterator end; - ReturnErrorOnFailure(accessRestriction->CommissioningEntries(begin, end)); + auto entries = accessRestrictionProvider->GetCommissioningEntries(); - for (AccessRestriction::EntryIterator it = begin; it != end; ++it) + for (auto & entry : entries) { - ArlStorage::EncodableEntry encodableEntry(*it); + ArlStorage::EncodableEntry encodableEntry(entry); ReturnErrorOnFailure(encoder.Encode(encodableEntry)); } return CHIP_NO_ERROR; @@ -490,8 +490,8 @@ CHIP_ERROR AccessControlAttribute::ReadCommissioningArl(AttributeValueEncoder & CHIP_ERROR AccessControlAttribute::ReadArl(AttributeValueEncoder & aEncoder) { - auto accessRestriction = GetAccessControl().GetAccessRestriction(); - if (accessRestriction == nullptr) + auto accessRestrictionProvider = GetAccessControl().GetAccessRestrictionProvider(); + if (accessRestrictionProvider == nullptr) { return CHIP_ERROR_NOT_IMPLEMENTED; } @@ -500,25 +500,30 @@ CHIP_ERROR AccessControlAttribute::ReadArl(AttributeValueEncoder & aEncoder) for (auto & info : Server::GetInstance().GetFabricTable()) { auto fabric = info.GetFabricIndex(); - AccessRestriction::EntryIterator begin; - AccessRestriction::EntryIterator end; - ReturnErrorOnFailure(accessRestriction->Entries(fabric, begin, end)); - - for (AccessRestriction::EntryIterator it = begin; it != end; ++it) + // get entries for fabric + const std::vector<AccessRestrictionProvider::Entry> * entries = nullptr; + ReturnErrorOnFailure(accessRestrictionProvider->GetEntries(fabric, entries)); + VerifyOrReturnError(entries != nullptr, CHIP_ERROR_INCORRECT_STATE); + for (auto & entry : *entries) { - ArlStorage::EncodableEntry encodableEntry(*it); + ArlStorage::EncodableEntry encodableEntry(entry); ReturnErrorOnFailure(encoder.Encode(encodableEntry)); } } return CHIP_NO_ERROR; }); } +void AccessControlAttribute::CommissioningRestrictionListChanged() +{ + MatterReportingAttributeChangeCallback(0, AccessControlCluster::Id, AccessControlCluster::Attributes::CommissioningARL::Id); +} -void AccessControlAttribute::OnEntryChanged(FabricIndex fabricIndex, size_t index, - Platform::SharedPtr<AccessRestriction::Entry> entry, - AccessRestriction::EntryListener::ChangeType changeType) +void AccessControlAttribute::RestrictionListChanged(FabricIndex fabricIndex) { CHIP_ERROR err; + + MatterReportingAttributeChangeCallback(0, AccessControlCluster::Id, AccessControlCluster::Attributes::Arl::Id); + ArlChangedEvent event{ .fabricIndex = fabricIndex }; EventNumber eventNumber; @@ -601,16 +606,6 @@ CHIP_ERROR AccessControlAttribute::Write(const ConcreteDataAttributePath & aPath return ChipErrorToImErrorMap(WriteImpl(aPath, aDecoder)); } -#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS -void OnPlatformEventHandler(const DeviceLayer::ChipDeviceEvent * event, intptr_t arg) -{ - if (event->Type == DeviceLayer::DeviceEventType::kCommissioningComplete) - { - Access::GetAccessControl().GetAccessRestriction()->CreateFabricEntries(event->CommissioningComplete.fabricIndex); - } -} -#endif - } // namespace void MatterAccessControlPluginServerInitCallback() @@ -621,13 +616,11 @@ void MatterAccessControlPluginServerInitCallback() GetAccessControl().AddEntryListener(sAttribute); #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS - auto accessRestriction = GetAccessControl().GetAccessRestriction(); - if (accessRestriction != nullptr) + auto accessRestrictionProvider = GetAccessControl().GetAccessRestrictionProvider(); + if (accessRestrictionProvider != nullptr) { - accessRestriction->AddListener(sAttribute); + accessRestrictionProvider->AddListener(sAttribute); } - - DeviceLayer::PlatformMgrImpl().AddEventHandler(OnPlatformEventHandler); #endif } @@ -643,11 +636,11 @@ bool emberAfAccessControlClusterReviewFabricRestrictionsCallback( } uint64_t token; - std::vector<AccessRestriction::Entry> entries; + std::vector<AccessRestrictionProvider::Entry> entries; auto entryIter = commandData.arl.begin(); while (entryIter.Next()) { - AccessRestriction::Entry entry; + AccessRestrictionProvider::Entry entry; entry.fabricIndex = commandObj->GetAccessingFabricIndex(); entry.endpointNumber = entryIter.GetValue().endpoint; entry.clusterId = entryIter.GetValue().cluster; @@ -655,8 +648,8 @@ bool emberAfAccessControlClusterReviewFabricRestrictionsCallback( auto restrictionIter = entryIter.GetValue().restrictions.begin(); while (restrictionIter.Next()) { - AccessRestriction::Restriction restriction; - restriction.restrictionType = static_cast<AccessRestriction::Type>(restrictionIter.GetValue().type); + AccessRestrictionProvider::Restriction restriction; + restriction.restrictionType = static_cast<AccessRestrictionProvider::Type>(restrictionIter.GetValue().type); if (!restrictionIter.GetValue().id.IsNull()) { restriction.id.SetValue(restrictionIter.GetValue().id.Value()); @@ -667,7 +660,7 @@ bool emberAfAccessControlClusterReviewFabricRestrictionsCallback( entries.push_back(entry); } - CHIP_ERROR err = GetAccessControl().GetAccessRestriction()->RequestFabricRestrictionReview( + CHIP_ERROR err = GetAccessControl().GetAccessRestrictionProvider()->RequestFabricRestrictionReview( commandObj->GetAccessingFabricIndex(), entries, token); if (err == CHIP_NO_ERROR) diff --git a/src/app/server/ArlStorage.cpp b/src/app/server/ArlStorage.cpp index 983f9a16ad8cd6..a0d6c38c855029 100644 --- a/src/app/server/ArlStorage.cpp +++ b/src/app/server/ArlStorage.cpp @@ -23,28 +23,28 @@ using namespace chip; using namespace chip::app; using namespace chip::Access; -using Entry = AccessRestriction::Entry; -using EntryListener = AccessRestriction::EntryListener; +using Entry = AccessRestrictionProvider::Entry; +using EntryListener = AccessRestrictionProvider::Listener; using StagingRestrictionType = Clusters::AccessControl::AccessRestrictionTypeEnum; using StagingRestriction = Clusters::AccessControl::Structs::AccessRestrictionStruct::Type; namespace { -CHIP_ERROR Convert(StagingRestrictionType from, AccessRestriction::Type & to) +CHIP_ERROR Convert(StagingRestrictionType from, AccessRestrictionProvider::Type & to) { switch (from) { case StagingRestrictionType::kAttributeAccessForbidden: - to = AccessRestriction::Type::kAttributeAccessForbidden; + to = AccessRestrictionProvider::Type::kAttributeAccessForbidden; break; case StagingRestrictionType::kAttributeWriteForbidden: - to = AccessRestriction::Type::kAttributeWriteForbidden; + to = AccessRestrictionProvider::Type::kAttributeWriteForbidden; break; case StagingRestrictionType::kCommandForbidden: - to = AccessRestriction::Type::kCommandForbidden; + to = AccessRestrictionProvider::Type::kCommandForbidden; break; case StagingRestrictionType::kEventForbidden: - to = AccessRestriction::Type::kEventForbidden; + to = AccessRestrictionProvider::Type::kEventForbidden; break; default: return CHIP_ERROR_INVALID_ARGUMENT; @@ -52,20 +52,20 @@ CHIP_ERROR Convert(StagingRestrictionType from, AccessRestriction::Type & to) return CHIP_NO_ERROR; } -CHIP_ERROR Convert(AccessRestriction::Type from, StagingRestrictionType & to) +CHIP_ERROR Convert(AccessRestrictionProvider::Type from, StagingRestrictionType & to) { switch (from) { - case AccessRestriction::Type::kAttributeAccessForbidden: + case AccessRestrictionProvider::Type::kAttributeAccessForbidden: to = StagingRestrictionType::kAttributeAccessForbidden; break; - case AccessRestriction::Type::kAttributeWriteForbidden: + case AccessRestrictionProvider::Type::kAttributeWriteForbidden: to = StagingRestrictionType::kAttributeWriteForbidden; break; - case AccessRestriction::Type::kCommandForbidden: + case AccessRestrictionProvider::Type::kCommandForbidden: to = StagingRestrictionType::kCommandForbidden; break; - case AccessRestriction::Type::kEventForbidden: + case AccessRestrictionProvider::Type::kEventForbidden: to = StagingRestrictionType::kEventForbidden; break; default: @@ -91,7 +91,7 @@ CHIP_ERROR ArlStorage::DecodableEntry::Decode(TLV::TLVReader & reader) while (iterator.Next()) { auto & tmp = iterator.GetValue(); - AccessRestriction::Restriction restriction; + AccessRestrictionProvider::Restriction restriction; ReturnErrorOnFailure(Convert(tmp.type, restriction.restrictionType)); if (!tmp.id.IsNull()) @@ -122,17 +122,17 @@ CHIP_ERROR ArlStorage::EncodableEntry::EncodeForWrite(TLV::TLVWriter & writer, T CHIP_ERROR ArlStorage::EncodableEntry::Stage() const { - mStagingEntry.fabricIndex = mEntry->fabricIndex; - mStagingEntry.endpoint = mEntry->endpointNumber; - mStagingEntry.cluster = mEntry->clusterId; + mStagingEntry.fabricIndex = mEntry.fabricIndex; + mStagingEntry.endpoint = mEntry.endpointNumber; + mStagingEntry.cluster = mEntry.clusterId; { - size_t count = mEntry->restrictions.size(); + size_t count = mEntry.restrictions.size(); if (count > 0 && count <= CHIP_CONFIG_ACCESS_RESTRICTION_MAX_RESTRICTIONS_PER_ENTRY) { for (size_t i = 0; i < count; i++) { - auto restriction = mEntry->restrictions[i]; + auto restriction = mEntry.restrictions[i]; StagingRestriction tmp; ReturnErrorOnFailure(Convert(restriction.restrictionType, tmp.type)); diff --git a/src/app/server/ArlStorage.h b/src/app/server/ArlStorage.h index 34fb616d226e4f..c87f2e0d99a2f5 100644 --- a/src/app/server/ArlStorage.h +++ b/src/app/server/ArlStorage.h @@ -17,7 +17,7 @@ #pragma once -#include <access/AccessRestriction.h> +#include <access/AccessRestrictionProvider.h> #include <credentials/FabricTable.h> #include <lib/core/CHIPPersistentStorageDelegate.h> @@ -52,7 +52,7 @@ class ArlStorage */ class DecodableEntry { - using Entry = Access::AccessRestriction::Entry; + using Entry = Access::AccessRestrictionProvider::Entry; using StagingEntry = Clusters::AccessControl::Structs::AccessRestrictionEntryStruct::DecodableType; public: @@ -89,12 +89,12 @@ class ArlStorage */ class EncodableEntry { - using Entry = Access::AccessRestriction::Entry; + using Entry = Access::AccessRestrictionProvider::Entry; using StagingEntry = Clusters::AccessControl::Structs::AccessRestrictionEntryStruct::Type; using StagingRestriction = Clusters::AccessControl::Structs::AccessRestrictionStruct::Type; public: - EncodableEntry(std::shared_ptr<Entry> entry) : mEntry(entry) {} + EncodableEntry(const Entry & entry) : mEntry(entry) {} /** * Constructor-provided entry is staged into a staging entry, @@ -120,10 +120,10 @@ class ArlStorage public: static constexpr bool kIsFabricScoped = true; - FabricIndex GetFabricIndex() const { return mEntry->fabricIndex; } + FabricIndex GetFabricIndex() const { return mEntry.fabricIndex; } private: - std::shared_ptr<Entry> mEntry; + Entry mEntry; mutable StagingEntry mStagingEntry; mutable StagingRestriction mStagingRestrictions[CHIP_CONFIG_ACCESS_RESTRICTION_MAX_RESTRICTIONS_PER_ENTRY]; diff --git a/src/app/server/DefaultArlStorage.cpp b/src/app/server/DefaultArlStorage.cpp index b7ab1aff23e4f0..9f43054a817d0f 100644 --- a/src/app/server/DefaultArlStorage.cpp +++ b/src/app/server/DefaultArlStorage.cpp @@ -25,10 +25,10 @@ using namespace chip::app; using namespace chip::Access; using EncodableEntry = ArlStorage::EncodableEntry; -using Entry = AccessRestriction::Entry; -using EntryListener = AccessRestriction::EntryListener; +using Entry = AccessRestrictionProvider::Entry; +using Listener = AccessRestrictionProvider::Listener; using StagingRestriction = Clusters::AccessControl::Structs::AccessRestrictionEntryStruct::Type; -using Restriction = AccessRestriction::Restriction; +using Restriction = AccessRestrictionProvider::Restriction; namespace { @@ -59,47 +59,70 @@ constexpr int kEncodedEntryOverheadBytes = 17 + 8; constexpr int kEncodedEntryRestrictionBytes = 11 * CHIP_CONFIG_ACCESS_RESTRICTION_MAX_RESTRICTIONS_PER_ENTRY; constexpr int kEncodedEntryTotalBytes = kEncodedEntryOverheadBytes + kEncodedEntryRestrictionBytes; -class : public EntryListener +class : public Listener { public: - void OnEntryChanged(FabricIndex fabricIndex, size_t index, SharedPtr<Entry> entry, ChangeType changeType) override + void CommissioningRestrictionListChanged() override { + // first clear out any existing entries CHIP_ERROR err; + for (size_t index = 0; /**/; ++index) + { + err = mPersistentStorage->SyncDeleteKeyValue( + DefaultStorageKeyAllocator::AccessControlCommissioningArlEntry(index).KeyName()); + if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) + { + break; + } + } - uint8_t buffer[kEncodedEntryTotalBytes] = { 0 }; + auto entries = GetAccessControl().GetAccessRestrictionProvider()->GetCommissioningEntries(); + size_t index = 0; + for (auto & entry : entries) + { + uint8_t buffer[kEncodedEntryTotalBytes] = { 0 }; + TLV::TLVWriter writer; + writer.Init(buffer); + EncodableEntry encodableEntry(entry); + SuccessOrExit(err = encodableEntry.EncodeForWrite(writer, TLV::AnonymousTag())); + SuccessOrExit(err = mPersistentStorage->SyncSetKeyValue( + DefaultStorageKeyAllocator::AccessControlCommissioningArlEntry(index++).KeyName(), buffer, + static_cast<uint16_t>(writer.GetLengthWritten()))); + } + + return; - VerifyOrExit(mPersistentStorage != nullptr, err = CHIP_ERROR_INCORRECT_STATE); + exit: + ChipLogError(DataManagement, "DefaultArlStorage: failed %" CHIP_ERROR_FORMAT, err.Format()); + } - if (changeType == ChangeType::kRemoved) + void RestrictionListChanged(FabricIndex fabricIndex) override + { + // first clear out any existing entries + CHIP_ERROR err; + size_t index = 0; + for (size_t index = 0; /**/; ++index) { - // Shuffle down entries past index, then delete entry at last index. - while (true) + err = mPersistentStorage->SyncDeleteKeyValue( + DefaultStorageKeyAllocator::AccessControlArlEntry(fabricIndex, index).KeyName()); + if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) { - uint16_t size = static_cast<uint16_t>(sizeof(buffer)); - err = mPersistentStorage->SyncGetKeyValue( - DefaultStorageKeyAllocator::AccessControlArlEntry(fabricIndex, index + 1).KeyName(), buffer, size); - if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) - { - break; - } - SuccessOrExit(err); - SuccessOrExit(err = mPersistentStorage->SyncSetKeyValue( - DefaultStorageKeyAllocator::AccessControlArlEntry(fabricIndex, index).KeyName(), buffer, size)); - index++; + break; } - SuccessOrExit(err = mPersistentStorage->SyncDeleteKeyValue( - DefaultStorageKeyAllocator::AccessControlArlEntry(fabricIndex, index).KeyName())); } - else + + const std::vector<Entry> * entries; + SuccessOrExit(err = GetAccessControl().GetAccessRestrictionProvider()->GetEntries(fabricIndex, entries)); + VerifyOrExit(entries != nullptr, err = CHIP_ERROR_INCORRECT_STATE); + for (auto & entry : *entries) { - // Write added/updated entry at index. - VerifyOrExit(entry != nullptr, err = CHIP_ERROR_INCORRECT_STATE); + uint8_t buffer[kEncodedEntryTotalBytes] = { 0 }; TLV::TLVWriter writer; writer.Init(buffer); EncodableEntry encodableEntry(entry); SuccessOrExit(err = encodableEntry.EncodeForWrite(writer, TLV::AnonymousTag())); SuccessOrExit(err = mPersistentStorage->SyncSetKeyValue( - DefaultStorageKeyAllocator::AccessControlArlEntry(fabricIndex, index).KeyName(), buffer, + DefaultStorageKeyAllocator::AccessControlArlEntry(fabricIndex, index++).KeyName(), buffer, static_cast<uint16_t>(writer.GetLengthWritten()))); } @@ -132,9 +155,10 @@ CHIP_ERROR DefaultArlStorage::Init(PersistentStorageDelegate & persistentStorage ChipLogProgress(DataManagement, "DefaultArlStorage: initializing"); CHIP_ERROR err; + size_t commissioningCount = 0; + size_t count = 0; - [[maybe_unused]] size_t count = 0; - + std::vector<Entry> commissioningEntries; for (size_t index = 0; /**/; ++index) { uint8_t buffer[kEncodedEntryTotalBytes] = { 0 }; @@ -154,13 +178,14 @@ CHIP_ERROR DefaultArlStorage::Init(PersistentStorageDelegate & persistentStorage DecodableEntry decodableEntry; SuccessOrExit(err = decodableEntry.Decode(reader)); - SuccessOrExit(err = GetAccessControl().GetAccessRestriction()->CreateCommissioningEntry( - Platform::MakeShared<Entry>(decodableEntry.GetEntry()))); - count++; + commissioningEntries.push_back(decodableEntry.GetEntry()); + commissioningCount++; } + GetAccessControl().GetAccessRestrictionProvider()->SetCommissioningEntries(commissioningEntries); for (auto it = first; it != last; ++it) { + std::vector<Entry> entries; auto fabric = it->GetFabricIndex(); for (size_t index = 0; /**/; ++index) { @@ -181,16 +206,17 @@ CHIP_ERROR DefaultArlStorage::Init(PersistentStorageDelegate & persistentStorage DecodableEntry decodableEntry; SuccessOrExit(err = decodableEntry.Decode(reader)); - Entry & entry = decodableEntry.GetEntry(); - SuccessOrExit(err = GetAccessControl().GetAccessRestriction()->CreateEntry(nullptr, entry, fabric)); + entries.push_back(decodableEntry.GetEntry()); count++; } + GetAccessControl().GetAccessRestrictionProvider()->SetEntries(fabric, commissioningEntries); } - ChipLogProgress(DataManagement, "DefaultArlStorage: %u entries loaded", (unsigned) count); + ChipLogProgress(DataManagement, "DefaultArlStorage: %u commissioning entries loaded, %u fabric entries loaded", + (unsigned) commissioningCount, (unsigned) count); sEntryListener.Init(persistentStorage); - GetAccessControl().GetAccessRestriction()->AddListener(sEntryListener); + GetAccessControl().GetAccessRestrictionProvider()->AddListener(sEntryListener); return CHIP_NO_ERROR; diff --git a/src/app/server/Server.cpp b/src/app/server/Server.cpp index fbdae0ccb213f8..0484669f97b742 100644 --- a/src/app/server/Server.cpp +++ b/src/app/server/Server.cpp @@ -181,12 +181,12 @@ CHIP_ERROR Server::Init(const ServerInitParams & initParams) SuccessOrExit(err = mAclStorage->Init(*mDeviceStorage, mFabrics.begin(), mFabrics.end())); #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS - if (initParams.accessRestriction != nullptr && initParams.arlStorage != nullptr) + if (initParams.accessRestrictionProvider != nullptr && initParams.arlStorage != nullptr) { - mAccessRestriction = initParams.accessRestriction; - mArlStorage = initParams.arlStorage; + mAccessRestrictionProvider = initParams.accessRestrictionProvider; + mArlStorage = initParams.arlStorage; - mAccessControl.SetAccessRestriction(mAccessRestriction); + mAccessControl.SetAccessRestrictionProvider(mAccessRestrictionProvider); SuccessOrExit(err = mArlStorage->Init(*mDeviceStorage, mFabrics.begin(), mFabrics.end())); } #endif diff --git a/src/app/server/Server.h b/src/app/server/Server.h index b8f07e526f80d4..d49850d52dfa73 100644 --- a/src/app/server/Server.h +++ b/src/app/server/Server.h @@ -170,7 +170,7 @@ struct ServerInitParams #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS // Access Restriction implementation: MUST be injected if MNGD feature enabled. Used to enforce // access restrictions that are managed by the device. - Access::AccessRestriction * accessRestriction = nullptr; + Access::AccessRestrictionProvider * accessRestrictionProvider = nullptr; // ARL storage: MUST be injected if MNGD feature enabled. Used to store ACL entries in // persistent storage. Must NOT be initialized before being provided. app::ArlStorage * arlStorage = nullptr; @@ -693,7 +693,7 @@ class Server app::AclStorage * mAclStorage; #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS - Access::AccessRestriction * mAccessRestriction; + Access::AccessRestrictionProvider * mAccessRestrictionProvider; app::ArlStorage * mArlStorage; #endif From 03ea9d056c4e6bad4eba82ea18810629a29578af Mon Sep 17 00:00:00 2001 From: Thomas Lea <thomas_lea@comcast.com> Date: Wed, 21 Aug 2024 13:57:18 -0500 Subject: [PATCH 04/22] Fixed encode/decode so reading CommissioningARL and Arl attributes work --- examples/platform/linux/AppMain.cpp | 27 +++-- examples/platform/linux/Options.cpp | 32 ++++-- examples/platform/linux/Options.h | 3 +- src/access/AccessRestrictionProvider.cpp | 13 +++ .../access-control-server.cpp | 2 +- src/app/server/ArlStorage.cpp | 103 +++++++++++------- src/app/server/ArlStorage.h | 77 +++++++++++++ src/app/server/DefaultArlStorage.cpp | 5 +- src/lib/support/DefaultStorageKeyAllocator.h | 2 +- 9 files changed, 203 insertions(+), 61 deletions(-) diff --git a/examples/platform/linux/AppMain.cpp b/examples/platform/linux/AppMain.cpp index df7fa6081331d4..ab0f6214fae068 100644 --- a/examples/platform/linux/AppMain.cpp +++ b/examples/platform/linux/AppMain.cpp @@ -186,6 +186,10 @@ Optional<app::Clusters::NetworkCommissioning::Instance> sWiFiNetworkCommissionin app::Clusters::NetworkCommissioning::Instance sEthernetNetworkCommissioningInstance(kRootEndpointId, &sEthernetDriver); #endif // CHIP_APP_MAIN_HAS_ETHERNET_DRIVER +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS +auto exampleAccessRestrictionProvider = std::make_unique<ExampleAccessRestrictionProvider>(); +#endif + void EnableThreadNetworkCommissioning() { #if CHIP_APP_MAIN_HAS_THREAD_DRIVER @@ -600,20 +604,27 @@ void ChipLinuxAppMainLoop(AppMainLoopImplementation * impl) LinuxDeviceOptions::GetInstance().mSimulateNoInternalTime); #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS - if (LinuxDeviceOptions::GetInstance().accessRestrictionEntries.HasValue()) + initParams.accessRestrictionProvider = exampleAccessRestrictionProvider.get(); + initParams.arlStorage = new app::DefaultArlStorage(); +#endif + + // Init ZCL Data Model and CHIP App Server + Server::GetInstance().Init(initParams); + +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + if (LinuxDeviceOptions::GetInstance().commissioningArlEntries.HasValue()) { - auto exampleAccessRestrictionProvider = new ExampleAccessRestrictionProvider(); exampleAccessRestrictionProvider->SetCommissioningEntries( - LinuxDeviceOptions::GetInstance().accessRestrictionEntries.Value()); + LinuxDeviceOptions::GetInstance().commissioningArlEntries.Value()); + } - initParams.accessRestrictionProvider = exampleAccessRestrictionProvider; - initParams.arlStorage = new app::DefaultArlStorage(); + if (LinuxDeviceOptions::GetInstance().arlEntries.HasValue()) + { + //This example use of the ARL feature proactively installs the provided entries on fabric index 1 + exampleAccessRestrictionProvider->SetEntries(1, LinuxDeviceOptions::GetInstance().arlEntries.Value()); } #endif - // Init ZCL Data Model and CHIP App Server - Server::GetInstance().Init(initParams); - #if CONFIG_BUILD_FOR_HOST_UNIT_TEST // Set ReadHandler Capacity for Subscriptions chip::app::InteractionModelEngine::GetInstance()->SetHandlerCapacityForSubscriptions( diff --git a/examples/platform/linux/Options.cpp b/examples/platform/linux/Options.cpp index abea582d1f1b43..c7e6c26ed8bb21 100644 --- a/examples/platform/linux/Options.cpp +++ b/examples/platform/linux/Options.cpp @@ -89,7 +89,8 @@ enum kDeviceOption_TraceLog, kDeviceOption_TraceDecode, #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS - kDeviceOption_UseAccessRestrictions, + kDeviceOption_CommissioningArlEntries, + kDeviceOption_ArlEntries, #endif kOptionCSRResponseCSRIncorrectType, kOptionCSRResponseCSRNonceIncorrectType, @@ -164,7 +165,8 @@ OptionDef sDeviceOptionDefs[] = { { "trace_decode", kArgumentRequired, kDeviceOption_TraceDecode }, #endif // CHIP_CONFIG_TRANSPORT_TRACE_ENABLED #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS - { "enable-access-restrictions", kArgumentRequired, kDeviceOption_UseAccessRestrictions }, + { "commissioning-arl-entries", kArgumentRequired, kDeviceOption_CommissioningArlEntries }, + { "arl-entries", kArgumentRequired, kDeviceOption_ArlEntries }, #endif // CHIP_CONFIG_USE_ACCESS_RESTRICTIONS { "cert_error_csr_incorrect_type", kNoArgument, kOptionCSRResponseCSRIncorrectType }, { "cert_error_csr_existing_keypair", kNoArgument, kOptionCSRResponseCSRExistingKeyPair }, @@ -293,9 +295,12 @@ const char * sDeviceOptionHelp = " A value of 1 enables traces decoding, 0 disables this (default 0).\n" #endif // CHIP_CONFIG_TRANSPORT_TRACE_ENABLED #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS - " --enable-access-restrictions <CommissioningARL JSON>\n" - " Enable ACL cluster access restrictions with the provided JSON CommissioningARL. Example:\n" - " \"[{\\\"endpoint\\\": 1,\\\"cluster\\\": 2,\\\"restrictions\\\": [{\\\"type\\\": 0,\\\"id\\\": 3}]}]\"\n" + " --commissioning-arl-entries <CommissioningARL JSON>\n" + " Enable ACL cluster access restrictions used during commissioning with the provided JSON. Example:\n" + " \"[{\\\"endpoint\\\": 1,\\\"cluster\\\": 1105,\\\"restrictions\\\": [{\\\"type\\\": 0,\\\"id\\\": 0}]}]\"\n" + " --arl-entries <ARL JSON>\n" + " Enable ACL cluster access restrictions applied to fabric index 1 with the provided JSON. Example:\n" + " \"[{\\\"endpoint\\\": 1,\\\"cluster\\\": 1105,\\\"restrictions\\\": [{\\\"type\\\": 0,\\\"id\\\": 0}]}]\"\n" #endif // CHIP_CONFIG_USE_ACCESS_RESTRICTIONS " --cert_error_csr_incorrect_type\n" " Configure the CSRResponse to be built with an invalid CSR type.\n" @@ -580,12 +585,21 @@ bool HandleOption(const char * aProgram, OptionSet * aOptions, int aIdentifier, #endif // CHIP_CONFIG_TRANSPORT_TRACE_ENABLED #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS - case kDeviceOption_UseAccessRestrictions: { - std::vector<AccessRestrictionProvider::Entry> accessRestrictionEntries; - retval = ParseAccessRestrictionEntriesFromJson(aValue, accessRestrictionEntries); + case kDeviceOption_CommissioningArlEntries: { + std::vector<AccessRestrictionProvider::Entry> entries; + retval = ParseAccessRestrictionEntriesFromJson(aValue, entries); if (retval) { - LinuxDeviceOptions::GetInstance().accessRestrictionEntries.SetValue(std::move(accessRestrictionEntries)); + LinuxDeviceOptions::GetInstance().commissioningArlEntries.SetValue(std::move(entries)); + } + } + break; + case kDeviceOption_ArlEntries: { + std::vector<AccessRestrictionProvider::Entry> entries; + retval = ParseAccessRestrictionEntriesFromJson(aValue, entries); + if (retval) + { + LinuxDeviceOptions::GetInstance().arlEntries.SetValue(std::move(entries)); } } break; diff --git a/examples/platform/linux/Options.h b/examples/platform/linux/Options.h index 8fdacd8b64ea5c..11a9061efcade8 100644 --- a/examples/platform/linux/Options.h +++ b/examples/platform/linux/Options.h @@ -88,7 +88,8 @@ struct LinuxDeviceOptions int32_t subscriptionResumptionRetryIntervalSec = -1; #endif #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS - chip::Optional<std::vector<chip::Access::AccessRestrictionProvider::Entry>> accessRestrictionEntries; + chip::Optional<std::vector<chip::Access::AccessRestrictionProvider::Entry>> commissioningArlEntries; + chip::Optional<std::vector<chip::Access::AccessRestrictionProvider::Entry>> arlEntries; #endif static LinuxDeviceOptions & GetInstance(); }; diff --git a/src/access/AccessRestrictionProvider.cpp b/src/access/AccessRestrictionProvider.cpp index 0ef962ca9f939f..bcaed16afd71ce 100644 --- a/src/access/AccessRestrictionProvider.cpp +++ b/src/access/AccessRestrictionProvider.cpp @@ -85,11 +85,18 @@ CHIP_ERROR AccessRestrictionProvider::SetCommissioningEntries(const std::vector< mCommissioningEntries = entries; + for (Listener * listener = mListeners; listener != nullptr; listener = listener->mNext) + { + listener->CommissioningRestrictionListChanged(); + } + return CHIP_NO_ERROR; } CHIP_ERROR AccessRestrictionProvider::SetEntries(const FabricIndex fabricIndex, const std::vector<Entry> & entries) { + std::vector<Entry> updatedEntries; + // check that the input entries are valid for (auto & entry : entries) { @@ -98,8 +105,14 @@ CHIP_ERROR AccessRestrictionProvider::SetEntries(const FabricIndex fabricIndex, ChipLogError(DataManagement, "AccessRestrictionProvider: invalid entry"); return CHIP_ERROR_INVALID_ARGUMENT; } + + Entry updatedEntry = entry; + updatedEntry.fabricIndex = fabricIndex; + updatedEntries.push_back(updatedEntry); } + mFabricEntries[fabricIndex] = std::move(updatedEntries); + for (Listener * listener = mListeners; listener != nullptr; listener = listener->mNext) { listener->RestrictionListChanged(fabricIndex); diff --git a/src/app/clusters/access-control-server/access-control-server.cpp b/src/app/clusters/access-control-server/access-control-server.cpp index 99c460d4a49b38..4876e54652b2f0 100644 --- a/src/app/clusters/access-control-server/access-control-server.cpp +++ b/src/app/clusters/access-control-server/access-control-server.cpp @@ -481,7 +481,7 @@ CHIP_ERROR AccessControlAttribute::ReadCommissioningArl(AttributeValueEncoder & for (auto & entry : entries) { - ArlStorage::EncodableEntry encodableEntry(entry); + ArlStorage::CommissioningEncodableEntry encodableEntry(entry); ReturnErrorOnFailure(encoder.Encode(encodableEntry)); } return CHIP_NO_ERROR; diff --git a/src/app/server/ArlStorage.cpp b/src/app/server/ArlStorage.cpp index a0d6c38c855029..6193f59ff0075a 100644 --- a/src/app/server/ArlStorage.cpp +++ b/src/app/server/ArlStorage.cpp @@ -74,20 +74,10 @@ CHIP_ERROR Convert(AccessRestrictionProvider::Type from, StagingRestrictionType return CHIP_NO_ERROR; } -} // namespace - -namespace chip { -namespace app { - -CHIP_ERROR ArlStorage::DecodableEntry::Decode(TLV::TLVReader & reader) +CHIP_ERROR ParseRestrictions(const chip::app::DataModel::DecodableList<Clusters::AccessControl::Structs::AccessRestrictionStruct::DecodableType> & source, + std::vector<AccessRestrictionProvider::Restriction> & destination) { - ReturnErrorOnFailure(mStagingEntry.Decode(reader)); - - mEntry.fabricIndex = mStagingEntry.GetFabricIndex(); - mEntry.endpointNumber = mStagingEntry.endpoint; - mEntry.clusterId = mStagingEntry.cluster; - - auto iterator = mStagingEntry.restrictions.begin(); + auto iterator = source.begin(); while (iterator.Next()) { auto & tmp = iterator.GetValue(); @@ -99,13 +89,61 @@ CHIP_ERROR ArlStorage::DecodableEntry::Decode(TLV::TLVReader & reader) restriction.id.SetValue(tmp.id.Value()); } - mEntry.restrictions.push_back(restriction); + destination.push_back(restriction); } ReturnErrorOnFailure(iterator.GetStatus()); return CHIP_NO_ERROR; } +CHIP_ERROR StageEntryRestrictions(const std::vector<AccessRestrictionProvider::Restriction> & source, + StagingRestriction destination[], size_t destinationCount) +{ + size_t count = source.size(); + if (count > 0 && count <= destinationCount) + { + for (size_t i = 0; i < count; i++) + { + auto restriction = source[i]; + ReturnErrorOnFailure(Convert(restriction.restrictionType, destination[i].type)); + + if (restriction.id.HasValue()) + { + destination[i].id.SetNonNull(restriction.id.Value()); + } + } + } + else + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + + return CHIP_NO_ERROR; +} + +} // namespace + +namespace chip { +namespace app { + +CHIP_ERROR ArlStorage::DecodableEntry::Decode(TLV::TLVReader & reader) +{ + ReturnErrorOnFailure(mStagingEntry.Decode(reader)); + + mEntry.fabricIndex = mStagingEntry.GetFabricIndex(); + mEntry.endpointNumber = mStagingEntry.endpoint; + mEntry.clusterId = mStagingEntry.cluster; + ReturnErrorOnFailure(ParseRestrictions(mStagingEntry.restrictions, mEntry.restrictions)); + return CHIP_NO_ERROR; +} + +CHIP_ERROR ArlStorage::CommissioningEncodableEntry::Encode(TLV::TLVWriter & writer, TLV::Tag tag) const +{ + ReturnErrorOnFailure(Stage()); + ReturnErrorOnFailure(mStagingEntry.Encode(writer, tag)); + return CHIP_NO_ERROR; +} + CHIP_ERROR ArlStorage::EncodableEntry::EncodeForRead(TLV::TLVWriter & writer, TLV::Tag tag, FabricIndex fabric) const { ReturnErrorOnFailure(Stage()); @@ -120,36 +158,23 @@ CHIP_ERROR ArlStorage::EncodableEntry::EncodeForWrite(TLV::TLVWriter & writer, T return CHIP_NO_ERROR; } -CHIP_ERROR ArlStorage::EncodableEntry::Stage() const +CHIP_ERROR ArlStorage::CommissioningEncodableEntry::Stage() const { - mStagingEntry.fabricIndex = mEntry.fabricIndex; mStagingEntry.endpoint = mEntry.endpointNumber; mStagingEntry.cluster = mEntry.clusterId; + ReturnErrorOnFailure(StageEntryRestrictions(mEntry.restrictions, mStagingRestrictions, sizeof(mStagingRestrictions) / sizeof(mStagingRestrictions[0]))); + mStagingEntry.restrictions = Span<StagingRestriction>(mStagingRestrictions, mEntry.restrictions.size()); - { - size_t count = mEntry.restrictions.size(); - if (count > 0 && count <= CHIP_CONFIG_ACCESS_RESTRICTION_MAX_RESTRICTIONS_PER_ENTRY) - { - for (size_t i = 0; i < count; i++) - { - auto restriction = mEntry.restrictions[i]; - StagingRestriction tmp; - ReturnErrorOnFailure(Convert(restriction.restrictionType, tmp.type)); - - if (restriction.id.HasValue()) - { - tmp.id.SetNonNull(restriction.id.Value()); - } + return CHIP_NO_ERROR; +} - mStagingRestrictions[i] = tmp; - } - mStagingEntry.restrictions = Span<StagingRestriction>(mStagingRestrictions, count); - } - else - { - return CHIP_ERROR_INVALID_ARGUMENT; - } - } +CHIP_ERROR ArlStorage::EncodableEntry::Stage() const +{ + mStagingEntry.fabricIndex = mEntry.fabricIndex; + mStagingEntry.endpoint = mEntry.endpointNumber; + mStagingEntry.cluster = mEntry.clusterId; + ReturnErrorOnFailure(StageEntryRestrictions(mEntry.restrictions, mStagingRestrictions, sizeof(mStagingRestrictions) / sizeof(mStagingRestrictions[0]))); + mStagingEntry.restrictions = Span<StagingRestriction>(mStagingRestrictions, mEntry.restrictions.size()); return CHIP_NO_ERROR; } diff --git a/src/app/server/ArlStorage.h b/src/app/server/ArlStorage.h index c87f2e0d99a2f5..bf1f5e975b8990 100644 --- a/src/app/server/ArlStorage.h +++ b/src/app/server/ArlStorage.h @@ -42,6 +42,41 @@ namespace app { class ArlStorage { public: + /** + * Used for decoding commissioning access restriction entries. + * + * Typically used temporarily on the stack to decode: + * - source: TLV + * - staging: generated cluster level code + * - destination: system level access restriction entry + */ + class CommissioningDecodableEntry + { + using Entry = Access::AccessRestrictionProvider::Entry; + using StagingEntry = Clusters::AccessControl::Structs::CommissioningAccessRestrictionEntryStruct::DecodableType; + + public: + CommissioningDecodableEntry() = default; + + /** + * Reader decodes into a staging entry, which is then unstaged + * into a member entry. + */ + CHIP_ERROR Decode(TLV::TLVReader & reader); + + Entry & GetEntry() { return mEntry; } + + const Entry & GetEntry() const { return mEntry; } + + public: + static constexpr bool kIsFabricScoped = false; + + private: + Entry mEntry; + + StagingEntry mStagingEntry; + }; + /** * Used for decoding access restriction entries. * @@ -79,6 +114,48 @@ class ArlStorage StagingEntry mStagingEntry; }; + /** + * Used for encoding commissionable access restriction entries. + * + * Typically used temporarily on the stack to encode: + * - source: system level access restriction entry + * - staging: generated cluster level code + * - destination: TLV + */ + class CommissioningEncodableEntry + { + using Entry = Access::AccessRestrictionProvider::Entry; + using StagingEntry = Clusters::AccessControl::Structs::CommissioningAccessRestrictionEntryStruct::Type; + using StagingRestriction = Clusters::AccessControl::Structs::AccessRestrictionStruct::Type; + + public: + CommissioningEncodableEntry(const Entry & entry) : mEntry(entry) {} + + /** + * Constructor-provided entry is staged into a staging entry, + * which is then encoded into a writer. + */ + CHIP_ERROR Encode(TLV::TLVWriter & aWriter, TLV::Tag aTag) const; + + /** + * Constructor-provided entry is staged into a staging entry. + */ + CHIP_ERROR Stage() const; + + StagingEntry & GetStagingEntry() { return mStagingEntry; } + + const StagingEntry & GetStagingEntry() const { return mStagingEntry; } + + public: + static constexpr bool kIsFabricScoped = false; + + private: + Entry mEntry; + + mutable StagingEntry mStagingEntry; + mutable StagingRestriction mStagingRestrictions[CHIP_CONFIG_ACCESS_RESTRICTION_MAX_RESTRICTIONS_PER_ENTRY]; + }; + /** * Used for encoding access restriction entries. * diff --git a/src/app/server/DefaultArlStorage.cpp b/src/app/server/DefaultArlStorage.cpp index 9f43054a817d0f..2bc5e24a56246e 100644 --- a/src/app/server/DefaultArlStorage.cpp +++ b/src/app/server/DefaultArlStorage.cpp @@ -101,7 +101,7 @@ class : public Listener // first clear out any existing entries CHIP_ERROR err; size_t index = 0; - for (size_t index = 0; /**/; ++index) + for (index = 0; /**/; ++index) { err = mPersistentStorage->SyncDeleteKeyValue( DefaultStorageKeyAllocator::AccessControlArlEntry(fabricIndex, index).KeyName()); @@ -114,6 +114,7 @@ class : public Listener const std::vector<Entry> * entries; SuccessOrExit(err = GetAccessControl().GetAccessRestrictionProvider()->GetEntries(fabricIndex, entries)); VerifyOrExit(entries != nullptr, err = CHIP_ERROR_INCORRECT_STATE); + index = 0; for (auto & entry : *entries) { uint8_t buffer[kEncodedEntryTotalBytes] = { 0 }; @@ -209,7 +210,7 @@ CHIP_ERROR DefaultArlStorage::Init(PersistentStorageDelegate & persistentStorage entries.push_back(decodableEntry.GetEntry()); count++; } - GetAccessControl().GetAccessRestrictionProvider()->SetEntries(fabric, commissioningEntries); + GetAccessControl().GetAccessRestrictionProvider()->SetEntries(fabric, entries); } ChipLogProgress(DataManagement, "DefaultArlStorage: %u commissioning entries loaded, %u fabric entries loaded", diff --git a/src/lib/support/DefaultStorageKeyAllocator.h b/src/lib/support/DefaultStorageKeyAllocator.h index dce9975cd35f2b..8cc1bd67a5784c 100644 --- a/src/lib/support/DefaultStorageKeyAllocator.h +++ b/src/lib/support/DefaultStorageKeyAllocator.h @@ -132,7 +132,7 @@ class DefaultStorageKeyAllocator static StorageKeyName AccessControlCommissioningArlEntry(size_t index) { - return StorageKeyName::Formatted("g/car/0/%x", static_cast<unsigned>(index)); + return StorageKeyName::Formatted("g/car/%x", static_cast<unsigned>(index)); } static StorageKeyName AccessControlArlEntry(FabricIndex fabric, size_t index) From 5a161e723ce7560f5a149746ddad720a2d2190df Mon Sep 17 00:00:00 2001 From: Thomas Lea <thomas_lea@comcast.com> Date: Thu, 22 Aug 2024 09:08:05 -0500 Subject: [PATCH 05/22] Reworked ARL storage Previously ARL related data was persisted in KVS. This has been removed and now the responsibility for managing/maintaining the related data (CommissioningARL and ARL attributes) is up to the app to set on AccessRestrictionProvider class. --- examples/platform/linux/AppMain.cpp | 2 - src/app/chip_data_model.gni | 6 + .../access-control-server/ArlEncoder.cpp} | 123 ++++------ .../access-control-server/ArlEncoder.h} | 107 +------- .../access-control-server.cpp | 13 +- src/app/server/BUILD.gn | 9 - src/app/server/DefaultArlStorage.cpp | 230 ------------------ src/app/server/DefaultArlStorage.h | 37 --- src/app/server/Server.cpp | 14 +- src/app/server/Server.h | 11 - 10 files changed, 75 insertions(+), 477 deletions(-) rename src/app/{server/ArlStorage.cpp => clusters/access-control-server/ArlEncoder.cpp} (66%) rename src/app/{server/ArlStorage.h => clusters/access-control-server/ArlEncoder.h} (55%) delete mode 100644 src/app/server/DefaultArlStorage.cpp delete mode 100644 src/app/server/DefaultArlStorage.h diff --git a/examples/platform/linux/AppMain.cpp b/examples/platform/linux/AppMain.cpp index ab0f6214fae068..ed9003fbe36c23 100644 --- a/examples/platform/linux/AppMain.cpp +++ b/examples/platform/linux/AppMain.cpp @@ -105,7 +105,6 @@ #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS #include "ExampleAccessRestrictionProvider.h" -#include <app/server/DefaultArlStorage.h> #endif #if CHIP_DEVICE_LAYER_TARGET_DARWIN @@ -605,7 +604,6 @@ void ChipLinuxAppMainLoop(AppMainLoopImplementation * impl) #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS initParams.accessRestrictionProvider = exampleAccessRestrictionProvider.get(); - initParams.arlStorage = new app::DefaultArlStorage(); #endif // Init ZCL Data Model and CHIP App Server diff --git a/src/app/chip_data_model.gni b/src/app/chip_data_model.gni index 3e4448a3bee467..397726914642c5 100644 --- a/src/app/chip_data_model.gni +++ b/src/app/chip_data_model.gni @@ -434,6 +434,12 @@ template("chip_data_model") { "${_app_root}/clusters/${cluster}/PresetStructWithOwnedMembers.h", "${_app_root}/clusters/${cluster}/thermostat-delegate.h", ] + } else if (cluster == "access-control-server") { + sources += [ + "${_app_root}/clusters/${cluster}/${cluster}.cpp", + "${_app_root}/clusters/${cluster}/ArlEncoder.cpp", + "${_app_root}/clusters/${cluster}/ArlEncoder.h", + ] } else { sources += [ "${_app_root}/clusters/${cluster}/${cluster}.cpp" ] } diff --git a/src/app/server/ArlStorage.cpp b/src/app/clusters/access-control-server/ArlEncoder.cpp similarity index 66% rename from src/app/server/ArlStorage.cpp rename to src/app/clusters/access-control-server/ArlEncoder.cpp index 6193f59ff0075a..843a74553aacbc 100644 --- a/src/app/server/ArlStorage.cpp +++ b/src/app/clusters/access-control-server/ArlEncoder.cpp @@ -15,9 +15,7 @@ * limitations under the License. */ -#include <app/server/ArlStorage.h> - -#include <lib/support/DefaultStorageKeyAllocator.h> +#include "ArlEncoder.h" using namespace chip; using namespace chip::app; @@ -30,7 +28,38 @@ using StagingRestriction = Clusters::AccessControl::Structs::AccessRestricti namespace { -CHIP_ERROR Convert(StagingRestrictionType from, AccessRestrictionProvider::Type & to) +CHIP_ERROR StageEntryRestrictions(const std::vector<AccessRestrictionProvider::Restriction> & source, + StagingRestriction destination[], size_t destinationCount) +{ + size_t count = source.size(); + if (count > 0 && count <= destinationCount) + { + for (size_t i = 0; i < count; i++) + { + auto restriction = source[i]; + ReturnErrorOnFailure(ArlEncoder::Convert(restriction.restrictionType, destination[i].type)); + + if (restriction.id.HasValue()) + { + destination[i].id.SetNonNull(restriction.id.Value()); + } + } + } + else + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + + return CHIP_NO_ERROR; +} + +} // namespace + +namespace chip { +namespace app { + +CHIP_ERROR ArlEncoder::Convert(Clusters::AccessControl::AccessRestrictionTypeEnum from, + Access::AccessRestrictionProvider::Type & to) { switch (from) { @@ -52,7 +81,8 @@ CHIP_ERROR Convert(StagingRestrictionType from, AccessRestrictionProvider::Type return CHIP_NO_ERROR; } -CHIP_ERROR Convert(AccessRestrictionProvider::Type from, StagingRestrictionType & to) +CHIP_ERROR ArlEncoder::Convert(Access::AccessRestrictionProvider::Type from, + Clusters::AccessControl::AccessRestrictionTypeEnum & to) { switch (from) { @@ -74,106 +104,45 @@ CHIP_ERROR Convert(AccessRestrictionProvider::Type from, StagingRestrictionType return CHIP_NO_ERROR; } -CHIP_ERROR ParseRestrictions(const chip::app::DataModel::DecodableList<Clusters::AccessControl::Structs::AccessRestrictionStruct::DecodableType> & source, - std::vector<AccessRestrictionProvider::Restriction> & destination) -{ - auto iterator = source.begin(); - while (iterator.Next()) - { - auto & tmp = iterator.GetValue(); - AccessRestrictionProvider::Restriction restriction; - ReturnErrorOnFailure(Convert(tmp.type, restriction.restrictionType)); - - if (!tmp.id.IsNull()) - { - restriction.id.SetValue(tmp.id.Value()); - } - - destination.push_back(restriction); - } - ReturnErrorOnFailure(iterator.GetStatus()); - - return CHIP_NO_ERROR; -} - -CHIP_ERROR StageEntryRestrictions(const std::vector<AccessRestrictionProvider::Restriction> & source, - StagingRestriction destination[], size_t destinationCount) -{ - size_t count = source.size(); - if (count > 0 && count <= destinationCount) - { - for (size_t i = 0; i < count; i++) - { - auto restriction = source[i]; - ReturnErrorOnFailure(Convert(restriction.restrictionType, destination[i].type)); - - if (restriction.id.HasValue()) - { - destination[i].id.SetNonNull(restriction.id.Value()); - } - } - } - else - { - return CHIP_ERROR_INVALID_ARGUMENT; - } - - return CHIP_NO_ERROR; -} - -} // namespace - -namespace chip { -namespace app { - -CHIP_ERROR ArlStorage::DecodableEntry::Decode(TLV::TLVReader & reader) -{ - ReturnErrorOnFailure(mStagingEntry.Decode(reader)); - - mEntry.fabricIndex = mStagingEntry.GetFabricIndex(); - mEntry.endpointNumber = mStagingEntry.endpoint; - mEntry.clusterId = mStagingEntry.cluster; - ReturnErrorOnFailure(ParseRestrictions(mStagingEntry.restrictions, mEntry.restrictions)); - return CHIP_NO_ERROR; -} - -CHIP_ERROR ArlStorage::CommissioningEncodableEntry::Encode(TLV::TLVWriter & writer, TLV::Tag tag) const +CHIP_ERROR ArlEncoder::CommissioningEncodableEntry::Encode(TLV::TLVWriter & writer, TLV::Tag tag) const { ReturnErrorOnFailure(Stage()); ReturnErrorOnFailure(mStagingEntry.Encode(writer, tag)); return CHIP_NO_ERROR; } -CHIP_ERROR ArlStorage::EncodableEntry::EncodeForRead(TLV::TLVWriter & writer, TLV::Tag tag, FabricIndex fabric) const +CHIP_ERROR ArlEncoder::EncodableEntry::EncodeForRead(TLV::TLVWriter & writer, TLV::Tag tag, FabricIndex fabric) const { ReturnErrorOnFailure(Stage()); ReturnErrorOnFailure(mStagingEntry.EncodeForRead(writer, tag, fabric)); return CHIP_NO_ERROR; } -CHIP_ERROR ArlStorage::EncodableEntry::EncodeForWrite(TLV::TLVWriter & writer, TLV::Tag tag) const +CHIP_ERROR ArlEncoder::EncodableEntry::EncodeForWrite(TLV::TLVWriter & writer, TLV::Tag tag) const { ReturnErrorOnFailure(Stage()); ReturnErrorOnFailure(mStagingEntry.EncodeForWrite(writer, tag)); return CHIP_NO_ERROR; } -CHIP_ERROR ArlStorage::CommissioningEncodableEntry::Stage() const +CHIP_ERROR ArlEncoder::CommissioningEncodableEntry::Stage() const { - mStagingEntry.endpoint = mEntry.endpointNumber; - mStagingEntry.cluster = mEntry.clusterId; - ReturnErrorOnFailure(StageEntryRestrictions(mEntry.restrictions, mStagingRestrictions, sizeof(mStagingRestrictions) / sizeof(mStagingRestrictions[0]))); + mStagingEntry.endpoint = mEntry.endpointNumber; + mStagingEntry.cluster = mEntry.clusterId; + ReturnErrorOnFailure(StageEntryRestrictions(mEntry.restrictions, mStagingRestrictions, + sizeof(mStagingRestrictions) / sizeof(mStagingRestrictions[0]))); mStagingEntry.restrictions = Span<StagingRestriction>(mStagingRestrictions, mEntry.restrictions.size()); return CHIP_NO_ERROR; } -CHIP_ERROR ArlStorage::EncodableEntry::Stage() const +CHIP_ERROR ArlEncoder::EncodableEntry::Stage() const { mStagingEntry.fabricIndex = mEntry.fabricIndex; mStagingEntry.endpoint = mEntry.endpointNumber; mStagingEntry.cluster = mEntry.clusterId; - ReturnErrorOnFailure(StageEntryRestrictions(mEntry.restrictions, mStagingRestrictions, sizeof(mStagingRestrictions) / sizeof(mStagingRestrictions[0]))); + ReturnErrorOnFailure(StageEntryRestrictions(mEntry.restrictions, mStagingRestrictions, + sizeof(mStagingRestrictions) / sizeof(mStagingRestrictions[0]))); mStagingEntry.restrictions = Span<StagingRestriction>(mStagingRestrictions, mEntry.restrictions.size()); return CHIP_NO_ERROR; diff --git a/src/app/server/ArlStorage.h b/src/app/clusters/access-control-server/ArlEncoder.h similarity index 55% rename from src/app/server/ArlStorage.h rename to src/app/clusters/access-control-server/ArlEncoder.h index bf1f5e975b8990..6832c739dfb3d6 100644 --- a/src/app/server/ArlStorage.h +++ b/src/app/clusters/access-control-server/ArlEncoder.h @@ -18,101 +18,28 @@ #pragma once #include <access/AccessRestrictionProvider.h> -#include <credentials/FabricTable.h> -#include <lib/core/CHIPPersistentStorageDelegate.h> - #include <app-common/zap-generated/cluster-objects.h> +#include <credentials/FabricTable.h> namespace chip { namespace app { /** - * Storage specifically for access restriction entries, which correspond to - * the ARL attribute of the access control cluster. - * - * An object of this class should be initialized directly after the access - * control module is initialized, as it will populate entries in the system - * module from storage, and also install a listener in the system module to - * keep storage up to date as entries change. - * - * This class also provides facilities for converting between access restriction + * This class provides facilities for converting between access restriction * entries (as used by the system module) and access restriction entries (as used * by the generated cluster code). */ -class ArlStorage +class ArlEncoder { public: - /** - * Used for decoding commissioning access restriction entries. - * - * Typically used temporarily on the stack to decode: - * - source: TLV - * - staging: generated cluster level code - * - destination: system level access restriction entry - */ - class CommissioningDecodableEntry - { - using Entry = Access::AccessRestrictionProvider::Entry; - using StagingEntry = Clusters::AccessControl::Structs::CommissioningAccessRestrictionEntryStruct::DecodableType; - - public: - CommissioningDecodableEntry() = default; - - /** - * Reader decodes into a staging entry, which is then unstaged - * into a member entry. - */ - CHIP_ERROR Decode(TLV::TLVReader & reader); + ArlEncoder() = default; + ~ArlEncoder() = default; - Entry & GetEntry() { return mEntry; } + static CHIP_ERROR Convert(Clusters::AccessControl::AccessRestrictionTypeEnum from, + Access::AccessRestrictionProvider::Type & to); - const Entry & GetEntry() const { return mEntry; } - - public: - static constexpr bool kIsFabricScoped = false; - - private: - Entry mEntry; - - StagingEntry mStagingEntry; - }; - - /** - * Used for decoding access restriction entries. - * - * Typically used temporarily on the stack to decode: - * - source: TLV - * - staging: generated cluster level code - * - destination: system level access restriction entry - */ - class DecodableEntry - { - using Entry = Access::AccessRestrictionProvider::Entry; - using StagingEntry = Clusters::AccessControl::Structs::AccessRestrictionEntryStruct::DecodableType; - - public: - DecodableEntry() = default; - - /** - * Reader decodes into a staging entry, which is then unstaged - * into a member entry. - */ - CHIP_ERROR Decode(TLV::TLVReader & reader); - - Entry & GetEntry() { return mEntry; } - - const Entry & GetEntry() const { return mEntry; } - - public: - static constexpr bool kIsFabricScoped = true; - - void SetFabricIndex(FabricIndex fabricIndex) { mEntry.fabricIndex = fabricIndex; } - - private: - Entry mEntry; - - StagingEntry mStagingEntry; - }; + static CHIP_ERROR Convert(Access::AccessRestrictionProvider::Type from, + Clusters::AccessControl::AccessRestrictionTypeEnum & to); /** * Used for encoding commissionable access restriction entries. @@ -120,7 +47,6 @@ class ArlStorage * Typically used temporarily on the stack to encode: * - source: system level access restriction entry * - staging: generated cluster level code - * - destination: TLV */ class CommissioningEncodableEntry { @@ -162,7 +88,6 @@ class ArlStorage * Typically used temporarily on the stack to encode: * - source: system level access restriction entry * - staging: generated cluster level code - * - destination: TLV */ class EncodableEntry { @@ -205,20 +130,6 @@ class ArlStorage mutable StagingEntry mStagingEntry; mutable StagingRestriction mStagingRestrictions[CHIP_CONFIG_ACCESS_RESTRICTION_MAX_RESTRICTIONS_PER_ENTRY]; }; - - virtual ~ArlStorage() = default; - - /** - * Initialize should be called after chip::Access::AccessControl is initialized. - * - * Implementations should take this opportunity to populate AccessControl with ARL entries - * loaded from persistent storage. A half-open range of fabrics [first, last) is provided - * so this can be done on a per-fabric basis. - * - * Implementations should also install an entry change listener on AccessControl to maintain - * ARL entries in persistent storage as they are changed. - */ - virtual CHIP_ERROR Init(PersistentStorageDelegate & persistentStorage, ConstFabricIterator first, ConstFabricIterator last) = 0; }; } // namespace app diff --git a/src/app/clusters/access-control-server/access-control-server.cpp b/src/app/clusters/access-control-server/access-control-server.cpp index 4876e54652b2f0..3707af022d3ff0 100644 --- a/src/app/clusters/access-control-server/access-control-server.cpp +++ b/src/app/clusters/access-control-server/access-control-server.cpp @@ -18,8 +18,8 @@ #include <access/AccessControl.h> #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS +#include "ArlEncoder.h" #include <access/AccessRestrictionProvider.h> -#include <app/server/ArlStorage.h> #endif #include <app-common/zap-generated/cluster-objects.h> @@ -481,7 +481,7 @@ CHIP_ERROR AccessControlAttribute::ReadCommissioningArl(AttributeValueEncoder & for (auto & entry : entries) { - ArlStorage::CommissioningEncodableEntry encodableEntry(entry); + ArlEncoder::CommissioningEncodableEntry encodableEntry(entry); ReturnErrorOnFailure(encoder.Encode(encodableEntry)); } return CHIP_NO_ERROR; @@ -506,7 +506,7 @@ CHIP_ERROR AccessControlAttribute::ReadArl(AttributeValueEncoder & aEncoder) VerifyOrReturnError(entries != nullptr, CHIP_ERROR_INCORRECT_STATE); for (auto & entry : *entries) { - ArlStorage::EncodableEntry encodableEntry(entry); + ArlEncoder::EncodableEntry encodableEntry(entry); ReturnErrorOnFailure(encoder.Encode(encodableEntry)); } } @@ -649,7 +649,12 @@ bool emberAfAccessControlClusterReviewFabricRestrictionsCallback( while (restrictionIter.Next()) { AccessRestrictionProvider::Restriction restriction; - restriction.restrictionType = static_cast<AccessRestrictionProvider::Type>(restrictionIter.GetValue().type); + if (ArlEncoder::Convert(restrictionIter.GetValue().type, restriction.restrictionType) != CHIP_NO_ERROR) + { + ChipLogError(DataManagement, "AccessControlCluster: invalid restriction type conversion"); + return true; + } + if (!restrictionIter.GetValue().id.IsNull()) { restriction.id.SetValue(restrictionIter.GetValue().id.Value()); diff --git a/src/app/server/BUILD.gn b/src/app/server/BUILD.gn index 1a82cd911f0c51..58524c3a648aab 100644 --- a/src/app/server/BUILD.gn +++ b/src/app/server/BUILD.gn @@ -80,13 +80,4 @@ static_library("server") { [ "${chip_root}/src/app/icd/server:default-check-in-back-off" ] } } - - if (chip_enable_access_restrictions) { - sources += [ - "ArlStorage.cpp", - "ArlStorage.h", - "DefaultArlStorage.cpp", - "DefaultArlStorage.h", - ] - } } diff --git a/src/app/server/DefaultArlStorage.cpp b/src/app/server/DefaultArlStorage.cpp deleted file mode 100644 index 2bc5e24a56246e..00000000000000 --- a/src/app/server/DefaultArlStorage.cpp +++ /dev/null @@ -1,230 +0,0 @@ -/* - * - * Copyright (c) 2024 Project CHIP Authors - * - * 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 <app/server/DefaultArlStorage.h> - -#include <access/AccessControl.h> -#include <lib/support/DefaultStorageKeyAllocator.h> - -using namespace chip; -using namespace chip::app; -using namespace chip::Access; - -using EncodableEntry = ArlStorage::EncodableEntry; -using Entry = AccessRestrictionProvider::Entry; -using Listener = AccessRestrictionProvider::Listener; -using StagingRestriction = Clusters::AccessControl::Structs::AccessRestrictionEntryStruct::Type; -using Restriction = AccessRestrictionProvider::Restriction; - -namespace { - -/* -Size calculation for TLV encoded entry. - -Because EncodeForWrite is used without an accessing fabric, the fabric index is -not encoded. However, let's assume it is. This yields 17 bytes total overhead, -but it's wise to add a few more for safety. - -Each restriction requires 11 bytes. - -DATA C T L V NOTES -structure (anonymous) 1 0x15 - field 0 endpoint 1 1 1 - field 1 cluster 1 1 4 - field 2 restrictions 1 1 - structure (anonymous) 1 per restriction - field 0 type 1 1 1 - field 1 id 1 1 4 - end structure 1 - end list 1 0x18 - field 254 fabric index 1 1 1 not written -end structure 1 0x18 -*/ - -constexpr int kEncodedEntryOverheadBytes = 17 + 8; -constexpr int kEncodedEntryRestrictionBytes = 11 * CHIP_CONFIG_ACCESS_RESTRICTION_MAX_RESTRICTIONS_PER_ENTRY; -constexpr int kEncodedEntryTotalBytes = kEncodedEntryOverheadBytes + kEncodedEntryRestrictionBytes; - -class : public Listener -{ -public: - void CommissioningRestrictionListChanged() override - { - // first clear out any existing entries - CHIP_ERROR err; - for (size_t index = 0; /**/; ++index) - { - err = mPersistentStorage->SyncDeleteKeyValue( - DefaultStorageKeyAllocator::AccessControlCommissioningArlEntry(index).KeyName()); - if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) - { - break; - } - } - - auto entries = GetAccessControl().GetAccessRestrictionProvider()->GetCommissioningEntries(); - size_t index = 0; - for (auto & entry : entries) - { - uint8_t buffer[kEncodedEntryTotalBytes] = { 0 }; - TLV::TLVWriter writer; - writer.Init(buffer); - EncodableEntry encodableEntry(entry); - SuccessOrExit(err = encodableEntry.EncodeForWrite(writer, TLV::AnonymousTag())); - SuccessOrExit(err = mPersistentStorage->SyncSetKeyValue( - DefaultStorageKeyAllocator::AccessControlCommissioningArlEntry(index++).KeyName(), buffer, - static_cast<uint16_t>(writer.GetLengthWritten()))); - } - - return; - - exit: - ChipLogError(DataManagement, "DefaultArlStorage: failed %" CHIP_ERROR_FORMAT, err.Format()); - } - - void RestrictionListChanged(FabricIndex fabricIndex) override - { - // first clear out any existing entries - CHIP_ERROR err; - size_t index = 0; - for (index = 0; /**/; ++index) - { - err = mPersistentStorage->SyncDeleteKeyValue( - DefaultStorageKeyAllocator::AccessControlArlEntry(fabricIndex, index).KeyName()); - if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) - { - break; - } - } - - const std::vector<Entry> * entries; - SuccessOrExit(err = GetAccessControl().GetAccessRestrictionProvider()->GetEntries(fabricIndex, entries)); - VerifyOrExit(entries != nullptr, err = CHIP_ERROR_INCORRECT_STATE); - index = 0; - for (auto & entry : *entries) - { - uint8_t buffer[kEncodedEntryTotalBytes] = { 0 }; - TLV::TLVWriter writer; - writer.Init(buffer); - EncodableEntry encodableEntry(entry); - SuccessOrExit(err = encodableEntry.EncodeForWrite(writer, TLV::AnonymousTag())); - SuccessOrExit(err = mPersistentStorage->SyncSetKeyValue( - DefaultStorageKeyAllocator::AccessControlArlEntry(fabricIndex, index++).KeyName(), buffer, - static_cast<uint16_t>(writer.GetLengthWritten()))); - } - - return; - - exit: - ChipLogError(DataManagement, "DefaultArlStorage: failed %" CHIP_ERROR_FORMAT, err.Format()); - } - - void OnFabricRestrictionReviewUpdate(FabricIndex fabricIndex, uint64_t token, const char * instruction, - const char * redirectUrl) override - {} - - // Must initialize before use. - void Init(PersistentStorageDelegate & persistentStorage) { mPersistentStorage = &persistentStorage; } - -private: - PersistentStorageDelegate * mPersistentStorage = nullptr; - -} sEntryListener; - -} // namespace - -namespace chip { -namespace app { - -CHIP_ERROR DefaultArlStorage::Init(PersistentStorageDelegate & persistentStorage, ConstFabricIterator first, - ConstFabricIterator last) -{ - ChipLogProgress(DataManagement, "DefaultArlStorage: initializing"); - - CHIP_ERROR err; - size_t commissioningCount = 0; - size_t count = 0; - - std::vector<Entry> commissioningEntries; - for (size_t index = 0; /**/; ++index) - { - uint8_t buffer[kEncodedEntryTotalBytes] = { 0 }; - uint16_t size = static_cast<uint16_t>(sizeof(buffer)); - err = persistentStorage.SyncGetKeyValue(DefaultStorageKeyAllocator::AccessControlCommissioningArlEntry(index).KeyName(), - buffer, size); - if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) - { - break; - } - SuccessOrExit(err); - - TLV::TLVReader reader; - reader.Init(buffer, size); - SuccessOrExit(err = reader.Next()); - - DecodableEntry decodableEntry; - SuccessOrExit(err = decodableEntry.Decode(reader)); - - commissioningEntries.push_back(decodableEntry.GetEntry()); - commissioningCount++; - } - GetAccessControl().GetAccessRestrictionProvider()->SetCommissioningEntries(commissioningEntries); - - for (auto it = first; it != last; ++it) - { - std::vector<Entry> entries; - auto fabric = it->GetFabricIndex(); - for (size_t index = 0; /**/; ++index) - { - uint8_t buffer[kEncodedEntryTotalBytes] = { 0 }; - uint16_t size = static_cast<uint16_t>(sizeof(buffer)); - err = persistentStorage.SyncGetKeyValue(DefaultStorageKeyAllocator::AccessControlArlEntry(fabric, index).KeyName(), - buffer, size); - if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) - { - break; - } - SuccessOrExit(err); - - TLV::TLVReader reader; - reader.Init(buffer, size); - SuccessOrExit(err = reader.Next()); - - DecodableEntry decodableEntry; - SuccessOrExit(err = decodableEntry.Decode(reader)); - - entries.push_back(decodableEntry.GetEntry()); - count++; - } - GetAccessControl().GetAccessRestrictionProvider()->SetEntries(fabric, entries); - } - - ChipLogProgress(DataManagement, "DefaultArlStorage: %u commissioning entries loaded, %u fabric entries loaded", - (unsigned) commissioningCount, (unsigned) count); - - sEntryListener.Init(persistentStorage); - GetAccessControl().GetAccessRestrictionProvider()->AddListener(sEntryListener); - - return CHIP_NO_ERROR; - -exit: - ChipLogError(DataManagement, "DefaultArlStorage: failed %" CHIP_ERROR_FORMAT, err.Format()); - return err; -} - -} // namespace app -} // namespace chip diff --git a/src/app/server/DefaultArlStorage.h b/src/app/server/DefaultArlStorage.h deleted file mode 100644 index ebdc23bbf65913..00000000000000 --- a/src/app/server/DefaultArlStorage.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * - * Copyright (c) 2024 Project CHIP Authors - * - * 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 <app/server/ArlStorage.h> - -namespace chip { -namespace app { - -class DefaultArlStorage : public ArlStorage -{ -public: - /** - * Initialize must be called. It loads ARL entries for all fabrics from persistent storage, - * then installs a listener for the access restriction system module to maintain ARL entries in - * persistent storage so they remain in sync with entries in the access restriction system module. - */ - CHIP_ERROR Init(PersistentStorageDelegate & persistentStorage, ConstFabricIterator first, ConstFabricIterator last) override; -}; - -} // namespace app -} // namespace chip diff --git a/src/app/server/Server.cpp b/src/app/server/Server.cpp index 0484669f97b742..426e3c686572c0 100644 --- a/src/app/server/Server.cpp +++ b/src/app/server/Server.cpp @@ -177,20 +177,16 @@ CHIP_ERROR Server::Init(const ServerInitParams & initParams) SuccessOrExit(err = mAccessControl.Init(initParams.accessDelegate, sDeviceTypeResolver)); Access::SetAccessControl(mAccessControl); - mAclStorage = initParams.aclStorage; - SuccessOrExit(err = mAclStorage->Init(*mDeviceStorage, mFabrics.begin(), mFabrics.end())); - #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS - if (initParams.accessRestrictionProvider != nullptr && initParams.arlStorage != nullptr) + if (initParams.accessRestrictionProvider != nullptr) { - mAccessRestrictionProvider = initParams.accessRestrictionProvider; - mArlStorage = initParams.arlStorage; - - mAccessControl.SetAccessRestrictionProvider(mAccessRestrictionProvider); - SuccessOrExit(err = mArlStorage->Init(*mDeviceStorage, mFabrics.begin(), mFabrics.end())); + mAccessControl.SetAccessRestrictionProvider(initParams.accessRestrictionProvider); } #endif + mAclStorage = initParams.aclStorage; + SuccessOrExit(err = mAclStorage->Init(*mDeviceStorage, mFabrics.begin(), mFabrics.end())); + mGroupsProvider = initParams.groupDataProvider; SetGroupDataProvider(mGroupsProvider); diff --git a/src/app/server/Server.h b/src/app/server/Server.h index d49850d52dfa73..27c850563f6e98 100644 --- a/src/app/server/Server.h +++ b/src/app/server/Server.h @@ -31,9 +31,6 @@ #include <app/TestEventTriggerDelegate.h> #include <app/server/AclStorage.h> #include <app/server/AppDelegate.h> -#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS -#include <app/server/ArlStorage.h> -#endif #include <app/server/CommissioningWindowManager.h> #include <app/server/DefaultAclStorage.h> #include <credentials/CertificateValidityPolicy.h> @@ -171,9 +168,6 @@ struct ServerInitParams // Access Restriction implementation: MUST be injected if MNGD feature enabled. Used to enforce // access restrictions that are managed by the device. Access::AccessRestrictionProvider * accessRestrictionProvider = nullptr; - // ARL storage: MUST be injected if MNGD feature enabled. Used to store ACL entries in - // persistent storage. Must NOT be initialized before being provided. - app::ArlStorage * arlStorage = nullptr; #endif // Network native params can be injected depending on the @@ -692,11 +686,6 @@ class Server Access::AccessControl mAccessControl; app::AclStorage * mAclStorage; -#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS - Access::AccessRestrictionProvider * mAccessRestrictionProvider; - app::ArlStorage * mArlStorage; -#endif - TestEventTriggerDelegate * mTestEventTriggerDelegate; Crypto::OperationalKeystore * mOperationalKeystore; Credentials::OperationalCertificateStore * mOpCertStore; From 459bee42fb4533fb95126636a676342de6648fe4 Mon Sep 17 00:00:00 2001 From: Thomas Lea <thomas_lea@comcast.com> Date: Thu, 22 Aug 2024 10:06:54 -0500 Subject: [PATCH 06/22] Review fixes cleanup ArlEncoder interface. return error to client if arl review request fails return token to client in FabricRestrictionReviewUpdate --- .../linux/ExampleAccessRestrictionProvider.h | 2 +- .../access-control-server/ArlEncoder.h | 38 +++++-------------- .../access-control-server.cpp | 5 +-- 3 files changed, 13 insertions(+), 32 deletions(-) diff --git a/examples/platform/linux/ExampleAccessRestrictionProvider.h b/examples/platform/linux/ExampleAccessRestrictionProvider.h index 28ca38a327a555..9346acdedae29a 100644 --- a/examples/platform/linux/ExampleAccessRestrictionProvider.h +++ b/examples/platform/linux/ExampleAccessRestrictionProvider.h @@ -42,7 +42,7 @@ class ExampleAccessRestrictionProvider : public AccessRestrictionProvider // this example simply removes all restrictions and will generate AccessRestrictionEntryChanged events Access::GetAccessControl().GetAccessRestrictionProvider()->SetEntries(fabricIndex, std::vector<Entry>{}); - chip::app::Clusters::AccessControl::Events::FabricRestrictionReviewUpdate::Type event{ .fabricIndex = fabricIndex }; + chip::app::Clusters::AccessControl::Events::FabricRestrictionReviewUpdate::Type event{ .token = token, .fabricIndex = fabricIndex }; EventNumber eventNumber; ReturnErrorOnFailure(chip::app::LogEvent(event, 0, eventNumber)); diff --git a/src/app/clusters/access-control-server/ArlEncoder.h b/src/app/clusters/access-control-server/ArlEncoder.h index 6832c739dfb3d6..ce0d6336c68c96 100644 --- a/src/app/clusters/access-control-server/ArlEncoder.h +++ b/src/app/clusters/access-control-server/ArlEncoder.h @@ -58,26 +58,16 @@ class ArlEncoder CommissioningEncodableEntry(const Entry & entry) : mEntry(entry) {} /** - * Constructor-provided entry is staged into a staging entry, - * which is then encoded into a writer. + * Encode the constructor-provided entry into the TLV writer. */ CHIP_ERROR Encode(TLV::TLVWriter & aWriter, TLV::Tag aTag) const; - /** - * Constructor-provided entry is staged into a staging entry. - */ - CHIP_ERROR Stage() const; - - StagingEntry & GetStagingEntry() { return mStagingEntry; } - - const StagingEntry & GetStagingEntry() const { return mStagingEntry; } - - public: static constexpr bool kIsFabricScoped = false; private: - Entry mEntry; + CHIP_ERROR Stage() const; + Entry mEntry; mutable StagingEntry mStagingEntry; mutable StagingRestriction mStagingRestrictions[CHIP_CONFIG_ACCESS_RESTRICTION_MAX_RESTRICTIONS_PER_ENTRY]; }; @@ -99,34 +89,26 @@ class ArlEncoder EncodableEntry(const Entry & entry) : mEntry(entry) {} /** - * Constructor-provided entry is staged into a staging entry, - * which is then encoded into a writer. + * Encode the constructor-provided entry into the TLV writer. */ CHIP_ERROR EncodeForRead(TLV::TLVWriter & writer, TLV::Tag tag, FabricIndex fabric) const; /** - * Constructor-provided entry is staged into a staging entry, - * which is then encoded into a writer. + * Encode the constructor-provided entry into the TLV writer. */ CHIP_ERROR EncodeForWrite(TLV::TLVWriter & writer, TLV::Tag tag) const; + FabricIndex GetFabricIndex() const { return mEntry.fabricIndex; } + + static constexpr bool kIsFabricScoped = true; + + private: /** * Constructor-provided entry is staged into a staging entry. */ CHIP_ERROR Stage() const; - StagingEntry & GetStagingEntry() { return mStagingEntry; } - - const StagingEntry & GetStagingEntry() const { return mStagingEntry; } - - public: - static constexpr bool kIsFabricScoped = true; - - FabricIndex GetFabricIndex() const { return mEntry.fabricIndex; } - - private: Entry mEntry; - mutable StagingEntry mStagingEntry; mutable StagingRestriction mStagingRestrictions[CHIP_CONFIG_ACCESS_RESTRICTION_MAX_RESTRICTIONS_PER_ENTRY]; }; diff --git a/src/app/clusters/access-control-server/access-control-server.cpp b/src/app/clusters/access-control-server/access-control-server.cpp index 3707af022d3ff0..038905e44cf3fb 100644 --- a/src/app/clusters/access-control-server/access-control-server.cpp +++ b/src/app/clusters/access-control-server/access-control-server.cpp @@ -676,9 +676,8 @@ bool emberAfAccessControlClusterReviewFabricRestrictionsCallback( } else { - ChipLogError(DataManagement, "AccessControlCluster: restriction check failed: %" CHIP_ERROR_FORMAT, err.Format()); - - // return error to client? + ChipLogError(DataManagement, "AccessControlCluster: restriction review failed: %" CHIP_ERROR_FORMAT, err.Format()); + commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::Failure); } return true; From d0928e7eeee88cfba09388edce556f397f03f7f7 Mon Sep 17 00:00:00 2001 From: Thomas Lea <thomas_lea@comcast.com> Date: Thu, 22 Aug 2024 11:03:02 -0500 Subject: [PATCH 07/22] Fixed GetEntries vector pointer arg --- src/access/AccessRestrictionProvider.h | 8 ++++---- .../access-control-server/access-control-server.cpp | 5 ++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/access/AccessRestrictionProvider.h b/src/access/AccessRestrictionProvider.h index 9178d8685a42f0..0b7be0095f748a 100644 --- a/src/access/AccessRestrictionProvider.h +++ b/src/access/AccessRestrictionProvider.h @@ -116,7 +116,7 @@ class AccessRestrictionProvider AccessRestrictionProvider() = default; virtual ~AccessRestrictionProvider() = default; - AccessRestrictionProvider(const AccessRestrictionProvider &) = delete; + AccessRestrictionProvider(const AccessRestrictionProvider &) = delete; AccessRestrictionProvider & operator=(const AccessRestrictionProvider &) = delete; /** @@ -184,9 +184,9 @@ class AccessRestrictionProvider * Get the restriction entries for a fabric. * * @param [in] fabricIndex the index of the fabric for which to get entries. - * @param [out] entries reference to a const vector to hold the entries. + * @param [out] entries vector to hold the entries. */ - CHIP_ERROR GetEntries(const FabricIndex fabricIndex, const std::vector<Entry> *& entries) const + CHIP_ERROR GetEntries(const FabricIndex fabricIndex, std::vector<Entry> & entries) const { auto it = mFabricEntries.find(fabricIndex); if (it == mFabricEntries.end()) @@ -194,7 +194,7 @@ class AccessRestrictionProvider return CHIP_ERROR_NOT_FOUND; } - entries = &(it->second); + entries = (it->second); return CHIP_NO_ERROR; } diff --git a/src/app/clusters/access-control-server/access-control-server.cpp b/src/app/clusters/access-control-server/access-control-server.cpp index 038905e44cf3fb..3c1412d57a6401 100644 --- a/src/app/clusters/access-control-server/access-control-server.cpp +++ b/src/app/clusters/access-control-server/access-control-server.cpp @@ -501,10 +501,9 @@ CHIP_ERROR AccessControlAttribute::ReadArl(AttributeValueEncoder & aEncoder) { auto fabric = info.GetFabricIndex(); // get entries for fabric - const std::vector<AccessRestrictionProvider::Entry> * entries = nullptr; + std::vector<AccessRestrictionProvider::Entry> entries; ReturnErrorOnFailure(accessRestrictionProvider->GetEntries(fabric, entries)); - VerifyOrReturnError(entries != nullptr, CHIP_ERROR_INCORRECT_STATE); - for (auto & entry : *entries) + for (auto & entry : entries) { ArlEncoder::EncodableEntry encodableEntry(entry); ReturnErrorOnFailure(encoder.Encode(encodableEntry)); From dcc85c70bd1bfa3fcb7ca971f019207dcb56fe41 Mon Sep 17 00:00:00 2001 From: Thomas Lea <thomas_lea@comcast.com> Date: Thu, 22 Aug 2024 12:09:46 -0500 Subject: [PATCH 08/22] Updated core restriction logic/integration --- src/access/AccessControl.cpp | 55 +++++++++++++++++------- src/access/AccessRestrictionProvider.cpp | 9 ++++ src/access/AccessRestrictionProvider.h | 20 +++++++++ 3 files changed, 68 insertions(+), 16 deletions(-) diff --git a/src/access/AccessControl.cpp b/src/access/AccessControl.cpp index d7d597f46b2665..d6667e059ff1d2 100644 --- a/src/access/AccessControl.cpp +++ b/src/access/AccessControl.cpp @@ -356,28 +356,13 @@ CHIP_ERROR AccessControl::Check(const SubjectDescriptor & subjectDescriptor, con VerifyOrReturnError(requestPath.requestType != RequestType::kRequestTypeUnknown, CHIP_ERROR_INVALID_ARGUMENT); } -#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS - if (mAccessRestrictionProvider != nullptr) - { - CHIP_ERROR result = mAccessRestrictionProvider->Check(subjectDescriptor, requestPath); - if (result != CHIP_NO_ERROR) - { - ChipLogProgress(DataManagement, "AccessControl: %s", - (result == CHIP_ERROR_ACCESS_DENIED) ? "denied (restricted)" : "denied (restriction error)"); - return result; - } - } -#endif - { CHIP_ERROR result = mDelegate->Check(subjectDescriptor, requestPath, requestPrivilege); if (result != CHIP_ERROR_NOT_IMPLEMENTED) { #if CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 0 ChipLogProgress(DataManagement, "AccessControl: %s (delegate)", - (result == CHIP_NO_ERROR) ? "allowed" - : (result == CHIP_ERROR_ACCESS_DENIED) ? "denied" - : "error"); + (result == CHIP_NO_ERROR) ? "allowed" : (result == CHIP_ERROR_ACCESS_DENIED) ? "denied" : "error"); #else if (result != CHIP_NO_ERROR) { @@ -385,6 +370,19 @@ CHIP_ERROR AccessControl::Check(const SubjectDescriptor & subjectDescriptor, con (result == CHIP_ERROR_ACCESS_DENIED) ? "denied" : "error"); } #endif // CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 0 + +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + if (result == CHIP_NO_ERROR && mAccessRestrictionProvider != nullptr) + { + result = mAccessRestrictionProvider->Check(subjectDescriptor, requestPath); + if (result != CHIP_NO_ERROR) + { + ChipLogProgress(DataManagement, "AccessControl: %s", + (result == CHIP_ERROR_ACCESS_DENIED) ? "denied (restricted)" : "denied (restriction error)"); + return result; + } + } +#endif return result; } } @@ -397,6 +395,18 @@ CHIP_ERROR AccessControl::Check(const SubjectDescriptor & subjectDescriptor, con #if CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 1 ChipLogProgress(DataManagement, "AccessControl: implicit admin (PASE)"); #endif // CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 1 +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + if (mAccessRestrictionProvider != nullptr) + { + CHIP_ERROR result = mAccessRestrictionProvider->CheckForCommissioning(subjectDescriptor, requestPath); + if (result != CHIP_NO_ERROR) + { + ChipLogProgress(DataManagement, "AccessControl: %s", + (result == CHIP_ERROR_ACCESS_DENIED) ? "denied (restricted)" : "denied (restriction error)"); + return result; + } + } +#endif return CHIP_NO_ERROR; } @@ -506,6 +516,19 @@ CHIP_ERROR AccessControl::Check(const SubjectDescriptor & subjectDescriptor, con ChipLogProgress(DataManagement, "AccessControl: allowed"); #endif // CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 0 +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + if (mAccessRestrictionProvider != nullptr) + { + CHIP_ERROR result = mAccessRestrictionProvider->Check(subjectDescriptor, requestPath); + if (result != CHIP_NO_ERROR) + { + ChipLogProgress(DataManagement, "AccessControl: %s", + (result == CHIP_ERROR_ACCESS_DENIED) ? "denied (restricted)" : "denied (restriction error)"); + return result; + } + } +#endif + return CHIP_NO_ERROR; } diff --git a/src/access/AccessRestrictionProvider.cpp b/src/access/AccessRestrictionProvider.cpp index bcaed16afd71ce..a0caccc544081a 100644 --- a/src/access/AccessRestrictionProvider.cpp +++ b/src/access/AccessRestrictionProvider.cpp @@ -121,7 +121,16 @@ CHIP_ERROR AccessRestrictionProvider::SetEntries(const FabricIndex fabricIndex, return CHIP_NO_ERROR; } +CHIP_ERROR AccessRestrictionProvider::CheckForCommissioning(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath) +{ + return DoCheck(mCommissioningEntries, subjectDescriptor, requestPath); +} CHIP_ERROR AccessRestrictionProvider::Check(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath) +{ + return DoCheck(mFabricEntries[subjectDescriptor.fabricIndex], subjectDescriptor, requestPath); +} + +CHIP_ERROR AccessRestrictionProvider::DoCheck(const std::vector<Entry> entries, const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath) { ChipLogProgress(DataManagement, "AccessRestrictionProvider: action %d", static_cast<int>(requestPath.requestType)); diff --git a/src/access/AccessRestrictionProvider.h b/src/access/AccessRestrictionProvider.h index 0b7be0095f748a..1880e62085ccfb 100644 --- a/src/access/AccessRestrictionProvider.h +++ b/src/access/AccessRestrictionProvider.h @@ -148,6 +148,20 @@ class AccessRestrictionProvider */ void RemoveListener(Listener & listener); + /** + * Check whether access by a subject descriptor to a request path should be restricted (denied) for the given action + * during commissioning by using the CommissioningEntries. + * + * These restrictions are are only a part of overall access evaluation. + * + * If access is not restricted, CHIP_NO_ERROR will be returned. + * + * @retval CHIP_ERROR_ACCESS_DENIED if access is denied. + * @retval other errors should also be treated as restricted/denied. + * @retval CHIP_NO_ERROR if access is not restricted/denied. + */ + CHIP_ERROR CheckForCommissioning(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath); + /** * Check whether access by a subject descriptor to a request path should be restricted (denied) for the given action. * These restrictions are are only a part of overall access evaluation. @@ -215,6 +229,12 @@ class AccessRestrictionProvider private: bool IsEntryValid(const Entry & entry) const; + /** + * Perform the access restriction check using the given entries. + */ + CHIP_ERROR DoCheck(const std::vector<Entry> entries, const SubjectDescriptor & subjectDescriptor, + const RequestPath & requestPath); + uint64_t mNextToken = 1; Listener * mListeners = nullptr; std::vector<Entry> mCommissioningEntries; From a3d130067988b0827d3efad7ec87257a7225f7e0 Mon Sep 17 00:00:00 2001 From: "Restyled.io" <commits@restyled.io> Date: Thu, 22 Aug 2024 17:10:26 +0000 Subject: [PATCH 09/22] Restyled by clang-format --- examples/platform/linux/AppMain.cpp | 2 +- .../platform/linux/ExampleAccessRestrictionProvider.h | 3 ++- src/access/AccessControl.cpp | 4 +++- src/access/AccessRestrictionProvider.cpp | 8 +++++--- src/access/AccessRestrictionProvider.h | 2 +- 5 files changed, 12 insertions(+), 7 deletions(-) diff --git a/examples/platform/linux/AppMain.cpp b/examples/platform/linux/AppMain.cpp index ed9003fbe36c23..074f078af003b4 100644 --- a/examples/platform/linux/AppMain.cpp +++ b/examples/platform/linux/AppMain.cpp @@ -618,7 +618,7 @@ void ChipLinuxAppMainLoop(AppMainLoopImplementation * impl) if (LinuxDeviceOptions::GetInstance().arlEntries.HasValue()) { - //This example use of the ARL feature proactively installs the provided entries on fabric index 1 + // This example use of the ARL feature proactively installs the provided entries on fabric index 1 exampleAccessRestrictionProvider->SetEntries(1, LinuxDeviceOptions::GetInstance().arlEntries.Value()); } #endif diff --git a/examples/platform/linux/ExampleAccessRestrictionProvider.h b/examples/platform/linux/ExampleAccessRestrictionProvider.h index 9346acdedae29a..d3958266f62c0d 100644 --- a/examples/platform/linux/ExampleAccessRestrictionProvider.h +++ b/examples/platform/linux/ExampleAccessRestrictionProvider.h @@ -42,7 +42,8 @@ class ExampleAccessRestrictionProvider : public AccessRestrictionProvider // this example simply removes all restrictions and will generate AccessRestrictionEntryChanged events Access::GetAccessControl().GetAccessRestrictionProvider()->SetEntries(fabricIndex, std::vector<Entry>{}); - chip::app::Clusters::AccessControl::Events::FabricRestrictionReviewUpdate::Type event{ .token = token, .fabricIndex = fabricIndex }; + chip::app::Clusters::AccessControl::Events::FabricRestrictionReviewUpdate::Type event{ .token = token, + .fabricIndex = fabricIndex }; EventNumber eventNumber; ReturnErrorOnFailure(chip::app::LogEvent(event, 0, eventNumber)); diff --git a/src/access/AccessControl.cpp b/src/access/AccessControl.cpp index d6667e059ff1d2..5ebe98a4305a62 100644 --- a/src/access/AccessControl.cpp +++ b/src/access/AccessControl.cpp @@ -362,7 +362,9 @@ CHIP_ERROR AccessControl::Check(const SubjectDescriptor & subjectDescriptor, con { #if CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 0 ChipLogProgress(DataManagement, "AccessControl: %s (delegate)", - (result == CHIP_NO_ERROR) ? "allowed" : (result == CHIP_ERROR_ACCESS_DENIED) ? "denied" : "error"); + (result == CHIP_NO_ERROR) ? "allowed" + : (result == CHIP_ERROR_ACCESS_DENIED) ? "denied" + : "error"); #else if (result != CHIP_NO_ERROR) { diff --git a/src/access/AccessRestrictionProvider.cpp b/src/access/AccessRestrictionProvider.cpp index a0caccc544081a..cb0d61d81d88d3 100644 --- a/src/access/AccessRestrictionProvider.cpp +++ b/src/access/AccessRestrictionProvider.cpp @@ -106,7 +106,7 @@ CHIP_ERROR AccessRestrictionProvider::SetEntries(const FabricIndex fabricIndex, return CHIP_ERROR_INVALID_ARGUMENT; } - Entry updatedEntry = entry; + Entry updatedEntry = entry; updatedEntry.fabricIndex = fabricIndex; updatedEntries.push_back(updatedEntry); } @@ -121,7 +121,8 @@ CHIP_ERROR AccessRestrictionProvider::SetEntries(const FabricIndex fabricIndex, return CHIP_NO_ERROR; } -CHIP_ERROR AccessRestrictionProvider::CheckForCommissioning(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath) +CHIP_ERROR AccessRestrictionProvider::CheckForCommissioning(const SubjectDescriptor & subjectDescriptor, + const RequestPath & requestPath) { return DoCheck(mCommissioningEntries, subjectDescriptor, requestPath); } @@ -130,7 +131,8 @@ CHIP_ERROR AccessRestrictionProvider::Check(const SubjectDescriptor & subjectDes return DoCheck(mFabricEntries[subjectDescriptor.fabricIndex], subjectDescriptor, requestPath); } -CHIP_ERROR AccessRestrictionProvider::DoCheck(const std::vector<Entry> entries, const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath) +CHIP_ERROR AccessRestrictionProvider::DoCheck(const std::vector<Entry> entries, const SubjectDescriptor & subjectDescriptor, + const RequestPath & requestPath) { ChipLogProgress(DataManagement, "AccessRestrictionProvider: action %d", static_cast<int>(requestPath.requestType)); diff --git a/src/access/AccessRestrictionProvider.h b/src/access/AccessRestrictionProvider.h index 1880e62085ccfb..8cec176678f489 100644 --- a/src/access/AccessRestrictionProvider.h +++ b/src/access/AccessRestrictionProvider.h @@ -116,7 +116,7 @@ class AccessRestrictionProvider AccessRestrictionProvider() = default; virtual ~AccessRestrictionProvider() = default; - AccessRestrictionProvider(const AccessRestrictionProvider &) = delete; + AccessRestrictionProvider(const AccessRestrictionProvider &) = delete; AccessRestrictionProvider & operator=(const AccessRestrictionProvider &) = delete; /** From 81effc2b763741abea71a7dae059f68337073408 Mon Sep 17 00:00:00 2001 From: Thomas Lea <thomas_lea@comcast.com> Date: Thu, 22 Aug 2024 15:54:15 -0500 Subject: [PATCH 10/22] fixed include check for renamed AccessRestrictionProvider.h file --- scripts/tools/check_includes_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/tools/check_includes_config.py b/scripts/tools/check_includes_config.py index efe66d57caed04..b5195f4ab05eda 100644 --- a/scripts/tools/check_includes_config.py +++ b/scripts/tools/check_includes_config.py @@ -185,5 +185,5 @@ 'src/app/icd/client/DefaultICDStorageKey.h': {'vector'}, 'src/controller/CHIPDeviceController.cpp': {'string'}, 'src/qrcodetool/setup_payload_commands.cpp': {'string'}, - 'src/access/AccessRestriction.h': {'vector', 'map'}, + 'src/access/AccessRestrictionProvider.h': {'vector', 'map'}, } From 1c34d9697b226ea81f580fca52a3136dbfc18a74 Mon Sep 17 00:00:00 2001 From: Thomas Lea <thomas_lea@comcast.com> Date: Fri, 23 Aug 2024 13:51:21 -0500 Subject: [PATCH 11/22] M-ACL updates - refactored AccessControl::Check into CheckACL and CheckARL - added placeholders for the upcoming CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL - extracted ARL exception processing to standalone class for better testing --- src/access/AccessControl.cpp | 99 ++++++++++++++---------- src/access/AccessControl.h | 13 ++++ src/access/AccessRestrictionProvider.cpp | 35 +++++++++ src/access/AccessRestrictionProvider.h | 38 +++++++++ 4 files changed, 142 insertions(+), 43 deletions(-) diff --git a/src/access/AccessControl.cpp b/src/access/AccessControl.cpp index 5ebe98a4305a62..152f38cba60d65 100644 --- a/src/access/AccessControl.cpp +++ b/src/access/AccessControl.cpp @@ -333,10 +333,25 @@ bool AccessControl::IsAccessRestrictionListSupported() const } CHIP_ERROR AccessControl::Check(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath, - Privilege requestPrivilege) + Privilege requestPrivilege) { VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE); + CHIP_ERROR result = CheckACL(subjectDescriptor, requestPath, requestPrivilege); + +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + if (result == CHIP_NO_ERROR) + { + result = CheckARL(subjectDescriptor, requestPath, requestPrivilege); + } +#endif + + return result; +} + +CHIP_ERROR AccessControl::CheckACL(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath, + Privilege requestPrivilege) +{ #if CHIP_PROGRESS_LOGGING && CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 1 { constexpr size_t kMaxCatsToLog = 6; @@ -351,11 +366,6 @@ CHIP_ERROR AccessControl::Check(const SubjectDescriptor & subjectDescriptor, con } #endif // CHIP_PROGRESS_LOGGING && CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 1 - if (IsAccessRestrictionListSupported()) - { - VerifyOrReturnError(requestPath.requestType != RequestType::kRequestTypeUnknown, CHIP_ERROR_INVALID_ARGUMENT); - } - { CHIP_ERROR result = mDelegate->Check(subjectDescriptor, requestPath, requestPrivilege); if (result != CHIP_ERROR_NOT_IMPLEMENTED) @@ -373,18 +383,6 @@ CHIP_ERROR AccessControl::Check(const SubjectDescriptor & subjectDescriptor, con } #endif // CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 0 -#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS - if (result == CHIP_NO_ERROR && mAccessRestrictionProvider != nullptr) - { - result = mAccessRestrictionProvider->Check(subjectDescriptor, requestPath); - if (result != CHIP_NO_ERROR) - { - ChipLogProgress(DataManagement, "AccessControl: %s", - (result == CHIP_ERROR_ACCESS_DENIED) ? "denied (restricted)" : "denied (restriction error)"); - return result; - } - } -#endif return result; } } @@ -397,18 +395,6 @@ CHIP_ERROR AccessControl::Check(const SubjectDescriptor & subjectDescriptor, con #if CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 1 ChipLogProgress(DataManagement, "AccessControl: implicit admin (PASE)"); #endif // CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 1 -#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS - if (mAccessRestrictionProvider != nullptr) - { - CHIP_ERROR result = mAccessRestrictionProvider->CheckForCommissioning(subjectDescriptor, requestPath); - if (result != CHIP_NO_ERROR) - { - ChipLogProgress(DataManagement, "AccessControl: %s", - (result == CHIP_ERROR_ACCESS_DENIED) ? "denied (restricted)" : "denied (restriction error)"); - return result; - } - } -#endif return CHIP_NO_ERROR; } @@ -518,19 +504,6 @@ CHIP_ERROR AccessControl::Check(const SubjectDescriptor & subjectDescriptor, con ChipLogProgress(DataManagement, "AccessControl: allowed"); #endif // CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 0 -#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS - if (mAccessRestrictionProvider != nullptr) - { - CHIP_ERROR result = mAccessRestrictionProvider->Check(subjectDescriptor, requestPath); - if (result != CHIP_NO_ERROR) - { - ChipLogProgress(DataManagement, "AccessControl: %s", - (result == CHIP_ERROR_ACCESS_DENIED) ? "denied (restricted)" : "denied (restriction error)"); - return result; - } - } -#endif - return CHIP_NO_ERROR; } @@ -539,6 +512,46 @@ CHIP_ERROR AccessControl::Check(const SubjectDescriptor & subjectDescriptor, con return CHIP_ERROR_ACCESS_DENIED; } +#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS +CHIP_ERROR AccessControl::CheckARL(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath, + Privilege requestPrivilege) +{ + CHIP_ERROR result = CHIP_NO_ERROR; + + VerifyOrReturnError(requestPath.requestType != RequestType::kRequestTypeUnknown, CHIP_ERROR_INVALID_ARGUMENT); + + if (!IsAccessRestrictionListSupported()) + { + // Access Restriction support is compiled in, but not configured/enabled. Nothing to restrict. + return CHIP_NO_ERROR; + } + + // If we are in PASE or if there is a pending fabric, we need to check against the CommissioningARL + if (subjectDescriptor.authMode == AuthMode::kPase) + { + result = mAccessRestrictionProvider->CheckForCommissioning(subjectDescriptor, requestPath); + } + else + { + result = mAccessRestrictionProvider->Check(subjectDescriptor, requestPath); + } + + if (result != CHIP_NO_ERROR) + { + ChipLogProgress(DataManagement, "AccessControl: %s", +#if 0 + //TODO: new error code coming with issue #35177 + (result == CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL) ? "denied (restricted)" : "denied (restriction error)"); +#else + (result == CHIP_ERROR_ACCESS_DENIED) ? "denied (restricted)" : "denied (restriction error)"); +#endif + return result; + } + + return result; +} +#endif + #if CHIP_ACCESS_CONTROL_DUMP_ENABLED CHIP_ERROR AccessControl::Dump(const Entry & entry) { diff --git a/src/access/AccessControl.h b/src/access/AccessControl.h index 855efa04346a3a..df986864b8ff4c 100644 --- a/src/access/AccessControl.h +++ b/src/access/AccessControl.h @@ -674,6 +674,19 @@ class AccessControl void NotifyEntryChanged(const SubjectDescriptor * subjectDescriptor, FabricIndex fabric, size_t index, const Entry * entry, EntryListener::ChangeType changeType); + /** + * Check ACL for whether access (by a subject descriptor, to a request path, + * requiring a privilege) should be allowed or denied. + */ + CHIP_ERROR CheckACL(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath, Privilege requestPrivilege); + + /** + * Check CommissioningARL or ARL (as appropriate) for whether access (by a + * subject descriptor, to a request path, requiring a privilege) should + * be allowed or denied. + */ + CHIP_ERROR CheckARL(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath, Privilege requestPrivilege); + private: Delegate * mDelegate = nullptr; diff --git a/src/access/AccessRestrictionProvider.cpp b/src/access/AccessRestrictionProvider.cpp index cb0d61d81d88d3..034fb836f28dce 100644 --- a/src/access/AccessRestrictionProvider.cpp +++ b/src/access/AccessRestrictionProvider.cpp @@ -121,11 +121,30 @@ CHIP_ERROR AccessRestrictionProvider::SetEntries(const FabricIndex fabricIndex, return CHIP_NO_ERROR; } +bool AccessRestrictionProvider::StandardAccessRestrictionExceptionChecker::AreRestrictionsDisallowed(const SubjectDescriptor & subjectDescriptor, + const RequestPath & requestPath) +{ + if (requestPath.endpoint == 0 || + requestPath.cluster == app::Clusters::NetworkCommissioning::Id || + requestPath.cluster == app::Clusters::Descriptor::Id) + { + return true; + } + + return false; +} + CHIP_ERROR AccessRestrictionProvider::CheckForCommissioning(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath) { + if (mExceptionChecker.AreRestrictionsDisallowed(subjectDescriptor, requestPath)) + { + return CHIP_NO_ERROR; + } + return DoCheck(mCommissioningEntries, subjectDescriptor, requestPath); } + CHIP_ERROR AccessRestrictionProvider::Check(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath) { return DoCheck(mFabricEntries[subjectDescriptor.fabricIndex], subjectDescriptor, requestPath); @@ -179,25 +198,41 @@ CHIP_ERROR AccessRestrictionProvider::DoCheck(const std::vector<Entry> entries, if (requestPath.requestType == RequestType::kAttributeReadRequest || requestPath.requestType == RequestType::kAttributeWriteRequest) { +#if 0 + return CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL; //TODO: new error code coming with issue #35177 +#else return CHIP_ERROR_ACCESS_DENIED; +#endif } break; case Type::kAttributeWriteForbidden: if (requestPath.requestType == RequestType::kAttributeWriteRequest) { +#if 0 + return CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL; //TODO: new error code coming with issue #35177 +#else return CHIP_ERROR_ACCESS_DENIED; +#endif } break; case Type::kCommandForbidden: if (requestPath.requestType == RequestType::kCommandInvokeRequest) { +#if 0 + return CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL; //TODO: new error code coming with issue #35177 +#else return CHIP_ERROR_ACCESS_DENIED; +#endif } break; case Type::kEventForbidden: if (requestPath.requestType == RequestType::kEventReadOrSubscribeRequest) { +#if 0 + return CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL; //TODO: new error code coming with issue #35177 +#else return CHIP_ERROR_ACCESS_DENIED; +#endif } break; } diff --git a/src/access/AccessRestrictionProvider.h b/src/access/AccessRestrictionProvider.h index 8cec176678f489..7cedeb75cdbd52 100644 --- a/src/access/AccessRestrictionProvider.h +++ b/src/access/AccessRestrictionProvider.h @@ -20,6 +20,7 @@ #include "RequestPath.h" #include "SubjectDescriptor.h" +#include "Privilege.h" #include <algorithm> #include <app-common/zap-generated/cluster-objects.h> #include <cstdint> @@ -76,6 +77,42 @@ class AccessRestrictionProvider std::vector<Restriction> restrictions; }; + /** + * Defines the interface for a checker for access restriction exceptions. + */ + class AccessRestrictionExceptionChecker + { + public: + virtual ~AccessRestrictionExceptionChecker() = default; + + /** + * Check if any restrictions are allowed to be applied to the given request. + * + * @retval true if restrictions may NOT be applied + */ + virtual bool AreRestrictionsDisallowed(const SubjectDescriptor & subjectDescriptor, + const RequestPath & requestPath) = 0; + }; + + /** + * Define a standard implementation of the AccessRestrictionExceptionChecker interface + * which is the default implementation used by AccessResrictionProvider. + */ + class StandardAccessRestrictionExceptionChecker : public AccessRestrictionExceptionChecker + { + public: + StandardAccessRestrictionExceptionChecker() = default; + ~StandardAccessRestrictionExceptionChecker() = default; + + /** + * Check if any restrictions are allowed to be applied to the given request. + * + * @retval true if restrictions may NOT be applied + */ + bool AreRestrictionsDisallowed(const SubjectDescriptor & subjectDescriptor, + const RequestPath & requestPath) override; + }; + /** * Used to notify of changes in the access restriction list and active reviews. */ @@ -237,6 +274,7 @@ class AccessRestrictionProvider uint64_t mNextToken = 1; Listener * mListeners = nullptr; + StandardAccessRestrictionExceptionChecker mExceptionChecker; std::vector<Entry> mCommissioningEntries; std::map<FabricIndex, std::vector<Entry>> mFabricEntries; }; From 9809516d97da84047a859a589fecccab21a6d3fd Mon Sep 17 00:00:00 2001 From: "tennessee.carmelveilleux@gmail.com" <tennessee.carmelveilleux@gmail.com> Date: Fri, 23 Aug 2024 15:40:25 -0400 Subject: [PATCH 12/22] Add plumbing for subject descriptor IsCommissioning field - Make session manager update that state on a message-per-message basis - Add tests Missing test: MRP test against a not-yet-committed fabric over CASE showing that IsCommissioning is true. --- src/access/SubjectDescriptor.h | 4 + src/credentials/FabricTable.cpp | 79 ++++++++++++------- src/credentials/FabricTable.h | 17 ++++ src/credentials/tests/TestFabricTable.cpp | 18 +++++ .../tests/TestReliableMessageProtocol.cpp | 10 ++- src/transport/SecureSession.cpp | 34 ++++++-- src/transport/SecureSession.h | 10 +++ src/transport/Session.h | 6 ++ src/transport/SessionManager.cpp | 7 ++ src/transport/tests/TestSessionManager.cpp | 10 ++- 10 files changed, 158 insertions(+), 37 deletions(-) diff --git a/src/access/SubjectDescriptor.h b/src/access/SubjectDescriptor.h index ec6abec0b38a30..9cde4102750d25 100644 --- a/src/access/SubjectDescriptor.h +++ b/src/access/SubjectDescriptor.h @@ -42,6 +42,10 @@ struct SubjectDescriptor // CASE Authenticated Tags (CATs) only valid if auth mode is CASE. CATValues cats; + + // Whether the subject is currently a pending commissionee. See `IsCommissioning` + // definition in Core Specification's ACL Architecture pseudocode. + bool isCommissioning = false; }; } // namespace Access diff --git a/src/credentials/FabricTable.cpp b/src/credentials/FabricTable.cpp index b847addbe10773..9a9eef9929c0db 100644 --- a/src/credentials/FabricTable.cpp +++ b/src/credentials/FabricTable.cpp @@ -71,6 +71,42 @@ constexpr size_t IndexInfoTLVMaxSize() return TLV::EstimateStructOverhead(sizeof(FabricIndex), CHIP_CONFIG_MAX_FABRICS * (1 + sizeof(FabricIndex)) + 1); } +CHIP_ERROR AddNewFabricForTestInternal(FabricTable & fabricTable, bool leavePending, const ByteSpan & rootCert, const ByteSpan & icacCert, const ByteSpan & nocCert, + const ByteSpan & opKeySpan, FabricIndex * outFabricIndex) +{ + VerifyOrReturnError(outFabricIndex != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + + CHIP_ERROR err = CHIP_ERROR_INTERNAL; + + Crypto::P256Keypair injectedOpKey; + Crypto::P256SerializedKeypair injectedOpKeysSerialized; + + Crypto::P256Keypair * opKey = nullptr; + if (!opKeySpan.empty()) + { + VerifyOrReturnError(opKeySpan.size() == injectedOpKeysSerialized.Capacity(), CHIP_ERROR_INVALID_ARGUMENT); + + memcpy(injectedOpKeysSerialized.Bytes(), opKeySpan.data(), opKeySpan.size()); + SuccessOrExit(err = injectedOpKeysSerialized.SetLength(opKeySpan.size())); + SuccessOrExit(err = injectedOpKey.Deserialize(injectedOpKeysSerialized)); + opKey = &injectedOpKey; + } + + SuccessOrExit(err = fabricTable.AddNewPendingTrustedRootCert(rootCert)); + SuccessOrExit(err = fabricTable.AddNewPendingFabricWithProvidedOpKey(nocCert, icacCert, VendorId::TestVendor1, opKey, + /*isExistingOpKeyExternallyOwned =*/false, outFabricIndex)); + if (!leavePending) + { + SuccessOrExit(err = fabricTable.CommitPendingFabricData()); + } +exit: + if (err != CHIP_NO_ERROR) + { + fabricTable.RevertPendingFabricData(); + } + return err; +} + } // anonymous namespace CHIP_ERROR FabricInfo::Init(const FabricInfo::InitParams & initParams) @@ -695,34 +731,13 @@ CHIP_ERROR FabricTable::LoadFromStorage(FabricInfo * fabric, FabricIndex newFabr CHIP_ERROR FabricTable::AddNewFabricForTest(const ByteSpan & rootCert, const ByteSpan & icacCert, const ByteSpan & nocCert, const ByteSpan & opKeySpan, FabricIndex * outFabricIndex) { - VerifyOrReturnError(outFabricIndex != nullptr, CHIP_ERROR_INVALID_ARGUMENT); - - CHIP_ERROR err = CHIP_ERROR_INTERNAL; - - Crypto::P256Keypair injectedOpKey; - Crypto::P256SerializedKeypair injectedOpKeysSerialized; - - Crypto::P256Keypair * opKey = nullptr; - if (!opKeySpan.empty()) - { - VerifyOrReturnError(opKeySpan.size() == injectedOpKeysSerialized.Capacity(), CHIP_ERROR_INVALID_ARGUMENT); - - memcpy(injectedOpKeysSerialized.Bytes(), opKeySpan.data(), opKeySpan.size()); - SuccessOrExit(err = injectedOpKeysSerialized.SetLength(opKeySpan.size())); - SuccessOrExit(err = injectedOpKey.Deserialize(injectedOpKeysSerialized)); - opKey = &injectedOpKey; - } + return AddNewFabricForTestInternal(*this, /*leavePending=*/ false, rootCert, icacCert, nocCert, opKeySpan, outFabricIndex); +} - SuccessOrExit(err = AddNewPendingTrustedRootCert(rootCert)); - SuccessOrExit(err = AddNewPendingFabricWithProvidedOpKey(nocCert, icacCert, VendorId::TestVendor1, opKey, - /*isExistingOpKeyExternallyOwned =*/false, outFabricIndex)); - SuccessOrExit(err = CommitPendingFabricData()); -exit: - if (err != CHIP_NO_ERROR) - { - RevertPendingFabricData(); - } - return err; +CHIP_ERROR FabricTable::AddNewUncommittedFabricForTest(const ByteSpan & rootCert, const ByteSpan & icacCert, const ByteSpan & nocCert, + const ByteSpan & opKeySpan, FabricIndex * outFabricIndex) +{ + return AddNewFabricForTestInternal(*this, /*leavePending=*/ true, rootCert, icacCert, nocCert, opKeySpan, outFabricIndex); } /* @@ -1546,6 +1561,16 @@ bool FabricTable::SetPendingDataFabricIndex(FabricIndex fabricIndex) return isLegal; } +FabricIndex FabricTable::GetPendingNewFabricIndex() const +{ + if (mStateFlags.Has(StateFlags::kIsAddPending)) + { + return mFabricIndexWithPendingState; + } + + return kUndefinedFabricIndex; +} + CHIP_ERROR FabricTable::AllocatePendingOperationalKey(Optional<FabricIndex> fabricIndex, MutableByteSpan & outputCsr) { // We can only manage commissionable pending fail-safe state if we have a keystore diff --git a/src/credentials/FabricTable.h b/src/credentials/FabricTable.h index aef60f6a17fbbe..1bb13485873c9d 100644 --- a/src/credentials/FabricTable.h +++ b/src/credentials/FabricTable.h @@ -736,6 +736,18 @@ class DLL_EXPORT FabricTable */ bool HasOperationalKeyForFabric(FabricIndex fabricIndex) const; + /** + * @brief If a newly-added fabric is pending, this returns its index, or kUndefinedFabricIndex in none are pending. + * + * A newly-added fabric is pending if AddNOC has been previously called successfully but the + * fabric is not yet fully committed by CommissioningComplete. + * + * NOTE: that this never returns a value other than kUndefinedFabricIndex when UpdateNOC is pending. + * + * @return the fabric index of the pending fabric, or kUndefinedFabricIndex if no fabrics are pending. + */ + FabricIndex GetPendingNewFabricIndex() const; + /** * @brief Returns the operational keystore. This is used for * CASE and the only way the keystore should be used. @@ -968,6 +980,11 @@ class DLL_EXPORT FabricTable CHIP_ERROR AddNewFabricForTest(const ByteSpan & rootCert, const ByteSpan & icacCert, const ByteSpan & nocCert, const ByteSpan & opKeySpan, FabricIndex * outFabricIndex); + // Add a new fabric for testing. The Operational Key is a raw P256Keypair (public key and private key raw bits) that will + // get copied (directly) into the fabric table. The fabric will NOT be committed, and will remain pending. + CHIP_ERROR AddNewUncommittedFabricForTest(const ByteSpan & rootCert, const ByteSpan & icacCert, const ByteSpan & nocCert, + const ByteSpan & opKeySpan, FabricIndex * outFabricIndex); + // Same as AddNewFabricForTest, but ignore if we are colliding with same <Root Public Key, Fabric Id>, so // that a single fabric table can have N nodes for same fabric. This usually works, but is bad form. CHIP_ERROR AddNewFabricForTestIgnoringCollisions(const ByteSpan & rootCert, const ByteSpan & icacCert, const ByteSpan & nocCert, diff --git a/src/credentials/tests/TestFabricTable.cpp b/src/credentials/tests/TestFabricTable.cpp index c5b78fc13e5b4f..0d34f48bce6599 100644 --- a/src/credentials/tests/TestFabricTable.cpp +++ b/src/credentials/tests/TestFabricTable.cpp @@ -551,6 +551,7 @@ TEST_F(TestFabricTable, TestBasicAddNocUpdateNocFlow) FabricTable & fabricTable = fabricTableHolder.GetFabricTable(); EXPECT_EQ(fabricTable.FabricCount(), 0); + EXPECT_EQ(fabricTable.GetPendingNewFabricIndex(), kUndefinedFabricIndex); { FabricIndex nextFabricIndex = kUndefinedFabricIndex; @@ -604,6 +605,7 @@ TEST_F(TestFabricTable, TestBasicAddNocUpdateNocFlow) EXPECT_EQ(fabricTable.FetchPendingNonFabricAssociatedRootCert(fetchedSpan), CHIP_NO_ERROR); EXPECT_TRUE(fetchedSpan.data_equal(rcac)); } + EXPECT_EQ(fabricTable.GetPendingNewFabricIndex(), kUndefinedFabricIndex); FabricIndex newFabricIndex = kUndefinedFabricIndex; bool keyIsExternallyOwned = true; @@ -614,6 +616,11 @@ TEST_F(TestFabricTable, TestBasicAddNocUpdateNocFlow) CHIP_NO_ERROR); EXPECT_EQ(newFabricIndex, 1); EXPECT_EQ(fabricTable.FabricCount(), 1); + + // After adding the pending new fabric (equivalent of AddNOC processing), the new + // fabric must be pending. + EXPECT_EQ(fabricTable.GetPendingNewFabricIndex(), 1); + { // No more pending root cert; it's associated with a fabric now. MutableByteSpan fetchedSpan{ rcacBuf }; @@ -661,6 +668,9 @@ TEST_F(TestFabricTable, TestBasicAddNocUpdateNocFlow) EXPECT_EQ(nextFabricIndex, 2); } + // Fabric can't be pending anymore. + EXPECT_EQ(fabricTable.GetPendingNewFabricIndex(), kUndefinedFabricIndex); + // Validate contents const auto * fabricInfo = fabricTable.FindFabricWithIndex(1); ASSERT_NE(fabricInfo, nullptr); @@ -732,12 +742,16 @@ TEST_F(TestFabricTable, TestBasicAddNocUpdateNocFlow) } EXPECT_EQ(fabricTable.AddNewPendingTrustedRootCert(rcac), CHIP_NO_ERROR); + EXPECT_EQ(fabricTable.GetPendingNewFabricIndex(), kUndefinedFabricIndex); + FabricIndex newFabricIndex = kUndefinedFabricIndex; EXPECT_EQ(fabricTable.FabricCount(), 1); EXPECT_EQ(fabricTable.AddNewPendingFabricWithOperationalKeystore(noc, icac, kVendorId, &newFabricIndex), CHIP_NO_ERROR); EXPECT_EQ(fabricTable.FabricCount(), 2); EXPECT_EQ(newFabricIndex, 2); + EXPECT_EQ(fabricTable.GetPendingNewFabricIndex(), 2); + // No storage yet EXPECT_EQ(storage.GetNumKeys(), numStorageAfterFirstAdd); // Next fabric index has not been updated yet. @@ -1897,6 +1911,8 @@ TEST_F(TestFabricTable, TestUpdateNocFailSafe) uint8_t csrBuf[chip::Crypto::kMIN_CSR_Buffer_Size]; MutableByteSpan csrSpan{ csrBuf }; + EXPECT_EQ(fabricTable.GetPendingNewFabricIndex(), kUndefinedFabricIndex); + // Make sure to tag fabric index to pending opkey: otherwise the UpdateNOC fails EXPECT_EQ(fabricTable.AllocatePendingOperationalKey(chip::MakeOptional(static_cast<FabricIndex>(1)), csrSpan), CHIP_NO_ERROR); @@ -1908,6 +1924,7 @@ TEST_F(TestFabricTable, TestUpdateNocFailSafe) EXPECT_EQ(fabricTable.FabricCount(), 1); EXPECT_EQ(fabricTable.UpdatePendingFabricWithOperationalKeystore(1, noc, ByteSpan{}), CHIP_NO_ERROR); + EXPECT_EQ(fabricTable.GetPendingNewFabricIndex(), kUndefinedFabricIndex); EXPECT_EQ(fabricTable.FabricCount(), 1); // No storage yet @@ -1936,6 +1953,7 @@ TEST_F(TestFabricTable, TestUpdateNocFailSafe) // Revert, should see Node ID 999 again fabricTable.RevertPendingFabricData(); EXPECT_EQ(fabricTable.FabricCount(), 1); + EXPECT_EQ(fabricTable.GetPendingNewFabricIndex(), kUndefinedFabricIndex); EXPECT_EQ(storage.GetNumKeys(), numStorageAfterAdd); diff --git a/src/messaging/tests/TestReliableMessageProtocol.cpp b/src/messaging/tests/TestReliableMessageProtocol.cpp index 68b6ed2852d377..f655fb8e47319c 100644 --- a/src/messaging/tests/TestReliableMessageProtocol.cpp +++ b/src/messaging/tests/TestReliableMessageProtocol.cpp @@ -125,6 +125,9 @@ class MockAppDelegate : public UnsolicitedMessageHandler, public ExchangeDelegat EXPECT_EQ(buffer->TotalLength(), sizeof(PAYLOAD)); EXPECT_EQ(memcmp(buffer->Start(), PAYLOAD, buffer->TotalLength()), 0); + + mLastSubjectDescriptor = ec->GetSessionHandle()->AsSecureSession()->GetSubjectDescriptor(); + return CHIP_NO_ERROR; } @@ -151,6 +154,8 @@ class MockAppDelegate : public UnsolicitedMessageHandler, public ExchangeDelegat } } + Access::SubjectDescriptor mLastSubjectDescriptor{}; + bool IsOnMessageReceivedCalled = false; bool mReceivedPiggybackAck = false; bool mRetainExchange = false; @@ -1830,9 +1835,12 @@ TEST_F(TestReliableMessageProtocol, CheckApplicationResponseDelayed) EXPECT_EQ(loopback.mSentMessageCount, kMaxMRPTransmits); EXPECT_EQ(loopback.mDroppedMessageCount, kMaxMRPTransmits - 1); EXPECT_EQ(rm->TestGetCountRetransTable(), 1); // We have no ack yet. - EXPECT_TRUE(mockReceiver.IsOnMessageReceivedCalled); // Other side got the message. + ASSERT_TRUE(mockReceiver.IsOnMessageReceivedCalled); // Other side got the message. EXPECT_FALSE(mockSender.IsOnMessageReceivedCalled); // We did not get a response. + // It was not a commissioning CASE session so that is lined-up properly. + EXPECT_FALSE(mockReceiver.mLastSubjectDescriptor.isCommissioning); + // Ensure there will be no more weirdness with acks and that our MRP timer is restarted properly. mockReceiver.SetDropAckResponse(false); diff --git a/src/transport/SecureSession.cpp b/src/transport/SecureSession.cpp index c96f9cdf908756..7694df2e2e3aca 100644 --- a/src/transport/SecureSession.cpp +++ b/src/transport/SecureSession.cpp @@ -160,10 +160,11 @@ Access::SubjectDescriptor SecureSession::GetSubjectDescriptor() const Access::SubjectDescriptor subjectDescriptor; if (IsOperationalNodeId(mPeerNodeId)) { - subjectDescriptor.authMode = Access::AuthMode::kCase; - subjectDescriptor.subject = mPeerNodeId; - subjectDescriptor.cats = mPeerCATs; - subjectDescriptor.fabricIndex = GetFabricIndex(); + subjectDescriptor.authMode = Access::AuthMode::kCase; + subjectDescriptor.subject = mPeerNodeId; + subjectDescriptor.cats = mPeerCATs; + subjectDescriptor.fabricIndex = GetFabricIndex(); + subjectDescriptor.isCommissioning = IsCommissioningSession(); } else if (IsPAKEKeyId(mPeerNodeId)) { @@ -171,9 +172,10 @@ Access::SubjectDescriptor SecureSession::GetSubjectDescriptor() const // Initiator (aka commissioner) leaves subject descriptor unfilled. if (GetCryptoContext().IsResponder()) { - subjectDescriptor.authMode = Access::AuthMode::kPase; - subjectDescriptor.subject = mPeerNodeId; - subjectDescriptor.fabricIndex = GetFabricIndex(); + subjectDescriptor.authMode = Access::AuthMode::kPase; + subjectDescriptor.subject = mPeerNodeId; + subjectDescriptor.fabricIndex = GetFabricIndex(); + subjectDescriptor.isCommissioning = IsCommissioningSession(); } } else @@ -183,6 +185,24 @@ Access::SubjectDescriptor SecureSession::GetSubjectDescriptor() const return subjectDescriptor; } +bool SecureSession::IsCommissioningSession() const +{ + // PASE session is always a commissioning session. + if (IsPASESession()) + { + return true; + } + + // CASE session is a commissioning session if it was marked as such. + // The SessionManager is what keeps track. + if (IsCASESession() && mIsCaseCommissioningSession) + { + return true; + } + + return false; +} + void SecureSession::Retain() { #if CHIP_CONFIG_SECURE_SESSION_REFCOUNT_LOGGING diff --git a/src/transport/SecureSession.h b/src/transport/SecureSession.h index fe70d6714e4a0f..b73ccf5b2793a7 100644 --- a/src/transport/SecureSession.h +++ b/src/transport/SecureSession.h @@ -156,6 +156,8 @@ class SecureSession : public Session, public ReferenceCounted<SecureSession, Sec Access::SubjectDescriptor GetSubjectDescriptor() const override; + bool IsCommissioningSession() const override; + bool AllowsMRP() const override { return GetPeerAddress().GetTransportType() == Transport::Type::kUdp; } bool AllowsLargePayload() const override { return GetPeerAddress().GetTransportType() == Transport::Type::kTcp; } @@ -245,6 +247,12 @@ class SecureSession : public Session, public ReferenceCounted<SecureSession, Sec } } + void SetCaseCommissioningSessionStatus(bool isCaseCommissioningSession) + { + VerifyOrDie(GetSecureSessionType() == Type::kCASE); + mIsCaseCommissioningSession = isCaseCommissioningSession; + } + bool IsPeerActive() const { return ((System::SystemClock().GetMonotonicTimestamp() - GetLastPeerActivityTime()) < @@ -340,6 +348,8 @@ class SecureSession : public Session, public ReferenceCounted<SecureSession, Sec SessionParameters mRemoteSessionParams; CryptoContext mCryptoContext; SessionMessageCounter mSessionMessageCounter; + + bool mIsCaseCommissioningSession = false; }; } // namespace Transport diff --git a/src/transport/Session.h b/src/transport/Session.h index fc656c85ac12e5..e527a7887f2dd9 100644 --- a/src/transport/Session.h +++ b/src/transport/Session.h @@ -244,6 +244,12 @@ class Session virtual bool AllowsLargePayload() const = 0; virtual const SessionParameters & GetRemoteSessionParameters() const = 0; virtual System::Clock::Timestamp GetMRPBaseTimeout() const = 0; + + // Returns true if `subjectDescriptor.IsCommissioning` (based on Core Specification + // pseudocode in ACL Architecture chapter) should be true when computing a + // subject descriptor for that session. + virtual bool IsCommissioningSession() const { return false; } + // GetAckTimeout is the estimate for how long it could take for the other // side to receive our message (accounting for our MRP retransmits if it // gets lost) and send a response. diff --git a/src/transport/SessionManager.cpp b/src/transport/SessionManager.cpp index d9582479458617..78b822b19acd87 100644 --- a/src/transport/SessionManager.cpp +++ b/src/transport/SessionManager.cpp @@ -1024,6 +1024,13 @@ void SessionManager::SecureUnicastMessageDispatch(const PacketHeader & partialPa MATTER_LOG_MESSAGE_RECEIVED(chip::Tracing::IncomingMessageType::kSecureUnicast, &payloadHeader, &packetHeader, secureSession, &peerAddress, chip::ByteSpan(msg->Start(), msg->TotalLength())); CHIP_TRACE_MESSAGE_RECEIVED(payloadHeader, packetHeader, secureSession, peerAddress, msg->Start(), msg->TotalLength()); + + // Always recompute whether a message is for a commissioning session based on the latest knowledge of + // the fabric table. + if (secureSession->IsCASESession()) + { + secureSession->SetCaseCommissioningSessionStatus(secureSession->GetFabricIndex() == mFabricTable->GetPendingNewFabricIndex()); + } mCB->OnMessageReceived(packetHeader, payloadHeader, session.Value(), isDuplicate, std::move(msg)); } else diff --git a/src/transport/tests/TestSessionManager.cpp b/src/transport/tests/TestSessionManager.cpp index 154071a56418c5..dfe3ca4fd5c5ff 100644 --- a/src/transport/tests/TestSessionManager.cpp +++ b/src/transport/tests/TestSessionManager.cpp @@ -28,6 +28,7 @@ #define CHIP_ENABLE_TEST_ENCRYPTED_BUFFER_API // Up here in case some other header // includes SessionManager.h indirectly +#include <access/SubjectDescriptor.h> #include <credentials/PersistentStorageOpCertStore.h> #include <credentials/tests/CHIPCert_unit_test_vectors.h> #include <crypto/DefaultSessionKeystore.h> @@ -112,10 +113,12 @@ class TestSessMgrCallback : public SessionMessageDelegate } ReceiveHandlerCallCount++; + lastSubjectDescriptor = session->GetSubjectDescriptor(); } int ReceiveHandlerCallCount = 0; bool LargeMessageSent = false; + Access::SubjectDescriptor lastSubjectDescriptor{}; }; class TestSessionManager : public ::testing::Test @@ -141,7 +144,7 @@ TEST_F(TestSessionManager, CheckSimpleInitTest) &fabricTableHolder.GetFabricTable(), sessionKeystore)); } -TEST_F(TestSessionManager, CheckMessageTest) +TEST_F(TestSessionManager, CheckMessageOverPaseTest) { uint16_t payload_len = sizeof(PAYLOAD); @@ -213,7 +216,10 @@ TEST_F(TestSessionManager, CheckMessageTest) EXPECT_EQ(err, CHIP_NO_ERROR); mContext.DrainAndServiceIO(); - EXPECT_EQ(callback.ReceiveHandlerCallCount, 1); + ASSERT_EQ(callback.ReceiveHandlerCallCount, 1); + + // This was a PASE session so we expect the subject descriptor to indicate it's for commissioning. + EXPECT_TRUE(callback.lastSubjectDescriptor.isCommissioning); // Let's send the max sized message and make sure it is received chip::System::PacketBufferHandle large_buffer = chip::MessagePacketBuffer::NewWithData(LARGE_PAYLOAD, kMaxAppMessageLen); From 8ac75f8eb6139d32e12706fb885a9ca04e01082b Mon Sep 17 00:00:00 2001 From: "tennessee.carmelveilleux@gmail.com" <tennessee.carmelveilleux@gmail.com> Date: Fri, 23 Aug 2024 16:53:07 -0400 Subject: [PATCH 13/22] Fix crash --- src/messaging/ExchangeContext.h | 2 +- src/messaging/tests/TestReliableMessageProtocol.cpp | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/messaging/ExchangeContext.h b/src/messaging/ExchangeContext.h index e10ef84ce911be..7d3aa146481195 100644 --- a/src/messaging/ExchangeContext.h +++ b/src/messaging/ExchangeContext.h @@ -163,7 +163,7 @@ class DLL_EXPORT ExchangeContext : public ReliableMessageContext, return std::move(sessionHandle.Value()); } - bool HasSessionHandle() const { return mSession; } + bool HasSessionHandle() const { return (bool)mSession; } uint16_t GetExchangeId() const { return mExchangeId; } diff --git a/src/messaging/tests/TestReliableMessageProtocol.cpp b/src/messaging/tests/TestReliableMessageProtocol.cpp index f655fb8e47319c..6390e4eca1920e 100644 --- a/src/messaging/tests/TestReliableMessageProtocol.cpp +++ b/src/messaging/tests/TestReliableMessageProtocol.cpp @@ -92,6 +92,12 @@ class MockAppDelegate : public UnsolicitedMessageHandler, public ExchangeDelegat System::PacketBufferHandle && buffer) override { IsOnMessageReceivedCalled = true; + + if (ec->HasSessionHandle() && ec->GetSessionHolder()->IsSecureSession()) + { + mLastSubjectDescriptor = ec->GetSessionHolder()->AsSecureSession()->GetSubjectDescriptor(); + } + if (payloadHeader.IsAckMsg()) { mReceivedPiggybackAck = true; @@ -126,8 +132,6 @@ class MockAppDelegate : public UnsolicitedMessageHandler, public ExchangeDelegat EXPECT_EQ(buffer->TotalLength(), sizeof(PAYLOAD)); EXPECT_EQ(memcmp(buffer->Start(), PAYLOAD, buffer->TotalLength()), 0); - mLastSubjectDescriptor = ec->GetSessionHandle()->AsSecureSession()->GetSubjectDescriptor(); - return CHIP_NO_ERROR; } From b151a0f7b8c636bea167e65dc3b6449a1d2128a6 Mon Sep 17 00:00:00 2001 From: Thomas Lea <thomas_lea@comcast.com> Date: Fri, 23 Aug 2024 16:07:05 -0500 Subject: [PATCH 14/22] Use new IsCommissioning in ARL check --- examples/platform/linux/Options.cpp | 2 +- src/access/AccessControl.cpp | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/platform/linux/Options.cpp b/examples/platform/linux/Options.cpp index c7e6c26ed8bb21..6ed9d95d45db3b 100644 --- a/examples/platform/linux/Options.cpp +++ b/examples/platform/linux/Options.cpp @@ -360,7 +360,7 @@ bool ParseAccessRestrictionEntriesFromJson(const char * jsonString, std::vector< for (Json::Value::const_iterator rIt = restrictions.begin(); rIt != restrictions.end(); rIt++) { AccessRestrictionProvider::Restriction restriction; - restriction.restrictionType = static_cast<AccessRestrictionProvider::Type>((*rIt)["type"].asInt()); + restriction.restrictionType = static_cast<AccessRestrictionProvider::Type>((*rIt)["type"].asUInt()); if ((*rIt).isMember("id")) { restriction.id.SetValue((*rIt)["id"].asUInt()); diff --git a/src/access/AccessControl.cpp b/src/access/AccessControl.cpp index 152f38cba60d65..e84e223cf4f0ab 100644 --- a/src/access/AccessControl.cpp +++ b/src/access/AccessControl.cpp @@ -526,8 +526,7 @@ CHIP_ERROR AccessControl::CheckARL(const SubjectDescriptor & subjectDescriptor, return CHIP_NO_ERROR; } - // If we are in PASE or if there is a pending fabric, we need to check against the CommissioningARL - if (subjectDescriptor.authMode == AuthMode::kPase) + if (subjectDescriptor.isCommissioning) { result = mAccessRestrictionProvider->CheckForCommissioning(subjectDescriptor, requestPath); } From 1d463a01d2522dd7ceb0468a54cb0f0b08e7b5d7 Mon Sep 17 00:00:00 2001 From: Thomas Lea <thomas_lea@comcast.com> Date: Sun, 25 Aug 2024 11:19:22 -0500 Subject: [PATCH 15/22] Updates for review comments --- .../linux/ExampleAccessRestrictionProvider.h | 2 +- examples/platform/linux/Options.cpp | 1 + src/access/AccessControl.cpp | 4 +- src/access/AccessRestrictionProvider.cpp | 61 +++++----- src/access/AccessRestrictionProvider.h | 34 +++--- src/access/RequestPath.h | 2 +- .../tests/TestAccessRestrictionProvider.cpp | 16 +-- src/app/EventManagement.cpp | 2 +- src/app/InteractionModelEngine.cpp | 4 +- .../access-control-server/ArlEncoder.cpp | 9 +- .../access-control-server/ArlEncoder.h | 5 - .../access-control-server.cpp | 108 ++++++++---------- src/app/reporting/Engine.cpp | 2 +- src/credentials/FabricTable.h | 2 +- src/lib/support/DefaultStorageKeyAllocator.h | 10 -- src/messaging/ExchangeContext.h | 2 +- src/transport/SecureSession.h | 3 +- src/transport/Session.h | 3 +- 18 files changed, 114 insertions(+), 156 deletions(-) diff --git a/examples/platform/linux/ExampleAccessRestrictionProvider.h b/examples/platform/linux/ExampleAccessRestrictionProvider.h index d3958266f62c0d..731a8ae5845a21 100644 --- a/examples/platform/linux/ExampleAccessRestrictionProvider.h +++ b/examples/platform/linux/ExampleAccessRestrictionProvider.h @@ -45,7 +45,7 @@ class ExampleAccessRestrictionProvider : public AccessRestrictionProvider chip::app::Clusters::AccessControl::Events::FabricRestrictionReviewUpdate::Type event{ .token = token, .fabricIndex = fabricIndex }; EventNumber eventNumber; - ReturnErrorOnFailure(chip::app::LogEvent(event, 0, eventNumber)); + ReturnErrorOnFailure(chip::app::LogEvent(event, kRootEndpointId, eventNumber)); return CHIP_NO_ERROR; } diff --git a/examples/platform/linux/Options.cpp b/examples/platform/linux/Options.cpp index 6ed9d95d45db3b..6f8afea5bb496b 100644 --- a/examples/platform/linux/Options.cpp +++ b/examples/platform/linux/Options.cpp @@ -585,6 +585,7 @@ bool HandleOption(const char * aProgram, OptionSet * aOptions, int aIdentifier, #endif // CHIP_CONFIG_TRANSPORT_TRACE_ENABLED #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS + // TODO(#35189): change to use a path to JSON files instead case kDeviceOption_CommissioningArlEntries: { std::vector<AccessRestrictionProvider::Entry> entries; retval = ParseAccessRestrictionEntriesFromJson(aValue, entries); diff --git a/src/access/AccessControl.cpp b/src/access/AccessControl.cpp index e84e223cf4f0ab..abb15b02da240d 100644 --- a/src/access/AccessControl.cpp +++ b/src/access/AccessControl.cpp @@ -181,7 +181,7 @@ char GetRequestTypeStringForLogging(RequestType requestType) return 'w'; case RequestType::kCommandInvokeRequest: return 'i'; - case RequestType::kEventReadOrSubscribeRequest: + case RequestType::kEventReadRequest: return 'e'; default: return '?'; @@ -539,7 +539,7 @@ CHIP_ERROR AccessControl::CheckARL(const SubjectDescriptor & subjectDescriptor, { ChipLogProgress(DataManagement, "AccessControl: %s", #if 0 - //TODO: new error code coming with issue #35177 + // TODO(#35177): new error code coming when access check plumbing are fixed in callers (result == CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL) ? "denied (restricted)" : "denied (restriction error)"); #else (result == CHIP_ERROR_ACCESS_DENIED) ? "denied (restricted)" : "denied (restriction error)"); diff --git a/src/access/AccessRestrictionProvider.cpp b/src/access/AccessRestrictionProvider.cpp index 034fb836f28dce..6c6a4c6aadf342 100644 --- a/src/access/AccessRestrictionProvider.cpp +++ b/src/access/AccessRestrictionProvider.cpp @@ -73,10 +73,9 @@ void AccessRestrictionProvider::RemoveListener(Listener & listener) CHIP_ERROR AccessRestrictionProvider::SetCommissioningEntries(const std::vector<Entry> & entries) { - // check that the input entries are valid for (auto & entry : entries) { - if (!IsEntryValid(entry)) + if (!mExceptionChecker.AreRestrictionsAllowed(entry.endpointNumber, entry.clusterId)) { ChipLogError(DataManagement, "AccessRestrictionProvider: invalid entry"); return CHIP_ERROR_INVALID_ARGUMENT; @@ -87,7 +86,7 @@ CHIP_ERROR AccessRestrictionProvider::SetCommissioningEntries(const std::vector< for (Listener * listener = mListeners; listener != nullptr; listener = listener->mNext) { - listener->CommissioningRestrictionListChanged(); + listener->MarkCommissioningRestrictionListChanged(); } return CHIP_NO_ERROR; @@ -97,10 +96,9 @@ CHIP_ERROR AccessRestrictionProvider::SetEntries(const FabricIndex fabricIndex, { std::vector<Entry> updatedEntries; - // check that the input entries are valid for (auto & entry : entries) { - if (!IsEntryValid(entry)) + if (!mExceptionChecker.AreRestrictionsAllowed(entry.endpointNumber, entry.clusterId)) { ChipLogError(DataManagement, "AccessRestrictionProvider: invalid entry"); return CHIP_ERROR_INVALID_ARGUMENT; @@ -115,18 +113,18 @@ CHIP_ERROR AccessRestrictionProvider::SetEntries(const FabricIndex fabricIndex, for (Listener * listener = mListeners; listener != nullptr; listener = listener->mNext) { - listener->RestrictionListChanged(fabricIndex); + listener->MarkRestrictionListChanged(fabricIndex); } return CHIP_NO_ERROR; } -bool AccessRestrictionProvider::StandardAccessRestrictionExceptionChecker::AreRestrictionsDisallowed(const SubjectDescriptor & subjectDescriptor, - const RequestPath & requestPath) +bool AccessRestrictionProvider::StandardAccessRestrictionExceptionChecker::AreRestrictionsAllowed(EndpointId endpoint, ClusterId cluster) { - if (requestPath.endpoint == 0 || - requestPath.cluster == app::Clusters::NetworkCommissioning::Id || - requestPath.cluster == app::Clusters::Descriptor::Id) + if (endpoint != 0 && + (cluster == app::Clusters::WiFiNetworkManagement::Id || + cluster == app::Clusters::ThreadBorderRouterManagement::Id || + cluster == app::Clusters::ThreadNetworkDirectory::Id)) { return true; } @@ -137,11 +135,6 @@ bool AccessRestrictionProvider::StandardAccessRestrictionExceptionChecker::AreRe CHIP_ERROR AccessRestrictionProvider::CheckForCommissioning(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath) { - if (mExceptionChecker.AreRestrictionsDisallowed(subjectDescriptor, requestPath)) - { - return CHIP_NO_ERROR; - } - return DoCheck(mCommissioningEntries, subjectDescriptor, requestPath); } @@ -150,10 +143,16 @@ CHIP_ERROR AccessRestrictionProvider::Check(const SubjectDescriptor & subjectDes return DoCheck(mFabricEntries[subjectDescriptor.fabricIndex], subjectDescriptor, requestPath); } -CHIP_ERROR AccessRestrictionProvider::DoCheck(const std::vector<Entry> entries, const SubjectDescriptor & subjectDescriptor, +CHIP_ERROR AccessRestrictionProvider::DoCheck(const std::vector<Entry> & entries, const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath) { - ChipLogProgress(DataManagement, "AccessRestrictionProvider: action %d", static_cast<int>(requestPath.requestType)); + if (!mExceptionChecker.AreRestrictionsAllowed(requestPath.endpoint, requestPath.cluster)) + { + ChipLogProgress(DataManagement, "AccessRestrictionProvider: skipping checks for unrestrictable request path"); + return CHIP_NO_ERROR; + } + + ChipLogProgress(DataManagement, "AccessRestrictionProvider: action %d", to_underlying(requestPath.requestType)); if (requestPath.requestType == RequestType::kRequestTypeUnknown) { @@ -166,7 +165,7 @@ CHIP_ERROR AccessRestrictionProvider::DoCheck(const std::vector<Entry> entries, // event id). All other requests require an entity id if (!requestPath.entityId.has_value()) { - if (requestPath.requestType == RequestType::kEventReadOrSubscribeRequest) + if (requestPath.requestType == RequestType::kEventReadRequest) { return CHIP_NO_ERROR; } @@ -176,7 +175,7 @@ CHIP_ERROR AccessRestrictionProvider::DoCheck(const std::vector<Entry> entries, } } - for (auto & entry : mFabricEntries[subjectDescriptor.fabricIndex]) + for (auto & entry : entries) { if (entry.endpointNumber != requestPath.endpoint || entry.clusterId != requestPath.cluster) { @@ -185,7 +184,7 @@ CHIP_ERROR AccessRestrictionProvider::DoCheck(const std::vector<Entry> entries, for (auto & restriction : entry.restrictions) { - // a missing id is a wildcard + // A missing id is a wildcard bool idMatch = !restriction.id.HasValue() || restriction.id.Value() == requestPath.entityId.value(); if (!idMatch) { @@ -199,7 +198,8 @@ CHIP_ERROR AccessRestrictionProvider::DoCheck(const std::vector<Entry> entries, requestPath.requestType == RequestType::kAttributeWriteRequest) { #if 0 - return CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL; //TODO: new error code coming with issue #35177 + // TODO(#35177): use new ARL error code when access checks are fixed + return CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL; #else return CHIP_ERROR_ACCESS_DENIED; #endif @@ -209,7 +209,8 @@ CHIP_ERROR AccessRestrictionProvider::DoCheck(const std::vector<Entry> entries, if (requestPath.requestType == RequestType::kAttributeWriteRequest) { #if 0 - return CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL; //TODO: new error code coming with issue #35177 + // TODO(#35177): use new ARL error code when access checks are fixed + return CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL; #else return CHIP_ERROR_ACCESS_DENIED; #endif @@ -219,17 +220,19 @@ CHIP_ERROR AccessRestrictionProvider::DoCheck(const std::vector<Entry> entries, if (requestPath.requestType == RequestType::kCommandInvokeRequest) { #if 0 - return CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL; //TODO: new error code coming with issue #35177 + // TODO(#35177): use new ARL error code when access checks are fixed + return CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL; #else return CHIP_ERROR_ACCESS_DENIED; #endif } break; case Type::kEventForbidden: - if (requestPath.requestType == RequestType::kEventReadOrSubscribeRequest) + if (requestPath.requestType == RequestType::kEventReadRequest) { #if 0 - return CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL; //TODO: new error code coming with issue #35177 + // TODO(#35177): use new ARL error code when access checks are fixed + return CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL; #else return CHIP_ERROR_ACCESS_DENIED; #endif @@ -242,11 +245,5 @@ CHIP_ERROR AccessRestrictionProvider::DoCheck(const std::vector<Entry> entries, return CHIP_NO_ERROR; } -bool AccessRestrictionProvider::IsEntryValid(const Entry & entry) const -{ - return entry.endpointNumber != 0 && entry.clusterId != app::Clusters::NetworkCommissioning::Id && - entry.clusterId != app::Clusters::Descriptor::Id; -} - } // namespace Access } // namespace chip diff --git a/src/access/AccessRestrictionProvider.h b/src/access/AccessRestrictionProvider.h index 7cedeb75cdbd52..77d8fb400255ea 100644 --- a/src/access/AccessRestrictionProvider.h +++ b/src/access/AccessRestrictionProvider.h @@ -86,12 +86,12 @@ class AccessRestrictionProvider virtual ~AccessRestrictionExceptionChecker() = default; /** - * Check if any restrictions are allowed to be applied to the given request. + * Check if any restrictions are allowed to be applied on the given endpoint and cluster + * because of constraints against their use in ARLs. * - * @retval true if restrictions may NOT be applied + * @retval true if ARL checks are allowed to be applied to the cluster on the endpoint, false otherwise */ - virtual bool AreRestrictionsDisallowed(const SubjectDescriptor & subjectDescriptor, - const RequestPath & requestPath) = 0; + virtual bool AreRestrictionsAllowed(EndpointId endpoint, ClusterId cluster) = 0; }; /** @@ -104,13 +104,7 @@ class AccessRestrictionProvider StandardAccessRestrictionExceptionChecker() = default; ~StandardAccessRestrictionExceptionChecker() = default; - /** - * Check if any restrictions are allowed to be applied to the given request. - * - * @retval true if restrictions may NOT be applied - */ - bool AreRestrictionsDisallowed(const SubjectDescriptor & subjectDescriptor, - const RequestPath & requestPath) override; + bool AreRestrictionsAllowed(EndpointId endpoint, ClusterId cluster) override; }; /** @@ -124,25 +118,25 @@ class AccessRestrictionProvider /** * Notifies of a change in the commissioning access restriction list. */ - virtual void CommissioningRestrictionListChanged() = 0; + virtual void MarkCommissioningRestrictionListChanged() = 0; /** * Notifies of a change in the access restriction list. * * @param [in] fabricIndex The index of the fabric in which the list has changed. */ - virtual void RestrictionListChanged(FabricIndex fabricIndex) = 0; + virtual void MarkRestrictionListChanged(FabricIndex fabricIndex) = 0; /** * Notifies of an update to an active review with instructions and an optional redirect URL. * * @param [in] fabricIndex The index of the fabric in which the entry has changed. * @param [in] token The token of the review being updated (obtained from ReviewFabricRestrictionsResponse) - * @param [in] instruction The instructions to be displayed to the user. - * @param [in] redirectUrl An optional URL to redirect the user to for more information. May be null. + * @param [in] instruction Optional instructions to be displayed to the user. + * @param [in] redirectUrl An optional URL to redirect the user to for more information. */ - virtual void OnFabricRestrictionReviewUpdate(FabricIndex fabricIndex, uint64_t token, const char * instruction, - const char * redirectUrl) = 0; + virtual void OnFabricRestrictionReviewUpdate(FabricIndex fabricIndex, uint64_t token, Optional<CharSpan> instruction, + Optional<CharSpan> redirectUrl) = 0; private: Listener * mNext = nullptr; @@ -220,7 +214,7 @@ class AccessRestrictionProvider */ CHIP_ERROR RequestFabricRestrictionReview(FabricIndex fabricIndex, const std::vector<Entry> & arl, uint64_t & token) { - token = ++mNextToken; + token = mNextToken++; return DoRequestFabricRestrictionReview(fabricIndex, token, arl); } @@ -264,12 +258,10 @@ class AccessRestrictionProvider const std::vector<Entry> & arl) = 0; private: - bool IsEntryValid(const Entry & entry) const; - /** * Perform the access restriction check using the given entries. */ - CHIP_ERROR DoCheck(const std::vector<Entry> entries, const SubjectDescriptor & subjectDescriptor, + CHIP_ERROR DoCheck(const std::vector<Entry> & entries, const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath); uint64_t mNextToken = 1; diff --git a/src/access/RequestPath.h b/src/access/RequestPath.h index af791d73eb151d..920d5fed372eb1 100644 --- a/src/access/RequestPath.h +++ b/src/access/RequestPath.h @@ -30,7 +30,7 @@ enum class RequestType : uint8_t kAttributeReadRequest, kAttributeWriteRequest, kCommandInvokeRequest, - kEventReadOrSubscribeRequest + kEventReadRequest }; struct RequestPath diff --git a/src/access/tests/TestAccessRestrictionProvider.cpp b/src/access/tests/TestAccessRestrictionProvider.cpp index 073d3e6081a47c..f7db6927424e33 100644 --- a/src/access/tests/TestAccessRestrictionProvider.cpp +++ b/src/access/tests/TestAccessRestrictionProvider.cpp @@ -94,7 +94,7 @@ constexpr CheckData checkDataNoRestrictions[] = { .privilege = Privilege::kAdminister, .allow = true }, { .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kPase, .subject = kOperationalNodeId3 }, - .requestPath = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kEventReadOrSubscribeRequest, .entityId = 1 }, + .requestPath = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 }, .privilege = Privilege::kAdminister, .allow = true }, // Checks for entry 0 @@ -111,7 +111,7 @@ constexpr CheckData checkDataNoRestrictions[] = { .privilege = Privilege::kAdminister, .allow = true }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kEventReadOrSubscribeRequest, .entityId = 1 }, + .requestPath = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 }, .privilege = Privilege::kAdminister, .allow = true }, // Checks for entry 1 @@ -128,7 +128,7 @@ constexpr CheckData checkDataNoRestrictions[] = { .privilege = Privilege::kAdminister, .allow = true }, { .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subject = kOperationalNodeId2 }, - .requestPath = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kEventReadOrSubscribeRequest, .entityId = 1 }, + .requestPath = { .cluster = 1, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 }, .privilege = Privilege::kAdminister, .allow = true }, }; @@ -251,7 +251,7 @@ constexpr CheckData accessAttributeRestrictionTestData[] = { { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, - .requestType = RequestType::kEventReadOrSubscribeRequest, + .requestType = RequestType::kEventReadRequest, .entityId = 1 }, .privilege = Privilege::kAdminister, .allow = true }, @@ -295,7 +295,7 @@ constexpr CheckData writeAttributeRestrictionTestData[] = { { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, - .requestType = RequestType::kEventReadOrSubscribeRequest, + .requestType = RequestType::kEventReadRequest, .entityId = 1 }, .privilege = Privilege::kAdminister, .allow = true }, @@ -339,7 +339,7 @@ constexpr CheckData commandAttributeRestrictionTestData[] = { { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, - .requestType = RequestType::kEventReadOrSubscribeRequest, + .requestType = RequestType::kEventReadRequest, .entityId = 1 }, .privilege = Privilege::kAdminister, .allow = true }, @@ -383,7 +383,7 @@ constexpr CheckData eventAttributeRestrictionTestData[] = { { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, - .requestType = RequestType::kEventReadOrSubscribeRequest, + .requestType = RequestType::kEventReadRequest, .entityId = 1 }, .privilege = Privilege::kAdminister, .allow = false }, @@ -439,7 +439,7 @@ constexpr CheckData combinedRestrictionTestData[] = { { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, - .requestType = RequestType::kEventReadOrSubscribeRequest, + .requestType = RequestType::kEventReadRequest, .entityId = 5 }, .privilege = Privilege::kAdminister, .allow = true }, diff --git a/src/app/EventManagement.cpp b/src/app/EventManagement.cpp index 4c5faab3cdcda4..2419d564d0bc7a 100644 --- a/src/app/EventManagement.cpp +++ b/src/app/EventManagement.cpp @@ -556,7 +556,7 @@ CHIP_ERROR EventManagement::CheckEventContext(EventLoadOutContext * eventLoadOut Access::RequestPath requestPath{ .cluster = event.mClusterId, .endpoint = event.mEndpointId, - .requestType = Access::RequestType::kEventReadOrSubscribeRequest, + .requestType = Access::RequestType::kEventReadRequest, .entityId = event.mEventId }; Access::Privilege requestPrivilege = RequiredPrivilege::ForReadEvent(path); CHIP_ERROR accessControlError = diff --git a/src/app/InteractionModelEngine.cpp b/src/app/InteractionModelEngine.cpp index 64d30bc6c0e42f..88c1777b6c8224 100644 --- a/src/app/InteractionModelEngine.cpp +++ b/src/app/InteractionModelEngine.cpp @@ -544,7 +544,7 @@ static bool CanAccessEvent(const Access::SubjectDescriptor & aSubjectDescriptor, { Access::RequestPath requestPath{ .cluster = aPath.mClusterId, .endpoint = aPath.mEndpointId, - .requestType = Access::RequestType::kEventReadOrSubscribeRequest }; + .requestType = Access::RequestType::kEventReadRequest }; // leave requestPath.entityId optional value unset to indicate wildcard CHIP_ERROR err = Access::GetAccessControl().Check(aSubjectDescriptor, requestPath, aNeededPrivilege); return (err == CHIP_NO_ERROR); @@ -555,7 +555,7 @@ static bool CanAccessEvent(const Access::SubjectDescriptor & aSubjectDescriptor, { Access::RequestPath requestPath{ .cluster = aPath.mClusterId, .endpoint = aPath.mEndpointId, - .requestType = Access::RequestType::kEventReadOrSubscribeRequest, + .requestType = Access::RequestType::kEventReadRequest, .entityId = aPath.mEventId }; CHIP_ERROR err = Access::GetAccessControl().Check(aSubjectDescriptor, requestPath, RequiredPrivilege::ForReadEvent(aPath)); return (err == CHIP_NO_ERROR); diff --git a/src/app/clusters/access-control-server/ArlEncoder.cpp b/src/app/clusters/access-control-server/ArlEncoder.cpp index 843a74553aacbc..810c7345a13c7e 100644 --- a/src/app/clusters/access-control-server/ArlEncoder.cpp +++ b/src/app/clusters/access-control-server/ArlEncoder.cpp @@ -36,7 +36,7 @@ CHIP_ERROR StageEntryRestrictions(const std::vector<AccessRestrictionProvider::R { for (size_t i = 0; i < count; i++) { - auto restriction = source[i]; + const auto & restriction = source[i]; ReturnErrorOnFailure(ArlEncoder::Convert(restriction.restrictionType, destination[i].type)); if (restriction.id.HasValue()) @@ -118,13 +118,6 @@ CHIP_ERROR ArlEncoder::EncodableEntry::EncodeForRead(TLV::TLVWriter & writer, TL return CHIP_NO_ERROR; } -CHIP_ERROR ArlEncoder::EncodableEntry::EncodeForWrite(TLV::TLVWriter & writer, TLV::Tag tag) const -{ - ReturnErrorOnFailure(Stage()); - ReturnErrorOnFailure(mStagingEntry.EncodeForWrite(writer, tag)); - return CHIP_NO_ERROR; -} - CHIP_ERROR ArlEncoder::CommissioningEncodableEntry::Stage() const { mStagingEntry.endpoint = mEntry.endpointNumber; diff --git a/src/app/clusters/access-control-server/ArlEncoder.h b/src/app/clusters/access-control-server/ArlEncoder.h index ce0d6336c68c96..8050bf4d739db6 100644 --- a/src/app/clusters/access-control-server/ArlEncoder.h +++ b/src/app/clusters/access-control-server/ArlEncoder.h @@ -93,11 +93,6 @@ class ArlEncoder */ CHIP_ERROR EncodeForRead(TLV::TLVWriter & writer, TLV::Tag tag, FabricIndex fabric) const; - /** - * Encode the constructor-provided entry into the TLV writer. - */ - CHIP_ERROR EncodeForWrite(TLV::TLVWriter & writer, TLV::Tag tag) const; - FabricIndex GetFabricIndex() const { return mEntry.fabricIndex; } static constexpr bool kIsFabricScoped = true; diff --git a/src/app/clusters/access-control-server/access-control-server.cpp b/src/app/clusters/access-control-server/access-control-server.cpp index 3c1412d57a6401..3a1bfb29cbb219 100644 --- a/src/app/clusters/access-control-server/access-control-server.cpp +++ b/src/app/clusters/access-control-server/access-control-server.cpp @@ -48,12 +48,7 @@ using EntryListener = AccessControl::EntryListener; using ExtensionEvent = Clusters::AccessControl::Events::AccessControlExtensionChanged::Type; #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS -using ArlChangedEvent = Clusters::AccessControl::Events::AccessRestrictionEntryChanged::Type; using ArlReviewEvent = Clusters::AccessControl::Events::FabricRestrictionReviewUpdate::Type; - -constexpr uint16_t kMaxInstructionStringLength = 512; - -constexpr uint16_t kMaxRedirectUrlStringLength = 256; #endif // TODO(#13590): generated code doesn't automatically handle max length so do it manually @@ -88,12 +83,12 @@ class AccessControlAttribute : public AttributeAccessInterface, const AccessControl::Entry * entry, AccessControl::EntryListener::ChangeType changeType) override; #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS - void CommissioningRestrictionListChanged() override; + void MarkCommissioningRestrictionListChanged() override; - void RestrictionListChanged(FabricIndex fabricIndex) override; + void MarkRestrictionListChanged(FabricIndex fabricIndex) override; - void OnFabricRestrictionReviewUpdate(FabricIndex fabricIndex, uint64_t token, const char * instruction, - const char * redirectUrl) override; + void OnFabricRestrictionReviewUpdate(FabricIndex fabricIndex, uint64_t token, Optional<CharSpan> instruction, + Optional<CharSpan> redirectUrl) override; #endif private: @@ -147,8 +142,8 @@ CHIP_ERROR CheckExtensionEntryDataFormat(const ByteSpan & data) TLV::TLVReader reader; reader.Init(data); - auto containerType = chip::TLV::kTLVType_List; - err = reader.Next(containerType, chip::TLV::AnonymousTag()); + auto containerType = TLV::kTLVType_List; + err = reader.Next(containerType, TLV::AnonymousTag()); VerifyOrReturnError(err == CHIP_NO_ERROR, CHIP_IM_GLOBAL_STATUS(ConstraintError)); err = reader.EnterContainer(containerType); @@ -156,7 +151,7 @@ CHIP_ERROR CheckExtensionEntryDataFormat(const ByteSpan & data) while ((err = reader.Next()) == CHIP_NO_ERROR) { - VerifyOrReturnError(chip::TLV::IsProfileTag(reader.GetTag()), CHIP_IM_GLOBAL_STATUS(ConstraintError)); + VerifyOrReturnError(TLV::IsProfileTag(reader.GetTag()), CHIP_IM_GLOBAL_STATUS(ConstraintError)); } VerifyOrReturnError(err == CHIP_END_OF_TLV, CHIP_IM_GLOBAL_STATUS(ConstraintError)); @@ -471,18 +466,17 @@ void AccessControlAttribute::OnEntryChanged(const SubjectDescriptor * subjectDes CHIP_ERROR AccessControlAttribute::ReadCommissioningArl(AttributeValueEncoder & aEncoder) { auto accessRestrictionProvider = GetAccessControl().GetAccessRestrictionProvider(); - if (accessRestrictionProvider == nullptr) - { - return CHIP_ERROR_NOT_IMPLEMENTED; - } return aEncoder.EncodeList([&](const auto & encoder) -> CHIP_ERROR { - auto entries = accessRestrictionProvider->GetCommissioningEntries(); - - for (auto & entry : entries) + if (accessRestrictionProvider != nullptr) { - ArlEncoder::CommissioningEncodableEntry encodableEntry(entry); - ReturnErrorOnFailure(encoder.Encode(encodableEntry)); + auto entries = accessRestrictionProvider->GetCommissioningEntries(); + + for (auto & entry : entries) + { + ArlEncoder::CommissioningEncodableEntry encodableEntry(entry); + ReturnErrorOnFailure(encoder.Encode(encodableEntry)); + } } return CHIP_NO_ERROR; }); @@ -491,72 +485,59 @@ CHIP_ERROR AccessControlAttribute::ReadCommissioningArl(AttributeValueEncoder & CHIP_ERROR AccessControlAttribute::ReadArl(AttributeValueEncoder & aEncoder) { auto accessRestrictionProvider = GetAccessControl().GetAccessRestrictionProvider(); - if (accessRestrictionProvider == nullptr) - { - return CHIP_ERROR_NOT_IMPLEMENTED; - } return aEncoder.EncodeList([&](const auto & encoder) -> CHIP_ERROR { - for (auto & info : Server::GetInstance().GetFabricTable()) + if (accessRestrictionProvider != nullptr) { - auto fabric = info.GetFabricIndex(); - // get entries for fabric - std::vector<AccessRestrictionProvider::Entry> entries; - ReturnErrorOnFailure(accessRestrictionProvider->GetEntries(fabric, entries)); - for (auto & entry : entries) + for (const auto & info : Server::GetInstance().GetFabricTable()) { - ArlEncoder::EncodableEntry encodableEntry(entry); - ReturnErrorOnFailure(encoder.Encode(encodableEntry)); + auto fabric = info.GetFabricIndex(); + // get entries for fabric + std::vector<AccessRestrictionProvider::Entry> entries; + ReturnErrorOnFailure(accessRestrictionProvider->GetEntries(fabric, entries)); + for (const auto & entry : entries) + { + ArlEncoder::EncodableEntry encodableEntry(entry); + ReturnErrorOnFailure(encoder.Encode(encodableEntry)); + } } } return CHIP_NO_ERROR; }); } -void AccessControlAttribute::CommissioningRestrictionListChanged() +void AccessControlAttribute::MarkCommissioningRestrictionListChanged() { - MatterReportingAttributeChangeCallback(0, AccessControlCluster::Id, AccessControlCluster::Attributes::CommissioningARL::Id); + MatterReportingAttributeChangeCallback(kRootEndpointId, AccessControlCluster::Id, AccessControlCluster::Attributes::CommissioningARL::Id); } -void AccessControlAttribute::RestrictionListChanged(FabricIndex fabricIndex) +void AccessControlAttribute::MarkRestrictionListChanged(FabricIndex fabricIndex) { - CHIP_ERROR err; - - MatterReportingAttributeChangeCallback(0, AccessControlCluster::Id, AccessControlCluster::Attributes::Arl::Id); - - ArlChangedEvent event{ .fabricIndex = fabricIndex }; - - EventNumber eventNumber; - SuccessOrExit(err = LogEvent(event, 0, eventNumber)); - - return; - -exit: - ChipLogError(DataManagement, "AccessControlCluster: restriction event failed %" CHIP_ERROR_FORMAT, err.Format()); + MatterReportingAttributeChangeCallback(kRootEndpointId, AccessControlCluster::Id, AccessControlCluster::Attributes::Arl::Id); } -void AccessControlAttribute::OnFabricRestrictionReviewUpdate(FabricIndex fabricIndex, uint64_t token, const char * instruction, - const char * redirectUrl) +void AccessControlAttribute::OnFabricRestrictionReviewUpdate(FabricIndex fabricIndex, uint64_t token, Optional<CharSpan> instruction, + Optional<CharSpan> redirectUrl) { CHIP_ERROR err; ArlReviewEvent event{ .token = token, .fabricIndex = fabricIndex }; - if (instruction != nullptr) + if (instruction.HasValue()) { - event.instruction.SetNonNull(chip::CharSpan(instruction, strnlen(instruction, kMaxInstructionStringLength))); + event.instruction.SetNonNull(instruction.Value()); } - if (redirectUrl != nullptr) + if (redirectUrl.HasValue()) { - event.redirectURL.SetNonNull(chip::CharSpan(redirectUrl, strnlen(redirectUrl, kMaxRedirectUrlStringLength))); + event.redirectURL.SetNonNull(redirectUrl.Value()); } EventNumber eventNumber; - SuccessOrExit(err = LogEvent(event, 0, eventNumber)); + SuccessOrExit(err = LogEvent(event, kRootEndpointId, eventNumber)); return; exit: - ChipLogError(DataManagement, "AccessControlCluster: review event failed %" CHIP_ERROR_FORMAT, err.Format()); + ChipLogError(DataManagement, "AccessControlCluster: review event failed: %" CHIP_ERROR_FORMAT, err.Format()); } #endif @@ -628,9 +609,10 @@ bool emberAfAccessControlClusterReviewFabricRestrictionsCallback( CommandHandler * commandObj, const ConcreteCommandPath & commandPath, const Clusters::AccessControl::Commands::ReviewFabricRestrictions::DecodableType & commandData) { - if (commandPath.mEndpointId != 0) + if (commandPath.mEndpointId != kRootEndpointId) { ChipLogError(DataManagement, "AccessControlCluster: invalid endpoint in ReviewFabricRestrictions request"); + commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::InvalidValue); return true; } @@ -651,6 +633,7 @@ bool emberAfAccessControlClusterReviewFabricRestrictionsCallback( if (ArlEncoder::Convert(restrictionIter.GetValue().type, restriction.restrictionType) != CHIP_NO_ERROR) { ChipLogError(DataManagement, "AccessControlCluster: invalid restriction type conversion"); + commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::InvalidValue); return true; } @@ -664,6 +647,13 @@ bool emberAfAccessControlClusterReviewFabricRestrictionsCallback( entries.push_back(entry); } + if (entryIter.GetStatus() != CHIP_NO_ERROR) + { + ChipLogError(DataManagement, "AccessControlCluster: invalid ARL data"); + commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::InvalidValue); + return true; + } + CHIP_ERROR err = GetAccessControl().GetAccessRestrictionProvider()->RequestFabricRestrictionReview( commandObj->GetAccessingFabricIndex(), entries, token); @@ -676,7 +666,7 @@ bool emberAfAccessControlClusterReviewFabricRestrictionsCallback( else { ChipLogError(DataManagement, "AccessControlCluster: restriction review failed: %" CHIP_ERROR_FORMAT, err.Format()); - commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::Failure); + commandObj->AddStatus(commandPath, Protocols::InteractionModel::ClusterStatusCode(err)); } return true; diff --git a/src/app/reporting/Engine.cpp b/src/app/reporting/Engine.cpp index 6d79e9e7b34af0..99d038a8c35471 100644 --- a/src/app/reporting/Engine.cpp +++ b/src/app/reporting/Engine.cpp @@ -341,7 +341,7 @@ CHIP_ERROR Engine::CheckAccessDeniedEventPaths(TLV::TLVWriter & aWriter, bool & Access::RequestPath requestPath{ .cluster = current->mValue.mClusterId, .endpoint = current->mValue.mEndpointId, - .requestType = RequestType::kEventReadOrSubscribeRequest, + .requestType = RequestType::kEventReadRequest, .entityId = current->mValue.mEventId }; Access::Privilege requestPrivilege = RequiredPrivilege::ForReadEvent(path); diff --git a/src/credentials/FabricTable.h b/src/credentials/FabricTable.h index 1bb13485873c9d..af90d781283963 100644 --- a/src/credentials/FabricTable.h +++ b/src/credentials/FabricTable.h @@ -737,7 +737,7 @@ class DLL_EXPORT FabricTable bool HasOperationalKeyForFabric(FabricIndex fabricIndex) const; /** - * @brief If a newly-added fabric is pending, this returns its index, or kUndefinedFabricIndex in none are pending. + * @brief If a newly-added fabric is pending, this returns its index, or kUndefinedFabricIndex if none are pending. * * A newly-added fabric is pending if AddNOC has been previously called successfully but the * fabric is not yet fully committed by CommissioningComplete. diff --git a/src/lib/support/DefaultStorageKeyAllocator.h b/src/lib/support/DefaultStorageKeyAllocator.h index 8cc1bd67a5784c..9ed8a2f56cfd77 100644 --- a/src/lib/support/DefaultStorageKeyAllocator.h +++ b/src/lib/support/DefaultStorageKeyAllocator.h @@ -130,16 +130,6 @@ class DefaultStorageKeyAllocator static StorageKeyName AccessControlExtensionEntry(FabricIndex fabric) { return StorageKeyName::Formatted("f/%x/ac/1", fabric); } - static StorageKeyName AccessControlCommissioningArlEntry(size_t index) - { - return StorageKeyName::Formatted("g/car/%x", static_cast<unsigned>(index)); - } - - static StorageKeyName AccessControlArlEntry(FabricIndex fabric, size_t index) - { - return StorageKeyName::Formatted("f/%x/ar/0/%x", fabric, static_cast<unsigned>(index)); - } - // Group Message Counters static StorageKeyName GroupDataCounter() { return StorageKeyName::FromConst("g/gdc"); } static StorageKeyName GroupControlCounter() { return StorageKeyName::FromConst("g/gcc"); } diff --git a/src/messaging/ExchangeContext.h b/src/messaging/ExchangeContext.h index 7d3aa146481195..e10ef84ce911be 100644 --- a/src/messaging/ExchangeContext.h +++ b/src/messaging/ExchangeContext.h @@ -163,7 +163,7 @@ class DLL_EXPORT ExchangeContext : public ReliableMessageContext, return std::move(sessionHandle.Value()); } - bool HasSessionHandle() const { return (bool)mSession; } + bool HasSessionHandle() const { return mSession; } uint16_t GetExchangeId() const { return mExchangeId; } diff --git a/src/transport/SecureSession.h b/src/transport/SecureSession.h index b73ccf5b2793a7..6dd8667cd63e95 100644 --- a/src/transport/SecureSession.h +++ b/src/transport/SecureSession.h @@ -331,6 +331,7 @@ class SecureSession : public Session, public ReferenceCounted<SecureSession, Sec SecureSessionTable & mTable; State mState; const Type mSecureSessionType; + bool mIsCaseCommissioningSession = false; NodeId mLocalNodeId = kUndefinedNodeId; NodeId mPeerNodeId = kUndefinedNodeId; CATValues mPeerCATs = CATValues{}; @@ -348,8 +349,6 @@ class SecureSession : public Session, public ReferenceCounted<SecureSession, Sec SessionParameters mRemoteSessionParams; CryptoContext mCryptoContext; SessionMessageCounter mSessionMessageCounter; - - bool mIsCaseCommissioningSession = false; }; } // namespace Transport diff --git a/src/transport/Session.h b/src/transport/Session.h index e527a7887f2dd9..b49d461acb744c 100644 --- a/src/transport/Session.h +++ b/src/transport/Session.h @@ -247,7 +247,8 @@ class Session // Returns true if `subjectDescriptor.IsCommissioning` (based on Core Specification // pseudocode in ACL Architecture chapter) should be true when computing a - // subject descriptor for that session. + // subject descriptor for that session. This is only valid to call during + // synchronous processing of a message received on the session. virtual bool IsCommissioningSession() const { return false; } // GetAckTimeout is the estimate for how long it could take for the other From b1ae8e3d4738cdfcc37827f80c10e6176060711e Mon Sep 17 00:00:00 2001 From: Thomas Lea <thomas_lea@comcast.com> Date: Sun, 25 Aug 2024 15:51:24 -0500 Subject: [PATCH 16/22] restyled --- src/access/AccessControl.cpp | 2 +- src/access/AccessRestrictionProvider.cpp | 6 +-- src/access/AccessRestrictionProvider.h | 30 ++++++------- .../tests/TestAccessRestrictionProvider.cpp | 45 +++++++------------ .../access-control-server.cpp | 9 ++-- src/credentials/FabricTable.cpp | 19 ++++---- src/transport/SecureSession.h | 6 +-- src/transport/SessionManager.cpp | 3 +- 8 files changed, 55 insertions(+), 65 deletions(-) diff --git a/src/access/AccessControl.cpp b/src/access/AccessControl.cpp index abb15b02da240d..8302fb0b122265 100644 --- a/src/access/AccessControl.cpp +++ b/src/access/AccessControl.cpp @@ -333,7 +333,7 @@ bool AccessControl::IsAccessRestrictionListSupported() const } CHIP_ERROR AccessControl::Check(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath, - Privilege requestPrivilege) + Privilege requestPrivilege) { VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE); diff --git a/src/access/AccessRestrictionProvider.cpp b/src/access/AccessRestrictionProvider.cpp index 6c6a4c6aadf342..1bf9175ce62fd9 100644 --- a/src/access/AccessRestrictionProvider.cpp +++ b/src/access/AccessRestrictionProvider.cpp @@ -119,11 +119,11 @@ CHIP_ERROR AccessRestrictionProvider::SetEntries(const FabricIndex fabricIndex, return CHIP_NO_ERROR; } -bool AccessRestrictionProvider::StandardAccessRestrictionExceptionChecker::AreRestrictionsAllowed(EndpointId endpoint, ClusterId cluster) +bool AccessRestrictionProvider::StandardAccessRestrictionExceptionChecker::AreRestrictionsAllowed(EndpointId endpoint, + ClusterId cluster) { if (endpoint != 0 && - (cluster == app::Clusters::WiFiNetworkManagement::Id || - cluster == app::Clusters::ThreadBorderRouterManagement::Id || + (cluster == app::Clusters::WiFiNetworkManagement::Id || cluster == app::Clusters::ThreadBorderRouterManagement::Id || cluster == app::Clusters::ThreadNetworkDirectory::Id)) { return true; diff --git a/src/access/AccessRestrictionProvider.h b/src/access/AccessRestrictionProvider.h index 77d8fb400255ea..705d9c365f8f1f 100644 --- a/src/access/AccessRestrictionProvider.h +++ b/src/access/AccessRestrictionProvider.h @@ -18,9 +18,9 @@ #pragma once +#include "Privilege.h" #include "RequestPath.h" #include "SubjectDescriptor.h" -#include "Privilege.h" #include <algorithm> #include <app-common/zap-generated/cluster-objects.h> #include <cstdint> @@ -82,16 +82,16 @@ class AccessRestrictionProvider */ class AccessRestrictionExceptionChecker { - public: - virtual ~AccessRestrictionExceptionChecker() = default; - - /** - * Check if any restrictions are allowed to be applied on the given endpoint and cluster - * because of constraints against their use in ARLs. - * - * @retval true if ARL checks are allowed to be applied to the cluster on the endpoint, false otherwise - */ - virtual bool AreRestrictionsAllowed(EndpointId endpoint, ClusterId cluster) = 0; + public: + virtual ~AccessRestrictionExceptionChecker() = default; + + /** + * Check if any restrictions are allowed to be applied on the given endpoint and cluster + * because of constraints against their use in ARLs. + * + * @retval true if ARL checks are allowed to be applied to the cluster on the endpoint, false otherwise + */ + virtual bool AreRestrictionsAllowed(EndpointId endpoint, ClusterId cluster) = 0; }; /** @@ -100,11 +100,11 @@ class AccessRestrictionProvider */ class StandardAccessRestrictionExceptionChecker : public AccessRestrictionExceptionChecker { - public: - StandardAccessRestrictionExceptionChecker() = default; - ~StandardAccessRestrictionExceptionChecker() = default; + public: + StandardAccessRestrictionExceptionChecker() = default; + ~StandardAccessRestrictionExceptionChecker() = default; - bool AreRestrictionsAllowed(EndpointId endpoint, ClusterId cluster) override; + bool AreRestrictionsAllowed(EndpointId endpoint, ClusterId cluster) override; }; /** diff --git a/src/access/tests/TestAccessRestrictionProvider.cpp b/src/access/tests/TestAccessRestrictionProvider.cpp index f7db6927424e33..d0dc87d7dcf966 100644 --- a/src/access/tests/TestAccessRestrictionProvider.cpp +++ b/src/access/tests/TestAccessRestrictionProvider.cpp @@ -249,12 +249,9 @@ constexpr CheckData accessAttributeRestrictionTestData[] = { .privilege = Privilege::kAdminister, .allow = true }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kOnOffCluster, - .endpoint = 1, - .requestType = RequestType::kEventReadRequest, - .entityId = 1 }, - .privilege = Privilege::kAdminister, - .allow = true }, + .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, }; TEST_F(TestAccessRestriction, AccessAttributeRestrictionTest) @@ -293,12 +290,9 @@ constexpr CheckData writeAttributeRestrictionTestData[] = { .privilege = Privilege::kAdminister, .allow = true }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kOnOffCluster, - .endpoint = 1, - .requestType = RequestType::kEventReadRequest, - .entityId = 1 }, - .privilege = Privilege::kAdminister, - .allow = true }, + .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, }; TEST_F(TestAccessRestriction, WriteAttributeRestrictionTest) @@ -337,12 +331,9 @@ constexpr CheckData commandAttributeRestrictionTestData[] = { .privilege = Privilege::kAdminister, .allow = false }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kOnOffCluster, - .endpoint = 1, - .requestType = RequestType::kEventReadRequest, - .entityId = 1 }, - .privilege = Privilege::kAdminister, - .allow = true }, + .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, }; TEST_F(TestAccessRestriction, CommandRestrictionTest) @@ -381,12 +372,9 @@ constexpr CheckData eventAttributeRestrictionTestData[] = { .privilege = Privilege::kAdminister, .allow = true }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kOnOffCluster, - .endpoint = 1, - .requestType = RequestType::kEventReadRequest, - .entityId = 1 }, - .privilege = Privilege::kAdminister, - .allow = false }, + .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = false }, }; TEST_F(TestAccessRestriction, EventRestrictionTest) @@ -437,12 +425,9 @@ constexpr CheckData combinedRestrictionTestData[] = { .privilege = Privilege::kAdminister, .allow = true }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kOnOffCluster, - .endpoint = 1, - .requestType = RequestType::kEventReadRequest, - .entityId = 5 }, - .privilege = Privilege::kAdminister, - .allow = true }, + .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 5 }, + .privilege = Privilege::kAdminister, + .allow = true }, { .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subject = kOperationalNodeId2 }, .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 }, .privilege = Privilege::kAdminister, diff --git a/src/app/clusters/access-control-server/access-control-server.cpp b/src/app/clusters/access-control-server/access-control-server.cpp index 3a1bfb29cbb219..e43aad01490b56 100644 --- a/src/app/clusters/access-control-server/access-control-server.cpp +++ b/src/app/clusters/access-control-server/access-control-server.cpp @@ -48,7 +48,7 @@ using EntryListener = AccessControl::EntryListener; using ExtensionEvent = Clusters::AccessControl::Events::AccessControlExtensionChanged::Type; #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS -using ArlReviewEvent = Clusters::AccessControl::Events::FabricRestrictionReviewUpdate::Type; +using ArlReviewEvent = Clusters::AccessControl::Events::FabricRestrictionReviewUpdate::Type; #endif // TODO(#13590): generated code doesn't automatically handle max length so do it manually @@ -507,7 +507,8 @@ CHIP_ERROR AccessControlAttribute::ReadArl(AttributeValueEncoder & aEncoder) } void AccessControlAttribute::MarkCommissioningRestrictionListChanged() { - MatterReportingAttributeChangeCallback(kRootEndpointId, AccessControlCluster::Id, AccessControlCluster::Attributes::CommissioningARL::Id); + MatterReportingAttributeChangeCallback(kRootEndpointId, AccessControlCluster::Id, + AccessControlCluster::Attributes::CommissioningARL::Id); } void AccessControlAttribute::MarkRestrictionListChanged(FabricIndex fabricIndex) @@ -515,8 +516,8 @@ void AccessControlAttribute::MarkRestrictionListChanged(FabricIndex fabricIndex) MatterReportingAttributeChangeCallback(kRootEndpointId, AccessControlCluster::Id, AccessControlCluster::Attributes::Arl::Id); } -void AccessControlAttribute::OnFabricRestrictionReviewUpdate(FabricIndex fabricIndex, uint64_t token, Optional<CharSpan> instruction, - Optional<CharSpan> redirectUrl) +void AccessControlAttribute::OnFabricRestrictionReviewUpdate(FabricIndex fabricIndex, uint64_t token, + Optional<CharSpan> instruction, Optional<CharSpan> redirectUrl) { CHIP_ERROR err; ArlReviewEvent event{ .token = token, .fabricIndex = fabricIndex }; diff --git a/src/credentials/FabricTable.cpp b/src/credentials/FabricTable.cpp index 9a9eef9929c0db..f999cf7a4f9a9d 100644 --- a/src/credentials/FabricTable.cpp +++ b/src/credentials/FabricTable.cpp @@ -71,8 +71,9 @@ constexpr size_t IndexInfoTLVMaxSize() return TLV::EstimateStructOverhead(sizeof(FabricIndex), CHIP_CONFIG_MAX_FABRICS * (1 + sizeof(FabricIndex)) + 1); } -CHIP_ERROR AddNewFabricForTestInternal(FabricTable & fabricTable, bool leavePending, const ByteSpan & rootCert, const ByteSpan & icacCert, const ByteSpan & nocCert, - const ByteSpan & opKeySpan, FabricIndex * outFabricIndex) +CHIP_ERROR AddNewFabricForTestInternal(FabricTable & fabricTable, bool leavePending, const ByteSpan & rootCert, + const ByteSpan & icacCert, const ByteSpan & nocCert, const ByteSpan & opKeySpan, + FabricIndex * outFabricIndex) { VerifyOrReturnError(outFabricIndex != nullptr, CHIP_ERROR_INVALID_ARGUMENT); @@ -93,8 +94,9 @@ CHIP_ERROR AddNewFabricForTestInternal(FabricTable & fabricTable, bool leavePend } SuccessOrExit(err = fabricTable.AddNewPendingTrustedRootCert(rootCert)); - SuccessOrExit(err = fabricTable.AddNewPendingFabricWithProvidedOpKey(nocCert, icacCert, VendorId::TestVendor1, opKey, - /*isExistingOpKeyExternallyOwned =*/false, outFabricIndex)); + SuccessOrExit(err = + fabricTable.AddNewPendingFabricWithProvidedOpKey(nocCert, icacCert, VendorId::TestVendor1, opKey, + /*isExistingOpKeyExternallyOwned =*/false, outFabricIndex)); if (!leavePending) { SuccessOrExit(err = fabricTable.CommitPendingFabricData()); @@ -731,13 +733,14 @@ CHIP_ERROR FabricTable::LoadFromStorage(FabricInfo * fabric, FabricIndex newFabr CHIP_ERROR FabricTable::AddNewFabricForTest(const ByteSpan & rootCert, const ByteSpan & icacCert, const ByteSpan & nocCert, const ByteSpan & opKeySpan, FabricIndex * outFabricIndex) { - return AddNewFabricForTestInternal(*this, /*leavePending=*/ false, rootCert, icacCert, nocCert, opKeySpan, outFabricIndex); + return AddNewFabricForTestInternal(*this, /*leavePending=*/false, rootCert, icacCert, nocCert, opKeySpan, outFabricIndex); } -CHIP_ERROR FabricTable::AddNewUncommittedFabricForTest(const ByteSpan & rootCert, const ByteSpan & icacCert, const ByteSpan & nocCert, - const ByteSpan & opKeySpan, FabricIndex * outFabricIndex) +CHIP_ERROR FabricTable::AddNewUncommittedFabricForTest(const ByteSpan & rootCert, const ByteSpan & icacCert, + const ByteSpan & nocCert, const ByteSpan & opKeySpan, + FabricIndex * outFabricIndex) { - return AddNewFabricForTestInternal(*this, /*leavePending=*/ true, rootCert, icacCert, nocCert, opKeySpan, outFabricIndex); + return AddNewFabricForTestInternal(*this, /*leavePending=*/true, rootCert, icacCert, nocCert, opKeySpan, outFabricIndex); } /* diff --git a/src/transport/SecureSession.h b/src/transport/SecureSession.h index 6dd8667cd63e95..ba586a3095e3dd 100644 --- a/src/transport/SecureSession.h +++ b/src/transport/SecureSession.h @@ -332,9 +332,9 @@ class SecureSession : public Session, public ReferenceCounted<SecureSession, Sec State mState; const Type mSecureSessionType; bool mIsCaseCommissioningSession = false; - NodeId mLocalNodeId = kUndefinedNodeId; - NodeId mPeerNodeId = kUndefinedNodeId; - CATValues mPeerCATs = CATValues{}; + NodeId mLocalNodeId = kUndefinedNodeId; + NodeId mPeerNodeId = kUndefinedNodeId; + CATValues mPeerCATs = CATValues{}; const uint16_t mLocalSessionId; uint16_t mPeerSessionId = 0; diff --git a/src/transport/SessionManager.cpp b/src/transport/SessionManager.cpp index 78b822b19acd87..82287339117182 100644 --- a/src/transport/SessionManager.cpp +++ b/src/transport/SessionManager.cpp @@ -1029,7 +1029,8 @@ void SessionManager::SecureUnicastMessageDispatch(const PacketHeader & partialPa // the fabric table. if (secureSession->IsCASESession()) { - secureSession->SetCaseCommissioningSessionStatus(secureSession->GetFabricIndex() == mFabricTable->GetPendingNewFabricIndex()); + secureSession->SetCaseCommissioningSessionStatus(secureSession->GetFabricIndex() == + mFabricTable->GetPendingNewFabricIndex()); } mCB->OnMessageReceived(packetHeader, payloadHeader, session.Value(), isDuplicate, std::move(msg)); } From 7cf925cdeae665c2a60e3cd4b4d37972c3dc7cb3 Mon Sep 17 00:00:00 2001 From: Thomas Lea <thomas_lea@comcast.com> Date: Mon, 26 Aug 2024 11:55:44 -0500 Subject: [PATCH 17/22] Review updates - fixed return type for some command failures - enhanced unit tests --- .../tests/TestAccessRestrictionProvider.cpp | 231 ++++++++++++++---- .../access-control-server.cpp | 4 +- 2 files changed, 187 insertions(+), 48 deletions(-) diff --git a/src/access/tests/TestAccessRestrictionProvider.cpp b/src/access/tests/TestAccessRestrictionProvider.cpp index d0dc87d7dcf966..b1908a4980ae63 100644 --- a/src/access/tests/TestAccessRestrictionProvider.cpp +++ b/src/access/tests/TestAccessRestrictionProvider.cpp @@ -22,9 +22,9 @@ #include <pw_unit_test/framework.h> +#include <app-common/zap-generated/ids/Attributes.h> #include <lib/core/CHIPCore.h> #include <lib/core/StringBuilderAdapters.h> - namespace chip { namespace Access { @@ -39,9 +39,14 @@ class TestAccessRestrictionProvider : public AccessRestrictionProvider AccessControl accessControl; TestAccessRestrictionProvider accessRestrictionProvider; -constexpr ClusterId kNetworkCommissioningCluster = 0x0000'0031; // must not be blocked by access restrictions on any endpoint -constexpr ClusterId kDescriptorCluster = 0x0000'001d; // must not be blocked by access restrictions on any endpoint -constexpr ClusterId kOnOffCluster = 0x0000'0006; +constexpr ClusterId kNetworkCommissioningCluster = app::Clusters::NetworkCommissioning::Id; +constexpr ClusterId kDescriptorCluster = app::Clusters::Descriptor::Id; +constexpr ClusterId kOnOffCluster = app::Clusters::OnOff::Id; + +// Clusters allowed to have restrictions +constexpr ClusterId kWiFiNetworkManagementCluster = app::Clusters::WiFiNetworkManagement::Id; +constexpr ClusterId kThreadBorderRouterMgmtCluster = app::Clusters::ThreadBorderRouterManagement::Id; +constexpr ClusterId kThreadNetworkDirectoryCluster = app::Clusters::ThreadNetworkDirectory::Id; constexpr NodeId kOperationalNodeId1 = 0x1111111111111111; constexpr NodeId kOperationalNodeId2 = 0x2222222222222222; @@ -207,49 +212,103 @@ TEST_F(TestAccessRestriction, MetaTest) } } -// ensure adding restrictons on endpoint 0 (any cluster) or for network commissioning and descriptor clusters fail -TEST_F(TestAccessRestriction, InvalidRestrictionsTest) +// ensure failure when adding restrictons on endpoint 0 (any cluster, including those allowed on other endpoints) +TEST_F(TestAccessRestriction, InvalidRestrictionsOnEndpointZeroTest) { std::vector<AccessRestrictionProvider::Entry> entries; AccessRestrictionProvider::Entry entry; - entry.fabricIndex = 1; - entry.clusterId = kOnOffCluster; + entry.endpointNumber = 0; + entry.fabricIndex = 1; entry.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kAttributeAccessForbidden }); - // must not restrict endpoint 0 - entry.endpointNumber = 0; + entry.clusterId = kDescriptorCluster; entries.push_back(entry); EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_ERROR_INVALID_ARGUMENT); - // must not restrict network commissioning cluster entries.clear(); - entry.endpointNumber = 1; - entry.clusterId = kNetworkCommissioningCluster; + entry.clusterId = kNetworkCommissioningCluster; entries.push_back(entry); EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_ERROR_INVALID_ARGUMENT); - // must not restrict descriptor cluster entries.clear(); - entry.clusterId = kDescriptorCluster; + entry.clusterId = kWiFiNetworkManagementCluster; + entries.push_back(entry); + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_ERROR_INVALID_ARGUMENT); + + entries.clear(); + entry.clusterId = kThreadBorderRouterMgmtCluster; + entries.push_back(entry); + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_ERROR_INVALID_ARGUMENT); + + entries.clear(); + entry.clusterId = kThreadNetworkDirectoryCluster; + entries.push_back(entry); + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_ERROR_INVALID_ARGUMENT); + + // also test a cluster on endpoint 0 that isnt in the special allowed list + entries.clear(); + entry.clusterId = kOnOffCluster; + entries.push_back(entry); + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_ERROR_INVALID_ARGUMENT); +} + +// ensure no failure adding restrictions on endpoint 1 for allowed clusters only: +// wifi network management, thread border router, thread network directory +TEST_F(TestAccessRestriction, ValidRestrictionsOnEndpointOneTest) +{ + std::vector<AccessRestrictionProvider::Entry> entries; + AccessRestrictionProvider::Entry entry; + entry.endpointNumber = 1; + entry.fabricIndex = 1; + entry.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kAttributeAccessForbidden }); + + entry.clusterId = kWiFiNetworkManagementCluster; + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_NO_ERROR); + + entries.clear(); + entry.clusterId = kThreadBorderRouterMgmtCluster; + entries.push_back(entry); + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_NO_ERROR); + + entries.clear(); + entry.clusterId = kThreadNetworkDirectoryCluster; + entries.push_back(entry); + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_NO_ERROR); + + // also test a cluster on endpoint 0 that isnt in the special allowed list + entries.clear(); + entry.clusterId = kOnOffCluster; + entries.push_back(entry); + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_ERROR_INVALID_ARGUMENT); +} + +TEST_F(TestAccessRestriction, InvalidRestrictionsOnEndpointOneTest) +{ + std::vector<AccessRestrictionProvider::Entry> entries; + AccessRestrictionProvider::Entry entry; + entry.endpointNumber = 1; + entry.fabricIndex = 1; + entry.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kAttributeAccessForbidden }); + entry.clusterId = kOnOffCluster; entries.push_back(entry); EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_ERROR_INVALID_ARGUMENT); } constexpr CheckData accessAttributeRestrictionTestData[] = { { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 }, .privilege = Privilege::kAdminister, .allow = false }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 }, .privilege = Privilege::kAdminister, .allow = false }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 }, .privilege = Privilege::kAdminister, .allow = true }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 }, .privilege = Privilege::kAdminister, .allow = true }, }; @@ -260,7 +319,7 @@ TEST_F(TestAccessRestriction, AccessAttributeRestrictionTest) AccessRestrictionProvider::Entry entry; entry.fabricIndex = 1; entry.endpointNumber = 1; - entry.clusterId = kOnOffCluster; + entry.clusterId = kWiFiNetworkManagementCluster; entry.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kAttributeAccessForbidden }); // test wildcarded entity id @@ -278,19 +337,19 @@ TEST_F(TestAccessRestriction, AccessAttributeRestrictionTest) constexpr CheckData writeAttributeRestrictionTestData[] = { { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 }, .privilege = Privilege::kAdminister, .allow = false }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 }, .privilege = Privilege::kAdminister, .allow = true }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 }, .privilege = Privilege::kAdminister, .allow = true }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 }, .privilege = Privilege::kAdminister, .allow = true }, }; @@ -301,7 +360,7 @@ TEST_F(TestAccessRestriction, WriteAttributeRestrictionTest) AccessRestrictionProvider::Entry entry; entry.fabricIndex = 1; entry.endpointNumber = 1; - entry.clusterId = kOnOffCluster; + entry.clusterId = kWiFiNetworkManagementCluster; entry.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kAttributeWriteForbidden }); // test wildcarded entity id @@ -319,19 +378,19 @@ TEST_F(TestAccessRestriction, WriteAttributeRestrictionTest) constexpr CheckData commandAttributeRestrictionTestData[] = { { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 }, .privilege = Privilege::kAdminister, .allow = true }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 }, .privilege = Privilege::kAdminister, .allow = true }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 }, .privilege = Privilege::kAdminister, .allow = false }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 }, .privilege = Privilege::kAdminister, .allow = true }, }; @@ -342,7 +401,7 @@ TEST_F(TestAccessRestriction, CommandRestrictionTest) AccessRestrictionProvider::Entry entry; entry.fabricIndex = 1; entry.endpointNumber = 1; - entry.clusterId = kOnOffCluster; + entry.clusterId = kWiFiNetworkManagementCluster; entry.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kCommandForbidden }); // test wildcarded entity id @@ -360,19 +419,19 @@ TEST_F(TestAccessRestriction, CommandRestrictionTest) constexpr CheckData eventAttributeRestrictionTestData[] = { { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 }, .privilege = Privilege::kAdminister, .allow = true }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 }, .privilege = Privilege::kAdminister, .allow = true }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 }, .privilege = Privilege::kAdminister, .allow = true }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 }, .privilege = Privilege::kAdminister, .allow = false }, }; @@ -383,7 +442,7 @@ TEST_F(TestAccessRestriction, EventRestrictionTest) AccessRestrictionProvider::Entry entry; entry.fabricIndex = 1; entry.endpointNumber = 1; - entry.clusterId = kOnOffCluster; + entry.clusterId = kWiFiNetworkManagementCluster; entry.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kEventForbidden }); // test wildcarded entity id @@ -401,39 +460,39 @@ TEST_F(TestAccessRestriction, EventRestrictionTest) constexpr CheckData combinedRestrictionTestData[] = { { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 }, .privilege = Privilege::kAdminister, .allow = false }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 2 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 2 }, .privilege = Privilege::kAdminister, .allow = false }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 3 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 3 }, .privilege = Privilege::kAdminister, .allow = true }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 4 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 4 }, .privilege = Privilege::kAdminister, .allow = true }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 3 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 3 }, .privilege = Privilege::kAdminister, .allow = true }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 4 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 4 }, .privilege = Privilege::kAdminister, .allow = true }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 5 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 5 }, .privilege = Privilege::kAdminister, .allow = true }, { .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subject = kOperationalNodeId2 }, - .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 }, .privilege = Privilege::kAdminister, .allow = false }, { .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subject = kOperationalNodeId2 }, - .requestPath = { .cluster = kOnOffCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 2 }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 2 }, .privilege = Privilege::kAdminister, .allow = true }, }; @@ -445,7 +504,7 @@ TEST_F(TestAccessRestriction, CombinedRestrictionTest) AccessRestrictionProvider::Entry entry1; entry1.fabricIndex = 1; entry1.endpointNumber = 1; - entry1.clusterId = kOnOffCluster; + entry1.clusterId = kWiFiNetworkManagementCluster; entry1.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kAttributeWriteForbidden }); entry1.restrictions[0].id.SetValue(1); entry1.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kAttributeAccessForbidden }); @@ -459,7 +518,7 @@ TEST_F(TestAccessRestriction, CombinedRestrictionTest) AccessRestrictionProvider::Entry entry2; entry2.fabricIndex = 2; entry2.endpointNumber = 1; - entry2.clusterId = kOnOffCluster; + entry2.clusterId = kWiFiNetworkManagementCluster; entry2.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kCommandForbidden }); entry2.restrictions[0].id.SetValue(1); entry2.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kCommandForbidden }); @@ -470,5 +529,85 @@ TEST_F(TestAccessRestriction, CombinedRestrictionTest) RunChecks(combinedRestrictionTestData, ArraySize(combinedRestrictionTestData)); } +TEST_F(TestAccessRestriction, AttributeStorageSeperationTest) +{ + std::vector<AccessRestrictionProvider::Entry> commissioningEntries; + AccessRestrictionProvider::Entry entry1; + entry1.fabricIndex = 1; + entry1.endpointNumber = 1; + entry1.clusterId = kWiFiNetworkManagementCluster; + entry1.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kAttributeWriteForbidden }); + entry1.restrictions[0].id.SetValue(1); + commissioningEntries.push_back(entry1); + EXPECT_EQ(accessRestrictionProvider.SetCommissioningEntries(commissioningEntries), CHIP_NO_ERROR); + + std::vector<AccessRestrictionProvider::Entry> entries; + AccessRestrictionProvider::Entry entry2; + entry2.fabricIndex = 2; + entry2.endpointNumber = 2; + entry2.clusterId = kThreadBorderRouterMgmtCluster; + entry2.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kCommandForbidden }); + entry2.restrictions[0].id.SetValue(2); + entries.push_back(entry2); + EXPECT_EQ(accessRestrictionProvider.SetEntries(2, entries), CHIP_NO_ERROR); + + auto commissioningEntriesFetched = accessRestrictionProvider.GetCommissioningEntries(); + std::vector<AccessRestrictionProvider::Entry> arlEntriesFetched; + EXPECT_EQ(accessRestrictionProvider.GetEntries(2, arlEntriesFetched), CHIP_NO_ERROR); + + EXPECT_NE(commissioningEntriesFetched[0].fabricIndex, arlEntriesFetched[0].fabricIndex); + EXPECT_NE(commissioningEntriesFetched[0].endpointNumber, arlEntriesFetched[0].endpointNumber); + EXPECT_NE(commissioningEntriesFetched[0].clusterId, arlEntriesFetched[0].clusterId); + EXPECT_NE(commissioningEntriesFetched[0].restrictions[0].restrictionType, arlEntriesFetched[0].restrictions[0].restrictionType); + EXPECT_NE(commissioningEntriesFetched[0].restrictions[0].id, arlEntriesFetched[0].restrictions[0].id); +} + +constexpr CheckData listSelectionDuringCommissioningData[] = { + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1, .isCommissioning = true }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1, .isCommissioning = true }, + .requestPath = { .cluster = kThreadBorderRouterMgmtCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = false }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1, .isCommissioning = false }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = false }, + { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1, .isCommissioning = false }, + .requestPath = { .cluster = kThreadBorderRouterMgmtCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, +}; + +TEST_F(TestAccessRestriction, ListSelectiondDuringCommissioningTest) +{ + // during commissioning, read is allowed on WifiNetworkManagement and disallowed on ThreadBorderRouterMgmt + // after commissioning, read is disallowed on WifiNetworkManagement and allowed on ThreadBorderRouterMgmt + + std::vector<AccessRestrictionProvider::Entry> entries; + AccessRestrictionProvider::Entry entry1; + entry1.fabricIndex = 1; + entry1.endpointNumber = 1; + entry1.clusterId = kThreadBorderRouterMgmtCluster; + entry1.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kAttributeAccessForbidden }); + entry1.restrictions[0].id.SetValue(1); + entries.push_back(entry1); + EXPECT_EQ(accessRestrictionProvider.SetCommissioningEntries(entries), CHIP_NO_ERROR); + + entries.clear(); + AccessRestrictionProvider::Entry entry2; + entry2.fabricIndex = 1; + entry2.endpointNumber = 1; + entry2.clusterId = kWiFiNetworkManagementCluster; + entry2.restrictions.push_back({ .restrictionType = AccessRestrictionProvider::Type::kAttributeAccessForbidden }); + entry2.restrictions[0].id.SetValue(1); + entries.push_back(entry2); + EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_NO_ERROR); + + RunChecks(listSelectionDuringCommissioningData, ArraySize(listSelectionDuringCommissioningData)); +} + } // namespace Access } // namespace chip diff --git a/src/app/clusters/access-control-server/access-control-server.cpp b/src/app/clusters/access-control-server/access-control-server.cpp index e43aad01490b56..9925cda8ede49a 100644 --- a/src/app/clusters/access-control-server/access-control-server.cpp +++ b/src/app/clusters/access-control-server/access-control-server.cpp @@ -613,7 +613,7 @@ bool emberAfAccessControlClusterReviewFabricRestrictionsCallback( if (commandPath.mEndpointId != kRootEndpointId) { ChipLogError(DataManagement, "AccessControlCluster: invalid endpoint in ReviewFabricRestrictions request"); - commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::InvalidValue); + commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::InvalidCommand); return true; } @@ -634,7 +634,7 @@ bool emberAfAccessControlClusterReviewFabricRestrictionsCallback( if (ArlEncoder::Convert(restrictionIter.GetValue().type, restriction.restrictionType) != CHIP_NO_ERROR) { ChipLogError(DataManagement, "AccessControlCluster: invalid restriction type conversion"); - commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::InvalidValue); + commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::InvalidCommand); return true; } From d27dfb6f7d51caff31d1445467b5aef0c7ec6100 Mon Sep 17 00:00:00 2001 From: Thomas Lea <thomas_lea@comcast.com> Date: Mon, 26 Aug 2024 11:57:27 -0500 Subject: [PATCH 18/22] restyled --- .../tests/TestAccessRestrictionProvider.cpp | 281 ++++++++++++------ 1 file changed, 190 insertions(+), 91 deletions(-) diff --git a/src/access/tests/TestAccessRestrictionProvider.cpp b/src/access/tests/TestAccessRestrictionProvider.cpp index b1908a4980ae63..73ccd2cdb47cd7 100644 --- a/src/access/tests/TestAccessRestrictionProvider.cpp +++ b/src/access/tests/TestAccessRestrictionProvider.cpp @@ -296,21 +296,33 @@ TEST_F(TestAccessRestriction, InvalidRestrictionsOnEndpointOneTest) constexpr CheckData accessAttributeRestrictionTestData[] = { { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 }, - .privilege = Privilege::kAdminister, - .allow = false }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kAttributeWriteRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = false }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 }, - .privilege = Privilege::kAdminister, - .allow = false }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kAttributeReadRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = false }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 }, - .privilege = Privilege::kAdminister, - .allow = true }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kCommandInvokeRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 }, - .privilege = Privilege::kAdminister, - .allow = true }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kEventReadRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, }; TEST_F(TestAccessRestriction, AccessAttributeRestrictionTest) @@ -337,21 +349,33 @@ TEST_F(TestAccessRestriction, AccessAttributeRestrictionTest) constexpr CheckData writeAttributeRestrictionTestData[] = { { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 }, - .privilege = Privilege::kAdminister, - .allow = false }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kAttributeWriteRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = false }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 }, - .privilege = Privilege::kAdminister, - .allow = true }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kAttributeReadRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 }, - .privilege = Privilege::kAdminister, - .allow = true }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kCommandInvokeRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 }, - .privilege = Privilege::kAdminister, - .allow = true }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kEventReadRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, }; TEST_F(TestAccessRestriction, WriteAttributeRestrictionTest) @@ -378,21 +402,33 @@ TEST_F(TestAccessRestriction, WriteAttributeRestrictionTest) constexpr CheckData commandAttributeRestrictionTestData[] = { { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 }, - .privilege = Privilege::kAdminister, - .allow = true }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kAttributeWriteRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 }, - .privilege = Privilege::kAdminister, - .allow = true }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kAttributeReadRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 }, - .privilege = Privilege::kAdminister, - .allow = false }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kCommandInvokeRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = false }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 }, - .privilege = Privilege::kAdminister, - .allow = true }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kEventReadRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, }; TEST_F(TestAccessRestriction, CommandRestrictionTest) @@ -419,21 +455,33 @@ TEST_F(TestAccessRestriction, CommandRestrictionTest) constexpr CheckData eventAttributeRestrictionTestData[] = { { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 }, - .privilege = Privilege::kAdminister, - .allow = true }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kAttributeWriteRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 }, - .privilege = Privilege::kAdminister, - .allow = true }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kAttributeReadRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 }, - .privilege = Privilege::kAdminister, - .allow = true }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kCommandInvokeRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 1 }, - .privilege = Privilege::kAdminister, - .allow = false }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kEventReadRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = false }, }; TEST_F(TestAccessRestriction, EventRestrictionTest) @@ -460,41 +508,68 @@ TEST_F(TestAccessRestriction, EventRestrictionTest) constexpr CheckData combinedRestrictionTestData[] = { { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 1 }, - .privilege = Privilege::kAdminister, - .allow = false }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kAttributeWriteRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = false }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 2 }, - .privilege = Privilege::kAdminister, - .allow = false }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kAttributeReadRequest, + .entityId = 2 }, + .privilege = Privilege::kAdminister, + .allow = false }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 3 }, - .privilege = Privilege::kAdminister, - .allow = true }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kAttributeWriteRequest, + .entityId = 3 }, + .privilege = Privilege::kAdminister, + .allow = true }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 4 }, - .privilege = Privilege::kAdminister, - .allow = true }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kAttributeReadRequest, + .entityId = 4 }, + .privilege = Privilege::kAdminister, + .allow = true }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 3 }, - .privilege = Privilege::kAdminister, - .allow = true }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kAttributeReadRequest, + .entityId = 3 }, + .privilege = Privilege::kAdminister, + .allow = true }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 4 }, - .privilege = Privilege::kAdminister, - .allow = true }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kCommandInvokeRequest, + .entityId = 4 }, + .privilege = Privilege::kAdminister, + .allow = true }, { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1 }, - .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kEventReadRequest, .entityId = 5 }, - .privilege = Privilege::kAdminister, - .allow = true }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kEventReadRequest, + .entityId = 5 }, + .privilege = Privilege::kAdminister, + .allow = true }, { .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subject = kOperationalNodeId2 }, - .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kCommandInvokeRequest, .entityId = 1 }, - .privilege = Privilege::kAdminister, - .allow = false }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kCommandInvokeRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = false }, { .subjectDescriptor = { .fabricIndex = 2, .authMode = AuthMode::kCase, .subject = kOperationalNodeId2 }, - .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeWriteRequest, .entityId = 2 }, - .privilege = Privilege::kAdminister, - .allow = true }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kAttributeWriteRequest, + .entityId = 2 }, + .privilege = Privilege::kAdminister, + .allow = true }, }; TEST_F(TestAccessRestriction, CombinedRestrictionTest) @@ -563,22 +638,46 @@ TEST_F(TestAccessRestriction, AttributeStorageSeperationTest) } constexpr CheckData listSelectionDuringCommissioningData[] = { - { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1, .isCommissioning = true }, - .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 }, - .privilege = Privilege::kAdminister, - .allow = true }, - { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1, .isCommissioning = true }, - .requestPath = { .cluster = kThreadBorderRouterMgmtCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 }, - .privilege = Privilege::kAdminister, - .allow = false }, - { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1, .isCommissioning = false }, - .requestPath = { .cluster = kWiFiNetworkManagementCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 }, - .privilege = Privilege::kAdminister, - .allow = false }, - { .subjectDescriptor = { .fabricIndex = 1, .authMode = AuthMode::kCase, .subject = kOperationalNodeId1, .isCommissioning = false }, - .requestPath = { .cluster = kThreadBorderRouterMgmtCluster, .endpoint = 1, .requestType = RequestType::kAttributeReadRequest, .entityId = 1 }, - .privilege = Privilege::kAdminister, - .allow = true }, + { .subjectDescriptor = { .fabricIndex = 1, + .authMode = AuthMode::kCase, + .subject = kOperationalNodeId1, + .isCommissioning = true }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kAttributeReadRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, + { .subjectDescriptor = { .fabricIndex = 1, + .authMode = AuthMode::kCase, + .subject = kOperationalNodeId1, + .isCommissioning = true }, + .requestPath = { .cluster = kThreadBorderRouterMgmtCluster, + .endpoint = 1, + .requestType = RequestType::kAttributeReadRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = false }, + { .subjectDescriptor = { .fabricIndex = 1, + .authMode = AuthMode::kCase, + .subject = kOperationalNodeId1, + .isCommissioning = false }, + .requestPath = { .cluster = kWiFiNetworkManagementCluster, + .endpoint = 1, + .requestType = RequestType::kAttributeReadRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = false }, + { .subjectDescriptor = { .fabricIndex = 1, + .authMode = AuthMode::kCase, + .subject = kOperationalNodeId1, + .isCommissioning = false }, + .requestPath = { .cluster = kThreadBorderRouterMgmtCluster, + .endpoint = 1, + .requestType = RequestType::kAttributeReadRequest, + .entityId = 1 }, + .privilege = Privilege::kAdminister, + .allow = true }, }; TEST_F(TestAccessRestriction, ListSelectiondDuringCommissioningTest) From cbcd55adead25f5487f13fae959478f1cf29ceae Mon Sep 17 00:00:00 2001 From: Thomas Lea <thomas_lea@comcast.com> Date: Mon, 26 Aug 2024 16:06:08 -0500 Subject: [PATCH 19/22] Updated ARL tests per review comments --- .../tests/TestAccessRestrictionProvider.cpp | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/access/tests/TestAccessRestrictionProvider.cpp b/src/access/tests/TestAccessRestrictionProvider.cpp index 73ccd2cdb47cd7..ddf58ae2488f4d 100644 --- a/src/access/tests/TestAccessRestrictionProvider.cpp +++ b/src/access/tests/TestAccessRestrictionProvider.cpp @@ -52,6 +52,17 @@ constexpr NodeId kOperationalNodeId1 = 0x1111111111111111; constexpr NodeId kOperationalNodeId2 = 0x2222222222222222; constexpr NodeId kOperationalNodeId3 = 0x3333333333333333; +bool operator==(const AccessRestrictionProvider::Restriction & lhs, const AccessRestrictionProvider::Restriction & rhs) +{ + return lhs.restrictionType == rhs.restrictionType && lhs.id == rhs.id; +} + +bool operator==(const AccessRestrictionProvider::Entry & lhs, const AccessRestrictionProvider::Entry & rhs) +{ + return lhs.fabricIndex == rhs.fabricIndex && lhs.endpointNumber == rhs.endpointNumber && lhs.clusterId == rhs.clusterId && + lhs.restrictions == rhs.restrictions; +} + struct AclEntryData { FabricIndex fabricIndex = kUndefinedFabricIndex; @@ -275,7 +286,7 @@ TEST_F(TestAccessRestriction, ValidRestrictionsOnEndpointOneTest) entries.push_back(entry); EXPECT_EQ(accessRestrictionProvider.SetEntries(1, entries), CHIP_NO_ERROR); - // also test a cluster on endpoint 0 that isnt in the special allowed list + // also test a cluster on endpoint 1 that isnt in the special allowed list entries.clear(); entry.clusterId = kOnOffCluster; entries.push_back(entry); @@ -629,12 +640,11 @@ TEST_F(TestAccessRestriction, AttributeStorageSeperationTest) auto commissioningEntriesFetched = accessRestrictionProvider.GetCommissioningEntries(); std::vector<AccessRestrictionProvider::Entry> arlEntriesFetched; EXPECT_EQ(accessRestrictionProvider.GetEntries(2, arlEntriesFetched), CHIP_NO_ERROR); - - EXPECT_NE(commissioningEntriesFetched[0].fabricIndex, arlEntriesFetched[0].fabricIndex); - EXPECT_NE(commissioningEntriesFetched[0].endpointNumber, arlEntriesFetched[0].endpointNumber); - EXPECT_NE(commissioningEntriesFetched[0].clusterId, arlEntriesFetched[0].clusterId); - EXPECT_NE(commissioningEntriesFetched[0].restrictions[0].restrictionType, arlEntriesFetched[0].restrictions[0].restrictionType); - EXPECT_NE(commissioningEntriesFetched[0].restrictions[0].id, arlEntriesFetched[0].restrictions[0].id); + EXPECT_EQ(commissioningEntriesFetched[0], entry1); + EXPECT_EQ(commissioningEntriesFetched.size(), static_cast<size_t>(1)); + EXPECT_EQ(arlEntriesFetched[0], entry2); + EXPECT_EQ(arlEntriesFetched.size(), static_cast<size_t>(1)); + EXPECT_FALSE(commissioningEntriesFetched[0] == arlEntriesFetched[0]); } constexpr CheckData listSelectionDuringCommissioningData[] = { From 25bd6508cdb728bfafae60be730ca5f4270dd9f0 Mon Sep 17 00:00:00 2001 From: Thomas Lea <thomas_lea@comcast.com> Date: Tue, 27 Aug 2024 09:23:59 -0500 Subject: [PATCH 20/22] work around nuttx and jsoncpp contention --- examples/platform/linux/BUILD.gn | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/platform/linux/BUILD.gn b/examples/platform/linux/BUILD.gn index b37e73f6debb9c..ca6f2056a78e0e 100644 --- a/examples/platform/linux/BUILD.gn +++ b/examples/platform/linux/BUILD.gn @@ -13,13 +13,16 @@ # limitations under the License. import("//build_overrides/chip.gni") -import("//build_overrides/jsoncpp.gni") import("${chip_root}/examples/common/pigweed/pigweed_rpcs.gni") import("${chip_root}/src/app/common_flags.gni") import("${chip_root}/src/lib/core/core.gni") import("${chip_root}/src/lib/lib.gni") import("${chip_root}/src/tracing/tracing_args.gni") +if (current_os != "nuttx") { + import("//build_overrides/jsoncpp.gni") +} + declare_args() { chip_enable_smoke_co_trigger = false chip_enable_boolean_state_configuration_trigger = false From 141c55574653c59938b015d3a12e07cedac9c43e Mon Sep 17 00:00:00 2001 From: Thomas Lea <thomas_lea@comcast.com> Date: Tue, 27 Aug 2024 10:27:20 -0500 Subject: [PATCH 21/22] Review comments and nuttx build failure fix attempt --- examples/platform/linux/BUILD.gn | 5 ++++- src/access/AccessRestrictionProvider.cpp | 2 +- .../access-control-server/access-control-server.cpp | 7 +++++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/examples/platform/linux/BUILD.gn b/examples/platform/linux/BUILD.gn index ca6f2056a78e0e..e588afa752ac02 100644 --- a/examples/platform/linux/BUILD.gn +++ b/examples/platform/linux/BUILD.gn @@ -98,7 +98,6 @@ source_set("app-main") { "${chip_root}/src/controller:gen_check_chip_controller_headers", "${chip_root}/src/lib", "${chip_root}/src/platform/logging:default", - jsoncpp_root, ] deps = [ ":ota-test-event-trigger", @@ -106,6 +105,10 @@ source_set("app-main") { "${chip_root}/src/app/server", ] + if (current_os != "nuttx") { + public_deps += [ jsoncpp_root ] + } + if (chip_enable_pw_rpc) { defines += [ "PW_RPC_ENABLED" ] } diff --git a/src/access/AccessRestrictionProvider.cpp b/src/access/AccessRestrictionProvider.cpp index 1bf9175ce62fd9..23e8082353abc8 100644 --- a/src/access/AccessRestrictionProvider.cpp +++ b/src/access/AccessRestrictionProvider.cpp @@ -122,7 +122,7 @@ CHIP_ERROR AccessRestrictionProvider::SetEntries(const FabricIndex fabricIndex, bool AccessRestrictionProvider::StandardAccessRestrictionExceptionChecker::AreRestrictionsAllowed(EndpointId endpoint, ClusterId cluster) { - if (endpoint != 0 && + if (endpoint != kRootEndpointId && (cluster == app::Clusters::WiFiNetworkManagement::Id || cluster == app::Clusters::ThreadBorderRouterManagement::Id || cluster == app::Clusters::ThreadNetworkDirectory::Id)) { diff --git a/src/app/clusters/access-control-server/access-control-server.cpp b/src/app/clusters/access-control-server/access-control-server.cpp index 9925cda8ede49a..435ea33740e837 100644 --- a/src/app/clusters/access-control-server/access-control-server.cpp +++ b/src/app/clusters/access-control-server/access-control-server.cpp @@ -645,6 +645,13 @@ bool emberAfAccessControlClusterReviewFabricRestrictionsCallback( entry.restrictions.push_back(restriction); } + if (restrictionIter.GetStatus() != CHIP_NO_ERROR) + { + ChipLogError(DataManagement, "AccessControlCluster: invalid ARL data"); + commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::InvalidValue); + return true; + } + entries.push_back(entry); } From 3d976bf5843a465c7ad3837e380d46c2f5322fa1 Mon Sep 17 00:00:00 2001 From: Thomas Lea <thomas_lea@comcast.com> Date: Tue, 27 Aug 2024 15:58:18 +0000 Subject: [PATCH 22/22] review updates --- examples/platform/linux/BUILD.gn | 2 +- .../clusters/access-control-server/access-control-server.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/platform/linux/BUILD.gn b/examples/platform/linux/BUILD.gn index e588afa752ac02..336534aae65043 100644 --- a/examples/platform/linux/BUILD.gn +++ b/examples/platform/linux/BUILD.gn @@ -106,7 +106,7 @@ source_set("app-main") { ] if (current_os != "nuttx") { - public_deps += [ jsoncpp_root ] + public_deps += [ jsoncpp_root ] } if (chip_enable_pw_rpc) { diff --git a/src/app/clusters/access-control-server/access-control-server.cpp b/src/app/clusters/access-control-server/access-control-server.cpp index 435ea33740e837..295b5535df2f95 100644 --- a/src/app/clusters/access-control-server/access-control-server.cpp +++ b/src/app/clusters/access-control-server/access-control-server.cpp @@ -648,7 +648,7 @@ bool emberAfAccessControlClusterReviewFabricRestrictionsCallback( if (restrictionIter.GetStatus() != CHIP_NO_ERROR) { ChipLogError(DataManagement, "AccessControlCluster: invalid ARL data"); - commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::InvalidValue); + commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::InvalidCommand); return true; } @@ -658,7 +658,7 @@ bool emberAfAccessControlClusterReviewFabricRestrictionsCallback( if (entryIter.GetStatus() != CHIP_NO_ERROR) { ChipLogError(DataManagement, "AccessControlCluster: invalid ARL data"); - commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::InvalidValue); + commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::InvalidCommand); return true; }