Files
query-orchestration/internal/cognitoauth/permission_utils.go
T

119 lines
3.8 KiB
Go
Raw Normal View History

2025-04-08 16:27:06 -07:00
package cognitoauth
import (
"log/slog"
"regexp"
"strings"
)
2025-04-24 13:28:49 -07:00
// matchRoute determines if a request path matches a route pattern by converting
// Echo-style route patterns to regular expressions for matching.
//
// The function supports several pattern formats:
// - Exact path matching (e.g., "/users/profile")
// - Parameter patterns (e.g., "/users/:id" matches "/users/123")
// - Wildcard patterns (e.g., "/static/*" matches any path starting with "/static/")
//
// Parameters:
// - requestPath: The actual HTTP request path to check
// - routePattern: The Echo-style route pattern to match against
//
// Returns:
// - bool: true if the requestPath matches the routePattern, false otherwise
//
// Note that if the conversion of the route pattern to a regular expression fails,
// the function will return false.
2025-04-08 16:27:06 -07:00
func matchRoute(requestPath, routePattern string) bool {
// Convert Echo route pattern to regex pattern
// e.g., "/users/:id" to "/users/([^/]+)"
regexPattern := "^"
patternParts := strings.Split(routePattern, "/")
for i, part := range patternParts {
if i > 0 {
regexPattern += "/"
}
if strings.HasPrefix(part, ":") {
// Parameter part (e.g., :id)
regexPattern += "([^/]+)"
} else if part == "*" {
// Wildcard - match anything
regexPattern += ".*"
} else {
// Literal part - escape any regex metacharacters
regexPattern += regexp.QuoteMeta(part)
}
}
regexPattern += "$"
// Compile and match
regex, err := regexp.Compile(regexPattern)
if err != nil {
return false
}
return regex.MatchString(requestPath)
}
2025-04-24 13:28:49 -07:00
// checkPermissions verifies if a user has the necessary permissions to access a specific
// path based on their group memberships and the route permission configuration.
//
// This function is used by both the main token validation middleware and the OAuth
// callback handler to implement route-based access control. It works by:
// 1. Finding a matching route pattern in the routePermissions map
// 2. Extracting the required groups for that route
// 3. Checking if the user belongs to any of the required groups
//
// If no matching route pattern is found, access is allowed by default.
// If a matching pattern is found but no groups are required, access is allowed.
// Otherwise, the user must belong to at least one of the required groups.
//
// Parameters:
// - path: The HTTP request path to check permissions for
// - userGroups: A slice of group names that the user belongs to
// - routePermissions: A map where keys are route patterns and values are slices of required group names
// - logger: A structured logger for recording debug and authorization information
//
// Returns:
// - bool: true if the user is authorized, false otherwise
// - []string: The list of required groups for the matching route (may be empty)
2025-04-08 16:27:06 -07:00
func checkPermissions(path string, userGroups []string, routePermissions map[string][]string, logger *slog.Logger) (bool, []string) {
// Find matching route pattern
var requiredGroups []string
var matched bool
for pattern, groups := range routePermissions {
if matchRoute(path, pattern) {
requiredGroups = groups
matched = true
logger.Debug("Found matching route pattern", "pattern", pattern, "requiredGroups", requiredGroups)
break
}
}
if !matched {
// No matching pattern found - could either allow or deny by default
logger.Info("No matching route pattern found for path", "path", path)
return true, nil // Allowing by default
}
// If no groups are required, allow access
if len(requiredGroups) == 0 {
return true, requiredGroups
}
// Check if user has any of the required groups
for _, requiredGroup := range requiredGroups {
for _, userGroup := range userGroups {
if userGroup == requiredGroup {
logger.Debug("User has required group", "group", requiredGroup)
return true, requiredGroups
}
}
}
return false, requiredGroups
}