diff --git a/src/app/BUILD.gn b/src/app/BUILD.gn index 64d255211680f4..f789fc2d3092e6 100644 --- a/src/app/BUILD.gn +++ b/src/app/BUILD.gn @@ -130,6 +130,7 @@ static_library("app") { public_deps = [ ":app_buildconfig", + "${chip_root}/src/access", "${chip_root}/src/app/util:device_callbacks_manager", "${chip_root}/src/lib/support", "${chip_root}/src/messaging", 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 25dde950bbd703..2edd1716194128 100644 --- a/src/app/clusters/access-control-server/access-control-server.cpp +++ b/src/app/clusters/access-control-server/access-control-server.cpp @@ -15,4 +15,242 @@ * limitations under the License. */ -void MatterAccessControlPluginServerInitCallback() {} +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace chip { +namespace app { +namespace Clusters { +namespace AccessControl { +namespace Structs { +namespace AccessControlEntry { + +struct AccessControlEntryCodec +{ + Access::AccessControl::Entry entry; + + // TODO: use enum classes once they settle down + static int Encode(Access::Privilege privilege) + { + switch (privilege) + { + case Access::Privilege::kView: + return 1; + case Access::Privilege::kProxyView: + return 2; + case Access::Privilege::kOperate: + return 3; + case Access::Privilege::kManage: + return 4; + case Access::Privilege::kAdminister: + return 5; + default: + return 0; + } + } + + // TODO: use enum classes once they settle down + static int Encode(Access::AuthMode authMode) + { + switch (authMode) + { + case Access::AuthMode::kPase: + return 1; + case Access::AuthMode::kCase: + return 2; + case Access::AuthMode::kGroup: + return 3; + default: + return 0; + } + } + + CHIP_ERROR Encode(TLV::TLVWriter & writer, TLV::Tag tag) const + { + TLV::TLVType outer; + ReturnErrorOnFailure(writer.StartContainer(tag, TLV::kTLVType_Structure, outer)); + + FabricIndex fabricIndex; + ReturnErrorOnFailure(entry.GetFabricIndex(fabricIndex)); + ReturnErrorOnFailure(DataModel::Encode(writer, TLV::ContextTag(to_underlying(Fields::kFabricIndex)), fabricIndex)); + + Access::Privilege privilege; + ReturnErrorOnFailure(entry.GetPrivilege(privilege)); + ReturnErrorOnFailure(DataModel::Encode(writer, TLV::ContextTag(to_underlying(Fields::kPrivilege)), Encode(privilege))); + + Access::AuthMode authMode; + ReturnErrorOnFailure(entry.GetAuthMode(authMode)); + ReturnErrorOnFailure(DataModel::Encode(writer, TLV::ContextTag(to_underlying(Fields::kAuthMode)), Encode(authMode))); + + size_t subjectCount = 0; + ReturnErrorOnFailure(entry.GetSubjectCount(subjectCount)); + if (subjectCount > 0) + { + TLV::TLVType inner; + ReturnErrorOnFailure( + writer.StartContainer(TLV::ContextTag(to_underlying(Fields::kSubjects)), TLV::kTLVType_Array, inner)); + for (size_t i = 0; i < subjectCount; ++i) + { + NodeId subject; + ReturnErrorOnFailure(entry.GetSubject(i, subject)); + ReturnErrorOnFailure(DataModel::Encode(writer, TLV::AnonymousTag, subject)); + } + ReturnErrorOnFailure(writer.EndContainer(inner)); + } + + size_t targetCount = 0; + ReturnErrorOnFailure(entry.GetTargetCount(targetCount)); + if (targetCount > 0) + { + TLV::TLVType inner; + ReturnErrorOnFailure( + writer.StartContainer(TLV::ContextTag(to_underlying(Fields::kTargets)), TLV::kTLVType_Array, inner)); + for (size_t i = 0; i < targetCount; ++i) + { + TLV::TLVType innermost; + ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag, TLV::kTLVType_Structure, innermost)); + Access::AccessControl::Entry::Target target; + ReturnErrorOnFailure(entry.GetTarget(i, target)); + if (target.flags & Access::AccessControl::Entry::Target::kCluster) + { + ReturnErrorOnFailure( + DataModel::Encode(writer, TLV::ContextTag(to_underlying(Target::Fields::kCluster)), target.cluster)); + } + if (target.flags & Access::AccessControl::Entry::Target::kEndpoint) + { + ReturnErrorOnFailure( + DataModel::Encode(writer, TLV::ContextTag(to_underlying(Target::Fields::kEndpoint)), target.endpoint)); + } + if (target.flags & Access::AccessControl::Entry::Target::kDeviceType) + { + ReturnErrorOnFailure( + DataModel::Encode(writer, TLV::ContextTag(to_underlying(Target::Fields::kDeviceType)), target.deviceType)); + } + ReturnErrorOnFailure(writer.EndContainer(innermost)); + } + ReturnErrorOnFailure(writer.EndContainer(inner)); + } + + ReturnErrorOnFailure(writer.EndContainer(outer)); + return CHIP_NO_ERROR; + } +}; + +} // namespace AccessControlEntry +} // namespace Structs +} // namespace AccessControl +} // namespace Clusters +} // namespace app +} // namespace chip + +using namespace chip; +using namespace chip::app; +using namespace chip::Access; + +class AccessControlAttribute : public AttributeAccessInterface +{ + using AccessControlEntryCodec = chip::app::Clusters::AccessControl::Structs::AccessControlEntry::AccessControlEntryCodec; + +public: + AccessControlAttribute() : AttributeAccessInterface(Optional(0), Clusters::AccessControl::Id) {} + + CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override; + +private: + CHIP_ERROR ReadAcl(AttributeValueEncoder & aEncoder); + CHIP_ERROR ReadExtension(AttributeValueEncoder & aEncoder); +}; + +CHIP_ERROR AccessControlAttribute::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) +{ + // TODO: probably need to handle list indexes, fabric filtering, etc. + // but don't worry about all that right now + + switch (aPath.mAttributeId) + { + case Clusters::AccessControl::Attributes::Acl::Id: + return ReadAcl(aEncoder); + case Clusters::AccessControl::Attributes::Extension::Id: + return ReadExtension(aEncoder); + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR AccessControlAttribute::ReadAcl(AttributeValueEncoder & aEncoder) +{ + AccessControlEntryCodec codec; + AccessControl::EntryIterator iterator; + + GetAccessControl().Entries(iterator); + + CHIP_ERROR err = aEncoder.EncodeList([&](const TagBoundEncoder & encoder) -> CHIP_ERROR { + while (iterator.Next(codec.entry) == CHIP_NO_ERROR) + { + encoder.Encode(codec); + } + return CHIP_NO_ERROR; + }); + + return err; +} + +CHIP_ERROR AccessControlAttribute::ReadExtension(AttributeValueEncoder & aEncoder) +{ + // TODO: implement extension support + return CHIP_ERROR_NOT_IMPLEMENTED; +} + +AccessControlAttribute gAttribute; + +AccessControl gAccessControl(Examples::GetAccessControlDelegate()); + +void MatterAccessControlPluginServerInitCallback() +{ + registerAttributeAccessOverride(&gAttribute); + + // TODO: move access control setup to a better place but it's fine here for now + gAccessControl.Init(); + SetAccessControl(gAccessControl); + + // TODO: create a few entries during development, these will be removed, + // it's OK because access control isn't persisted and isn't hooked up in IM + AccessControl::Entry entry; + + GetAccessControl().PrepareEntry(entry); + entry.SetFabricIndex(1); + entry.SetPrivilege(Privilege::kAdminister); + entry.SetAuthMode(AuthMode::kCase); + GetAccessControl().CreateEntry(nullptr, entry); + + GetAccessControl().PrepareEntry(entry); + entry.SetFabricIndex(1); + entry.SetPrivilege(Privilege::kManage); + entry.SetAuthMode(AuthMode::kPase); + entry.AddSubject(nullptr, 0x0000000011112222); + entry.AddSubject(nullptr, 0x0000000033334444); + entry.AddTarget(nullptr, { AccessControl::Entry::Target::kCluster, 10, 0, 0 }); + entry.AddTarget(nullptr, { AccessControl::Entry::Target::kEndpoint, 0, 1, 0 }); + GetAccessControl().CreateEntry(nullptr, entry); + + GetAccessControl().PrepareEntry(entry); + entry.SetFabricIndex(1); + entry.SetPrivilege(Privilege::kOperate); + entry.SetAuthMode(AuthMode::kGroup); + entry.AddSubject(nullptr, 0x0000000055556666); + entry.AddSubject(nullptr, 0x0000000077778888); + entry.AddTarget(nullptr, { AccessControl::Entry::Target::kCluster | AccessControl::Entry::Target::kEndpoint, 11, 2, 0 }); + entry.AddTarget(nullptr, { AccessControl::Entry::Target::kDeviceType, 0, 0, 100 }); + GetAccessControl().CreateEntry(nullptr, entry); +}