Skip to content

Commit

Permalink
Add initial prototype of AccessControl module (#10579)
Browse files Browse the repository at this point in the history
* Add initial prototype of AccessControl module

- Not complete, always allows actions
- Not hooked up to interaction model or messaging layer
- Progress toward issues #10236 and #10249
- Fully isolated as a module
- Has unit tests

* Remove file comments from files

* Add 'k' prefix to enum values

* Restyled by whitespace

* Restyled by clang-format

* Restyled by gn

* Remove "empty" .cpp files

* Apply suggestions from code review

* Apply suggestions from code review

* Fix compatibility under different compilers

* Fix unit test compatability on different compilers

* Restyled by clang-format

* Change forward declaration to include

Allows tooling to detect circular dependencies.

* Changes from code review suggestions

- rename namespace access --> Access
- rename DataProvider --> AccessControlDataProvider
- decouple DataProvider lifecycle (Init/Finish)
- rename DataProviderImpl --> ExampleAccessControlDataProvider
- change GetInstance/SetInstance to global functions
- remove Config.h since global instance must be set
- change EntryIterator::Next to return pointer
- add comments to Privilege and AuthMode
- remove SubjectDescriptor.isCommissioning for now
- improve naming of CAT subjects in SubjectDescriptor
- change SubjectId typedef to use NodeId

* Make tests table-driven

Should also fix some build complaints on ESP32

* Restyled by clang-format

* Restyle

* Add more metatesting

Ensure not just that results are correct, but that they were correctly
obtained.

* Restyled by clang-format

* Change enums to enum classes

* Address review comments

* Use basic types in lib/core

Basic types (FabricIndex etc.) were moved in PR #10925 from app to
lib/core, so now they can be used from this module.

* A bit of cleanup

* Refactor SubjectId and SubjectDescriptor

Also, remove CatId and move PasscodeId into lib/core.

* Restyled by clang-format

* Add clarifying examples to documentation.

Co-authored-by: Restyled.io <[email protected]>
  • Loading branch information
2 people authored and pull[bot] committed Jun 16, 2023
1 parent 065152d commit 1540239
Show file tree
Hide file tree
Showing 15 changed files with 1,238 additions and 1 deletion.
3 changes: 2 additions & 1 deletion src/BUILD.gn
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2020 Project CHIP Authors
# Copyright (c) 2020-2021 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.
Expand Down Expand Up @@ -44,6 +44,7 @@ if (chip_build_tests) {

chip_test_group("tests") {
deps = [
"${chip_root}/src/access/tests",
"${chip_root}/src/app/tests",
"${chip_root}/src/crypto/tests",
"${chip_root}/src/inet/tests",
Expand Down
115 changes: 115 additions & 0 deletions src/access/AccessControl.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
*
* Copyright (c) 2021 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 "AccessControl.h"

namespace {

using chip::FabricIndex;
using namespace chip::Access;

// Avoid GetAccessControl returning nullptr before SetAccessControl is called.
class UnimplementedDataProvider : public AccessControlDataProvider
{
CHIP_ERROR Init() override { return CHIP_NO_ERROR; }

void Finish() override {}

EntryIterator * Entries() const override { return nullptr; }

EntryIterator * Entries(FabricIndex fabricIndex) const override { return nullptr; }
};

// Avoid GetAccessControl returning nullptr before SetAccessControl is called.
UnimplementedDataProvider gUnimplementedDataProvider;
AccessControl gUnimplementedAccessControl(gUnimplementedDataProvider);

AccessControl * gAccessControl = &gUnimplementedAccessControl;

} // namespace

namespace chip {
namespace Access {

CHIP_ERROR AccessControl::Init()
{
ChipLogDetail(DataManagement, "access control: initializing");
// ...
return CHIP_NO_ERROR;
}

void AccessControl::Finish()
{
ChipLogDetail(DataManagement, "access control: finishing");
// ...
}

CHIP_ERROR AccessControl::Check(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath, Privilege privilege)
{
CHIP_ERROR err = CHIP_ERROR_ACCESS_DENIED;

EntryIterator * iterator = mDataProvider.Entries(subjectDescriptor.fabricIndex);
// TODO: check error (but can't until we have an implementation)
#if 0
ReturnErrorCodeIf(iterator == nullptr, CHIP_ERROR_INTERNAL);
#else
ReturnErrorCodeIf(iterator == nullptr, CHIP_NO_ERROR);
#endif

// TODO: a few more cases (PASE commissioning, CASE Authenticated Tags, etc.)

while (auto entry = iterator->Next())
{
ChipLogDetail(DataManagement, "Checking entry");

if (!entry->MatchesPrivilege(privilege))
continue;
ChipLogDetail(DataManagement, " --> matched privilege");
if (!entry->MatchesAuthMode(subjectDescriptor.authMode))
continue;
ChipLogDetail(DataManagement, " --> matched authmode");
if (!entry->MatchesSubject(subjectDescriptor.subjects[0]))
continue;
ChipLogDetail(DataManagement, " --> matched subject");
if (!entry->MatchesTarget(requestPath.endpoint, requestPath.cluster))
continue;
ChipLogDetail(DataManagement, " --> matched target");

err = CHIP_NO_ERROR;
break;
}

iterator->Release();
return err;
}

AccessControl * GetAccessControl()
{
return gAccessControl;
}

void SetAccessControl(AccessControl * accessControl)
{
if (accessControl != nullptr)
{
gAccessControl = accessControl;
}
}

} // namespace Access
} // namespace chip
90 changes: 90 additions & 0 deletions src/access/AccessControl.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
*
* Copyright (c) 2021 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 "AccessControlDataProvider.h"
#include "Privilege.h"
#include "RequestPath.h"
#include "SubjectDescriptor.h"

#include <lib/core/CHIPCore.h>

namespace chip {
namespace Access {

class AccessControl
{
public:
/**
* Create an access control module. Must be initialized before use, and
* deinitialized when finished. Must be configured with an
* AccessControlDataProvider, which must outlive this module.
*/
AccessControl(AccessControlDataProvider & dataProvider) : mDataProvider(dataProvider) {}

AccessControl(const AccessControl &) = delete;
AccessControl & operator=(const AccessControl &) = delete;

/**
* Initialize the access control module. Must be called before first use.
*
* @retval various errors, probably fatal.
*/
CHIP_ERROR Init();

/**
* Deinitialize the access control module. Must be called when finished.
*/
void Finish();

/**
* Check whether access (by a subject descriptor, to a request path,
* requiring a privilege) should be allowed or denied.
*
* @retval #CHIP_ERROR_ACCESS_DENIED if denied.
* @retval other errors should also be treated as denied.
* @retval #CHIP_NO_ERROR if allowed.
*/
CHIP_ERROR Check(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath, Privilege privilege);

private:
AccessControlDataProvider & mDataProvider;
};

/**
* Instance getter for the global AccessControl.
*
* Callers have to externally synchronize usage of this function.
*
* @return The global AccessControl instance. Assume never null.
*/
AccessControl * GetAccessControl();

/**
* Instance setter for the global AccessControl.
*
* Callers have to externally synchronize usage of this function.
*
* @param[in] accessControl the instance to start returning with the getter;
* if nullptr, no change occurs.
*/
void SetAccessControl(AccessControl * accessControl);

} // namespace Access
} // namespace chip
134 changes: 134 additions & 0 deletions src/access/AccessControlDataProvider.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
*
* Copyright (c) 2021 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 "AuthMode.h"
#include "Privilege.h"
#include "SubjectDescriptor.h"

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

namespace chip {
namespace Access {

class Entry
{
public:
virtual ~Entry() = default;

/**
* Whether the auth mode matches the entry. Must be called before calling
* MatchesSubject.
*/
virtual bool MatchesAuthMode(AuthMode authMode) const = 0;

/**
* Whether the fabric matches the entry. Entries with fabric index 0 will
* match all fabrics.
*/
virtual bool MatchesFabric(FabricIndex fabricIndex) const = 0;

/**
* Whether the privilege matches the entry, including subsumed privileges.
* E.g. both Privilege::kOperate and Privilege::kView will match an entry
* with Privilege::kOperate, but Privilege::kManage will not match such an
* entry.
*/
virtual bool MatchesPrivilege(Privilege privilege) const = 0;

/**
* Whether the subject matches the entry. Must only be called if auth mode
* matches.
*/
virtual bool MatchesSubject(SubjectId subject) const = 0;

/**
* Whether the target matches the entry. Some entries may match all
* endpoints or all clusters.
*/
virtual bool MatchesTarget(EndpointId endpoint, ClusterId cluster) const = 0;
};

class EntryIterator
{
public:
/**
* Create an entry iterator. Must call release when finished.
*/
EntryIterator() = default;

virtual ~EntryIterator() = default;

/**
* Returns the next entry, or nullptr if there is no next entry.
*/
virtual Entry * Next() = 0;

/**
* Release the iterator. Must be called when finished.
*/
virtual void Release() = 0;
};

class AccessControlDataProvider
{
public:
/**
* Create a data provider. Must be initialized before use, and deinitialized
* when finished.
*/
AccessControlDataProvider() = default;

virtual ~AccessControlDataProvider() = default;

AccessControlDataProvider(const AccessControlDataProvider &) = delete;
AccessControlDataProvider & operator=(const AccessControlDataProvider &) = delete;

/**
* Initialize the data provider.
*
* @retval various errors, probably fatal.
*/
virtual CHIP_ERROR Init() = 0;

/**
* Deinitialize the data provider.
*/
virtual void Finish() = 0;

/**
* Get an iterator over all entries.
*
* @retval iterator, release when finished.
* @retval nullptr if error, probably fatal, generally should not happen.
*/
virtual EntryIterator * Entries() const = 0;

/**
* Get an iterator over all entries for a particular fabric.
*
* @retval iterator, release when finished.
* @retval nullptr if error, probably fatal, generally should not happen.
*/
virtual EntryIterator * Entries(FabricIndex fabricIndex) const = 0;
};

} // namespace Access
} // namespace chip
35 changes: 35 additions & 0 deletions src/access/AuthMode.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
*
* Copyright (c) 2021 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

namespace chip {
namespace Access {

// Using bitfield values so auth mode and privilege set can be stored together.
// Auth mode should have only one value expressed, which should not be None.
enum class AuthMode
{
kNone = 0,
kPase = 1 << 5,
kCase = 1 << 6,
kGroup = 1 << 7
};

} // namespace Access
} // namespace chip
Loading

0 comments on commit 1540239

Please sign in to comment.