Skip to content

[NAE-2286] Group API#423

Open
renczesstefan wants to merge 26 commits intorelease/7.0.0-rev10from
NAE-2286
Open

[NAE-2286] Group API#423
renczesstefan wants to merge 26 commits intorelease/7.0.0-rev10from
NAE-2286

Conversation

@renczesstefan
Copy link
Member

@renczesstefan renczesstefan commented Mar 11, 2026

Description

Enhanced implementation of Group API, including Action API, REST API and Service API.

Implements NAE-2286

Dependencies

Added one new third party dependency

Third party dependencies

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

Blocking Pull requests

There are no dependencies on other PR.

How Has Been This Tested?

This was tested manually and with unit tests.

Test Configuration

Name Tested on
OS macOS Tahoe 26.3
Runtime Java 21
Dependency Manager Maven 3.9.9n
Framework version Spring Boot 3.4.4
Run parameters
Other configuration

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • My changes have been checked, personally or remotely, with @machacjozef
  • I have commented my code, particularly in hard-to-understand areas
  • I have resolved all conflicts with the target branch of the PR
  • I have updated and synced my code with the target branch
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing tests pass locally with my changes:
    • Lint test
    • Unit tests
    • Integration tests
  • I have checked my contribution with code analysis tools:
  • I have made corresponding changes to the documentation:
    • Developer documentation
    • User Guides
    • Migration Guides

Summary by CodeRabbit

  • New Features

    • Full group management REST API and programmatic group operations (create, list, view, update, delete, search; members, roles, authorities, subgroups).
  • Changes

    • User responses switched to DTOs and include a distinct username; group-related DTOs and validated request formats introduced.
    • Group indexing and repository existence check added; expanded role/authority APIs; some legacy controller/class removed.
  • Documentation

    • API docs updated to reflect renamed/updated authorization/service types.
  • Tests

    • Tests updated to use username and new DTO shapes.

- Introduced methods in `ActionDelegate` for CRUD operations on groups:
  - Added methods for creating, deleting, saving, and finding groups (`createGroup`, `deleteGroup`, `saveGroup`, `findGroupByIdentifier`, etc.).
  - Supported user assignments to groups with methods like `addUserToGroup`, `removeUserFromGroup`.
  - Added operations for managing group roles and authorities.

- Enhanced `GroupService` with detailed methods for:
  - Adding/removing users, roles, and authorities to/from groups.
  - Managing subgroup relationships, including creation and deletion of parent-child links.
  - Extended MongoDB query support for group searches.

- Annotated `Group` entity with custom annotations:
  - Ensured collection using `@EnsureCollection`.
  - Enhanced query capabilities with `@Indexed` on key fields.

- Improved documentation for all newly added methods.

These changes introduce robust support for group-related operations, enhancing scalability and clarity for permissions and role management.
- Added `NewGroupDto` to support new group creation with validation constraints.
- Renamed `GroupSearchDto` -> `SearchGroupDto` for consistent naming.
- Renamed `assignAuthority` methods to `addAuthority` in `GroupService` for clarity.
- Added `GroupController` to handle API operations for group management, including:
  - Retrieving a group by ID.
  - Searching for groups with search criteria.
  - Creating, updating, and deleting groups with proper authorization checks.
- added GroupDto class
- Replaced `SearchGroupDto` with `GroupSearchRequestDto` in `GroupService` and `GroupController` for improved DTO consistency.
- Introduced `CreateGroupRequestDto` and `UpdateGroupRequestDto` to support group creation and updates, respectively.
- Added role management endpoints in `GroupController`:
  - Assign roles to a group (`/roles/assign`).
  - Add roles to a group (`/roles/add`).
- Refactored the `createGroup` and `updateGroup` methods for better validation and error handling.
- Removed the redundant `NewGroupRequest` class and its usage.
- Added `SecurityRequirement` annotations to secure endpoints in `GroupController`.

These changes improve the flexibility and security of group management services while maintaining a cleaner and more consistent codebase.
- resolved unused import
- updated return types
- updated validation dependencies
Replaced references to AuthorizationService with AuthorizationServiceImpl across the application for consistency. Updated method and class names, adjusted related documentation, and ensured all `@PreAuthorize` annotations now reference the renamed implementation.
- Added `assignSubgroups`, `addSubgroups`, and `removeSubgroups` API endpoints in `GroupController` to support dynamic subgroup management.
- Extended `GroupService` interface and implemented methods in `GroupServiceImpl` for assigning and handling subgroups.
- Updated `GroupSearchRequestDto` DTO to include support for filtering by `ids`.
- Modified `GroupDto` to include `groupIds` and `subGroupIds` for enriched group representation.
- Introduced `Pair` for returning parent-child group relationships in `ActionDelegate`.
- Enhanced search functionality in `GroupServiceImpl` to filter groups by IDs in `search` method.
- Introduced new methods `addSubGroup` and `removeSubGroup` in `ActionDelegate` to manage subgroup relationships using `Pair`.
- Extended `GroupSearchRequestDto` to support filtering by `ids`.
- Updated `GroupDto` to include `groupIds` and `subGroupIds` for better representation of group relationships.
- Added API endpoints in `GroupController` for assigning, adding, and removing subgroups:
  - `assignSubgroupsToGroup`
  - `addSubgroupsToGroup`
  - `removeSubgroupsFromGroup`
- Enhanced `GroupService` with methods for handling subgroup operations such as `assignSubgroups`.
- Improved MongoDB search capabilities in `GroupServiceImpl` to filter by group IDs in addition to full-text searches.
Enhanced the group creation flow with validation for null or blank identifiers and a check for existing group identifiers to prevent duplicates. Added corresponding error handling in `GroupController` to return appropriate HTTP responses for invalid input or unexpected errors. Updated `GroupRepository` with a method to check identifier existence.
The `@Validated` import was removed from `GroupController` as it was unused. Additionally, a redundant empty check on the group search IDs in `GroupServiceImpl` was simplified for clarity and efficiency.
Updated `assignRolesToGroup` to return void and adjusted related calls. Renamed `groupIdentifier` parameter to `groupId` in `removeUser` methods for better alignment with naming conventions. These changes improve code readability and align method behaviors across the codebase.
Refactored GroupServiceTest by modularizing existing tests into discrete methods and added new test cases for role and subgroup operations. Introduced `@BeforeEach` setup to streamline initial data preparation. Added a supporting XML file for testing role-related functionality.
Updated test cases to include the `username` field in user creation calls for consistency. Enhanced the system user creation logic to prevent duplication and improve maintainability. These changes ensure better alignment with application requirements and improve test reliability.
Replaced `email` with `username` where appropriate to better align with existing conventions. Simplified and streamlined system group creation by removing redundant update logic. Refactored `createSystemUser` to ensure proper handling of existing system users and improved clarity.
Added null assertions to ensure consistency and prevent issues caused by null parameters in critical methods. Refactored authority management by introducing overloads for adding and removing authorities with Group and Authority parameters, improving modularity and clarity.
Replaced `User` with `UserDto` across the application for better DTO consistency. Added validation for `CreateGroupRequestDto` and implemented enhanced group management operations, including retrieving group members, assigning users to groups, and realm-wide group management.
@coderabbitai
Copy link

coderabbitai bot commented Mar 11, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds full group management: new GroupController and DTOs/requests, extensive GroupService/GroupServiceImpl API and implementation changes, repository/index annotations, ActionDelegate group methods, User→UserDto migration (username added), auth interface/impl rename, tests updated and new test resources.

Changes

