blob: e853d7c35ff7e00bc1a29b1c0bfe2102485d7a77 [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 <lib/core/StringBuilderAdapters.h>
#include <pw_unit_test/framework.h>
#include <access/examples/PermissiveAccessControlDelegate.h>
#include <app/ConcreteAttributePath.h>
#include <app/ConcreteEventPath.h>
#include <app/InteractionModelEngine.h>
#include <app/MessageDef/AttributeReportIBs.h>
#include <app/MessageDef/EventDataIB.h>
#include <app/reporting/tests/MockReportScheduler.h>
#include <app/tests/AppTestContext.h>
#include <app/tests/test-interaction-model-api.h>
#include <app/util/basic-types.h>
#include <app/util/mock/Constants.h>
#include <app/util/mock/Functions.h>
#include <lib/core/CHIPCore.h>
#include <lib/core/ErrorStr.h>
#include <lib/core/TLV.h>
#include <lib/core/TLVDebug.h>
#include <lib/core/TLVUtilities.h>
#include <lib/support/CHIPCounter.h>
#include <messaging/ExchangeContext.h>
#include <messaging/Flags.h>
#include <protocols/interaction_model/Constants.h>
namespace {
using namespace chip;
using namespace chip::Access;
using namespace chip::Test;
constexpr CommandId kTestUnsupportedCommandId = 77;
constexpr CommandId kTestCommandId = 4;
const MockNodeConfig & TestMockNodeConfig()
{
using namespace chip::app;
using namespace chip::app::Clusters::Globals::Attributes;
// clang-format off
static const MockNodeConfig config({
MockEndpointConfig(kTestEndpointId, {
MockClusterConfig(kTestClusterId, {
ClusterRevision::Id, FeatureMap::Id, 1, 2
}),
}),
MockEndpointConfig(kTestDeniedEndpointId, {
MockClusterConfig(kTestClusterId, {
ClusterRevision::Id, FeatureMap::Id, 1, 2
}),
}),
});
// clang-format on
return config;
}
class TestAccessControlDelegate : public AccessControl::Delegate
{
public:
CHIP_ERROR Check(const SubjectDescriptor & subjectDescriptor, const chip::Access::RequestPath & requestPath,
Privilege requestPrivilege) override
{
if (requestPath.cluster == chip::Test::kTestDeniedClusterId2 || requestPath.endpoint == chip::Test::kTestDeniedEndpointId)
{
return CHIP_ERROR_ACCESS_DENIED;
}
return CHIP_NO_ERROR;
}
};
AccessControl::Delegate * GetTestAccessControlDelegate()
{
static TestAccessControlDelegate accessControlDelegate;
return &accessControlDelegate;
}
class TestDeviceTypeResolver : public AccessControl::DeviceTypeResolver
{
public:
bool IsDeviceTypeOnEndpoint(DeviceTypeId deviceType, EndpointId endpoint) override { return false; }
} gDeviceTypeResolver;
class MockCommandSenderCallback : public app::CommandSender::Callback
{
public:
void OnError(const app::CommandSender * apCommandSender, CHIP_ERROR aError) override
{
ChipLogError(Controller, "OnError happens with %" CHIP_ERROR_FORMAT, aError.Format());
mError = aError;
onErrorCalledTimes++;
mError = aError;
}
void OnDone(app::CommandSender * apCommandSender) override { onFinalCalledTimes++; }
void ResetCounter()
{
onErrorCalledTimes = 0;
onFinalCalledTimes = 0;
}
int onErrorCalledTimes = 0;
int onFinalCalledTimes = 0;
CHIP_ERROR mError = CHIP_NO_ERROR;
} mockCommandSenderDelegate;
} // namespace
namespace chip {
namespace app {
class TestAclCommand : public Test::AppContext
{
public:
void SetUp() override
{
AppContext::SetUp();
Access::GetAccessControl().Finish();
Access::GetAccessControl().Init(GetTestAccessControlDelegate(), gDeviceTypeResolver);
mOldProvider = InteractionModelEngine::GetInstance()->SetDataModelProvider(&TestImCustomDataModel::Instance());
chip::Test::SetMockNodeConfig(TestMockNodeConfig());
}
void TearDown() override
{
chip::Test::ResetMockNodeConfig();
AppContext::TearDown();
InteractionModelEngine::GetInstance()->SetDataModelProvider(mOldProvider);
}
static void AddInvokeRequestData(CommandSender * apCommandSender, CommandPathParams aCommandPathParams);
private:
chip::app::DataModel::Provider * mOldProvider = nullptr;
};
void TestAclCommand::AddInvokeRequestData(CommandSender * apCommandSender, CommandPathParams aCommandPathParams)
{
EXPECT_EQ(apCommandSender->PrepareCommand(aCommandPathParams), CHIP_NO_ERROR);
chip::TLV::TLVWriter * writer = apCommandSender->GetCommandDataIBTLVWriter();
EXPECT_EQ(writer->PutBoolean(chip::TLV::ContextTag(1), true), CHIP_NO_ERROR);
EXPECT_EQ(apCommandSender->FinishCommand(), CHIP_NO_ERROR);
}
// If an endpoint isn't supported, but access to the request path is denied, we should receive UnsupportedAccess rather than
// UnsupportedEndpoint to prevent information leakage
TEST_F(TestAclCommand, AccessDeniedPrecedenceOverUnsupportedEndpoint)
{
{
mockCommandSenderDelegate.ResetCounter();
app::CommandSender commandSender(&mockCommandSenderDelegate, &GetExchangeManager());
auto commandPathParams = CommandPathParams(kTestUnsupportedEndpointId, 0, kTestDeniedClusterId2, kTestCommandId,
(chip::app::CommandPathFlags::kEndpointIdValid));
AddInvokeRequestData(&commandSender, commandPathParams);
EXPECT_EQ(commandSender.SendCommandRequest(GetSessionBobToAlice()), CHIP_NO_ERROR);
EXPECT_EQ(commandSender.GetInvokeResponseMessageCount(), 0u);
DrainAndServiceIO();
EXPECT_EQ(mockCommandSenderDelegate.mError, CHIP_IM_GLOBAL_STATUS(UnsupportedAccess));
EXPECT_EQ(mockCommandSenderDelegate.onFinalCalledTimes, 1);
EXPECT_EQ(mockCommandSenderDelegate.onErrorCalledTimes, 1);
EXPECT_EQ(commandSender.GetInvokeResponseMessageCount(), 1u);
EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u);
}
{
mockCommandSenderDelegate.ResetCounter();
app::CommandSender commandSender(&mockCommandSenderDelegate, &GetExchangeManager());
auto commandPathParams = CommandPathParams(kTestUnsupportedEndpointId, 0, kTestClusterId, kTestCommandId,
(chip::app::CommandPathFlags::kEndpointIdValid));
AddInvokeRequestData(&commandSender, commandPathParams);
EXPECT_EQ(commandSender.SendCommandRequest(GetSessionBobToAlice()), CHIP_NO_ERROR);
EXPECT_EQ(commandSender.GetInvokeResponseMessageCount(), 0u);
DrainAndServiceIO();
EXPECT_EQ(mockCommandSenderDelegate.mError, CHIP_IM_GLOBAL_STATUS(UnsupportedEndpoint));
EXPECT_EQ(mockCommandSenderDelegate.onFinalCalledTimes, 1);
EXPECT_EQ(mockCommandSenderDelegate.onErrorCalledTimes, 1);
EXPECT_EQ(commandSender.GetInvokeResponseMessageCount(), 1u);
EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u);
}
}
// If a Cluster isn't supported, but access to the request path is denied, we should receive UnsupportedAccess rather than
// UnsupportedCluster to prevent information leakage
TEST_F(TestAclCommand, AccessDeniedPrecedenceOverUnsupportedCluster)
{
{
mockCommandSenderDelegate.ResetCounter();
app::CommandSender commandSender(&mockCommandSenderDelegate, &GetExchangeManager());
auto commandPathParams = CommandPathParams(kTestDeniedEndpointId, 0, kTestUnsupportedClusterId, kTestCommandId,
(chip::app::CommandPathFlags::kEndpointIdValid));
AddInvokeRequestData(&commandSender, commandPathParams);
EXPECT_EQ(commandSender.SendCommandRequest(GetSessionBobToAlice()), CHIP_NO_ERROR);
EXPECT_EQ(commandSender.GetInvokeResponseMessageCount(), 0u);
DrainAndServiceIO();
EXPECT_EQ(mockCommandSenderDelegate.mError, CHIP_IM_GLOBAL_STATUS(UnsupportedAccess));
EXPECT_EQ(mockCommandSenderDelegate.onFinalCalledTimes, 1);
EXPECT_EQ(mockCommandSenderDelegate.onErrorCalledTimes, 1);
EXPECT_EQ(commandSender.GetInvokeResponseMessageCount(), 1u);
EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u);
}
{
mockCommandSenderDelegate.ResetCounter();
app::CommandSender commandSender(&mockCommandSenderDelegate, &GetExchangeManager());
auto commandPathParams = CommandPathParams(kTestEndpointId, 0, kTestUnsupportedClusterId, kTestCommandId,
(chip::app::CommandPathFlags::kEndpointIdValid));
AddInvokeRequestData(&commandSender, commandPathParams);
EXPECT_EQ(commandSender.SendCommandRequest(GetSessionBobToAlice()), CHIP_NO_ERROR);
EXPECT_EQ(commandSender.GetInvokeResponseMessageCount(), 0u);
DrainAndServiceIO();
EXPECT_EQ(mockCommandSenderDelegate.mError, CHIP_IM_GLOBAL_STATUS(UnsupportedCluster));
EXPECT_EQ(mockCommandSenderDelegate.onFinalCalledTimes, 1);
EXPECT_EQ(mockCommandSenderDelegate.onErrorCalledTimes, 1);
EXPECT_EQ(commandSender.GetInvokeResponseMessageCount(), 1u);
EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u);
}
}
// If a Command isn't supported, but access to the request path is denied, we should receive UnsupportedAccess rather than
// UnsupportedCommand to prevent information leakage
TEST_F(TestAclCommand, AccessDeniedPrecedenceOverUnsupportedCommand)
{
{
mockCommandSenderDelegate.ResetCounter();
app::CommandSender commandSender(&mockCommandSenderDelegate, &GetExchangeManager());
auto commandPathParams = CommandPathParams(kTestDeniedEndpointId, 0, kTestClusterId, kTestUnsupportedCommandId,
(chip::app::CommandPathFlags::kEndpointIdValid));
AddInvokeRequestData(&commandSender, commandPathParams);
EXPECT_EQ(commandSender.SendCommandRequest(GetSessionBobToAlice()), CHIP_NO_ERROR);
EXPECT_EQ(commandSender.GetInvokeResponseMessageCount(), 0u);
DrainAndServiceIO();
EXPECT_EQ(mockCommandSenderDelegate.mError, CHIP_IM_GLOBAL_STATUS(UnsupportedAccess));
EXPECT_EQ(mockCommandSenderDelegate.onFinalCalledTimes, 1);
EXPECT_EQ(mockCommandSenderDelegate.onErrorCalledTimes, 1);
EXPECT_EQ(commandSender.GetInvokeResponseMessageCount(), 1u);
EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u);
}
{
mockCommandSenderDelegate.ResetCounter();
app::CommandSender commandSender(&mockCommandSenderDelegate, &GetExchangeManager());
auto commandPathParams = CommandPathParams(kTestEndpointId, 0, kTestClusterId, kTestUnsupportedCommandId,
(chip::app::CommandPathFlags::kEndpointIdValid));
AddInvokeRequestData(&commandSender, commandPathParams);
EXPECT_EQ(commandSender.SendCommandRequest(GetSessionBobToAlice()), CHIP_NO_ERROR);
EXPECT_EQ(commandSender.GetInvokeResponseMessageCount(), 0u);
DrainAndServiceIO();
EXPECT_EQ(mockCommandSenderDelegate.mError, CHIP_IM_GLOBAL_STATUS(UnsupportedCommand));
EXPECT_EQ(mockCommandSenderDelegate.onFinalCalledTimes, 1);
EXPECT_EQ(mockCommandSenderDelegate.onErrorCalledTimes, 1);
EXPECT_EQ(commandSender.GetInvokeResponseMessageCount(), 1u);
EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u);
}
}
} // namespace app
} // namespace chip