-
In most organizations, there exists the need to have delegated content creators or user managers that are not full blown application or site administrators. I am suggesting that the built-in Oqtane roles be expanded to include 5 new static roles that would allow delegated content & user management for select modules. While most features of the Admin Dashboard should be left to Administrators, the following areas would benefit from being able to be delegated to others: Page Management, User Management, Profile Management, Role Management and File Management. While not as flexible as a full dynamic authorization policy could be, this small change would likely suffice for most organizations delegation requirements. The API security to allow delegated access to these modules could be made with minimal changes to the controller files (e.g. PageController.cs) by including the additional 5 static roles in the appropriate API’s. Additional changes would also be required for the module .razor screens to change the current use of “SecurityAccessLevel.Admin” to an Edit level. Current RoleNames: Additional RoleNames: I have partially implemented this in my own code for delegated User Management. See related discussion at the end of this thread: #1620 |
Beta Was this translation helpful? Give feedback.
Replies: 11 comments 7 replies
-
I am not in favor of this change as the end result would be a solution which is non-flexible as it would still be based on static roles. |
Beta Was this translation helpful? Give feedback.
-
@W6HBR At a high level, as part of the the initial creation of the framework, certain administrative functions in Oqtane were identified as requiring more privileges and were therefore made available only to users in the Administrators role. Although this approach works well for many installations, there are certainly scenarios in larger organizations where a more fine-grained approach would be beneficial for delegated administration. I have investigated this further and there are a couple of primary areas which would need to be enhanced to support this capability. UI There are a subset of user interface components which are part of the Admin Dashboard and are focused on Administrator functionality. Currently, due to the fact that they are only expected to be used by Administrators, these components utilize the SecurityAccessLevel.Admin property. This property is essentially the UI equivalent of the Authorize attribute in Controllers and prevents any user other than Administrators from accessing the component. So this property would need to be changed to View or Edit depending on the scenario. And speaking of scenarios, the actual functionality within the components would also need to be modified to ensure that every action is wrapped with the appropriate authorization validation (currently these components rely on the SecurityAccessLevel.Admin property which means that all actions are enabled). In addition, the Control Panel has a button for accessing the Admin Dashboard, and this button is only visible to Administrators. The logic would need to be modified to instead check if a user has access to any of the Admin pages. Once these changes were made it would be possible for an Administrator to modify the page settings for an Admin page and allow other users/roles to access the specific page and module. API The back-end functionality is exposed via an API and currently that API uses some security conventions which are intended to safeguard the system from inappropriate use. Specifically, many of the API methods include static role specifications for authorization (ie. a user must be in the Administrators role in order to perform an action). The API would need to implement dynamic authorization policies through a new API Management feature. This would allow the Administrator of a site the ability to specify which roles or users are allowed to access a specific API. One of the reasons I am explaining the details above is to make it clear that adding a few more static roles to the system (as suggested) does not significantly reduce the amount of work to support this capability - especially in regards to the user interface changes. |
Beta Was this translation helpful? Give feedback.
-
@sbwalker I appreciate you taking the time to look at this in more detail. I understand the work required to make these changes and I'll see if I can wrangle the time to make the changes and put them forth for a pull request. No worries if the functionality is not approved for merging as I'll likely just maintain it to use in my own deployments. |
Beta Was this translation helpful? Give feedback.
-
@W6HBR some changes were already made in the Dev branch to support Dynamic Authorization Policies and Entity-level Permissions - both of which will be important for delegation of administration functionality. Dynamic Authorization Policies - previously Oqtane only supported a static set of authorization policies which were declared in Startup using the standard .NET Core approach. For example there were PolicyNames.ViewModule and PolicyNames.EditModule policies which were declared and used to secure API methods using Oqtane's permissions model. The fact that all authorization policies had to be declared in Startup was not flexible, so an AuthorizationPolicyProvider was introduced which now allows policies to be dynamic. So if you wanted to have a Read policy for User entities you could simply specify an attribute of [Authorize(Policy = "User:Read")] on your API method and the PermissionHandler would dynamically be able to create the policy and use Oqtane's permissions model to authorize the request. Entity-level Permissions - the permissions system in Oqtane was designed with flexibility in mind so that it can be used with any entity or permission type. All of the initial implementations of the permission model in the framework had a requirement that a user needed to be authorized to access a specific Entity and EntityId. For example a user could be provided Edit rights to a specific Module instance based on its ModuleId. However after thinking about the delegated administration scenario it became clear that there is a also a need to be able to indicate that a user is authorized to access an Entity - but at a global level without any need to specify an EntityId. For example, you might want to specify that a specific custom role (ie. "RoleManager") is authorized to access ALL Roles within a Site (as Site is always used to scope permissions). So the framework was enhanced to support these entity-level permissions (ie. without a specific EntityId). |
Beta Was this translation helpful? Give feedback.
-
So this enhancement ended up being much more complicated than I expected. I made 2 attempts at an implementation but ran into issues along the way which forced me to roll them back... and it was the third attempt which ended up being the best option (which also explains why there has not been an official Oqtane release for over 2 months). In the process I ran into some scenarios which third party developers would very likely encounter in their own modules, so it was worthwhile to work through this challenge and come up with a general solution. The fundamentals of delegating access boils down to permissions... so the improvements I outlined earlier in this thread in regards to dynamic policies and entity (API) permissions were completely valid and fundamental to the eventual solution. Integrating these concepts into the client UI in a way which was intuitive and aligned with the Oqtane architecture was where the real challenge resided. In the end I was luckily able to leverage some existing extensibility points to accomplish the goal. With this new solution in place, in order to delegate administrative responsibility, a site Administrator needs to to follow the standard approach for configuring their site. First they need to create a new role in Role Management - let's call it "Delegate". Then they need to assign a user to this role. Then they need to provide View rights to the new Delegate role for the administrative functionality they want to delegate. Let's focus on User Management as an example. They would navigate to the User Management page via the Admin Dashboard, then use the Control Panel to access the Edit Page option, and in the Permissions tab they would grant View Page rights to the Delegate role. Then they need to set the User Management module permissions on that page. To do this they need to invoke Edit Mode (pencil icon at upper right of page) and then access the Action Menu for the module (down arrow icon at top left corner of module) and choose Module Settings. Select Permissions tab and they would grant View Module, Edit Module, and Write User and Write UserRole rights to the Delegate role. Note that the Write User and Write UserRole permissions are a new concept - they are permissions which control access to the User and UserRole APIs and indicate who is allowed to perform API functions such as add, update, or delete user. With these changes completed, when a user who is in the Delegate role logs into the system, they will see the cog icon at the top right of the page to access the Admin Dashboard. In this case the Admin Dashboard will only display the pages to which they have access (note this screen shot was taken for a user who was provided access to User, Role, and Profile pages): They could access User Management and add/edit/delete users. Note that they are NOT a site Administrator and do not have the ability to perform any other administrative functions. So how does this work? Module's have an IModule interface where various properties are defined for a module. There has always been a property for PermissionNames. This property is used in the permission grid in Module Settings to define the list of permissions supported by the module. In the past only "Module" entity permissions were supported. It has now been enhanced to support API permissions as well. This is important as most modules have an API for a custom entity or entities that they rely upon. The format for API permissions is "EntityName:PermissionName:DefaultRoles". This is the exact same format as the new dynamic policy specification I explained earlier which can be used for APIs - which is not a coincidence, as these permission specifications are used for the exact same purpose - however one is used server-side whereas the other is used client-side. The example above basically says that the User Management module supports standard View and Edit module permissions... but it also depends on Write API permissions for User and UserRole entities. The DefaultRoles is set to Admin and is used to initialize the role specification in the permissions grid in Module Settings. The above is the corresponding dynamic policy specification in the UserController for the Delete method - specifying that the User entity requires Write access for this method and to fallback to checking the Admin role if no permissions have been explicitly set. This demonstrates the correlation between API policies and Module PermissionNames property. One remaining work item which I still need to tackle is optimizing the permissions from an internal data management perspective. Currently permissions are transformed from their native storage format into a couple of other custom formats using a variety of string manipulation techniques. The historic reasons for this are because the very early versions of Blazor had a very simplistic object serializer which could only handle primitive types (ie. integers, strings, etc...). So in order to handle more complex data structures it was necessary to "flatten" them for data transfer between the server and client. Permissions are a custom collection attached to various objects and therefore it fell into this category. Fast forward to today and Blazor now has a robust System.Text.Json implementation which can handle complex data structures. So it is no longer necessary to use custom serialization... and refactoring this logic will result in simplification and performance improvements as a lot of expensive string manipulation can be eliminated. However it must be done in a backward compatible manner.... so this will need to be implemented in a subsequent release. |
Beta Was this translation helpful? Give feedback.
-
Wow! This is fantastic and looks to be a very robust permissions model. I will be utilizing this immediately. |
Beta Was this translation helpful? Give feedback.
-
@sbwalker These changes for permissions have been awesome and have brought a lot of great fine-grained control. However, I'm struggling with how to use a user defined role in an API as a RoleName parameter. The following works just fine for "Registered" users and other built-in roles: However, I'd like to control access to my modules custom API's to only user accounts that are part of a specific user-defined role. e.g. "Inventory Edit", "Inventory Read", etc. Can you give an example of how I might define that policy? |
Beta Was this translation helpful? Give feedback.
-
Thanks, I didn't realize I could use text values for the role name... thought it had to be an object. I'm trying to define rights for two different roles on an API. One role has read only rights and the other has write rights. Individually, each one works, but I'm failing to get them combined for a single policy. When combined as a single policy, the user must be a member of both groups, which is not what I'd like to do. These work individually (e.g. when only one is active):
Attempt at combining them does not work: |
Beta Was this translation helpful? Give feedback.
-
@sbwalker I think I have a grasp of the "what" & "who" concepts, but I'm still a little unsure about when to use different entities. e.g. couldn't the example we are discussing also use the UserRole entity since I'm trying to assign permissions to roles? Isn't your example above only specifying the "View" permission to both roles "Inventory View" and "Inventory Write"? I'm trying to assign the view permission to the role "Inventory View" and the write permission to the role "Inventory Write". I have some (API) users that need to be limited to read-only access, while others need to be able to perform data updates. |
Beta Was this translation helpful? Give feedback.
-
Yes, "Inventory View" is a role created through Role Management. For the sake of clarity, let's call the roles "Inventory Viewers" and "Inventory Writers". For API access, how can I define the Authorization Policy to encompass both roles to allow the viewers read only access and the writers to have read/write access? As previously stated, I can do a policy that works for a single role & permission, but it's the combined policy for the two roles with different permissions that I'm struggling with. |
Beta Was this translation helpful? Give feedback.
-
Thanks @sbwalker, seems I wasn't seeing the forest for the trees. I was (mistakenly) looking to add both the read/write permissions on the GET method and totally ignoring that the write permissions needed to be on the POST/PUT method. I do have a related question doing this with a JWT token, but I will start a new discussion post for that. |
Beta Was this translation helpful? Give feedback.
So this enhancement ended up being much more complicated than I expected. I made 2 attempts at an implementation but ran into issues along the way which forced me to roll them back... and it was the third attempt which ended up being the best option (which also explains why there has not been an official Oqtane release for over 2 months). In the process I ran into some scenarios which third party developers would very likely encounter in their own modules, so it was worthwhile to work through this challenge and come up with a general solution.
The fundamentals of delegating access boils down to permissions... so the improvements I outlined earlier in this thread in regards to dynamic poli…