Cohort / File(s) Summary
Build
application-engine/pom.xml
Added spring-boot-starter-validation; removed explicit hibernate-validator.
New REST API
application-engine/src/main/java/.../auth/web/GroupController.java
New controller exposing CRUD, member/role/authority/subgroup management, search/paging, validation and ADMIN guards.
ActionDelegate additions
application-engine/src/main/groovy/.../ActionDelegate.groovy
~14 new public methods delegating group operations (find/create/save/delete, add/remove users/roles/authorities/subgroups, findGroups).
Domain & indexing
nae-object-library/src/main/java/.../objects/auth/domain/Group.java
Annotated class with @EnsureCollection and indexed fields (identifier, displayName, ownerId, ownerUsername).
DTOs & requests
nae-object-library/src/main/java/.../dto/request/group/*, .../dto/response/group/GroupDto.java, .../response/authority/AuthorityDto.java, .../response/petrinet/ProcessRoleDto.java
Added Create/Update/Search request records, GroupDto, AuthorityDto, ProcessRoleDto with mapping/factory methods; removed old GroupSearchDto.
Service interfaces
nae-user-common/src/main/java/.../auth/service/GroupService.java, .../auth/service/AuthorizationService.java, nae-user-common/.../auth/service/UserFactory.java
Expanded GroupService API (ids, assignUsers, authority/role/subgroup ops, search), added new AuthorizationService interface, changed UserFactory.getUser to return UserDto.
Repository
nae-user-common/src/main/java/.../auth/repository/GroupRepository.java
Added existsByIdentifier(String identifier).
Service implementations
nae-user-ce/src/main/java/.../auth/service/GroupServiceImpl.java, nae-user-ce/.../auth/service/AuthorizationServiceImpl.java, nae-user-ce/.../auth/service/UserFactoryImpl.java
Large GroupServiceImpl changes: validation, bulk assignUsers, add/remove authority/role/subgroup, search refactor, ProcessRoleService integration; authorization implementation renamed/retargeted; UserFactoryImpl now returns UserDto including groups.
User DTO & responses
nae-user-common/.../auth/web/responsebodies/UserDto.java, application-engine/.../auth/service/UserResourceHelperService.java, .../interfaces/IUserResourceHelperService.java, .../web/responsebodies/UserResource.java
Renamed UserUserDto, added groupIds/groups, updated factories and return types across helper/service/resource.
Controllers — migrate responses & security bean
application-engine/src/main/java/.../auth/web/UserController.java, .../PublicUserController.java, various controllers (PetriNet, Elastic, Workflow, ProcessRole)
Updated response types/imports from UserUserDto; adjusted method signatures and mappings; several @PreAuthorize annotations switched from @authorizationService to @authorizationServiceImpl.
Legacy group code commented out
application-engine/src/main/java/.../orgstructure/GroupController.java, .../responsebodies/GroupsResource.java
Commented-out legacy GroupController and GroupsResource, removing compiled public exports.
Startup & runners
application-engine/src/main/java/.../startup/runner/GroupRunner.java, .../SuperCreatorRunner.java
Refactored default group creation to lookup by system username; swapped some groupService.addUser parameter order usages.
Tests & fixtures
application-engine/src/test/... (many files), application-engine/src/test/resources/simple_role.xml
Added username to User constructions across tests, updated lookups to use username, adjusted many tests, added simple_role.xml, expanded GroupServiceTest cases.
Docs & javadocs
docs/javadoc/...
Updated Javadoc HTML references to reflect renames (AuthorizationServiceImpl, UserDto constructor/params).

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant GroupController
    participant GroupService
    participant GroupRepo
    participant UserService
    participant ProcessRoleService

    Client->>GroupController: POST /api/groups (CreateGroupRequestDto)
    GroupController->>GroupService: createGroup(request)
    GroupService->>GroupRepo: save(group)
    GroupRepo-->>GroupService: saved Group
    GroupService-->>GroupController: GroupDto
    GroupController-->>Client: 201 Created

    Client->>GroupController: PUT /api/groups/{id}/users (Set<userIds>)
    GroupController->>GroupService: assignUsersToGroup(groupId, userIds)
    GroupService->>UserService: findAllByIds(userIds)
    UserService-->>GroupService: List<AbstractUser>
    GroupService->>GroupRepo: save(updatedGroup)
    GroupRepo-->>GroupService: saved Group
    GroupService-->>GroupController: GroupDto
    GroupController-->>Client: 200 OK

    Client->>GroupController: PUT /api/groups/{id}/roles (Set<roleIds>)
    GroupController->>GroupService: addRole(groupId, roleId)
    GroupService->>ProcessRoleService: findById(roleId)
    ProcessRoleService-->>GroupService: ProcessRole
    GroupService->>GroupRepo: save(updatedGroup)
    GroupRepo-->>GroupService: saved Group
    GroupService-->>GroupController: GroupDto
    GroupController-->>Client: 200 OK
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 24.62% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'NAE-2286 Group API' clearly and specifically identifies the main change: a comprehensive implementation of Group API functionality (Action API, REST API, and Service API).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai bot added improvement A change that improves on an existing feature Large Medium labels Mar 11, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 18

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/GroupServiceImpl.java (2)

255-297: ⚠️ Potential issue | 🟠 Major

Preserve realm isolation in the new group-id membership path.

These overloads now resolve the target group globally by _id, but neither path verifies that user.getRealmId() matches group.getRealmId(). If a caller knows a foreign realm's group ID, they can create or remove cross-realm memberships.

🔒 Suggested guard
 public Group addUser(AbstractUser user, Group group) {
     Assert.notNull(user, "User cannot be null");
     Assert.notNull(group, "Group cannot be null");
+    Assert.isTrue(Objects.equals(user.getRealmId(), group.getRealmId()),
+            "User and group must belong to the same realm");

     log.info("Adding user [{}] to group [{}]", user.getStringId(), group.getStringId());
     user.addGroupId(group.getStringId());
     group.addMemberId(user.getStringId());
     userService.saveUser(user, user.getRealmId());
     return save(group);
 }
 
 public Group removeUser(AbstractUser user, Group group) {
     Assert.notNull(user, "User cannot be null");
     Assert.notNull(group, "Group cannot be null");
+    Assert.isTrue(Objects.equals(user.getRealmId(), group.getRealmId()),
+            "User and group must belong to the same realm");

     log.info("Removing user [{}] from group [{}]", user.getStringId(), group.getStringId());
     user.removeGroupId(group.getStringId());
     group.removeMemberId(user.getStringId());
     userService.saveUser(user, user.getRealmId());
     return save(group);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/GroupServiceImpl.java`
around lines 255 - 297, The addUser/removeUser overloads that accept a Group
resolved by ID (methods addUser(AbstractUser user, String groupId) and
removeUser(AbstractUser user, String groupId) and the internal
addUser(AbstractUser user, Group group)/removeUser(AbstractUser user, Group
group)) must enforce realm isolation: after resolving the Group (via findById)
check that user.getRealmId() equals group.getRealmId() and fail fast if not (use
Assert.isTrue or throw an AccessDenied/IllegalArgumentException with a clear
message). Apply this guard before mutating user/group (before
user.addGroupId()/group.addMemberId() and before saving) so cross-realm
membership cannot be created or removed.

411-424: ⚠️ Potential issue | 🟠 Major

Reject subgroup cycles, not just self-links.

The current check only blocks A -> A. It still allows A -> B -> C -> A, which corrupts the hierarchy and can break any recursive traversal or inherited-permission logic built on top of it. assignSubgroups() routes through this path as well.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/GroupServiceImpl.java`
around lines 411 - 424, The code currently only prevents self-links in
addSubgroup(Group parentGroup, Group childGroup) but must also reject any cycle
where parent would become reachable from child (e.g., A->B->C->A). Before
mutating parentGroup/childGroup, perform a graph reachability check: starting
from childGroup, traverse its subGroupIds recursively (DFS/BFS) using the same
group lookup used elsewhere in GroupServiceImpl (e.g., the repository/service
methods you already use to load groups in assignSubgroups or other methods) and
if you encounter parentGroup.getStringId() throw an IllegalArgumentException
indicating a cycle; only if the parent id is not found proceed to
addSubGroupId/addGroupId, save both groups and return as before. Ensure the
traversal guards against infinite loops by tracking visited ids.
application-engine/src/main/java/com/netgrif/application/engine/auth/web/UserController.java (2)

156-163: ⚠️ Potential issue | 🟠 Major

Use getStringId() in the self-access check.

userId is the path parameter string, but this guard compares it to loggedUser.getId(). If getId() is the persisted/ObjectId form, non-admin users will never be able to fetch their own record.

💡 Proposed fix
-        if (!loggedUser.isAdmin() && !Objects.equals(loggedUser.getId(), userId)) {
+        if (!loggedUser.isAdmin() && !Objects.equals(loggedUser.getStringId(), userId)) {
             log.info("User [{}] trying to get another user with ID [{}]", actualUser.getUsername(), userId);
             return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@application-engine/src/main/java/com/netgrif/application/engine/auth/web/UserController.java`
around lines 156 - 163, In getUser replace the self-access check that compares
the path param userId to loggedUser.getId() with a comparison to
loggedUser.getStringId() so non-admin users can match their string ID; update
the Objects.equals call to use loggedUser.getStringId() (method on LoggedUser)
instead of getId() in the authorization guard inside the getUser method.

268-286: ⚠️ Potential issue | 🟠 Major

The ResponseMessage helpers are inverted here.

The 400 branch currently builds a success message, while the 200 branch builds an error message. Clients that look at the response body will read the result backwards.

💡 Proposed fix
         } catch (IllegalArgumentException e) {
             log.error("Assigning authority to user [{}] has failed!", userId, e);
-            return ResponseEntity.badRequest().body(ResponseMessage.createSuccessMessage("Assigning authority to user " + userId + " has failed!"));
+            return ResponseEntity.badRequest().body(ResponseMessage.createErrorMessage("Assigning authority to user " + userId + " has failed!"));
         }
-        return ResponseEntity.ok(ResponseMessage.createErrorMessage("Authority was assigned to user successfully"));
+        return ResponseEntity.ok(ResponseMessage.createSuccessMessage("Authority was assigned to user successfully"));
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@application-engine/src/main/java/com/netgrif/application/engine/auth/web/UserController.java`
around lines 268 - 286, In assignAuthorityToUser, the ResponseMessage helpers
are inverted: when catching IllegalArgumentException you should return a bad
request with ResponseMessage.createErrorMessage containing the failure text
(e.g. "Assigning authority to user {userId} has failed!"), and on success return
ResponseEntity.ok with ResponseMessage.createSuccessMessage containing
"Authority was assigned to user successfully"; update the two ResponseMessage
calls (ResponseMessage.createSuccessMessage and
ResponseMessage.createErrorMessage) accordingly while keeping the existing
status codes and messages and leaving userService.assignAuthority as-is.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@application-engine/src/main/java/com/netgrif/application/engine/auth/web/GroupController.java`:
- Around line 273-281: The addRolesToGroup controller currently iterates roleIds
and calls groupService.addRole per ID, which can leave partial changes if one ID
fails; validate all roleIds up front and replace the per-ID loop with a single
transactional bulk operation on the service (e.g., addRolesToGroup -> call a new
or existing groupService.addRoles(groupId, Set<String>) that performs validation
and a single DB transaction) or wrap the loop in a service-level transaction
that rolls back on error; apply the same pattern to the other endpoints
mentioned (the similar loops at lines referenced: 294-302, 315-323, 336-344,
417-425, 438-446) so all batch mutations are atomic and validated before
mutating.
- Around line 67-69: The mapping currently rebuilds the Page using
groups.size(), losing the original total count and breaking pagination; update
calls that map Page<Group> to Page<GroupDto> (e.g., getAllGroupsOfRealm and the
other methods that call transformPageContent with a Page<Group> named "groups")
so the reconstructed PageImpl uses groups.getTotalElements() (and the original
pageable/sort) instead of groups.size(); ensure the PageImpl constructor is
passed mappedContent, the incoming Pageable, and groups.getTotalElements() so
totalElements/totalPages remain correct.
- Around line 87-98: The code validates request.realmId() but then resolves the
owner with userService.findById(request.ownerId(), null) and might create the
group with a user from a different realm; change the lookup to resolve the owner
inside the requested realm (e.g. call userService.findById(request.ownerId(),
request.realmId()) or otherwise verify the returned AbstractUser's realm equals
request.realmId()), handle a null return by returning NOT_FOUND with the
existing error message, and only call groupService.create(request.identifier(),
request.displayName(), user) once the owner has been validated to belong to the
requested realm.

In
`@application-engine/src/main/java/com/netgrif/application/engine/auth/web/responsebodies/UserResource.java`:
- Around line 10-11: The constructor for UserResource currently ignores the
selfRel parameter; update the UserResource(UserDto content, String selfRel)
constructor to include the provided selfRel as the entity's self link instead of
passing new ArrayList<>() to the superclass (or remove the selfRel parameter if
you prefer API cleanup). Specifically, construct and pass a Link (or Collection
of Links) containing a self link built from selfRel into the call to
super(content, ...), so callers in UserResourceHelperService and
UserResourceAssembler (which supply "profile" or a variable selfRel) have their
link preserved in the resulting EntityModel; alternatively drop the unused
selfRel parameter from the constructor and adjust callers accordingly.

In
`@application-engine/src/main/java/com/netgrif/application/engine/orgstructure/web/GroupController.java`:
- Around line 24-55: The legacy GET /api/group/all route was removed and the
commented GroupController/getAllGroups is left in-source; either reintroduce
compatible routing in the new GroupController (add an endpoint that maps GET
/api/group/all and delegates to the existing service/getAllGroups logic or
routes to the new handler) or remove the commented legacy controller and add
explicit migration docs explaining the breaking change and the new path format
(api/groups/{realmId}/all) with example client update steps; ensure references
to `@RequestMapping`, GroupController, and getAllGroups are updated/removed
consistently and do not leave commented legacy endpoints in the codebase.

In
`@application-engine/src/main/java/com/netgrif/application/engine/startup/runner/GroupRunner.java`:
- Around line 35-39: The createDefaultGroup method in GroupRunner duplicates
lazy-creation logic; replace its manual lookup/create flow by calling
GroupService.getDefaultSystemGroup() (via the groupService instance) to obtain
the default system group instead of using groupService.findByIdentifier(...) and
groupService.create(...); update any logging to reflect that
getDefaultSystemGroup() was invoked and remove the duplicated creation logic so
startup uses the canonical service behavior.

In
`@application-engine/src/test/groovy/com/netgrif/application/engine/orgstructure/groups/GroupServiceTest.groovy`:
- Around line 104-108: The FileInputStream passed into
petriNetService.importPetriNet via ImportPetriNetParams.with().xmlFile(new
FileInputStream("src/test/resources/simple_role.xml")) is never closed; change
the test to open the XML stream in a try-with-resources (or Groovy's
withCloseable/using) so the InputStream is closed after calling
petriNetService.importPetriNet (referencing ImportPetriNetEventOutcome and
petriNetService.importPetriNet); ensure the stream variable is used for
xmlFile(...) and automatically closed to prevent file handle leaks.

In
`@docs/javadoc/com/netgrif/application/engine/auth/service/AuthorizationService.html`:
- Line 128: The Javadoc file is inconsistent: the inheritance tree references
AuthorizationServiceImpl while the title, declaration block, constructors, and
anchors still describe AuthorizationService; regenerate the entire Javadoc page
for the AuthorizationService/AuthorizationServiceImpl classes instead of
hand-editing a single line. Run the Javadoc generation for the package
com.netgrif.application.engine.auth.service (so the AuthorizationService and
AuthorizationServiceImpl pages are rebuilt), ensure the generated HTML now
consistently shows AuthorizationServiceImpl where appropriate and that anchors,
declaration blocks, and constructor sections match the actual class names.

In
`@docs/javadoc/com/netgrif/application/engine/auth/service/class-use/AuthorizationService.html`:
- Line 6: The Javadoc page class-use/AuthorizationService.html was manually
edited to reference AuthorizationServiceImpl causing inconsistent title/body;
revert the manual changes to this file and regenerate the Javadoc so the page
correctly documents AuthorizationService (not AuthorizationServiceImpl).
Specifically, restore/remove any references where AuthorizationServiceImpl was
inserted into class-use/AuthorizationService.html (and the similar mismatches
you saw for other occurrences) and run the project's Javadoc generation task so
the generated title/header/body consistently reflect the AuthorizationService
class and its actual usages.

In
`@docs/javadoc/com/netgrif/application/engine/auth/service/interfaces/class-use/IAuthorizationService.html`:
- Line 6: The Javadoc page class-use/IAuthorizationService.html is a stale
artifact that was only partially renamed (title changed to AuthorizationService)
while links and nav still reference the interface IAuthorizationService;
regenerate the Javadoc for the type
com.netgrif.application.engine.auth.service.AuthorizationService (so
class-use/IAuthorizationService.html and related class-use pages are rebuilt
consistently) or delete the stale class-use/IAuthorizationService.html file (and
any other mismatched artifacts) so that references to IAuthorizationService.html
and AuthorizationService are not contradictory in the docs.

In
`@docs/javadoc/com/netgrif/application/engine/auth/web/responsebodies/UserResource.html`:
- Line 162: The generated Javadoc for the UserResource constructor is
inconsistent: some constructor anchors reference UserDto while other parts
(hierarchy and constructor body) still reference User/User.html, causing mixed
APIs; regenerate the docs from sources and fix the Javadoc generation so all
constructor links and type references are consistent by updating the
UserResource constructor signature to use UserDto everywhere (ensure the
constructor anchor for UserResource(<init>(UserDto,String)) and all related type
links and the hierarchy refer to
com.netgrif.application.engine.auth.web.responsebodies.UserDto instead of User),
then rebuild the Javadoc to produce a single coherent page.

In
`@nae-object-library/src/main/java/com/netgrif/application/engine/objects/dto/request/group/UpdateGroupRequestDto.java`:
- Line 3: The UpdateGroupRequestDto record currently allows null/blank values;
annotate the record components with validation constraints (e.g., add `@NotNull`
on id and `@NotBlank` on identifier and displayName) and import the corresponding
Jakarta Validation annotations so the DTO fails validation instead of causing an
NPE in updateGroup; also update the controller method that receives this DTO
(the updateGroup endpoint parameter) to be annotated with `@Valid` (and keep
`@RequestBody` if present) so validation is triggered before processing.

In
`@nae-object-library/src/main/java/com/netgrif/application/engine/objects/dto/response/authority/AuthorityDto.java`:
- Around line 11-15: AuthorityDto currently uses ObjectId for its id field;
change the record signature to use String id to match the DTO pattern and
GroupDto, and update the factory to call authority.getStringId() instead of
get_id(); specifically modify the record declaration AuthorityDto(ObjectId id,
String name) to AuthorityDto(String id, String name) and update the
fromAuthority(Authority authority) method to construct the record with
authority.getStringId() and authority.getName().

In
`@nae-object-library/src/main/java/com/netgrif/application/engine/objects/dto/response/group/GroupDto.java`:
- Around line 21-27: The factory method GroupDto.fromGroup currently passes
group.getGroupIds() and group.getSubgroupIds() directly into the GroupDto, which
leaks mutable backing collections; make defensive copies before storing them in
the DTO by replacing those arguments with new collection instances created from
group.getGroupIds() and group.getSubgroupIds() (e.g., copy into new
HashSet/ArrayList or Collections.unmodifiableSet) so the DTO holds its own
immutable/mutably-isolated copies.

In
`@nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/AuthorizationServiceImpl.java`:
- Around line 7-8: The service bean is currently unnamed causing Spring to
generate authorizationServiceImpl and coupling security expressions; update the
class declaration for AuthorizationServiceImpl (which implements
AuthorizationService) to declare an explicit bean name (e.g.
`@Service`("authorizationService")) so the bean id is stable, then update all
`@PreAuthorize` references that use `@authorizationServiceImpl` to use
`@authorizationService` instead in the controllers that reference it
(WorkflowController, ProcessRoleController, PetriNetController,
ElasticController, UserController, GroupController); ensure the interface name
AuthorizationService remains the type and only the bean name changes.

In
`@nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/GroupServiceImpl.java`:
- Around line 239-247: The bulk-diff logic in assignUsersToGroup (and similarly
in assignSubgroups) assumes group.getMemberIds()/parentGroup.getSubgroupIds()
and the incoming userIds/childGroupIds are non-null; create defensive
normalization before constructing new HashSet instances by replacing any null
with Collections.emptySet() or new HashSet<>() (e.g., Set<String>
currentGroupMemberIds =
Optional.ofNullable(group.getMemberIds()).orElse(Collections.emptySet()); and
Set<String> userIdsSafe =
Optional.ofNullable(userIds).orElse(Collections.emptySet());), then compute
removableMemberIds and newMemberIds from these safe sets; apply the same pattern
in assignSubgroups using parentGroup.getSubgroupIds() and childGroupIds to avoid
NPEs.

In
`@nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/GroupService.java`:
- Around line 163-171: The overloads for group membership operations are
inconsistent (mixing groupIdentifier and groupId) which can cause callers to
pass the wrong value; update the public API so all symmetric methods use the
same parameter name/type (prefer persisted IDs) — change method signatures like
Group addUser(String userId, String groupIdentifier, String realmId) and its
counterparts (removeUser, addUsers, removeUsers, etc.) to accept a single
consistent parameter name (e.g., groupId) and update all implementations and
callers (controllers, services) to pass persisted group IDs accordingly so the
API surface is uniform.

In
`@nae-user-common/src/main/java/com/netgrif/application/engine/auth/web/responsebodies/UserDto.java`:
- Around line 87-89: The createUser(AbstractUser user, List<GroupDto> groups)
factory in UserDto currently does new HashSet<>(groups) which throws when groups
is null; update the method (UserDto.createUser) to treat a null groups argument
as an empty collection before constructing the HashSet (e.g., use an explicit
null check or default to Collections.emptySet()/Collections.emptyList()) so
result.setGroups(...) always receives a non-null, possibly-empty Set.

---

Outside diff comments:
In
`@application-engine/src/main/java/com/netgrif/application/engine/auth/web/UserController.java`:
- Around line 156-163: In getUser replace the self-access check that compares
the path param userId to loggedUser.getId() with a comparison to
loggedUser.getStringId() so non-admin users can match their string ID; update
the Objects.equals call to use loggedUser.getStringId() (method on LoggedUser)
instead of getId() in the authorization guard inside the getUser method.
- Around line 268-286: In assignAuthorityToUser, the ResponseMessage helpers are
inverted: when catching IllegalArgumentException you should return a bad request
with ResponseMessage.createErrorMessage containing the failure text (e.g.
"Assigning authority to user {userId} has failed!"), and on success return
ResponseEntity.ok with ResponseMessage.createSuccessMessage containing
"Authority was assigned to user successfully"; update the two ResponseMessage
calls (ResponseMessage.createSuccessMessage and
ResponseMessage.createErrorMessage) accordingly while keeping the existing
status codes and messages and leaving userService.assignAuthority as-is.

In
`@nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/GroupServiceImpl.java`:
- Around line 255-297: The addUser/removeUser overloads that accept a Group
resolved by ID (methods addUser(AbstractUser user, String groupId) and
removeUser(AbstractUser user, String groupId) and the internal
addUser(AbstractUser user, Group group)/removeUser(AbstractUser user, Group
group)) must enforce realm isolation: after resolving the Group (via findById)
check that user.getRealmId() equals group.getRealmId() and fail fast if not (use
Assert.isTrue or throw an AccessDenied/IllegalArgumentException with a clear
message). Apply this guard before mutating user/group (before
user.addGroupId()/group.addMemberId() and before saving) so cross-realm
membership cannot be created or removed.
- Around line 411-424: The code currently only prevents self-links in
addSubgroup(Group parentGroup, Group childGroup) but must also reject any cycle
where parent would become reachable from child (e.g., A->B->C->A). Before
mutating parentGroup/childGroup, perform a graph reachability check: starting
from childGroup, traverse its subGroupIds recursively (DFS/BFS) using the same
group lookup used elsewhere in GroupServiceImpl (e.g., the repository/service
methods you already use to load groups in assignSubgroups or other methods) and
if you encounter parentGroup.getStringId() throw an IllegalArgumentException
indicating a cycle; only if the parent id is not found proceed to
addSubGroupId/addGroupId, save both groups and return as before. Ensure the
traversal guards against infinite loops by tracking visited ids.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: a1e2fbde-f276-43dc-8cba-7cd503e85058

📥 Commits

Reviewing files that changed from the base of the PR and between fe97154 and 4c39084.

📒 Files selected for processing (52)
  • application-engine/pom.xml
  • application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy
  • application-engine/src/main/java/com/netgrif/application/engine/auth/service/UserResourceHelperService.java
  • application-engine/src/main/java/com/netgrif/application/engine/auth/service/interfaces/IAuthorizationService.java
  • application-engine/src/main/java/com/netgrif/application/engine/auth/service/interfaces/IUserResourceHelperService.java
  • application-engine/src/main/java/com/netgrif/application/engine/auth/web/GroupController.java
  • application-engine/src/main/java/com/netgrif/application/engine/auth/web/PublicUserController.java
  • application-engine/src/main/java/com/netgrif/application/engine/auth/web/UserController.java
  • application-engine/src/main/java/com/netgrif/application/engine/auth/web/responsebodies/UserResource.java
  • application-engine/src/main/java/com/netgrif/application/engine/elastic/web/ElasticController.java
  • application-engine/src/main/java/com/netgrif/application/engine/orgstructure/web/GroupController.java
  • application-engine/src/main/java/com/netgrif/application/engine/orgstructure/web/responsebodies/GroupsResource.java
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/PetriNetController.java
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/ProcessRoleController.java
  • application-engine/src/main/java/com/netgrif/application/engine/startup/runner/GroupRunner.java
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/web/WorkflowController.java
  • application-engine/src/test/groovy/com/netgrif/application/engine/action/AssignActionTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/auth/LoginAttemptsTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/auth/TaskAuthorizationServiceTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/auth/WorkflowAuthorizationServiceTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/elastic/DataSearchRequestTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/menu/MenuImportExportTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/orgstructure/groups/GroupServiceTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/permissions/ElasticSearchViewPermissionTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/permissions/QueryDSLViewPermissionTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/dataset/FileFieldTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/domain/dataset/FileListFieldTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/CachePetriNetServiceTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/service/PetriNetServiceTest.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/petrinet/web/PetriNetControllerTest.groovy
  • application-engine/src/test/java/com/netgrif/application/engine/workflow/web/VariableArcsTest.java
  • application-engine/src/test/resources/simple_role.xml
  • docs/javadoc/com/netgrif/application/engine/auth/service/AuthorizationService.html
  • docs/javadoc/com/netgrif/application/engine/auth/service/class-use/AuthorizationService.html
  • docs/javadoc/com/netgrif/application/engine/auth/service/interfaces/class-use/IAuthorizationService.html
  • docs/javadoc/com/netgrif/application/engine/auth/web/responsebodies/UserResource.html
  • nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/Group.java
  • nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/dto/GroupSearchDto.java
  • nae-object-library/src/main/java/com/netgrif/application/engine/objects/dto/request/group/CreateGroupRequestDto.java
  • nae-object-library/src/main/java/com/netgrif/application/engine/objects/dto/request/group/GroupSearchRequestDto.java
  • nae-object-library/src/main/java/com/netgrif/application/engine/objects/dto/request/group/UpdateGroupRequestDto.java
  • nae-object-library/src/main/java/com/netgrif/application/engine/objects/dto/response/authority/AuthorityDto.java
  • nae-object-library/src/main/java/com/netgrif/application/engine/objects/dto/response/group/GroupDto.java
  • nae-object-library/src/main/java/com/netgrif/application/engine/objects/dto/response/petrinet/ProcessRoleDto.java
  • nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/AuthorizationServiceImpl.java
  • nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/GroupServiceImpl.java
  • nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserFactoryImpl.java
  • nae-user-common/src/main/java/com/netgrif/application/engine/auth/repository/GroupRepository.java
  • nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/AuthorizationService.java
  • nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/GroupService.java
  • nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/UserFactory.java
  • nae-user-common/src/main/java/com/netgrif/application/engine/auth/web/responsebodies/UserDto.java
💤 Files with no reviewable changes (2)
  • application-engine/src/main/java/com/netgrif/application/engine/auth/service/interfaces/IAuthorizationService.java
  • nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/dto/GroupSearchDto.java

Comment on lines +67 to +69
public ResponseEntity<Page<GroupDto>> getAllGroupsOfRealm(@PathVariable("realmId") String realmId, Pageable pageable, Locale locale) {
Page<Group> groups = groupService.findAllFromRealm(realmId, pageable);
return ResponseEntity.ok(transformPageContent(groups.getContent(), pageable, locale));
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Preserve the original page metadata when mapping to GroupDto.

transformPageContent(...) rebuilds the page with groups.size() as the total, so /all and /search report only the current page size instead of the real match count. Pagination becomes wrong as soon as there is more than one page.

💡 Proposed fix
     public ResponseEntity<Page<GroupDto>> getAllGroupsOfRealm(`@PathVariable`("realmId") String realmId, Pageable pageable, Locale locale) {
         Page<Group> groups = groupService.findAllFromRealm(realmId, pageable);
-        return ResponseEntity.ok(transformPageContent(groups.getContent(), pageable, locale));
+        return ResponseEntity.ok(transformPageContent(groups, locale));
     }
@@
     `@GetMapping`(produces = MediaType.APPLICATION_JSON_VALUE)
     public ResponseEntity<Page<GroupDto>> search(GroupSearchRequestDto query, Pageable pageable, Locale locale) {
-        List<Group> groups = groupService.search(query, pageable).getContent();
-        return ResponseEntity.ok(transformPageContent(groups, pageable, locale));
+        Page<Group> groups = groupService.search(query, pageable);
+        return ResponseEntity.ok(transformPageContent(groups, locale));
     }
@@
-    private Page<GroupDto> transformPageContent(List<Group> groups, Pageable pageable, Locale locale) {
-        return new PageImpl<>(groups.stream().map(group -> GroupDto.fromGroup(group, locale)).toList(), pageable, groups.size());
+    private Page<GroupDto> transformPageContent(Page<Group> groups, Locale locale) {
+        return new PageImpl<>(
+                groups.getContent().stream().map(group -> GroupDto.fromGroup(group, locale)).toList(),
+                groups.getPageable(),
+                groups.getTotalElements()
+        );
     }

Also applies to: 355-357, 450-451

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@application-engine/src/main/java/com/netgrif/application/engine/auth/web/GroupController.java`
around lines 67 - 69, The mapping currently rebuilds the Page using
groups.size(), losing the original total count and breaking pagination; update
calls that map Page<Group> to Page<GroupDto> (e.g., getAllGroupsOfRealm and the
other methods that call transformPageContent with a Page<Group> named "groups")
so the reconstructed PageImpl uses groups.getTotalElements() (and the original
pageable/sort) instead of groups.size(); ensure the PageImpl constructor is
passed mappedContent, the incoming Pageable, and groups.getTotalElements() so
totalElements/totalPages remain correct.

Comment on lines +87 to +98
if (!realmExists(request.realmId())) {
String message = "Cannot create group, realm with id [" + request.realmId() + "] does not exist";
log.error(message);
return ResponseEntity.badRequest().build();
}
AbstractUser user = userService.findById(request.ownerId(), null);
if (user == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ResponseMessage.createErrorMessage("User with id [%s] not found".formatted(request.ownerId())));
}
try {
groupService.create(request.identifier(), request.displayName(), user);
return ResponseEntity.ok(ResponseMessage.createSuccessMessage("Group created successfully"));
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Resolve the owner inside the requested realm before creating the group.

This endpoint validates request.realmId(), but then loads the owner with findById(request.ownerId(), null) and creates the group from that user. A mismatched owner can therefore create the group in a different realm than the one the request just validated.

💡 Proposed fix
-        AbstractUser user = userService.findById(request.ownerId(), null);
-        if (user == null) {
-            return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ResponseMessage.createErrorMessage("User with id [%s] not found".formatted(request.ownerId())));
-        }
         try {
+            AbstractUser user = userService.findById(request.ownerId(), request.realmId());
+            if (user == null) {
+                return ResponseEntity.status(HttpStatus.NOT_FOUND)
+                        .body(ResponseMessage.createErrorMessage("User with id [%s] not found in realm [%s]".formatted(request.ownerId(), request.realmId())));
+            }
             groupService.create(request.identifier(), request.displayName(), user);
-            return ResponseEntity.ok(ResponseMessage.createSuccessMessage("Group created successfully"));
+            return ResponseEntity.status(HttpStatus.CREATED)
+                    .body(ResponseMessage.createSuccessMessage("Group created successfully"));
         } catch (IllegalArgumentException e) {
             return ResponseEntity.badRequest().body(ResponseMessage.createErrorMessage(e.getMessage()));
         } catch (Exception e) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!realmExists(request.realmId())) {
String message = "Cannot create group, realm with id [" + request.realmId() + "] does not exist";
log.error(message);
return ResponseEntity.badRequest().build();
}
AbstractUser user = userService.findById(request.ownerId(), null);
if (user == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ResponseMessage.createErrorMessage("User with id [%s] not found".formatted(request.ownerId())));
}
try {
groupService.create(request.identifier(), request.displayName(), user);
return ResponseEntity.ok(ResponseMessage.createSuccessMessage("Group created successfully"));
if (!realmExists(request.realmId())) {
String message = "Cannot create group, realm with id [" + request.realmId() + "] does not exist";
log.error(message);
return ResponseEntity.badRequest().build();
}
try {
AbstractUser user = userService.findById(request.ownerId(), request.realmId());
if (user == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(ResponseMessage.createErrorMessage("User with id [%s] not found in realm [%s]".formatted(request.ownerId(), request.realmId())));
}
groupService.create(request.identifier(), request.displayName(), user);
return ResponseEntity.status(HttpStatus.CREATED)
.body(ResponseMessage.createSuccessMessage("Group created successfully"));
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().body(ResponseMessage.createErrorMessage(e.getMessage()));
} catch (Exception e) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@application-engine/src/main/java/com/netgrif/application/engine/auth/web/GroupController.java`
around lines 87 - 98, The code validates request.realmId() but then resolves the
owner with userService.findById(request.ownerId(), null) and might create the
group with a user from a different realm; change the lookup to resolve the owner
inside the requested realm (e.g. call userService.findById(request.ownerId(),
request.realmId()) or otherwise verify the returned AbstractUser's realm equals
request.realmId()), handle a null return by returning NOT_FOUND with the
existing error message, and only call groupService.create(request.identifier(),
request.displayName(), user) once the owner has been validated to belong to the
requested realm.

Comment on lines +273 to +281
public ResponseEntity<ResponseMessage> addRolesToGroup(@PathVariable("id") String groupId, @RequestBody Set<String> roleIds) {
try {
roleIds.forEach(roleId -> groupService.addRole(groupId, roleId));
log.info("Process roles {} added to group with id [{}]", roleIds, groupId);
return ResponseEntity.ok(ResponseMessage.createSuccessMessage("Selected roles added to group " + groupId));
} catch (IllegalArgumentException e) {
String message = "Adding roles to group [" + groupId + "] has failed!";
log.error(message, e);
return ResponseEntity.badRequest().body(ResponseMessage.createErrorMessage("Adding roles to group " + groupId + " has failed!"));
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

These batch mutations can leave the group half-updated.

Each endpoint applies changes one ID at a time and only then catches IllegalArgumentException. If a later ID is invalid, earlier additions/removals are already committed even though the API returns 400. Please validate the whole request up front and execute the mutation through a single service-level bulk operation or transaction.

Also applies to: 294-302, 315-323, 336-344, 417-425, 438-446

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@application-engine/src/main/java/com/netgrif/application/engine/auth/web/GroupController.java`
around lines 273 - 281, The addRolesToGroup controller currently iterates
roleIds and calls groupService.addRole per ID, which can leave partial changes
if one ID fails; validate all roleIds up front and replace the per-ID loop with
a single transactional bulk operation on the service (e.g., addRolesToGroup ->
call a new or existing groupService.addRoles(groupId, Set<String>) that performs
validation and a single DB transaction) or wrap the loop in a service-level
transaction that rolls back on error; apply the same pattern to the other
endpoints mentioned (the similar loops at lines referenced: 294-302, 315-323,
336-344, 417-425, 438-446) so all batch mutations are atomic and validated
before mutating.

Comment on lines +10 to 11
public UserResource(UserDto content, String selfRel) {
super(content, new ArrayList<>());
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "UserResource.java" | head -20

Repository: netgrif/application-engine

Length of output: 177


🏁 Script executed:

cat -n ./application-engine/src/main/java/com/netgrif/application/engine/auth/web/responsebodies/UserResource.java

Repository: netgrif/application-engine

Length of output: 488


🏁 Script executed:

# Check EntityModel usage patterns in the codebase
rg "EntityModel" --type java -A 3 -B 1 | head -100

Repository: netgrif/application-engine

Length of output: 13963


🏁 Script executed:

# Search for UserResource instantiation
rg "new UserResource" --type java -B 2 -A 2

Repository: netgrif/application-engine

Length of output: 2152


🏁 Script executed:

# Let's look at UserResourceAssembler to understand the full context
cat -n ./application-engine/src/main/java/com/netgrif/application/engine/auth/web/responsebodies/UserResourceAssembler.java

Repository: netgrif/application-engine

Length of output: 1621


🏁 Script executed:

# Let's check UserResourceHelperService
cat -n ./application-engine/src/main/java/com/netgrif/application/engine/auth/service/UserResourceHelperService.java

Repository: netgrif/application-engine

Length of output: 2789


🏁 Script executed:

# Check LocalisedTaskResource to see how it handles links
cat -n ./application-engine/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/LocalisedTaskResource.java

Repository: netgrif/application-engine

Length of output: 2715


🏁 Script executed:

# Check if there's a method in UserResource that adds links or uses selfRel
rg "UserResource" --type java -A 10 -B 2 | grep -A 10 "class UserResource"

Repository: netgrif/application-engine

Length of output: 4088


Use the selfRel parameter or remove it from the constructor.

The constructor accepts a selfRel argument but ignores it, always passing an empty link list to the parent EntityModel. Both UserResourceHelperService and UserResourceAssembler pass meaningful values ("profile" and the selfRel variable respectively), but these are silently discarded. The serialized HAL response will be missing the _links.self reference.

Either apply the self link in the constructor or remove the unused parameter from the public API.

Suggested fix
 import org.springframework.hateoas.EntityModel;
+import org.springframework.hateoas.Link;
 
-import java.util.ArrayList;
+import java.util.Collections;
 
 public class UserResource extends EntityModel<UserDto> {
 
     public UserResource(UserDto content, String selfRel) {
-        super(content, new ArrayList<>());
+        super(content, selfRel == null
+                ? Collections.emptyList()
+                : Collections.singletonList(Link.of(selfRel).withSelfRel()));
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@application-engine/src/main/java/com/netgrif/application/engine/auth/web/responsebodies/UserResource.java`
around lines 10 - 11, The constructor for UserResource currently ignores the
selfRel parameter; update the UserResource(UserDto content, String selfRel)
constructor to include the provided selfRel as the entity's self link instead of
passing new ArrayList<>() to the superclass (or remove the selfRel parameter if
you prefer API cleanup). Specifically, construct and pass a Link (or Collection
of Links) containing a self link built from selfRel into the call to
super(content, ...), so callers in UserResourceHelperService and
UserResourceAssembler (which supply "profile" or a variable selfRel) have their
link preserved in the resulting EntityModel; alternatively drop the unused
selfRel parameter from the constructor and adjust callers accordingly.

Comment on lines +24 to +55
//@RestController
//@RequestMapping("/api/group")
//@ConditionalOnProperty(
// value = "netgrif.engine.security.web.group-enabled",
// havingValue = "true",
// matchIfMissing = true
//)
//@Tag(name = "Group")
//public class GroupController {
//
// private final GroupService service;
//
// public GroupController(GroupService service) {
// this.service = service;
// }
//
// @PreAuthorize("@authorizationService.hasAuthority('ADMIN')")
// @Operation(summary = "Get all groups in the system",
// description = "Caller must have the ADMIN role",
// security = {@SecurityRequirement(name = "BasicAuth")})
// @GetMapping(value = "/all", produces = MediaTypes.HAL_JSON_VALUE)
// @ApiResponses(value = {
// @ApiResponse(responseCode = "200", description = "OK"),
// @ApiResponse(responseCode = "403", description = "Caller doesn't fulfill the authorisation requirements"),
// })
// public GroupsResource getAllGroups() {
// List<com.netgrif.application.engine.objects.auth.domain.Group> groups = service.findAll(Pageable.unpaged()).getContent();
// Set<Group> groupResponse = groups.stream()
// .map(g -> new Group(g.getStringId(), g.getDisplayName()))
// .collect(Collectors.toCollection(HashSet::new));
// return new GroupsResource(groupResponse);
// }
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

for f in \
  application-engine/src/main/java/com/netgrif/application/engine/orgstructure/web/GroupController.java \
  application-engine/src/main/java/com/netgrif/application/engine/auth/web/GroupController.java
do
  if [ -f "$f" ]; then
    echo "== $f =="
    rg -n -C2 '@RequestMapping|@GetMapping|/api/group|/all|class GroupController' "$f" || true
  fi
done

if [ -d docs ]; then
  echo "== docs references =="
  rg -n -C2 '/api/group/all|group/all' docs || true
fi

Repository: netgrif/application-engine

Length of output: 2538


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search for migration, changelog, or breaking changes documentation
rg -l 'CHANGELOG|MIGRATION|BREAKING|MIGRATION_GUIDE|BREAKING_CHANGES|UPGRADE' . --type md --type txt --type asciidoc 2>/dev/null | head -20

# Also search the README for migration info
if [ -f README.md ]; then
  echo "=== README.md references to group or migration ==="
  rg -n -i 'group.*migration|breaking.*change|v[0-9]+\.[0-9]+' README.md || true
fi

# Look for any release notes or version-specific docs
find . -maxdepth 3 -type f \( -name "*CHANGELOG*" -o -name "*MIGRATION*" -o -name "*RELEASE*" \) 2>/dev/null | head -10

Repository: netgrif/application-engine

Length of output: 52


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# List files at repo root that might be docs
ls -la | grep -i -E 'readme|changelog|migration|doc|guide' || echo "No matching files at root"

# Check if docs directory exists and list contents
if [ -d docs ]; then
  echo "=== Docs directory exists ==="
  find docs -maxdepth 2 -type f | head -20
else
  echo "No docs directory found"
fi

Repository: netgrif/application-engine

Length of output: 815


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search within docs for group-related or migration-related information
echo "=== Searching docs for Group API changes ==="
rg -n 'group|migration|breaking|deprecat' docs --type md --type txt -i | grep -v '.nojekyll' | head -30

echo -e "\n=== Files mentioning Group or API ==="
find docs -name "*.md" -type f -exec grep -l -i 'group\|api' {} \; | head -10

Repository: netgrif/application-engine

Length of output: 3518


The legacy GET /api/group/all route has changed incompatibly and needs explicit migration documentation.

The new controller at auth/web/GroupController.java uses @RequestMapping("api/groups") and serves GET /api/groups/{realmId}/all instead. Existing clients calling the old GET /api/group/all endpoint will receive 404s. Either restore the legacy route in the new controller, or document this as a breaking change in your migration guide with upgrade instructions for affected clients. The commented-out source code should be deleted unless there is a documented migration path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@application-engine/src/main/java/com/netgrif/application/engine/orgstructure/web/GroupController.java`
around lines 24 - 55, The legacy GET /api/group/all route was removed and the
commented GroupController/getAllGroups is left in-source; either reintroduce
compatible routing in the new GroupController (add an endpoint that maps GET
/api/group/all and delegates to the existing service/getAllGroups logic or
routes to the new handler) or remove the commented legacy controller and add
explicit migration docs explaining the breaking change and the new path format
(api/groups/{realmId}/all) with example client update steps; ensure references
to `@RequestMapping`, GroupController, and getAllGroups are updated/removed
consistently and do not leave commented legacy endpoints in the codebase.

Comment on lines +21 to +27
public static GroupDto fromGroup(Group group, Locale locale) {
return new GroupDto(group.getStringId(), group.getDisplayName(), group.getIdentifier(), group.getOwnerUsername(),
group.getAuthoritySet().stream().map(AuthorityDto::fromAuthority).collect(Collectors.toSet()),
group.getProcessRoles().stream().map(processRole -> new ProcessRoleDto(processRole, locale)).collect(Collectors.toSet()),
group.getGroupIds(),
group.getSubgroupIds()
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C4 'getGroupIds|getSubgroupIds|groupIds|subgroupIds|subGroupIds' \
  nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/Group.java || true

Repository: netgrif/application-engine

Length of output: 1454


🏁 Script executed:

rg -n 'getGroupIds|getSubGroupIds|getSubgroupIds' nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/Group.java -A 3

Repository: netgrif/application-engine

Length of output: 52


🏁 Script executed:

rg -n 'private.*groupIds|public.*get.*[Gg]roup[Ii]ds' nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/Group.java -A 3

Repository: netgrif/application-engine

Length of output: 226


🏁 Script executed:

wc -l nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/Group.java

Repository: netgrif/application-engine

Length of output: 168


🏁 Script executed:

cat -n nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/Group.java | head -100

Repository: netgrif/application-engine

Length of output: 3221


🏁 Script executed:

cat -n nae-object-library/src/main/java/com/netgrif/application/engine/objects/dto/response/group/GroupDto.java

Repository: netgrif/application-engine

Length of output: 1623


🏁 Script executed:

cat -n nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/Group.java | tail -100

Repository: netgrif/application-engine

Length of output: 3709


🏁 Script executed:

find nae-object-library -name "AbstractActor.java" -exec cat -n {} \;

Repository: netgrif/application-engine

Length of output: 11777


Copy ID collections before storing them in the record.

The factory materializes new sets for authorities and roles (lines 23-24), but lines 25-26 pass groupIds and subGroupIds directly. Since Lombok's @Getter returns direct field references and these setters only defend writes, the DTO receives mutable backing collections. Subsequent mutations to the Group's ID sets will leak into the response.

🧱 Proposed fix
         return new GroupDto(group.getStringId(), group.getDisplayName(), group.getIdentifier(), group.getOwnerUsername(),
                 group.getAuthoritySet().stream().map(AuthorityDto::fromAuthority).collect(Collectors.toSet()),
                 group.getProcessRoles().stream().map(processRole -> new ProcessRoleDto(processRole, locale)).collect(Collectors.toSet()),
-                group.getGroupIds(),
-                group.getSubgroupIds()
+                group.getGroupIds() == null ? Set.of() : Set.copyOf(group.getGroupIds()),
+                group.getSubgroupIds() == null ? Set.of() : Set.copyOf(group.getSubgroupIds())
         );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@nae-object-library/src/main/java/com/netgrif/application/engine/objects/dto/response/group/GroupDto.java`
around lines 21 - 27, The factory method GroupDto.fromGroup currently passes
group.getGroupIds() and group.getSubgroupIds() directly into the GroupDto, which
leaks mutable backing collections; make defensive copies before storing them in
the DTO by replacing those arguments with new collection instances created from
group.getGroupIds() and group.getSubgroupIds() (e.g., copy into new
HashSet/ArrayList or Collections.unmodifiableSet) so the DTO holds its own
immutable/mutably-isolated copies.

Comment on lines 7 to +8
@Service
public class AuthorizationService implements IAuthorizationService {
public class AuthorizationServiceImpl implements AuthorizationService {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

fd 'AuthorizationServiceImpl\.java$' | xargs -r rg -n -C2 '@Service|class AuthorizationServiceImpl'
rg -n '@PreAuthorize\(".*authorizationServiceImpl' --glob '*.java'
rg -n '@PreAuthorize\(".*authorizationService\.' --glob '*.java'

Repository: netgrif/application-engine

Length of output: 4944


Pin an explicit Spring bean name to decouple security expressions from implementation details.

The class currently uses @Service without an explicit bean name, which causes Spring to auto-generate the bean ID as authorizationServiceImpl (implementation name). This couples 26 @PreAuthorize annotations across multiple controllers to the implementation class name instead of the interface. Add an explicit bean name to stabilize this and enable future refactoring without touching security logic.

♻️ Suggested change
-@Service
+@Service("authorizationService")
 public class AuthorizationServiceImpl implements AuthorizationService {

After applying this change, update the 26 @PreAuthorize annotations currently referencing @authorizationServiceImpl to use @authorizationService instead (references found in WorkflowController, ProcessRoleController, PetriNetController, ElasticController, UserController, and GroupController).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/AuthorizationServiceImpl.java`
around lines 7 - 8, The service bean is currently unnamed causing Spring to
generate authorizationServiceImpl and coupling security expressions; update the
class declaration for AuthorizationServiceImpl (which implements
AuthorizationService) to declare an explicit bean name (e.g.
`@Service`("authorizationService")) so the bean id is stable, then update all
`@PreAuthorize` references that use `@authorizationServiceImpl` to use
`@authorizationService` instead in the controllers that reference it
(WorkflowController, ProcessRoleController, PetriNetController,
ElasticController, UserController, GroupController); ensure the interface name
AuthorizationService remains the type and only the bean name changes.

Comment on lines +239 to +247
public Group assignUsersToGroup(String groupId, Set<String> userIds) {
Group group = this.findById(groupId);
Set<String> currentGroupMemberIds = group.getMemberIds();

Set<String> removableMemberIds = new HashSet<>(currentGroupMemberIds);
removableMemberIds.removeAll(userIds);

Set<String> newMemberIds = new HashSet<>(userIds);
newMemberIds.removeAll(currentGroupMemberIds);
Copy link

@coderabbitai coderabbitai bot Mar 11, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Null-safe the bulk diff logic before creating HashSets.

group.getMemberIds() / parentGroup.getSubgroupIds() can be null in this class, and the incoming replacement sets can also be null. Both new HashSet<>(currentGroupMemberIds) and new HashSet<>(childGroupIds) will then fail with NullPointerException before any validation runs.

💡 Suggested fix
 public Group assignUsersToGroup(String groupId, Set<String> userIds) {
     Group group = this.findById(groupId);
-    Set<String> currentGroupMemberIds = group.getMemberIds();
+    Set<String> currentGroupMemberIds = Optional.ofNullable(group.getMemberIds()).orElseGet(Collections::emptySet);
+    Set<String> requestedUserIds = Optional.ofNullable(userIds).orElseGet(Collections::emptySet);

     Set<String> removableMemberIds = new HashSet<>(currentGroupMemberIds);
-    removableMemberIds.removeAll(userIds);
+    removableMemberIds.removeAll(requestedUserIds);

-    Set<String> newMemberIds = new HashSet<>(userIds);
+    Set<String> newMemberIds = new HashSet<>(requestedUserIds);
     newMemberIds.removeAll(currentGroupMemberIds);

Apply the same normalization in assignSubgroups(...).

Also applies to: 367-375

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/GroupServiceImpl.java`
around lines 239 - 247, The bulk-diff logic in assignUsersToGroup (and similarly
in assignSubgroups) assumes group.getMemberIds()/parentGroup.getSubgroupIds()
and the incoming userIds/childGroupIds are non-null; create defensive
normalization before constructing new HashSet instances by replacing any null
with Collections.emptySet() or new HashSet<>() (e.g., Set<String>
currentGroupMemberIds =
Optional.ofNullable(group.getMemberIds()).orElse(Collections.emptySet()); and
Set<String> userIdsSafe =
Optional.ofNullable(userIds).orElse(Collections.emptySet());), then compute
removableMemberIds and newMemberIds from these safe sets; apply the same pattern
in assignSubgroups using parentGroup.getSubgroupIds() and childGroupIds to avoid
NPEs.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

Copy link

Choose a reason for hiding this comment

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

@renczesstefan, thanks for the update! The incoming userIds null-check in assignUsersToGroup looks good, but the fix is still incomplete in two places:

  1. assignUsersToGroupgroup.getMemberIds() is still not null-safe. If it returns null, new HashSet<>(currentGroupMemberIds) will throw a NullPointerException:
// still needed
Set<String> currentGroupMemberIds = Optional.ofNullable(group.getMemberIds()).orElseGet(Collections::emptySet);
  1. assignSubgroups — neither parentGroup.getSubgroupIds() nor the incoming childGroupIds are guarded:
childGroupIds = childGroupIds == null ? new HashSet<>() : childGroupIds;
Set<String> currentSubgroupIds = Optional.ofNullable(parentGroup.getSubgroupIds()).orElseGet(Collections::emptySet);

Could you apply these remaining guards as well?

Comment on lines +87 to +89
public static UserDto createUser(AbstractUser user, List<GroupDto> groups) {
UserDto result = createUser(user);
result.setGroups(new HashSet<>(groups));
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Handle null in the new groups overload.

new HashSet<>(groups) will throw immediately when the caller omits expanded group data. This factory has no non-null contract, so it should degrade to an empty set instead of failing at runtime.

💡 Proposed fix
     public static UserDto createUser(AbstractUser user, List<GroupDto> groups) {
         UserDto result = createUser(user);
-        result.setGroups(new HashSet<>(groups));
+        result.setGroups(groups == null ? Set.of() : new HashSet<>(groups));
         return result;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@nae-user-common/src/main/java/com/netgrif/application/engine/auth/web/responsebodies/UserDto.java`
around lines 87 - 89, The createUser(AbstractUser user, List<GroupDto> groups)
factory in UserDto currently does new HashSet<>(groups) which throws when groups
is null; update the method (UserDto.createUser) to treat a null groups argument
as an empty collection before constructing the HashSet (e.g., use an explicit
null check or default to Collections.emptySet()/Collections.emptyList()) so
result.setGroups(...) always receives a non-null, possibly-empty Set.

Added group fetching and mapping in `UserFactoryImpl` to include group details in `UserDto`. Updated `createUser` method in `UserDto` to handle optional group data gracefully. Removed obsolete `getUser` method from `UserFactoryImpl`.
@coderabbitai coderabbitai bot added the new feature A change that introduces new functionality label Mar 11, 2026
@coderabbitai coderabbitai bot added the breaking change Fix or feature that would cause existing functionality doesn't work as expected label Mar 11, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserFactoryImpl.java`:
- Around line 26-27: In UserFactoryImpl.getUser, guard against a null
AbstractUser.getGroupIds() before calling groupService.findAllByIds: compute a
local Collection<Long> groupIds = (user.getGroupIds() == null ?
Collections.emptyList() : user.getGroupIds()), call
groupService.findAllByIds(groupIds, Pageable.unpaged()), map results with
GroupDto.fromGroup(locale) into a List<GroupDto> groups variable, then pass that
groups list into UserDto.createUser; this both prevents NPEs and makes the line
more readable.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 485ad185-6934-41be-a39a-c735874bddeb

📥 Commits

Reviewing files that changed from the base of the PR and between 4c39084 and f342588.

📒 Files selected for processing (2)
  • nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserFactoryImpl.java
  • nae-user-common/src/main/java/com/netgrif/application/engine/auth/web/responsebodies/UserDto.java

Comment on lines +26 to +27
public UserDto getUser(AbstractUser user, Locale locale) {
UserDto result = UserDto.createUser(user, groupService.findAllByIds(user.getGroupIds(), Pageable.unpaged()).stream().map(group -> GroupDto.fromGroup(group, locale)).collect(Collectors.toList()));
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check the GroupService.findAllByIds method signature and implementation for null handling

# Find the GroupService interface definition
ast-grep --pattern $'interface GroupService {
  $$$
  findAllByIds($_, $_) {
    $$$
  }
  $$$
}'

# Search for findAllByIds method signatures
rg -n -A5 'findAllByIds.*Set.*String'

Repository: netgrif/application-engine

Length of output: 1811


🌐 Web query:

netgrif application engine GroupService findAllByIds null handling

💡 Result:

In NETGRIF Application Engine, the group service method is INextGroupService#findByIds(Collection<String> groupIds) (there is no findAllByIds on the group service in the public API). [1]

For null handling, you should not pass null as the IDs collection. Under the hood, these lookups typically delegate to Spring Data repository methods like CrudRepository#findAllById(Iterable<ID> ids), whose parameters are required to be non-null (“must not be null”), so null will result in a runtime exception (commonly IllegalArgumentException / NullPointerException, depending on the call path). [2]

Recommended pattern:

Collection<String> idsSafe = (ids == null) ? List.of() : ids;
List<Case> groups = groupService.findByIds(idsSafe);

Sources: INextGroupService Javadoc [1], Spring Data CrudRepository Javadoc (findAllById parameter must not be null) [2].


Potential NullPointerException if user.getGroupIds() returns null.

The group service method requires a non-null collection parameter. If AbstractUser.getGroupIds() returns null, it will cause a runtime exception. Add a null-safety check before passing group IDs to the service.

The line is also quite long; extracting the group mapping logic improves readability.

Proposed null-safe refactoring
     `@Override`
     public UserDto getUser(AbstractUser user, Locale locale) {
-        UserDto result = UserDto.createUser(user, groupService.findAllByIds(user.getGroupIds(), Pageable.unpaged()).stream().map(group -> GroupDto.fromGroup(group, locale)).collect(Collectors.toList()));
+        Collection<String> groupIds = user.getGroupIds();
+        List<GroupDto> groupDtos = (groupIds != null && !groupIds.isEmpty())
+            ? groupService.findAllByIds(groupIds, Pageable.unpaged())
+                .stream()
+                .map(group -> GroupDto.fromGroup(group, locale))
+                .collect(Collectors.toList())
+            : List.of();
+        UserDto result = UserDto.createUser(user, groupDtos);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public UserDto getUser(AbstractUser user, Locale locale) {
UserDto result = UserDto.createUser(user, groupService.findAllByIds(user.getGroupIds(), Pageable.unpaged()).stream().map(group -> GroupDto.fromGroup(group, locale)).collect(Collectors.toList()));
public UserDto getUser(AbstractUser user, Locale locale) {
Collection<String> groupIds = user.getGroupIds();
List<GroupDto> groupDtos = (groupIds != null && !groupIds.isEmpty())
? groupService.findAllByIds(groupIds, Pageable.unpaged())
.stream()
.map(group -> GroupDto.fromGroup(group, locale))
.collect(Collectors.toList())
: List.of();
UserDto result = UserDto.createUser(user, groupDtos);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserFactoryImpl.java`
around lines 26 - 27, In UserFactoryImpl.getUser, guard against a null
AbstractUser.getGroupIds() before calling groupService.findAllByIds: compute a
local Collection<Long> groupIds = (user.getGroupIds() == null ?
Collections.emptyList() : user.getGroupIds()), call
groupService.findAllByIds(groupIds, Pageable.unpaged()), map results with
GroupDto.fromGroup(locale) into a List<GroupDto> groups variable, then pass that
groups list into UserDto.createUser; this both prevents NPEs and makes the line
more readable.

Reordered parameters in `addUser` and `removeUser` methods across the codebase to consistently place `group` before `user`. This improves clarity and aligns method signatures for better maintainability and usage consistency.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (7)
nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/GroupServiceImpl.java (2)

239-252: ⚠️ Potential issue | 🟠 Major

Null-safe the bulk diff logic before creating HashSets.

group.getMemberIds() and the incoming userIds set can both be null. Passing null to new HashSet<>(null) throws NullPointerException. Normalize both values before computing the diff.

💡 Proposed fix
 `@Override`
 public Group assignUsersToGroup(String groupId, Set<String> userIds) {
     Group group = this.findById(groupId);
-    Set<String> currentGroupMemberIds = group.getMemberIds();
+    Set<String> currentGroupMemberIds = Optional.ofNullable(group.getMemberIds()).orElseGet(Collections::emptySet);
+    Set<String> requestedUserIds = Optional.ofNullable(userIds).orElseGet(Collections::emptySet);

-    Set<String> removableMemberIds = new HashSet<>(currentGroupMemberIds);
-    removableMemberIds.removeAll(userIds);
+    Set<String> removableMemberIds = new HashSet<>(currentGroupMemberIds);
+    removableMemberIds.removeAll(requestedUserIds);

-    Set<String> newMemberIds = new HashSet<>(userIds);
+    Set<String> newMemberIds = new HashSet<>(requestedUserIds);
     newMemberIds.removeAll(currentGroupMemberIds);

     removableMemberIds.forEach(toBeRemovedId -> removeUser(group, userService.findById(toBeRemovedId, group.getRealmId())));
     newMemberIds.forEach(toBeAddedId -> addUser(group, userService.findById(toBeAddedId, group.getRealmId())));
     return group;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/GroupServiceImpl.java`
around lines 239 - 252, Make the bulk-diff null-safe in assignUsersToGroup by
normalizing group.getMemberIds() and the incoming userIds to empty sets before
constructing removableMemberIds and newMemberIds; ensure you never call new
HashSet<>(null) — use a safe empty Set fallback for currentGroupMemberIds and
userIds, then compute removableMemberIds and newMemberIds and continue calling
removeUser(...) and addUser(...) with userService.findById(...) as before.

366-380: ⚠️ Potential issue | 🟠 Major

Apply the same null-safety fix for assignSubgroups.

Same NPE risk as assignUsersToGroup: parentGroup.getSubgroupIds() and childGroupIds can be null.

💡 Proposed fix
 `@Override`
 public Group assignSubgroups(String parentGroupId, Set<String> childGroupIds) {
     Group parentGroup = this.findById(parentGroupId);
-    Set<String> currentSubgroupIds = parentGroup.getSubgroupIds();
+    Set<String> currentSubgroupIds = Optional.ofNullable(parentGroup.getSubgroupIds()).orElseGet(Collections::emptySet);
+    Set<String> requestedChildIds = Optional.ofNullable(childGroupIds).orElseGet(Collections::emptySet);

-    Set<String> removableGroupIds = new HashSet<>(currentSubgroupIds);
-    removableGroupIds.removeAll(childGroupIds);
+    Set<String> removableGroupIds = new HashSet<>(currentSubgroupIds);
+    removableGroupIds.removeAll(requestedChildIds);

-    Set<String> newSubgroupIds = new HashSet<>(childGroupIds);
+    Set<String> newSubgroupIds = new HashSet<>(requestedChildIds);
     newSubgroupIds.removeAll(currentSubgroupIds);
     ...
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/GroupServiceImpl.java`
around lines 366 - 380, The assignSubgroups method can NPE when
parentGroup.getSubgroupIds() or childGroupIds are null; mirror the fix used in
assignUsersToGroup by normalizing both to non-null sets before computing
removable/new sets. Specifically, in assignSubgroups retrieve parentGroup via
findById, then replace parentGroup.getSubgroupIds() with a safeSet (e.g.,
currentSubgroupIds =
Optional.ofNullable(parentGroup.getSubgroupIds()).orElseGet(HashSet::new)) and
likewise ensure childGroupIds =
Optional.ofNullable(childGroupIds).orElseGet(HashSet::new); then compute
removableGroupIds/newSubgroupIds and call removeSubgroup/addSubgroup as before.
application-engine/src/main/java/com/netgrif/application/engine/auth/web/GroupController.java (4)

92-94: ⚠️ Potential issue | 🟠 Major

Resolve owner within the requested realm.

The owner lookup at Line 92 uses null for the realm parameter instead of request.realmId(). This could allow creating a group in one realm with an owner from a different realm, breaking multi-tenancy isolation.

-AbstractUser user = userService.findById(request.ownerId(), null);
+AbstractUser user = userService.findById(request.ownerId(), request.realmId());
 if (user == null) {
-    return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ResponseMessage.createErrorMessage("User with id [%s] not found".formatted(request.ownerId())));
+    return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ResponseMessage.createErrorMessage("User with id [%s] not found in realm [%s]".formatted(request.ownerId(), request.realmId())));
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@application-engine/src/main/java/com/netgrif/application/engine/auth/web/GroupController.java`
around lines 92 - 94, The owner lookup in GroupController uses
userService.findById(request.ownerId(), null) which ignores realm isolation;
update the lookup to pass the requested realm (use request.realmId()) so the
owner is resolved within the same realm as the group creation (locate the
userService.findById call inside the GroupController method handling the group
create request and replace the null realm argument with request.realmId()).

67-70: ⚠️ Potential issue | 🟠 Major

Pagination metadata is lost when transforming to DTOs.

The transformPageContent helper at Line 450 uses groups.size() as the total element count, which is just the current page size—not the actual total. This breaks pagination for clients, as they cannot determine total pages or navigate correctly.

💡 Proposed fix
-private Page<GroupDto> transformPageContent(List<Group> groups, Pageable pageable, Locale locale) {
-    return new PageImpl<>(groups.stream().map(group -> GroupDto.fromGroup(group, locale)).toList(), pageable, groups.size());
+private Page<GroupDto> transformPageContent(Page<Group> groups, Locale locale) {
+    return new PageImpl<>(
+            groups.getContent().stream().map(group -> GroupDto.fromGroup(group, locale)).toList(),
+            groups.getPageable(),
+            groups.getTotalElements()
+    );
 }

Then update callers:

 // In getAllGroupsOfRealm:
 Page<Group> groups = groupService.findAllFromRealm(realmId, pageable);
-return ResponseEntity.ok(transformPageContent(groups.getContent(), pageable, locale));
+return ResponseEntity.ok(transformPageContent(groups, locale));

 // In search:
-List<Group> groups = groupService.search(query, pageable).getContent();
-return ResponseEntity.ok(transformPageContent(groups, pageable, locale));
+Page<Group> groups = groupService.search(query, pageable);
+return ResponseEntity.ok(transformPageContent(groups, locale));

Also applies to: 355-357, 450-452

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@application-engine/src/main/java/com/netgrif/application/engine/auth/web/GroupController.java`
around lines 67 - 70, The transformation loses pagination metadata because
transformPageContent uses collection.size() instead of the Page total; update
transformPageContent (used by methods like GroupController.getAllGroupsOfRealm
and other callers) to accept the original Page<T> or an explicit totalElements
parameter and use page.getTotalElements() (or the passed total) when
constructing the DTO Page<Page<GroupDto>>/PageImpl so totalPages and navigation
remain correct; then update all callers (e.g., getAllGroupsOfRealm) to pass the
original Page or totalElements rather than relying on the page content size.

273-283: ⚠️ Potential issue | 🟠 Major

Batch mutations are not atomic—partial updates persist on failure.

Multiple endpoints iterate over a set of IDs (e.g., roleIds.forEach(...)) and call individual service methods. If one ID is invalid, earlier operations have already been committed, leaving the group in an inconsistent state while returning a 400 error.

Consider either:

  1. Validating all IDs upfront before any mutations
  2. Using a single transactional bulk operation in the service layer
  3. Accepting partial success and documenting it in the API

Example for addRolesToGroup:

// Option 1: Pre-validate
Set<ProcessRole> roles = roleIds.stream()
    .map(id -> processRoleService.findById(new ProcessResourceId(id)))
    .peek(role -> {
        if (role == null) throw new IllegalArgumentException("Role not found");
    })
    .collect(Collectors.toSet());
// Then apply all at once

Also applies to: 294-304, 315-325, 336-346, 417-427, 438-448

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@application-engine/src/main/java/com/netgrif/application/engine/auth/web/GroupController.java`
around lines 273 - 283, The loop in addRolesToGroup calls groupService.addRole
per ID which allows partial commits on error; instead either validate all
roleIds up front (e.g., resolve each ID to a domain Role and throw if any
missing) before calling any mutation, or add a single transactional bulk service
method (e.g., groupService.addRoles(groupId, Set<String>) or
groupService.addRolesTransactional) that performs the whole operation inside a
transaction and throws to trigger rollback; apply the same pattern for the other
endpoints that iterate and call per-id service methods (the blocks using
roleIds.forEach(...) / groupService.addRole and their counterparts).

96-98: ⚠️ Potential issue | 🟡 Minor

Return HTTP 201 Created for successful group creation.

The @ApiResponse at Line 77 documents response code 201 for successful creation, but the implementation at Line 98 returns 200 OK. Use HttpStatus.CREATED for consistency with REST conventions and the documented API contract.

         groupService.create(request.identifier(), request.displayName(), user);
-        return ResponseEntity.ok(ResponseMessage.createSuccessMessage("Group created successfully"));
+        return ResponseEntity.status(HttpStatus.CREATED).body(ResponseMessage.createSuccessMessage("Group created successfully"));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@application-engine/src/main/java/com/netgrif/application/engine/auth/web/GroupController.java`
around lines 96 - 98, The controller currently returns ResponseEntity.ok(...)
after calling groupService.create(request.identifier(), request.displayName(),
user);; change the response to return HTTP 201 Created instead of 200 OK by
replacing the ResponseEntity.ok(...) usage with a ResponseEntity constructed
with HttpStatus.CREATED (while still returning
ResponseMessage.createSuccessMessage("Group created successfully")). Update the
return in the method that calls groupService.create(...) so it matches the
`@ApiResponse` documentation.
nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/GroupService.java (1)

208-215: 🧹 Nitpick | 🔵 Trivial

Javadoc parameter name mismatch.

The @param tag at Line 212 references groupIdentifier, but the actual method parameter is named groupId. This inconsistency can confuse developers using the API.

     /**
      * Removes a user from a group specified by its unique identifier.
      *
-     * `@param` user            the user to be removed
-     * `@param` groupIdentifier the unique identifier of the target group
+     * `@param` groupId the ID of the group
+     * `@param` user    the user to be removed
      * `@return` the updated {`@link` Group}
      */
     Group removeUser(String groupId, AbstractUser user);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/GroupService.java`
around lines 208 - 215, The Javadoc for GroupService.removeUser references a
non-existent parameter name `groupIdentifier`; update the Javadoc `@param` to
match the actual method signature parameter `groupId` (or rename the method
parameter to `groupIdentifier` if you prefer that name) so the `@param` tag and
method parameter (GroupService.removeUser(String groupId, AbstractUser user))
are consistent and accurate.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@application-engine/src/main/java/com/netgrif/application/engine/auth/web/GroupController.java`:
- Around line 348-358: The search method in GroupController (public
ResponseEntity<Page<GroupDto>> search(...)) is missing an `@PreAuthorize`
restriction; add the same `@PreAuthorize` expression used by the other endpoints
in GroupController (e.g., the one applied to create/update/delete methods) to
restrict who can call search and prevent unrestricted group enumeration, ensure
the annotation is placed immediately above the `@Operation/`@GetMapping and import
the required Spring Security annotation.

