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:
*
*
* - They are grandfathered for OAuth (existing user before policy change), OR
- *
- The system has an ENTERPRISE license (SSO is enterprise-only)
+ *
- The system has a paid license (SERVER or ENTERPRISE)
*
*
* @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:
+ *
+ *
+ * - They are grandfathered for OAuth (existing user before policy change), OR
+ *
- The system has an ENTERPRISE license (SAML is enterprise-only)
+ *
+ *
+ * @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'