Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Server access control #2984

Open
3 of 7 tasks
ghost opened this issue Aug 9, 2019 · 15 comments
Open
3 of 7 tasks

Server access control #2984

ghost opened this issue Aug 9, 2019 · 15 comments

Comments

@ghost
Copy link

ghost commented Aug 9, 2019

Hello,

i am running a server on STM32F767 nucleo using FreeRTOS and LwIP and every thing is ok.
i add 2 users according to the example for the server_access_control:

https://github.com/open62541/open62541/blob/master/examples/access_control/server_access_control.c

my question is about to give each user permissions.
it is about like the first user can just read the Variables and can't call any method
the second user can read/write the variables and call methods.

is there any explanation about it or any example?

thnx

Checklist

Please provide the following information:

  • open62541 Version (release number or git tag): FixFreeRTOS Branch
  • Other OPC UA SDKs used (client or server): UAExpert Client
  • Operating system: FreeRTOS
  • Logs (with UA_LOGLEVEL set as low as necessary) attached
  • Wireshark network dump attached
  • Self-contained code example attached
  • Critical issue
@schroeder-
Copy link
Contributor

You have to override the config->accessControl->activateSession there you have to set a specific sessionContext so you can distinguish between user. Or you use a struct with all access related values as sessionContext.
As starting point you can use activateSession_default in https://github.com/open62541/open62541/blob/master/plugins/ua_accesscontrol_default.c and set a sessionContext.

@ghost
Copy link
Author

ghost commented Aug 9, 2019

@schroeder- thnx for ur answer, but i still don't understand where i should use the specified sessionContext for each user.

/* 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;
            }
}

i can here distinguish between the users but as i said, i don't know how to use the sessionContext after that and where should i use it
the server runs bevore i can distinguish between both of them

@schroeder-
Copy link
Contributor

schroeder- commented Aug 9, 2019

Best way is to write a complete new access_control plugin and extend the usernamePasswordLogin with a struct for accessControl settings.

But as easy way you can use set *sessionContext = i+1;
on match.
An then in the access functions use like:

static UA_Boolean
allowDeleteNode(UA_Server *server, UA_AccessControl *ac,
                const UA_NodeId *sessionId, void *sessionContext,
                const UA_DeleteNodesItem *item) {
    printf("Called allowDeleteNode\n");
    if(sessionContext == 1){
         return UA_FALSE;
    } else if(sessionContext == 2)
         return UA_TRUE;
    }
    return UA_FALSE;
}

@ghost
Copy link
Author

ghost commented Aug 9, 2019

thnx once again, but that was not what i want to do, or maybe i missed something
i just want that the first user just read the variables that i added to the server and the seconde one read/write the the same variables

@schroeder-
Copy link
Contributor

use getUserAccessLevel function like:

	UA_Byte myGetUserAccessLevel(UA_Server *server, UA_AccessControl *ac,
		const UA_NodeId *sessionId, void *sessionContext,
		const UA_NodeId *nodeId, void *nodeContext) {
		if (nodeId->identifierType == UA_NODEIDTYPE_NUMERIC && nodeId->namespaceIndex == 4) {
			auto id = nodeId->identifier.numeric;
			if (id >= 50000 && id <= 60000 && sessionContext != 2) {
				return UA_ACCESSLEVELMASK_READ; 
			}

		}
		return UA_ACCESSLEVELMASK_WRITE | UA_ACCESSLEVELMASK_READ ;
	}

...

config->accessControl->getUserAccessLevel = myGetUserAccessLevel;

@ghost
Copy link
Author

ghost commented Aug 9, 2019

@schroeder- i tried it and it don't work, i don't know why

here is the variable that i add:

  int x = 200;
  UA_VariableAttributes nattr = UA_VariableAttributes_default;
  UA_Variant_setScalar(&nattr.value, &x, &UA_TYPES[UA_TYPES_INT32]);
  nattr.accessLevel = UA_ACCESSLEVELMASK_READ |  UA_ACCESSLEVELMASK_WRITE;
  nattr.userAccessLevel = UA_ACCESSLEVELMASK_READ |  UA_ACCESSLEVELMASK_WRITE;
  nattr.dataType = UA_TYPES[UA_TYPES_INT32].typeId;
  nattr.displayName = UA_LOCALIZEDTEXT("en-US", "Value_1");

  UA_NodeId myIntegerNodeId = UA_NODEID_NUMERIC(1, 12000);
  UA_QualifiedName myIntegerName = UA_QUALIFIEDNAME(1, "Value_1");
  UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
  UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);

  UA_Server_addVariableNode(server, myIntegerNodeId, parentNodeId,
                            parentReferenceNodeId , myIntegerName,
                            UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), nattr, NULL, NULL);

and i checked the matching in activateSession_default:

       /* 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;
               *(size_t*)sessionContext = i ; // zero or one
               break;
           }
       }

and in the getUserAccessLevel_default i did as u said:

static UA_Byte
getUserAccessLevel_default(UA_Server *server, UA_AccessControl *ac,
                          const UA_NodeId *sessionId, void *sessionContext,
                          const UA_NodeId *nodeId, void *nodeContext) {

 if (nodeId->identifierType == UA_NODEIDTYPE_NUMERIC && nodeId->namespaceIndex == 1)
 {
   UA_UInt32 id = nodeId->identifier.numeric;
   if((id == 12000) && (*(int*)sessionContext == 1)) //User1 can write and read but User0 not
   {
     return (UA_ACCESSLEVELMASK_WRITE | UA_ACCESSLEVELMASK_READ) ;
   }
 }

 return UA_ACCESSLEVELMASK_READ ;
}

and it doesn't work, in both situation "User0 or User1" i get in UAexpert BadUserAccessDenied.
should i change anything more?

@ghost
Copy link
Author

ghost commented Aug 12, 2019

@schroeder- i did something wrong as i changed the getUserAccessLevel_default, so i removed every thing and started once again as in the example, configuring the config->accesscontrol->getUserAccessLevel to a function that i wrote and it is the same, the both users cannot write the value of variable

i get a warning every time i want to connect, it is about that i don't use security policy

err1212

and if i click on ignore the client can connect to server and i can browse the server but not write the variable, do u have any idea what should i do to run it?

thnx

@jpfr
Copy link
Member

jpfr commented Aug 13, 2019

You need to look at

getUserRightsMask_default
getUserAccessLevel_default

The meaning of the returned bitfield is documented in the standard and here:
https://github.com/open62541/open62541/blob/master/include/open62541/constants.h#L56

@Jonas-Bartkowski
Copy link

Jonas-Bartkowski commented Dec 1, 2019

I'm currently trying to implement the same thing as @Feras992
I implemented the matching loop like this:

/* 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;
        *sessionContext = i+1;
        printf("Setting user context for user %s to %d!\n", &context->usernamePasswordLogin[i].username, *sessionContext);
        break;
    }
}

And my getUserAccessLevel like this:

printf("sessionContext = %p\n", sessionContext);
UA_Byte res = 0;
if (sessionContext != 1)
{
    printf("Rights set to only read!\n");
    res = UA_ACCESSLEVELMASK_READ;
}
else
{
    printf("Rights set to read & write!\n");
    res = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
}
printf("Result was %02X!\n", res);
return res;    

My problem: sessionContext always is a NULL pointer whenever the function is called.
Are there any issues with the way I'm trying to implement per-user access control?

@StengerP
Copy link

StengerP commented Dec 12, 2019

@Jonas-Bartkowski
is your issue still up-to-date?
I successfully implemented per-user access control with the tipps of this issue. I checked the sessionContext in the "getUserAccessLevel_default" function and then set the necessary ACCESSLEVELMASK.

Maybe you just did not see that at the end of the method "activateSession_default" there is following line:
/* No userdata atm */
*sessionContext = NULL;

