学习open62541 --- [41] 给不同用户添加指定权限

这篇文章里,讲述了如何使用用户名和密码连接OPC UA Server,例子中添加了2个用户,权限都是一样的。本文讲述如何给不同用户添加指定权限。


一 Client代码

client端代码如下,和之前的文章中一样,


#include <stdlib.h>

#include "open62541.h"

int main(void) 
{
    
    
	UA_Client *client = UA_Client_new();
	UA_ClientConfig_setDefault(UA_Client_getConfig(client));

	UA_StatusCode retval = UA_Client_connect_username(client, "opc.tcp://localhost:4840", "paula", "paula123");
	if (retval != UA_STATUSCODE_GOOD) 
	{
    
    
		UA_Client_delete(client);
		return EXIT_FAILURE;
	}

	UA_NodeId newVariableIdRequest = UA_NODEID_NUMERIC(1, 1001);
	UA_NodeId newVariableId = UA_NODEID_NULL;

	UA_VariableAttributes newVariableAttributes = UA_VariableAttributes_default;

	newVariableAttributes.accessLevel = UA_ACCESSLEVELMASK_READ;
	newVariableAttributes.description = UA_LOCALIZEDTEXT_ALLOC("en-US", "NewVariable desc");
	newVariableAttributes.displayName = UA_LOCALIZEDTEXT_ALLOC("en-US", "NewVariable");
	newVariableAttributes.dataType = UA_TYPES[UA_TYPES_UINT32].typeId;
	UA_UInt32 value = 50;
	UA_Variant_setScalarCopy(&newVariableAttributes.value, &value, &UA_TYPES[UA_TYPES_UINT32]);

	UA_StatusCode retCode;

	retCode = UA_Client_addVariableNode(client, newVariableIdRequest,
		UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
		UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
		UA_QUALIFIEDNAME(1, "newVariable"),
		UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
		newVariableAttributes, &newVariableId);

	printf("addVariable returned: %s\n", UA_StatusCode_name(retCode));

	UA_ExpandedNodeId extNodeId = UA_EXPANDEDNODEID_NUMERIC(0, 0);
	extNodeId.nodeId = newVariableId;

	retCode = UA_Client_addReference(client, UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
		UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), UA_TRUE,
		UA_STRING_NULL, extNodeId, UA_NODECLASS_VARIABLE);

	printf("addReference returned: %s\n", UA_StatusCode_name(retCode));

	retCode = UA_Client_deleteReference(client, UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
		UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES), UA_TRUE, extNodeId,
		UA_TRUE);

	printf("deleteReference returned: %s\n", UA_StatusCode_name(retCode));

	retCode = UA_Client_deleteNode(client, newVariableId, UA_TRUE);
	printf("deleteNode returned: %s\n", UA_StatusCode_name(retCode));

	/* Clean up */
	UA_VariableAttributes_clear(&newVariableAttributes);
	UA_Client_delete(client); /* Disconnects the client internally */
	return EXIT_SUCCESS;
}

代码功能是使用用户名和密码连接Server,然后测试是否可以添加Node,添加Reference,删除Reference以及删除Node。用户名:paula,密码:paula123


二 Server指定权限原理

Server代码是基于之前文章中的代码,进行修改。

首先定义一个结构体,表示用户权限,

typedef struct {
    
    
	UA_String userName;
	UA_Boolean fAllowAddNode; // 允许添加节点
	UA_Boolean fAllowAddReference; // 允许添加Reference
	UA_Boolean fAllowDeleteNode; // 允许删除节点
	UA_Boolean fAllowDeleteReference; // 允许删除Reference
} UserRight_t;

然后使用这个结构体创建一个数组来表示用户peter和paula的权限,

static UserRight_t userRight[2] = {
    
    
	{
    
     UA_STRING_STATIC("peter"), UA_TRUE, UA_TRUE, UA_TRUE, UA_TRUE },
	{
    
     UA_STRING_STATIC("paula"), UA_TRUE, UA_TRUE, UA_FALSE, UA_FALSE }
};

在初始化Server的access control时,我们调用了UA_AccessControl_default(),这个会设置默认函数,具体可以自行查阅源码。

