mirror of https://github.com/docusealco/docuseal
parent
5f04a2d098
commit
f2f1745526
@ -0,0 +1,91 @@
|
||||
FROM ruby:3.4.2-alpine AS download
|
||||
|
||||
WORKDIR /fonts
|
||||
|
||||
RUN apk --no-cache add fontforge wget && \
|
||||
wget https://github.com/satbyy/go-noto-universal/releases/download/v7.0/GoNotoKurrent-Regular.ttf && \
|
||||
wget https://github.com/satbyy/go-noto-universal/releases/download/v7.0/GoNotoKurrent-Bold.ttf && \
|
||||
wget https://github.com/impallari/DancingScript/raw/master/fonts/DancingScript-Regular.otf && \
|
||||
wget https://cdn.jsdelivr.net/gh/notofonts/notofonts.github.io/fonts/NotoSansSymbols2/hinted/ttf/NotoSansSymbols2-Regular.ttf && \
|
||||
wget https://github.com/Maxattax97/gnu-freefont/raw/master/ttf/FreeSans.ttf && \
|
||||
wget https://github.com/impallari/DancingScript/raw/master/OFL.txt && \
|
||||
wget -O pdfium-linux.tgz "https://github.com/docusealco/pdfium-binaries/releases/latest/download/pdfium-linux-$(uname -m | sed 's/x86_64/x64/;s/aarch64/arm64/').tgz" && \
|
||||
mkdir -p /pdfium-linux && \
|
||||
tar -xzf pdfium-linux.tgz -C /pdfium-linux
|
||||
|
||||
RUN fontforge -lang=py -c 'font1 = fontforge.open("FreeSans.ttf"); font2 = fontforge.open("NotoSansSymbols2-Regular.ttf"); font1.mergeFonts(font2); font1.generate("FreeSans.ttf")'
|
||||
|
||||
FROM ruby:3.4.2-alpine AS app
|
||||
|
||||
# Development environment settings
|
||||
ENV RAILS_ENV=development
|
||||
ENV NODE_ENV=development
|
||||
ENV BUNDLE_WITHOUT=""
|
||||
ENV LD_PRELOAD=/lib/libgcompat.so.0
|
||||
ENV OPENSSL_CONF=/app/openssl_legacy.cnf
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install system dependencies including nodejs and yarn for development
|
||||
RUN echo '@edge https://dl-cdn.alpinelinux.org/alpine/edge/community' >> /etc/apk/repositories && \
|
||||
apk add --no-cache \
|
||||
sqlite-dev \
|
||||
libpq-dev \
|
||||
mariadb-dev \
|
||||
vips-dev@edge \
|
||||
redis \
|
||||
libheif@edge \
|
||||
vips-heif@edge \
|
||||
gcompat \
|
||||
ttf-freefont \
|
||||
nodejs \
|
||||
yarn \
|
||||
git \
|
||||
build-base && \
|
||||
mkdir /fonts && \
|
||||
rm /usr/share/fonts/freefont/FreeSans.otf
|
||||
|
||||
# Copy fonts from download stage
|
||||
COPY --from=download /fonts /fonts
|
||||
COPY --from=download /pdfium-linux /pdfium-linux
|
||||
|
||||
# Install PDFium library
|
||||
RUN cp /pdfium-linux/lib/libpdfium.so /usr/local/lib/ && \
|
||||
chmod +x /usr/local/lib/libpdfium.so
|
||||
|
||||
# OpenSSL legacy configuration for compatibility
|
||||
RUN echo $'.include = /etc/ssl/openssl.cnf\n\
|
||||
\n\
|
||||
[provider_sect]\n\
|
||||
default = default_sect\n\
|
||||
legacy = legacy_sect\n\
|
||||
\n\
|
||||
[default_sect]\n\
|
||||
activate = 1\n\
|
||||
\n\
|
||||
[legacy_sect]\n\
|
||||
activate = 1' >> /app/openssl_legacy.cnf
|
||||
|
||||
# Copy Gemfile and package.json for dependency installation
|
||||
COPY ./Gemfile ./Gemfile.lock ./
|
||||
COPY ./package.json ./yarn.lock ./
|
||||
|
||||
# Install Ruby gems and Node packages
|
||||
RUN bundle install && \
|
||||
yarn install --network-timeout 1000000
|
||||
|
||||
# Install shakapacker for asset compilation
|
||||
RUN gem install shakapacker
|
||||
|
||||
# Create necessary directories
|
||||
RUN mkdir -p log tmp/pids tmp/cache tmp/sockets public/assets
|
||||
|
||||
# Set up the application
|
||||
COPY ./bin ./bin
|
||||
RUN chmod +x ./bin/*
|
||||
|
||||
# Expose port 3000
|
||||
EXPOSE 3000
|
||||
|
||||
# Default command for development
|
||||
CMD ["bundle", "exec", "rails", "server", "-b", "0.0.0.0"]
|
||||
@ -0,0 +1,221 @@
|
||||
# DocuSeal Omniauth Implementation Guide
|
||||
|
||||
This document describes the complete implementation of omniauth/SSO functionality in DocuSeal, restoring the features that were previously gated behind paywall checks.
|
||||
|
||||
## Overview
|
||||
|
||||
We have successfully implemented comprehensive omniauth support with the following providers:
|
||||
- **SAML 2.0** - For enterprise SSO integration
|
||||
- **Google OAuth2** - For Google account authentication
|
||||
- **Microsoft Graph** - For Microsoft/Office 365 authentication
|
||||
|
||||
## What Was Implemented
|
||||
|
||||
### 1. Database Schema Changes
|
||||
- Added `provider` and `uid` columns to the `users` table
|
||||
- Created migration: `db/migrate/20250719104801_add_omniauth_to_users.rb`
|
||||
- Added unique index on `[provider, uid]` combination
|
||||
|
||||
### 2. User Model Updates
|
||||
- Added `:omniauthable` to Devise modules
|
||||
- Configured omniauth providers: `%i[saml google_oauth2 microsoft_graph]`
|
||||
- Implemented `User.from_omniauth(auth)` class method for handling authentication callbacks
|
||||
- Added support for both single-tenant and multi-tenant account creation
|
||||
|
||||
### 3. Devise Configuration
|
||||
- Updated `config/initializers/devise.rb` with omniauth provider configurations
|
||||
- Added environment variable-based configuration for SAML
|
||||
- Configured Google OAuth2 and Microsoft Graph providers
|
||||
- Added proper attribute mapping for SAML assertions
|
||||
|
||||
### 4. Controllers
|
||||
- Created `app/controllers/users/omniauth_callbacks_controller.rb`
|
||||
- Handles callbacks for all three providers (SAML, Google, Microsoft)
|
||||
- Includes proper error handling and session management
|
||||
- Updated `app/controllers/sso_settings_controller.rb` with configuration management
|
||||
|
||||
### 5. Views and UI
|
||||
- Replaced SSO paywall placeholder with functional SAML configuration form
|
||||
- Updated login form to include all three authentication providers
|
||||
- Added SSO settings interface for SAML configuration
|
||||
- Removed `Docuseal.multitenant?` checks that were gating SSO features
|
||||
|
||||
### 6. Routes
|
||||
- Updated routes to include omniauth callbacks
|
||||
- Added update action to SSO settings controller
|
||||
- Enabled SSO settings for all users (removed paywall restrictions)
|
||||
|
||||
### 7. Gemfile Dependencies
|
||||
Added the following gems:
|
||||
```ruby
|
||||
gem 'omniauth'
|
||||
gem 'omniauth-rails_csrf_protection'
|
||||
gem 'omniauth-saml'
|
||||
gem 'omniauth-google-oauth2'
|
||||
gem 'omniauth-microsoft_graph'
|
||||
```
|
||||
|
||||
### 8. Development Environment
|
||||
- Created `Dockerfile.dev` for development with mounted codebase
|
||||
- Created `docker-compose.dev.yml` for local development environment
|
||||
- Configured development environment with PostgreSQL and Redis
|
||||
|
||||
## Configuration
|
||||
|
||||
### SAML Configuration
|
||||
|
||||
#### Environment Variables
|
||||
Set these environment variables for SAML configuration:
|
||||
```bash
|
||||
SAML_IDP_SSO_SERVICE_URL=https://your-idp.com/sso/saml
|
||||
SAML_IDP_CERT_FINGERPRINT=AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD
|
||||
SAML_SP_ENTITY_ID=docuseal
|
||||
APP_URL=http://localhost:3000
|
||||
```
|
||||
|
||||
#### Database Configuration
|
||||
Alternatively, configure SAML through the web interface at `/settings/sso` which stores encrypted configuration in the database.
|
||||
|
||||
#### Service Provider URLs
|
||||
Provide these URLs to your Identity Provider:
|
||||
- **Assertion Consumer Service URL**: `http://localhost:3000/users/auth/saml/callback`
|
||||
- **SP Metadata URL**: `http://localhost:3000/users/auth/saml/metadata`
|
||||
- **SP Entity ID**: `docuseal` (or your custom value)
|
||||
|
||||
### Google OAuth2 Configuration
|
||||
|
||||
Add to Rails credentials or environment variables:
|
||||
```bash
|
||||
GOOGLE_CLIENT_ID=your-google-client-id
|
||||
GOOGLE_CLIENT_SECRET=your-google-client-secret
|
||||
```
|
||||
|
||||
### Microsoft Graph Configuration
|
||||
|
||||
Add to Rails credentials or environment variables:
|
||||
```bash
|
||||
MICROSOFT_CLIENT_ID=your-microsoft-client-id
|
||||
MICROSOFT_CLIENT_SECRET=your-microsoft-client-secret
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### For Users
|
||||
1. Navigate to the login page
|
||||
2. Choose from available authentication methods:
|
||||
- Sign in with Google
|
||||
- Sign in with Microsoft
|
||||
- Sign in with SAML SSO (if configured)
|
||||
3. Complete authentication with your chosen provider
|
||||
4. You'll be automatically signed in or prompted to complete registration
|
||||
|
||||
### For Administrators
|
||||
1. Go to Settings → SSO
|
||||
2. Configure SAML settings with your Identity Provider details
|
||||
3. Test the configuration using the "Test SAML Login" button
|
||||
4. Users can now authenticate using the configured SSO provider
|
||||
|
||||
## Development Setup
|
||||
|
||||
### Using Docker (Recommended)
|
||||
```bash
|
||||
# Build the development environment
|
||||
docker-compose -f docker-compose.dev.yml build
|
||||
|
||||
# Start the development environment
|
||||
docker-compose -f docker-compose.dev.yml up
|
||||
|
||||
# Access the application at http://localhost:3000
|
||||
```
|
||||
|
||||
### Local Development
|
||||
```bash
|
||||
# Install dependencies
|
||||
bundle install
|
||||
yarn install
|
||||
|
||||
# Run database migrations
|
||||
rails db:create db:migrate
|
||||
|
||||
# Start the development server
|
||||
rails server
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### SAML Testing
|
||||
1. Set up environment variables for SAML configuration
|
||||
2. Navigate to `/settings/sso` to configure SAML
|
||||
3. Use the "Test SAML Login" button to verify configuration
|
||||
4. Check logs for any authentication errors
|
||||
|
||||
### OAuth Testing
|
||||
1. Configure Google/Microsoft credentials
|
||||
2. Navigate to login page
|
||||
3. Click "Sign in with Google" or "Sign in with Microsoft"
|
||||
4. Complete OAuth flow and verify user creation/authentication
|
||||
|
||||
## Security Considerations
|
||||
|
||||
1. **CSRF Protection**: Implemented via `omniauth-rails_csrf_protection` gem
|
||||
2. **Secure Credentials**: Store sensitive configuration in Rails credentials or environment variables
|
||||
3. **Certificate Validation**: SAML certificate fingerprints are validated
|
||||
4. **Session Management**: Proper session cleanup and management implemented
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **"NameError: uninitialized constant EncryptedConfig"**
|
||||
- This was resolved by moving SAML configuration to environment variables
|
||||
- Ensure proper initialization order in Devise configuration
|
||||
|
||||
2. **"Invalid credentials" errors**
|
||||
- Verify OAuth client IDs and secrets are correct
|
||||
- Check redirect URIs match exactly
|
||||
|
||||
3. **SAML authentication failures**
|
||||
- Verify IdP certificate fingerprint is correct
|
||||
- Check that assertion consumer service URL matches
|
||||
- Ensure name identifier format matches IdP configuration
|
||||
|
||||
### Logs
|
||||
Check application logs for detailed error messages:
|
||||
```bash
|
||||
docker-compose -f docker-compose.dev.yml logs app
|
||||
```
|
||||
|
||||
## Files Modified/Created
|
||||
|
||||
### New Files
|
||||
- `db/migrate/20250719104801_add_omniauth_to_users.rb`
|
||||
- `app/controllers/users/omniauth_callbacks_controller.rb`
|
||||
- `app/views/sso_settings/_saml_form.html.erb`
|
||||
- `Dockerfile.dev`
|
||||
- `docker-compose.dev.yml`
|
||||
- `OMNIAUTH_IMPLEMENTATION.md`
|
||||
|
||||
### Modified Files
|
||||
- `Gemfile` - Added omniauth gems
|
||||
- `app/models/user.rb` - Added omniauthable and from_omniauth method
|
||||
- `config/initializers/devise.rb` - Added omniauth provider configurations
|
||||
- `app/controllers/sso_settings_controller.rb` - Added update method
|
||||
- `app/views/sso_settings/index.html.erb` - Replaced placeholder with form
|
||||
- `app/views/devise/sessions/new.html.erb` - Added omniauth provider buttons
|
||||
- `app/views/shared/_settings_nav.html.erb` - Removed paywall check
|
||||
- `config/routes.rb` - Added update route for SSO settings
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Production Deployment**: Configure production environment variables
|
||||
2. **Additional Providers**: Add more omniauth providers as needed
|
||||
3. **Advanced SAML**: Implement IdP-initiated SSO and SLO (Single Logout)
|
||||
4. **User Management**: Add admin interface for managing SSO users
|
||||
5. **Audit Logging**: Add logging for SSO authentication events
|
||||
|
||||
## Support
|
||||
|
||||
For issues or questions about this implementation, refer to:
|
||||
- [Devise Omniauth Documentation](https://github.com/heartcombo/devise/wiki/OmniAuth:-Overview)
|
||||
- [Omniauth SAML Documentation](https://github.com/omniauth/omniauth-saml)
|
||||
- DocuSeal application logs and error messages
|
||||
@ -0,0 +1,141 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
||||
# See https://github.com/omniauth/omniauth/wiki/FAQ#rails-session-is-clobbered-after-callback-on-developer-strategy
|
||||
skip_before_action :verify_authenticity_token, only: [:saml, :google_oauth2, :microsoft_graph]
|
||||
|
||||
def saml
|
||||
handle_omniauth_callback('SAML')
|
||||
end
|
||||
|
||||
def google_oauth2
|
||||
handle_omniauth_callback('Google')
|
||||
end
|
||||
|
||||
def microsoft_graph
|
||||
handle_omniauth_callback('Microsoft')
|
||||
end
|
||||
|
||||
def failure
|
||||
Rails.logger.error "Omniauth failure: #{params[:message]}"
|
||||
redirect_to new_user_session_path, alert: "Authentication failed: #{params[:message]}"
|
||||
end
|
||||
|
||||
def passthru
|
||||
# Handle requests to unconfigured or improperly configured SSO providers
|
||||
provider = params[:provider] || request.env['omniauth.strategy']&.name&.to_s
|
||||
|
||||
case provider
|
||||
when 'saml'
|
||||
# Check if SAML is properly configured anywhere in the system
|
||||
saml_configured = false
|
||||
|
||||
# Check environment variables first
|
||||
if ENV['SAML_IDP_SSO_SERVICE_URL'].present? && ENV['SAML_IDP_CERT_FINGERPRINT'].present?
|
||||
saml_configured = true
|
||||
else
|
||||
# Check if any account has SAML configured in database
|
||||
saml_config_record = EncryptedConfig.find_by(key: 'saml_configs')
|
||||
if saml_config_record&.value.present?
|
||||
begin
|
||||
config = JSON.parse(saml_config_record.value)
|
||||
saml_configured = config['idp_sso_service_url'].present? && config['idp_cert_fingerprint'].present?
|
||||
rescue JSON::ParserError
|
||||
# Invalid JSON, treat as not configured
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless saml_configured
|
||||
redirect_to new_user_session_path, alert: 'SAML SSO is not configured. Please contact your administrator to set up SAML authentication.'
|
||||
return
|
||||
end
|
||||
when 'google_oauth2'
|
||||
if Rails.application.credentials.google_client_id.blank? || Rails.application.credentials.google_client_id == 'placeholder_client_id'
|
||||
redirect_to new_user_session_path, alert: 'Google OAuth is not configured. Please contact your administrator to set up Google authentication.'
|
||||
return
|
||||
end
|
||||
when 'microsoft_graph'
|
||||
if Rails.application.credentials.microsoft_client_id.blank? || Rails.application.credentials.microsoft_client_id == 'placeholder_client_id'
|
||||
redirect_to new_user_session_path, alert: 'Microsoft OAuth is not configured. Please contact your administrator to set up Microsoft authentication.'
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
# If we get here, redirect to configuration page
|
||||
provider_name = provider&.humanize || 'SSO'
|
||||
redirect_to new_user_session_path, alert: "#{provider_name} authentication is not available. Please contact your administrator."
|
||||
end
|
||||
|
||||
def metadata
|
||||
# Generate basic SAML metadata for the service provider
|
||||
entity_id = ENV.fetch('SAML_SP_ENTITY_ID', 'docuseal')
|
||||
app_url = ENV.fetch('APP_URL', 'http://localhost:3000')
|
||||
callback_url = "#{app_url}/auth/saml/callback"
|
||||
|
||||
# Build comprehensive SP metadata XML
|
||||
logout_url = "#{app_url}/sign_out"
|
||||
|
||||
metadata_xml = <<~XML
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
|
||||
entityID="#{entity_id}">
|
||||
<md:SPSSODescriptor
|
||||
AuthnRequestsSigned="false"
|
||||
WantAssertionsSigned="true"
|
||||
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
|
||||
|
||||
<!-- Name ID Format -->
|
||||
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>
|
||||
|
||||
<!-- Assertion Consumer Services -->
|
||||
<md:AssertionConsumerService
|
||||
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
||||
Location="#{callback_url}"
|
||||
index="0"
|
||||
isDefault="true" />
|
||||
<md:AssertionConsumerService
|
||||
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
||||
Location="#{callback_url}"
|
||||
index="1" />
|
||||
|
||||
<!-- Single Logout Service -->
|
||||
<md:SingleLogoutService
|
||||
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
||||
Location="#{logout_url}" />
|
||||
<md:SingleLogoutService
|
||||
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
||||
Location="#{logout_url}" />
|
||||
|
||||
</md:SPSSODescriptor>
|
||||
</md:EntityDescriptor>
|
||||
XML
|
||||
|
||||
response.headers['Content-Disposition'] = 'attachment; filename="saml-metadata.xml"'
|
||||
render xml: metadata_xml, content_type: 'application/samlmetadata+xml'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def handle_omniauth_callback(provider_name)
|
||||
@user = User.from_omniauth(request.env['omniauth.auth'])
|
||||
|
||||
if @user&.persisted?
|
||||
sign_in_and_redirect @user, event: :authentication
|
||||
set_flash_message(:notice, :success, kind: provider_name) if is_navigational_format?
|
||||
else
|
||||
# Store the omniauth data in session for potential account linking
|
||||
session['devise.omniauth_data'] = request.env['omniauth.auth'].except(:extra)
|
||||
|
||||
# Redirect to registration with error message
|
||||
redirect_to new_user_registration_url,
|
||||
alert: "There was an issue with your #{provider_name} account. Please try again or contact support."
|
||||
end
|
||||
rescue StandardError => e
|
||||
Rails.logger.error "Omniauth callback error: #{e.message}"
|
||||
Rails.logger.error e.backtrace.join("\n")
|
||||
|
||||
redirect_to new_user_session_path,
|
||||
alert: "Authentication failed. Please try again or contact support."
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,52 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class SamlConfigService
|
||||
def self.load_config(account = nil)
|
||||
# Try to load from database first
|
||||
if account
|
||||
config_record = EncryptedConfig.find_by(account: account, key: 'saml_configs')
|
||||
if config_record&.value.present?
|
||||
return JSON.parse(config_record.value).with_indifferent_access
|
||||
end
|
||||
end
|
||||
|
||||
# Fall back to environment variables
|
||||
{
|
||||
idp_sso_service_url: ENV['SAML_IDP_SSO_SERVICE_URL'],
|
||||
idp_cert_fingerprint: ENV['SAML_IDP_CERT_FINGERPRINT'],
|
||||
sp_entity_id: ENV.fetch('SAML_SP_ENTITY_ID', 'docuseal'),
|
||||
name_identifier_format: 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
|
||||
email_attribute: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress',
|
||||
first_name_attribute: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname',
|
||||
last_name_attribute: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname',
|
||||
name_attribute: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name'
|
||||
}.with_indifferent_access
|
||||
rescue => e
|
||||
Rails.logger.warn "Could not load SAML config: #{e.message}"
|
||||
{}
|
||||
end
|
||||
|
||||
def self.configured?(account = nil)
|
||||
config = load_config(account)
|
||||
config[:idp_sso_service_url].present? && config[:idp_cert_fingerprint].present?
|
||||
end
|
||||
|
||||
def self.omniauth_config(account = nil)
|
||||
config = load_config(account)
|
||||
return nil unless configured?(account)
|
||||
|
||||
{
|
||||
assertion_consumer_service_url: "#{ENV.fetch('APP_URL', 'http://localhost:3000')}/auth/saml/callback",
|
||||
sp_entity_id: config[:sp_entity_id],
|
||||
idp_sso_service_url: config[:idp_sso_service_url],
|
||||
idp_cert_fingerprint: config[:idp_cert_fingerprint],
|
||||
name_identifier_format: config[:name_identifier_format],
|
||||
attribute_statements: {
|
||||
email: [config[:email_attribute]],
|
||||
first_name: [config[:first_name_attribute]],
|
||||
last_name: [config[:last_name_attribute]],
|
||||
name: [config[:name_attribute]]
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,160 @@
|
||||
<%= form_with model: @encrypted_config, url: settings_sso_path, method: :patch, local: true, multipart: true, class: "space-y-4" do |f| %>
|
||||
<div class="space-y-6">
|
||||
<div class="alert alert-info">
|
||||
<%= svg_icon('info_circle', class: 'w-6 h-6') %>
|
||||
<div>
|
||||
<p class="font-bold">SAML SSO Configuration</p>
|
||||
<p class="text-gray-700">
|
||||
Configure SAML 2.0 Single Sign-On for your organization. Users will be able to sign in using your identity provider.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% saml_config = @encrypted_config.value.present? ? JSON.parse(@encrypted_config.value) : {} %>
|
||||
|
||||
<!-- IdP Metadata Upload Section -->
|
||||
<div class="card bg-base-100 border border-base-300">
|
||||
<div class="card-body">
|
||||
<h3 class="card-title text-lg">Quick Setup: Upload IdP Metadata</h3>
|
||||
<p class="text-sm text-gray-600 mb-4">
|
||||
Upload your Identity Provider's metadata XML file to automatically populate the configuration below.
|
||||
</p>
|
||||
|
||||
<div class="form-control">
|
||||
<%= label_tag 'idp_metadata_file', 'IdP Metadata XML File', class: 'label' %>
|
||||
<%= file_field_tag 'idp_metadata_file',
|
||||
accept: '.xml,application/xml,text/xml',
|
||||
class: 'file-input file-input-bordered w-full' %>
|
||||
<div class="label">
|
||||
<span class="label-text-alt">Select your IdP's metadata.xml file to auto-configure SAML settings</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-actions justify-end mt-4">
|
||||
<%= submit_tag 'Parse Metadata', class: 'btn btn-primary btn-sm' %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="divider">OR configure manually</div>
|
||||
|
||||
<div class="form-control">
|
||||
<%= label_tag 'saml_config[idp_sso_service_url]', 'Identity Provider SSO URL', class: 'label' %>
|
||||
<%= text_field_tag 'saml_config[idp_sso_service_url]', saml_config['idp_sso_service_url'],
|
||||
class: 'input input-bordered w-full',
|
||||
placeholder: 'https://your-idp.com/sso/saml',
|
||||
required: true %>
|
||||
<div class="label">
|
||||
<span class="label-text-alt">The URL where users will be redirected to authenticate</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<%= label_tag 'saml_config[idp_cert_fingerprint]', 'Identity Provider Certificate Fingerprint', class: 'label' %>
|
||||
<%= text_field_tag 'saml_config[idp_cert_fingerprint]', saml_config['idp_cert_fingerprint'],
|
||||
class: 'input input-bordered w-full',
|
||||
placeholder: 'AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD',
|
||||
required: true %>
|
||||
<div class="label">
|
||||
<span class="label-text-alt">SHA1 fingerprint of your IdP's certificate</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<%= label_tag 'saml_config[sp_entity_id]', 'Service Provider Entity ID', class: 'label' %>
|
||||
<%= text_field_tag 'saml_config[sp_entity_id]', saml_config['sp_entity_id'] || 'docuseal',
|
||||
class: 'input input-bordered w-full',
|
||||
placeholder: 'docuseal',
|
||||
required: true %>
|
||||
<div class="label">
|
||||
<span class="label-text-alt">Unique identifier for this DocuSeal instance</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<%= label_tag 'saml_config[name_identifier_format]', 'Name ID Format', class: 'label' %>
|
||||
<%= select_tag 'saml_config[name_identifier_format]',
|
||||
options_for_select([
|
||||
['Email Address', 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'],
|
||||
['Persistent', 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent'],
|
||||
['Transient', 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient']
|
||||
], saml_config['name_identifier_format'] || 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'),
|
||||
class: 'select select-bordered w-full' %>
|
||||
<div class="label">
|
||||
<span class="label-text-alt">Format for the user identifier sent by your IdP</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="divider">Attribute Mapping</div>
|
||||
|
||||
<div class="form-control">
|
||||
<%= label_tag 'saml_config[email_attribute]', 'Email Attribute', class: 'label' %>
|
||||
<%= text_field_tag 'saml_config[email_attribute]',
|
||||
saml_config['email_attribute'] || 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress',
|
||||
class: 'input input-bordered w-full',
|
||||
placeholder: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress' %>
|
||||
<div class="label">
|
||||
<span class="label-text-alt">SAML attribute name that contains the user's email</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<%= label_tag 'saml_config[first_name_attribute]', 'First Name Attribute', class: 'label' %>
|
||||
<%= text_field_tag 'saml_config[first_name_attribute]',
|
||||
saml_config['first_name_attribute'] || 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname',
|
||||
class: 'input input-bordered w-full',
|
||||
placeholder: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname' %>
|
||||
<div class="label">
|
||||
<span class="label-text-alt">SAML attribute name that contains the user's first name</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<%= label_tag 'saml_config[last_name_attribute]', 'Last Name Attribute', class: 'label' %>
|
||||
<%= text_field_tag 'saml_config[last_name_attribute]',
|
||||
saml_config['last_name_attribute'] || 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname',
|
||||
class: 'input input-bordered w-full',
|
||||
placeholder: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname' %>
|
||||
<div class="label">
|
||||
<span class="label-text-alt">SAML attribute name that contains the user's last name</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="divider">Service Provider Information</div>
|
||||
|
||||
<div class="alert">
|
||||
<%= svg_icon('info_circle', class: 'w-6 h-6') %>
|
||||
<div>
|
||||
<p class="font-bold">Configuration URLs for your Identity Provider</p>
|
||||
<div class="mt-2 space-y-1 text-sm">
|
||||
<p><strong>Assertion Consumer Service URL:</strong></p>
|
||||
<code class="bg-base-200 px-2 py-1 rounded text-xs"><%= "#{request.base_url}/auth/saml/callback" %></code>
|
||||
|
||||
<p class="mt-2"><strong>SP Metadata URL:</strong></p>
|
||||
<div class="flex items-center gap-2">
|
||||
<code class="bg-base-200 px-2 py-1 rounded text-xs flex-1"><%= "#{request.base_url}/auth/saml/metadata" %></code>
|
||||
<a href="<%= "#{request.base_url}/auth/saml/metadata" %>"
|
||||
class="btn btn-sm btn-outline"
|
||||
target="_blank"
|
||||
title="Download SAML Metadata">
|
||||
Download
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<p class="mt-2"><strong>SP Entity ID:</strong></p>
|
||||
<code class="bg-base-200 px-2 py-1 rounded text-xs"><%= saml_config['sp_entity_id'] || 'docuseal' %></code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<%= f.submit 'Save SAML Configuration', class: 'btn btn-primary' %>
|
||||
<% if @encrypted_config.persisted? && @encrypted_config.value.present? %>
|
||||
<%= link_to 'Test SAML Login', user_saml_omniauth_authorize_path,
|
||||
method: :post,
|
||||
class: 'btn btn-outline',
|
||||
data: { turbo: false } %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
@ -0,0 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AddOmniauthToUsers < ActiveRecord::Migration[7.1]
|
||||
def change
|
||||
add_column :users, :provider, :string
|
||||
add_column :users, :uid, :string
|
||||
|
||||
add_index :users, [:provider, :uid], unique: true
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,89 @@
|
||||
services:
|
||||
app:
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_started
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.dev
|
||||
tty: true
|
||||
stdin_open: true
|
||||
ports:
|
||||
- 3000:3000
|
||||
volumes:
|
||||
# Mount the entire codebase for live development
|
||||
- .:/app
|
||||
# Preserve node_modules and bundle cache
|
||||
- /app/node_modules
|
||||
- bundle_cache:/usr/local/bundle
|
||||
# Mount data directory
|
||||
- ./docuseal:/data/docuseal
|
||||
environment:
|
||||
- RAILS_ENV=development
|
||||
- NODE_ENV=development
|
||||
- DATABASE_URL=postgresql://postgres:postgres@postgres:5432/docuseal_development
|
||||
- REDIS_URL=redis://redis:6379/0
|
||||
- FORCE_SSL=false
|
||||
- SECRET_KEY_BASE=development_secret_key_base_change_in_production
|
||||
command: >
|
||||
sh -c "
|
||||
bundle install &&
|
||||
yarn install &&
|
||||
bundle exec rails db:create db:migrate &&
|
||||
rm -f /app/tmp/pids/server.pid &&
|
||||
bundle exec rails server -b 0.0.0.0
|
||||
"
|
||||
|
||||
postgres:
|
||||
image: postgres:15
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: docuseal_development
|
||||
ports:
|
||||
- "5432:5432"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
|
||||
# Optional: Sidekiq for background jobs
|
||||
sidekiq:
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_started
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.dev
|
||||
volumes:
|
||||
- .:/app
|
||||
- /app/node_modules
|
||||
- bundle_cache:/usr/local/bundle
|
||||
environment:
|
||||
- RAILS_ENV=development
|
||||
- DATABASE_URL=postgresql://postgres:postgres@postgres:5432/docuseal_development
|
||||
- REDIS_URL=redis://redis:6379/0
|
||||
command: >
|
||||
sh -c "
|
||||
bundle install &&
|
||||
bundle exec sidekiq
|
||||
"
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
bundle_cache:
|
||||
Loading…
Reference in new issue