/src/open62541/plugins/ua_accesscontrol_default.c
Line | Count | Source |
1 | | /* This work is licensed under a Creative Commons CCZero 1.0 Universal License. |
2 | | * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. |
3 | | * |
4 | | * Copyright 2016-2017 (c) Fraunhofer IOSB (Author: Julius Pfrommer) |
5 | | * Copyright 2017 (c) Stefan Profanter, fortiss GmbH |
6 | | * Copyright 2019 (c) HMS Industrial Networks AB (Author: Jonas Green) |
7 | | */ |
8 | | |
9 | | #include <open62541/plugin/accesscontrol_default.h> |
10 | | |
11 | | #ifdef UA_ENABLE_RBAC |
12 | | /* Internal RBAC API; not declared in public plugin headers. */ |
13 | | UA_StatusCode |
14 | | UA_Server_getEffectivePermissions(UA_Server *server, |
15 | | const UA_NodeId *sessionId, |
16 | | const UA_NodeId *nodeId, |
17 | | UA_PermissionType *effectivePermissions); |
18 | | #endif |
19 | | |
20 | | /* Example access control management. Anonymous and username / password login. |
21 | | * The access rights are maximally permissive. |
22 | | * |
23 | | * FOR PRODUCTION USE, THIS EXAMPLE PLUGIN SHOULD BE REPLACED WITH LESS |
24 | | * PERMISSIVE ACCESS CONTROL. |
25 | | * |
26 | | * For TransferSubscriptions, we check whether the transfer happens between |
27 | | * Sessions for the same user. */ |
28 | | |
29 | | typedef struct { |
30 | | UA_Boolean allowAnonymous; |
31 | | size_t usernamePasswordLoginSize; |
32 | | UA_UsernamePasswordLogin *usernamePasswordLogin; |
33 | | UA_UsernamePasswordLoginCallback loginCallback; |
34 | | void *loginContext; |
35 | | UA_CertificateGroup verifyX509; |
36 | | } AccessControlContext; |
37 | | |
38 | | #define ANONYMOUS_POLICY "open62541-anonymous-policy" |
39 | | #define CERTIFICATE_POLICY "open62541-certificate-policy" |
40 | | #define USERNAME_POLICY "open62541-username-policy" |
41 | | |
42 | | /************************/ |
43 | | /* Access Control Logic */ |
44 | | /************************/ |
45 | | |
46 | | static UA_StatusCode |
47 | | activateSession_default(UA_Server *server, UA_AccessControl *ac, |
48 | | const UA_EndpointDescription *endpointDescription, |
49 | | const UA_ByteString *secureChannelRemoteCertificate, |
50 | | const UA_NodeId *sessionId, |
51 | | const UA_ExtensionObject *userIdentityToken, |
52 | 975 | void **sessionContext) { |
53 | 975 | AccessControlContext *context = (AccessControlContext*)ac->context; |
54 | | |
55 | | /* The empty token is interpreted as anonymous */ |
56 | 975 | UA_AnonymousIdentityToken anonToken; |
57 | 975 | UA_ExtensionObject tmpIdentity; |
58 | 975 | if(userIdentityToken->encoding == UA_EXTENSIONOBJECT_ENCODED_NOBODY) { |
59 | 975 | UA_AnonymousIdentityToken_init(&anonToken); |
60 | 975 | UA_ExtensionObject_init(&tmpIdentity); |
61 | 975 | UA_ExtensionObject_setValueNoDelete(&tmpIdentity, |
62 | 975 | &anonToken, |
63 | 975 | &UA_TYPES[UA_TYPES_ANONYMOUSIDENTITYTOKEN]); |
64 | 975 | userIdentityToken = &tmpIdentity; |
65 | 975 | } |
66 | | |
67 | | /* Could the token be decoded? */ |
68 | 975 | if(userIdentityToken->encoding < UA_EXTENSIONOBJECT_DECODED) |
69 | 0 | return UA_STATUSCODE_BADIDENTITYTOKENINVALID; |
70 | | |
71 | 975 | const UA_DataType *tokenType = userIdentityToken->content.decoded.type; |
72 | 975 | if(tokenType == &UA_TYPES[UA_TYPES_ANONYMOUSIDENTITYTOKEN]) { |
73 | | /* Anonymous login */ |
74 | 975 | if(!context->allowAnonymous) |
75 | 0 | return UA_STATUSCODE_BADIDENTITYTOKENINVALID; |
76 | 975 | if(context->loginCallback) { |
77 | 0 | UA_StatusCode res = |
78 | 0 | context->loginCallback(&UA_STRING_NULL, &UA_BYTESTRING_NULL, |
79 | 0 | context->usernamePasswordLoginSize, |
80 | 0 | context->usernamePasswordLogin, |
81 | 0 | sessionContext, context->loginContext); |
82 | 0 | if(res != UA_STATUSCODE_GOOD) |
83 | 0 | return UA_STATUSCODE_BADUSERACCESSDENIED; |
84 | 0 | } |
85 | 975 | } else if(tokenType == &UA_TYPES[UA_TYPES_USERNAMEIDENTITYTOKEN]) { |
86 | | /* Username and password */ |
87 | 0 | const UA_UserNameIdentityToken *userToken = (UA_UserNameIdentityToken*) |
88 | 0 | userIdentityToken->content.decoded.data; |
89 | | |
90 | | /* Empty username and password */ |
91 | 0 | if(userToken->userName.length == 0 && userToken->password.length == 0) |
92 | 0 | return UA_STATUSCODE_BADIDENTITYTOKENINVALID; |
93 | | |
94 | | /* Try to match username/pw */ |
95 | 0 | if(context->loginCallback) { |
96 | | /* Configured callback */ |
97 | 0 | UA_StatusCode res = |
98 | 0 | context->loginCallback(&userToken->userName, &userToken->password, |
99 | 0 | context->usernamePasswordLoginSize, |
100 | 0 | context->usernamePasswordLogin, |
101 | 0 | sessionContext, context->loginContext); |
102 | 0 | if(res != UA_STATUSCODE_GOOD) |
103 | 0 | return UA_STATUSCODE_BADUSERACCESSDENIED; |
104 | 0 | } else { |
105 | | /* Compare against the configured list */ |
106 | 0 | UA_Boolean match = false; |
107 | 0 | for(size_t i = 0; i < context->usernamePasswordLoginSize; i++) { |
108 | 0 | UA_UsernamePasswordLogin *upl = &context->usernamePasswordLogin[i]; |
109 | 0 | if(!UA_String_equal(&userToken->userName, &upl->username)) |
110 | 0 | continue; |
111 | 0 | if(userToken->password.length != upl->password.length) |
112 | 0 | continue; |
113 | 0 | if(!UA_constantTimeEqual(userToken->password.data, |
114 | 0 | upl->password.data, |
115 | 0 | upl->password.length)) |
116 | 0 | continue; |
117 | 0 | match = true; |
118 | 0 | break; |
119 | 0 | } |
120 | 0 | if(!match) |
121 | 0 | return UA_STATUSCODE_BADUSERACCESSDENIED; |
122 | 0 | } |
123 | 0 | } else if(tokenType == &UA_TYPES[UA_TYPES_X509IDENTITYTOKEN]) { |
124 | | /* x509 certificate was already validated against the sessionPKI in the |
125 | | * server */ |
126 | 0 | } else { |
127 | | /* Unsupported token type */ |
128 | 0 | return UA_STATUSCODE_BADIDENTITYTOKENINVALID; |
129 | 0 | } |
130 | | |
131 | | /* Store the endpoint's SecurityPolicyUri as a custom session attribute. |
132 | | * This is needed later in allowTransferSubscription_default to determine |
133 | | * whether the session was established over a secure channel. */ |
134 | 975 | if(endpointDescription) { |
135 | 975 | UA_Variant spUri; |
136 | 975 | UA_Variant_setScalar(&spUri, (void*)(uintptr_t)&endpointDescription->securityPolicyUri, |
137 | 975 | &UA_TYPES[UA_TYPES_STRING]); |
138 | 975 | UA_Server_setSessionAttribute(server, sessionId, |
139 | 975 | UA_QUALIFIEDNAME(0, "channelSecurityPolicyUri"), |
140 | 975 | &spUri); |
141 | 975 | } |
142 | | |
143 | 975 | return UA_STATUSCODE_GOOD; |
144 | 975 | } |
145 | | |
146 | | static void |
147 | | closeSession_default(UA_Server *server, UA_AccessControl *ac, |
148 | 5.52k | const UA_NodeId *sessionId, void *sessionContext) { |
149 | 5.52k | } |
150 | | |
151 | | /* Map RBAC PermissionType bits to the node's UserWriteMask. |
152 | | * |
153 | | * OPC UA Part 3, Table 8 defines three separate permission bits that |
154 | | * control attribute writing: |
155 | | * - WriteAttribute -> all WriteMask bits EXCEPT RolePermissions |
156 | | * and Historizing (the "catch-all" permission) |
157 | | * - WriteRolePermissions -> UA_WRITEMASK_ROLEPERMISSIONS (bit 23) |
158 | | * - WriteHistorizing -> UA_WRITEMASK_HISTORIZING (bit 9) |
159 | | * |
160 | | * 0xFFFFFFFF effectivePerms means "no RBAC restrictions configured" for |
161 | | * the node, so we return all-bits-set (fully permissive). */ |
162 | | static UA_UInt32 |
163 | | getUserRightsMask_default(UA_Server *server, UA_AccessControl *ac, |
164 | | const UA_NodeId *sessionId, void *sessionContext, |
165 | 0 | const UA_NodeId *nodeId, void *nodeContext) { |
166 | | #ifdef UA_ENABLE_RBAC |
167 | | UA_PermissionType effectivePerms = 0; |
168 | | UA_StatusCode res = UA_Server_getEffectivePermissions(server, sessionId, |
169 | | nodeId, &effectivePerms); |
170 | | if(res != UA_STATUSCODE_GOOD || effectivePerms == 0xFFFFFFFF) |
171 | | return 0xFFFFFFFF; |
172 | | UA_UInt32 userWriteMask = 0; |
173 | | if(effectivePerms & UA_PERMISSIONTYPE_WRITEATTRIBUTE) { |
174 | | /* Grant all attribute-write bits, then carve out the two that |
175 | | * have their own dedicated permission bits. */ |
176 | | userWriteMask = 0xFFFFFFFF; |
177 | | userWriteMask &= ~UA_WRITEMASK_ROLEPERMISSIONS; |
178 | | userWriteMask &= ~UA_WRITEMASK_HISTORIZING; |
179 | | } |
180 | | if(effectivePerms & UA_PERMISSIONTYPE_WRITEROLEPERMISSIONS) |
181 | | userWriteMask |= UA_WRITEMASK_ROLEPERMISSIONS; |
182 | | if(effectivePerms & UA_PERMISSIONTYPE_WRITEHISTORIZING) |
183 | | userWriteMask |= UA_WRITEMASK_HISTORIZING; |
184 | | return userWriteMask; |
185 | | #else |
186 | 0 | return 0xFFFFFFFF; |
187 | 0 | #endif |
188 | 0 | } |
189 | | |
190 | | /* Map RBAC PermissionType bits to the Variable node's UserAccessLevel. |
191 | | * |
192 | | * OPC UA Part 3, Table 8 maps: |
193 | | * - Read -> ACCESSLEVELMASK_READ (bit 0) |
194 | | * - Write -> ACCESSLEVELMASK_WRITE (bit 1) |
195 | | * - ReadHistory -> ACCESSLEVELMASK_HISTORYREAD (bit 2) |
196 | | * - InsertHistory | |
197 | | * ModifyHistory | |
198 | | * DeleteHistory -> ACCESSLEVELMASK_HISTORYWRITE (bit 3) |
199 | | * |
200 | | * StatusWrite (bit 5) and TimestampWrite (bit 6) are not mapped from |
201 | | * RBAC permissions — they remain restricted unless the node has no |
202 | | * RBAC configuration (0xFFFFFFFF). */ |
203 | | static UA_Byte |
204 | | getUserAccessLevel_default(UA_Server *server, UA_AccessControl *ac, |
205 | | const UA_NodeId *sessionId, void *sessionContext, |
206 | 0 | const UA_NodeId *nodeId, void *nodeContext) { |
207 | | #ifdef UA_ENABLE_RBAC |
208 | | UA_PermissionType effectivePerms = 0; |
209 | | UA_StatusCode res = UA_Server_getEffectivePermissions(server, sessionId, |
210 | | nodeId, &effectivePerms); |
211 | | if(res != UA_STATUSCODE_GOOD || effectivePerms == 0xFFFFFFFF) |
212 | | return 0xFF; |
213 | | UA_Byte userAccessLevel = 0; |
214 | | if(effectivePerms & UA_PERMISSIONTYPE_READ) |
215 | | userAccessLevel |= UA_ACCESSLEVELMASK_READ; |
216 | | if(effectivePerms & UA_PERMISSIONTYPE_WRITE) |
217 | | userAccessLevel |= UA_ACCESSLEVELMASK_WRITE; |
218 | | if(effectivePerms & UA_PERMISSIONTYPE_READHISTORY) |
219 | | userAccessLevel |= UA_ACCESSLEVELMASK_HISTORYREAD; |
220 | | if(effectivePerms & (UA_PERMISSIONTYPE_INSERTHISTORY | |
221 | | UA_PERMISSIONTYPE_MODIFYHISTORY | |
222 | | UA_PERMISSIONTYPE_DELETEHISTORY)) |
223 | | userAccessLevel |= UA_ACCESSLEVELMASK_HISTORYWRITE; |
224 | | return userAccessLevel; |
225 | | #else |
226 | 0 | return 0xFF; |
227 | 0 | #endif |
228 | 0 | } |
229 | | |
230 | | /* OPC UA Part 3, Table 8: Call permission -> UserExecutable attribute. */ |
231 | | static UA_Boolean |
232 | | getUserExecutable_default(UA_Server *server, UA_AccessControl *ac, |
233 | | const UA_NodeId *sessionId, void *sessionContext, |
234 | 0 | const UA_NodeId *methodId, void *methodContext) { |
235 | | #ifdef UA_ENABLE_RBAC |
236 | | UA_PermissionType effectivePerms = 0; |
237 | | UA_StatusCode res = UA_Server_getEffectivePermissions(server, sessionId, |
238 | | methodId, &effectivePerms); |
239 | | if(res != UA_STATUSCODE_GOOD || effectivePerms == 0xFFFFFFFF) |
240 | | return true; |
241 | | return (effectivePerms & UA_PERMISSIONTYPE_CALL) != 0; |
242 | | #else |
243 | 0 | return true; |
244 | 0 | #endif |
245 | 0 | } |
246 | | |
247 | | /* Call permission is checked on both the object and the method node. |
248 | | * Both must grant CALL for the method invocation to be allowed. */ |
249 | | static UA_Boolean |
250 | | getUserExecutableOnObject_default(UA_Server *server, UA_AccessControl *ac, |
251 | | const UA_NodeId *sessionId, void *sessionContext, |
252 | | const UA_NodeId *methodId, void *methodContext, |
253 | 0 | const UA_NodeId *objectId, void *objectContext) { |
254 | | #ifdef UA_ENABLE_RBAC |
255 | | UA_PermissionType objectPerms = 0; |
256 | | UA_StatusCode res = UA_Server_getEffectivePermissions(server, sessionId, |
257 | | objectId, &objectPerms); |
258 | | if(res != UA_STATUSCODE_GOOD) |
259 | | return true; |
260 | | if(objectPerms != 0xFFFFFFFF && !(objectPerms & UA_PERMISSIONTYPE_CALL)) |
261 | | return false; |
262 | | UA_PermissionType methodPerms = 0; |
263 | | res = UA_Server_getEffectivePermissions(server, sessionId, |
264 | | methodId, &methodPerms); |
265 | | if(res != UA_STATUSCODE_GOOD) |
266 | | return true; |
267 | | if(methodPerms != 0xFFFFFFFF && !(methodPerms & UA_PERMISSIONTYPE_CALL)) |
268 | | return false; |
269 | | return true; |
270 | | #else |
271 | 0 | return true; |
272 | 0 | #endif |
273 | 0 | } |
274 | | |
275 | | /* AddNode permission is checked on the parent node. */ |
276 | | static UA_Boolean |
277 | | allowAddNode_default(UA_Server *server, UA_AccessControl *ac, |
278 | | const UA_NodeId *sessionId, void *sessionContext, |
279 | 0 | const UA_AddNodesItem *item) { |
280 | | #ifdef UA_ENABLE_RBAC |
281 | | UA_PermissionType effectivePerms = 0; |
282 | | UA_StatusCode res = UA_Server_getEffectivePermissions(server, sessionId, |
283 | | &item->parentNodeId.nodeId, |
284 | | &effectivePerms); |
285 | | if(res != UA_STATUSCODE_GOOD || effectivePerms == 0xFFFFFFFF) |
286 | | return true; |
287 | | return (effectivePerms & UA_PERMISSIONTYPE_ADDNODE) != 0; |
288 | | #else |
289 | 0 | return true; |
290 | 0 | #endif |
291 | 0 | } |
292 | | |
293 | | /* AddReference permission is checked on the source node. */ |
294 | | static UA_Boolean |
295 | | allowAddReference_default(UA_Server *server, UA_AccessControl *ac, |
296 | | const UA_NodeId *sessionId, void *sessionContext, |
297 | 0 | const UA_AddReferencesItem *item) { |
298 | | #ifdef UA_ENABLE_RBAC |
299 | | UA_PermissionType effectivePerms = 0; |
300 | | UA_StatusCode res = UA_Server_getEffectivePermissions(server, sessionId, |
301 | | &item->sourceNodeId, |
302 | | &effectivePerms); |
303 | | if(res != UA_STATUSCODE_GOOD || effectivePerms == 0xFFFFFFFF) |
304 | | return true; |
305 | | return (effectivePerms & UA_PERMISSIONTYPE_ADDREFERENCE) != 0; |
306 | | #else |
307 | 0 | return true; |
308 | 0 | #endif |
309 | 0 | } |
310 | | |
311 | | /* DeleteNode permission is checked on the node itself. */ |
312 | | static UA_Boolean |
313 | | allowDeleteNode_default(UA_Server *server, UA_AccessControl *ac, |
314 | | const UA_NodeId *sessionId, void *sessionContext, |
315 | 149 | const UA_DeleteNodesItem *item) { |
316 | | #ifdef UA_ENABLE_RBAC |
317 | | UA_PermissionType effectivePerms = 0; |
318 | | UA_StatusCode res = UA_Server_getEffectivePermissions(server, sessionId, |
319 | | &item->nodeId, |
320 | | &effectivePerms); |
321 | | if(res != UA_STATUSCODE_GOOD || effectivePerms == 0xFFFFFFFF) |
322 | | return true; |
323 | | return (effectivePerms & UA_PERMISSIONTYPE_DELETENODE) != 0; |
324 | | #else |
325 | 149 | return true; |
326 | 149 | #endif |
327 | 149 | } |
328 | | |
329 | | /* RemoveReference permission is checked on the source node. */ |
330 | | static UA_Boolean |
331 | | allowDeleteReference_default(UA_Server *server, UA_AccessControl *ac, |
332 | | const UA_NodeId *sessionId, void *sessionContext, |
333 | 121k | const UA_DeleteReferencesItem *item) { |
334 | | #ifdef UA_ENABLE_RBAC |
335 | | UA_PermissionType effectivePerms = 0; |
336 | | UA_StatusCode res = UA_Server_getEffectivePermissions(server, sessionId, |
337 | | &item->sourceNodeId, |
338 | | &effectivePerms); |
339 | | if(res != UA_STATUSCODE_GOOD || effectivePerms == 0xFFFFFFFF) |
340 | | return true; |
341 | | return (effectivePerms & UA_PERMISSIONTYPE_REMOVEREFERENCE) != 0; |
342 | | #else |
343 | 121k | return true; |
344 | 121k | #endif |
345 | 121k | } |
346 | | |
347 | | /* OPC UA Part 3, Table 8: Browse permission. */ |
348 | | static UA_Boolean |
349 | | allowBrowseNode_default(UA_Server *server, UA_AccessControl *ac, |
350 | | const UA_NodeId *sessionId, void *sessionContext, |
351 | 0 | const UA_NodeId *nodeId, void *nodeContext) { |
352 | | #ifdef UA_ENABLE_RBAC |
353 | | UA_PermissionType effectivePerms = 0; |
354 | | UA_StatusCode res = UA_Server_getEffectivePermissions(server, sessionId, |
355 | | nodeId, &effectivePerms); |
356 | | if(res != UA_STATUSCODE_GOOD || effectivePerms == 0xFFFFFFFF) |
357 | | return true; |
358 | | return (effectivePerms & UA_PERMISSIONTYPE_BROWSE) != 0; |
359 | | #else |
360 | 0 | return true; |
361 | 0 | #endif |
362 | 0 | } |
363 | | |
364 | | #ifdef UA_ENABLE_SUBSCRIPTIONS |
365 | | static UA_Boolean |
366 | | allowCreateSubscription_default(UA_Server *server, UA_AccessControl *ac, |
367 | 159 | const UA_NodeId *sessionId, void *sessionContext) { |
368 | 159 | return true; |
369 | 159 | } |
370 | | |
371 | | static UA_Boolean |
372 | | allowTransferSubscription_default(UA_Server *server, UA_AccessControl *ac, |
373 | | const UA_NodeId *oldSessionId, void *oldSessionContext, |
374 | 0 | const UA_NodeId *newSessionId, void *newSessionContext) { |
375 | 0 | if(!oldSessionId) |
376 | 0 | return false; |
377 | | |
378 | | /* Get clientUserId for both sessions */ |
379 | 0 | UA_Variant session1UserId; |
380 | 0 | UA_Variant_init(&session1UserId); |
381 | 0 | UA_Server_getSessionAttribute(server, oldSessionId, |
382 | 0 | UA_QUALIFIEDNAME(0, "clientUserId"), |
383 | 0 | &session1UserId); |
384 | 0 | UA_Variant session2UserId; |
385 | 0 | UA_Variant_init(&session2UserId); |
386 | 0 | UA_Server_getSessionAttribute(server, newSessionId, |
387 | 0 | UA_QUALIFIEDNAME(0, "clientUserId"), |
388 | 0 | &session2UserId); |
389 | | |
390 | | /* clientUserId is always a String type */ |
391 | 0 | UA_Boolean result = false; |
392 | 0 | if(session1UserId.type == &UA_TYPES[UA_TYPES_STRING] && |
393 | 0 | session2UserId.type == &UA_TYPES[UA_TYPES_STRING]) { |
394 | 0 | UA_String *userId1 = (UA_String*)session1UserId.data; |
395 | 0 | UA_String *userId2 = (UA_String*)session2UserId.data; |
396 | | |
397 | 0 | if(userId1->length == 0 && userId2->length == 0) { |
398 | | /* Both users are anonymous. |
399 | | * For anonymous users, the OPC UA specification requires |
400 | | * checking the ApplicationUri from the clientDescription |
401 | | * to verify that the same application is transferring the |
402 | | * subscription (e.g. after a network interruption or client |
403 | | * crash with reconnect on a different SecureChannel). |
404 | | * |
405 | | * Additionally, on unsecure connections (SecurityPolicy#None) |
406 | | * the ApplicationUri is not verified against a certificate, |
407 | | * so the transfer must be rejected. */ |
408 | | |
409 | | /* First check: Both sessions must use a secure channel |
410 | | * (not SecurityPolicy#None). The securityPolicyUri was |
411 | | * stored as a session attribute during ActivateSession. */ |
412 | 0 | UA_Variant session1SpUri; |
413 | 0 | UA_Variant_init(&session1SpUri); |
414 | 0 | UA_Server_getSessionAttribute(server, oldSessionId, |
415 | 0 | UA_QUALIFIEDNAME(0, "channelSecurityPolicyUri"), |
416 | 0 | &session1SpUri); |
417 | 0 | UA_Variant session2SpUri; |
418 | 0 | UA_Variant_init(&session2SpUri); |
419 | 0 | UA_Server_getSessionAttribute(server, newSessionId, |
420 | 0 | UA_QUALIFIEDNAME(0, "channelSecurityPolicyUri"), |
421 | 0 | &session2SpUri); |
422 | |
|
423 | 0 | UA_Boolean bothSecure = false; |
424 | 0 | if(session1SpUri.type == &UA_TYPES[UA_TYPES_STRING] && |
425 | 0 | session2SpUri.type == &UA_TYPES[UA_TYPES_STRING]) { |
426 | 0 | UA_String *spUri1 = (UA_String*)session1SpUri.data; |
427 | 0 | UA_String *spUri2 = (UA_String*)session2SpUri.data; |
428 | | /* Reject if either session uses SecurityPolicy#None */ |
429 | 0 | if(!UA_String_equal(spUri1, &UA_SECURITY_POLICY_NONE_URI) && |
430 | 0 | !UA_String_equal(spUri2, &UA_SECURITY_POLICY_NONE_URI)) { |
431 | 0 | bothSecure = true; |
432 | 0 | } |
433 | 0 | } |
434 | |
|
435 | 0 | UA_Variant_clear(&session1SpUri); |
436 | 0 | UA_Variant_clear(&session2SpUri); |
437 | | |
438 | | /* Second check: If both channels are secure, compare the |
439 | | * ApplicationUri from the clientDescription. On secure |
440 | | * connections the ApplicationUri is validated against |
441 | | * the client certificate during CreateSession. */ |
442 | 0 | if(bothSecure) { |
443 | 0 | UA_Variant session1Desc; |
444 | 0 | UA_Variant_init(&session1Desc); |
445 | 0 | UA_Server_getSessionAttribute(server, oldSessionId, |
446 | 0 | UA_QUALIFIEDNAME(0, "clientDescription"), |
447 | 0 | &session1Desc); |
448 | 0 | UA_Variant session2Desc; |
449 | 0 | UA_Variant_init(&session2Desc); |
450 | 0 | UA_Server_getSessionAttribute(server, newSessionId, |
451 | 0 | UA_QUALIFIEDNAME(0, "clientDescription"), |
452 | 0 | &session2Desc); |
453 | |
|
454 | 0 | if(session1Desc.type == &UA_TYPES[UA_TYPES_APPLICATIONDESCRIPTION] && |
455 | 0 | session2Desc.type == &UA_TYPES[UA_TYPES_APPLICATIONDESCRIPTION]) { |
456 | 0 | UA_ApplicationDescription *desc1 = |
457 | 0 | (UA_ApplicationDescription*)session1Desc.data; |
458 | 0 | UA_ApplicationDescription *desc2 = |
459 | 0 | (UA_ApplicationDescription*)session2Desc.data; |
460 | 0 | if(desc1->applicationUri.length > 0 && |
461 | 0 | desc2->applicationUri.length > 0 && |
462 | 0 | UA_String_equal(&desc1->applicationUri, |
463 | 0 | &desc2->applicationUri)) { |
464 | 0 | result = true; |
465 | 0 | } |
466 | 0 | } |
467 | |
|
468 | 0 | UA_Variant_clear(&session1Desc); |
469 | 0 | UA_Variant_clear(&session2Desc); |
470 | 0 | } |
471 | 0 | } else if(UA_String_equal(userId1, userId2)) { |
472 | | /* Same authenticated user - allow transfer */ |
473 | 0 | result = true; |
474 | 0 | } |
475 | 0 | } |
476 | | |
477 | 0 | UA_Variant_clear(&session1UserId); |
478 | 0 | UA_Variant_clear(&session2UserId); |
479 | | |
480 | 0 | return result; |
481 | 0 | } |
482 | | #endif |
483 | | |
484 | | #ifdef UA_ENABLE_HISTORIZING |
485 | | /* OPC UA Part 3, Table 8: InsertHistory / ModifyHistory permissions. |
486 | | * INSERT -> InsertHistory, REPLACE/UPDATE -> ModifyHistory. */ |
487 | | static UA_Boolean |
488 | | allowHistoryUpdateUpdateData_default(UA_Server *server, UA_AccessControl *ac, |
489 | | const UA_NodeId *sessionId, void *sessionContext, |
490 | | const UA_NodeId *nodeId, |
491 | | UA_PerformUpdateType performInsertReplace, |
492 | 0 | const UA_DataValue *value) { |
493 | | #ifdef UA_ENABLE_RBAC |
494 | | UA_PermissionType effectivePerms = 0; |
495 | | UA_StatusCode res = UA_Server_getEffectivePermissions(server, sessionId, |
496 | | nodeId, &effectivePerms); |
497 | | if(res != UA_STATUSCODE_GOOD || effectivePerms == 0xFFFFFFFF) |
498 | | return true; |
499 | | if(performInsertReplace == UA_PERFORMUPDATETYPE_INSERT) |
500 | | return (effectivePerms & UA_PERMISSIONTYPE_INSERTHISTORY) != 0; |
501 | | else if(performInsertReplace == UA_PERFORMUPDATETYPE_REPLACE || |
502 | | performInsertReplace == UA_PERFORMUPDATETYPE_UPDATE) |
503 | | return (effectivePerms & UA_PERMISSIONTYPE_MODIFYHISTORY) != 0; |
504 | | return true; |
505 | | #else |
506 | 0 | return true; |
507 | 0 | #endif |
508 | 0 | } |
509 | | |
510 | | /* OPC UA Part 3, Table 8: DeleteHistory permission. */ |
511 | | static UA_Boolean |
512 | | allowHistoryUpdateDeleteRawModified_default(UA_Server *server, UA_AccessControl *ac, |
513 | | const UA_NodeId *sessionId, void *sessionContext, |
514 | | const UA_NodeId *nodeId, |
515 | | UA_DateTime startTimestamp, |
516 | | UA_DateTime endTimestamp, |
517 | 0 | bool isDeleteModified) { |
518 | | #ifdef UA_ENABLE_RBAC |
519 | | UA_PermissionType effectivePerms = 0; |
520 | | UA_StatusCode res = UA_Server_getEffectivePermissions(server, sessionId, |
521 | | nodeId, &effectivePerms); |
522 | | if(res != UA_STATUSCODE_GOOD || effectivePerms == 0xFFFFFFFF) |
523 | | return true; |
524 | | return (effectivePerms & UA_PERMISSIONTYPE_DELETEHISTORY) != 0; |
525 | | #else |
526 | 0 | return true; |
527 | 0 | #endif |
528 | 0 | } |
529 | | #endif |
530 | | |
531 | | /***************************************/ |
532 | | /* Create Delete Access Control Plugin */ |
533 | | /***************************************/ |
534 | | |
535 | 13.5k | static void clear_default(UA_AccessControl *ac) { |
536 | 13.5k | UA_Array_delete((void*)(uintptr_t)ac->userTokenPolicies, |
537 | 13.5k | ac->userTokenPoliciesSize, |
538 | 13.5k | &UA_TYPES[UA_TYPES_USERTOKENPOLICY]); |
539 | 13.5k | ac->userTokenPolicies = NULL; |
540 | 13.5k | ac->userTokenPoliciesSize = 0; |
541 | | |
542 | 13.5k | AccessControlContext *context = (AccessControlContext*)ac->context; |
543 | | |
544 | 13.5k | if (context) { |
545 | 13.5k | for(size_t i = 0; i < context->usernamePasswordLoginSize; i++) { |
546 | 0 | UA_String_clear(&context->usernamePasswordLogin[i].username); |
547 | 0 | UA_ByteString_clear(&context->usernamePasswordLogin[i].password); |
548 | 0 | } |
549 | 13.5k | if(context->usernamePasswordLoginSize > 0) |
550 | 0 | UA_free(context->usernamePasswordLogin); |
551 | | |
552 | 13.5k | UA_free(ac->context); |
553 | 13.5k | ac->context = NULL; |
554 | 13.5k | } |
555 | 13.5k | } |
556 | | |
557 | | UA_StatusCode |
558 | | UA_AccessControl_default(UA_ServerConfig *config, |
559 | | UA_Boolean allowAnonymous, |
560 | | const UA_String *userTokenPolicyUri, |
561 | | size_t usernamePasswordLoginSize, |
562 | 13.5k | const UA_UsernamePasswordLogin *usernamePasswordLogin) { |
563 | 13.5k | UA_LOG_WARNING(config->logging, UA_LOGCATEGORY_SERVER, |
564 | 13.5k | "AccessControl: Unconfigured AccessControl. Users have all permissions."); |
565 | 13.5k | UA_AccessControl *ac = &config->accessControl; |
566 | | |
567 | 13.5k | if(ac->clear) |
568 | 0 | ac->clear(ac); |
569 | | |
570 | 13.5k | ac->clear = clear_default; |
571 | 13.5k | ac->activateSession = activateSession_default; |
572 | 13.5k | ac->closeSession = closeSession_default; |
573 | 13.5k | ac->getUserRightsMask = getUserRightsMask_default; |
574 | 13.5k | ac->getUserAccessLevel = getUserAccessLevel_default; |
575 | 13.5k | ac->getUserExecutable = getUserExecutable_default; |
576 | 13.5k | ac->getUserExecutableOnObject = getUserExecutableOnObject_default; |
577 | 13.5k | ac->allowAddNode = allowAddNode_default; |
578 | 13.5k | ac->allowAddReference = allowAddReference_default; |
579 | 13.5k | ac->allowBrowseNode = allowBrowseNode_default; |
580 | | |
581 | 13.5k | #ifdef UA_ENABLE_SUBSCRIPTIONS |
582 | 13.5k | ac->allowCreateSubscription = allowCreateSubscription_default; |
583 | 13.5k | ac->allowTransferSubscription = allowTransferSubscription_default; |
584 | 13.5k | #endif |
585 | | |
586 | 13.5k | #ifdef UA_ENABLE_HISTORIZING |
587 | 13.5k | ac->allowHistoryUpdateUpdateData = allowHistoryUpdateUpdateData_default; |
588 | 13.5k | ac->allowHistoryUpdateDeleteRawModified = allowHistoryUpdateDeleteRawModified_default; |
589 | 13.5k | #endif |
590 | | |
591 | 13.5k | ac->allowDeleteNode = allowDeleteNode_default; |
592 | 13.5k | ac->allowDeleteReference = allowDeleteReference_default; |
593 | | |
594 | 13.5k | AccessControlContext *context = (AccessControlContext*) |
595 | 13.5k | UA_malloc(sizeof(AccessControlContext)); |
596 | 13.5k | if(!context) |
597 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
598 | 13.5k | memset(context, 0, sizeof(AccessControlContext)); |
599 | 13.5k | ac->context = context; |
600 | | |
601 | | /* Allow anonymous? */ |
602 | 13.5k | context->allowAnonymous = allowAnonymous; |
603 | 13.5k | if(allowAnonymous) { |
604 | 13.5k | UA_LOG_INFO(config->logging, UA_LOGCATEGORY_SERVER, |
605 | 13.5k | "AccessControl: Anonymous login is enabled"); |
606 | 13.5k | } |
607 | | |
608 | | /* Copy username/password to the access control plugin */ |
609 | 13.5k | if(usernamePasswordLoginSize > 0) { |
610 | 0 | context->usernamePasswordLogin = (UA_UsernamePasswordLogin*) |
611 | 0 | UA_malloc(usernamePasswordLoginSize * sizeof(UA_UsernamePasswordLogin)); |
612 | 0 | if(!context->usernamePasswordLogin) |
613 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
614 | 0 | context->usernamePasswordLoginSize = usernamePasswordLoginSize; |
615 | 0 | for(size_t i = 0; i < usernamePasswordLoginSize; i++) { |
616 | 0 | UA_String_copy(&usernamePasswordLogin[i].username, |
617 | 0 | &context->usernamePasswordLogin[i].username); |
618 | 0 | UA_ByteString_copy(&usernamePasswordLogin[i].password, |
619 | 0 | &context->usernamePasswordLogin[i].password); |
620 | 0 | } |
621 | 0 | } |
622 | | |
623 | 13.5k | size_t numOfPolcies = 1; |
624 | 13.5k | if(!userTokenPolicyUri) { |
625 | 13.5k | if(config->securityPoliciesSize > 0) |
626 | 13.5k | numOfPolcies = config->securityPoliciesSize; |
627 | 0 | else { |
628 | 0 | UA_LOG_WARNING(config->logging, UA_LOGCATEGORY_SERVER, |
629 | 0 | "No security policies defined for the secure channel."); |
630 | 0 | return UA_STATUSCODE_BADINTERNALERROR; |
631 | 0 | } |
632 | 13.5k | } |
633 | | |
634 | | /* Set the allowed policies */ |
635 | 13.5k | size_t policies = 0; |
636 | 13.5k | if(allowAnonymous) |
637 | 13.5k | policies++; |
638 | 13.5k | if(usernamePasswordLoginSize > 0) |
639 | 0 | policies++; |
640 | 13.5k | if(config->sessionPKI.verifyCertificate) |
641 | 13.5k | policies++; |
642 | 13.5k | ac->userTokenPoliciesSize = 0; |
643 | 13.5k | ac->userTokenPolicies = (UA_UserTokenPolicy *) |
644 | 13.5k | UA_Array_new(policies * numOfPolcies, &UA_TYPES[UA_TYPES_USERTOKENPOLICY]); |
645 | 13.5k | if(!ac->userTokenPolicies) |
646 | 0 | return UA_STATUSCODE_BADOUTOFMEMORY; |
647 | 13.5k | ac->userTokenPoliciesSize = policies * numOfPolcies; |
648 | | |
649 | 13.5k | if(policies == 0) { |
650 | 0 | UA_LOG_WARNING(config->logging, UA_LOGCATEGORY_SERVER, |
651 | 0 | "No allowed policies set."); |
652 | 0 | return UA_STATUSCODE_GOOD; |
653 | 0 | } |
654 | | |
655 | 13.5k | const UA_String *utpUri = NULL; |
656 | 13.5k | policies = 0; |
657 | 27.0k | for(size_t i = 0; i < numOfPolcies; i++) { |
658 | 13.5k | if(userTokenPolicyUri) { |
659 | 0 | utpUri = userTokenPolicyUri; |
660 | 13.5k | } else { |
661 | 13.5k | utpUri = &config->securityPolicies[i].policyUri; |
662 | 13.5k | } |
663 | 13.5k | if(allowAnonymous) { |
664 | 13.5k | ac->userTokenPolicies[policies].tokenType = UA_USERTOKENTYPE_ANONYMOUS; |
665 | 13.5k | ac->userTokenPolicies[policies].policyId = UA_STRING_ALLOC(ANONYMOUS_POLICY); |
666 | 13.5k | UA_String_copy(utpUri, |
667 | 13.5k | &ac->userTokenPolicies[policies].securityPolicyUri); |
668 | 13.5k | policies++; |
669 | 13.5k | } |
670 | | |
671 | 13.5k | if(config->sessionPKI.verifyCertificate) { |
672 | 13.5k | ac->userTokenPolicies[policies].tokenType = UA_USERTOKENTYPE_CERTIFICATE; |
673 | 13.5k | ac->userTokenPolicies[policies].policyId = UA_STRING_ALLOC(CERTIFICATE_POLICY); |
674 | | #if UA_LOGLEVEL <= 400 |
675 | | if(UA_String_equal(utpUri, &UA_SECURITY_POLICY_NONE_URI)) { |
676 | | UA_LOG_WARNING(config->logging, UA_LOGCATEGORY_SERVER, |
677 | | "x509 Certificate Authentication configured, " |
678 | | "but no encrypting SecurityPolicy. " |
679 | | "This can leak credentials on the network."); |
680 | | } |
681 | | #endif |
682 | 13.5k | UA_String_copy(utpUri, |
683 | 13.5k | &ac->userTokenPolicies[policies].securityPolicyUri); |
684 | 13.5k | policies++; |
685 | 13.5k | } |
686 | | |
687 | 13.5k | if(usernamePasswordLoginSize > 0) { |
688 | 0 | ac->userTokenPolicies[policies].tokenType = UA_USERTOKENTYPE_USERNAME; |
689 | 0 | ac->userTokenPolicies[policies].policyId = UA_STRING_ALLOC(USERNAME_POLICY); |
690 | | #if UA_LOGLEVEL <= 400 |
691 | | if(UA_String_equal(utpUri, &UA_SECURITY_POLICY_NONE_URI)) { |
692 | | UA_LOG_WARNING(config->logging, UA_LOGCATEGORY_SERVER, |
693 | | "Username/Password Authentication configured, " |
694 | | "but no encrypting SecurityPolicy. " |
695 | | "This can leak credentials on the network."); |
696 | | } |
697 | | #endif |
698 | 0 | UA_String_copy(utpUri, |
699 | 0 | &ac->userTokenPolicies[policies].securityPolicyUri); |
700 | 0 | policies++; |
701 | 0 | } |
702 | 13.5k | } |
703 | 13.5k | return UA_STATUSCODE_GOOD; |
704 | 13.5k | } |
705 | | |
706 | | UA_StatusCode |
707 | | UA_AccessControl_defaultWithLoginCallback(UA_ServerConfig *config, |
708 | | UA_Boolean allowAnonymous, |
709 | | const UA_String *userTokenPolicyUri, |
710 | | size_t usernamePasswordLoginSize, |
711 | | const UA_UsernamePasswordLogin *usernamePasswordLogin, |
712 | | UA_UsernamePasswordLoginCallback loginCallback, |
713 | 0 | void *loginContext) { |
714 | 0 | AccessControlContext *context; |
715 | 0 | UA_StatusCode sc = |
716 | 0 | UA_AccessControl_default(config, allowAnonymous, userTokenPolicyUri, |
717 | 0 | usernamePasswordLoginSize, usernamePasswordLogin); |
718 | 0 | if(sc != UA_STATUSCODE_GOOD) |
719 | 0 | return sc; |
720 | | |
721 | 0 | context = (AccessControlContext *)config->accessControl.context; |
722 | 0 | context->loginCallback = loginCallback; |
723 | 0 | context->loginContext = loginContext; |
724 | |
|
725 | 0 | return UA_STATUSCODE_GOOD; |
726 | 0 | } |
727 | | |