Fix: Assign Internal API User to Internal Team and Eager-Load User’s Team Association (#3698)

# Description of Changes

- **What was changed**  
- Added logic in `InitialSecuritySetup` to assign the
`INTERNAL_API_USER` to a dedicated “internal” team both during initial
creation and on subsequent startups.
- Enhanced `assignUsersToDefaultTeamIfMissing()` to route the internal
API user to the `internalTeam`, while all other users go to the default
team.
- Switched the JPA mapping of `User.team` from `LAZY` to `EAGER` fetch
to ensure the team association is always loaded with the user.
- Introduced a new `UserService.changeUserTeam(User, Team)` method to
handle moving an existing user to a different team and persist the
change.
- Imported `java.util.Optional` to safely handle lookups of the internal
API user.

- **Why the change was made**  
- To guarantee that the special internal API user is always part of the
“internal” team and never left on the default team, preventing
permission and routing issues.
- Eagerly loading the `team` association avoids lazy-init exceptions in
contexts where the user’s team is needed immediately (e.g., security
checks).

---

## Checklist

### General

- [x] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [x] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md)
(if applicable)
- [x] I have performed a self-review of my own code
- [x] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#6-testing)
for more details.
This commit is contained in:
Ludy 2025-06-14 22:00:24 +02:00 committed by GitHub
parent 142dba185c
commit 5393ae24cb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 36 additions and 6 deletions

View File

@ -2,6 +2,7 @@ package stirling.software.proprietary.security;
import java.sql.SQLException;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import org.springframework.stereotype.Component;
@ -53,10 +54,15 @@ public class InitialSecuritySetup {
private void assignUsersToDefaultTeamIfMissing() {
Team defaultTeam = teamService.getOrCreateDefaultTeam();
Team internalTeam = teamService.getOrCreateInternalTeam();
List<User> usersWithoutTeam = userService.getUsersWithoutTeam();
for (User user : usersWithoutTeam) {
user.setTeam(defaultTeam);
if (user.getUsername().equalsIgnoreCase(Role.INTERNAL_API_USER.getRoleId())) {
user.setTeam(internalTeam);
} else {
user.setTeam(defaultTeam);
}
}
userService.saveAll(usersWithoutTeam); // batch save
@ -108,6 +114,20 @@ public class InitialSecuritySetup {
false);
userService.addApiKeyToUser(Role.INTERNAL_API_USER.getRoleId());
log.info("Internal API user created: {}", Role.INTERNAL_API_USER.getRoleId());
} else {
Optional<User> internalApiUserOpt =
userService.findByUsernameIgnoreCase(Role.INTERNAL_API_USER.getRoleId());
if (internalApiUserOpt.isPresent()) {
User internalApiUser = internalApiUserOpt.get();
// move to team internal API user
if (!internalApiUser.getTeam().getName().equals(TeamService.INTERNAL_TEAM_NAME)) {
log.info(
"Moving internal API user to team: {}", TeamService.INTERNAL_TEAM_NAME);
Team internalTeam = teamService.getOrCreateInternalTeam();
userService.changeUserTeam(internalApiUser, internalTeam);
}
}
}
userService.syncCustomApiUser(applicationProperties.getSecurity().getCustomGlobalAPIKey());
}

View File

@ -58,7 +58,7 @@ public class User implements Serializable {
@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "user")
private Set<Authority> authorities = new HashSet<>();
@ManyToOne(fetch = FetchType.LAZY)
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "team_id")
private Team team;

View File

@ -371,6 +371,16 @@ public class UserService implements UserServiceInterface {
databaseService.exportDatabase();
}
public void changeUserTeam(User user, Team team)
throws SQLException, UnsupportedProviderException {
if (team == null) {
team = getDefaultTeam();
}
user.setTeam(team);
userRepository.save(user);
databaseService.exportDatabase();
}
public boolean isPasswordCorrect(User user, String currentPassword) {
return passwordEncoder.matches(currentPassword, user.getPassword());
}

View File

@ -435,14 +435,14 @@ document.addEventListener('DOMContentLoaded', function() {
e.preventDefault();
e.stopPropagation();
if (confirm('Are you sure you want to remove this bookmark' +
if (confirm('Are you sure you want to remove this bookmark' +
(bookmark.children.length > 0 ? ' and all its children?' : '?'))) {
removeBookmark(bookmark.id);
}
});
headerRight.appendChild(quickAddChildButton);
headerRight.appendChild(quickAddSiblingButton);
headerRight.appendChild(quickAddSiblingButton);
headerRight.appendChild(quickRemoveButton);
// Assemble header
@ -650,4 +650,4 @@ document.addEventListener('DOMContentLoaded', function() {
});
}
});
});
});

View File

@ -276,4 +276,4 @@ class MergeControllerTest {
verify(mockMergedDocument, never()).addPage(any(PDPage.class));
}
}
}