fixed multiple roles in 1 policy

This commit is contained in:
2025-12-15 13:24:16 +08:00
parent 5743dbf22d
commit 15deba4584
6 changed files with 79 additions and 84 deletions
+6 -9
View File
@@ -3,17 +3,16 @@ package services
import (
"authorization/models"
"authorization/repository"
"database/sql"
"fmt"
"time"
)
// Authorize performs RBAC + ABAC authorization check
func Authorize(repo *repository.PermissionRepository, ctx *models.AuthorizationContext) (*models.AuthorizationResult, error) {
func Authorize(ctx *models.AuthorizationContext) (*models.AuthorizationResult, error) {
startTime := time.Now()
// Step 1: Find the permission for the requested resource and action
permission, err := repo.GetPermissionByResourceAndAction(ctx.Resource, ctx.Action)
permission, err := repository.GetPermissionByResourceAndAction(ctx.Resource, ctx.Action)
if err != nil {
return &models.AuthorizationResult{
Allowed: false,
@@ -22,7 +21,7 @@ func Authorize(repo *repository.PermissionRepository, ctx *models.AuthorizationC
}
// Step 2: Get user attributes
userAttrs, err := repo.GetUserAttributes(ctx.UserID)
userAttrs, err := repository.GetUserAttributes(ctx.UserID)
if err != nil {
return &models.AuthorizationResult{
Allowed: false,
@@ -32,7 +31,7 @@ func Authorize(repo *repository.PermissionRepository, ctx *models.AuthorizationC
ctx.UserAttributes = userAttrs
// Step 3: Get policy attributes for the permission
policies, err := repo.GetPolicyAttributesByPermission(permission.ID)
policies, err := repository.GetPolicyAttributesByPermission(permission.ID)
if err != nil {
return &models.AuthorizationResult{
Allowed: false,
@@ -64,9 +63,7 @@ func Authorize(repo *repository.PermissionRepository, ctx *models.AuthorizationC
}
// CheckPermission is a simplified authorization check
func CheckPermission(db *sql.DB, userID, resource, action string, resourceData map[string]string) (bool, string, error) {
repo := repository.NewPermissionRepository(db)
func CheckPermission(userID, resource, action string, resourceData map[string]string) (bool, string, error) {
ctx := &models.AuthorizationContext{
UserID: userID,
Resource: resource,
@@ -78,7 +75,7 @@ func CheckPermission(db *sql.DB, userID, resource, action string, resourceData m
// Add current time to environment
ctx.Environment["time"] = time.Now().Format(time.RFC3339)
result, err := Authorize(repo, ctx)
result, err := Authorize(ctx)
if err != nil {
return false, fmt.Sprintf("Authorization error: %v", err), err
}
+16 -11
View File
@@ -3,7 +3,7 @@ package services
import (
"authorization/models"
"authorization/repository"
"database/sql"
"log"
"sync"
"time"
)
@@ -21,8 +21,7 @@ func getCachedUserAttributes(s *models.CachedAuthorizationService, userID string
}
// Cache miss - fetch from DB
repo := s.Repo.(*repository.PermissionRepository)
attrs, err := repo.GetUserAttributes(userID)
attrs, err := repository.GetUserAttributes(userID)
if err != nil {
return nil, err
}
@@ -37,15 +36,14 @@ func getCachedUserAttributes(s *models.CachedAuthorizationService, userID string
// refreshCache reloads permissions and policies from database
func refreshCache(s *models.CachedAuthorizationService) {
repo := s.Repo.(*repository.PermissionRepository)
// Load all permissions
permissions, err := repo.GetAllPermissions()
permissions, err := repository.GetAllPermissions()
if err != nil {
return
}
// Load all policies
policies, err := repo.GetAllPolicyAttributes()
policies, err := repository.GetAllPolicyAttributes()
if err != nil {
return
}
@@ -90,15 +88,14 @@ func cacheRefreshLoop(s *models.CachedAuthorizationService) {
}
}
func NewCachedAuthorizationService(db *sql.DB) *models.CachedAuthorizationService {
func NewCachedAuthorizationService() *models.CachedAuthorizationService {
service := &models.CachedAuthorizationService{
Repo: repository.NewPermissionRepository(db),
PermissionCache: make(map[string]*models.Permission),
PolicyCache: make(map[int][]models.PolicyAttribute),
UserAttrCache: make(map[string]map[string]string),
CacheMutex: &sync.RWMutex{},
UserAttrMutex: &sync.RWMutex{},
CacheExpiry: 5 * time.Minute,
CacheExpiry: 30 * time.Second, // Changed from 5 minutes for faster updates
LastCacheRefresh: time.Now(),
}
@@ -115,13 +112,14 @@ func NewCachedAuthorizationService(db *sql.DB) *models.CachedAuthorizationServic
func AuthorizeWithCache(s *models.CachedAuthorizationService, ctx *models.AuthorizationContext) (*models.AuthorizationResult, error) {
startTime := time.Now()
// Step 1: Get permission from cache
// Step 1: Get permission from cache()
cacheKey := ctx.Resource + ":" + ctx.Action
cacheMutex := s.CacheMutex.(*sync.RWMutex)
cacheMutex.RLock()
permission, exists := s.PermissionCache[cacheKey]
cacheMutex.RUnlock()
log.Print("Cached authorization lookup for user=", ctx.UserID, ", resource=", ctx.Resource, ", action=", ctx.Action)
if !exists {
return &models.AuthorizationResult{
Allowed: false,
@@ -159,8 +157,15 @@ func AuthorizeWithCache(s *models.CachedAuthorizationService, ctx *models.Author
// Performance monitoring
evalTime := time.Since(startTime)
if evalTime < 50*time.Millisecond {
log.Print("Cached authorization evaluation time: ", evalTime,
" for user=", ctx.UserID, ", resource=", ctx.Resource, ", action=", ctx.Action)
}
if evalTime > 50*time.Millisecond {
// Cached should be much faster
log.Print("WARN: Slow cached authorization evaluation: ", evalTime,
" for user=", ctx.UserID, ", resource=", ctx.Resource, ", action=", ctx.Action)
}
return result, nil
+33 -34
View File
@@ -3,14 +3,13 @@ package services
import (
"authorization/models"
"fmt"
"log"
"regexp"
"strconv"
"strings"
)
// resolveVariables resolves variable references like ${resource.region}
func resolveVariables(value string, ctx *models.AuthorizationContext) string {
// Pattern: ${type.attribute}
re := regexp.MustCompile(`\$\{([^.]+)\.([^}]+)\}`)
return re.ReplaceAllStringFunc(value, func(match string) string {
@@ -41,7 +40,6 @@ func resolveVariables(value string, ctx *models.AuthorizationContext) string {
})
}
// compare performs the actual comparison based on operator
func compare(actual, expected, operator string) bool {
actual = strings.TrimSpace(actual)
expected = strings.TrimSpace(expected)
@@ -82,7 +80,6 @@ func compare(actual, expected, operator string) bool {
}
}
// numericCompare performs numeric comparison
func numericCompare(actual, expected string, compareFn func(float64, float64) bool) bool {
actualNum, err1 := strconv.ParseFloat(actual, 64)
expectedNum, err2 := strconv.ParseFloat(expected, 64)
@@ -94,11 +91,13 @@ func numericCompare(actual, expected string, compareFn func(float64, float64) bo
return compareFn(actualNum, expectedNum)
}
// inComparison checks if actual value is in comma-separated list
func inComparison(actual, expected string) bool {
values := strings.Split(expected, ",")
actual = strings.ToLower(strings.TrimSpace(actual))
log.Print("IN comparison values: ", values)
log.Print("Actual value: ", actual)
log.Print("Expected values: ", expected)
for _, val := range values {
if strings.ToLower(strings.TrimSpace(val)) == actual {
return true
@@ -108,62 +107,62 @@ func inComparison(actual, expected string) bool {
return false
}
// evaluatePolicy evaluates a single policy attribute
func evaluatePolicy(
policy models.PolicyAttribute,
ctx *models.AuthorizationContext,
) (bool, string) {
// Get the actual value based on attribute type
func evaluatePolicy(policyAttribute models.PolicyAttribute, ctx *models.AuthorizationContext) (bool, string) {
var actualValue string
var exists bool
switch policy.AttributeType {
case "user":
actualValue, exists = ctx.UserAttributes[policy.AttributeName]
if !exists {
return false, fmt.Sprintf("User attribute '%s' not found", policy.AttributeName)
}
log.Print("Attribute Type: ", policyAttribute.AttributeType)
case "resource":
actualValue, exists = ctx.ResourceData[policy.AttributeName]
switch policyAttribute.AttributeType {
case "user":
log.Print("Fetching from User Attributes")
log.Print("User Attributes: ", ctx.UserAttributes)
actualValue, exists = ctx.UserAttributes[policyAttribute.AttributeName]
if !exists {
return false, fmt.Sprintf("Resource attribute '%s' not found", policy.AttributeName)
return false, fmt.Sprintf("User attribute '%s' not found", policyAttribute.AttributeName)
}
log.Print("Found User Attribute: ", actualValue)
case "resource":
actualValue, exists = ctx.ResourceData[policyAttribute.AttributeName]
if !exists {
return false, fmt.Sprintf("Resource attribute '%s' not found", policyAttribute.AttributeName)
}
case "environment":
actualValue, exists = ctx.Environment[policy.AttributeName]
actualValue, exists = ctx.Environment[policyAttribute.AttributeName]
if !exists {
return false, fmt.Sprintf("Environment attribute '%s' not found", policy.AttributeName)
return false, fmt.Sprintf("Environment attribute '%s' not found", policyAttribute.AttributeName)
}
default:
return false, fmt.Sprintf("Unknown attribute type: %s", policy.AttributeType)
return false, fmt.Sprintf("Unknown attribute type: %s", policyAttribute.AttributeType)
}
// Handle variable substitution (e.g., ${resource.region})
expectedValue := resolveVariables(policy.AttributeValue, ctx)
expectedValue := resolveVariables(policyAttribute.AttributeValue, ctx)
// Perform comparison
satisfied := compare(actualValue, expectedValue, policy.Comparison)
fmt.Printf("[POLICY EVALUATION] Type: %s, Attribute: %s\n", policyAttribute.AttributeType, policyAttribute.AttributeName)
fmt.Printf(" Expected: %s %s %s\n", policyAttribute.AttributeName, policyAttribute.Comparison, expectedValue)
fmt.Printf(" Actual: %s = %s\n", policyAttribute.AttributeName, actualValue)
log.Print("Comparison: ", policyAttribute.Comparison)
satisfied := compare(actualValue, expectedValue, policyAttribute.Comparison)
if !satisfied {
fmt.Printf(" Result: ❌ FAILED\n\n")
return false, fmt.Sprintf(
"Policy failed: %s %s %s (actual: %s)",
policy.AttributeName,
policy.Comparison,
policyAttribute.AttributeName,
policyAttribute.Comparison,
expectedValue,
actualValue,
)
}
fmt.Printf(" Result: ✓ PASSED\n\n")
return true, ""
}
// EvaluatePolicies checks if all policy attributes are satisfied
func EvaluatePolicies(
policies []models.PolicyAttribute,
ctx *models.AuthorizationContext,
) (bool, string) {
func EvaluatePolicies(policies []models.PolicyAttribute, ctx *models.AuthorizationContext) (bool, string) {
if len(policies) == 0 {
// No policies means permission is granted by default (RBAC only)
return true, "No policies to evaluate"