@embbo
Copy link

embbo commented Feb 4, 2020

Hi all , How can I call these fucntion ..
/* Set accessControl functions for nodeManagement */
config->accessControl.allowAddNode = allowAddNode;
config->accessControl.allowAddReference = allowAddReference;
config->accessControl.allowDeleteNode = allowDeleteNode;
config->accessControl.allowDeleteReference = allowDeleteReference;

I am using example but , I can call these function .

@embbo
Copy link

embbo commented Apr 7, 2020

@schroeder- must we restart the server ?
what i noticed is ,, you can't update without restarting server

@jopi2016
Copy link

jopi2016 commented Jun 9, 2020

Hi,
I've been following the discussion and tried the different code samples.

However my use case is that I want to completely deny access to certain nodes for certain users. So what I've been doing is creating my own getUserAccessLevel and returning 0x00 if I want to deny access. Unfortunately I then see that the client disconnects and connects in a loop.
What is the correct way to deny access?

Is this done in allowBrowseNode?
But as far as I know it is also possible to subscribe directly to nodes without the need of browsing (eg. in UAExpert with "add custom node"). When I know the Id and the namespace of a certain node I could skip browsing. So in this case I would need another way of blocking access. Is this then done by getUserRightsMask? (I tried this but as far as I remember the debug breakpoint was not always hit in Visual Studio).

Thanks in advance
jopi2016

@EvgKlin
Copy link

EvgKlin commented Feb 26, 2021

@jpfr i set both masks like this, but i see can add or write Nodes. What is wrong?
config->accessControl.getUserAccessLevel= UA_ACCESSLEVELMASK_READ;
config->accessControl.getUserRightsMask= UA_ACCESSLEVELMASK_READ;

@EvgKlin
Copy link

EvgKlin commented Mar 1, 2021

@jopi2016 Do you find Solution?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants