Line data Source code
1 : /*
2 : *
3 : * Copyright (c) 2026 Project CHIP Authors
4 : * All rights reserved.
5 : *
6 : * Licensed under the Apache License, Version 2.0 (the "License");
7 : * you may not use this file except in compliance with the License.
8 : * You may obtain a copy of the License at
9 : *
10 : * http://www.apache.org/licenses/LICENSE-2.0
11 : *
12 : * Unless required by applicable law or agreed to in writing, software
13 : * distributed under the License is distributed on an "AS IS" BASIS,
14 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 : * See the License for the specific language governing permissions and
16 : * limitations under the License.
17 : */
18 :
19 : #include "GroupAuxiliaryAccessControlDelegate.h"
20 :
21 : #include <credentials/FabricTable.h>
22 : #include <credentials/GroupDataProvider.h>
23 : #include <lib/core/CHIPCore.h>
24 : #include <lib/core/NodeId.h>
25 : #include <lib/core/Optional.h>
26 : #include <lib/support/CHIPMem.h>
27 : #include <lib/support/CodeUtils.h>
28 : #include <lib/support/TypeTraits.h>
29 :
30 : namespace {
31 :
32 : using namespace chip;
33 :
34 : using chip::Access::AccessControl;
35 : using chip::Access::AuthMode;
36 : using chip::Access::AuxiliaryType;
37 : using chip::Access::Privilege;
38 : using chip::Access::RequestPath;
39 : using chip::Access::SubjectDescriptor;
40 :
41 : using Entry = chip::Access::AccessControl::Entry;
42 : using EntryIterator = chip::Access::AccessControl::EntryIterator;
43 : using Target = Entry::Target;
44 :
45 : class EntryDelegate : public AccessControl::Entry::Delegate
46 : {
47 : public:
48 8 : void Init(Entry & entry, FabricIndex fabricIndex, GroupId groupId, EndpointId endpointId)
49 : {
50 8 : mFabricIndex = fabricIndex;
51 8 : mGroupId = groupId;
52 8 : mEndpointId = endpointId;
53 8 : entry.SetDelegate(*this);
54 8 : }
55 :
56 8 : void Release() override { Platform::Delete(this); }
57 :
58 0 : CHIP_ERROR GetAuthMode(AuthMode & authMode) const override
59 : {
60 0 : authMode = AuthMode::kGroup;
61 0 : return CHIP_NO_ERROR;
62 : }
63 :
64 8 : CHIP_ERROR GetFabricIndex(FabricIndex & fabricIndex) const override
65 : {
66 8 : fabricIndex = mFabricIndex;
67 8 : return CHIP_NO_ERROR;
68 : }
69 :
70 0 : CHIP_ERROR GetPrivilege(Privilege & privilege) const override
71 : {
72 0 : privilege = Privilege::kOperate;
73 0 : return CHIP_NO_ERROR;
74 : }
75 :
76 0 : CHIP_ERROR GetAuxiliaryType(AuxiliaryType & auxiliaryType) const override
77 : {
78 0 : auxiliaryType = AuxiliaryType::kGroupcast;
79 0 : return CHIP_NO_ERROR;
80 : }
81 :
82 8 : CHIP_ERROR GetSubjectCount(size_t & count) const override
83 : {
84 8 : count = 1;
85 8 : return CHIP_NO_ERROR;
86 : }
87 :
88 8 : CHIP_ERROR GetSubject(size_t index, NodeId & subject) const override
89 : {
90 8 : if (index == 0)
91 : {
92 8 : subject = NodeIdFromGroupId(mGroupId);
93 8 : return CHIP_NO_ERROR;
94 : }
95 0 : return CHIP_ERROR_SENTINEL;
96 : }
97 :
98 8 : CHIP_ERROR GetTargetCount(size_t & count) const override
99 : {
100 8 : count = 1;
101 8 : return CHIP_NO_ERROR;
102 : }
103 :
104 8 : CHIP_ERROR GetTarget(size_t index, Target & target) const override
105 : {
106 8 : if (index == 0)
107 : {
108 8 : target.flags = Target::kEndpoint;
109 8 : target.endpoint = mEndpointId;
110 8 : return CHIP_NO_ERROR;
111 : }
112 0 : return CHIP_ERROR_SENTINEL;
113 : }
114 :
115 : private:
116 : FabricIndex mFabricIndex;
117 : GroupId mGroupId;
118 : EndpointId mEndpointId;
119 : };
120 :
121 : class AuxiliaryEntryIteratorDelegate : public EntryIterator::Delegate
122 : {
123 : public:
124 4 : void Init(EntryIterator & iterator, Credentials::GroupDataProvider * groupDataProvider, FabricTable * fabricTable,
125 : FabricIndex fabricIndex)
126 : {
127 4 : mGroupDataProvider = groupDataProvider;
128 4 : mFabricTable = fabricTable;
129 4 : mFabricIndex = fabricIndex;
130 :
131 4 : if (mFabricIndex == kUndefinedFabricIndex)
132 : {
133 2 : mIterateOverFabricIndices = true;
134 : // If the fabric table is defined, it can be used to find and iterate over all
135 : // valid existing fabric indices. Otherwise, iteration can be done starting from
136 : // the minimum fabric index and going up
137 2 : if (mFabricTable)
138 : {
139 1 : mFabricTableIter.SetValue(mFabricTable->begin());
140 1 : if (mFabricTableIter.Value() != mFabricTable->end())
141 : {
142 1 : mFabricIndex = mFabricTableIter.Value()->GetFabricIndex();
143 : }
144 : }
145 : else
146 : {
147 1 : mFabricIndex = kMinValidFabricIndex;
148 : }
149 : }
150 :
151 4 : if (mGroupDataProvider)
152 : {
153 4 : mGroupInfoIterator = mGroupDataProvider->IterateGroupInfo(mFabricIndex);
154 : }
155 4 : iterator.SetDelegate(*this);
156 4 : }
157 :
158 4 : ~AuxiliaryEntryIteratorDelegate() override
159 4 : {
160 4 : if (mGroupInfoIterator)
161 : {
162 0 : mGroupInfoIterator->Release();
163 : }
164 4 : if (mEndpointIterator)
165 : {
166 0 : mEndpointIterator->Release();
167 : }
168 4 : }
169 :
170 4 : void Release() override { Platform::Delete(this); }
171 :
172 12 : CHIP_ERROR Next(Entry & entry) override
173 : {
174 12 : if (mGroupDataProvider == nullptr)
175 : {
176 0 : return CHIP_ERROR_SENTINEL;
177 : }
178 :
179 278 : while (mGroupInfoIterator != nullptr || mEndpointIterator != nullptr || mIterateOverFabricIndices)
180 : {
181 274 : if (mEndpointIterator != nullptr)
182 : {
183 15 : Credentials::GroupDataProvider::GroupEndpoint endpoint;
184 15 : if (mEndpointIterator->Next(endpoint))
185 : {
186 : // Groups cannot be created with endpoint 0, so this state should never be reached
187 : // because of restrictions with creating/joining groups. We skip here in the case
188 : // this does occur, no entry would be needed to validate against for endpoint 0.
189 8 : if (endpoint.endpoint_id == kRootEndpointId)
190 : {
191 0 : continue;
192 : }
193 :
194 8 : auto * delegate = Platform::New<EntryDelegate>();
195 8 : if (delegate == nullptr)
196 : {
197 0 : return CHIP_ERROR_NO_MEMORY;
198 : }
199 8 : delegate->Init(entry, mFabricIndex, mGroupId, endpoint.endpoint_id);
200 8 : return CHIP_NO_ERROR;
201 : }
202 7 : mEndpointIterator->Release();
203 7 : mEndpointIterator = nullptr;
204 : }
205 :
206 266 : if (mGroupInfoIterator != nullptr)
207 : {
208 266 : Credentials::GroupDataProvider::GroupInfo info;
209 266 : if (mGroupInfoIterator->Next(info))
210 : {
211 8 : if (info.flags & to_underlying(Credentials::GroupDataProvider::GroupInfo::Flags::kHasAuxiliaryACL))
212 : {
213 7 : mGroupId = info.group_id;
214 7 : mEndpointIterator = mGroupDataProvider->IterateEndpoints(mFabricIndex, mGroupId);
215 : }
216 8 : continue;
217 : }
218 258 : mGroupInfoIterator->Release();
219 258 : mGroupInfoIterator = nullptr;
220 : }
221 :
222 258 : if (mIterateOverFabricIndices)
223 : {
224 : // This indicates that there are no more fabric indices to iterate over (unless it is
225 : // determined in the conditions below that at least 1 more is remaining, in which
226 : // case this will be set to true again).
227 256 : mIterateOverFabricIndices = false;
228 :
229 256 : if (mFabricTable && mFabricTableIter.HasValue())
230 : {
231 2 : if ((mFabricTableIter.Value() != mFabricTable->end()) && (++mFabricTableIter.Value() != mFabricTable->end()))
232 : {
233 1 : mFabricIndex = mFabricTableIter.Value()->GetFabricIndex();
234 1 : mGroupInfoIterator = mGroupDataProvider->IterateGroupInfo(mFabricIndex);
235 1 : mIterateOverFabricIndices = true;
236 : }
237 : }
238 254 : else if (mFabricIndex < kMaxValidFabricIndex)
239 : {
240 253 : mFabricIndex++;
241 253 : mGroupInfoIterator = mGroupDataProvider->IterateGroupInfo(mFabricIndex);
242 253 : mIterateOverFabricIndices = true;
243 : }
244 : }
245 : }
246 :
247 4 : return CHIP_ERROR_SENTINEL;
248 : }
249 :
250 : private:
251 : Credentials::GroupDataProvider * mGroupDataProvider;
252 : FabricTable * mFabricTable;
253 : FabricIndex mFabricIndex;
254 : chip::Optional<chip::ConstFabricIterator> mFabricTableIter;
255 : Credentials::GroupDataProvider::GroupInfoIterator * mGroupInfoIterator = nullptr;
256 : Credentials::GroupDataProvider::EndpointIterator * mEndpointIterator = nullptr;
257 : GroupId mGroupId = kUndefinedGroupId;
258 :
259 : // When true, this indicates the entries are not fabric scoped. AuxiliaryEntries() through
260 : // the Next() function will iterate over all fabrics to fetch auxiliary ACL entries. This
261 : // will be set to true when a fabric index is not specified (kUndefinedFabricIndex).
262 : bool mIterateOverFabricIndices = false;
263 : };
264 :
265 : } // namespace
266 :
267 : namespace chip {
268 : namespace Access {
269 : namespace Examples {
270 :
271 : /*
272 : * This function (in conjunction with Next() from the AuxiliaryEntryIteratorDelegate) will create an auxiliary
273 : * ACL entry for every <fabric index, group ID, endpoint ID> that belongs based on the information from
274 : * the group data provider. This is the simplest base case of what a set of auxiliary ACL entries will look
275 : * like. The structure of auxiliary ACL entries can be formatted differently, as long as the equivalence class
276 : * maps to this simplest base case.
277 : */
278 4 : CHIP_ERROR GroupAuxiliaryAccessControlDelegate::AuxiliaryEntries(AccessControl::EntryIterator & iterator,
279 : const FabricIndex * fabricIndex) const
280 : {
281 4 : auto * delegate = Platform::New<AuxiliaryEntryIteratorDelegate>();
282 4 : if (delegate)
283 : {
284 4 : delegate->Init(iterator, mGroupDataProvider, mFabricTable, fabricIndex ? *fabricIndex : kUndefinedFabricIndex);
285 4 : return CHIP_NO_ERROR;
286 : }
287 0 : return CHIP_ERROR_NO_MEMORY;
288 : }
289 :
290 4 : CHIP_ERROR GroupAuxiliaryAccessControlDelegate::Check(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath,
291 : Privilege requestPrivilege)
292 : {
293 4 : if (IsGroupId(subjectDescriptor.subject) && (mGroupDataProvider != nullptr))
294 : {
295 4 : GroupId groupId = GroupIdFromNodeId(subjectDescriptor.subject);
296 3 : if ((requestPath.endpoint != kRootEndpointId) &&
297 3 : (mGroupDataProvider->HasEndpoint(subjectDescriptor.fabricIndex, groupId, requestPath.endpoint)) &&
298 9 : (requestPath.requestType == Access::RequestType::kCommandInvokeRequest) && (requestPrivilege == Privilege::kOperate) &&
299 2 : (subjectDescriptor.authMode == Access::AuthMode::kGroup))
300 : {
301 2 : return CHIP_NO_ERROR;
302 : }
303 : }
304 :
305 2 : return CHIP_ERROR_ACCESS_DENIED;
306 : }
307 :
308 : } // namespace Examples
309 : } // namespace Access
310 : } // namespace chip
|