Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -824,7 +824,7 @@ unleash-port-forward: check-kubectl ## Port-forward Unleash (localhost:4242)
@echo "$(COLOR_BOLD)🔌 Port forwarding Unleash$(COLOR_RESET)"
@echo ""
@echo " Unleash UI: http://localhost:4242"
@echo " Login: admin / unleash4all"
@echo " Login: admin / $$(kubectl get secret unleash-credentials -n $${NAMESPACE:-ambient-code} -o jsonpath='{.data.default-admin-password}' 2>/dev/null | base64 -d || echo '(unknown — check unleash-credentials secret)')"
@echo ""
@echo "$(COLOR_YELLOW)Press Ctrl+C to stop$(COLOR_RESET)"
@kubectl port-forward svc/unleash 4242:4242 -n $${NAMESPACE:-ambient-code}
Expand Down
3 changes: 3 additions & 0 deletions components/backend/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ require (
cloud.google.com/go/auth v0.7.2 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.3 // indirect
cloud.google.com/go/compute/metadata v0.5.0 // indirect
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
github.com/Masterminds/semver/v3 v3.4.0 // indirect
github.com/bytedance/sonic v1.13.3 // indirect
github.com/bytedance/sonic/loader v0.2.4 // indirect
Expand All @@ -34,6 +35,8 @@ require (
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
github.com/go-ldap/ldap/v3 v3.4.12 // indirect
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Promote github.com/go-ldap/ldap/v3 to a direct requirement if the new LDAP client imports it.

If any backend file imports this module directly, keeping Line 39 as // indirect just creates churn the next time go mod tidy runs. If it is only transitive, ignore this.

Verify whether the dependency is directly imported
#!/bin/bash
set -euo pipefail

rg -n --type=go 'github\.com/go-ldap/ldap/v3' components/backend
rg -n 'github\.com/go-ldap/ldap/v3' components/backend/go.mod
Minimal fix if the import is direct
-	github.com/go-ldap/ldap/v3 v3.4.12 // indirect
+	github.com/go-ldap/ldap/v3 v3.4.12
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
github.com/go-ldap/ldap/v3 v3.4.12 // indirect
github.com/go-ldap/ldap/v3 v3.4.12
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/backend/go.mod` at line 39, Run the verification commands shown to
check whether any Go source in the backend imports github.com/go-ldap/ldap/v3;
if it does, remove the // indirect marker and promote github.com/go-ldap/ldap/v3
to a direct require in go.mod by adding a require entry for that module (same
version currently listed) and then run go mod tidy to update the module graph;
if no direct imports exist, leave the dependency as indirect. Ensure you
reference the module name "github.com/go-ldap/ldap/v3" and update the go.mod
require block accordingly, then run Go tooling (go mod tidy) to validate and
commit the change.

github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
Expand Down
6 changes: 6 additions & 0 deletions components/backend/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ cloud.google.com/go/auth/oauth2adapt v0.2.3 h1:MlxF+Pd3OmSudg/b1yZ5lJwoXCEaeedAg
cloud.google.com/go/auth/oauth2adapt v0.2.3/go.mod h1:tMQXOfZzFuNuUxOypHlQEXgdfX5cuhwU+ffUuXRJE8I=
cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY=
cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
Expand Down Expand Up @@ -51,6 +53,10 @@ github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZ
github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=
github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE=
github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-ldap/ldap/v3 v3.4.12 h1:1b81mv7MagXZ7+1r7cLTWmyuTqVqdwbtJSjC0DAp9s4=
github.com/go-ldap/ldap/v3 v3.4.12/go.mod h1:+SPAGcTtOfmGsCb3h1RFiq4xpp4N636G75OEace8lNo=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
Expand Down
110 changes: 110 additions & 0 deletions components/backend/handlers/ldap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package handlers

import (
"fmt"
"log"
"net/http"

"ambient-code-backend/ldap"

"github.com/gin-gonic/gin"
)

// LDAPClient is the shared LDAP client instance, initialized in main.go when LDAP_URL is set.
// Access is gated in the frontend by the "ldap.autocomplete.enabled" workspace feature flag.
var LDAPClient *ldap.Client

// SearchLDAPUsers handles GET /api/ldap/users?q={query}
func SearchLDAPUsers(c *gin.Context) {
reqK8s, _ := GetK8sClientsForRequest(c)
if reqK8s == nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid or missing token"})
c.Abort()
return
}
Comment on lines +18 to +24
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider logging the discarded error from GetK8sClientsForRequest.

The error returned by GetK8sClientsForRequest is silently discarded. While returning 401 is correct, logging the error would aid debugging authentication issues.

♻️ Proposed fix to log auth errors
 func SearchLDAPUsers(c *gin.Context) {
-	reqK8s, _ := GetK8sClientsForRequest(c)
+	reqK8s, err := GetK8sClientsForRequest(c)
 	if reqK8s == nil {
+		if err != nil {
+			log.Printf("LDAP auth error: %v", err)
+		}
 		c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid or missing token"})
 		c.Abort()
 		return
 	}

Apply the same pattern to SearchLDAPGroups and GetLDAPUser.

Also applies to: 48-54, 78-84

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/backend/handlers/ldap.go` around lines 18 - 24, The error returned
from GetK8sClientsForRequest is being discarded in SearchLDAPUsers (and likewise
in SearchLDAPGroups and GetLDAPUser); modify each function to capture the
returned error and log it before returning the 401 response — e.g., call your
existing logger (or c.Error()/log.Printf) with a clear message like
"GetK8sClientsForRequest failed" plus the error value when reqK8s is nil; ensure
you do this in the SearchLDAPUsers, SearchLDAPGroups, and GetLDAPUser functions
where GetK8sClientsForRequest is called.


query := c.Query("q")
if len(query) < ldap.MinQueryLength {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("query must be at least %d characters", ldap.MinQueryLength)})
return
}