需要使用我们自定义的函数来替代其中一些默认函数,第一个是activateSession,其默认函数是activateSession_default(),
自定义activateSession函数如下,

static UA_StatusCode
activateSession(UA_Server *server, UA_AccessControl *ac,
	const UA_EndpointDescription *endpointDescription,
	const UA_ByteString *secureChannelRemoteCertificate,
	const UA_NodeId *sessionId,
	const UA_ExtensionObject *userIdentityToken,
	void **sessionContext) 
{
    
    
	AccessControlContext * context = (AccessControlContext*)ac->context;

	/* The empty token is interpreted as anonymous */
	if (userIdentityToken->encoding == UA_EXTENSIONOBJECT_ENCODED_NOBODY) {
    
    
		if (!context->allowAnonymous)
			return UA_STATUSCODE_BADIDENTITYTOKENINVALID;

		/* No userdata atm */
		*sessionContext = NULL;
		return UA_STATUSCODE_GOOD;
	}

	/* Could the token be decoded? */
	if (userIdentityToken->encoding < UA_EXTENSIONOBJECT_DECODED)
		return UA_STATUSCODE_BADIDENTITYTOKENINVALID;

	/* Anonymous login */
	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
		* policyId. This is not compliant. For compatibility, assume that empty
		* policyId == ANONYMOUS_POLICY */
		if (token->policyId.data && !UA_String_equal(&token->policyId, &anonymous_policy))
			return UA_STATUSCODE_BADIDENTITYTOKENINVALID;

		/* No userdata atm */
		*sessionContext = NULL;
		return UA_STATUSCODE_GOOD;
	}

	/* Username and password */
	if (userIdentityToken->content.decoded.type == &UA_TYPES[UA_TYPES_USERNAMEIDENTITYTOKEN]) {
    
    
		const UA_UserNameIdentityToken *userToken =
			(UA_UserNameIdentityToken*)userIdentityToken->content.decoded.data;

		if (!UA_String_equal(&userToken->policyId, &username_policy))
			return UA_STATUSCODE_BADIDENTITYTOKENINVALID;

		/* The userToken has been decrypted by the server before forwarding
		* it to the plugin. This information can be used here. */
		/* if(userToken->encryptionAlgorithm.length > 0) {} */

		/* Empty username and password */
		if (userToken->userName.length == 0 && userToken->password.length == 0)
			return UA_STATUSCODE_BADIDENTITYTOKENINVALID;

		/* Try to match username/pw */
		UA_Boolean match = false;
		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;
				break;
			}
		}
		if (!match)
			return UA_STATUSCODE_BADUSERACCESSDENIED;

		/* 关键代码 */
		UA_UsernamePasswordLogin* currentUsernamePasswordLogin = 
			(UA_UsernamePasswordLogin*)UA_malloc(sizeof(UA_UsernamePasswordLogin));
		UA_String_copy(&(userToken->userName), &(currentUsernamePasswordLogin->username));
		UA_String_copy(&(userToken->password), &(currentUsernamePasswordLogin->password));

		*sessionContext = currentUsernamePasswordLogin;
		return UA_STATUSCODE_GOOD;
	}

	/* Unsupported token type */
	return UA_STATUSCODE_BADIDENTITYTOKENINVALID;
}

代码内容和activateSession_default()基本是一样的,只是在最后把访问的用户名和密码存到动态分配的内存里,然后把该内存的地址传给sessionContext ,这样在后续的节点管理函数里就可以知道是哪个用户在调用,这个非常重要!

然后就是自定义添加Node,添加Reference,删除Reference以及删除Node的函数,如下,

const UA_UInt32 UserCount = sizeof(userRight) / sizeof(UserRight_t);

static UA_Boolean
allowAddNode(UA_Server *server, UA_AccessControl *ac,
	const UA_NodeId *sessionId, void *sessionContext,
	const UA_AddNodesItem *item) 
{
    
    
	printf("Called allowAddNode\n");

	UA_UsernamePasswordLogin* currentUsernamePasswordLogin =
		(UA_UsernamePasswordLogin*)sessionContext;

	for (UA_UInt32 i = 0; i < UserCount; ++i)
	{
    
    
		if (UA_String_equal(&(currentUsernamePasswordLogin->username), &(userRight[i].userName)))
		{
    
    
			return userRight[i].fAllowAddNode;
		}
	}

	return UA_TRUE;
}

