/src/connectedhomeip/src/access/AccessControl.cpp
Line | Count | Source |
1 | | /* |
2 | | * |
3 | | * Copyright (c) 2021 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 | | // Included for the default AccessControlDelegate logging enables/disables. |
20 | | // See `chip_access_control_policy_logging_verbosity` in `src/app/BUILD.gn` for |
21 | | // the levels available. |
22 | | #include <app/AppConfig.h> |
23 | | |
24 | | #include "AccessControl.h" |
25 | | |
26 | | #include <lib/core/Global.h> |
27 | | #include <lib/support/TypeTraits.h> |
28 | | |
29 | | #include <credentials/GroupDataProvider.h> |
30 | | |
31 | | namespace chip { |
32 | | namespace Access { |
33 | | |
34 | | using chip::CATValues; |
35 | | using chip::FabricIndex; |
36 | | using chip::NodeId; |
37 | | |
38 | | namespace { |
39 | | |
40 | | Global<AccessControl> defaultAccessControl; |
41 | | AccessControl * globalAccessControl = nullptr; // lazily defaulted to defaultAccessControl in GetAccessControl |
42 | | |
43 | | static_assert(((unsigned(Privilege::kAdminister) & unsigned(Privilege::kManage)) == 0) && |
44 | | ((unsigned(Privilege::kAdminister) & unsigned(Privilege::kOperate)) == 0) && |
45 | | ((unsigned(Privilege::kAdminister) & unsigned(Privilege::kView)) == 0) && |
46 | | ((unsigned(Privilege::kAdminister) & unsigned(Privilege::kProxyView)) == 0) && |
47 | | ((unsigned(Privilege::kManage) & unsigned(Privilege::kOperate)) == 0) && |
48 | | ((unsigned(Privilege::kManage) & unsigned(Privilege::kView)) == 0) && |
49 | | ((unsigned(Privilege::kManage) & unsigned(Privilege::kProxyView)) == 0) && |
50 | | ((unsigned(Privilege::kOperate) & unsigned(Privilege::kView)) == 0) && |
51 | | ((unsigned(Privilege::kOperate) & unsigned(Privilege::kProxyView)) == 0) && |
52 | | ((unsigned(Privilege::kView) & unsigned(Privilege::kProxyView)) == 0), |
53 | | "Privilege bits must be unique"); |
54 | | |
55 | | bool CheckRequestPrivilegeAgainstEntryPrivilege(Privilege requestPrivilege, Privilege entryPrivilege) |
56 | 0 | { |
57 | 0 | switch (entryPrivilege) |
58 | 0 | { |
59 | 0 | case Privilege::kView: |
60 | 0 | return requestPrivilege == Privilege::kView; |
61 | 0 | case Privilege::kProxyView: |
62 | 0 | return requestPrivilege == Privilege::kProxyView || requestPrivilege == Privilege::kView; |
63 | 0 | case Privilege::kOperate: |
64 | 0 | return requestPrivilege == Privilege::kOperate || requestPrivilege == Privilege::kView; |
65 | 0 | case Privilege::kManage: |
66 | 0 | return requestPrivilege == Privilege::kManage || requestPrivilege == Privilege::kOperate || |
67 | 0 | requestPrivilege == Privilege::kView; |
68 | 0 | case Privilege::kAdminister: |
69 | 0 | return requestPrivilege == Privilege::kAdminister || requestPrivilege == Privilege::kManage || |
70 | 0 | requestPrivilege == Privilege::kOperate || requestPrivilege == Privilege::kView || |
71 | 0 | requestPrivilege == Privilege::kProxyView; |
72 | 0 | } |
73 | 0 | return false; |
74 | 0 | } |
75 | | |
76 | | constexpr bool IsValidCaseNodeId(NodeId aNodeId) |
77 | 0 | { |
78 | 0 | if (IsOperationalNodeId(aNodeId)) |
79 | 0 | { |
80 | 0 | return true; |
81 | 0 | } |
82 | | |
83 | 0 | if (IsCASEAuthTag(aNodeId) && (GetCASEAuthTagVersion(CASEAuthTagFromNodeId(aNodeId)) != 0)) |
84 | 0 | { |
85 | 0 | return true; |
86 | 0 | } |
87 | | |
88 | 0 | return false; |
89 | 0 | } |
90 | | |
91 | | constexpr bool IsValidGroupNodeId(NodeId aNodeId) |
92 | 0 | { |
93 | 0 | return IsGroupId(aNodeId) && IsValidGroupId(GroupIdFromNodeId(aNodeId)); |
94 | 0 | } |
95 | | |
96 | | #if CHIP_PROGRESS_LOGGING && CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 1 |
97 | | |
98 | | char GetAuthModeStringForLogging(AuthMode authMode) |
99 | 0 | { |
100 | 0 | switch (authMode) |
101 | 0 | { |
102 | 0 | case AuthMode::kNone: |
103 | 0 | return 'n'; |
104 | 0 | case AuthMode::kInternalDeviceAccess: |
105 | 0 | return 'i'; |
106 | 0 | case AuthMode::kPase: |
107 | 0 | return 'p'; |
108 | 0 | case AuthMode::kCase: |
109 | 0 | return 'c'; |
110 | 0 | case AuthMode::kGroup: |
111 | 0 | return 'g'; |
112 | 0 | } |
113 | 0 | return 'u'; |
114 | 0 | } |
115 | | |
116 | | constexpr int kCharsPerCatForLogging = 11; // including final null terminator |
117 | | |
118 | | char * GetCatStringForLogging(char * buf, size_t size, const CATValues & cats) |
119 | 0 | { |
120 | 0 | if (size == 0) |
121 | 0 | { |
122 | 0 | return nullptr; |
123 | 0 | } |
124 | 0 | char * p = buf; |
125 | 0 | char * const end = buf + size; |
126 | 0 | *p = '\0'; |
127 | | // Format string chars needed: |
128 | | // 1 for comma (optional) |
129 | | // 2 for 0x prefix |
130 | | // 8 for 32-bit hex value |
131 | | // 1 for null terminator (at end) |
132 | 0 | static constexpr char fmtWithoutComma[] = "0x%08" PRIX32; |
133 | 0 | static constexpr char fmtWithComma[] = ",0x%08" PRIX32; |
134 | 0 | constexpr int countWithoutComma = 10; |
135 | 0 | constexpr int countWithComma = countWithoutComma + 1; |
136 | 0 | bool withComma = false; |
137 | 0 | for (auto cat : cats.values) |
138 | 0 | { |
139 | 0 | if (cat == chip::kUndefinedCAT) |
140 | 0 | { |
141 | 0 | break; |
142 | 0 | } |
143 | 0 | snprintf(p, static_cast<size_t>(end - p), withComma ? fmtWithComma : fmtWithoutComma, cat); |
144 | 0 | p += withComma ? countWithComma : countWithoutComma; |
145 | 0 | if (p >= end) |
146 | 0 | { |
147 | | // Output was truncated. |
148 | 0 | p = end - ((size < 4) ? size : 4); |
149 | 0 | while (*p) |
150 | 0 | { |
151 | | // Indicate truncation if possible. |
152 | 0 | *p++ = '.'; |
153 | 0 | } |
154 | 0 | break; |
155 | 0 | } |
156 | 0 | withComma = true; |
157 | 0 | } |
158 | 0 | return buf; |
159 | 0 | } |
160 | | |
161 | | char GetPrivilegeStringForLogging(Privilege privilege) |
162 | 0 | { |
163 | 0 | switch (privilege) |
164 | 0 | { |
165 | 0 | case Privilege::kView: |
166 | 0 | return 'v'; |
167 | 0 | case Privilege::kProxyView: |
168 | 0 | return 'p'; |
169 | 0 | case Privilege::kOperate: |
170 | 0 | return 'o'; |
171 | 0 | case Privilege::kManage: |
172 | 0 | return 'm'; |
173 | 0 | case Privilege::kAdminister: |
174 | 0 | return 'a'; |
175 | 0 | } |
176 | 0 | return 'u'; |
177 | 0 | } |
178 | | |
179 | | char GetAuxiliaryTypeStringForLogging(AuxiliaryType auxiliaryType) |
180 | 0 | { |
181 | 0 | switch (auxiliaryType) |
182 | 0 | { |
183 | 0 | case AuxiliaryType::kSystem: |
184 | 0 | return 's'; |
185 | 0 | case AuxiliaryType::kGroupcast: |
186 | 0 | return 'g'; |
187 | 0 | default: |
188 | 0 | return 'u'; |
189 | 0 | } |
190 | 0 | } |
191 | | |
192 | | char GetRequestTypeStringForLogging(RequestType requestType) |
193 | 0 | { |
194 | 0 | switch (requestType) |
195 | 0 | { |
196 | 0 | case RequestType::kAttributeReadRequest: |
197 | 0 | return 'r'; |
198 | 0 | case RequestType::kAttributeWriteRequest: |
199 | 0 | return 'w'; |
200 | 0 | case RequestType::kCommandInvokeRequest: |
201 | 0 | return 'i'; |
202 | 0 | case RequestType::kEventReadRequest: |
203 | 0 | return 'e'; |
204 | 0 | default: |
205 | 0 | return '?'; |
206 | 0 | } |
207 | 0 | } |
208 | | |
209 | | #endif // CHIP_PROGRESS_LOGGING && CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 1 |
210 | | |
211 | | } // namespace |
212 | | |
213 | | Global<AccessControl::Entry::Delegate> AccessControl::Entry::mDefaultDelegate; |
214 | | Global<AccessControl::EntryIterator::Delegate> AccessControl::EntryIterator::mDefaultDelegate; |
215 | | |
216 | | CHIP_ERROR AccessControl::Init(AccessControl::Delegate * delegate, DeviceTypeResolver & deviceTypeResolver) |
217 | 0 | { |
218 | 0 | VerifyOrReturnError(!IsInitialized(), CHIP_ERROR_INCORRECT_STATE); |
219 | | |
220 | 0 | ChipLogProgress(DataManagement, "AccessControl: initializing"); |
221 | |
|
222 | 0 | VerifyOrReturnError(delegate != nullptr, CHIP_ERROR_INVALID_ARGUMENT); |
223 | 0 | CHIP_ERROR retval = delegate->Init(); |
224 | 0 | if (retval == CHIP_NO_ERROR) |
225 | 0 | { |
226 | 0 | mDelegate = delegate; |
227 | 0 | mDeviceTypeResolver = &deviceTypeResolver; |
228 | 0 | } |
229 | |
|
230 | 0 | return retval; |
231 | 0 | } |
232 | | |
233 | | void AccessControl::Finish() |
234 | 0 | { |
235 | 0 | VerifyOrReturn(IsInitialized()); |
236 | 0 | ChipLogProgress(DataManagement, "AccessControl: finishing"); |
237 | 0 | mDelegate->Finish(); |
238 | 0 | mDelegate = nullptr; |
239 | |
|
240 | 0 | if (IsGroupAuxiliaryDelegateRegistered()) |
241 | 0 | { |
242 | 0 | mGroupAuxDelegate->Finish(); |
243 | 0 | UnregisterGroupAuxiliaryDelegate(); |
244 | 0 | } |
245 | 0 | } |
246 | | |
247 | | CHIP_ERROR AccessControl::CreateEntry(const SubjectDescriptor * subjectDescriptor, FabricIndex fabric, size_t * index, |
248 | | const Entry & entry) |
249 | 0 | { |
250 | 0 | VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE); |
251 | | |
252 | 0 | size_t count = 0; |
253 | 0 | size_t maxCount = 0; |
254 | 0 | ReturnErrorOnFailure(mDelegate->GetEntryCount(fabric, count)); |
255 | 0 | ReturnErrorOnFailure(mDelegate->GetMaxEntriesPerFabric(maxCount)); |
256 | | |
257 | 0 | VerifyOrReturnError((count + 1) <= maxCount, CHIP_ERROR_BUFFER_TOO_SMALL); |
258 | | |
259 | 0 | VerifyOrReturnError(entry.IsValid(), CHIP_ERROR_INVALID_ARGUMENT); |
260 | | |
261 | 0 | size_t i = 0; |
262 | 0 | ReturnErrorOnFailure(mDelegate->CreateEntry(&i, entry, &fabric)); |
263 | | |
264 | 0 | if (index) |
265 | 0 | { |
266 | 0 | *index = i; |
267 | 0 | } |
268 | |
|
269 | 0 | NotifyEntryChanged(subjectDescriptor, fabric, i, &entry, EntryListener::ChangeType::kAdded); |
270 | 0 | return CHIP_NO_ERROR; |
271 | 0 | } |
272 | | |
273 | | CHIP_ERROR AccessControl::UpdateEntry(const SubjectDescriptor * subjectDescriptor, FabricIndex fabric, size_t index, |
274 | | const Entry & entry) |
275 | 0 | { |
276 | 0 | VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE); |
277 | 0 | VerifyOrReturnError(entry.IsValid(), CHIP_ERROR_INVALID_ARGUMENT); |
278 | 0 | ReturnErrorOnFailure(mDelegate->UpdateEntry(index, entry, &fabric)); |
279 | 0 | NotifyEntryChanged(subjectDescriptor, fabric, index, &entry, EntryListener::ChangeType::kUpdated); |
280 | 0 | return CHIP_NO_ERROR; |
281 | 0 | } |
282 | | |
283 | | CHIP_ERROR AccessControl::DeleteEntry(const SubjectDescriptor * subjectDescriptor, FabricIndex fabric, size_t index) |
284 | 0 | { |
285 | 0 | VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE); |
286 | 0 | Entry entry; |
287 | 0 | Entry * p = nullptr; |
288 | 0 | if (mEntryListener != nullptr && ReadEntry(fabric, index, entry) == CHIP_NO_ERROR) |
289 | 0 | { |
290 | 0 | p = &entry; |
291 | 0 | } |
292 | 0 | ReturnErrorOnFailure(mDelegate->DeleteEntry(index, &fabric)); |
293 | 0 | if (p && p->HasDefaultDelegate()) |
294 | 0 | { |
295 | | // The entry was read prior to deletion so its latest value could be provided |
296 | | // to the listener after deletion. If it's been reset to its default delegate, |
297 | | // that best effort attempt to retain the latest value failed. This is |
298 | | // regrettable but OK. |
299 | 0 | p = nullptr; |
300 | 0 | } |
301 | 0 | NotifyEntryChanged(subjectDescriptor, fabric, index, p, EntryListener::ChangeType::kRemoved); |
302 | 0 | return CHIP_NO_ERROR; |
303 | 0 | } |
304 | | |
305 | | void AccessControl::AddEntryListener(EntryListener & listener) |
306 | 0 | { |
307 | 0 | if (mEntryListener == nullptr) |
308 | 0 | { |
309 | 0 | mEntryListener = &listener; |
310 | 0 | listener.mNext = nullptr; |
311 | 0 | return; |
312 | 0 | } |
313 | | |
314 | 0 | for (EntryListener * l = mEntryListener; /**/; l = l->mNext) |
315 | 0 | { |
316 | 0 | if (l == &listener) |
317 | 0 | { |
318 | 0 | return; |
319 | 0 | } |
320 | | |
321 | 0 | if (l->mNext == nullptr) |
322 | 0 | { |
323 | 0 | l->mNext = &listener; |
324 | 0 | listener.mNext = nullptr; |
325 | 0 | return; |
326 | 0 | } |
327 | 0 | } |
328 | 0 | } |
329 | | |
330 | | void AccessControl::RemoveEntryListener(EntryListener & listener) |
331 | 0 | { |
332 | 0 | if (mEntryListener == &listener) |
333 | 0 | { |
334 | 0 | mEntryListener = listener.mNext; |
335 | 0 | listener.mNext = nullptr; |
336 | 0 | return; |
337 | 0 | } |
338 | | |
339 | 0 | for (EntryListener * l = mEntryListener; l != nullptr; l = l->mNext) |
340 | 0 | { |
341 | 0 | if (l->mNext == &listener) |
342 | 0 | { |
343 | 0 | l->mNext = listener.mNext; |
344 | 0 | listener.mNext = nullptr; |
345 | 0 | return; |
346 | 0 | } |
347 | 0 | } |
348 | 0 | } |
349 | | |
350 | | bool AccessControl::IsAccessRestrictionListSupported() const |
351 | 0 | { |
352 | | #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS |
353 | | return mAccessRestrictionProvider != nullptr; |
354 | | #else |
355 | 0 | return false; |
356 | 0 | #endif |
357 | 0 | } |
358 | | |
359 | | CHIP_ERROR AccessControl::Check(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath, |
360 | | Privilege requestPrivilege) |
361 | 0 | { |
362 | 0 | VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE); |
363 | 0 | VerifyOrReturnError(IsValidPrivilege(requestPrivilege), CHIP_ERROR_INVALID_ARGUMENT); |
364 | | |
365 | 0 | CHIP_ERROR result = CheckACL(subjectDescriptor, requestPath, requestPrivilege); |
366 | |
|
367 | | #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS |
368 | | if (result == CHIP_NO_ERROR) |
369 | | { |
370 | | result = CheckARL(subjectDescriptor, requestPath, requestPrivilege); |
371 | | } |
372 | | #endif |
373 | |
|
374 | 0 | if ((CHIP_NO_ERROR != result) && (Access::AuthMode::kGroup == subjectDescriptor.authMode) && |
375 | 0 | (Access::RequestType::kCommandInvokeRequest == requestPath.requestType) && |
376 | 0 | (Access::Privilege::kOperate == requestPrivilege) && IsGroupId(subjectDescriptor.subject)) |
377 | 0 | { |
378 | 0 | Credentials::GroupDataProvider * groups = Credentials::GetGroupDataProvider(); |
379 | 0 | VerifyOrReturnError(nullptr != groups, result); |
380 | 0 | Credentials::GroupDataProvider::GroupInfo info; |
381 | 0 | GroupId gid = GroupIdFromNodeId(subjectDescriptor.subject); |
382 | 0 | ReturnErrorOnFailure(groups->GetGroupInfo(subjectDescriptor.fabricIndex, gid, info)); |
383 | 0 | if (info.HasAuxiliaryACL() && IsGroupAuxiliaryDelegateRegistered()) |
384 | 0 | { |
385 | 0 | return mGroupAuxDelegate->Check(subjectDescriptor, requestPath, requestPrivilege); |
386 | 0 | } |
387 | 0 | } |
388 | | |
389 | 0 | return result; |
390 | 0 | } |
391 | | |
392 | | CHIP_ERROR AccessControl::CheckACL(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath, |
393 | | Privilege requestPrivilege) |
394 | 0 | { |
395 | 0 | #if CHIP_PROGRESS_LOGGING && CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 1 |
396 | 0 | { |
397 | 0 | constexpr size_t kMaxCatsToLog = 6; |
398 | 0 | char catLogBuf[kMaxCatsToLog * kCharsPerCatForLogging]; |
399 | 0 | ChipLogProgress(DataManagement, |
400 | 0 | "AccessControl: checking f=%u a=%c s=0x" ChipLogFormatX64 " t=%s c=" ChipLogFormatMEI " e=%u p=%c r=%c", |
401 | 0 | subjectDescriptor.fabricIndex, GetAuthModeStringForLogging(subjectDescriptor.authMode), |
402 | 0 | ChipLogValueX64(subjectDescriptor.subject), |
403 | 0 | GetCatStringForLogging(catLogBuf, sizeof(catLogBuf), subjectDescriptor.cats), |
404 | 0 | ChipLogValueMEI(requestPath.cluster), requestPath.endpoint, GetPrivilegeStringForLogging(requestPrivilege), |
405 | 0 | GetRequestTypeStringForLogging(requestPath.requestType)); |
406 | 0 | } |
407 | 0 | #endif // CHIP_PROGRESS_LOGGING && CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 1 |
408 | |
|
409 | 0 | { |
410 | 0 | CHIP_ERROR result = mDelegate->Check(subjectDescriptor, requestPath, requestPrivilege); |
411 | 0 | if (result != CHIP_ERROR_NOT_IMPLEMENTED) |
412 | 0 | { |
413 | 0 | #if CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 0 |
414 | 0 | ChipLogProgress(DataManagement, "AccessControl: %s (delegate)", |
415 | 0 | (result == CHIP_NO_ERROR) ? "allowed" |
416 | 0 | : (result == CHIP_ERROR_ACCESS_DENIED) ? "denied" |
417 | 0 | : "error"); |
418 | | #else |
419 | | if (result != CHIP_NO_ERROR) |
420 | | { |
421 | | ChipLogProgress(DataManagement, "AccessControl: %s (delegate)", |
422 | | (result == CHIP_ERROR_ACCESS_DENIED) ? "denied" : "error"); |
423 | | } |
424 | | #endif // CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 0 |
425 | |
|
426 | 0 | return result; |
427 | 0 | } |
428 | 0 | } |
429 | | |
430 | | // Operational PASE not supported for v1.0, so PASE implies commissioning, which has highest privilege. |
431 | | // Currently, subject descriptor is only PASE if this node is the responder (aka commissionee); |
432 | | // if this node is the initiator (aka commissioner) then the subject descriptor remains blank. |
433 | 0 | if (subjectDescriptor.authMode == AuthMode::kPase) |
434 | 0 | { |
435 | 0 | #if CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 1 |
436 | 0 | ChipLogProgress(DataManagement, "AccessControl: implicit admin (PASE)"); |
437 | 0 | #endif // CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 1 |
438 | 0 | return CHIP_NO_ERROR; |
439 | 0 | } |
440 | | |
441 | 0 | EntryIterator iterator; |
442 | 0 | ReturnErrorOnFailure(Entries(iterator, &subjectDescriptor.fabricIndex)); |
443 | | |
444 | 0 | Entry entry; |
445 | 0 | while (iterator.Next(entry) == CHIP_NO_ERROR) |
446 | 0 | { |
447 | 0 | AuthMode authMode = AuthMode::kNone; |
448 | 0 | ReturnErrorOnFailure(entry.GetAuthMode(authMode)); |
449 | | // Operational PASE not supported for v1.0. |
450 | 0 | VerifyOrReturnError(authMode == AuthMode::kCase || authMode == AuthMode::kGroup, CHIP_ERROR_INCORRECT_STATE); |
451 | 0 | if (authMode != subjectDescriptor.authMode) |
452 | 0 | { |
453 | 0 | continue; |
454 | 0 | } |
455 | | |
456 | 0 | Privilege privilege = Privilege::kView; |
457 | 0 | ReturnErrorOnFailure(entry.GetPrivilege(privilege)); |
458 | 0 | if (!CheckRequestPrivilegeAgainstEntryPrivilege(requestPrivilege, privilege)) |
459 | 0 | { |
460 | 0 | continue; |
461 | 0 | } |
462 | | |
463 | 0 | size_t subjectCount = 0; |
464 | 0 | ReturnErrorOnFailure(entry.GetSubjectCount(subjectCount)); |
465 | 0 | if (subjectCount > 0) |
466 | 0 | { |
467 | 0 | bool subjectMatched = false; |
468 | 0 | for (size_t i = 0; i < subjectCount; ++i) |
469 | 0 | { |
470 | 0 | NodeId subject = kUndefinedNodeId; |
471 | 0 | ReturnErrorOnFailure(entry.GetSubject(i, subject)); |
472 | 0 | if (IsOperationalNodeId(subject)) |
473 | 0 | { |
474 | 0 | VerifyOrReturnError(authMode == AuthMode::kCase, CHIP_ERROR_INCORRECT_STATE); |
475 | 0 | if (subject == subjectDescriptor.subject) |
476 | 0 | { |
477 | 0 | subjectMatched = true; |
478 | 0 | break; |
479 | 0 | } |
480 | 0 | } |
481 | 0 | else if (IsCASEAuthTag(subject)) |
482 | 0 | { |
483 | 0 | VerifyOrReturnError(authMode == AuthMode::kCase, CHIP_ERROR_INCORRECT_STATE); |
484 | 0 | if (subjectDescriptor.cats.CheckSubjectAgainstCATs(subject)) |
485 | 0 | { |
486 | 0 | subjectMatched = true; |
487 | 0 | break; |
488 | 0 | } |
489 | 0 | } |
490 | 0 | else if (IsGroupId(subject)) |
491 | 0 | { |
492 | 0 | VerifyOrReturnError(authMode == AuthMode::kGroup, CHIP_ERROR_INCORRECT_STATE); |
493 | 0 | if (subject == subjectDescriptor.subject) |
494 | 0 | { |
495 | 0 | subjectMatched = true; |
496 | 0 | break; |
497 | 0 | } |
498 | 0 | } |
499 | 0 | else |
500 | 0 | { |
501 | | // Operational PASE not supported for v1.0. |
502 | 0 | return CHIP_ERROR_INCORRECT_STATE; |
503 | 0 | } |
504 | 0 | } |
505 | 0 | if (!subjectMatched) |
506 | 0 | { |
507 | 0 | continue; |
508 | 0 | } |
509 | 0 | } |
510 | | |
511 | 0 | size_t targetCount = 0; |
512 | 0 | ReturnErrorOnFailure(entry.GetTargetCount(targetCount)); |
513 | 0 | if (targetCount > 0) |
514 | 0 | { |
515 | 0 | bool targetMatched = false; |
516 | 0 | for (size_t i = 0; i < targetCount; ++i) |
517 | 0 | { |
518 | 0 | Entry::Target target; |
519 | 0 | ReturnErrorOnFailure(entry.GetTarget(i, target)); |
520 | 0 | if ((target.flags & Entry::Target::kCluster) && target.cluster != requestPath.cluster) |
521 | 0 | { |
522 | 0 | continue; |
523 | 0 | } |
524 | 0 | if ((target.flags & Entry::Target::kEndpoint) && target.endpoint != requestPath.endpoint) |
525 | 0 | { |
526 | 0 | continue; |
527 | 0 | } |
528 | 0 | if (target.flags & Entry::Target::kDeviceType && |
529 | 0 | !mDeviceTypeResolver->IsDeviceTypeOnEndpoint(target.deviceType, requestPath.endpoint)) |
530 | 0 | { |
531 | 0 | continue; |
532 | 0 | } |
533 | 0 | targetMatched = true; |
534 | 0 | break; |
535 | 0 | } |
536 | 0 | if (!targetMatched) |
537 | 0 | { |
538 | 0 | continue; |
539 | 0 | } |
540 | 0 | } |
541 | | // Entry passed all checks: access is allowed. |
542 | | |
543 | 0 | #if CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 0 |
544 | 0 | ChipLogProgress(DataManagement, "AccessControl: allowed"); |
545 | 0 | #endif // CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 0 |
546 | |
|
547 | 0 | return CHIP_NO_ERROR; |
548 | 0 | } |
549 | | |
550 | | // No entry was found which passed all checks: access is denied. |
551 | 0 | ChipLogProgress(DataManagement, "AccessControl: denied"); |
552 | 0 | return CHIP_ERROR_ACCESS_DENIED; |
553 | 0 | } |
554 | | |
555 | | #if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS |
556 | | CHIP_ERROR AccessControl::CheckARL(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath, |
557 | | Privilege requestPrivilege) |
558 | | { |
559 | | CHIP_ERROR result = CHIP_NO_ERROR; |
560 | | |
561 | | VerifyOrReturnError(requestPath.requestType != RequestType::kRequestTypeUnknown, CHIP_ERROR_INVALID_ARGUMENT); |
562 | | |
563 | | if (!IsAccessRestrictionListSupported()) |
564 | | { |
565 | | // Access Restriction support is compiled in, but not configured/enabled. Nothing to restrict. |
566 | | return CHIP_NO_ERROR; |
567 | | } |
568 | | |
569 | | if (subjectDescriptor.isCommissioning) |
570 | | { |
571 | | result = mAccessRestrictionProvider->CheckForCommissioning(subjectDescriptor, requestPath); |
572 | | } |
573 | | else |
574 | | { |
575 | | result = mAccessRestrictionProvider->Check(subjectDescriptor, requestPath); |
576 | | } |
577 | | |
578 | | if (result != CHIP_NO_ERROR) |
579 | | { |
580 | | ChipLogProgress(DataManagement, "AccessControl: %s", |
581 | | (result == CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL) ? "denied (restricted)" : "denied (restriction error)"); |
582 | | return result; |
583 | | } |
584 | | |
585 | | return result; |
586 | | } |
587 | | #endif |
588 | | |
589 | | #if CHIP_ACCESS_CONTROL_DUMP_ENABLED |
590 | | CHIP_ERROR AccessControl::Dump(const Entry & entry) |
591 | | { |
592 | | CHIP_ERROR err; |
593 | | |
594 | | ChipLogDetail(DataManagement, "----- BEGIN ENTRY -----"); |
595 | | |
596 | | { |
597 | | FabricIndex fabricIndex; |
598 | | SuccessOrExit(err = entry.GetFabricIndex(fabricIndex)); |
599 | | ChipLogDetail(DataManagement, "fabricIndex: %u", fabricIndex); |
600 | | } |
601 | | |
602 | | { |
603 | | Privilege privilege; |
604 | | SuccessOrExit(err = entry.GetPrivilege(privilege)); |
605 | | ChipLogDetail(DataManagement, "privilege: %d", to_underlying(privilege)); |
606 | | } |
607 | | |
608 | | { |
609 | | AuthMode authMode; |
610 | | SuccessOrExit(err = entry.GetAuthMode(authMode)); |
611 | | ChipLogDetail(DataManagement, "authMode: %d", to_underlying(authMode)); |
612 | | } |
613 | | |
614 | | { |
615 | | AuxiliaryType auxiliaryType; |
616 | | // Auxiliary type is optional, so it not being implemented |
617 | | // is still a valid configuration, all other errors should |
618 | | // be handled appropriately. |
619 | | err = GetAuxiliaryType(auxiliaryType); |
620 | | if (err != CHIP_ERROR_NOT_IMPLEMENTED) |
621 | | { |
622 | | SuccessOrExit(err); |
623 | | ChipLogDetail(DataManagement, "auxiliaryType: %d", to_underlying(auxiliaryType)); |
624 | | } |
625 | | } |
626 | | |
627 | | { |
628 | | size_t count; |
629 | | SuccessOrExit(err = entry.GetSubjectCount(count)); |
630 | | if (count) |
631 | | { |
632 | | ChipLogDetail(DataManagement, "subjects: %u", static_cast<unsigned>(count)); |
633 | | for (size_t i = 0; i < count; ++i) |
634 | | { |
635 | | NodeId subject; |
636 | | SuccessOrExit(err = entry.GetSubject(i, subject)); |
637 | | ChipLogDetail(DataManagement, " %u: 0x" ChipLogFormatX64, static_cast<unsigned>(i), ChipLogValueX64(subject)); |
638 | | } |
639 | | } |
640 | | } |
641 | | |
642 | | { |
643 | | size_t count; |
644 | | SuccessOrExit(err = entry.GetTargetCount(count)); |
645 | | if (count) |
646 | | { |
647 | | ChipLogDetail(DataManagement, "targets: %u", static_cast<unsigned>(count)); |
648 | | for (size_t i = 0; i < count; ++i) |
649 | | { |
650 | | Entry::Target target; |
651 | | SuccessOrExit(err = entry.GetTarget(i, target)); |
652 | | if (target.flags & Entry::Target::kCluster) |
653 | | { |
654 | | ChipLogDetail(DataManagement, " %u: cluster: 0x" ChipLogFormatMEI, static_cast<unsigned>(i), |
655 | | ChipLogValueMEI(target.cluster)); |
656 | | } |
657 | | if (target.flags & Entry::Target::kEndpoint) |
658 | | { |
659 | | ChipLogDetail(DataManagement, " %u: endpoint: %u", static_cast<unsigned>(i), target.endpoint); |
660 | | } |
661 | | if (target.flags & Entry::Target::kDeviceType) |
662 | | { |
663 | | ChipLogDetail(DataManagement, " %u: deviceType: 0x" ChipLogFormatMEI, static_cast<unsigned>(i), |
664 | | ChipLogValueMEI(target.deviceType)); |
665 | | } |
666 | | } |
667 | | } |
668 | | } |
669 | | |
670 | | ChipLogDetail(DataManagement, "----- END ENTRY -----"); |
671 | | |
672 | | return CHIP_NO_ERROR; |
673 | | |
674 | | exit: |
675 | | ChipLogError(DataManagement, "AccessControl: dump failed %" CHIP_ERROR_FORMAT, err.Format()); |
676 | | return err; |
677 | | } |
678 | | #endif |
679 | | |
680 | | bool AccessControl::Entry::IsValid() const |
681 | 0 | { |
682 | 0 | const char * log = "unexpected error"; |
683 | 0 | IgnoreUnusedVariable(log); // logging may be disabled |
684 | |
|
685 | 0 | AuthMode authMode = AuthMode::kNone; |
686 | 0 | FabricIndex fabricIndex = kUndefinedFabricIndex; |
687 | 0 | Privilege privilege = static_cast<Privilege>(0); |
688 | 0 | AuxiliaryType auxiliaryType = AuxiliaryType::kSystem; |
689 | 0 | size_t subjectCount = 0; |
690 | 0 | size_t targetCount = 0; |
691 | |
|
692 | 0 | CHIP_ERROR err = CHIP_NO_ERROR; |
693 | 0 | SuccessOrExit(err = GetAuthMode(authMode)); |
694 | 0 | SuccessOrExit(err = GetFabricIndex(fabricIndex)); |
695 | 0 | SuccessOrExit(err = GetPrivilege(privilege)); |
696 | 0 | SuccessOrExit(err = GetSubjectCount(subjectCount)); |
697 | 0 | SuccessOrExit(err = GetTargetCount(targetCount)); |
698 | | |
699 | | // Auxiliary type is optional, so it not being implemented |
700 | | // is still a valid configuration, all other errors should |
701 | | // be handled appropriately. |
702 | 0 | err = GetAuxiliaryType(auxiliaryType); |
703 | 0 | if (err != CHIP_ERROR_NOT_IMPLEMENTED) |
704 | 0 | { |
705 | 0 | SuccessOrExit(err); |
706 | 0 | } |
707 | | |
708 | 0 | #if CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 1 |
709 | 0 | ChipLogProgress(DataManagement, "AccessControl: validating f=%u p=%c a=%c x=%d s=%d t=%d", fabricIndex, |
710 | 0 | GetPrivilegeStringForLogging(privilege), GetAuthModeStringForLogging(authMode), |
711 | 0 | GetAuxiliaryTypeStringForLogging(auxiliaryType), static_cast<int>(subjectCount), static_cast<int>(targetCount)); |
712 | 0 | #endif // CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 1 |
713 | | |
714 | | // Fabric index must be defined. |
715 | 0 | VerifyOrExit(fabricIndex != kUndefinedFabricIndex, log = "invalid fabric index"); |
716 | | |
717 | 0 | if (authMode != AuthMode::kCase) |
718 | 0 | { |
719 | | // Operational PASE not supported for v1.0 (so must be group). |
720 | 0 | VerifyOrExit(authMode == AuthMode::kGroup, log = "invalid auth mode"); |
721 | | |
722 | | // Privilege must not be administer. |
723 | 0 | VerifyOrExit(privilege != Privilege::kAdminister, log = "invalid privilege"); |
724 | 0 | } |
725 | | |
726 | 0 | for (size_t i = 0; i < subjectCount; ++i) |
727 | 0 | { |
728 | 0 | NodeId subject; |
729 | 0 | SuccessOrExit(err = GetSubject(i, subject)); |
730 | 0 | const bool kIsCase = authMode == AuthMode::kCase; |
731 | 0 | const bool kIsGroup = authMode == AuthMode::kGroup; |
732 | 0 | #if CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 1 |
733 | 0 | ChipLogProgress(DataManagement, " validating subject 0x" ChipLogFormatX64, ChipLogValueX64(subject)); |
734 | 0 | #endif // CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY > 1 |
735 | 0 | VerifyOrExit((kIsCase && IsValidCaseNodeId(subject)) || (kIsGroup && IsValidGroupNodeId(subject)), log = "invalid subject"); |
736 | 0 | } |
737 | | |
738 | 0 | for (size_t i = 0; i < targetCount; ++i) |
739 | 0 | { |
740 | 0 | Entry::Target target; |
741 | 0 | SuccessOrExit(err = GetTarget(i, target)); |
742 | 0 | const bool kHasCluster = target.flags & Entry::Target::kCluster; |
743 | 0 | const bool kHasEndpoint = target.flags & Entry::Target::kEndpoint; |
744 | 0 | const bool kHasDeviceType = target.flags & Entry::Target::kDeviceType; |
745 | 0 | VerifyOrExit((kHasCluster || kHasEndpoint || kHasDeviceType) && !(kHasEndpoint && kHasDeviceType) && |
746 | 0 | (!kHasCluster || IsValidClusterId(target.cluster)) && |
747 | 0 | (!kHasEndpoint || IsValidEndpointId(target.endpoint)) && |
748 | 0 | (!kHasDeviceType || IsValidDeviceTypeId(target.deviceType)), |
749 | 0 | log = "invalid target"); |
750 | 0 | } |
751 | | |
752 | 0 | return true; |
753 | | |
754 | 0 | exit: |
755 | 0 | if (err != CHIP_NO_ERROR) |
756 | 0 | { |
757 | 0 | ChipLogError(DataManagement, "AccessControl: %s %" CHIP_ERROR_FORMAT, log, err.Format()); |
758 | 0 | } |
759 | 0 | else |
760 | 0 | { |
761 | 0 | ChipLogError(DataManagement, "AccessControl: %s", log); |
762 | 0 | } |
763 | 0 | return false; |
764 | 0 | } |
765 | | |
766 | | void AccessControl::NotifyEntryChanged(const SubjectDescriptor * subjectDescriptor, FabricIndex fabric, size_t index, |
767 | | const Entry * entry, EntryListener::ChangeType changeType) |
768 | 0 | { |
769 | 0 | for (EntryListener * listener = mEntryListener; listener != nullptr; listener = listener->mNext) |
770 | 0 | { |
771 | 0 | listener->OnEntryChanged(subjectDescriptor, fabric, index, entry, changeType); |
772 | 0 | } |
773 | 0 | } |
774 | | |
775 | | AccessControl & GetAccessControl() |
776 | 0 | { |
777 | 0 | return (globalAccessControl) ? *globalAccessControl : defaultAccessControl.get(); |
778 | 0 | } |
779 | | |
780 | | void SetAccessControl(AccessControl & accessControl) |
781 | 0 | { |
782 | 0 | ChipLogProgress(DataManagement, "AccessControl: setting"); |
783 | 0 | globalAccessControl = &accessControl; |
784 | 0 | } |
785 | | |
786 | | void ResetAccessControlToDefault() |
787 | 0 | { |
788 | 0 | globalAccessControl = nullptr; |
789 | 0 | } |
790 | | |
791 | | } // namespace Access |
792 | | } // namespace chip |