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
+4 -2
View File
@@ -1,12 +1,12 @@
package handlers
import (
"authorization/db"
"authorization/helper"
"authorization/middleware"
"authorization/models"
"authorization/services"
"encoding/json"
"log"
"net/http"
)
@@ -14,7 +14,7 @@ var authService *models.CachedAuthorizationService
// InitAuthService initializes the authorization service with caching
func InitAuthService() {
authService = services.NewCachedAuthorizationService(db.DB)
authService = services.NewCachedAuthorizationService()
}
// AuthorizeHandler godoc
@@ -52,6 +52,8 @@ func AuthorizeHandler(w http.ResponseWriter, r *http.Request) {
return
}
log.Print("Authorization request for user=", ctx.UserID, ", resource=", ctx.Resource, ", action=", ctx.Action)
log.Print("JWT claims user=", claims.UserID, ", username=", claims.Username, ", role=", claims.Role)
// Verify JWT user matches request user (security check)
if ctx.UserID != claims.UserID {
helper.RespondWithError(w, http.StatusForbidden, "User ID mismatch")
+7 -8
View File
@@ -77,12 +77,11 @@ type AuthorizationResult struct {
// CachedAuthorizationService adds caching layer to authorization
type CachedAuthorizationService struct {
Repo interface{} // repository.PermissionRepository
PermissionCache map[string]*Permission // key: "resource:action"
PolicyCache map[int][]PolicyAttribute
UserAttrCache map[string]map[string]string // key: userID
CacheMutex interface{} // sync.RWMutex
UserAttrMutex interface{} // sync.RWMutex
CacheExpiry time.Duration
LastCacheRefresh time.Time
PermissionCache map[string]*Permission `json:"-"` // key: "resource:action"
PolicyCache map[int][]PolicyAttribute `json:"-"`
UserAttrCache map[string]map[string]string `json:"-"` // key: userID
CacheMutex interface{} `json:"-"` // sync.RWMutex
UserAttrMutex interface{} `json:"-"` // sync.RWMutex
CacheExpiry time.Duration `json:"cache_expiry"`
LastCacheRefresh time.Time `json:"last_cache_refresh"`
}
+13 -20
View File
@@ -1,21 +1,14 @@
package repository
import (
"authorization/db"
"authorization/models"
"database/sql"
"fmt"
)
type PermissionRepository struct {
db *sql.DB
}
func NewPermissionRepository(db *sql.DB) *PermissionRepository {
return &PermissionRepository{db: db}
}
// GetPermissionByResourceAndAction finds a permission by resource and action
func (r *PermissionRepository) GetPermissionByResourceAndAction(resource, action string) (*models.Permission, error) {
func GetPermissionByResourceAndAction(resource, action string) (*models.Permission, error) {
query := `
SELECT id, permission_name, description, resource, action
FROM permissions
@@ -24,7 +17,7 @@ func (r *PermissionRepository) GetPermissionByResourceAndAction(resource, action
`
var perm models.Permission
err := r.db.QueryRow(query, resource, action).Scan(
err := db.DB.QueryRow(query, resource, action).Scan(
&perm.ID,
&perm.PermissionName,
&perm.Description,
@@ -43,14 +36,14 @@ func (r *PermissionRepository) GetPermissionByResourceAndAction(resource, action
}
// GetPolicyAttributesByPermission retrieves all policy attributes for a permission
func (r *PermissionRepository) GetPolicyAttributesByPermission(permissionID int) ([]models.PolicyAttribute, error) {
func GetPolicyAttributesByPermission(permissionID int) ([]models.PolicyAttribute, error) {
query := `
SELECT id, attribute_name, attribute_type, comparison, attribute_value, permission_id
FROM policy_attributes
WHERE permission_id = ?
`
rows, err := r.db.Query(query, permissionID)
rows, err := db.DB.Query(query, permissionID)
if err != nil {
return nil, fmt.Errorf("error querying policy attributes: %w", err)
}
@@ -77,14 +70,14 @@ func (r *PermissionRepository) GetPolicyAttributesByPermission(permissionID int)
}
// GetUserAttributes retrieves all attributes for a user
func (r *PermissionRepository) GetUserAttributes(userID string) (map[string]string, error) {
func GetUserAttributes(userID string) (map[string]string, error) {
query := `
SELECT attribute_name, attribute_value
FROM user_attributes
WHERE user_id = ?
`
rows, err := r.db.Query(query, userID)
rows, err := db.DB.Query(query, userID)
if err != nil {
return nil, fmt.Errorf("error querying user attributes: %w", err)
}
@@ -104,7 +97,7 @@ func (r *PermissionRepository) GetUserAttributes(userID string) (map[string]stri
}
// GetUserByID retrieves user details
func (r *PermissionRepository) GetUserByID(userID string) (*models.User, error) {
func GetUserByID(userID string) (*models.User, error) {
query := `
SELECT user_id, first_name, middle_name, last_name, suffix, email_address,
account_type, emp_id, reg, prov, aProv, mun, bgy, is_logged_in,
@@ -116,7 +109,7 @@ func (r *PermissionRepository) GetUserByID(userID string) (*models.User, error)
`
var user models.User
err := r.db.QueryRow(query, userID).Scan(
err := db.DB.QueryRow(query, userID).Scan(
&user.UserID,
&user.FirstName,
&user.MiddleName,
@@ -155,14 +148,14 @@ func (r *PermissionRepository) GetUserByID(userID string) (*models.User, error)
}
// GetAllPermissions retrieves all permissions (for caching)
func (r *PermissionRepository) GetAllPermissions() ([]models.Permission, error) {
func GetAllPermissions() ([]models.Permission, error) {
query := `
SELECT id, permission_name, description, resource, action
FROM permissions
ORDER BY id
`
rows, err := r.db.Query(query)
rows, err := db.DB.Query(query)
if err != nil {
return nil, fmt.Errorf("error querying all permissions: %w", err)
}
@@ -188,14 +181,14 @@ func (r *PermissionRepository) GetAllPermissions() ([]models.Permission, error)
}
// GetAllPolicyAttributes retrieves all policy attributes (for caching)
func (r *PermissionRepository) GetAllPolicyAttributes() (map[int][]models.PolicyAttribute, error) {
func GetAllPolicyAttributes() (map[int][]models.PolicyAttribute, error) {
query := `
SELECT id, attribute_name, attribute_type, comparison, attribute_value, permission_id
FROM policy_attributes
ORDER BY permission_id, id
`
rows, err := r.db.Query(query)
rows, err := db.DB.Query(query)
if err != nil {
return nil, fmt.Errorf("error querying all policy attributes: %w", err)
}
+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"