In
`@nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/GroupServiceImpl.java`:
- Around line 283-286: The method signature for removeUser in GroupServiceImpl
does not match the GroupService interface—change the signature from
removeUser(String userId, String groupId, String realmId) to removeUser(String
groupId, String userId, String realmId) so the `@Override` is correct, and ensure
the implementation calls removeUser(groupId, userService.findById(userId,
realmId)) (keeping the userService.findById call using the userId and realmId in
that order).

In
`@nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/GroupService.java`:
- Around line 252-266: Add Javadoc comments for the two overloaded methods in
the GroupService interface: addAuthority(Group group, Authority authority) and
removeAuthority(Group group, Authority authority). For each method, document the
purpose, parameters (describe Group and Authority parameters), and the return
value (the updated Group), following the style of the existing
removeAuthority(String, String) comment so API docs remain consistent and
complete.

---

Duplicate comments:
In
`@application-engine/src/main/java/com/netgrif/application/engine/auth/web/GroupController.java`:
- Around line 92-94: The owner lookup in GroupController uses
userService.findById(request.ownerId(), null) which ignores realm isolation;
update the lookup to pass the requested realm (use request.realmId()) so the
owner is resolved within the same realm as the group creation (locate the
userService.findById call inside the GroupController method handling the group
create request and replace the null realm argument with request.realmId()).
- Around line 67-70: The transformation loses pagination metadata because
transformPageContent uses collection.size() instead of the Page total; update
transformPageContent (used by methods like GroupController.getAllGroupsOfRealm
and other callers) to accept the original Page<T> or an explicit totalElements
parameter and use page.getTotalElements() (or the passed total) when
constructing the DTO Page<Page<GroupDto>>/PageImpl so totalPages and navigation
remain correct; then update all callers (e.g., getAllGroupsOfRealm) to pass the
original Page or totalElements rather than relying on the page content size.
- Around line 273-283: The loop in addRolesToGroup calls groupService.addRole
per ID which allows partial commits on error; instead either validate all
roleIds up front (e.g., resolve each ID to a domain Role and throw if any
missing) before calling any mutation, or add a single transactional bulk service
method (e.g., groupService.addRoles(groupId, Set<String>) or
groupService.addRolesTransactional) that performs the whole operation inside a
transaction and throws to trigger rollback; apply the same pattern for the other
endpoints that iterate and call per-id service methods (the blocks using
roleIds.forEach(...) / groupService.addRole and their counterparts).
- Around line 96-98: The controller currently returns ResponseEntity.ok(...)
after calling groupService.create(request.identifier(), request.displayName(),
user);; change the response to return HTTP 201 Created instead of 200 OK by
replacing the ResponseEntity.ok(...) usage with a ResponseEntity constructed
with HttpStatus.CREATED (while still returning
ResponseMessage.createSuccessMessage("Group created successfully")). Update the
return in the method that calls groupService.create(...) so it matches the
`@ApiResponse` documentation.

