Skip to content

KEYCLOAK-10695 - fixed view permissions of a group#6114

Closed
br0xpl wants to merge 5 commits intokeycloak:masterfrom
br0xpl:master
Closed

KEYCLOAK-10695 - fixed view permissions of a group#6114
br0xpl wants to merge 5 commits intokeycloak:masterfrom
br0xpl:master

Conversation

@br0xpl
Copy link

@br0xpl br0xpl commented Jun 21, 2019

Hi,
According to the issue discribed here:
https://issues.jboss.org/browse/KEYCLOAK-10695
I'm sending a PR with a proposition of fix.

Please let me know if this seems good?

Copy link
Contributor

@pedroigor pedroigor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @br0xpl. Overall, it looks good to me.

However, there is something that I think you are missing, pagination. If the parameters used for pagination (first and max) are provided you will end up enforcing access for a specific page and, eventually, responding with less or no entries although you may have groups in other pages that the user has access.

We had the same issue with the users pagination which we end up doing some hacks to get this working. For instance, getting the groups beforehand prior to making the query.

For groups, we would need something similar or any other more clever logic.

@br0xpl
Copy link
Author

br0xpl commented Jun 26, 2019

Thanks, @br0xpl. Overall, it looks good to me.

However, there is something that I think you are missing, pagination. If the parameters used for pagination (first and max) are provided you will end up enforcing access for a specific page and, eventually, responding with less or no entries although you may have groups in other pages that the user has access.

We had the same issue with the users pagination which we end up doing some hacks to get this working. For instance, getting the groups beforehand prior to making the query.

For groups, we would need something similar or any other more clever logic.

Hi!
I've done the filtering just like in Users case, where all groups are fetched from db each time. This fixes the issue and it's similar to what is in Users but in my opinion we should open another issue to fix the pagination (as I wrote in the comment in the source file).

@pedroigor What do you think?

Thanks.

@br0xpl
Copy link
Author

br0xpl commented Jun 27, 2019

@pedroigor Another thing is that the travis build failed but because of some maven dependencies being unresolvable (forbidden).. seems some problem with maven artifactory rather than with the PR..

Can we somehow rerun the travis build?

@abstractj
Copy link
Contributor

@pedroigor Another thing is that the travis build failed but because of some maven dependencies being unresolvable (forbidden).. seems some problem with maven artifactory rather than with the PR..

Can we somehow rerun the travis build?

That's unrelated with this PR, we are trying to figure how to fix this. Meanwhile, I will try to trigger the job again.

@pedroigor
Copy link
Contributor

Thanks @br0xpl . Like we discussed, it is indeed the most obvious solution although not the most optimal. You also need to provide some tests but before doing so, I will ask @mposolda for his feedback about the current solution.

Regarding the other options to pagination, when doing users pagination I also had the same ideas. I think #2 is the best one as it does keep clients simple and requires some relatively simple work on the server side.

@pedroigor pedroigor requested a review from mposolda July 2, 2019 12:08
Copy link
Contributor

@mposolda mposolda left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 for the approach instead of handle pagination on the client. I added few more comments inline.

results = ModelToRepresentation.searchForGroupByName(realm, search.trim(), firstResult, maxResults);
} else if(Objects.nonNull(firstResult) && Objects.nonNull(maxResults)) {
results = ModelToRepresentation.toGroupHierarchy(realm, false, firstResult, maxResults);
results = realm.searchForGroupByName(search.trim(),null, null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't be here "firstResult, maxResults" instead of "null, null" ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed. @br0xpl, could you consider both firstResult and maxResults when searching by name ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is we are getting maxResults then and some of the groups might get filtered out. So we would have to:
a) fetch maxResults in a loop until we get maxResults after filtering
b) fetch all
I chose b - but if you prefer a just let me know?

@pedroigor , @mposolda : let me know how would you see it done?

But indeed my code misses the filtering first and max in after filtering - this has to be done.

  • Filter first and max results out of the filtered results

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@br0xpl, another option would be:

  • Check if the user has permissions to view groups (granted by view-users or manage-users)
  • If not, obtain the groups that have permission enabled and return those that the user can view
  • You can then use the list of groups to filter the results when searching by name

Look at this code in users

. We set the list of groups that the user has view permission in the KeycloakSession. Then we use this list to filter results in the database.

You should also consider filtering top level groups. Just like it is done today.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@br0xpl, see https://github.com/pedroigor/keycloak/tree/KEYCLOAK-10695. Similar to users. But still missing the top-level group pagination. Just a quick work to give you some ideas :)

Copy link
Author

@br0xpl br0xpl Jul 24, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pedroigor , @mposolda : I've fixed it according to your description - please see it.

I've used the getGroupsWithViewPermission function of GroupPermissions but I have a doubt about it's behaviour, see here:

@Override
public Set<String> getGroupsWithViewPermission() {
if (root.users().canView() || root.users().canManage()) return Collections.emptySet();
if (!root.isAdminSameRealm()) {
return Collections.emptySet();
}
ResourceServer server = root.realmResourceServer();
if (server == null) {
return Collections.emptySet();
}
Set<String> granted = new HashSet<>();
resourceStore.findByType("Group", server.getId(), resource -> {
if (hasPermission(resource, null, VIEW_MEMBERS_SCOPE, MANAGE_MEMBERS_SCOPE)) {
granted.add(resource.getName().substring(RESOURCE_NAME_PREFIX.length()));
}
});
return granted;
}

