From bfbd85b225f0e36ab28667431e2d37676cc8a6fe Mon Sep 17 00:00:00 2001 From: Dario Ghunney Ware Date: Wed, 26 Nov 2025 16:24:42 +0000 Subject: [PATCH] adding unactivated (invited/pending activation) users to grandfathering --- .../database/repository/UserRepository.java | 13 ++++++++++ .../security/service/UserService.java | 26 +++++++++++++++++++ .../service/UserLicenseSettingsService.java | 10 +++++++ .../UserLicenseSettingsServiceTest.java | 23 ++++++++++++++++ 4 files changed, 72 insertions(+) diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/database/repository/UserRepository.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/database/repository/UserRepository.java index 1a8b51bca1..312c19964d 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/database/repository/UserRepository.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/database/repository/UserRepository.java @@ -56,6 +56,19 @@ public interface UserRepository extends JpaRepository { + "OR LOWER(u.authenticationType) IN ('sso', 'oauth2', 'saml2')") List 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 findPendingSsoUsersWithoutSession(); + /** * Counts all SSO users - those with sso_provider set OR authenticationType is sso/oauth2/saml2. */ diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/service/UserService.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/service/UserService.java index d131eb2bdc..2614a9754d 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/service/UserService.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/service/UserService.java @@ -776,6 +776,32 @@ public class UserService implements UserServiceInterface { userRepository.saveAll(ssoUsers); } + 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 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; } } diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/service/UserLicenseSettingsService.java b/app/proprietary/src/main/java/stirling/software/proprietary/service/UserLicenseSettingsService.java index 9d7a8a3adc..a10e161a83 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/service/UserLicenseSettingsService.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/service/UserLicenseSettingsService.java @@ -198,6 +198,16 @@ public class UserLicenseSettingsService { "OAuth grandfathering already completed: {} users grandfathered", grandfatheredCount); } + + int pendingUpdated = userService.grandfatherPendingSsoUsersWithoutSession(); + if (pendingUpdated > 0) { + log.warn( + "OAuth GRANDFATHERING: Marked {} pending SSO users (no prior sessions) as" + + " grandfathered.", + pendingUpdated); + } else { + log.debug("No pending SSO users required grandfathering"); + } } } diff --git a/app/proprietary/src/test/java/stirling/software/proprietary/service/UserLicenseSettingsServiceTest.java b/app/proprietary/src/test/java/stirling/software/proprietary/service/UserLicenseSettingsServiceTest.java index 046c6745e0..03a40fde98 100644 --- a/app/proprietary/src/test/java/stirling/software/proprietary/service/UserLicenseSettingsServiceTest.java +++ b/app/proprietary/src/test/java/stirling/software/proprietary/service/UserLicenseSettingsServiceTest.java @@ -232,4 +232,27 @@ class UserLicenseSettingsServiceTest { verify(userService, never()).grandfatherAllOAuthUsers(); } + + @Test + void grandfatherExistingOAuthUsers_handlesPendingUsersWithoutSessions() { + when(userService.countOAuthUsers()).thenReturn(5L); + when(userService.countGrandfatheredOAuthUsers()).thenReturn(5L); + when(userService.grandfatherPendingSsoUsersWithoutSession()).thenReturn(3); + + service.grandfatherExistingOAuthUsers(); + + verify(userService, never()).grandfatherAllOAuthUsers(); + verify(userService, times(1)).grandfatherPendingSsoUsersWithoutSession(); + } + + @Test + void grandfatherExistingOAuthUsers_checksPendingUsersEvenWhenNoneExist() { + when(userService.countOAuthUsers()).thenReturn(0L); + when(userService.countGrandfatheredOAuthUsers()).thenReturn(0L); + when(userService.grandfatherPendingSsoUsersWithoutSession()).thenReturn(0); + + service.grandfatherExistingOAuthUsers(); + + verify(userService, times(1)).grandfatherPendingSsoUsersWithoutSession(); + } }