Skip to content

Commit

Permalink
Add initial prototype hookup of AccessControl
Browse files Browse the repository at this point in the history
- Hook up to interaction model and messaging layer
- Not complete, always allows actions
- Completes issue project-chip#10236
  • Loading branch information
mlepage-google committed Oct 15, 2021
1 parent 2eed92c commit 014ac99
Show file tree
Hide file tree
Showing 14 changed files with 128 additions and 28 deletions.
1 change: 1 addition & 0 deletions src/app/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ static_library("app") {

public_deps = [
":app_buildconfig",
"${chip_root}/src/access",
"${chip_root}/src/lib/support",
"${chip_root}/src/messaging",
"${chip_root}/src/protocols/secure_channel",
Expand Down
15 changes: 8 additions & 7 deletions src/app/InteractionModelEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -240,19 +240,20 @@ bool ServerClusterCommandExists(const ConcreteCommandPath & aCommandPath);
* This function is implemented by CHIP as a part of cluster data storage & management.
* The apWriter and apDataExists can be nullptr.
*
* @param[in] aPath The concrete path of the data being read.
* @param[in] apWriter The TLVWriter for holding cluster data. Can be a nullptr if the caller does not care
* the exact value of the attribute.
* @param[out] apDataExists Tell whether the cluster data exist on server. Can be a nullptr if the caller does not care
* whether the data exists.
* @param[in] aSubjectDescriptor The subject descriptor.
* @param[in] aPath The concrete path of the data being read.
* @param[in] apWriter The TLVWriter for holding cluster data. Can be a nullptr if the caller does not care
* the exact value of the attribute.
* @param[out] apDataExists Tell whether the cluster data exist on server. Can be a nullptr if the caller does not care
* whether the data exists.
*
* @retval CHIP_NO_ERROR on success
*/
CHIP_ERROR ReadSingleClusterData(const ConcreteAttributePath & aPath, TLV::TLVWriter * apWriter, bool * apDataExists);
CHIP_ERROR ReadSingleClusterData(const access::SubjectDescriptor & aSubjectDescriptor, const ConcreteAttributePath & aPath, TLV::TLVWriter * apWriter, bool * apDataExists);

/**
* TODO: Document.
*/
CHIP_ERROR WriteSingleClusterData(ClusterInfo & aClusterInfo, TLV::TLVReader & aReader, WriteHandler * apWriteHandler);
CHIP_ERROR WriteSingleClusterData(const access::SubjectDescriptor & aSubjectDescriptor, ClusterInfo & aClusterInfo, TLV::TLVReader & aReader, WriteHandler * apWriteHandler);
} // namespace app
} // namespace chip
5 changes: 5 additions & 0 deletions src/app/ReadHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,11 @@ class ReadHandler : public Messaging::ExchangeDelegate
NodeId GetInitiatorNodeId() const { return mInitiatorNodeId; }
FabricIndex GetFabricIndex() const { return mFabricIndex; }

const access::SubjectDescriptor GetSubjectDescriptor() const
{
return mpExchangeCtx ? mpExchangeCtx->GetSubjectDescriptor() : access::SubjectDescriptor();
}

