mirror of
https://github.com/we-promise/sure.git
synced 2026-04-19 03:54: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:
157
app/controllers/admin/sso_providers_controller.rb
Normal file
157
app/controllers/admin/sso_providers_controller.rb
Normal file
@@ -0,0 +1,157 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class SsoProvidersController < Admin::BaseController
|
||||
before_action :set_sso_provider, only: %i[show edit update destroy toggle test_connection]
|
||||
|
||||
def index
|
||||
authorize SsoProvider
|
||||
@sso_providers = policy_scope(SsoProvider).order(:name)
|
||||
end
|
||||
|
||||
def show
|
||||
authorize @sso_provider
|
||||
end
|
||||
|
||||
def new
|
||||
@sso_provider = SsoProvider.new
|
||||
authorize @sso_provider
|
||||
end
|
||||
|
||||
def create
|
||||
@sso_provider = SsoProvider.new(processed_params)
|
||||
authorize @sso_provider
|
||||
|
||||
# Auto-generate redirect_uri if not provided
|
||||
if @sso_provider.redirect_uri.blank? && @sso_provider.name.present?
|
||||
@sso_provider.redirect_uri = "#{request.base_url}/auth/#{@sso_provider.name}/callback"
|
||||
end
|
||||
|
||||
if @sso_provider.save
|
||||
log_provider_change(:create, @sso_provider)
|
||||
clear_provider_cache
|
||||
redirect_to admin_sso_providers_path, notice: t(".success")
|
||||
else
|
||||
render :new, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
authorize @sso_provider
|
||||
end
|
||||
|
||||
def update
|
||||
authorize @sso_provider
|
||||
|
||||
# Auto-update redirect_uri if name changed
|
||||
params_hash = processed_params.to_h
|
||||
if params_hash[:name].present? && params_hash[:name] != @sso_provider.name
|
||||
params_hash[:redirect_uri] = "#{request.base_url}/auth/#{params_hash[:name]}/callback"
|
||||
end
|
||||
|
||||
if @sso_provider.update(params_hash)
|
||||
log_provider_change(:update, @sso_provider)
|
||||
clear_provider_cache
|
||||
redirect_to admin_sso_providers_path, notice: t(".success")
|
||||
else
|
||||
render :edit, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize @sso_provider
|
||||
|
||||
@sso_provider.destroy!
|
||||
log_provider_change(:destroy, @sso_provider)
|
||||
clear_provider_cache
|
||||
|
||||
redirect_to admin_sso_providers_path, notice: t(".success")
|
||||
end
|
||||
|
||||
def toggle
|
||||
authorize @sso_provider
|
||||
|
||||
@sso_provider.update!(enabled: !@sso_provider.enabled)
|
||||
log_provider_change(:toggle, @sso_provider)
|
||||
clear_provider_cache
|
||||
|
||||
notice = @sso_provider.enabled? ? t(".success_enabled") : t(".success_disabled")
|
||||
redirect_to admin_sso_providers_path, notice: notice
|
||||
end
|
||||
|
||||
def test_connection
|
||||
authorize @sso_provider
|
||||
|
||||
tester = SsoProviderTester.new(@sso_provider)
|
||||
result = tester.test!
|
||||
|
||||
render json: {
|
||||
success: result.success?,
|
||||
message: result.message,
|
||||
details: result.details
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
def set_sso_provider
|
||||
@sso_provider = SsoProvider.find(params[:id])
|
||||
end
|
||||
|
||||
def sso_provider_params
|
||||
params.require(:sso_provider).permit(
|
||||
:strategy,
|
||||
:name,
|
||||
:label,
|
||||
:icon,
|
||||
:enabled,
|
||||
:issuer,
|
||||
:client_id,
|
||||
:client_secret,
|
||||
:redirect_uri,
|
||||
:scopes,
|
||||
:prompt,
|
||||
settings: [
|
||||
:default_role, :scopes, :prompt,
|
||||
# SAML settings
|
||||
:idp_metadata_url, :idp_sso_url, :idp_slo_url,
|
||||
:idp_certificate, :idp_cert_fingerprint, :name_id_format,
|
||||
role_mapping: {}
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
# Process params to convert role_mapping comma-separated strings to arrays
|
||||
def processed_params
|
||||
result = sso_provider_params.to_h
|
||||
|
||||
if result[:settings].present? && result[:settings][:role_mapping].present?
|
||||
result[:settings][:role_mapping] = result[:settings][:role_mapping].transform_values do |v|
|
||||
# Convert comma-separated string to array, removing empty values
|
||||
v.to_s.split(",").map(&:strip).reject(&:blank?)
|
||||
end
|
||||
|
||||
# Remove empty role mappings
|
||||
result[:settings][:role_mapping] = result[:settings][:role_mapping].reject { |_, v| v.empty? }
|
||||
result[:settings].delete(:role_mapping) if result[:settings][:role_mapping].empty?
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def log_provider_change(action, provider)
|
||||
Rails.logger.info(
|
||||
"[Admin::SsoProviders] #{action.to_s.upcase} - " \
|
||||
"user_id=#{Current.user.id} " \
|
||||
"provider_id=#{provider.id} " \
|
||||
"provider_name=#{provider.name} " \
|
||||
"strategy=#{provider.strategy} " \
|
||||
"enabled=#{provider.enabled}"
|
||||
)
|
||||
end
|
||||
|
||||
def clear_provider_cache
|
||||
ProviderLoader.clear_cache
|
||||
Rails.logger.info("[Admin::SsoProviders] Provider cache cleared by user_id=#{Current.user.id}")
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user