Conversation
There was a problem hiding this comment.
Pull request overview
This PR implements CRM (Customer Relationship Management) integration to support SAB (SURFconext Autorisatie Beheer) phaseout by allowing Invite to receive CRM messages about contact persons, organizations, and roles, and to provide SAB authorization attributes to service providers.
Changes:
- Adds a new CRM API endpoint (
/api/internal/v1/crm) for receiving POST (provision/invite users) and DELETE (remove users) requests from CRM - Implements SAB attribute generation in the Attribute Aggregator to return organization codes, GUIDs, and role URNs for CRM-managed users
- Adds database schema changes to track CRM-specific information (contact IDs, organization IDs, role IDs) in users, roles, and invitations tables
Reviewed changes
Copilot reviewed 26 out of 27 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| server/src/main/resources/db/mysql/migration/V55_0__crm_roles_users.sql | Adds CRM-related columns to roles, users, and invitations tables with appropriate indexes |
| server/src/main/resources/crm/crm_config.json | Configuration mapping SAB codes to role names and associated applications |
| server/src/main/resources/application.yml | Adds CRM configuration properties (API key, collab person prefix, config resource path) |
| server/src/main/java/invite/security/SecurityConfig.java | Adds security filter chain for CRM API with API-KEY header authentication |
| server/src/main/java/invite/repository/UserRepository.java | Adds query method to find users by CRM contact ID |
| server/src/main/java/invite/repository/RoleRepository.java | Adds query method to find roles by CRM role ID |
| server/src/main/java/invite/provision/scim/GroupURN.java | Updates URN generation to support SAB role URN format for CRM roles |
| server/src/main/java/invite/provision/ProvisioningServiceDefault.java | Removes unused roleRepository field |
| server/src/main/java/invite/model/User.java | Adds crmContactId field and reorders constructor |
| server/src/main/java/invite/model/Role.java | Adds CRM-related fields (organisation ID/code, role ID/name) |
| server/src/main/java/invite/model/Invitation.java | Adds crmContactId field |
| server/src/main/java/invite/mail/MailBox.java | Minor indentation fix |
| server/src/main/java/invite/exception/InvitationUniqueCrmOrganisationException.java | New exception for enforcing single-organization constraint per SAB requirements |
| server/src/main/java/invite/crm/CrmManageIdentifier.java | Record class for CRM manage identifier configuration |
| server/src/main/java/invite/crm/CrmConfigEntry.java | Record class for CRM configuration entries |
| server/src/main/java/invite/crm/CRMRole.java | DTO for CRM role data |
| server/src/main/java/invite/crm/CRMOrganisation.java | DTO for CRM organization data |
| server/src/main/java/invite/crm/CRMController.java | Main controller implementing CRM POST/DELETE endpoints for user provisioning and invitation |
| server/src/main/java/invite/crm/CRMContact.java | DTO for CRM contact data |
| server/src/main/java/invite/api/InvitationOperations.java | Refactors identityProviderName to static method for reuse |
| server/src/main/java/invite/api/InvitationController.java | Adds validation to enforce single-organization constraint for CRM invitations; expands imports |
| server/src/main/java/invite/aggregation/AttributeAggregatorController.java | Adds SAB organization codes/GUIDs to attribute aggregation response for CRM users |
| server/src/test/java/invite/crm/CRMControllerTest.java | Test cases for CRM contact provisioning and invitation flows |
| server/src/test/java/invite/AbstractMailTest.java | Adds myconext.uri to test configuration |
| server/src/test/resources/crm/put.json | Test data for CRM PUT requests |
| server/src/test/resources/crm/post.json | Test data for CRM POST requests |
| server/src/test/resources/crm/delete.json | Test data for CRM DELETE requests |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| null, | ||
| Language.en, | ||
| null, | ||
| null,//Defauly expiryDate |
There was a problem hiding this comment.
There is a typo in the comment: "Defauly" should be "Default".
| null,//Defauly expiryDate | |
| null,//Default expiryDate |
| null,//Defauly expiryDate | ||
| null, | ||
| invitationRoles, | ||
| null |
There was a problem hiding this comment.
The crmContactId is not being set on the created invitation. This means the checkCrmUniqueOrganisation validation in InvitationController will not work correctly for CRM invitations. The invitation should have its crmContactId set from crmContact.getContactId().
| null | |
| crmContact.getContactId() |
server/src/main/java/invite/aggregation/AttributeAggregatorController.java
Outdated
Show resolved
Hide resolved
| @DeleteMapping("") | ||
| public ResponseEntity<String> delete(@RequestBody CRMContact crmContact) { | ||
| LOG.debug("DELETE /api/external/v1/crm: " + crmContact); | ||
|
|
||
| List<User> users = userRepository.findByCrmContactId(crmContact.getContactId()); | ||
| users.forEach(user -> { | ||
| LOG.info("Deleting CRM user: " + user.getEmail()); | ||
|
|
||
| this.provisioningService.deleteUserRequest(user); | ||
| this.userRepository.delete(user); | ||
| }); | ||
|
|
||
| return ResponseEntity.ok().body("deleted"); | ||
| } |
There was a problem hiding this comment.
The DELETE endpoint deletes all users with the given contactId, which may be incorrect behavior. According to the issue description, a DELETE should remove roles, not delete the entire user. The delete.json test data includes organization information, suggesting that DELETE should remove roles for a specific contact-organization combination rather than deleting all users with that contactId. Additionally, there may be legitimate non-CRM roles that should be preserved.
| private boolean provisionUser(CRMContact crmContact) { | ||
| String sub = constructSub(crmContact); | ||
| Optional<User> optionalUser = userRepository.findBySubIgnoreCase(sub); | ||
| User user = optionalUser.orElseGet(() -> createUser(crmContact, sub)); | ||
| List<CRMRole> newCrmRoles = syncCrmRoles(crmContact, user); | ||
|
|
||
| List<Role> roles = convertCrmRolesToInviteRoles(crmContact, newCrmRoles); | ||
| roles | ||
| .forEach(role -> { | ||
| UserRole userRole = new UserRole(Authority.GUEST, role); | ||
| user.addUserRole(userRole); | ||
| this.provisioningService.updateGroupRequest(userRole, OperationType.add); | ||
| }); | ||
| userRepository.save(user); | ||
| return optionalUser.isEmpty(); | ||
| } |
There was a problem hiding this comment.
Missing audit logging for CRM operations. The userRoleAuditService is injected but never used. When user roles are added or removed through CRM operations, the changes should be logged using userRoleAuditService.logAction, as done in other controllers (InvitationController, UserRoleController, TeamsController).
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
See #650