private:
friend class TestReadInteraction;
enum class HandlerState
Expand Down
4 changes: 3 additions & 1 deletion src/app/WriteHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ CHIP_ERROR WriteHandler::SendWriteResponse()
CHIP_ERROR WriteHandler::ProcessAttributeDataList(TLV::TLVReader & aAttributeDataListReader)
{
CHIP_ERROR err = CHIP_NO_ERROR;
const access::SubjectDescriptor subjectDescriptor = mpExchangeCtx->GetSubjectDescriptor();

while (CHIP_NO_ERROR == (err = aAttributeDataListReader.Next()))
{
chip::TLV::TLVReader dataReader;
Expand Down Expand Up @@ -151,7 +153,7 @@ CHIP_ERROR WriteHandler::ProcessAttributeDataList(TLV::TLVReader & aAttributeDat

err = element.GetData(&dataReader);
SuccessOrExit(err);
err = WriteSingleClusterData(clusterInfo, dataReader, this);
err = WriteSingleClusterData(subjectDescriptor, clusterInfo, dataReader, this);
SuccessOrExit(err);
}

Expand Down
14 changes: 8 additions & 6 deletions src/app/reporting/Engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ EventNumber Engine::CountEvents(ReadHandler * apReadHandler, EventNumber * apIni
}

CHIP_ERROR
Engine::RetrieveClusterData(AttributeDataList::Builder & aAttributeDataList, ClusterInfo & aClusterInfo)
Engine::RetrieveClusterData(const access::SubjectDescriptor & aSubjectDescriptor, AttributeDataList::Builder & aAttributeDataList, ClusterInfo & aClusterInfo)
{
CHIP_ERROR err = CHIP_NO_ERROR;
ConcreteAttributePath path(aClusterInfo.mEndpointId, aClusterInfo.mClusterId, aClusterInfo.mFieldId);
Expand All @@ -79,7 +79,7 @@ Engine::RetrieveClusterData(AttributeDataList::Builder & aAttributeDataList, Clu
ChipLogDetail(DataManagement, "<RE:Run> Cluster %" PRIx32 ", Field %" PRIx32 " is dirty", aClusterInfo.mClusterId,
aClusterInfo.mFieldId);

err = ReadSingleClusterData(path, attributeDataElementBuilder.GetWriter(), nullptr /* data exists */);
err = ReadSingleClusterData(aSubjectDescriptor, path, attributeDataElementBuilder.GetWriter(), nullptr /* data exists */);
SuccessOrExit(err);
attributeDataElementBuilder.MoreClusterData(false);
attributeDataElementBuilder.EndOfAttributeDataElement();
Expand All @@ -97,19 +97,21 @@ Engine::RetrieveClusterData(AttributeDataList::Builder & aAttributeDataList, Clu

CHIP_ERROR Engine::BuildSingleReportDataAttributeDataList(ReportData::Builder & aReportDataBuilder, ReadHandler * apReadHandler)
{
CHIP_ERROR err = CHIP_NO_ERROR;
CHIP_ERROR err = CHIP_NO_ERROR;
const access::SubjectDescriptor subjectDescriptor = apReadHandler->GetSubjectDescriptor();
bool attributeClean = true;
TLV::TLVWriter backup;
aReportDataBuilder.Checkpoint(backup);
AttributeDataList::Builder attributeDataList = aReportDataBuilder.CreateAttributeDataListBuilder();
SuccessOrExit(err = aReportDataBuilder.GetError());

// TODO: Need to handle multiple chunk of message
for (auto clusterInfo = apReadHandler->GetAttributeClusterInfolist(); clusterInfo != nullptr; clusterInfo = clusterInfo->mpNext)
{
if (apReadHandler->IsInitialReport())
{
// Retrieve data for this cluster instance and clear its dirty flag.
err = RetrieveClusterData(attributeDataList, *clusterInfo);
err = RetrieveClusterData(subjectDescriptor, attributeDataList, *clusterInfo);
VerifyOrExit(err == CHIP_NO_ERROR,
ChipLogError(DataManagement, "<RE:Run> Error retrieving data from cluster, aborting"));
attributeClean = false;
Expand All @@ -120,11 +122,11 @@ CHIP_ERROR Engine::BuildSingleReportDataAttributeDataList(ReportData::Builder &
{
if (clusterInfo->IsAttributePathSupersetOf(*path))
{
err = RetrieveClusterData(attributeDataList, *path);
err = RetrieveClusterData(subjectDescriptor, attributeDataList, *path);
}
else if (path->IsAttributePathSupersetOf(*clusterInfo))
{
err = RetrieveClusterData(attributeDataList, *clusterInfo);
err = RetrieveClusterData(subjectDescriptor, attributeDataList, *clusterInfo);
}
else
{
Expand Down
2 changes: 1 addition & 1 deletion src/app/reporting/Engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ class Engine

CHIP_ERROR BuildSingleReportDataAttributeDataList(ReportData::Builder & reportDataBuilder, ReadHandler * apReadHandler);
CHIP_ERROR BuildSingleReportDataEventList(ReportData::Builder & reportDataBuilder, ReadHandler * apReadHandler);
CHIP_ERROR RetrieveClusterData(AttributeDataList::Builder & aAttributeDataList, ClusterInfo & aClusterInfo);
CHIP_ERROR RetrieveClusterData(const access::SubjectDescriptor & aSubjectDescriptor, AttributeDataList::Builder & aAttributeDataList, ClusterInfo & aClusterInfo);
EventNumber CountEvents(ReadHandler * apReadHandler, EventNumber * apInitialEvents);

/**
Expand Down
2 changes: 1 addition & 1 deletion src/app/tests/TestReadInteraction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ class MockInteractionModelApp : public chip::app::InteractionModelDelegate

namespace chip {
namespace app {
CHIP_ERROR ReadSingleClusterData(const ConcreteAttributePath & aPath, TLV::TLVWriter * apWriter, bool * apDataExists)
CHIP_ERROR ReadSingleClusterData(const access::SubjectDescriptor & aSubjectDescriptor, const ConcreteAttributePath & aPath, TLV::TLVWriter * apWriter, bool * apDataExists)
{
uint64_t version = 0;
ChipLogDetail(DataManagement, "TEST Cluster %" PRIx32 ", Field %" PRIx32 " is dirty", aPath.mClusterId, aPath.mAttributeId);
Expand Down
2 changes: 1 addition & 1 deletion src/app/tests/TestWriteInteraction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ void TestWriteInteraction::TestWriteHandler(nlTestSuite * apSuite, void * apCont
NL_TEST_ASSERT(apSuite, rm->TestGetCountRetransTable() == 0);
}

CHIP_ERROR WriteSingleClusterData(ClusterInfo & aClusterInfo, TLV::TLVReader & aReader, WriteHandler * aWriteHandler)
CHIP_ERROR WriteSingleClusterData(const access::SubjectDescriptor & aSubjectDescriptor, ClusterInfo & aClusterInfo, TLV::TLVReader & aReader, WriteHandler * aWriteHandler)
{
TLV::TLVWriter writer;
writer.Init(attributeDataTLV);
Expand Down
4 changes: 2 additions & 2 deletions src/app/tests/integration/chip_im_initiator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -636,15 +636,15 @@ void DispatchSingleClusterResponseCommand(const ConcreteCommandPath & aCommandPa
gLastCommandResult = TestCommandResult::kSuccess;
}

CHIP_ERROR ReadSingleClusterData(const ConcreteAttributePath & aPath, TLV::TLVWriter * apWriter, bool * apDataExists)
CHIP_ERROR ReadSingleClusterData(const access::SubjectDescriptor & aSubjectDescriptor, const ConcreteAttributePath & aPath, TLV::TLVWriter * apWriter, bool * apDataExists)
{
// We do not really care about the value, just return a not found status code.
VerifyOrReturnError(apWriter != nullptr, CHIP_NO_ERROR);
return apWriter->Put(chip::TLV::ContextTag(AttributeDataElement::kCsTag_Status),
Protocols::InteractionModel::Status::UnsupportedAttribute);
}

CHIP_ERROR WriteSingleClusterData(ClusterInfo & aClusterInfo, TLV::TLVReader & aReader, WriteHandler *)
CHIP_ERROR WriteSingleClusterData(const access::SubjectDescriptor & aSubjectDescriptor, ClusterInfo & aClusterInfo, TLV::TLVReader & aReader, WriteHandler *)
{
if (aClusterInfo.mClusterId != kTestClusterId || aClusterInfo.mEndpointId != kTestEndpointId)
{
Expand Down
4 changes: 2 additions & 2 deletions src/app/tests/integration/chip_im_responder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ void DispatchSingleClusterResponseCommand(const ConcreteCommandPath & aCommandPa
// Nothing todo.
}

CHIP_ERROR ReadSingleClusterData(const ConcreteAttributePath & aPath, TLV::TLVWriter * apWriter, bool * apDataExists)
CHIP_ERROR ReadSingleClusterData(const access::SubjectDescriptor & aSubjectDescriptor, const ConcreteAttributePath & aPath, TLV::TLVWriter * apWriter, bool * apDataExists)
{
CHIP_ERROR err = CHIP_NO_ERROR;
uint64_t version = 0;
Expand All @@ -125,7 +125,7 @@ CHIP_ERROR ReadSingleClusterData(const ConcreteAttributePath & aPath, TLV::TLVWr
return err;
}

CHIP_ERROR WriteSingleClusterData(ClusterInfo & aClusterInfo, TLV::TLVReader & aReader, WriteHandler * apWriteHandler)
CHIP_ERROR WriteSingleClusterData(const access::SubjectDescriptor & aSubjectDescriptor, ClusterInfo & aClusterInfo, TLV::TLVReader & aReader, WriteHandler * apWriteHandler)
{
CHIP_ERROR err = CHIP_NO_ERROR;
AttributePathParams attributePathParams;
Expand Down
78 changes: 73 additions & 5 deletions src/app/util/ember-compatibility-functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
* when calling ember callbacks.
*/

#include <access/AccessControl.h>
#include <access/Privilege.h>
#include <app/ClusterInfo.h>
#include <app/Command.h>
#include <app/ConcreteAttributePath.h>
Expand Down Expand Up @@ -48,6 +50,9 @@ using namespace chip;
using namespace chip::app;
using namespace chip::app::Compatibility;

using chip::access::AccessControl;
using chip::access::Privilege;

namespace chip {
namespace app {
namespace Compatibility {
Expand Down Expand Up @@ -186,7 +191,7 @@ bool ServerClusterCommandExists(const ConcreteCommandPath & aCommandPath)
return emberAfContainsServer(aCommandPath.mEndpointId, aCommandPath.mClusterId);
}

CHIP_ERROR ReadSingleClusterData(const ConcreteAttributePath & aPath, TLV::TLVWriter * apWriter, bool * apDataExists)
CHIP_ERROR ReadSingleClusterData(const access::SubjectDescriptor & aSubjectDescriptor, const ConcreteAttributePath & aPath, TLV::TLVWriter * apWriter, bool * apDataExists)
{
ChipLogDetail(DataManagement,
"Reading attribute: Cluster=" ChipLogFormatMEI " Endpoint=%" PRIx16 " AttributeId=" ChipLogFormatMEI,
Expand Down Expand Up @@ -232,6 +237,37 @@ CHIP_ERROR ReadSingleClusterData(const ConcreteAttributePath & aPath, TLV::TLVWr
return apWriter->Put(chip::TLV::ContextTag(AttributeDataElement::kCsTag_Status), ToInteractionModelStatus(status));
}

#if 0
// TODO: get required privilege from yet-to-be-written ember api
#else
// TEMP: assume view privilege required
Privilege privilege = Privilege::View;
#endif

{
access::RequestPath requestPath {
.endpoint = aPath.mEndpointId,
.cluster = aPath.mClusterId
};
CHIP_ERROR err = AccessControl::GetInstance()->Check(aSubjectDescriptor, requestPath, privilege);
if (err != CHIP_NO_ERROR)
{
if (err == CHIP_ERROR_ACCESS_DENIED)
{
ChipLogProgress(DataManagement, "ACCESS CONTROL --> DENIED");
// TODO: In some cases (wildcards) we'll want to just discard request path
return apWriter->Put(chip::TLV::ContextTag(AttributeDataElement::kCsTag_Status), Protocols::InteractionModel::Status::UnsupportedAccess);
}
else
{
return err;
}
}
ChipLogProgress(DataManagement, "ACCESS CONTROL --> ALLOWED");
}

// TODO: filter fabric sensitive data if fabric doesn't match

// TODO: ZCL_STRUCT_ATTRIBUTE_TYPE is not included in this switch case currently, should add support for structures.
switch (BaseType(attributeType))
{
Expand Down Expand Up @@ -426,7 +462,8 @@ CHIP_ERROR prepareWriteData(EmberAfAttributeType expectedType, TLV::TLVReader &
}
} // namespace

static Protocols::InteractionModel::Status WriteSingleClusterDataInternal(ClusterInfo & aClusterInfo, TLV::TLVReader & aReader,
static Protocols::InteractionModel::Status WriteSingleClusterDataInternal(const access::SubjectDescriptor & aSubjectDescriptor,
ClusterInfo & aClusterInfo, TLV::TLVReader & aReader,
WriteHandler * apWriteHandler)
{
// Passing nullptr as buf to emberAfReadAttribute means we only need attribute type here, and ember will not do data read &
Expand All @@ -439,11 +476,42 @@ static Protocols::InteractionModel::Status WriteSingleClusterDataInternal(Cluste
return Protocols::InteractionModel::Status::UnsupportedAttribute;
}

#if 0
// TODO: get required privilege from yet-to-be-written ember api
#else
// TEMP: assume operate privilege required
Privilege privilege = Privilege::Operate;
#endif

{
access::RequestPath requestPath {
.endpoint = aClusterInfo.mEndpointId,
.cluster = aClusterInfo.mClusterId
};
CHIP_ERROR err = AccessControl::GetInstance()->Check(aSubjectDescriptor, requestPath, privilege);
if (err != CHIP_NO_ERROR)
{
if (err == CHIP_ERROR_ACCESS_DENIED)
{
ChipLogProgress(DataManagement, "ACCESS CONTROL --> DENIED");
return Protocols::InteractionModel::Status::UnsupportedAccess;
}
else
{
// TODO: better mapping of chip error to IM status
return Protocols::InteractionModel::Status::Failure;
}
}
ChipLogProgress(DataManagement, "ACCESS CONTROL --> ALLOWED");
}

// TODO: don't write fabric scoped data if fabric doesn't match

CHIP_ERROR preparationError = CHIP_NO_ERROR;
uint16_t dataLen = 0;
if ((preparationError = prepareWriteData(attributeMetadata->attributeType, aReader, dataLen)) != CHIP_NO_ERROR)
{
ChipLogDetail(Zcl, "Failed to preapre data to write: %s", ErrorStr(preparationError));
ChipLogDetail(Zcl, "Failed to prepare data to write: %s", ErrorStr(preparationError));
return Protocols::InteractionModel::Status::InvalidValue;
}

Expand All @@ -458,7 +526,7 @@ static Protocols::InteractionModel::Status WriteSingleClusterDataInternal(Cluste
attributeMetadata->attributeType));
}

CHIP_ERROR WriteSingleClusterData(ClusterInfo & aClusterInfo, TLV::TLVReader & aReader, WriteHandler * apWriteHandler)
CHIP_ERROR WriteSingleClusterData(const access::SubjectDescriptor & aSubjectDescriptor, ClusterInfo & aClusterInfo, TLV::TLVReader & aReader, WriteHandler * apWriteHandler)
{
AttributePathParams attributePathParams;
attributePathParams.mNodeId = aClusterInfo.mNodeId;
Expand All @@ -467,7 +535,7 @@ CHIP_ERROR WriteSingleClusterData(ClusterInfo & aClusterInfo, TLV::TLVReader & a
attributePathParams.mFieldId = aClusterInfo.mFieldId;
attributePathParams.mFlags.Set(AttributePathParams::Flags::kFieldIdValid);

auto imCode = WriteSingleClusterDataInternal(aClusterInfo, aReader, apWriteHandler);
auto imCode = WriteSingleClusterDataInternal(aSubjectDescriptor, aClusterInfo, aReader, apWriteHandler);
return apWriteHandler->AddStatus(attributePathParams, imCode);
}
} // namespace app
Expand Down
4 changes: 2 additions & 2 deletions src/controller/tests/data_model/TestCommands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,12 +120,12 @@ bool ServerClusterCommandExists(const ConcreteCommandPath & aCommandPath)
return (aCommandPath.mEndpointId == kTestEndpointId && aCommandPath.mClusterId == TestCluster::Id);
}

CHIP_ERROR ReadSingleClusterData(const ConcreteAttributePath & aPath, TLV::TLVWriter * apWriter, bool * apDataExists)
CHIP_ERROR ReadSingleClusterData(const access::SubjectDescriptor & aSubjectDescriptor, const ConcreteAttributePath & aPath, TLV::TLVWriter * apWriter, bool * apDataExists)
{
return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE;
}

CHIP_ERROR WriteSingleClusterData(ClusterInfo & aClusterInfo, TLV::TLVReader & aReader, WriteHandler * aWriteHandler)
CHIP_ERROR WriteSingleClusterData(const access::SubjectDescriptor & aSubjectDescriptor, ClusterInfo & aClusterInfo, TLV::TLVReader & aReader, WriteHandler * aWriteHandler)
{
return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE;
}
Expand Down
1 change: 1 addition & 0 deletions src/messaging/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ static_library("messaging") {
cflags = [ "-Wconversion" ]

public_deps = [
"${chip_root}/src/access",
"${chip_root}/src/crypto",
"${chip_root}/src/inet",
"${chip_root}/src/lib/core",
Expand Down
20 changes: 20 additions & 0 deletions src/messaging/ExchangeContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

#pragma once

#include <access/SubjectDescriptor.h>
#include <lib/core/ReferenceCounted.h>
#include <lib/support/BitFlags.h>
#include <lib/support/DLLUtil.h>
Expand Down Expand Up @@ -154,6 +155,25 @@ class DLL_EXPORT ExchangeContext : public ReliableMessageContext, public Referen

uint16_t GetExchangeId() const { return mExchangeId; }

const access::SubjectDescriptor GetSubjectDescriptor() const
{
access::SubjectDescriptor subjectDescriptor;

if (mSecureSession.HasValue())
{
// TODO: these are just placeholder values for now, need to get appropriate
// fields (auth mode, node id, etc.) from session handle (or whereever)
// and put in SubjectDescriptor appropriately, and ensure it's correct for
// all cases (e.g. peer node ID is good)
auto& session = mSecureSession.Value();
subjectDescriptor.authMode = access::AuthMode::Case;
subjectDescriptor.subject = session.GetPeerNodeId();
subjectDescriptor.fabricIndex = session.GetFabricIndex();
}

return subjectDescriptor;
}

/*
* In order to use reference counting (see refCount below) we use a hold/free paradigm where users of the exchange
* can hold onto it while it's out of their direct control to make sure it isn't closed before everyone's ready.
Expand Down

0 comments on commit 014ac99

Please sign in to comment.