mirror of
https://github.com/we-promise/sure.git
synced 2026-04-07 22:34:47 +00:00
Multi-provider SSO support: - Database-backed SSO provider management with admin UI - Support for OpenID Connect, Google OAuth2, GitHub, and SAML 2.0 - Flipper feature flag (db_sso_providers) for dynamic provider loading - ProviderLoader service for YAML or database configuration Admin functionality: - Admin::SsoProvidersController for CRUD operations - Admin::UsersController for super_admin role management - Pundit policies for authorization - Test connection endpoint for validating provider config User provisioning improvements: - JIT (just-in-time) account creation with configurable default role - Changed default JIT role from admin to member (security) - User attribute sync on each SSO login - Group/role mapping from IdP claims SSO identity management: - Settings::SsoIdentitiesController for users to manage connected accounts - Issuer validation for OIDC identities - Unlink protection when no password set Audit logging: - SsoAuditLog model tracking login, logout, link, unlink, JIT creation - Captures IP address, user agent, and metadata Advanced OIDC features: - Custom scopes per provider - Configurable prompt parameter (login, consent, select_account, none) - RP-initiated logout (federated logout to IdP) - id_token storage for logout SAML 2.0 support: - omniauth-saml gem integration - IdP metadata URL or manual configuration - Certificate and fingerprint validation - NameID format configuration
88 lines
2.9 KiB
Ruby
88 lines
2.9 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
# Service class to load SSO provider configurations from either YAML or database
|
|
# based on the :db_sso_providers feature flag.
|
|
#
|
|
# Usage:
|
|
# providers = ProviderLoader.load_providers
|
|
#
|
|
class ProviderLoader
|
|
CACHE_KEY = "sso_providers_config"
|
|
CACHE_EXPIRES_IN = 5.minutes
|
|
|
|
class << self
|
|
# Load providers from either DB or YAML based on feature flag
|
|
# Returns an array of provider configuration hashes
|
|
def load_providers
|
|
# Check cache first for performance
|
|
cached = Rails.cache.read(CACHE_KEY)
|
|
return cached if cached.present?
|
|
|
|
providers = if use_database_providers?
|
|
load_from_database
|
|
else
|
|
load_from_yaml
|
|
end
|
|
|
|
# Cache the result
|
|
Rails.cache.write(CACHE_KEY, providers, expires_in: CACHE_EXPIRES_IN)
|
|
providers
|
|
end
|
|
|
|
# Clear the provider cache (call after updating providers in admin)
|
|
def clear_cache
|
|
Rails.cache.delete(CACHE_KEY)
|
|
end
|
|
|
|
private
|
|
def use_database_providers?
|
|
return false if Rails.env.test?
|
|
|
|
begin
|
|
# Check if feature exists, create if not (defaults to disabled)
|
|
unless Flipper.exist?(:db_sso_providers)
|
|
Flipper.add(:db_sso_providers)
|
|
end
|
|
Flipper.enabled?(:db_sso_providers)
|
|
rescue ActiveRecord::NoDatabaseError, ActiveRecord::StatementInvalid, StandardError => e
|
|
# Database not ready or other error, fall back to YAML
|
|
Rails.logger.warn("[ProviderLoader] Could not check feature flag (#{e.class}), falling back to YAML providers")
|
|
false
|
|
end
|
|
end
|
|
|
|
def load_from_database
|
|
begin
|
|
providers = SsoProvider.enabled.order(:name).map(&:to_omniauth_config)
|
|
|
|
if providers.empty?
|
|
Rails.logger.info("[ProviderLoader] No enabled providers in database, falling back to YAML")
|
|
return load_from_yaml
|
|
end
|
|
|
|
Rails.logger.info("[ProviderLoader] Loaded #{providers.count} provider(s) from database")
|
|
providers
|
|
rescue ActiveRecord::StatementInvalid, ActiveRecord::NoDatabaseError => e
|
|
Rails.logger.error("[ProviderLoader] Database error loading providers: #{e.message}, falling back to YAML")
|
|
load_from_yaml
|
|
rescue StandardError => e
|
|
Rails.logger.error("[ProviderLoader] Unexpected error loading providers from database: #{e.message}, falling back to YAML")
|
|
load_from_yaml
|
|
end
|
|
end
|
|
|
|
def load_from_yaml
|
|
begin
|
|
auth_config = Rails.application.config_for(:auth)
|
|
providers = auth_config.dig("providers") || []
|
|
|
|
Rails.logger.info("[ProviderLoader] Loaded #{providers.count} provider(s) from YAML")
|
|
providers
|
|
rescue RuntimeError, Errno::ENOENT => e
|
|
Rails.logger.error("[ProviderLoader] Error loading auth.yml: #{e.message}")
|
|
[]
|
|
end
|
|
end
|
|
end
|
|
end
|