In
`@nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/GroupServiceImpl.java`:
- Around line 239-252: Make the bulk-diff null-safe in assignUsersToGroup by
normalizing group.getMemberIds() and the incoming userIds to empty sets before
constructing removableMemberIds and newMemberIds; ensure you never call new
HashSet<>(null) — use a safe empty Set fallback for currentGroupMemberIds and
userIds, then compute removableMemberIds and newMemberIds and continue calling
removeUser(...) and addUser(...) with userService.findById(...) as before.
- Around line 366-380: The assignSubgroups method can NPE when
parentGroup.getSubgroupIds() or childGroupIds are null; mirror the fix used in
assignUsersToGroup by normalizing both to non-null sets before computing
removable/new sets. Specifically, in assignSubgroups retrieve parentGroup via
findById, then replace parentGroup.getSubgroupIds() with a safeSet (e.g.,
currentSubgroupIds =
Optional.ofNullable(parentGroup.getSubgroupIds()).orElseGet(HashSet::new)) and
likewise ensure childGroupIds =
Optional.ofNullable(childGroupIds).orElseGet(HashSet::new); then compute
removableGroupIds/newSubgroupIds and call removeSubgroup/addSubgroup as before.

In
`@nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/GroupService.java`:
- Around line 208-215: The Javadoc for GroupService.removeUser references a
non-existent parameter name `groupIdentifier`; update the Javadoc `@param` to
match the actual method signature parameter `groupId` (or rename the method
parameter to `groupIdentifier` if you prefer that name) so the `@param` tag and
method parameter (GroupService.removeUser(String groupId, AbstractUser user))
are consistent and accurate.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0942a262-4e8a-4486-8c87-093332393f87

