fix: require Wayne user ID for role binding

This commit is contained in:
mac
2026-07-17 15:47:32 +08:00
parent d9d93fcd20
commit a10d4a5921
5 changed files with 85 additions and 60 deletions
+48 -30
View File
@@ -64,11 +64,11 @@ func (h *WayneRoleBindingHandler) ListGroups(c *gin.Context) {
}
func (h *WayneRoleBindingHandler) GetCurrentUserRoles(c *gin.Context) {
username, ok := currentTokenEmail(c)
userID, ok := parseUintPathParam(c, "userid")
if !ok {
return
}
h.handleQuery(c, "user_roles", 0, username)
h.handleQuery(c, "user_roles", userID, "")
}
func (h *WayneRoleBindingHandler) NamespaceOperatorPermissions(c *gin.Context) {
@@ -111,30 +111,34 @@ func (h *WayneRoleBindingHandler) handle(c *gin.Context, scope string, method st
if !ok {
return
}
targetUserID, ok := roleBindingTargetUserID(c, req)
if !ok {
return
}
if method == http.MethodPut && len(req.GroupIDs) == 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "groupIds is required"})
return
}
result, err := h.call(c, scope, method, resourceID, operatorEmail, req)
result, err := h.call(c, scope, method, resourceID, targetUserID, operatorEmail, req)
if err != nil {
h.writeAudit(c, claims.UserID, operatorEmail, scope, resourceID, operatorEmail, "deny", req.RequestID, err.Error())
h.writeAudit(c, claims.UserID, operatorEmail, scope, resourceID, targetUserID, "deny", req.RequestID, err.Error())
writeWayneRoleBindingError(c, result, err)
return
}
h.writeAudit(c, claims.UserID, operatorEmail, scope, resourceID, operatorEmail, "allow", req.RequestID, "")
h.writeAudit(c, claims.UserID, operatorEmail, scope, resourceID, targetUserID, "allow", req.RequestID, "")
writeWayneRoleBindingResult(c, result)
}
func (h *WayneRoleBindingHandler) handleQuery(c *gin.Context, resourceType string, resourceID uint64, username string) {
func (h *WayneRoleBindingHandler) handleQuery(c *gin.Context, resourceType string, resourceID uint64, _ string) {
var result *service.WayneRoleBindingResult
var err error
switch resourceType {
case "namespaces":
result, err = h.wayne.ListNamespaces(c.Request.Context())
case "user_roles":
result, err = h.wayne.GetUserRoles(c.Request.Context(), username)
result, err = h.wayne.GetUserRoles(c.Request.Context(), resourceID)
default:
c.JSON(http.StatusBadRequest, gin.H{"error": "unsupported query resource"})
return
@@ -177,17 +181,17 @@ func (h *WayneRoleBindingHandler) handleOperatorPermissions(c *gin.Context, scop
writeWayneRoleBindingResult(c, result)
}
func (h *WayneRoleBindingHandler) call(c *gin.Context, scope, method string, resourceID uint64, operatorEmail string, req service.WayneRoleBindingRequest) (*service.WayneRoleBindingResult, error) {
func (h *WayneRoleBindingHandler) call(c *gin.Context, scope, method string, resourceID uint64, targetUserID uint64, operatorEmail string, req service.WayneRoleBindingRequest) (*service.WayneRoleBindingResult, error) {
if scope == "namespace" {
if method == http.MethodPut {
return h.wayne.BindNamespace(c.Request.Context(), resourceID, operatorEmail, req)
return h.wayne.BindNamespace(c.Request.Context(), resourceID, targetUserID, operatorEmail, req)
}
return h.wayne.UnbindNamespace(c.Request.Context(), resourceID, operatorEmail, req)
return h.wayne.UnbindNamespace(c.Request.Context(), resourceID, targetUserID, operatorEmail, req)
}
if method == http.MethodPut {
return h.wayne.BindApp(c.Request.Context(), resourceID, operatorEmail, req)
return h.wayne.BindApp(c.Request.Context(), resourceID, targetUserID, operatorEmail, req)
}
return h.wayne.UnbindApp(c.Request.Context(), resourceID, operatorEmail, req)
return h.wayne.UnbindApp(c.Request.Context(), resourceID, targetUserID, operatorEmail, req)
}
func parseRoleBindingRequest(c *gin.Context) (service.WayneRoleBindingRequest, bool) {
@@ -208,6 +212,26 @@ func parseRoleBindingRequest(c *gin.Context) (service.WayneRoleBindingRequest, b
return req, true
}
func roleBindingTargetUserID(c *gin.Context, req service.WayneRoleBindingRequest) (uint64, bool) {
if req.UserID != nil && *req.UserID != 0 {
return *req.UserID, true
}
for _, key := range []string{"userId", "user_id", "userid"} {
raw := strings.TrimSpace(c.Query(key))
if raw == "" {
continue
}
value, err := strconv.ParseUint(raw, 10, 64)
if err != nil || value == 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid userId"})
return 0, false
}
return value, true
}
c.JSON(http.StatusBadRequest, gin.H{"error": "userId is required"})
return 0, false
}
func parseUintPathParam(c *gin.Context, name string) (uint64, bool) {
raw := strings.TrimSpace(c.Param(name))
value, err := strconv.ParseUint(raw, 10, 64)
@@ -256,21 +280,7 @@ func writeWayneRoleBindingError(c *gin.Context, result *service.WayneRoleBinding
}
}
func currentTokenEmail(c *gin.Context) (string, bool) {
claims, ok := CurrentClaims(c)
if !ok {
c.JSON(http.StatusUnauthorized, gin.H{"error": "missing current user"})
return "", false
}
email := strings.TrimSpace(claims.Email)
if email == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "email is missing in token"})
return "", false
}
return email, true
}
func (h *WayneRoleBindingHandler) writeAudit(c *gin.Context, userID uint64, operatorEmail, scope string, resourceID uint64, targetUsername string, decision, requestID, reason string) {
func (h *WayneRoleBindingHandler) writeAudit(c *gin.Context, userID uint64, operatorEmail, scope string, resourceID uint64, targetUserID uint64, decision, requestID, reason string) {
h.audit.Write(service.AuditEntry{
RequestID: requestID,
ActorUserID: userID,
@@ -283,9 +293,9 @@ func (h *WayneRoleBindingHandler) writeAudit(c *gin.Context, userID uint64, oper
ScopeType: scope,
ScopeID: resourceID,
Decision: decision,
Reason: reason,
Reason: truncateAuditReason(reason),
Metadata: map[string]any{
"targetUsername": targetUsername,
"targetUserId": targetUserID,
},
})
}
@@ -310,6 +320,14 @@ func (h *WayneRoleBindingHandler) writeQueryAudit(c *gin.Context, resourceType s
ResourceType: "wayne_" + resourceType,
ResourceID: strconv.FormatUint(resourceID, 10),
Decision: decision,
Reason: reason,
Reason: truncateAuditReason(reason),
})
}
func truncateAuditReason(reason string) string {
const maxReasonBytes = 512
if len(reason) <= maxReasonBytes {
return reason
}
return reason[:maxReasonBytes]
}