static UA_Boolean
allowAddReference(UA_Server *server, UA_AccessControl *ac,
	const UA_NodeId *sessionId, void *sessionContext,
	const UA_AddReferencesItem *item) 
{
    
    
	printf("Called allowAddReference\n");

	UA_UsernamePasswordLogin* currentUsernamePasswordLogin =
		(UA_UsernamePasswordLogin*)sessionContext;

	for (UA_UInt32 i = 0; i < UserCount; ++i)
	{
    
    
		if (UA_String_equal(&(currentUsernamePasswordLogin->username), &(userRight[i].userName)))
		{
    
    
			return userRight[i].fAllowAddReference;
		}
	}

	return UA_TRUE;
}

static UA_Boolean
allowDeleteNode(UA_Server *server, UA_AccessControl *ac,
	const UA_NodeId *sessionId, void *sessionContext,
	const UA_DeleteNodesItem *item) 
{
    
    
	printf("Called allowDeleteNode\n");

	UA_UsernamePasswordLogin* currentUsernamePasswordLogin =
		(UA_UsernamePasswordLogin*)sessionContext;

	for (UA_UInt32 i = 0; i < UserCount; ++i)
	{
    
    
		if (UA_String_equal(&(currentUsernamePasswordLogin->username), &(userRight[i].userName)))
		{
    
    
			return userRight[i].fAllowDeleteNode;
		}
	}

	return UA_TRUE;
}

static UA_Boolean
allowDeleteReference(UA_Server *server, UA_AccessControl *ac,
	const UA_NodeId *sessionId, void *sessionContext,
	const UA_DeleteReferencesItem *item) 
{
    
    
	printf("Called allowDeleteReference\n");

	UA_UsernamePasswordLogin* currentUsernamePasswordLogin =
		(UA_UsernamePasswordLogin*)sessionContext;

	for (UA_UInt32 i = 0; i < UserCount; ++i)
	{
    
    
		if (UA_String_equal(&(currentUsernamePasswordLogin->username), &(userRight[i].userName)))
		{
    
    
			return userRight[i].fAllowDeleteReference;
		}
	}

	return UA_TRUE;
}

这4个函数用于判断用户是否拥有对应的节点管理权限,其内容也很简单,就是从sessionContext里提取用户名,然后查找用户名,最后返回该用户名的权限。

最后要做的就是把这些自定义函数覆盖掉原先的默认函数,

	// Set accessControl functions for activae session
	config->accessControl.activateSession = activateSession;

	/* Set accessControl functions for nodeManagement */
	config->accessControl.allowAddNode = allowAddNode;
	config->accessControl.allowAddReference = allowAddReference;
	config->accessControl.allowDeleteNode = allowDeleteNode;
	config->accessControl.allowDeleteReference = allowDeleteReference;

整体源码在第四节


三 测试

测试很简单,先编译Server和Client,然后先运行Server再运行Client,由于Client使用的用户是paula,而paula在Server这边没有删除Node和删除Reference的权限,所以会提示没权限,如下,
在这里插入图片描述
和设置的权限表现一致。


四 Server整体源码

整体源码如下,

#include <signal.h>
#include <stdlib.h>
#include <stdio.h>

#include "open62541.h"



typedef struct {
    
    
	UA_Boolean allowAnonymous;
	size_t usernamePasswordLoginSize;
	UA_UsernamePasswordLogin *usernamePasswordLogin;
} AccessControlContext;

typedef struct {
    
    
	UA_String userName;
	UA_Boolean fAllowAddNode;
	UA_Boolean fAllowAddReference;
	UA_Boolean fAllowDeleteNode;
	UA_Boolean fAllowDeleteReference;
} UserRight_t;

#define ANONYMOUS_POLICY "open62541-anonymous-policy"
#define USERNAME_POLICY "open62541-username-policy"
const UA_String anonymous_policy = UA_STRING_STATIC(ANONYMOUS_POLICY);
const UA_String username_policy = UA_STRING_STATIC(USERNAME_POLICY);


