chore: remove debugging files
This commit is contained in:
@@ -1,153 +0,0 @@
|
||||
# SAML Testing Guide with MockSAML.com
|
||||
|
||||
## Prerequisites
|
||||
- Chatwoot running locally on `http://localhost:3000`
|
||||
- MockSAML.com account (free tier works)
|
||||
- A user account in Chatwoot that you'll use for testing
|
||||
|
||||
## Step 1: Set Up MockSAML
|
||||
|
||||
1. Go to https://mocksaml.com and sign in
|
||||
2. Click "Create New Configuration"
|
||||
3. Fill in the configuration:
|
||||
- **Configuration Name**: Chatwoot Test
|
||||
- **Service Provider Entity ID**: `http://localhost:3000/saml/1` (or use your custom entity ID from SAML settings)
|
||||
- **Assertion Consumer Service URL**: `http://localhost:3000/auth/saml/1/callback`
|
||||
- **Authentication Method**: Email/Password
|
||||
- **Default RelayState**: `/app/dashboard` (optional)
|
||||
|
||||
4. In the "Attributes" section, add:
|
||||
- **Email Address**:
|
||||
- Name: `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress`
|
||||
- Value: Use the email of an existing user in your Chatwoot database
|
||||
- **Name**:
|
||||
- Name: `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name`
|
||||
- Value: The user's full name
|
||||
|
||||
5. Save the configuration and note down:
|
||||
- The SSO URL (will be like `https://mocksaml.com/api/saml/sso/{config-id}`)
|
||||
- Download the certificate from the "Metadata" section
|
||||
|
||||
## Step 2: Update Chatwoot SAML Settings
|
||||
|
||||
### Using Rails Console:
|
||||
```ruby
|
||||
rails console
|
||||
|
||||
# Find your account
|
||||
account = Account.find(1)
|
||||
|
||||
# Get or create SAML settings
|
||||
saml_settings = account.account_saml_settings || account.create_account_saml_settings
|
||||
|
||||
# Update with MockSAML values
|
||||
saml_settings.update!(
|
||||
enabled: true,
|
||||
sso_url: 'https://mocksaml.com/api/saml/sso/YOUR_CONFIG_ID', # Replace with your MockSAML SSO URL
|
||||
certificate: %{-----BEGIN CERTIFICATE-----
|
||||
PASTE_THE_CERTIFICATE_FROM_MOCKSAML_HERE
|
||||
-----END CERTIFICATE-----},
|
||||
sp_entity_id: 'http://localhost:3000/saml/1', # Must match what you set in MockSAML
|
||||
enforced_sso: false
|
||||
)
|
||||
|
||||
# Verify settings
|
||||
puts saml_settings.certificate_fingerprint
|
||||
```
|
||||
|
||||
### Using API:
|
||||
```bash
|
||||
# Update SAML settings via API
|
||||
curl -X PUT http://localhost:3000/api/v1/accounts/1/saml_settings \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "api_access_token: YOUR_API_TOKEN" \
|
||||
-d '{
|
||||
"enabled": true,
|
||||
"sso_url": "https://mocksaml.com/api/saml/sso/YOUR_CONFIG_ID",
|
||||
"certificate": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
|
||||
"sp_entity_id": "http://localhost:3000/saml/1"
|
||||
}'
|
||||
```
|
||||
|
||||
## Step 3: Test the SAML Flow
|
||||
|
||||
### Method 1: Direct Browser Test
|
||||
1. Open your browser
|
||||
2. Navigate to: `http://localhost:3000/auth/saml/1`
|
||||
3. You should be redirected to MockSAML login page
|
||||
4. Enter the test credentials you configured in MockSAML
|
||||
5. After successful authentication, you should be redirected to:
|
||||
- `http://localhost:3000/app/login?email=USER_EMAIL&sso_auth_token=TOKEN`
|
||||
6. The frontend should automatically complete the login using the SSO token
|
||||
|
||||
### Method 2: API Test
|
||||
```bash
|
||||
# 1. Get SAML configuration
|
||||
curl http://localhost:3000/api/v1/accounts/1/saml/sso
|
||||
|
||||
# 2. Initiate SAML flow (in browser)
|
||||
# Visit: http://localhost:3000/auth/saml/1
|
||||
```
|
||||
|
||||
## Step 4: Verify the Login
|
||||
|
||||
1. Check browser developer tools:
|
||||
- Network tab should show the SAML flow
|
||||
- After redirect, check for `/auth/sign_in` API call with SSO token
|
||||
|
||||
2. Check Rails logs:
|
||||
```bash
|
||||
tail -f log/development.log | grep -E "OmniauthController|SAML|SSO"
|
||||
```
|
||||
|
||||
3. Verify in Rails console:
|
||||
```ruby
|
||||
# Check if SSO token was created
|
||||
Redis::Alfred.keys("chatwoot:user:*:sso_auth_token:*")
|
||||
|
||||
# Check user's last sign in
|
||||
User.find_by(email: 'your-test-email@example.com').current_sign_in_at
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues:
|
||||
|
||||
1. **"SAML not enabled for this account"**
|
||||
- Ensure `enabled: true` in SAML settings
|
||||
- Verify account ID matches
|
||||
|
||||
2. **Redirect to /auth/sign_in without SSO token**
|
||||
- Check if SAML response validation failed
|
||||
- Verify certificate and fingerprint match
|
||||
- Check Rails logs for OmniAuth errors
|
||||
|
||||
3. **"User not found" error**
|
||||
- Ensure the email in MockSAML matches an existing Chatwoot user
|
||||
- Check if `User.find_by(email: 'test@example.com')` returns a user
|
||||
|
||||
4. **Certificate validation errors**
|
||||
- Make sure you copied the entire certificate including BEGIN/END lines
|
||||
- Certificate should not have any extra whitespace
|
||||
|
||||
### Debug Commands:
|
||||
|
||||
```bash
|
||||
# Check current SAML settings
|
||||
rails runner "pp AccountSamlSettings.find_by(account_id: 1)"
|
||||
|
||||
# Test metadata endpoint
|
||||
curl http://localhost:3000/auth/saml/1/metadata
|
||||
|
||||
# Check OmniAuth configuration
|
||||
rails runner "pp Rails.application.config.omniauth"
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
Once basic login works:
|
||||
1. Test with multiple users
|
||||
2. Test logout flow (if implementing SLO)
|
||||
3. Test with enforced SSO enabled
|
||||
4. Implement user provisioning for new users
|
||||
5. Add role mapping from SAML attributes
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/bin/bash
|
||||
echo "Starting Rails server with debug logging..."
|
||||
echo "Watch for [SAML Setup] and [OmniAuth Callback] messages in the logs"
|
||||
echo ""
|
||||
bundle exec rails server
|
||||
@@ -1,270 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
# Run with: rails runner test_saml_devise_flow.rb
|
||||
|
||||
require 'net/http'
|
||||
require 'uri'
|
||||
require 'json'
|
||||
require 'base64'
|
||||
require 'time'
|
||||
|
||||
# Enable OmniAuth debug logging
|
||||
OmniAuth.config.logger = Rails.logger
|
||||
OmniAuth.config.logger.level = Logger::DEBUG
|
||||
|
||||
puts "=== SAML Authentication Flow Test ==="
|
||||
puts ""
|
||||
|
||||
# 1. Find SAML settings
|
||||
account_id = 1
|
||||
saml_settings = AccountSamlSettings.find_by(account_id: account_id, enabled: true)
|
||||
|
||||
unless saml_settings
|
||||
puts "❌ No SAML settings found for account #{account_id}"
|
||||
exit 1
|
||||
end
|
||||
|
||||
puts "✅ Found SAML settings for account #{account_id}"
|
||||
puts " SSO URL: #{saml_settings.sso_url}"
|
||||
puts " SP Entity ID: #{saml_settings.sp_entity_id_or_default}"
|
||||
puts ""
|
||||
|
||||
# 2. Find a test user
|
||||
user = User.first
|
||||
unless user
|
||||
puts "❌ No user found to test with"
|
||||
exit 1
|
||||
end
|
||||
|
||||
puts "✅ Testing with user: #{user.email}"
|
||||
puts ""
|
||||
|
||||
# 3. Test the initial SAML request endpoint
|
||||
puts "=== Testing SAML Initiation ==="
|
||||
saml_init_url = "http://localhost:3000/auth/saml?account_id=#{account_id}"
|
||||
puts "GET #{saml_init_url}"
|
||||
|
||||
uri = URI(saml_init_url)
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
http.use_ssl = false
|
||||
http.set_debug_output($stdout) if ENV['DEBUG']
|
||||
|
||||
# Enable cookie handling
|
||||
http.start do |http_session|
|
||||
request = Net::HTTP::Get.new(uri)
|
||||
request['Accept'] = 'text/html'
|
||||
|
||||
response = http_session.request(request)
|
||||
puts "Response Status: #{response.code}"
|
||||
|
||||
# Store cookies
|
||||
cookies = response.get_fields('set-cookie')
|
||||
cookie_hash = {}
|
||||
if cookies
|
||||
cookies.each do |cookie|
|
||||
cookie_hash[cookie.split(';')[0].split('=')[0]] = cookie.split(';')[0]
|
||||
end
|
||||
end
|
||||
|
||||
if response.code == '307'
|
||||
puts "✅ Got temporary redirect to: #{response['location']}"
|
||||
|
||||
# Follow the redirect
|
||||
redirect_uri = URI(response['location'])
|
||||
redirect_request = Net::HTTP::Get.new(redirect_uri)
|
||||
redirect_request['Cookie'] = cookie_hash.values.join('; ') if cookie_hash.any?
|
||||
|
||||
redirect_response = http_session.request(redirect_request)
|
||||
puts "Redirect Response Status: #{redirect_response.code}"
|
||||
|
||||
if redirect_response.code == '302' || redirect_response.code == '303'
|
||||
puts "✅ Redirected to IdP: #{redirect_response['location']}"
|
||||
|
||||
# Check if it's redirecting to the IdP
|
||||
if redirect_response['location']&.include?(saml_settings.sso_url)
|
||||
puts "✅ Correctly redirecting to IdP"
|
||||
else
|
||||
puts "❌ Not redirecting to expected IdP URL"
|
||||
end
|
||||
end
|
||||
elsif response.code == '302' || response.code == '303'
|
||||
puts "✅ Redirected to: #{response['location']}"
|
||||
|
||||
# Check if it's redirecting to the IdP
|
||||
if response['location']&.include?(saml_settings.sso_url)
|
||||
puts "✅ Correctly redirecting to IdP"
|
||||
else
|
||||
puts "❌ Not redirecting to expected IdP URL"
|
||||
end
|
||||
else
|
||||
puts "❌ Expected redirect but got #{response.code}"
|
||||
puts "Response body: #{response.body[0..500]}"
|
||||
end
|
||||
end
|
||||
|
||||
puts ""
|
||||
|
||||
# 4. Simulate SAML callback
|
||||
puts "=== Simulating SAML Callback ==="
|
||||
|
||||
# Generate a mock SAML response
|
||||
def generate_saml_response(saml_settings, user)
|
||||
issue_instant = Time.now.utc.iso8601
|
||||
response_id = "_#{SecureRandom.hex(21)}"
|
||||
assertion_id = "_#{SecureRandom.hex(21)}"
|
||||
|
||||
# Create a simple unsigned SAML response for testing
|
||||
saml_response = <<-XML
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
|
||||
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
|
||||
ID="#{response_id}"
|
||||
Version="2.0"
|
||||
IssueInstant="#{issue_instant}"
|
||||
Destination="http://localhost:3000/omniauth/saml/callback">
|
||||
<saml:Issuer>#{saml_settings.sso_url}</saml:Issuer>
|
||||
<samlp:Status>
|
||||
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
|
||||
</samlp:Status>
|
||||
<saml:Assertion ID="#{assertion_id}" Version="2.0" IssueInstant="#{issue_instant}">
|
||||
<saml:Issuer>#{saml_settings.sso_url}</saml:Issuer>
|
||||
<saml:Subject>
|
||||
<saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">#{user.email}</saml:NameID>
|
||||
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
|
||||
<saml:SubjectConfirmationData NotOnOrAfter="#{(Time.now.utc + 300).iso8601}"
|
||||
Recipient="http://localhost:3000/omniauth/saml/callback"/>
|
||||
</saml:SubjectConfirmation>
|
||||
</saml:Subject>
|
||||
<saml:AuthnStatement AuthnInstant="#{issue_instant}">
|
||||
<saml:AuthnContext>
|
||||
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef>
|
||||
</saml:AuthnContext>
|
||||
</saml:AuthnStatement>
|
||||
<saml:AttributeStatement>
|
||||
<saml:Attribute Name="email">
|
||||
<saml:AttributeValue>#{user.email}</saml:AttributeValue>
|
||||
</saml:Attribute>
|
||||
<saml:Attribute Name="name">
|
||||
<saml:AttributeValue>#{user.name}</saml:AttributeValue>
|
||||
</saml:Attribute>
|
||||
</saml:AttributeStatement>
|
||||
</saml:Assertion>
|
||||
</samlp:Response>
|
||||
XML
|
||||
|
||||
Base64.strict_encode64(saml_response)
|
||||
end
|
||||
|
||||
callback_url = "http://localhost:3000/omniauth/saml/callback"
|
||||
puts "POST #{callback_url}"
|
||||
|
||||
saml_response = generate_saml_response(saml_settings, user)
|
||||
|
||||
uri = URI(callback_url)
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
|
||||
# We need to maintain session cookies
|
||||
cookie_jar = {}
|
||||
|
||||
# First, get a session by visiting the auth endpoint
|
||||
session_uri = URI(saml_init_url)
|
||||
session_http = Net::HTTP.new(session_uri.host, session_uri.port)
|
||||
session_request = Net::HTTP::Get.new(session_uri)
|
||||
|
||||
session_response = session_http.request(session_request)
|
||||
if session_response['set-cookie']
|
||||
cookie_jar['Cookie'] = session_response['set-cookie'].split(';')[0]
|
||||
puts "✅ Got session cookie"
|
||||
end
|
||||
|
||||
# Now send the callback with the session
|
||||
request = Net::HTTP::Post.new(uri)
|
||||
request['Content-Type'] = 'application/x-www-form-urlencoded'
|
||||
request['Cookie'] = cookie_jar['Cookie'] if cookie_jar['Cookie']
|
||||
|
||||
callback_params = {
|
||||
'SAMLResponse' => saml_response,
|
||||
'RelayState' => ''
|
||||
}
|
||||
|
||||
request.body = URI.encode_www_form(callback_params)
|
||||
|
||||
puts "Sending SAML response for user: #{user.email}"
|
||||
response = http.request(request)
|
||||
|
||||
puts "Response Status: #{response.code}"
|
||||
|
||||
if response.code == '302' || response.code == '303'
|
||||
redirect_location = response['location']
|
||||
puts "✅ Redirected to: #{redirect_location}"
|
||||
|
||||
# Parse redirect URL to check for SSO token
|
||||
if redirect_location
|
||||
redirect_uri = URI(redirect_location)
|
||||
if redirect_uri.path == '/app/login'
|
||||
params = URI.decode_www_form(redirect_uri.query || '')
|
||||
params_hash = params.to_h
|
||||
|
||||
puts ""
|
||||
puts "=== Login Redirect Parameters ==="
|
||||
puts "Email: #{params_hash['email']}"
|
||||
puts "SSO Token: #{params_hash['sso_auth_token']}"
|
||||
|
||||
if params_hash['sso_auth_token']
|
||||
puts ""
|
||||
puts "✅ Successfully generated SSO auth token!"
|
||||
|
||||
# Optional: Test the SSO token login
|
||||
puts ""
|
||||
puts "=== Testing SSO Token Login ==="
|
||||
login_url = "http://localhost:3000/auth/sign_in"
|
||||
|
||||
login_params = {
|
||||
email: params_hash['email'],
|
||||
sso_auth_token: params_hash['sso_auth_token']
|
||||
}
|
||||
|
||||
uri = URI(login_url)
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
request = Net::HTTP::Post.new(uri)
|
||||
request['Content-Type'] = 'application/json'
|
||||
request.body = login_params.to_json
|
||||
|
||||
login_response = http.request(request)
|
||||
puts "Login Response Status: #{login_response.code}"
|
||||
|
||||
if login_response.code == '200'
|
||||
puts "✅ SSO token login successful!"
|
||||
|
||||
# Check for auth headers
|
||||
if login_response['access-token']
|
||||
puts ""
|
||||
puts "Authentication Headers:"
|
||||
puts " access-token: #{login_response['access-token']}"
|
||||
puts " client: #{login_response['client']}"
|
||||
puts " uid: #{login_response['uid']}"
|
||||
end
|
||||
else
|
||||
puts "❌ SSO token login failed"
|
||||
puts "Response: #{login_response.body}"
|
||||
end
|
||||
elsif params_hash['error']
|
||||
puts "❌ Error: #{params_hash['error']}"
|
||||
end
|
||||
else
|
||||
puts "❌ Unexpected redirect path: #{redirect_uri.path}"
|
||||
end
|
||||
end
|
||||
else
|
||||
puts "❌ Expected redirect but got #{response.code}"
|
||||
puts "Response body: #{response.body[0..1000]}"
|
||||
|
||||
# Check if it's an error page
|
||||
if response.body.include?('error') || response.body.include?('Error')
|
||||
puts ""
|
||||
puts "Possible error in response. Check Rails logs for details."
|
||||
end
|
||||
end
|
||||
|
||||
puts ""
|
||||
puts "=== Test Complete ==="
|
||||
puts "Check your Rails server logs for detailed OmniAuth debug information"
|
||||
@@ -1,195 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
# Run with: rails runner test_saml_login.rb
|
||||
|
||||
require 'net/http'
|
||||
require 'uri'
|
||||
require 'json'
|
||||
require 'openssl'
|
||||
require 'base64'
|
||||
require 'time'
|
||||
|
||||
# Find SAML settings for account 1
|
||||
saml_settings = AccountSamlSettings.find_by(id: 1)
|
||||
|
||||
unless saml_settings
|
||||
puts "No SAML settings found with ID 1"
|
||||
exit 1
|
||||
end
|
||||
|
||||
puts "Found SAML settings for account #{saml_settings.account_id}:"
|
||||
puts " SSO URL: #{saml_settings.sso_url}"
|
||||
puts " SP Entity ID: #{saml_settings.sp_entity_id_or_default}"
|
||||
puts " Certificate Fingerprint: #{saml_settings.certificate_fingerprint}"
|
||||
puts ""
|
||||
|
||||
# Find a user to login as
|
||||
user = User.first
|
||||
unless user
|
||||
puts "No user found to test with"
|
||||
exit 1
|
||||
end
|
||||
|
||||
puts "Testing with user: #{user.email}"
|
||||
puts ""
|
||||
|
||||
# Generate a proper SAML response
|
||||
def generate_saml_response(saml_settings, user)
|
||||
# Create a basic SAML response XML
|
||||
issue_instant = Time.now.utc.iso8601
|
||||
response_id = "_#{SecureRandom.hex(21)}"
|
||||
assertion_id = "_#{SecureRandom.hex(21)}"
|
||||
|
||||
saml_response = <<-XML
|
||||
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
|
||||
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
|
||||
ID="#{response_id}"
|
||||
Version="2.0"
|
||||
IssueInstant="#{issue_instant}"
|
||||
Destination="http://localhost:3000/saml/#{saml_settings.account_id}/callback">
|
||||
<saml:Issuer>#{saml_settings.sso_url}</saml:Issuer>
|
||||
<samlp:Status>
|
||||
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
|
||||
</samlp:Status>
|
||||
<saml:Assertion ID="#{assertion_id}" Version="2.0" IssueInstant="#{issue_instant}">
|
||||
<saml:Issuer>#{saml_settings.sso_url}</saml:Issuer>
|
||||
<saml:Subject>
|
||||
<saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">#{user.email}</saml:NameID>
|
||||
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
|
||||
<saml:SubjectConfirmationData NotOnOrAfter="#{(Time.now.utc + 300).iso8601}"
|
||||
Recipient="http://localhost:3000/saml/#{saml_settings.account_id}/callback"/>
|
||||
</saml:SubjectConfirmation>
|
||||
</saml:Subject>
|
||||
<saml:AuthnStatement AuthnInstant="#{issue_instant}">
|
||||
<saml:AuthnContext>
|
||||
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef>
|
||||
</saml:AuthnContext>
|
||||
</saml:AuthnStatement>
|
||||
<saml:AttributeStatement>
|
||||
<saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress">
|
||||
<saml:AttributeValue>#{user.email}</saml:AttributeValue>
|
||||
</saml:Attribute>
|
||||
<saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name">
|
||||
<saml:AttributeValue>#{user.name}</saml:AttributeValue>
|
||||
</saml:Attribute>
|
||||
</saml:AttributeStatement>
|
||||
</saml:Assertion>
|
||||
</samlp:Response>
|
||||
XML
|
||||
|
||||
Base64.encode64(saml_response)
|
||||
end
|
||||
|
||||
# First, test the SSO endpoint
|
||||
sso_url = "http://localhost:3000/api/v1/accounts/#{saml_settings.account_id}/saml/sso"
|
||||
puts "Testing SSO endpoint: #{sso_url}"
|
||||
|
||||
uri = URI(sso_url)
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
request = Net::HTTP::Get.new(uri)
|
||||
request['Content-Type'] = 'application/json'
|
||||
|
||||
response = http.request(request)
|
||||
puts "SSO Response Status: #{response.code}"
|
||||
puts "SSO Response Body: #{response.body}"
|
||||
puts ""
|
||||
|
||||
# Now test the callback with a proper SAML response
|
||||
callback_url = "http://localhost:3000/saml/#{saml_settings.account_id}/callback"
|
||||
puts "Testing callback endpoint: #{callback_url}"
|
||||
|
||||
# Generate SAML response
|
||||
saml_response = generate_saml_response(saml_settings, user)
|
||||
|
||||
# Create a form-encoded body (SAML callbacks are typically POST with form data)
|
||||
callback_params = {
|
||||
'SAMLResponse' => saml_response,
|
||||
'RelayState' => '/app/dashboard'
|
||||
}
|
||||
|
||||
uri = URI(callback_url)
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
request = Net::HTTP::Post.new(uri)
|
||||
request['Content-Type'] = 'application/x-www-form-urlencoded'
|
||||
request.body = URI.encode_www_form(callback_params)
|
||||
|
||||
puts "Sending POST to callback with SAML response for user: #{user.email}"
|
||||
response = http.request(request)
|
||||
puts "Callback Response Status: #{response.code}"
|
||||
puts "Callback Response Headers:"
|
||||
response.each_header do |key, value|
|
||||
puts " #{key}: #{value}" if key.downcase.include?('location') || key.downcase.include?('content-type')
|
||||
end
|
||||
|
||||
if response.code == '302'
|
||||
puts "\nRedirect location: #{response['location']}"
|
||||
|
||||
# Parse the redirect URL to extract SSO token
|
||||
if response['location']
|
||||
redirect_uri = URI(response['location'])
|
||||
params = URI.decode_www_form(redirect_uri.query || '')
|
||||
params_hash = params.to_h
|
||||
|
||||
puts "\nRedirect parameters:"
|
||||
puts " Email: #{params_hash['email']}"
|
||||
puts " SSO Auth Token: #{params_hash['sso_auth_token']}"
|
||||
|
||||
# Test if we can use this SSO token to login
|
||||
if params_hash['sso_auth_token']
|
||||
puts "\n\nTesting SSO token login:"
|
||||
login_url = "http://localhost:3000/auth/sign_in"
|
||||
|
||||
login_params = {
|
||||
email: params_hash['email'],
|
||||
sso_auth_token: params_hash['sso_auth_token']
|
||||
}
|
||||
|
||||
uri = URI(login_url)
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
request = Net::HTTP::Post.new(uri)
|
||||
request['Content-Type'] = 'application/json'
|
||||
request.body = login_params.to_json
|
||||
|
||||
login_response = http.request(request)
|
||||
puts "Login Response Status: #{login_response.code}"
|
||||
|
||||
if login_response.body && !login_response.body.empty?
|
||||
begin
|
||||
json = JSON.parse(login_response.body)
|
||||
puts "Login Response:"
|
||||
puts JSON.pretty_generate(json)
|
||||
|
||||
# Check for auth headers
|
||||
puts "\nAuthentication Headers:"
|
||||
puts " access-token: #{login_response['access-token']}" if login_response['access-token']
|
||||
puts " client: #{login_response['client']}" if login_response['client']
|
||||
puts " uid: #{login_response['uid']}" if login_response['uid']
|
||||
rescue
|
||||
puts "Login Response Body: #{login_response.body}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
elsif response.body && !response.body.empty?
|
||||
puts "\nCallback Response Body:"
|
||||
begin
|
||||
json = JSON.parse(response.body)
|
||||
puts JSON.pretty_generate(json)
|
||||
rescue
|
||||
puts response.body
|
||||
end
|
||||
end
|
||||
|
||||
# Test direct OmniAuth request
|
||||
puts "\n\nTesting OmniAuth request endpoint:"
|
||||
omniauth_url = "http://localhost:3000/saml/#{saml_settings.account_id}"
|
||||
puts "GET #{omniauth_url}"
|
||||
|
||||
uri = URI(omniauth_url)
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
request = Net::HTTP::Get.new(uri)
|
||||
|
||||
response = http.request(request)
|
||||
puts "Response Status: #{response.code}"
|
||||
if response.code == '302'
|
||||
puts "Redirected to: #{response['location']}"
|
||||
end
|
||||
@@ -1,71 +0,0 @@
|
||||
# Testing SAML Authentication Flow
|
||||
|
||||
## 1. Start the SAML Authentication Flow
|
||||
|
||||
Visit this URL in your browser to initiate SAML authentication:
|
||||
|
||||
```
|
||||
http://localhost:3000/auth/saml?account_id=1
|
||||
```
|
||||
|
||||
This should:
|
||||
1. Trigger the OmniAuth SAML provider
|
||||
2. Run the setup phase to load account-specific SAML settings
|
||||
3. Redirect you to MockSAML.com with the correct parameters
|
||||
|
||||
## 2. Complete Authentication at MockSAML
|
||||
|
||||
At MockSAML.com:
|
||||
1. Enter any email address (use an existing Chatwoot user's email)
|
||||
2. Complete the authentication
|
||||
3. MockSAML will POST back to: `http://localhost:3000/omniauth/saml/callback`
|
||||
|
||||
## 3. Expected Flow
|
||||
|
||||
After successful SAML authentication:
|
||||
1. The `devise_overrides/omniauth_callbacks#omniauth_success` method will be called
|
||||
2. It will find the user by email
|
||||
3. Generate an SSO auth token
|
||||
4. Redirect to the frontend login page with the token: `http://localhost:3000/app/login?email=user@example.com&sso_auth_token=xxx`
|
||||
|
||||
## 4. Check Server Logs
|
||||
|
||||
Monitor the Rails server logs for:
|
||||
- "Processing by DeviseOverrides::OmniauthCallbacksController#omniauth_success"
|
||||
- Any SAML-related errors or warnings
|
||||
|
||||
## 5. Debugging
|
||||
|
||||
If you encounter issues:
|
||||
|
||||
1. **Check if SAML settings are loaded:**
|
||||
```ruby
|
||||
rails console
|
||||
AccountSamlSettings.find_by(account_id: 1, enabled: true)
|
||||
```
|
||||
|
||||
2. **Test the setup phase manually:**
|
||||
```ruby
|
||||
# In rails console
|
||||
settings = AccountSamlSettings.find_by(account_id: 1)
|
||||
puts "SSO URL: #{settings.sso_url}"
|
||||
puts "SP Entity ID: #{settings.sp_entity_id_or_default}"
|
||||
puts "Certificate present: #{settings.certificate.present?}"
|
||||
```
|
||||
|
||||
3. **Enable OmniAuth debug logging:**
|
||||
Add this to an initializer:
|
||||
```ruby
|
||||
OmniAuth.config.logger = Rails.logger
|
||||
OmniAuth.config.logger.level = Logger::DEBUG
|
||||
```
|
||||
|
||||
## 6. Alternative Testing with cURL
|
||||
|
||||
You can also test the initial redirect:
|
||||
|
||||
```bash
|
||||
curl -v "http://localhost:3000/auth/saml?account_id=1"
|
||||
```
|
||||
|
||||
This should return a 302 redirect to MockSAML.com.
|
||||
@@ -1,122 +0,0 @@
|
||||
# Testing SAML with MockSAML.com
|
||||
|
||||
## 1. Verify Your SAML Settings
|
||||
|
||||
First, check your current SAML configuration:
|
||||
|
||||
```bash
|
||||
rails console
|
||||
```
|
||||
|
||||
```ruby
|
||||
settings = AccountSamlSettings.find_by(account_id: 1)
|
||||
puts "SSO URL: #{settings.sso_url}"
|
||||
puts "SP Entity ID: #{settings.sp_entity_id_or_default}"
|
||||
puts "ACS URL: #{ENV.fetch('FRONTEND_URL', 'http://localhost:3000')}/omniauth/saml/callback?account_id=1"
|
||||
```
|
||||
|
||||
## 2. Update MockSAML Configuration
|
||||
|
||||
Go to MockSAML.com and ensure these settings match:
|
||||
- **SP Entity ID**: Should match `settings.sp_entity_id_or_default`
|
||||
- **ACS URL**: `http://localhost:3000/omniauth/saml/callback?account_id=1`
|
||||
- **Name ID Format**: Email Address
|
||||
|
||||
## 3. Start the Authentication Flow
|
||||
|
||||
### Option A: Browser Test
|
||||
1. Open your browser
|
||||
2. Navigate to: `http://localhost:3000/auth/saml?account_id=1`
|
||||
3. You should be redirected to MockSAML.com
|
||||
4. Enter any email (use an existing Chatwoot user's email like `john@acme.inc`)
|
||||
5. Complete authentication at MockSAML
|
||||
6. You should be redirected back to Chatwoot login page with SSO token
|
||||
|
||||
### Option B: Direct Link Test
|
||||
1. Get the SAML initiation URL:
|
||||
```
|
||||
http://localhost:3000/auth/saml?account_id=1
|
||||
```
|
||||
|
||||
2. Click or paste this in your browser
|
||||
|
||||
## 4. What to Expect
|
||||
|
||||
### Success Flow:
|
||||
1. Browser redirects to MockSAML.com
|
||||
2. You authenticate at MockSAML
|
||||
3. MockSAML POST backs to `/omniauth/saml/callback?account_id=1`
|
||||
4. Chatwoot processes the SAML response
|
||||
5. You're redirected to: `http://localhost:3000/app/login?email=USER_EMAIL&sso_auth_token=TOKEN`
|
||||
|
||||
### If Using ngrok or Public URL:
|
||||
If you're testing with a public URL (ngrok), update the FRONTEND_URL:
|
||||
```bash
|
||||
export FRONTEND_URL=https://your-ngrok-url.ngrok.io
|
||||
```
|
||||
|
||||
## 5. Debugging Tips
|
||||
|
||||
### Watch Server Logs:
|
||||
Look for these log messages:
|
||||
- `[SAML Setup] Request path:`
|
||||
- `[SAML Setup] Account ID:`
|
||||
- `[SAML Setup] Found settings for account`
|
||||
- `[OmniAuth Callback] Provider: saml`
|
||||
|
||||
### Common Issues:
|
||||
|
||||
1. **"No fingerprint or certificate on settings"**
|
||||
- The account_id isn't being passed properly
|
||||
- Check logs for `[SAML Setup] Account ID:`
|
||||
|
||||
2. **Redirect to /auth/sign_in instead of /app/login**
|
||||
- The SAML response wasn't processed correctly
|
||||
- Check if the user exists with that email
|
||||
|
||||
3. **404 on callback**
|
||||
- The route isn't configured properly
|
||||
- Run `rails routes | grep saml` to verify
|
||||
|
||||
### Manual SAML Response Test:
|
||||
If you want to test just the callback with a SAML response from MockSAML:
|
||||
|
||||
1. Use browser developer tools to intercept the POST to `/omniauth/saml/callback`
|
||||
2. Copy the SAMLResponse parameter
|
||||
3. Use the test script to send it:
|
||||
|
||||
```ruby
|
||||
# save_as: test_saml_callback.rb
|
||||
require 'net/http'
|
||||
require 'uri'
|
||||
|
||||
saml_response = "PASTE_YOUR_SAML_RESPONSE_HERE"
|
||||
callback_url = "http://localhost:3000/omniauth/saml/callback?account_id=1"
|
||||
|
||||
uri = URI(callback_url)
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
request = Net::HTTP::Post.new(uri)
|
||||
request['Content-Type'] = 'application/x-www-form-urlencoded'
|
||||
request.body = URI.encode_www_form({
|
||||
'SAMLResponse' => saml_response,
|
||||
'RelayState' => ''
|
||||
})
|
||||
|
||||
response = http.request(request)
|
||||
puts "Status: #{response.code}"
|
||||
puts "Location: #{response['location']}" if response['location']
|
||||
```
|
||||
|
||||
## 6. Test Different Scenarios
|
||||
|
||||
### Existing User Login:
|
||||
- Use email of existing user (e.g., `john@acme.inc`)
|
||||
- Should redirect with SSO token
|
||||
|
||||
### New User (if enabled):
|
||||
- Use new email address
|
||||
- Should redirect to password setup page (if account signup is enabled)
|
||||
|
||||
### SAML Disabled:
|
||||
- Disable SAML in settings and try again
|
||||
- Should show error message
|
||||
@@ -1,42 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
# Run with: rails runner update_mocksaml_certificate.rb
|
||||
|
||||
# The certificate from the MockSAML response
|
||||
mocksaml_certificate = <<~CERT
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIC4jCCAcoCCQC33wnybT5QZDANBgkqhkiG9w0BAQsFADAyMQswCQYDVQQGEwJV
|
||||
SzEPMA0GA1UECgwGQm94eUhRMRIwEAYDVQQDDAlNb2NrIFNBTUwwIBcNMjIwMjI4
|
||||
MjE0NjM4WhgPMzAyMTA3MDEyMTQ2MzhaMDIxCzAJBgNVBAYTAlVLMQ8wDQYDVQQK
|
||||
DAZCb3h5SFExEjAQBgNVBAMMCU1vY2sgU0FNTDCCASIwDQYJKoZIhvcNAQEBBQAD
|
||||
ggEPADCCAQoCggEBALGfYettMsct1T6tVUwTudNJH5Pnb9GGnkXi9Zw/e6x45DD0
|
||||
RuRONbFlJ2T4RjAE/uG+AjXxXQ8o2SZfb9+GgmCHuTJFNgHoZ1nFVXCmb/Hg8Hpd
|
||||
4vOAGXndixaReOiq3EH5XvpMjMkJ3+8+9VYMzMZOjkgQtAqO36eAFFfNKX7dTj3V
|
||||
pwLkvz6/KFCq8OAwY+AUi4eZm5J57D31GzjHwfjH9WTeX0MyndmnNB1qV75qQR3b
|
||||
2/W5sGHRv+9AarggJkF+ptUkXoLtVA51wcfYm6hILptpde5FQC8RWY1YrswBWAEZ
|
||||
NfyrR4JeSweElNHg4NVOs4TwGjOPwWGqzTfgTlECAwEAATANBgkqhkiG9w0BAQsF
|
||||
AAOCAQEAAYRlYflSXAWoZpFfwNiCQVE5d9zZ0DPzNdWhAybXcTyMf0z5mDf6FWBW
|
||||
5Gyoi9u3EMEDnzLcJNkwJAAc39Apa4I2/tml+Jy29dk8bTyX6m93ngmCgdLh5Za4
|
||||
khuU3AM3L63g7VexCuO7kwkjh/+LqdcIXsVGO6XDfu2QOs1Xpe9zIzLpwm/RNYeX
|
||||
UjbSj5ce/jekpAw7qyVVL4xOyh8AtUW1ek3wIw1MJvEgEPt0d16oshWJpoS1OT8L
|
||||
r/22SvYEo3EmSGdTVGgk3x3s+A0qWAqTcyjr7Q4s/GKYRFfomGwz0TZ4Iw1ZN99M
|
||||
m0eo2USlSRTVl7QHRTuiuSThHpLKQQ==
|
||||
-----END CERTIFICATE-----
|
||||
CERT
|
||||
|
||||
# Find SAML settings for account 1
|
||||
saml_settings = AccountSamlSettings.find_by(account_id: 1)
|
||||
|
||||
if saml_settings
|
||||
puts "Current fingerprint: #{saml_settings.certificate_fingerprint}"
|
||||
|
||||
# Update with MockSAML certificate
|
||||
saml_settings.certificate = mocksaml_certificate
|
||||
saml_settings.save!
|
||||
|
||||
puts "Updated fingerprint: #{saml_settings.reload.certificate_fingerprint}"
|
||||
puts ""
|
||||
puts "SAML settings updated successfully!"
|
||||
puts "The certificate has been updated to match MockSAML."
|
||||
else
|
||||
puts "No SAML settings found for account 1"
|
||||
end
|
||||
Reference in New Issue
Block a user