📥 Commits

Reviewing files that changed from the base of the PR and between f342588 and accafcd.

📒 Files selected for processing (6)
  • application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy
  • application-engine/src/main/java/com/netgrif/application/engine/auth/service/RegistrationService.java
  • application-engine/src/main/java/com/netgrif/application/engine/auth/web/GroupController.java
  • application-engine/src/main/java/com/netgrif/application/engine/startup/runner/SuperCreatorRunner.java
  • nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/GroupServiceImpl.java
  • nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/GroupService.java

Comment on lines +348 to +358
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Groups retrieved successfully"),
@ApiResponse(responseCode = "400", description = "Invalid group data"),
@ApiResponse(responseCode = "500", description = "Internal server error")
})
@Operation(summary = "Generic group search", security = {@SecurityRequirement(name = "X-Auth-Token")})
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Page<GroupDto>> search(GroupSearchRequestDto query, Pageable pageable, Locale locale) {
List<Group> groups = groupService.search(query, pageable).getContent();
return ResponseEntity.ok(transformPageContent(groups, pageable, locale));
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing authorization on search endpoint.

The search endpoint at Line 354 lacks @PreAuthorize annotation, unlike all other endpoints in this controller. This allows any authenticated user to search and enumerate groups, potentially exposing sensitive organizational structure. Add authorization if this is unintended.

+@PreAuthorize("@authorizationServiceImpl.hasAuthority('ADMIN')")
 `@ApiResponses`(value = {
         `@ApiResponse`(responseCode = "200", description = "Groups retrieved successfully"),
         `@ApiResponse`(responseCode = "400", description = "Invalid group data"),
+        `@ApiResponse`(responseCode = "403", description = "Caller doesn't fulfill the authorisation requirements"),
         `@ApiResponse`(responseCode = "500", description = "Internal server error")
 })
 `@Operation`(summary = "Generic group search", security = {`@SecurityRequirement`(name = "X-Auth-Token")})
 `@GetMapping`(produces = MediaType.APPLICATION_JSON_VALUE)
 public ResponseEntity<Page<GroupDto>> search(GroupSearchRequestDto query, Pageable pageable, Locale locale) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@application-engine/src/main/java/com/netgrif/application/engine/auth/web/GroupController.java`
around lines 348 - 358, The search method in GroupController (public
ResponseEntity<Page<GroupDto>> search(...)) is missing an `@PreAuthorize`
restriction; add the same `@PreAuthorize` expression used by the other endpoints
in GroupController (e.g., the one applied to create/update/delete methods) to
restrict who can call search and prevent unrestricted group enumeration, ensure
the annotation is placed immediately above the `@Operation/`@GetMapping and import
the required Spring Security annotation.

Introduced a new method to remove an authority from a Group object directly, enhancing flexibility in authority management. This complements the existing method by providing an additional, more intuitive way to update groups.
Introduced a new method `addAuthority(Group, Authority)` in `GroupService` to enable assigning a specific authority to a group. This enhances the service by allowing direct updates to a group's associated authorities.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/GroupService.java`:
- Around line 208-215: Update the Javadoc for GroupService.removeUser so the
`@param` name matches the method signature: replace the incorrect `@param`
groupIdentifier with `@param` groupId (or alternatively rename the method
parameter to groupIdentifier and update references); ensure the Javadoc text
describes the same value (the unique identifier of the target group) and that
the method signature Group removeUser(String groupId, AbstractUser user) stays
consistent with the documentation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 94254b58-a55d-4da9-8a4f-fc855701abd6

📥 Commits

Reviewing files that changed from the base of the PR and between accafcd and 23db6a2.

📒 Files selected for processing (1)
  • nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/GroupService.java

Comment on lines 208 to +215
/**
* Removes a user from a group specified by identifier.
* Removes a user from a group specified by its unique identifier.
*
* @param user the user to remove
* @param groupIdentifier identifier of the target group
* @param user the user to be removed
* @param groupIdentifier the unique identifier of the target group
* @return the updated {@link Group}
*/
Group removeUser(AbstractUser user, String groupIdentifier);
Group removeUser(String groupId, AbstractUser user);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Javadoc parameter name doesn't match method signature.

The Javadoc on line 212 references @param groupIdentifier but the actual method parameter on line 215 is named groupId. This creates confusion for API consumers.

📝 Proposed fix
     /**
      * Removes a user from a group specified by its unique identifier.
      *
-     * `@param` user            the user to be removed
-     * `@param` groupIdentifier the unique identifier of the target group
+     * `@param` groupId the unique identifier of the target group
+     * `@param` user    the user to be removed
      * `@return` the updated {`@link` Group}
      */
     Group removeUser(String groupId, AbstractUser user);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/GroupService.java`
around lines 208 - 215, Update the Javadoc for GroupService.removeUser so the
`@param` name matches the method signature: replace the incorrect `@param`
groupIdentifier with `@param` groupId (or alternatively rename the method
parameter to groupIdentifier and update references); ensure the Javadoc text
describes the same value (the unique identifier of the target group) and that
the method signature Group removeUser(String groupId, AbstractUser user) stays
consistent with the documentation.

Removed unnecessary blank lines to improve code readability and maintain consistent formatting in the GroupService class. No functionality was altered.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/GroupService.java`:
- Around line 198-205: Fix the Javadoc formatting for the removeUser method in
GroupService by ensuring each line in the comment block follows the standard
Javadoc pattern (a space after the leading asterisk). Update the lines
describing parameters so they start with " * " (e.g., " * `@param` groupId ...", "
* `@param` userId ...", " * `@param` realmId ...") to match the rest of the block
above the method Group removeUser(String groupId, String userId, String
realmId).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 834b6b51-d01d-4c1b-8517-0c1d56b729ee

📥 Commits

Reviewing files that changed from the base of the PR and between 23db6a2 and 49bf891.

📒 Files selected for processing (1)
  • nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/GroupService.java

Comment on lines 198 to +205
/**
* Removes a user from a group specified by identifier.
* Removes a user from a group within a specific realm.
* @param groupId the unique identifier of the group
* @param userId the ID of the user to remove
* @param realmId the ID of the realm where the group exists
* @return the updated {@link Group}
*/
Group removeUser(String groupId, String userId, String realmId);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Minor Javadoc formatting inconsistency.

Lines 200-201 are missing the leading space before the asterisk, while the rest of the Javadoc block uses standard formatting.

📝 Suggested fix
     /**
      * Removes a user from a group within a specific realm.
-    * `@param` groupId   the unique identifier of the group
-    * `@param` userId    the ID of the user to remove
+     * `@param` groupId  the unique identifier of the group
+     * `@param` userId   the ID of the user to remove
      * `@param` realmId  the ID of the realm where the group exists
      * `@return` the updated {`@link` Group}
      */
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* Removes a user from a group specified by identifier.
* Removes a user from a group within a specific realm.
* @param groupId the unique identifier of the group
* @param userId the ID of the user to remove
* @param realmId the ID of the realm where the group exists
* @return the updated {@link Group}
*/
Group removeUser(String groupId, String userId, String realmId);
/**
* Removes a user from a group within a specific realm.
* `@param` groupId the unique identifier of the group
* `@param` userId the ID of the user to remove
* `@param` realmId the ID of the realm where the group exists
* `@return` the updated {`@link` Group}
*/
Group removeUser(String groupId, String userId, String realmId);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/GroupService.java`
around lines 198 - 205, Fix the Javadoc formatting for the removeUser method in
GroupService by ensuring each line in the comment block follows the standard
Javadoc pattern (a space after the leading asterisk). Update the lines
describing parameters so they start with " * " (e.g., " * `@param` groupId ...", "
* `@param` userId ...", " * `@param` realmId ...") to match the rest of the block
above the method Group removeUser(String groupId, String userId, String
realmId).

Ensure proper initialization of memberIds and subgroupIds to avoid null references. Adjust method parameter order in removeUser for consistency and readability.
Introduced a method to find groups by process roles in `GroupService` and `GroupRepository`. Refactored role deletion to also handle removal of process roles from groups alongside users. This ensures roles of deleted Petri nets are cleaned up consistently across users and groups.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking change Fix or feature that would cause existing functionality doesn't work as expected improvement A change that improves on an existing feature Large Medium new feature A change that introduces new functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant