From 2c4de1b503d00d37c96cc8478eb11f0923903cc8 Mon Sep 17 00:00:00 2001 From: Marc Lepage <67919234+mlepage-google@users.noreply.github.com> Date: Thu, 27 Jan 2022 13:25:46 -0500 Subject: [PATCH] Add support for custom required privileges (#14284) * Add required privilege support Until Ember has support for providing custom required privileges, use a simple implementation to provide that functionality. * Address minor review comments * Add required privilege support Until Ember has support for providing custom required privileges, use a simple implementation to provide that functionality. * Address minor review comments * Refactor - move class from src/access to src/app - remove config flags * More refactoring * Fix merge Merge brought back these config flags which I had removed. * More merge fix These files were moved. --- src/app/BUILD.gn | 2 + src/app/CommandHandler.cpp | 4 +- src/app/RequiredPrivilege.cpp | 137 ++++++++++++++++++ src/app/RequiredPrivilege.h | 47 ++++++ .../util/ember-compatibility-functions.cpp | 5 +- src/lib/core/DataModelTypes.h | 6 +- 6 files changed, 196 insertions(+), 5 deletions(-) create mode 100644 src/app/RequiredPrivilege.cpp create mode 100644 src/app/RequiredPrivilege.h diff --git a/src/app/BUILD.gn b/src/app/BUILD.gn index c9a80289364a46..8e109c2cea4eb8 100644 --- a/src/app/BUILD.gn +++ b/src/app/BUILD.gn @@ -130,6 +130,8 @@ static_library("app") { "OperationalDeviceProxyPool.h", "ReadClient.cpp", "ReadHandler.cpp", + "RequiredPrivilege.cpp", + "RequiredPrivilege.h", "StatusResponse.cpp", "StatusResponse.h", "TimedHandler.cpp", diff --git a/src/app/CommandHandler.cpp b/src/app/CommandHandler.cpp index b8e3b1970bd352..eac126be2c9992 100644 --- a/src/app/CommandHandler.cpp +++ b/src/app/CommandHandler.cpp @@ -24,9 +24,11 @@ #include "CommandHandler.h" #include "InteractionModelEngine.h" +#include "RequiredPrivilege.h" #include "messaging/ExchangeContext.h" #include +#include #include #include #include @@ -257,7 +259,7 @@ CHIP_ERROR CommandHandler::ProcessCommandDataIB(CommandDataIB::Parser & aCommand { Access::SubjectDescriptor subjectDescriptor = mpExchangeCtx->GetSessionHandle()->GetSubjectDescriptor(); Access::RequestPath requestPath{ .cluster = concretePath.mClusterId, .endpoint = concretePath.mEndpointId }; - Access::Privilege requestPrivilege = Access::Privilege::kOperate; // TODO: get actual request privilege + Access::Privilege requestPrivilege = RequiredPrivilege::ForInvokeCommand(concretePath); err = Access::GetAccessControl().Check(subjectDescriptor, requestPath, requestPrivilege); err = CHIP_NO_ERROR; // TODO: remove override if (err != CHIP_NO_ERROR) diff --git a/src/app/RequiredPrivilege.cpp b/src/app/RequiredPrivilege.cpp new file mode 100644 index 00000000000000..2da03d01122c8a --- /dev/null +++ b/src/app/RequiredPrivilege.cpp @@ -0,0 +1,137 @@ +/* + * + * Copyright (c) 2022 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 "RequiredPrivilege.h" + +#include + +namespace { + +using namespace chip; +using namespace chip::app; +using namespace chip::Access; + +// Privilege override entries are stored in a table per operation (read attribute, +// write attribute, invoke command, read entry). Cluster cannot be invalid, but +// endpoint and field can be invalid, which means wildcard. For each cluster, +// more specific entries should be before less specific entries, so they take effect. +struct PrivilegeOverride +{ + ClusterId mCluster; + EndpointId mEndpoint; + Privilege mPrivilege; // NOTE: here so packing is tighter + FieldId mField; + + constexpr PrivilegeOverride(ClusterId cluster, EndpointId endpoint, FieldId field, Privilege privilege) : + mCluster(cluster), mEndpoint(endpoint), mPrivilege(privilege), mField(field) + {} + + static_assert(sizeof(FieldId) >= sizeof(AttributeId), "FieldId must be able to hold AttributeId"); + static_assert(sizeof(FieldId) >= sizeof(CommandId), "FieldId must be able to hold CommandId"); + static_assert(sizeof(FieldId) >= sizeof(EventId), "FieldId must be able to hold EventId"); +}; + +// WARNING: for each cluster, put more specific entries before less specific entries +constexpr PrivilegeOverride kPrivilegeOverrideForReadAttribute[] = { + PrivilegeOverride(Clusters::AccessControl::Id, kInvalidEndpointId, kInvalidFieldId, Privilege::kAdminister), +}; + +// WARNING: for each cluster, put more specific entries before less specific entries +constexpr PrivilegeOverride kPrivilegeOverrideForWriteAttribute[] = { + PrivilegeOverride(Clusters::AccessControl::Id, kInvalidEndpointId, kInvalidFieldId, Privilege::kAdminister), +}; + +// WARNING: for each cluster, put more specific entries before less specific entries +constexpr PrivilegeOverride kPrivilegeOverrideForInvokeCommand[] = { + PrivilegeOverride(Clusters::AccessControl::Id, kInvalidEndpointId, kInvalidFieldId, Privilege::kAdminister), +}; + +// WARNING: for each cluster, put more specific entries before less specific entries +constexpr PrivilegeOverride kPrivilegeOverrideForReadEvent[] = { + PrivilegeOverride(Clusters::AccessControl::Id, kInvalidEndpointId, kInvalidFieldId, Privilege::kAdminister), +}; + +enum class Operation +{ + kReadAttribute = 0, + kWriteAttribute = 1, + kInvokeCommand = 2, + kReadEvent = 3 +}; + +constexpr Privilege kDefaultPrivilege[] = { + Privilege::kView, // for read attribute + Privilege::kOperate, // for write attribute + Privilege::kOperate, // for invoke command + Privilege::kView // for read event +}; + +const PrivilegeOverride * const kPrivilegeOverride[] = { kPrivilegeOverrideForReadAttribute, kPrivilegeOverrideForWriteAttribute, + kPrivilegeOverrideForInvokeCommand, kPrivilegeOverrideForReadEvent }; + +constexpr size_t kNumPrivilegeOverride[] = { ArraySize(kPrivilegeOverrideForReadAttribute), + ArraySize(kPrivilegeOverrideForWriteAttribute), + ArraySize(kPrivilegeOverrideForInvokeCommand), + ArraySize(kPrivilegeOverrideForReadEvent) }; + +Privilege GetRequiredPrivilege(Operation operation, ClusterId cluster, EndpointId endpoint, FieldId field) +{ + VerifyOrDie(cluster != kInvalidClusterId && endpoint != kInvalidEndpointId && field != kInvalidFieldId); + + const auto * const pStart = kPrivilegeOverride[static_cast(operation)]; + const auto * const pEnd = pStart + kNumPrivilegeOverride[static_cast(operation)]; + + for (const auto * p = pStart; p < pEnd; ++p) + { + if (p->mCluster == cluster && (p->mEndpoint == endpoint || p->mEndpoint == kInvalidEndpointId) && + (p->mField == field || p->mField == kInvalidFieldId)) + { + return p->mPrivilege; + } + } + + return kDefaultPrivilege[static_cast(operation)]; +} + +} // namespace + +namespace chip { +namespace app { + +Privilege RequiredPrivilege::ForReadAttribute(const ConcreteAttributePath & path) +{ + return GetRequiredPrivilege(Operation::kReadAttribute, path.mClusterId, path.mEndpointId, path.mAttributeId); +} + +Privilege RequiredPrivilege::ForWriteAttribute(const ConcreteAttributePath & path) +{ + return GetRequiredPrivilege(Operation::kWriteAttribute, path.mClusterId, path.mEndpointId, path.mAttributeId); +} + +Privilege RequiredPrivilege::ForInvokeCommand(const ConcreteCommandPath & path) +{ + return GetRequiredPrivilege(Operation::kInvokeCommand, path.mClusterId, path.mEndpointId, path.mCommandId); +} + +Privilege RequiredPrivilege::ForReadEvent(const ConcreteEventPath & path) +{ + return GetRequiredPrivilege(Operation::kReadEvent, path.mClusterId, path.mEndpointId, path.mEventId); +} + +} // namespace app +} // namespace chip diff --git a/src/app/RequiredPrivilege.h b/src/app/RequiredPrivilege.h new file mode 100644 index 00000000000000..ff2fec0e8b395c --- /dev/null +++ b/src/app/RequiredPrivilege.h @@ -0,0 +1,47 @@ +/* + * + * Copyright (c) 2022 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 "ConcreteAttributePath.h" +#include "ConcreteCommandPath.h" +#include "ConcreteEventPath.h" + +#include + +#include +#include + +namespace chip { +namespace app { + +// This functionality is intended to come from Ember, but until Ember supports it, +// this class will provide a workable alternative. +class RequiredPrivilege +{ + using Privilege = Access::Privilege; + +public: + static Privilege ForReadAttribute(const ConcreteAttributePath & path); + static Privilege ForWriteAttribute(const ConcreteAttributePath & path); + static Privilege ForInvokeCommand(const ConcreteCommandPath & path); + static Privilege ForReadEvent(const ConcreteEventPath & path); +}; + +} // namespace app +} // namespace chip diff --git a/src/app/util/ember-compatibility-functions.cpp b/src/app/util/ember-compatibility-functions.cpp index f5cdb2eec6c773..74f7ecb1b9a73c 100644 --- a/src/app/util/ember-compatibility-functions.cpp +++ b/src/app/util/ember-compatibility-functions.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -386,7 +387,7 @@ CHIP_ERROR ReadSingleClusterData(const SubjectDescriptor & aSubjectDescriptor, b { Access::RequestPath requestPath{ .cluster = aPath.mClusterId, .endpoint = aPath.mEndpointId }; - Access::Privilege requestPrivilege = Access::Privilege::kView; // TODO: get actual request privilege + Access::Privilege requestPrivilege = RequiredPrivilege::ForReadAttribute(aPath); CHIP_ERROR err = Access::GetAccessControl().Check(aSubjectDescriptor, requestPath, requestPrivilege); err = CHIP_NO_ERROR; // TODO: remove override if (err != CHIP_NO_ERROR) @@ -818,7 +819,7 @@ CHIP_ERROR WriteSingleClusterData(const SubjectDescriptor & aSubjectDescriptor, { Access::RequestPath requestPath{ .cluster = aPath.mClusterId, .endpoint = aPath.mEndpointId }; - Access::Privilege requestPrivilege = Access::Privilege::kOperate; // TODO: get actual request privilege + Access::Privilege requestPrivilege = RequiredPrivilege::ForWriteAttribute(aPath); CHIP_ERROR err = Access::GetAccessControl().Check(aSubjectDescriptor, requestPath, requestPrivilege); err = CHIP_NO_ERROR; // TODO: remove override if (err != CHIP_NO_ERROR) diff --git a/src/lib/core/DataModelTypes.h b/src/lib/core/DataModelTypes.h index b36ad1ce179975..0630a7416c65eb 100644 --- a/src/lib/core/DataModelTypes.h +++ b/src/lib/core/DataModelTypes.h @@ -48,10 +48,12 @@ constexpr EndpointId kInvalidEndpointId = 0xFFFF; constexpr EndpointId kRootEndpointId = 0; constexpr ListIndex kInvalidListIndex = 0xFFFF; // List index is a uint16 thus 0xFFFF is a invalid list index. -// ClusterId, AttributeId and EventId are MEIs, -// 0xFFFF is not a valid manufacturer code, thus 0xFFFF'FFFF is not a valid MEI. +// These are MEIs, 0xFFFF is not a valid manufacturer code, +// thus 0xFFFF'FFFF is not a valid MEI. static constexpr ClusterId kInvalidClusterId = 0xFFFF'FFFF; static constexpr AttributeId kInvalidAttributeId = 0xFFFF'FFFF; +static constexpr CommandId kInvalidCommandId = 0xFFFF'FFFF; static constexpr EventId kInvalidEventId = 0xFFFF'FFFF; +static constexpr FieldId kInvalidFieldId = 0xFFFF'FFFF; } // namespace chip