ldap test connection button

This commit is contained in:
Chris
2025-12-10 11:08:14 -08:00
parent 1c02a79a26
commit cea3a4920c
7 changed files with 123 additions and 4 deletions

View File

@@ -60,6 +60,24 @@ module Accounts
end
end
def test_connection
ldap_configuration = LDAPConfiguration.new(ldap_configuration_params)
result = LDAP::Authenticator.new(ldap_configuration).test_connection
if result.success?
render turbo_stream: turbo_stream.replace(
"ldap_test_connection_result",
partial: "accounts/sso_providers/ldap/connection_success"
)
else
render turbo_stream: turbo_stream.replace(
"ldap_test_connection_result",
partial: "accounts/sso_providers/ldap/connection_failed",
locals: { error_message: result.error_message }
)
end
end
private
def sso_provider_params

View File

@@ -0,0 +1,44 @@
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["button", "result"]
async test(event) {
debugger
event.preventDefault()
const form = this.element.closest("form")
const formData = new FormData(form)
// Clear previous result
this.resultTarget.innerHTML = ""
// Update button to show loading state
const button = this.buttonTarget
const originalContent = button.innerHTML
button.disabled = true
button.innerHTML = '<span class="loading loading-spinner loading-xs"></span> Testing...'
try {
const response = await fetch("/accounts/sso_provider/test_connection", {
method: "POST",
headers: {
"X-CSRF-Token": document.querySelector('meta[name="csrf-token"]').content,
"Accept": "text/vnd.turbo-stream.html"
},
body: formData
})
if (response.ok) {
const html = await response.text()
Turbo.renderStreamMessage(html)
}
} catch (error) {
console.error("LDAP test connection failed:", error)
} finally {
// Always re-enable the button so it can be clicked again
button.disabled = false
button.innerHTML = originalContent
}
}
}

View File

@@ -21,8 +21,33 @@ module LDAP
# Public API
# ----------
# call(username:, password:) -> Result
# test_connection -> Result (tests bind credentials only)
# call(username:, password:) -> Result (full authentication)
#
def test_connection
# Validate required configuration
if config.host.blank?
return Result.new(success?: false, error_message: "Host is required")
end
if config.bind_dn.blank? && !config.allow_anonymous_reads?
return Result.new(success?: false, error_message: "Bind DN and password are required when anonymous reads are disabled")
end
reader_ldap = build_reader_connection
if reader_ldap.bind
Result.new(success?: true, error_message: nil)
else
msg = "LDAP bind failed: #{reader_ldap.get_operation_result.message}"
@logger.warn msg
Result.new(success?: false, error_message: msg)
end
rescue => e
@logger.error "LDAP test connection: unexpected error - #{e.class}: #{e.message}"
Result.new(success?: false, error_message: e.message)
end
def call(username:, password:, fetch_groups:)
# 1) Bind as reader (service account or anonymous)
reader_ldap = build_reader_connection
@@ -54,8 +79,8 @@ module LDAP
end
# 4) Successful LDAP auth → map attributes, fetch groups
email = resolve_email(entry, username)
name = resolve_name(entry, username)
email = resolve_email(entry, username)
name = resolve_name(entry, username)
if fetch_groups
groups = fetch_group_membership(entry)
else
@@ -103,6 +128,7 @@ module LDAP
# No way to bind safely
# Let caller see failure via bind result
logger.info "LDAP: no reader credentials and anonymous reads disabled"
raise "LDAP: no reader credentials and anonymous reads disabled"
end
Net::LDAP.new(options)

View File

@@ -0,0 +1,11 @@
<%= turbo_frame_tag "ldap_test_connection_result", class: "block mt-3", data: { ldap_test_connection_target: "result" } do %>
<div class="flex flex-col gap-1">
<div class="flex items-center gap-2 text-error">
<iconify-icon icon="lucide:x-circle" height="16"></iconify-icon>
<span>Connection failed</span>
</div>
<% if local_assigns[:error_message].present? %>
<span class="text-sm text-base-content/70"><%= error_message %></span>
<% end %>
</div>
<% end %>

View File

@@ -0,0 +1,6 @@
<%= turbo_frame_tag "ldap_test_connection_result", class: "block mt-3", data: { ldap_test_connection_target: "result" } do %>
<div class="flex items-center gap-2 text-success">
<iconify-icon icon="lucide:check-circle" height="16"></iconify-icon>
<span>Connection successful!</span>
</div>
<% end %>

View File

@@ -107,4 +107,16 @@
</div>
</div>
</details>
<div class="mt-6 mb-4" data-controller="ldap-test-connection">
<button type="button"
class="btn btn-outline btn-sm"
data-action="click->ldap-test-connection#test"
data-ldap-test-connection-target="button">
<iconify-icon icon="lucide:plug" height="16"></iconify-icon>
Test Connection
</button>
<%= turbo_frame_tag "ldap_test_connection_result", class: "block mt-3", data: { ldap_test_connection_target: "result" } do %>
<% end %>
</div>
<% end %>

View File

@@ -16,7 +16,9 @@ Rails.application.routes.draw do
resources :accounts, only: [ :create ] do
collection do
resources :account_users, only: %i[create index destroy], module: :accounts
resource :sso_provider, only: %i[show new create edit update destroy], module: :accounts
resource :sso_provider, only: %i[show new create edit update destroy], module: :accounts do
post :test_connection
end
resources :teams, module: :accounts do
resources :team_memberships, only: %i[create destroy], module: :teams
resources :team_resources, only: %i[create destroy], module: :teams