fixed multiple roles in 1 policy
This commit is contained in:
@@ -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
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user