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

feat: add native groups to identity #18465

Merged
merged 8 commits into from
May 21, 2024
Merged

feat: add native groups to identity #18465

merged 8 commits into from
May 21, 2024

Conversation

maryarm
Copy link
Contributor

@maryarm maryarm commented May 13, 2024

Description

Crud on native groups is added. Assign a user to a group or removing of membership is also supported.

Related issues

closes #18463

@github-actions github-actions bot added the component/identity Related to the Identity component/team label May 13, 2024
@maryarm
Copy link
Contributor Author

maryarm commented May 13, 2024

The PR is ready for review, but I have to move some classes to new packages after the PR from Ben is merged.

Comment on lines 21 to 24
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";
Copy link
Contributor

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 = ?

Copy link
Contributor

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))
Copy link
Contributor

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

Copy link
Contributor Author

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);
Copy link
Contributor

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

Copy link
Contributor

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

Copy link
Contributor Author

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) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
public List<CamundaUser> geMembers(final Group group) {
public List<CamundaUser> getMembers(final Group group) {

import org.springframework.stereotype.Service;

@Service
public class MembershipService {
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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")))
Copy link
Contributor

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();

Copy link
Contributor Author

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.

@maryarm maryarm requested a review from Ben-Sheppard May 17, 2024 14:49
*/
package io.camunda.identity.usermanagement;

public record Group(Integer id, String name) {
Copy link
Contributor

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 :)

Copy link
Contributor Author

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;
Copy link
Contributor

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 :)

Copy link
Contributor Author

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;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
private final UserService usersService;
private final UserService userService;

final GroupService groupService,
final MembershipRepository membershipRepository) {
this.userDetailsManager = userDetailsManager;
usersService = userService;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
usersService = userService;
this.userService = userService;

Copy link
Contributor

@Ben-Sheppard Ben-Sheppard left a 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 :)

@Ben-Sheppard Ben-Sheppard self-requested a review May 21, 2024 08:32
Copy link
Contributor

@Ben-Sheppard Ben-Sheppard left a 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!

@maryarm maryarm added this pull request to the merge queue May 21, 2024
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks May 21, 2024
@maryarm maryarm added this pull request to the merge queue May 21, 2024
Merged via the queue into main with commit 8d818a1 May 21, 2024
17 checks passed
@maryarm maryarm deleted the 2798-native-groups-cruds branch May 21, 2024 12:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component/identity Related to the Identity component/team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Identity can CRUD on native groups
2 participants