static UA_UsernamePasswordLogin logins[2] = {
    
    
	{
    
     UA_STRING_STATIC("peter"), UA_STRING_STATIC("peter123") },
	{
    
     UA_STRING_STATIC("paula"), UA_STRING_STATIC("paula123") }
};


static UserRight_t userRight[2] = {
    
    
	{
    
     UA_STRING_STATIC("peter"), UA_TRUE, UA_TRUE, UA_TRUE, UA_TRUE },
	{
    
     UA_STRING_STATIC("paula"), UA_TRUE, UA_TRUE, UA_FALSE, UA_FALSE }
};

const UA_UInt32 UserCount = sizeof(userRight) / sizeof(UserRight_t);

static UA_Boolean
allowAddNode(UA_Server *server, UA_AccessControl *ac,
	const UA_NodeId *sessionId, void *sessionContext,
	const UA_AddNodesItem *item) 
{
    
    
	printf("Called allowAddNode\n");

	UA_UsernamePasswordLogin* currentUsernamePasswordLogin =
		(UA_UsernamePasswordLogin*)sessionContext;

	for (UA_UInt32 i = 0; i < UserCount; ++i)
	{
    
    
		if (UA_String_equal(&(currentUsernamePasswordLogin->username), &(userRight[i].userName)))
		{
    
    
			return userRight[i].fAllowAddNode;
		}
	}

	return UA_TRUE;
}

static UA_Boolean
allowAddReference(UA_Server *server, UA_AccessControl *ac,
	const UA_NodeId *sessionId, void *sessionContext,
	const UA_AddReferencesItem *item) 
{
    
    
	printf("Called allowAddReference\n");

	UA_UsernamePasswordLogin* currentUsernamePasswordLogin =
		(UA_UsernamePasswordLogin*)sessionContext;

	for (UA_UInt32 i = 0; i < UserCount; ++i)
	{
    
    
		if (UA_String_equal(&(currentUsernamePasswordLogin->username), &(userRight[i].userName)))
		{
    
    
			return userRight[i].fAllowAddReference;
		}
	}

	return UA_TRUE;
}

static UA_Boolean
allowDeleteNode(UA_Server *server, UA_AccessControl *ac,
	const UA_NodeId *sessionId, void *sessionContext,
	const UA_DeleteNodesItem *item) 
{
    
    
	printf("Called allowDeleteNode\n");

	UA_UsernamePasswordLogin* currentUsernamePasswordLogin =
		(UA_UsernamePasswordLogin*)sessionContext;

	for (UA_UInt32 i = 0; i < UserCount; ++i)
	{
    
    
		if (UA_String_equal(&(currentUsernamePasswordLogin->username), &(userRight[i].userName)))
		{
    
    
			return userRight[i].fAllowDeleteNode;
		}
	}

	return UA_TRUE;
}

static UA_Boolean
allowDeleteReference(UA_Server *server, UA_AccessControl *ac,
	const UA_NodeId *sessionId, void *sessionContext,
	const UA_DeleteReferencesItem *item) 
{
    
    
	printf("Called allowDeleteReference\n");

	UA_UsernamePasswordLogin* currentUsernamePasswordLogin =
		(UA_UsernamePasswordLogin*)sessionContext;

	for (UA_UInt32 i = 0; i < UserCount; ++i)
	{
    
    
		if (UA_String_equal(&(currentUsernamePasswordLogin->username), &(userRight[i].userName)))
		{
    
    
			return userRight[i].fAllowDeleteReference;
		}
	}

	return UA_TRUE;
}




