feat: LDAP-Test zeigt User-Tabelle mit Univention-UCS-Support
Nach erfolgreichem Verbindungstest werden bis zu 50 Benutzer (UID, Name, E-Mail) in einer scrollbaren Tabelle angezeigt. Unterstützt mailPrimaryAddress (Univention UCS) als Fallback für mail sowie inetOrgPerson objectClass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,14 @@ type Config struct {
|
|||||||
TLSSkipVerify bool
|
TLSSkipVerify bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LDAPUser is a preview entry returned by TestConnection.
|
||||||
|
type LDAPUser struct {
|
||||||
|
DN string `json:"dn"`
|
||||||
|
UID string `json:"uid"`
|
||||||
|
DisplayName string `json:"display_name"`
|
||||||
|
Mail string `json:"mail"`
|
||||||
|
}
|
||||||
|
|
||||||
// TestResult is the structured output of TestConnection.
|
// TestResult is the structured output of TestConnection.
|
||||||
type TestResult struct {
|
type TestResult struct {
|
||||||
OK bool `json:"ok"`
|
OK bool `json:"ok"`
|
||||||
@@ -30,6 +38,7 @@ type TestResult struct {
|
|||||||
LatencyMS int64 `json:"latency_ms"`
|
LatencyMS int64 `json:"latency_ms"`
|
||||||
ServerInfo string `json:"server_info"`
|
ServerInfo string `json:"server_info"`
|
||||||
UsersFound int `json:"users_found"`
|
UsersFound int `json:"users_found"`
|
||||||
|
Users []LDAPUser `json:"users"`
|
||||||
ErrorDetail string `json:"error_detail"`
|
ErrorDetail string `json:"error_detail"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,8 +71,8 @@ func TestConnection(cfg Config) TestResult {
|
|||||||
// Query RootDSE for server info
|
// Query RootDSE for server info
|
||||||
serverInfo := queryRootDSE(conn)
|
serverInfo := queryRootDSE(conn)
|
||||||
|
|
||||||
// Count user objects (capped at 500 to avoid large result sets)
|
// Fetch user objects (capped at 500 for count, 50 for preview list)
|
||||||
usersFound := countUsers(conn, cfg.BaseDN)
|
usersFound, users := listUsers(conn, cfg.BaseDN)
|
||||||
|
|
||||||
return TestResult{
|
return TestResult{
|
||||||
OK: true,
|
OK: true,
|
||||||
@@ -71,6 +80,7 @@ func TestConnection(cfg Config) TestResult {
|
|||||||
LatencyMS: time.Since(start).Milliseconds(),
|
LatencyMS: time.Since(start).Milliseconds(),
|
||||||
ServerInfo: serverInfo,
|
ServerInfo: serverInfo,
|
||||||
UsersFound: usersFound,
|
UsersFound: usersFound,
|
||||||
|
Users: users,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,20 +186,44 @@ func queryRootDSE(conn *ldapv3.Conn) string {
|
|||||||
return strings.Join(parts, " ")
|
return strings.Join(parts, " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
// countUsers searches for person/user objects and returns the count (max 500).
|
// listUsers searches for person/user objects and returns the total count (max 500)
|
||||||
func countUsers(conn *ldapv3.Conn, baseDN string) int {
|
// plus a preview slice of up to 50 entries. Supports both AD (mail) and
|
||||||
|
// Univention UCS (mailPrimaryAddress) mail attributes.
|
||||||
|
func listUsers(conn *ldapv3.Conn, baseDN string) (int, []LDAPUser) {
|
||||||
req := ldapv3.NewSearchRequest(
|
req := ldapv3.NewSearchRequest(
|
||||||
baseDN,
|
baseDN,
|
||||||
ldapv3.ScopeWholeSubtree,
|
ldapv3.ScopeWholeSubtree,
|
||||||
ldapv3.NeverDerefAliases,
|
ldapv3.NeverDerefAliases,
|
||||||
500, 30, false,
|
500, 30, false,
|
||||||
"(|(objectClass=person)(objectClass=user))",
|
"(|(objectClass=person)(objectClass=user)(objectClass=inetOrgPerson))",
|
||||||
[]string{"dn"},
|
[]string{"dn", "uid", "cn", "displayName", "mail", "mailPrimaryAddress"},
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
res, err := conn.Search(req)
|
res, err := conn.Search(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0
|
return 0, nil
|
||||||
}
|
}
|
||||||
return len(res.Entries)
|
|
||||||
|
preview := make([]LDAPUser, 0, min(50, len(res.Entries)))
|
||||||
|
for i, e := range res.Entries {
|
||||||
|
if i >= 50 {
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
mail := e.GetAttributeValue("mail")
|
||||||
|
if mail == "" {
|
||||||
|
mail = e.GetAttributeValue("mailPrimaryAddress")
|
||||||
|
}
|
||||||
|
displayName := e.GetAttributeValue("displayName")
|
||||||
|
if displayName == "" {
|
||||||
|
displayName = e.GetAttributeValue("cn")
|
||||||
|
}
|
||||||
|
preview = append(preview, LDAPUser{
|
||||||
|
DN: e.DN,
|
||||||
|
UID: e.GetAttributeValue("uid"),
|
||||||
|
DisplayName: displayName,
|
||||||
|
Mail: mail,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return len(res.Entries), preview
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
+62
-8
@@ -2195,7 +2195,7 @@ export default function AdminPage() {
|
|||||||
{/* Test result */}
|
{/* Test result */}
|
||||||
{ldapTestResult && (
|
{ldapTestResult && (
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent className="pt-4 space-y-2">
|
<CardContent className="pt-4 space-y-3">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Badge variant={ldapTestResult.ok ? "default" : "destructive"}>
|
<Badge variant={ldapTestResult.ok ? "default" : "destructive"}>
|
||||||
{ldapTestResult.ok ? "Verbunden" : "Fehler"}
|
{ldapTestResult.ok ? "Verbunden" : "Fehler"}
|
||||||
@@ -2208,12 +2208,39 @@ export default function AdminPage() {
|
|||||||
{ldapTestResult.server_info && (
|
{ldapTestResult.server_info && (
|
||||||
<p className="text-xs text-muted-foreground font-mono">{ldapTestResult.server_info}</p>
|
<p className="text-xs text-muted-foreground font-mono">{ldapTestResult.server_info}</p>
|
||||||
)}
|
)}
|
||||||
{ldapTestResult.users_found > 0 && (
|
|
||||||
<p className="text-sm">{ldapTestResult.users_found} Benutzer gefunden</p>
|
|
||||||
)}
|
|
||||||
{ldapTestResult.error_detail && (
|
{ldapTestResult.error_detail && (
|
||||||
<p className="text-xs text-destructive font-mono">{ldapTestResult.error_detail}</p>
|
<p className="text-xs text-destructive font-mono">{ldapTestResult.error_detail}</p>
|
||||||
)}
|
)}
|
||||||
|
{ldapTestResult.ok && ldapTestResult.users_found > 0 && (
|
||||||
|
<div className="space-y-1">
|
||||||
|
<p className="text-sm font-medium">
|
||||||
|
{ldapTestResult.users_found} Benutzer gefunden
|
||||||
|
{ldapTestResult.users?.length < ldapTestResult.users_found && (
|
||||||
|
<span className="text-muted-foreground font-normal"> (Vorschau: {ldapTestResult.users?.length})</span>
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<div className="rounded border overflow-auto max-h-64">
|
||||||
|
<table className="w-full text-xs">
|
||||||
|
<thead className="bg-muted sticky top-0">
|
||||||
|
<tr>
|
||||||
|
<th className="text-left px-2 py-1 font-medium">UID</th>
|
||||||
|
<th className="text-left px-2 py-1 font-medium">Name</th>
|
||||||
|
<th className="text-left px-2 py-1 font-medium">E-Mail</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{ldapTestResult.users?.map((u, i) => (
|
||||||
|
<tr key={i} className="border-t">
|
||||||
|
<td className="px-2 py-1 font-mono">{u.uid || "–"}</td>
|
||||||
|
<td className="px-2 py-1">{u.display_name || "–"}</td>
|
||||||
|
<td className="px-2 py-1 text-muted-foreground">{u.mail || "–"}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
@@ -2458,7 +2485,7 @@ export default function AdminPage() {
|
|||||||
{/* Test result */}
|
{/* Test result */}
|
||||||
{tenantLdapTestResult && (
|
{tenantLdapTestResult && (
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent className="pt-4 space-y-2">
|
<CardContent className="pt-4 space-y-3">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Badge variant={tenantLdapTestResult.ok ? "default" : "destructive"}>
|
<Badge variant={tenantLdapTestResult.ok ? "default" : "destructive"}>
|
||||||
{tenantLdapTestResult.ok ? "Verbunden" : "Fehler"}
|
{tenantLdapTestResult.ok ? "Verbunden" : "Fehler"}
|
||||||
@@ -2471,12 +2498,39 @@ export default function AdminPage() {
|
|||||||
{tenantLdapTestResult.server_info && (
|
{tenantLdapTestResult.server_info && (
|
||||||
<p className="text-xs text-muted-foreground font-mono">{tenantLdapTestResult.server_info}</p>
|
<p className="text-xs text-muted-foreground font-mono">{tenantLdapTestResult.server_info}</p>
|
||||||
)}
|
)}
|
||||||
{tenantLdapTestResult.users_found > 0 && (
|
|
||||||
<p className="text-sm">{tenantLdapTestResult.users_found} Benutzer gefunden</p>
|
|
||||||
)}
|
|
||||||
{tenantLdapTestResult.error_detail && (
|
{tenantLdapTestResult.error_detail && (
|
||||||
<p className="text-xs text-destructive font-mono">{tenantLdapTestResult.error_detail}</p>
|
<p className="text-xs text-destructive font-mono">{tenantLdapTestResult.error_detail}</p>
|
||||||
)}
|
)}
|
||||||
|
{tenantLdapTestResult.ok && tenantLdapTestResult.users_found > 0 && (
|
||||||
|
<div className="space-y-1">
|
||||||
|
<p className="text-sm font-medium">
|
||||||
|
{tenantLdapTestResult.users_found} Benutzer gefunden
|
||||||
|
{tenantLdapTestResult.users?.length < tenantLdapTestResult.users_found && (
|
||||||
|
<span className="text-muted-foreground font-normal"> (Vorschau: {tenantLdapTestResult.users?.length})</span>
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<div className="rounded border overflow-auto max-h-64">
|
||||||
|
<table className="w-full text-xs">
|
||||||
|
<thead className="bg-muted sticky top-0">
|
||||||
|
<tr>
|
||||||
|
<th className="text-left px-2 py-1 font-medium">UID</th>
|
||||||
|
<th className="text-left px-2 py-1 font-medium">Name</th>
|
||||||
|
<th className="text-left px-2 py-1 font-medium">E-Mail</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{tenantLdapTestResult.users?.map((u, i) => (
|
||||||
|
<tr key={i} className="border-t">
|
||||||
|
<td className="px-2 py-1 font-mono">{u.uid || "–"}</td>
|
||||||
|
<td className="px-2 py-1">{u.display_name || "–"}</td>
|
||||||
|
<td className="px-2 py-1 text-muted-foreground">{u.mail || "–"}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -642,12 +642,20 @@ export interface LDAPConfig {
|
|||||||
updated_by?: string;
|
updated_by?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface LDAPTestUser {
|
||||||
|
dn: string;
|
||||||
|
uid: string;
|
||||||
|
display_name: string;
|
||||||
|
mail: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface LDAPTestResult {
|
export interface LDAPTestResult {
|
||||||
ok: boolean;
|
ok: boolean;
|
||||||
message: string;
|
message: string;
|
||||||
latency_ms: number;
|
latency_ms: number;
|
||||||
server_info: string;
|
server_info: string;
|
||||||
users_found: number;
|
users_found: number;
|
||||||
|
users: LDAPTestUser[];
|
||||||
error_detail: string;
|
error_detail: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user