blob: 257d866d1509140684ca4a055e5735917cc48027 [file] [log] [blame]
/*
*
* 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::CATValues;
using chip::FabricIndex;
using chip::NodeId;
using namespace chip::Access;
AccessControl defaultAccessControl;
AccessControl * globalAccessControl = &defaultAccessControl;
static_assert(((unsigned(Privilege::kAdminister) & unsigned(Privilege::kManage)) == 0) &&
((unsigned(Privilege::kAdminister) & unsigned(Privilege::kOperate)) == 0) &&
((unsigned(Privilege::kAdminister) & unsigned(Privilege::kView)) == 0) &&
((unsigned(Privilege::kAdminister) & unsigned(Privilege::kProxyView)) == 0) &&
((unsigned(Privilege::kManage) & unsigned(Privilege::kOperate)) == 0) &&
((unsigned(Privilege::kManage) & unsigned(Privilege::kView)) == 0) &&
((unsigned(Privilege::kManage) & unsigned(Privilege::kProxyView)) == 0) &&
((unsigned(Privilege::kOperate) & unsigned(Privilege::kView)) == 0) &&
((unsigned(Privilege::kOperate) & unsigned(Privilege::kProxyView)) == 0) &&
((unsigned(Privilege::kView) & unsigned(Privilege::kProxyView)) == 0),
"Privilege bits must be unique");
bool CheckRequestPrivilegeAgainstEntryPrivilege(Privilege requestPrivilege, Privilege entryPrivilege)
{
switch (entryPrivilege)
{
case Privilege::kView:
return requestPrivilege == Privilege::kView;
case Privilege::kProxyView:
return requestPrivilege == Privilege::kProxyView || requestPrivilege == Privilege::kView;
case Privilege::kOperate:
return requestPrivilege == Privilege::kOperate || requestPrivilege == Privilege::kView;
case Privilege::kManage:
return requestPrivilege == Privilege::kManage || requestPrivilege == Privilege::kOperate ||
requestPrivilege == Privilege::kView;
case Privilege::kAdminister:
return requestPrivilege == Privilege::kAdminister || requestPrivilege == Privilege::kManage ||
requestPrivilege == Privilege::kOperate || requestPrivilege == Privilege::kView ||
requestPrivilege == Privilege::kProxyView;
}
return false;
}
} // namespace
namespace chip {
namespace Access {
AccessControl::Entry::Delegate AccessControl::Entry::mDefaultDelegate;
AccessControl::EntryIterator::Delegate AccessControl::EntryIterator::mDefaultDelegate;
AccessControl::Delegate AccessControl::mDefaultDelegate;
CHIP_ERROR AccessControl::Init()
{
ChipLogDetail(DataManagement, "AccessControl::Init");
return mDelegate.Init();
}
CHIP_ERROR AccessControl::Finish()
{
ChipLogDetail(DataManagement, "AccessControl::Finish");
return mDelegate.Finish();
}
CHIP_ERROR AccessControl::Check(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath,
Privilege requestPrivilege)
{
// During development, allow access if delegate is transitional
ReturnErrorCodeIf(mDelegate.IsTransitional(), CHIP_NO_ERROR);
EntryIterator iterator;
ReturnErrorOnFailure(Entries(iterator, &subjectDescriptor.fabricIndex));
Entry entry;
while (iterator.Next(entry) == CHIP_NO_ERROR)
{
AuthMode authMode = AuthMode::kNone;
ReturnErrorOnFailure(entry.GetAuthMode(authMode));
if (authMode != subjectDescriptor.authMode)
{
continue;
}
Privilege privilege = Privilege::kView;
ReturnErrorOnFailure(entry.GetPrivilege(privilege));
if (!CheckRequestPrivilegeAgainstEntryPrivilege(requestPrivilege, privilege))
{
continue;
}
size_t subjectCount = 0;
ReturnErrorOnFailure(entry.GetSubjectCount(subjectCount));
if (subjectCount > 0)
{
bool subjectMatched = false;
for (size_t i = 0; i < subjectCount; ++i)
{
NodeId subject = kUndefinedNodeId;
ReturnErrorOnFailure(entry.GetSubject(i, subject));
if (IsOperationalNodeId(subject))
{
if (subject == subjectDescriptor.subject)
{
subjectMatched = true;
break;
}
}
else if (IsGroupId(subject))
{
VerifyOrReturnError(authMode == AuthMode::kGroup, CHIP_ERROR_INVALID_ARGUMENT);
if (subject == subjectDescriptor.subject)
{
subjectMatched = true;
break;
}
}
// TODO: Add the implicit admit for PASE after the spec is updated.
else if (IsPAKEKeyId(subject))
{
VerifyOrReturnError(authMode == AuthMode::kPase, CHIP_ERROR_INVALID_ARGUMENT);
if (subject == subjectDescriptor.subject)
{
subjectMatched = true;
break;
}
}
else if (IsCASEAuthTag(subject))
{
VerifyOrReturnError(authMode == AuthMode::kCase, CHIP_ERROR_INVALID_ARGUMENT);
if (subjectDescriptor.cats.CheckSubjectAgainstCATs(subject))
{
subjectMatched = true;
break;
}
}
else
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
}
if (!subjectMatched)
{
continue;
}
}
size_t targetCount = 0;
ReturnErrorOnFailure(entry.GetTargetCount(targetCount));
if (targetCount > 0)
{
bool targetMatched = false;
for (size_t i = 0; i < targetCount; ++i)
{
Entry::Target target;
ReturnErrorOnFailure(entry.GetTarget(i, target));
if ((target.flags & Entry::Target::kCluster) && target.cluster != requestPath.cluster)
{
continue;
}
if ((target.flags & Entry::Target::kEndpoint) && target.endpoint != requestPath.endpoint)
{
continue;
}
// TODO: check against target.deviceType (requires lookup)
targetMatched = true;
break;
}
if (!targetMatched)
{
continue;
}
}
// Entry passed all checks: access is allowed.
return CHIP_NO_ERROR;
}
// No entry was found which passed all checks: access is denied.
return CHIP_ERROR_ACCESS_DENIED;
}
AccessControl & GetAccessControl()
{
return *globalAccessControl;
}
void SetAccessControl(AccessControl & accessControl)
{
globalAccessControl = &accessControl;
}
} // namespace Access
} // namespace chip