static UA_StatusCode
activateSession(UA_Server *server, UA_AccessControl *ac,
	const UA_EndpointDescription *endpointDescription,
	const UA_ByteString *secureChannelRemoteCertificate,
	const UA_NodeId *sessionId,
	const UA_ExtensionObject *userIdentityToken,
	void **sessionContext) 
{
    
    
	AccessControlContext * context = (AccessControlContext*)ac->context;

	/* The empty token is interpreted as anonymous */
	if (userIdentityToken->encoding == UA_EXTENSIONOBJECT_ENCODED_NOBODY) {
    
    
		if (!context->allowAnonymous)
			return UA_STATUSCODE_BADIDENTITYTOKENINVALID;

		/* No userdata atm */
		*sessionContext = NULL;
		return UA_STATUSCODE_GOOD;
	}

	/* Could the token be decoded? */
	if (userIdentityToken->encoding < UA_EXTENSIONOBJECT_DECODED)
		return UA_STATUSCODE_BADIDENTITYTOKENINVALID;

	/* Anonymous login */
	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
		* policyId. This is not compliant. For compatibility, assume that empty
		* policyId == ANONYMOUS_POLICY */
		if (token->policyId.data && !UA_String_equal(&token->policyId, &anonymous_policy))
			return UA_STATUSCODE_BADIDENTITYTOKENINVALID;

		/* No userdata atm */
		*sessionContext = NULL;
		return UA_STATUSCODE_GOOD;
	}

	/* Username and password */
	if (userIdentityToken->content.decoded.type == &UA_TYPES[UA_TYPES_USERNAMEIDENTITYTOKEN]) {
    
    
		const UA_UserNameIdentityToken *userToken =
			(UA_UserNameIdentityToken*)userIdentityToken->content.decoded.data;

		if (!UA_String_equal(&userToken->policyId, &username_policy))
			return UA_STATUSCODE_BADIDENTITYTOKENINVALID;

		/* The userToken has been decrypted by the server before forwarding
		* it to the plugin. This information can be used here. */
		/* if(userToken->encryptionAlgorithm.length > 0) {} */

		/* Empty username and password */
		if (userToken->userName.length == 0 && userToken->password.length == 0)
			return UA_STATUSCODE_BADIDENTITYTOKENINVALID;

		/* Try to match username/pw */
		UA_Boolean match = false;
		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;
				break;
			}
		}
		if (!match)
			return UA_STATUSCODE_BADUSERACCESSDENIED;

		/* No userdata atm */
		UA_UsernamePasswordLogin* currentUsernamePasswordLogin = 
			(UA_UsernamePasswordLogin*)UA_malloc(sizeof(UA_UsernamePasswordLogin));
		UA_String_copy(&(userToken->userName), &(currentUsernamePasswordLogin->username));
		UA_String_copy(&(userToken->password), &(currentUsernamePasswordLogin->password));

		*sessionContext = currentUsernamePasswordLogin;
		return UA_STATUSCODE_GOOD;
	}

	/* Unsupported token type */
	return UA_STATUSCODE_BADIDENTITYTOKENINVALID;
}



UA_Boolean running = true;
static void stopHandler(int sign) 
{
    
    
	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
	running = false;
}



int main(void) 
{
    
    
	signal(SIGINT, stopHandler);
	signal(SIGTERM, stopHandler);

	UA_Server *server = UA_Server_new();
	UA_ServerConfig *config = UA_Server_getConfig(server);
	UA_ServerConfig_setDefault(config);

	/* Disable anonymous logins, enable two user/password logins */
	config->accessControl.deleteMembers(&config->accessControl);
	UA_StatusCode retval = UA_AccessControl_default(config, false,
		&config->securityPolicies[config->securityPoliciesSize - 1].policyUri, 2, logins);
	if (retval != UA_STATUSCODE_GOOD)
		goto cleanup;

	// Set accessControl functions for activae session
	config->accessControl.activateSession = activateSession;

	/* Set accessControl functions for nodeManagement */
	config->accessControl.allowAddNode = allowAddNode;
	config->accessControl.allowAddReference = allowAddReference;
	config->accessControl.allowDeleteNode = allowDeleteNode;
	config->accessControl.allowDeleteReference = allowDeleteReference;


	retval = UA_Server_run(server, &running);

cleanup:
	UA_Server_delete(server);
	return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}

需要注意的是如果用的是1.0之后的版本,第235行需要替换成下面的语句,api发生了变化

config->accessControl.clear(&config->accessControl);

五 总结

本文主要讲述了如何给不同用户指定不同的权限,主要是修改默认的access control函数,知道原理就比较简单了,最核心的是要获取运行时的用户名。

如果有写的不对的地方,希望能留言指正,谢谢阅读。

猜你喜欢

转载自blog.csdn.net/whahu1989/article/details/111566832
41
今日推荐