-
Notifications
You must be signed in to change notification settings - Fork 555
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
feat: add native groups to identity #18465
Conversation
The PR is ready for review, but I have to move some classes to new packages after the PR from Ben is merged. |
public static final String DEF_USERS_GROUPS_QUERY = | ||
"select g.id, g.group_name" | ||
+ " from groups g, group_members gm" | ||
+ " where gm.username = ? and g.id = gm.group_id"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔧 - I'm not sure it matters too much now as our data is small but I wonder about the performance of multiple tables in the from vs a JOIN i.e
SELECT g.id, g.group_name
FROM groups g
JOIN group_members gm ON g.id = gm.group_id
WHERE gm.username = ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔧 - I don't think we use the id
column here, we can simplify by just selecting the group_name
and then the loadUserGroups
method can use the same pattern as the loadUsers
method (by using queryForList)
|
||
public List<Group> findAllGroups() { | ||
return userDetailsManager.findAllGroups().stream() | ||
.map(g -> new Group(userDetailsManager.findGroupId(g), g)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❓ is there any reason we make n calls to the database for this information? We could have a single repo method that returns a list of group objects
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A valid point, I did it because I wanted to reuse the existing methods but as It's a findAll method it may downgrade performance a lot, I try to change it as you suggested.
} | ||
|
||
public Optional<Group> findGroupByName(final String group) { | ||
final int groupId = userDetailsManager.findGroupId(group); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❓ What does the findGroupId
method return if there is no group? Is it a Integer
with null
? I think based on https://github.com/camunda/zeebe/pull/18465/files#diff-12171547b1d5162f24b1cced4f6ba4b53d8229dfdb4b68519597db91f3e0eed2R64 you'll get an exception so the if
block is never used.
Instead of having to compose the objects in the service layer here can we just use a Group
return method from the repository? It can still be optional because the SQL can return null
for no results
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also think have the repository return a proper object makes the code in this class simpler
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer to keep the UserDetailManager class as much as possible independent from identity models and more like its parent class.
userDetailsManager.removeUserFromGroup(user.username(), group.name()); | ||
} | ||
|
||
public List<CamundaUser> geMembers(final Group group) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public List<CamundaUser> geMembers(final Group group) { | |
public List<CamundaUser> getMembers(final Group group) { |
import org.springframework.stereotype.Service; | ||
|
||
@Service | ||
public class MembershipService { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❓ I'm wondering if we need to have a separate service for this, are these methods really applicable to the object they are applied to i.e. these are group methods "addUserToGroup" or "findUsersInGroup" whereas "getUserGroups" is likely a userService
level thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did the separation because I preferred to have a central place to look for all membership related stuff and avoid adding dependency to groups in UserService and vice versa. For example "addUserToGroup" can be in both UserService or GroupService but with this specific MembershipService it's easier to find.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thats fair :) - I agree, some methods can easily be in multiple places so moving it out like this creates a nice intentional place for it
g -> | ||
groupService | ||
.findGroupByName(g) | ||
.orElseThrow(() -> new RuntimeException("group.notFound"))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔧 Do we use this exception for anything? If we don't I think we can remove it as it doesn't add value, I think we can change this method to just filter those out, for example
return userDetailsManager.loadUserGroups(user.username()).stream()
.map(groupService::findGroupByName)
.filter(Optional::isPresent)
.map(Optional::get)
.toList();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my idea, this exception should never happens unless we have data inconsistency so it's better to keep it so later in log we can be better noticed about problem.
*/ | ||
package io.camunda.identity.usermanagement; | ||
|
||
public record Group(Integer id, String name) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❓ Just wondering, should we use Integer
here or UUID
? I personally prefer UUIDs but not sure on the general feeling :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my idea we can have int for internal/table level relations and use the uuid (as a uk column) whenever we want to make a service available for public or other modules. however here I just used the default ddl of spring security for groups.
+ " from groups g, group_members gm" | ||
+ " where gm.username = ? and g.id = gm.group_id"; | ||
|
||
private final JdbcTemplate jdbcTemplate; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💭 NABD but I've just remembered that I think we could use a NamedParameterJdbcTemplate here which would be nicer to work with (providing a map of params) but its not required of course :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer to keep it as it is in this PR because maybe with next task (customised fields for User) I change these repository classes to use spring data jpa instead of jdbc.
public class MembershipService { | ||
private final CamundaUserDetailsManager userDetailsManager; | ||
|
||
private final UserService usersService; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
private final UserService usersService; | |
private final UserService userService; |
final GroupService groupService, | ||
final MembershipRepository membershipRepository) { | ||
this.userDetailsManager = userDetailsManager; | ||
usersService = userService; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
usersService = userService; | |
this.userService = userService; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a couple of tweaks from my side but other than that looks good :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for addressing my comments :) - lets get it in and carry on the progress!
Description
Crud on native groups is added. Assign a user to a group or removing of membership is also supported.
Related issues
closes #18463