yao/openapi/oauth/acl/enforce.go
Max 0bd6bdcda3 Refactor OAuth guard and ACL error handling for improved response structure
- Updated the OAuth guard to utilize a standardized response format for error handling, enhancing consistency across error responses.
- Integrated detailed error information for insufficient scope and permission denied scenarios, providing clearer insights into access issues.
- Modified the ACL enforcement logic to return structured error details, allowing for better handling of permission-related errors.
- Enhanced the ErrorResponse struct to include optional fields for reason, required scopes, and missing scopes, following OAuth 2.0 extensibility guidelines.
2025-10-20 16:23:12 +08:00

83 lines
2.5 KiB
Go

package acl
import (
"strings"
"github.com/gin-gonic/gin"
"github.com/yaoapp/yao/openapi/oauth/authorized"
"github.com/yaoapp/yao/openapi/oauth/types"
)
// Enforce checks if a user has access to a resource based on the request context
func (acl *ACL) Enforce(c *gin.Context) (bool, error) {
// If ACL is not enabled, allow access
if !acl.Enabled() {
return true, nil
}
// If scope manager not loaded, deny access
if acl.Scope == nil {
return false, nil
}
// Get authorized info from context (set by OAuth guard middleware)
authInfo := authorized.GetInfo(c)
// Resolve all scopes (client + user + team) from authorized info
// Note: This should include scope expansion from roles, aliases, etc.
scopes := getScopes(authInfo)
// Build access request (focused on scope-based access control)
request := &AccessRequest{
Method: c.Request.Method,
Path: c.Request.URL.Path,
Scopes: scopes,
}
// Check scopes
decision := acl.Scope.Check(request)
if !decision.Allowed {
// Return error with details, let the caller handle the response
err := &Error{
Type: ErrorTypePermissionDenied,
Message: decision.Reason,
Details: map[string]interface{}{
"required_scopes": decision.RequiredScopes,
"missing_scopes": decision.MissingScopes,
},
}
return false, err
}
return true, nil
}
// getScopes resolves all scopes from authorized info
// This function is responsible for the complete scope resolution process:
// 1. Get base scopes from token (authInfo.Scope)
// 2. Get user role scopes from database (if authInfo.UserID exists)
// 3. Get team role scopes from database (if authInfo.TeamID exists)
// 4. Merge all scopes and return the complete list
//
// Scope resolution logic:
// - Pure API call (no user_id): Returns client scopes from token
// - User call: Returns merged scopes (client + user roles + team roles)
//
// This keeps the ACL layer focused on scope-based access control,
// while relying on the authorized package for context extraction.
func getScopes(authInfo *types.AuthorizedInfo) []string {
// TODO: Implement scope resolution
// 1. Parse base scopes from authInfo.Scope (space-separated)
// 2. Query user roles and convert to scopes (if authInfo.UserID exists)
// 3. Query team roles and convert to scopes (if authInfo.TeamID exists)
// 4. Merge and deduplicate all scopes
// For now, just return scopes from token
if authInfo.Scope == "" {
return []string{}
}
// Split space-separated scopes
// e.g., "read:users write:users" -> ["read:users", "write:users"]
return strings.Split(authInfo.Scope, " ")
}