Skip to content

Commit

Permalink
Add support for custom required privileges (#14284)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
mlepage-google authored Jan 27, 2022
1 parent 30ed0c5 commit 2c4de1b
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 5 deletions.
2 changes: 2 additions & 0 deletions src/app/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ static_library("app") {
"OperationalDeviceProxyPool.h",
"ReadClient.cpp",
"ReadHandler.cpp",
"RequiredPrivilege.cpp",
"RequiredPrivilege.h",
"StatusResponse.cpp",
"StatusResponse.h",
"TimedHandler.cpp",
Expand Down
4 changes: 3 additions & 1 deletion src/app/CommandHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@

#include "CommandHandler.h"
#include "InteractionModelEngine.h"
#include "RequiredPrivilege.h"
#include "messaging/ExchangeContext.h"

#include <access/AccessControl.h>
#include <app/RequiredPrivilege.h>
#include <app/util/MatterCallbacks.h>
#include <credentials/GroupDataProvider.h>
#include <lib/support/TypeTraits.h>
Expand Down Expand Up @@ -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)
Expand Down
137 changes: 137 additions & 0 deletions src/app/RequiredPrivilege.cpp
Original file line number Diff line number Diff line change
@@ -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 <app-common/zap-generated/cluster-objects.h>

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<int>(operation)];
const auto * const pEnd = pStart + kNumPrivilegeOverride[static_cast<int>(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<int>(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
47 changes: 47 additions & 0 deletions src/app/RequiredPrivilege.h
Original file line number Diff line number Diff line change
@@ -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 <access/Privilege.h>

#include <lib/core/CHIPCore.h>
#include <lib/core/DataModelTypes.h>

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
5 changes: 3 additions & 2 deletions src/app/util/ember-compatibility-functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <app/ClusterInfo.h>
#include <app/ConcreteAttributePath.h>
#include <app/InteractionModelEngine.h>
#include <app/RequiredPrivilege.h>
#include <app/reporting/Engine.h>
#include <app/reporting/reporting.h>
#include <app/util/af.h>
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
6 changes: 4 additions & 2 deletions src/lib/core/DataModelTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 2c4de1b

Please sign in to comment.