mirror of
https://github.com/we-promise/sure.git
synced 2026-04-19 20:14:08 +00:00
feat: comprehensive SSO/OIDC upgrade with enterprise features
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
This commit is contained in:
154
lib/tasks/sso_providers.rake
Normal file
154
lib/tasks/sso_providers.rake
Normal file
@@ -0,0 +1,154 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
namespace :sso_providers do
|
||||
desc "Seed SSO providers from config/auth.yml into the database"
|
||||
task seed: :environment do
|
||||
dry_run = ENV["DRY_RUN"] == "true"
|
||||
|
||||
puts "=" * 80
|
||||
puts "SSO Provider Seeding Task"
|
||||
puts "=" * 80
|
||||
puts "Mode: #{dry_run ? 'DRY RUN (no changes will be saved)' : 'LIVE (changes will be saved)'}"
|
||||
puts "Source: config/auth.yml"
|
||||
puts "-" * 80
|
||||
|
||||
begin
|
||||
# Load auth.yml safely
|
||||
auth_config_path = Rails.root.join("config", "auth.yml")
|
||||
unless File.exist?(auth_config_path)
|
||||
puts "ERROR: config/auth.yml not found"
|
||||
exit 1
|
||||
end
|
||||
|
||||
# Use safe_load to prevent code injection
|
||||
auth_config = YAML.safe_load(
|
||||
ERB.new(File.read(auth_config_path)).result,
|
||||
permitted_classes: [ Symbol ],
|
||||
aliases: true
|
||||
)
|
||||
|
||||
# Get providers for current environment
|
||||
env_config = auth_config[Rails.env] || auth_config["default"]
|
||||
providers = env_config&.dig("providers") || []
|
||||
|
||||
if providers.empty?
|
||||
puts "WARNING: No providers found in config/auth.yml for #{Rails.env} environment"
|
||||
exit 0
|
||||
end
|
||||
|
||||
puts "Found #{providers.count} provider(s) in config/auth.yml"
|
||||
puts "-" * 80
|
||||
|
||||
created_count = 0
|
||||
updated_count = 0
|
||||
skipped_count = 0
|
||||
errors = []
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
providers.each do |provider_config|
|
||||
provider_config = provider_config.deep_symbolize_keys
|
||||
|
||||
# Extract provider attributes
|
||||
name = provider_config[:name] || provider_config[:id]
|
||||
strategy = provider_config[:strategy]
|
||||
|
||||
unless name.present? && strategy.present?
|
||||
puts "SKIP: Provider missing name or strategy: #{provider_config.inspect}"
|
||||
skipped_count += 1
|
||||
next
|
||||
end
|
||||
|
||||
# Find or initialize provider
|
||||
provider = SsoProvider.find_or_initialize_by(name: name)
|
||||
is_new = provider.new_record?
|
||||
|
||||
# Build attributes hash
|
||||
attributes = {
|
||||
strategy: strategy,
|
||||
label: provider_config[:label] || name.titleize,
|
||||
icon: provider_config[:icon],
|
||||
enabled: provider_config.key?(:enabled) ? provider_config[:enabled] : true,
|
||||
issuer: provider_config[:issuer],
|
||||
client_id: provider_config[:client_id],
|
||||
redirect_uri: provider_config[:redirect_uri],
|
||||
settings: provider_config[:settings] || {}
|
||||
}
|
||||
|
||||
# Only set client_secret if provided (don't overwrite existing)
|
||||
if provider_config[:client_secret].present?
|
||||
attributes[:client_secret] = provider_config[:client_secret]
|
||||
end
|
||||
|
||||
# Assign attributes
|
||||
provider.assign_attributes(attributes.compact)
|
||||
|
||||
# Check if changed
|
||||
if provider.changed?
|
||||
if dry_run
|
||||
puts "#{is_new ? 'CREATE' : 'UPDATE'} (dry-run): #{name} (#{strategy})"
|
||||
puts " Changes: #{provider.changes.keys.join(', ')}"
|
||||
else
|
||||
if provider.save
|
||||
puts "#{is_new ? 'CREATE' : 'UPDATE'}: #{name} (#{strategy})"
|
||||
is_new ? created_count += 1 : updated_count += 1
|
||||
else
|
||||
error_msg = "Failed to save #{name}: #{provider.errors.full_messages.join(', ')}"
|
||||
puts "ERROR: #{error_msg}"
|
||||
errors << error_msg
|
||||
end
|
||||
end
|
||||
else
|
||||
puts "SKIP: #{name} (no changes)"
|
||||
skipped_count += 1
|
||||
end
|
||||
end
|
||||
|
||||
# Rollback transaction if dry run
|
||||
raise ActiveRecord::Rollback if dry_run
|
||||
end
|
||||
|
||||
puts "-" * 80
|
||||
puts "Summary:"
|
||||
puts " Created: #{created_count}"
|
||||
puts " Updated: #{updated_count}"
|
||||
puts " Skipped: #{skipped_count}"
|
||||
puts " Errors: #{errors.count}"
|
||||
|
||||
if errors.any?
|
||||
puts "\nErrors encountered:"
|
||||
errors.each { |error| puts " - #{error}" }
|
||||
end
|
||||
|
||||
if dry_run
|
||||
puts "\nDRY RUN: No changes were saved to the database"
|
||||
puts "Run without DRY_RUN=true to apply changes"
|
||||
else
|
||||
puts "\nSeeding completed successfully!"
|
||||
puts "Note: Clear provider cache or restart server for changes to take effect"
|
||||
end
|
||||
|
||||
puts "=" * 80
|
||||
|
||||
rescue => e
|
||||
puts "ERROR: #{e.class}: #{e.message}"
|
||||
puts e.backtrace.first(5).join("\n")
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
desc "List all SSO providers in the database"
|
||||
task list: :environment do
|
||||
providers = SsoProvider.order(:name)
|
||||
|
||||
if providers.empty?
|
||||
puts "No SSO providers found in database"
|
||||
else
|
||||
puts "SSO Providers (#{providers.count}):"
|
||||
puts "-" * 80
|
||||
providers.each do |provider|
|
||||
status = provider.enabled? ? "✓ enabled" : "✗ disabled"
|
||||
puts "#{provider.name.ljust(20)} | #{provider.strategy.ljust(20)} | #{status}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user