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
|
|
|
|
|
}
|