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

Dynamic permissions #13644

Merged
merged 76 commits into from
Aug 25, 2022
Merged

Dynamic permissions #13644

merged 76 commits into from
Aug 25, 2022

Conversation

hikalkan
Copy link
Member

@hikalkan hikalkan commented Aug 11, 2022

The Problem

When we create a microservice system, we will have many microservices. Every microservice will define their permissions in their Application.Contratcs packages. We also have a permission management dialog to assign permissions to users/roles. In this dialog, we need all the permissions. Also, when a user logins, we get all the granted permissions for this user to be able to render the user interface (many items on UI depends on granted permissions, like toolbars, main menu items, etc). Asking permission grants or definitions to individual microservices would be very inefficient (even if you cache every possible thing and make a complex system).

Until now, we were solving the issue by adding an administration microservice to the solution. This administration microservice has project/package reference to Application.Contratcs packages of all other microservices. In this way, it can know all the permission definitions in the system. So, it can return a list of all permission definitions and also easily check all permission grants of a specific user in a single service.

However, this design makes administration microservice depending on all other service. Whenever we deploy a new version of Microservice X, we need to re-deploy the administration service. Otherwise, it can't have knowledge of newly defined permission in the Microservice X, and we can't see the new permissions while authorizing roles/users in the admin side, or getting all granted permissions of a user.

The Solution

The solution in this PR introduces dynamic permission store;

  • Every microservice can now store their permission definitions into the database. In service startup, they check the permissions in db, compare with the current statically defined permissions, and updates the database (it serialize permission definitions). It is very optimized. If no new permissions, almost nothing done (even not querying permissions from database, because we use a hash in the distributed cache, and check if it was changed). Also runs in a separate thread to not block the application startup
  • Admin microservice reads all permission definitions from database, deserialize them and creates PermissionDefinition objects in-memory (all the dynamic permissions are stored in the memory). In this way, it knows all the permissions in the system. This is also well optimized. Permissions are read only once in service startup (in a separate thread) and read again only if another microservice has changed permissions.

While the solution is simple and straightforward, I made a lot of changes to make this solution working with the ABP Framework and current Permission Management module.

What's Done in This PR, in More Details

  • Introduced PermissionGroupDefinitionRecord and PermissionDefinitionRecord entities in the Permission Management module, to save permission definitions in the database.
  • Saving static permission definitions to database on application start (in AbpPermissionManagementDomainModule). Also, reading permission definitions from database. In this way, different applications may save permissions in the same database, and one of this applications can be used to get all permissions and authorize users in the UI.
  • Serializing / deserializing simple state checkers (mainly introduced ISimpleStateCheckerSerializer and ISimpleStateCheckerSerializerContributor interfaces). I implemented the contributor for all state checkers in the framework. If you have custom state checkers, you can write your own contributors and register to DI, that's all.
  • Introduced IDynamicPermissionDefinitionStore to allow us to get permission definitions dynamically. Also introduced IStaticPermissionDefinitionStore that gets permission definitions as before. IPermissionDefinitionManager now uses both of these.
  • Allow to check a permission (with its name) without defining it (statically or dynamically). PermissionChecker returns false (prohibited) for undefined permissions.
  • Introduced IRootServiceProviderAccessor as a new feature. It is used to get the service provider from the root scope, which won't be disposed.
  • Used cancellation token in distributed lock (it should already be done like that before).
  • LocalizableString now fallbacks to the default localization resource if it can't localize in the given resource, or the resource was not given.
  • Introduced ILocalizableStringSerializer to serialize an ILocalizableString to a string and vice verse. I used this to while serializing permission definitions (for their localizable display names).
  • Added extension methods: IHasExtraProperties.HasSameExtraProperties and ExtraPropertyDictionary.HasSameItems for some comparison purposes.
  • We are returning DisplayNameKey and DisplayNameResource in the PermissionGrantInfo DTO class. In this way, UI layer can localize the display name in the UI side. DisplayName is preserved to be backward-compatible, but may not be truly set in the application layer if the related localization resource is not available in the application that hosts the permission management application service. So, I updated localizations in MVC and Blazor UIs. It should also be done in Angular too (I will organize this).
  • Introduced Application Name feature. In this way, we can assign a unique name to the current application and use that name in our services when we need. It is especially useful when multiple applications share same resources (database, cache, etc) and we want to distinguish resources of different applications. We can set it while creating a new abp application (in the AbpApplicationCreationOptions). Then, when we need it, we can inject IApplicationNameAccessor. If we need it before the dependency injection system is ready, we can get it from the IServiceCollection.GetApplicationName() extension method (for example in the ConfigureServices method of our module. Application name is null if not configured. If you have a monolith application, you mostly don't need to care about it. In a microservice system, it can be set to microservice's name.
  • Set AbpAuditingOptions.ApplicationName from the new Application Name by default.

Breaking Changes

  • IPermissionDefinitionManager methods are converted to asynchronous, and renamed (added Async postfix).
  • Removed MultiTenancySides from permission groups.
  • Inherit MultiTenancySides enum from byte (default was int).
  • Needs to add migration for new entities in the Permission Management module.

@hikalkan
Copy link
Member Author

@maliming you are the main reviewer of this PR. I assigned others to inform them what's happing.

From the global application name.
@hikalkan
Copy link
Member Author

BTW, I've created a branch for manual testing: https://github.com/abpframework/abp/tree/dynamic-permissions-test-app-template-never-merge
I've changed the app startup template, added a test application that has different permissions than the main application. You may want to check it if you want to manually test. You can compare changes here: https://github.com/abpframework/abp/compare/dynamic-permissions...dynamic-permissions-test-app-template-never-merge?expand=1

@enisn enisn requested a review from yekalkan August 23, 2022 06:05
@Nokecy
Copy link
Contributor

Nokecy commented May 23, 2023

setting manage and menu manage same problem @hikalkan

@hikalkan
Copy link
Member Author

setting is done: #16945
I didn't understand the menu problem, can you write more?

@snowchenlei
Copy link
Contributor

localization has the same problem @hikalkan

@maliming
Copy link
Member

localization has the same problem @hikalkan

#13845

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

Successfully merging this pull request may close these issues.

None yet

5 participants