mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-12-18 20:04:17 +01:00
Grandpa Fix (#5030)
PR to address inactive accounts (invited/pending activation) not being grandfathered during migration --------- Signed-off-by: dependabot[bot] <support@github.com> Signed-off-by: stirlingbot[bot] <stirlingbot[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ludy <Ludy87@users.noreply.github.com> Co-authored-by: EthanHealy01 <80844253+EthanHealy01@users.noreply.github.com> Co-authored-by: Ethan <ethan@MacBook-Pro.local> Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Co-authored-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com>
This commit is contained in:
parent
feebfe82fa
commit
341adaa07d
@ -56,6 +56,19 @@ public interface UserRepository extends JpaRepository<User, Long> {
|
||||
+ "OR LOWER(u.authenticationType) IN ('sso', 'oauth2', 'saml2')")
|
||||
List<User> findAllSsoUsers();
|
||||
|
||||
/**
|
||||
* Finds SSO users who have never created a session (pending activation) and are not yet
|
||||
* grandfathered.
|
||||
*/
|
||||
@Query(
|
||||
"SELECT u FROM User u "
|
||||
+ "LEFT JOIN SessionEntity s ON u.username = s.principalName "
|
||||
+ "WHERE (u.ssoProvider IS NOT NULL "
|
||||
+ "OR LOWER(u.authenticationType) IN ('sso', 'oauth2', 'saml2')) "
|
||||
+ "AND (u.oauthGrandfathered IS NULL OR u.oauthGrandfathered = false) "
|
||||
+ "AND s.sessionId IS NULL")
|
||||
List<User> findPendingSsoUsersWithoutSession();
|
||||
|
||||
/**
|
||||
* Counts all SSO users - those with sso_provider set OR authenticationType is sso/oauth2/saml2.
|
||||
*/
|
||||
|
||||
@ -778,4 +778,30 @@ public class UserService implements UserServiceInterface {
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Grandfathers SSO users who have never created a session (invited/pending accounts). These
|
||||
* users would otherwise be blocked when SSO requires a paid license despite existing before the
|
||||
* policy change.
|
||||
*
|
||||
* @return Number of pending users updated
|
||||
*/
|
||||
@Transactional
|
||||
public int grandfatherPendingSsoUsersWithoutSession() {
|
||||
List<User> pendingUsers = userRepository.findPendingSsoUsersWithoutSession();
|
||||
int updated = 0;
|
||||
|
||||
for (User user : pendingUsers) {
|
||||
if (!user.isOauthGrandfathered()) {
|
||||
user.setOauthGrandfathered(true);
|
||||
updated++;
|
||||
}
|
||||
}
|
||||
|
||||
if (updated > 0) {
|
||||
userRepository.saveAll(pendingUsers);
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
}
|
||||
|
||||
@ -192,10 +192,18 @@ public class UserLicenseSettingsService {
|
||||
+ "They will retain OAuth access even without a paid license. "
|
||||
+ "New users will require a paid license for OAuth.",
|
||||
updated);
|
||||
} else if (grandfatheredCount > 0) {
|
||||
log.debug(
|
||||
"OAuth grandfathering already completed: {} users grandfathered",
|
||||
grandfatheredCount);
|
||||
}
|
||||
|
||||
// Grandfather pending users (invited but never logged in)
|
||||
// The query filters to non-grandfathered users only, so this is idempotent
|
||||
if (grandfatheredCount > 0 || oauthUsersCount > 0) {
|
||||
int pendingUpdated = userService.grandfatherPendingSsoUsersWithoutSession();
|
||||
if (pendingUpdated > 0) {
|
||||
log.warn(
|
||||
"OAuth GRANDFATHERING: Marked {} pending SSO users (no prior sessions) as"
|
||||
+ " grandfathered.",
|
||||
pendingUpdated);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,9 @@ package stirling.software.proprietary.service;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.Optional;
|
||||
@ -198,4 +201,70 @@ class UserLicenseSettingsServiceTest {
|
||||
|
||||
assertEquals(5, result, "Should fall back to default 5 users if grandfathered is 0");
|
||||
}
|
||||
|
||||
@Test
|
||||
void grandfatherExistingOAuthUsers_runsOnlyWhenNoneGrandfathered() {
|
||||
// With grandfatheredCount == 0, should run grandfathering for all users
|
||||
when(userService.countOAuthUsers()).thenReturn(10L);
|
||||
when(userService.countGrandfatheredOAuthUsers()).thenReturn(0L);
|
||||
when(userService.grandfatherAllOAuthUsers()).thenReturn(10);
|
||||
when(userService.grandfatherPendingSsoUsersWithoutSession()).thenReturn(0);
|
||||
|
||||
service.grandfatherExistingOAuthUsers();
|
||||
|
||||
verify(userService, times(1)).grandfatherAllOAuthUsers();
|
||||
verify(userService, times(1)).grandfatherPendingSsoUsersWithoutSession();
|
||||
}
|
||||
|
||||
@Test
|
||||
void grandfatherExistingOAuthUsers_skipsMainButRunsPendingWhenSomeAlreadyGrandfathered() {
|
||||
// V2→V2.1 upgrade: some users already grandfathered, but pending users need to be checked
|
||||
when(userService.countOAuthUsers()).thenReturn(10L);
|
||||
when(userService.countGrandfatheredOAuthUsers()).thenReturn(4L);
|
||||
when(userService.grandfatherPendingSsoUsersWithoutSession()).thenReturn(2);
|
||||
|
||||
service.grandfatherExistingOAuthUsers();
|
||||
|
||||
verify(userService, never()).grandfatherAllOAuthUsers();
|
||||
verify(userService, times(1)).grandfatherPendingSsoUsersWithoutSession();
|
||||
}
|
||||
|
||||
@Test
|
||||
void grandfatherExistingOAuthUsers_stillChecksPendingWhenAllUsersGrandfathered() {
|
||||
// All active users grandfathered, but still check for pending users
|
||||
when(userService.countOAuthUsers()).thenReturn(10L);
|
||||
when(userService.countGrandfatheredOAuthUsers()).thenReturn(10L);
|
||||
when(userService.grandfatherPendingSsoUsersWithoutSession()).thenReturn(0);
|
||||
|
||||
service.grandfatherExistingOAuthUsers();
|
||||
|
||||
verify(userService, never()).grandfatherAllOAuthUsers();
|
||||
verify(userService, times(1)).grandfatherPendingSsoUsersWithoutSession();
|
||||
}
|
||||
|
||||
@Test
|
||||
void grandfatherExistingOAuthUsers_skipsWhenNoOAuthUsers() {
|
||||
when(userService.countOAuthUsers()).thenReturn(0L);
|
||||
when(userService.countGrandfatheredOAuthUsers()).thenReturn(0L);
|
||||
|
||||
service.grandfatherExistingOAuthUsers();
|
||||
|
||||
verify(userService, never()).grandfatherAllOAuthUsers();
|
||||
verify(userService, never()).grandfatherPendingSsoUsersWithoutSession();
|
||||
}
|
||||
|
||||
@Test
|
||||
void grandfatherExistingOAuthUsers_grandfathersPendingUsersOnFirstRun() {
|
||||
// Pending users (invited but never logged in) should be grandfathered
|
||||
// during the initial grandfathering run (when grandfatheredCount == 0)
|
||||
when(userService.countOAuthUsers()).thenReturn(5L);
|
||||
when(userService.countGrandfatheredOAuthUsers()).thenReturn(0L);
|
||||
when(userService.grandfatherAllOAuthUsers()).thenReturn(5);
|
||||
when(userService.grandfatherPendingSsoUsersWithoutSession()).thenReturn(3);
|
||||
|
||||
service.grandfatherExistingOAuthUsers();
|
||||
|
||||
verify(userService, times(1)).grandfatherAllOAuthUsers();
|
||||
verify(userService, times(1)).grandfatherPendingSsoUsersWithoutSession();
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user