if LDAPClient == nil {
c.JSON(http.StatusOK, gin.H{"users": []ldap.LDAPUser{}})
return
}

users, err := LDAPClient.SearchUsers(query)
if err != nil {
log.Printf("LDAP user search error for query %q: %v", query, err)
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "LDAP search unavailable"})
return
}

c.JSON(http.StatusOK, gin.H{"users": users})
Comment on lines +37 to +44
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Inconsistent HTTP status codes for LDAP errors.

SearchUsers/SearchGroups return 503 Service Unavailable on LDAP errors, while GetLDAPUser returns 500 Internal Server Error. Consider aligning these for consistency—503 is more appropriate since LDAP is an external dependency.

♻️ Proposed fix for consistency
 	user, err := LDAPClient.GetUser(uid)
 	if err != nil {
 		log.Printf("LDAP user get error for uid %q: %v", uid, err)
-		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to look up user"})
+		c.JSON(http.StatusServiceUnavailable, gin.H{"error": "LDAP lookup unavailable"})
 		return
 	}

Also applies to: 67-74, 97-102

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/backend/handlers/ldap.go` around lines 37 - 44, Change all LDAP
error responses to use HTTP 503 Service Unavailable for consistency with
external dependency failures: update the handlers that call
LDAPClient.SearchUsers and LDAPClient.SearchGroups to return
c.JSON(http.StatusServiceUnavailable, ...) on err (instead of 500), and modify
the GetLDAPUser handler to return http.StatusServiceUnavailable when LDAP lookup
fails; ensure the error log messages remain unchanged and only the HTTP status
code is adjusted.

}

// SearchLDAPGroups handles GET /api/ldap/groups?q={query}
func SearchLDAPGroups(c *gin.Context) {
reqK8s, _ := GetK8sClientsForRequest(c)
if reqK8s == nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid or missing token"})
c.Abort()
return
}

query := c.Query("q")
if len(query) < ldap.MinQueryLength {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("query must be at least %d characters", ldap.MinQueryLength)})
return
}

if LDAPClient == nil {
c.JSON(http.StatusOK, gin.H{"groups": []ldap.LDAPGroup{}})
return
}

groups, err := LDAPClient.SearchGroups(query)
if err != nil {
log.Printf("LDAP group search error for query %q: %v", query, err)
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "LDAP search unavailable"})
return
}

c.JSON(http.StatusOK, gin.H{"groups": groups})
}

// GetLDAPUser handles GET /api/ldap/users/:uid
func GetLDAPUser(c *gin.Context) {
reqK8s, _ := GetK8sClientsForRequest(c)
if reqK8s == nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid or missing token"})
c.Abort()
return
}

uid := c.Param("uid")
if uid == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "uid is required"})
return
}

if LDAPClient == nil {
c.JSON(http.StatusNotFound, gin.H{"error": "LDAP not configured"})
return
}

user, err := LDAPClient.GetUser(uid)
if err != nil {
log.Printf("LDAP user get error for uid %q: %v", uid, err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to look up user"})
return
}

if user == nil {
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
return
}

c.JSON(http.StatusOK, user)
}
Loading
Loading