From 7459463a3ccc133368e5d263e5bfe992d7febdf2 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Wed, 3 Dec 2025 21:12:29 +0000 Subject: [PATCH] V2 sso in server plan (#5158) # Description of Changes --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/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/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] 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/devGuide/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/devGuide/DeveloperGuide.md#6-testing) for more details. --- ...stomSaml2AuthenticationSuccessHandler.java | 10 ++--- .../service/UserLicenseSettingsService.java | 43 +++++++++++++++---- .../public/locales/en-GB/translation.toml | 4 +- .../configSections/providerDefinitions.ts | 6 ++- .../proprietary/constants/planConstants.ts | 11 +++-- 5 files changed, 53 insertions(+), 21 deletions(-) diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/saml2/CustomSaml2AuthenticationSuccessHandler.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/saml2/CustomSaml2AuthenticationSuccessHandler.java index 0f350d7b4..b342fdcb4 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/saml2/CustomSaml2AuthenticationSuccessHandler.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/saml2/CustomSaml2AuthenticationSuccessHandler.java @@ -67,19 +67,19 @@ public class CustomSaml2AuthenticationSuccessHandler boolean userExists = userService.usernameExistsIgnoreCase(username); - // Check if user is eligible for SAML (grandfathered or system has paid license) + // Check if user is eligible for SAML (grandfathered or system has ENTERPRISE license) if (userExists) { stirling.software.proprietary.security.model.User user = userService.findByUsernameIgnoreCase(username).orElse(null); - if (user != null && !licenseSettingsService.isOAuthEligible(user)) { - // User is not grandfathered and no paid license - block SAML login + if (user != null && !licenseSettingsService.isSamlEligible(user)) { + // User is not grandfathered and no ENTERPRISE license - block SAML login response.sendRedirect( request.getContextPath() + "/logout?saml2RequiresLicense=true"); return; } - } else if (!licenseSettingsService.isOAuthEligible(null)) { - // No existing user and no paid license -> block auto creation + } else if (!licenseSettingsService.isSamlEligible(null)) { + // No existing user and no ENTERPRISE license -> block auto creation response.sendRedirect( request.getContextPath() + "/logout?saml2RequiresLicense=true"); return; 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 1cddefde3..d3bade89c 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 @@ -331,17 +331,17 @@ public class UserLicenseSettingsService { } /** - * Checks if a user is eligible to use OAuth/SAML authentication. + * Checks if a user is eligible to use OAuth authentication. * *

A user is eligible if: * *

* * @param user The user to check - * @return true if the user can use OAuth/SAML + * @return true if the user can use OAuth */ public boolean isOAuthEligible(stirling.software.proprietary.security.model.User user) { // Grandfathered users always have OAuth access @@ -350,10 +350,36 @@ public class UserLicenseSettingsService { return true; } - // Users can use OAuth/SAML only if system has ENTERPRISE license - boolean hasEnterpriseLicense = hasEnterpriseLicense(); - log.debug("OAuth eligibility check: hasEnterpriseLicense={}", hasEnterpriseLicense); - return hasEnterpriseLicense; + // Users can use OAuth with SERVER or ENTERPRISE license + boolean hasPaid = hasPaidLicense(); + log.debug("OAuth eligibility check: hasPaidLicense={}", hasPaid); + return hasPaid; + } + + /** + * Checks if a user is eligible to use SAML authentication. + * + *

A user is eligible if: + * + *

+ * + * @param user The user to check + * @return true if the user can use SAML + */ + public boolean isSamlEligible(stirling.software.proprietary.security.model.User user) { + // Grandfathered users always have SAML access + if (user != null && user.isOauthGrandfathered()) { + log.debug("User {} is grandfathered for SAML", user.getUsername()); + return true; + } + + // Users can use SAML only with ENTERPRISE license + boolean hasEnterprise = hasEnterpriseLicense(); + log.debug("SAML eligibility check: hasEnterpriseLicense={}", hasEnterprise); + return hasEnterprise; } /** @@ -500,8 +526,7 @@ public class UserLicenseSettingsService { } /** - * Checks if the system has an ENTERPRISE license. Used for enterprise-only features like SSO - * (OAuth/SAML). + * Checks if the system has an ENTERPRISE license. Used for enterprise-only features like SAML. * * @return true if ENTERPRISE license is active */ diff --git a/frontend/public/locales/en-GB/translation.toml b/frontend/public/locales/en-GB/translation.toml index 2d2fa19c6..b68a380d6 100644 --- a/frontend/public/locales/en-GB/translation.toml +++ b/frontend/public/locales/en-GB/translation.toml @@ -3539,8 +3539,8 @@ signinTitle = "Please sign in" ssoSignIn = "Login via Single Sign-on" oAuth2AutoCreateDisabled = "OAUTH2 Auto-Create User Disabled" oAuth2AdminBlockedUser = "Registration or logging in of non-registered users is currently blocked. Please contact the administrator." -oAuth2RequiresLicense = "OAuth/SSO login requires a paid license (Server or Enterprise). Please contact the administrator to upgrade your plan." -saml2RequiresLicense = "SAML login requires a paid license (Server or Enterprise). Please contact the administrator to upgrade your plan." +oAuth2RequiresLicense = "OAuth/SSO login requires a Server or Enterprise license. Please contact the administrator to upgrade your plan." +saml2RequiresLicense = "SAML login requires an Enterprise license. Please contact the administrator to upgrade your plan." maxUsersReached = "Maximum number of users reached for your current license. Please contact the administrator to upgrade your plan or add more seats." oauth2RequestNotFound = "Authorization request not found" oauth2InvalidUserInfoResponse = "Invalid User Info Response" diff --git a/frontend/src/core/components/shared/config/configSections/providerDefinitions.ts b/frontend/src/core/components/shared/config/configSections/providerDefinitions.ts index f51f997c7..f9d4af7c4 100644 --- a/frontend/src/core/components/shared/config/configSections/providerDefinitions.ts +++ b/frontend/src/core/components/shared/config/configSections/providerDefinitions.ts @@ -97,6 +97,7 @@ export const OAUTH2_PROVIDERS: Provider[] = [ icon: 'key-rounded', type: 'oauth2', scope: 'SSO', + businessTier: false, // Server tier - OAuth2/OIDC SSO fields: [ { key: 'issuer', @@ -141,6 +142,7 @@ export const GENERIC_OAUTH2_PROVIDER: Provider = { icon: 'link-rounded', type: 'oauth2', scope: 'SSO', + businessTier: false, // Server tier - OAuth2/OIDC SSO fields: [ { key: 'enabled', @@ -262,8 +264,8 @@ export const SAML2_PROVIDER: Provider = { name: 'SAML2', icon: 'verified-user-rounded', type: 'saml2', - scope: 'SSO', - businessTier: true, + scope: 'SSO (SAML)', + businessTier: true, // Enterprise tier - SAML only fields: [ { key: 'enabled', diff --git a/frontend/src/proprietary/constants/planConstants.ts b/frontend/src/proprietary/constants/planConstants.ts index ab14a2c55..f7073d25c 100644 --- a/frontend/src/proprietary/constants/planConstants.ts +++ b/frontend/src/proprietary/constants/planConstants.ts @@ -19,6 +19,7 @@ export const PLAN_FEATURES = { { name: 'Editing text in pdfs', included: false }, { name: 'Users limited to seats', included: false }, { name: 'SSO', included: false }, + { name: 'SAML', included: false }, { name: 'Auditing', included: false }, { name: 'Usage tracking', included: false }, { name: 'Prometheus Support', included: false }, @@ -37,7 +38,8 @@ export const PLAN_FEATURES = { { name: 'External Database', included: true }, { name: 'Editing text in pdfs', included: true }, { name: 'Users limited to seats', included: false }, - { name: 'SSO', included: false }, + { name: 'SSO', included: true }, + { name: 'SAML', included: false }, { name: 'Auditing', included: false }, { name: 'Usage tracking', included: false }, { name: 'Prometheus Support', included: false }, @@ -57,6 +59,7 @@ export const PLAN_FEATURES = { { name: 'Editing text in pdfs', included: true }, { name: 'Users limited to seats', included: true }, { name: 'SSO', included: true }, + { name: 'SAML', included: true }, { name: 'Auditing', included: true }, { name: 'Usage tracking', included: true }, { name: 'Prometheus Support', included: true }, @@ -74,6 +77,7 @@ export const PLAN_HIGHLIGHTS = { 'Self-hosted on your infrastructure', 'Unlimited users', 'Advanced integrations', + 'SSO (OAuth2/OIDC)', 'Editing text in PDFs', 'Cancel anytime' ], @@ -81,17 +85,18 @@ export const PLAN_HIGHLIGHTS = { 'Self-hosted on your infrastructure', 'Unlimited users', 'Advanced integrations', + 'SSO (OAuth2/OIDC)', 'Editing text in PDFs', 'Save with annual billing' ], ENTERPRISE_MONTHLY: [ - 'Enterprise features (SSO, Auditing)', + 'Enterprise features (SAML, Auditing)', 'Usage tracking & Prometheus', 'Custom PDF metadata', 'Per-seat licensing' ], ENTERPRISE_YEARLY: [ - 'Enterprise features (SSO, Auditing)', + 'Enterprise features (SAML, Auditing)', 'Usage tracking & Prometheus', 'Custom PDF metadata', 'Save with annual billing'