|
@@ -10,44 +10,44 @@
|
|
/* Example access control management. Anonymous and username / password login.
|
|
/* Example access control management. Anonymous and username / password login.
|
|
* The access rights are maximally permissive. */
|
|
* The access rights are maximally permissive. */
|
|
|
|
|
|
|
|
+typedef struct {
|
|
|
|
+ UA_Boolean allowAnonymous;
|
|
|
|
+ size_t usernamePasswordLoginSize;
|
|
|
|
+ UA_UsernamePasswordLogin *usernamePasswordLogin;
|
|
|
|
+} AccessControlContext;
|
|
|
|
+
|
|
#define ANONYMOUS_POLICY "open62541-anonymous-policy"
|
|
#define ANONYMOUS_POLICY "open62541-anonymous-policy"
|
|
#define USERNAME_POLICY "open62541-username-policy"
|
|
#define USERNAME_POLICY "open62541-username-policy"
|
|
-
|
|
|
|
-// TODO: There should be one definition of these strings in the endpoint.
|
|
|
|
-// Put the endpoint definition in the access control struct?
|
|
|
|
-#define UA_STRING_STATIC(s) {sizeof(s)-1, (UA_Byte*)s}
|
|
|
|
const UA_String anonymous_policy = UA_STRING_STATIC(ANONYMOUS_POLICY);
|
|
const UA_String anonymous_policy = UA_STRING_STATIC(ANONYMOUS_POLICY);
|
|
const UA_String username_policy = UA_STRING_STATIC(USERNAME_POLICY);
|
|
const UA_String username_policy = UA_STRING_STATIC(USERNAME_POLICY);
|
|
|
|
|
|
-typedef struct {
|
|
|
|
- UA_String username;
|
|
|
|
- UA_String password;
|
|
|
|
-} UA_UsernamePasswordLogin;
|
|
|
|
-
|
|
|
|
-const size_t usernamePasswordsSize = 2;
|
|
|
|
-UA_UsernamePasswordLogin usernamePasswords[2] = {
|
|
|
|
- { UA_STRING_STATIC("user1"), UA_STRING_STATIC("password") },
|
|
|
|
- { UA_STRING_STATIC("user2"), UA_STRING_STATIC("password1") } };
|
|
|
|
|
|
+/************************/
|
|
|
|
+/* Access Control Logic */
|
|
|
|
+/************************/
|
|
|
|
|
|
-UA_StatusCode
|
|
|
|
-activateSession_default(const UA_NodeId *sessionId,
|
|
|
|
|
|
+static UA_StatusCode
|
|
|
|
+activateSession_default(UA_Server *server, UA_AccessControl *ac,
|
|
|
|
+ const UA_NodeId *sessionId,
|
|
const UA_ExtensionObject *userIdentityToken,
|
|
const UA_ExtensionObject *userIdentityToken,
|
|
void **sessionContext) {
|
|
void **sessionContext) {
|
|
/* Could the token be decoded? */
|
|
/* Could the token be decoded? */
|
|
if(userIdentityToken->encoding < UA_EXTENSIONOBJECT_DECODED)
|
|
if(userIdentityToken->encoding < UA_EXTENSIONOBJECT_DECODED)
|
|
return UA_STATUSCODE_BADIDENTITYTOKENINVALID;
|
|
return UA_STATUSCODE_BADIDENTITYTOKENINVALID;
|
|
|
|
|
|
|
|
+ AccessControlContext *context = (AccessControlContext*)ac->context;
|
|
|
|
+
|
|
/* Anonymous login */
|
|
/* Anonymous login */
|
|
- if(userIdentityToken->content.decoded.type ==
|
|
|
|
- &UA_TYPES[UA_TYPES_ANONYMOUSIDENTITYTOKEN]) {
|
|
|
|
- const UA_AnonymousIdentityToken *token =
|
|
|
|
- (UA_AnonymousIdentityToken*)userIdentityToken->content.decoded.data;
|
|
|
|
|
|
+ if(userIdentityToken->content.decoded.type == &UA_TYPES[UA_TYPES_ANONYMOUSIDENTITYTOKEN]) {
|
|
|
|
+ if(!context->allowAnonymous)
|
|
|
|
+ return UA_STATUSCODE_BADIDENTITYTOKENINVALID;
|
|
|
|
+
|
|
|
|
+ const UA_AnonymousIdentityToken *token = (UA_AnonymousIdentityToken*)
|
|
|
|
+ userIdentityToken->content.decoded.data;
|
|
|
|
|
|
/* Compatibility notice: Siemens OPC Scout v10 provides an empty
|
|
/* Compatibility notice: Siemens OPC Scout v10 provides an empty
|
|
* policyId. This is not compliant. For compatibility, assume that empty
|
|
* policyId. This is not compliant. For compatibility, assume that empty
|
|
* policyId == ANONYMOUS_POLICY */
|
|
* policyId == ANONYMOUS_POLICY */
|
|
- if(token->policyId.data &&
|
|
|
|
- !UA_String_equal(&token->policyId, &anonymous_policy))
|
|
|
|
|
|
+ if(token->policyId.data && !UA_String_equal(&token->policyId, &anonymous_policy))
|
|
return UA_STATUSCODE_BADIDENTITYTOKENINVALID;
|
|
return UA_STATUSCODE_BADIDENTITYTOKENINVALID;
|
|
|
|
|
|
/* No userdata atm */
|
|
/* No userdata atm */
|
|
@@ -56,28 +56,26 @@ activateSession_default(const UA_NodeId *sessionId,
|
|
}
|
|
}
|
|
|
|
|
|
/* Username and password */
|
|
/* Username and password */
|
|
- if(userIdentityToken->content.decoded.type ==
|
|
|
|
- &UA_TYPES[UA_TYPES_USERNAMEIDENTITYTOKEN]) {
|
|
|
|
- const UA_UserNameIdentityToken *token =
|
|
|
|
|
|
+ if(userIdentityToken->content.decoded.type == &UA_TYPES[UA_TYPES_USERNAMEIDENTITYTOKEN]) {
|
|
|
|
+ const UA_UserNameIdentityToken *userToken =
|
|
(UA_UserNameIdentityToken*)userIdentityToken->content.decoded.data;
|
|
(UA_UserNameIdentityToken*)userIdentityToken->content.decoded.data;
|
|
- if(!UA_String_equal(&token->policyId, &username_policy))
|
|
|
|
|
|
+
|
|
|
|
+ if(!UA_String_equal(&userToken->policyId, &username_policy))
|
|
return UA_STATUSCODE_BADIDENTITYTOKENINVALID;
|
|
return UA_STATUSCODE_BADIDENTITYTOKENINVALID;
|
|
|
|
|
|
/* TODO: Support encrypted username/password over unencrypted SecureChannels */
|
|
/* TODO: Support encrypted username/password over unencrypted SecureChannels */
|
|
- if(token->encryptionAlgorithm.length > 0)
|
|
|
|
|
|
+ if(userToken->encryptionAlgorithm.length > 0)
|
|
return UA_STATUSCODE_BADIDENTITYTOKENINVALID;
|
|
return UA_STATUSCODE_BADIDENTITYTOKENINVALID;
|
|
|
|
|
|
/* Empty username and password */
|
|
/* Empty username and password */
|
|
- if(token->userName.length == 0 && token->password.length == 0)
|
|
|
|
|
|
+ if(userToken->userName.length == 0 && userToken->password.length == 0)
|
|
return UA_STATUSCODE_BADIDENTITYTOKENINVALID;
|
|
return UA_STATUSCODE_BADIDENTITYTOKENINVALID;
|
|
|
|
|
|
/* Try to match username/pw */
|
|
/* Try to match username/pw */
|
|
UA_Boolean match = false;
|
|
UA_Boolean match = false;
|
|
- for(size_t i = 0; i < usernamePasswordsSize; i++) {
|
|
|
|
- const UA_String *user = &usernamePasswords[i].username;
|
|
|
|
- const UA_String *pw = &usernamePasswords[i].password;
|
|
|
|
- if(UA_String_equal(&token->userName, user) &&
|
|
|
|
- UA_String_equal(&token->password, pw)) {
|
|
|
|
|
|
+ for(size_t i = 0; i < context->usernamePasswordLoginSize; i++) {
|
|
|
|
+ if(UA_String_equal(&userToken->userName, &context->usernamePasswordLogin[i].username) &&
|
|
|
|
+ UA_String_equal(&userToken->password, &context->usernamePasswordLogin[i].password)) {
|
|
match = true;
|
|
match = true;
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
@@ -94,56 +92,148 @@ activateSession_default(const UA_NodeId *sessionId,
|
|
return UA_STATUSCODE_BADIDENTITYTOKENINVALID;
|
|
return UA_STATUSCODE_BADIDENTITYTOKENINVALID;
|
|
}
|
|
}
|
|
|
|
|
|
-void
|
|
|
|
-closeSession_default(const UA_NodeId *sessionId, void *sessionContext) {
|
|
|
|
|
|
+static void
|
|
|
|
+closeSession_default(UA_Server *server, UA_AccessControl *ac,
|
|
|
|
+ const UA_NodeId *sessionId, void *sessionContext) {
|
|
/* no context to clean up */
|
|
/* no context to clean up */
|
|
}
|
|
}
|
|
|
|
|
|
-UA_UInt32
|
|
|
|
-getUserRightsMask_default(const UA_NodeId *sessionId, void *sessionContext,
|
|
|
|
|
|
+static UA_UInt32
|
|
|
|
+getUserRightsMask_default(UA_Server *server, UA_AccessControl *ac,
|
|
|
|
+ const UA_NodeId *sessionId, void *sessionContext,
|
|
const UA_NodeId *nodeId, void *nodeContext) {
|
|
const UA_NodeId *nodeId, void *nodeContext) {
|
|
return 0xFFFFFFFF;
|
|
return 0xFFFFFFFF;
|
|
}
|
|
}
|
|
|
|
|
|
-UA_Byte
|
|
|
|
-getUserAccessLevel_default(const UA_NodeId *sessionId, void *sessionContext,
|
|
|
|
|
|
+static UA_Byte
|
|
|
|
+getUserAccessLevel_default(UA_Server *server, UA_AccessControl *ac,
|
|
|
|
+ const UA_NodeId *sessionId, void *sessionContext,
|
|
const UA_NodeId *nodeId, void *nodeContext) {
|
|
const UA_NodeId *nodeId, void *nodeContext) {
|
|
return 0xFF;
|
|
return 0xFF;
|
|
}
|
|
}
|
|
|
|
|
|
-UA_Boolean
|
|
|
|
-getUserExecutable_default(const UA_NodeId *sessionId, void *sessionContext,
|
|
|
|
|
|
+static UA_Boolean
|
|
|
|
+getUserExecutable_default(UA_Server *server, UA_AccessControl *ac,
|
|
|
|
+ const UA_NodeId *sessionId, void *sessionContext,
|
|
const UA_NodeId *methodId, void *methodContext) {
|
|
const UA_NodeId *methodId, void *methodContext) {
|
|
return true;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
-UA_Boolean
|
|
|
|
-getUserExecutableOnObject_default(const UA_NodeId *sessionId, void *sessionContext,
|
|
|
|
|
|
+static UA_Boolean
|
|
|
|
+getUserExecutableOnObject_default(UA_Server *server, UA_AccessControl *ac,
|
|
|
|
+ const UA_NodeId *sessionId, void *sessionContext,
|
|
const UA_NodeId *methodId, void *methodContext,
|
|
const UA_NodeId *methodId, void *methodContext,
|
|
const UA_NodeId *objectId, void *objectContext) {
|
|
const UA_NodeId *objectId, void *objectContext) {
|
|
return true;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
-UA_Boolean
|
|
|
|
-allowAddNode_default(const UA_NodeId *sessionId, void *sessionContext,
|
|
|
|
|
|
+static UA_Boolean
|
|
|
|
+allowAddNode_default(UA_Server *server, UA_AccessControl *ac,
|
|
|
|
+ const UA_NodeId *sessionId, void *sessionContext,
|
|
const UA_AddNodesItem *item) {
|
|
const UA_AddNodesItem *item) {
|
|
return true;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
-UA_Boolean
|
|
|
|
-allowAddReference_default(const UA_NodeId *sessionId, void *sessionContext,
|
|
|
|
|
|
+static UA_Boolean
|
|
|
|
+allowAddReference_default(UA_Server *server, UA_AccessControl *ac,
|
|
|
|
+ const UA_NodeId *sessionId, void *sessionContext,
|
|
const UA_AddReferencesItem *item) {
|
|
const UA_AddReferencesItem *item) {
|
|
return true;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
-UA_Boolean
|
|
|
|
-allowDeleteNode_default(const UA_NodeId *sessionId, void *sessionContext,
|
|
|
|
|
|
+static UA_Boolean
|
|
|
|
+allowDeleteNode_default(UA_Server *server, UA_AccessControl *ac,
|
|
|
|
+ const UA_NodeId *sessionId, void *sessionContext,
|
|
const UA_DeleteNodesItem *item) {
|
|
const UA_DeleteNodesItem *item) {
|
|
return true;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
-UA_Boolean
|
|
|
|
-allowDeleteReference_default(const UA_NodeId *sessionId, void *sessionContext,
|
|
|
|
|
|
+static UA_Boolean
|
|
|
|
+allowDeleteReference_default(UA_Server *server, UA_AccessControl *ac,
|
|
|
|
+ const UA_NodeId *sessionId, void *sessionContext,
|
|
const UA_DeleteReferencesItem *item) {
|
|
const UA_DeleteReferencesItem *item) {
|
|
return true;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+/***************************************/
|
|
|
|
+/* Create Delete Access Control Plugin */
|
|
|
|
+/***************************************/
|
|
|
|
+
|
|
|
|
+static void deleteMembers_default(UA_AccessControl *ac) {
|
|
|
|
+ UA_Array_delete((void*)(uintptr_t)ac->userTokenPolicies,
|
|
|
|
+ ac->userTokenPoliciesSize,
|
|
|
|
+ &UA_TYPES[UA_TYPES_USERTOKENPOLICY]);
|
|
|
|
+
|
|
|
|
+ AccessControlContext *context = (AccessControlContext*)ac->context;
|
|
|
|
+ for(size_t i = 0; i < context->usernamePasswordLoginSize; i++) {
|
|
|
|
+ UA_String_deleteMembers(&context->usernamePasswordLogin[i].username);
|
|
|
|
+ UA_String_deleteMembers(&context->usernamePasswordLogin[i].password);
|
|
|
|
+ }
|
|
|
|
+ if(context->usernamePasswordLoginSize > 0)
|
|
|
|
+ UA_free(context->usernamePasswordLogin);
|
|
|
|
+ UA_free(ac->context);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+UA_AccessControl
|
|
|
|
+UA_AccessControl_default(UA_Boolean allowAnonymous, size_t usernamePasswordLoginSize,
|
|
|
|
+ const UA_UsernamePasswordLogin *usernamePasswordLogin) {
|
|
|
|
+ AccessControlContext *context = (AccessControlContext*)
|
|
|
|
+ UA_malloc(sizeof(AccessControlContext));
|
|
|
|
+
|
|
|
|
+ UA_AccessControl ac;
|
|
|
|
+ memset(&ac, 0, sizeof(ac));
|
|
|
|
+ ac.context = context;
|
|
|
|
+ ac.deleteMembers = deleteMembers_default;
|
|
|
|
+ ac.activateSession = activateSession_default;
|
|
|
|
+ ac.closeSession = closeSession_default;
|
|
|
|
+ ac.getUserRightsMask = getUserRightsMask_default;
|
|
|
|
+ ac.getUserAccessLevel = getUserAccessLevel_default;
|
|
|
|
+ ac.getUserExecutable = getUserExecutable_default;
|
|
|
|
+ ac.getUserExecutableOnObject = getUserExecutableOnObject_default;
|
|
|
|
+ ac.allowAddNode = allowAddNode_default;
|
|
|
|
+ ac.allowAddReference = allowAddReference_default;
|
|
|
|
+ ac.allowDeleteNode = allowDeleteNode_default;
|
|
|
|
+ ac.allowDeleteReference = allowDeleteReference_default;
|
|
|
|
+
|
|
|
|
+ /* Allow anonymous? */
|
|
|
|
+ context->allowAnonymous = allowAnonymous;
|
|
|
|
+
|
|
|
|
+ /* Copy username/password to the access control plugin */
|
|
|
|
+ if(usernamePasswordLoginSize > 0) {
|
|
|
|
+ context->usernamePasswordLogin = (UA_UsernamePasswordLogin*)
|
|
|
|
+ UA_malloc(usernamePasswordLoginSize * sizeof(UA_UsernamePasswordLogin));
|
|
|
|
+ if(!context->usernamePasswordLogin)
|
|
|
|
+ return ac;
|
|
|
|
+ context->usernamePasswordLoginSize = usernamePasswordLoginSize;
|
|
|
|
+ for(size_t i = 0; i < usernamePasswordLoginSize; i++) {
|
|
|
|
+ UA_String_copy(&usernamePasswordLogin[i].username, &context->usernamePasswordLogin[i].username);
|
|
|
|
+ UA_String_copy(&usernamePasswordLogin[i].password, &context->usernamePasswordLogin[i].password);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* Set the allowed policies */
|
|
|
|
+ size_t policies = 0;
|
|
|
|
+ if(allowAnonymous)
|
|
|
|
+ policies++;
|
|
|
|
+ if(usernamePasswordLoginSize > 0)
|
|
|
|
+ policies++;
|
|
|
|
+ ac.userTokenPoliciesSize = 0;
|
|
|
|
+ ac.userTokenPolicies = (UA_UserTokenPolicy *)
|
|
|
|
+ UA_Array_new(policies, &UA_TYPES[UA_TYPES_USERTOKENPOLICY]);
|
|
|
|
+ if(!ac.userTokenPolicies)
|
|
|
|
+ return ac;
|
|
|
|
+ ac.userTokenPoliciesSize = policies;
|
|
|
|
+
|
|
|
|
+ policies = 0;
|
|
|
|
+ if(allowAnonymous) {
|
|
|
|
+ ac.userTokenPolicies[policies].tokenType = UA_USERTOKENTYPE_ANONYMOUS;
|
|
|
|
+ ac.userTokenPolicies[policies].policyId = UA_STRING_ALLOC(ANONYMOUS_POLICY);
|
|
|
|
+ policies++;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if(usernamePasswordLoginSize > 0) {
|
|
|
|
+ ac.userTokenPolicies[policies].tokenType = UA_USERTOKENTYPE_USERNAME;
|
|
|
|
+ ac.userTokenPolicies[policies].policyId = UA_STRING_ALLOC(USERNAME_POLICY);
|
|
|
|
+ }
|
|
|
|
+ return ac;
|
|
|
|
+}
|