WIP: add support for attaching realm and client roles to users and groups#175
WIP: add support for attaching realm and client roles to users and groups#175mattock wants to merge 5 commits intotreydock:masterfrom
Conversation
|
Why not use the API to make these changes? https://www.keycloak.org/docs-api/11.0/rest-api/index.html Ideally just use REST API to make the changes if API endpoints exist. |
|
If the ability to do this via API exists and this is assigning a role to a client I think would make sense to support this: There are a few provides in this module where they get data from numerous API calls, I think clients or client protocol mappers are ones like that. The benefit to that is it becomes easier to ensure stray roles don't get assigned by checking the list of desired roles against the list of actually configured roles. |
|
I initially tried to use the REST API. I was able to figure out the correct REST API incantations and payloads with Keycloak Admin UI + Chrome Devtools. When I tried to replicate the same calls with kcadm it refused to co-operate. Basically it claimed that the endpoint, for example /{realm}/groups/{id}/role-mappings/realm, does not support POST requests. The GET endpoints did work with kcadm, but that alone was not very useful. Anyways, this may be the reason why there are dedicated "add-roles", "get-roles", etc. commands in kcadm. I can double-check this though, just in case, and provide you with kcadm command-lines you can try out yourself if you want. |
|
I think to add client role to group and user: https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_client_role_mappings_resource I think the realm role: https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_role_mapper_resource Were those the API calls that were not working? |
I will double-check. |
|
@treydock you were correct. I'm not sure why my initial tests failed. To add or delete realm role mapping for a user Where the playload is I will restructure the provider to use the API. |
|
Works with client roles as well: where the payload is |
|
I did a bit more testing and the current implementation seems to be the most user-friendly and peformant approach after all. The challenge with the API approach is that it requires passing role's "id" and "name" to work. Moreover, the URL needs to have the "id" of the user being modified. The "add-roles" command is able to do its work without the "id" for either of these. So, either the user of this type/provider is forced to define the "id" for both user and role, or the provider has to dynamically figure them out, which results in two extra API calls (sample code below): When the equivalent changes are made using kcadm add-roles it is much faster: The crude but functional Ruby PoC here: Any opinion on this @treydock? |
|
The main benefit with using the pure API approach is you should in theory be able to query all existing role assignments (prefetch) and then add or modify what's needed. That's currently how all the types work for this module, they have expensive code at the beginning that essentially dumps all resources and then evaluates those in-memory data structures to see what should be added/removed/modified. This is always going to be more costly on one or two resources but it scales better. If you have dozens of resources defined and then only way to query them is individual "get" of each resource, that begins to slow things down much more than if you do one massive dump and compare data structures. |
|
Yeah, indeed. If I skip extra validation steps like "is this client role really available to be attached to a user" and just let kcadm barf if it is not, then the number of API calls could be kept reasonable, even for client roles. Anyhow, I'll move forward with the API-based approach with prefetch and all. It would definitely be nice to be able to gather all the data in one go and reuse it across provider instances. |
|
I've not yet found a good way to re-used prefetched data across different resource types, like a I have used this pattern in Rails applications for caching data so expensive queries are only performed once but not sure if will work with prefetch since my usage of such patterns has usually been with instance methods and not class methods though I think the same pattern might work for a class method. |
|
I created a separate branch for developing the API-based provider. I've implemented self.instances and self.prefetch so far. Current implementation's performance won't be great because many following API calls will be necessary:
Maybe the best option would be that self.prefetch would check the Puppet::Type::Keycloak_role_mapping instances passed to it via the "resources" parameter and only check the state of those instances. If no state was found then assume the resource is not present. This way looping through users and groups and their respective role mappings could be avoided. Self.instances could be kept as-is (=get all data, even if it takes times). My new work is here, please ignore the horrific commit history. Or better yet, don't even look at it 😄 |
|
I would recommend opening a new pull request if you want to do something in parallel to this pull request, or just replace this pull request with that branch using instances/prefetch. This will make it easier for me to provide comments inline. Just a quick look at the code and I think it might be better to create 2 types/provides. I would not worry too much about performance. The Keycloak API is not structured in a way to lend itself to high performance automation. At some point I'd like to circle back and find ways for people to opt-out of the slow operations they may not need. |
|
I did some testing and may have changed my mind on how to best handle this. I have 4853 groups in my Keycloak instance, our LDAP has a lot of groups, and the first query of all groups took a while, like 20-30 seconds, but subsequent queries have been less than 1 second. Iterating over all those groups would take forever though since the prefetch would end up making one query per group which is extremely expensive. So given that I think this approach, not using the prefetch/instances might be best unless you want to use prefetch/instances and somehow query the catalog for resource names that are defined and only loop over those group/user names, which is pretty much what this pull request's approach would do. |
|
Also sorry if you spent a lot of time on the prefetch/instances just to switch back to this pull request. I should have tested the prefetch approach on my environment as I have rather massive LDAP deployment. |
|
@treydock no worries. This gave me the opportunity to really learn self.instances and self.prefetch. I had not needed them previously. I do like the simplicity of the original implementation. I'll look a bit more into separating self.prefetch from self.instances plus implementing self.flush, then make some decision. |
|
I spent a fair amount of time to optimize the self.prefetch implementation and decided to give up. Technically it is possible to only query the role mappings that are present in the catalog, but code complexity gets pretty high for little gain if one wants to minimize the amount of API calls:
So in a nutshell the return on investment was poor. I'll meld self.instances from that abandoned approach here, clean up and write tests. Then this should be good to go. |
This is required for cases where a JSON file is not passed in as input. Some examples are get-roles and add-roles. Signed-off-by: Samuli Seppänen <samuli.seppanen@puppeteers.net>
This new type allows attaching client and realm roles into users and groups. Using the normal JSON payload approach ended up being overly complex as well as much less performant than this simplistic approach. Signed-off-by: Samuli Seppänen <samuli.seppanen@puppeteers.net>
Signed-off-by: Samuli Seppänen <samuli.seppanen@puppeteers.net>
Signed-off-by: Samuli Seppänen <samuli.seppanen@puppeteers.net>
Signed-off-by: Samuli Seppänen <samuli.seppanen@puppeteers.net>
6e1305d to
4a9f81b
Compare
|
Please ignore the latest changes. I pushed completely WIP work to this branch, forgetting I had created a PR about it 😄. |
|
We were also planning to implement this functionality so is it possible to give me a status update on this pull request? |
|
@jverrydt I did some code archaeology and here is the original implementation that worked well afaics: It needs rspec tests and all that, of course. The only thing in this PR that is possibly worth saving is the self.instances implementation. @jverrydt if you want to finish the work by all means do. I've found it really difficult to find enough dedicated working time to wrap this thing up. If you decide to do that let me know - I can do code review and testing. |
|
Closing in favor of #233 |
This PR adds support for attaching realm - and later client - roles to users and groups.
It modifies the kcadm method to accept a hash with arbitrary keys and values so that we can run things like "kcadm-wrapper.sh add-roles -r myrealm --uusername john --rolename foo --rolename bar".
If this approach looks generally ok to you I will add unit and acceptance tests. Any feedback on the Ruby style is also most welcome.
I'm thinking of making the client role attachments a separate type, e.g. keycloak_client_role_mapping. For example:
Alternatively I could handle all client roles for a user/group in one place, by passing in a hash like
Thoughts?