feat: SAML authentication controllers [CW-2958] (#12319)

This commit is contained in:
Shivam Mishra
2025-09-10 20:02:27 +05:30
committed by GitHub
parent 257df30589
commit 79b93bed77
18 changed files with 653 additions and 27 deletions

View File

@@ -0,0 +1,61 @@
require 'rails_helper'
RSpec.describe 'Enterprise SAML OmniAuth Callbacks', type: :request do
let!(:account) { create(:account) }
let(:saml_settings) { create(:account_saml_settings, account: account) }
def set_saml_config(email = 'test@example.com')
OmniAuth.config.test_mode = true
OmniAuth.config.mock_auth[:saml] = OmniAuth::AuthHash.new(
provider: 'saml',
uid: '123545',
info: {
name: 'Test User',
email: email
}
)
end
before do
allow(ChatwootApp).to receive(:enterprise?).and_return(true)
account.enable_features!('saml')
saml_settings
end
describe '#saml callback' do
it 'creates new user and logs them in' do
with_modified_env FRONTEND_URL: 'http://www.example.com' do
set_saml_config('new_user@example.com')
get "/omniauth/saml/callback?account_id=#{account.id}"
# expect a 302 redirect to auth/saml/callback
expect(response).to redirect_to('http://www.example.com/auth/saml/callback')
follow_redirect!
# expect redirect to login with SSO token
expect(response).to redirect_to(%r{/app/login\?email=.+&sso_auth_token=.+$})
# verify user was created
user = User.from_email('new_user@example.com')
expect(user).to be_present
expect(user.provider).to eq('saml')
end
end
it 'logs in existing user' do
with_modified_env FRONTEND_URL: 'http://www.example.com' do
create(:user, email: 'existing@example.com', account: account)
set_saml_config('existing@example.com')
get "/omniauth/saml/callback?account_id=#{account.id}"
# expect a 302 redirect to auth/saml/callback
expect(response).to redirect_to('http://www.example.com/auth/saml/callback')
follow_redirect!
expect(response).to redirect_to(%r{/app/login\?email=.+&sso_auth_token=.+$})
end
end
end
end

View File

@@ -0,0 +1,36 @@
require 'rails_helper'
RSpec.describe 'Enterprise Passwords Controller', type: :request do
let!(:account) { create(:account) }
describe 'POST /auth/password' do
context 'with SAML user email' do
let!(:saml_user) { create(:user, email: 'saml@example.com', provider: 'saml', account: account) }
it 'prevents password reset and returns forbidden with custom error message' do
params = { email: saml_user.email, redirect_url: 'http://test.host' }
post user_password_path, params: params, as: :json
expect(response).to have_http_status(:forbidden)
json_response = JSON.parse(response.body)
expect(json_response['success']).to be(false)
expect(json_response['errors']).to include(I18n.t('messages.reset_password_saml_user'))
end
end
context 'with non-SAML user email' do
let!(:regular_user) { create(:user, email: 'regular@example.com', provider: 'email', account: account) }
it 'allows password reset for non-SAML users' do
params = { email: regular_user.email, redirect_url: 'http://test.host' }
post user_password_path, params: params, as: :json
expect(response).to have_http_status(:ok)
json_response = JSON.parse(response.body)
expect(json_response['message']).to be_present
end
end
end
end

View File

@@ -5,32 +5,75 @@ RSpec.describe 'Enterprise Audit API', type: :request do
let!(:user) { create(:user, password: 'Password1!', account: account) }
describe 'POST /sign_in' do
it 'creates a sign_in audit event wwith valid credentials' do
params = { email: user.email, password: 'Password1!' }
context 'with SAML user attempting password login' do
let(:saml_settings) { create(:account_saml_settings, account: account) }
let(:saml_user) { create(:user, email: 'saml@example.com', provider: 'saml', account: account) }
expect do
post new_user_session_url,
params: params,
as: :json
end.to change(Enterprise::AuditLog, :count).by(1)
before do
saml_settings
saml_user
end
expect(response).to have_http_status(:success)
expect(response.body).to include(user.email)
it 'prevents login and returns SAML authentication error' do
params = { email: saml_user.email, password: 'Password1!' }
# Check if the sign_in event is created
user.reload
expect(user.audits.last.action).to eq('sign_in')
expect(user.audits.last.associated_id).to eq(account.id)
expect(user.audits.last.associated_type).to eq('Account')
post new_user_session_url, params: params, as: :json
expect(response).to have_http_status(:unauthorized)
json_response = JSON.parse(response.body)
expect(json_response['success']).to be(false)
expect(json_response['errors']).to include(I18n.t('messages.login_saml_user'))
end
it 'allows login with valid SSO token' do
valid_token = saml_user.generate_sso_auth_token
params = { email: saml_user.email, sso_auth_token: valid_token, password: 'Password1!' }
expect do
post new_user_session_url, params: params, as: :json
end.to change(Enterprise::AuditLog, :count).by(1)
expect(response).to have_http_status(:success)
expect(response.body).to include(saml_user.email)
end
end
it 'will not create a sign_in audit event with invalid credentials' do
params = { email: user.email, password: 'invalid' }
expect do
post new_user_session_url,
params: params,
as: :json
end.not_to change(Enterprise::AuditLog, :count)
context 'with regular user credentials' do
it 'creates a sign_in audit event wwith valid credentials' do
params = { email: user.email, password: 'Password1!' }
expect do
post new_user_session_url,
params: params,
as: :json
end.to change(Enterprise::AuditLog, :count).by(1)
expect(response).to have_http_status(:success)
expect(response.body).to include(user.email)
# Check if the sign_in event is created
user.reload
expect(user.audits.last.action).to eq('sign_in')
expect(user.audits.last.associated_id).to eq(account.id)
expect(user.audits.last.associated_type).to eq('Account')
end
it 'will not create a sign_in audit event with invalid credentials' do
params = { email: user.email, password: 'invalid' }
expect do
post new_user_session_url,
params: params,
as: :json
end.not_to change(Enterprise::AuditLog, :count)
end
end
context 'with blank email' do
it 'skips SAML check and processes normally' do
params = { email: '', password: 'Password1!' }
post new_user_session_url, params: params, as: :json
expect(response).to have_http_status(:unauthorized)
end
end
end