and line 323 - it is checking if user has manage _members or view _members permission. Shouldn't it be checking if user has view or manage permission for that group? The function name suggest just "view", not view_members..?

Seems we need to either change function name and create another function with view or we need to change this function?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can change the method name. Its intent is to obtain the groups that a user has either view or manage members permission. Good catch.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've done that. I moved the common part to generic getGroupsWithPermissions(String... scopes) function and created two functions: getGroupsWithViewPermission /new function/, and renamed the previous: getGroupsWithViewMembersPermission.

@pedroigor : please review all the changes -> I've tested and the groups work as expected now.


if (Objects.nonNull(search)) {
results = realm.getGroupsCountByNameContaining(search);
results = realm.searchForGroupByName(search, null, null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is potential performance killer, but I don't have better idea how to address this. So I assume we need to live with that. Fortunately there will be thousands of groups at most (not millions) and it is just about admin requests, so hopefully it is ok.

IMO in general, it is probably better to have pagination approach, which doesn't require counts. Something, which always returns the token for next page? Although this approach also has limitations (EG. it is not easily possible to move to the exact page like "Go directly to page 50" etc), and there are probably other limitations. But maybe something to consider in the design of new REST API (CC @pedroigor ). Sorry for hijack the PR :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mposolda, the guideline is now updated with the pagination practice. In a nutshell, we are keeping the existing behavior when paginating results with small improvements on how we communicate whether or not the client reached the last page. We can discuss more there :)

* TODO: IMHO we should consider different pagination technique:
* (1) REST return filtered results (in some cases even epty page) and JS
* client retrieves next pages until it gets all page of results or reaches the end of list
* (2) or we fetch results in backend page by page until we get all required results.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 for this

results = ModelToRepresentation.searchForGroupByName(realm, search.trim(), firstResult, maxResults);
} else if(Objects.nonNull(firstResult) && Objects.nonNull(maxResults)) {
results = ModelToRepresentation.toGroupHierarchy(realm, false, firstResult, maxResults);
results = realm.searchForGroupByName(search.trim(),null, null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed. @br0xpl, could you consider both firstResult and maxResults when searching by name ?

@abstractj abstractj requested review from mposolda and pedroigor July 18, 2019 13:52
Copy link
Contributor

@pedroigor pedroigor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only a few comments. We should also consider adding a test

.

@pedroigor
Copy link
Contributor

@br0xpl, this test is failing GroupTest.searchAndCountGroups.

…ionsResource adding delete and update methods.
@br0xpl
Copy link
Author

br0xpl commented Aug 12, 2019

@pedroigor: Thanks for your valuable comments. I've added a test for the fine grained permission. While writing the test I've found some methods missing in the keycloak-admin-client code - (update and delete in Scope/Resource PermissionsResource classes) - I've added them.

Please verify - thanks!

@abstractj abstractj requested a review from pedroigor August 15, 2019 10:54
@abstractj
Copy link
Contributor

@br0xpl I believe there were some changes in master, which is causing some conflicts with your changes. Could you please rebase?

Response create(ResourcePermissionRepresentation representation);

@Path("{id}")
@PUT
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes are not needed because you have these same methods in org.keycloak.admin.client.resource.ResourcePermissionResource.

Response create(ScopePermissionRepresentation representation);

@Path("{id}")
@PUT
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes are not needed because you have these same methods in org.keycloak.admin.client.resource.ScopePermissionResource.

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.*;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, don't use wildcards. Import specific types instead. This change is not really necessary. The new types you are importing are fine.

}
Set<String> groups = auth.groups().getGroupsWithViewPermission();
Stream<String> s = groups.stream();
if (search!=null) s=s.filter(group->group.contains(search.trim()));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't you have to search by name? Also, I think you should consider filtering by name just like the [code](https://github.com/pedroigor/keycloak/commit/81e718bf9af893d335c4cb50ddc369c813b0ed26) I shared with you.

import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.RoleMappingResource;
import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.admin.client.resource.*;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing here in regards to import statements. Prefer importing types. Most of these changes to import statements can be avoided.

@pedroigor
Copy link
Contributor

@br0xpl, thanks again for your work. In addition to rebasing your branch, could you also squash your commits so that we have a single one ?

@br0xpl
Copy link
Author

br0xpl commented Oct 13, 2019

@pedroigor, @abstractj : I'll try to come back and finish this once again. @pedroigor I sorry I missed your contribution, I'll make sure to take it as indeed we need to search by the name of course. Please give me some time to fix this and squash all the changes together. Thanks!

Copy link
Contributor

@abstractj abstractj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@br0xpl this PR has some conflicts with master at the moment. Could you please follow up on @pedroigor's considerations and rebase your changes?

@stianst stianst added this to the 10.0.0 milestone Mar 17, 2020
@stianst stianst removed this from the 10.0.0 milestone Apr 21, 2020
@stianst stianst added this to the 11.0.0 milestone Apr 21, 2020
@stianst
Copy link
Contributor

stianst commented Jun 15, 2020

Closing this as out of date due to lack of feedback

@stianst stianst closed this Jun 15, 2020
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

Successfully merging this pull request may close these issues.

5 participants