feat: standardize field names and add flexible role_id handling for JWT compatibility

- Rename user_id → users_id across all models, handlers, services, and tests
- Add custom RoleIDs type supporting string/int/array unmarshaling (e.g., "1", 1, [1])
- Implement flexible JSON unmarshaling for JWT Claims to handle field name variants
  - Support both user_id/users_id and email/email_address field names
  - Enable role_id as string ("1"), int (1), or array ([1,2])
- Update AuthorizationContext to handle role_id type flexibility
- Add comprehensive logging to repository, service, and handler layers
  - Entry/exit logs with full context
  - Success (✓) and failure (✗) indicators
  - Step-by-step authorization flow tracking
- Add containsRole helper for multi-role membership checks
- Fix database queries: user_id → users_id, id → permissions_id
- Update all tests to use models.RoleIDs{} syntax
- Change GetRole middleware return type: string → []int
- Maintain backward compatibility with legacy JWT tokens

This change improves integration with external services (MIS) that may send
role_id in different formats and standardizes field naming conventions
throughout the authorization microservice.
This commit is contained in:
2026-02-03 16:35:16 +08:00
parent 97f1ef5f07
commit ae1831e61f
15 changed files with 348 additions and 184 deletions
+37 -10
View File
@@ -6,6 +6,7 @@ import (
"authorization/models"
"authorization/services"
"encoding/json"
"io"
"log"
"net/http"
)
@@ -38,24 +39,39 @@ func AuthorizeHandler(w http.ResponseWriter, r *http.Request) {
return
}
log.Printf("JWT Claims: UsersID='%s', EmailAddress='%s', RoleID=%v", claims.UsersID, claims.EmailAddress, claims.RoleID)
var ctx models.AuthorizationContext
err := json.NewDecoder(r.Body).Decode(&ctx)
// Read and log raw request body
bodyBytes, err := io.ReadAll(r.Body)
if err != nil {
helper.RespondWithError(w, http.StatusBadRequest, "Invalid request body")
return
}
log.Printf("Raw authorization request body: %s", string(bodyBytes))
// Decode JSON into AuthorizationContext
if err := json.Unmarshal(bodyBytes, &ctx); err != nil {
log.Printf("ERROR: Failed to unmarshal request body: %v", err)
helper.RespondWithError(w, http.StatusBadRequest, "Invalid request payload")
return
}
// Validate request
if ctx.UserID == "" || ctx.Resource == "" || ctx.Action == "" {
helper.RespondWithError(w, http.StatusBadRequest, "Missing required fields: user_id, resource, action")
log.Printf("Decoded authorization context: %+v", ctx)
log.Printf("User ID ctx=%s, resource=%s, action=%s, roleID=%d", ctx.UsersID, ctx.Resource, ctx.Action, ctx.RoleID)
if ctx.UsersID == "" || ctx.Resource == "" || ctx.Action == "" {
log.Printf("ERROR: Missing required fields - UsersID=%s, Resource=%s, Action=%s", ctx.UsersID, ctx.Resource, ctx.Action)
helper.RespondWithError(w, http.StatusBadRequest, "Missing required fields: users_id, resource, action")
return
}
log.Print("Authorization request for user=", ctx.UserID, ", resource=", ctx.Resource, ", action=", ctx.Action)
log.Print("JWT claims user=", claims.UserID, ", role=", claims.RoleID)
log.Print("Authorization request for user=", ctx.UsersID, ", resource=", ctx.Resource, ", action=", ctx.Action)
log.Print("JWT claims user=", claims.UsersID, ", role=", claims.RoleID)
// Verify JWT user matches request user (security check)
if ctx.UserID != claims.UserID {
if ctx.UsersID != claims.UsersID {
log.Printf("ERROR: User ID mismatch - ctx.UsersID='%s' vs claims.UsersID='%s'", ctx.UsersID, claims.UsersID)
helper.RespondWithError(w, http.StatusForbidden, "User ID mismatch")
return
}
@@ -68,17 +84,19 @@ func AuthorizeHandler(w http.ResponseWriter, r *http.Request) {
ctx.Environment = make(map[string]string)
}
if ctx.RoleID != claims.RoleID {
// containsRole checks if a role exists in a slice of roles
if !containsRole([]int(claims.RoleID), ctx.RoleID) {
helper.RespondWithError(w, http.StatusForbidden, "Role ID mismatch")
return
}
log.Print("User role verified: ", ctx.RoleID)
// Perform authorization
log.Printf("[Handler] Performing authorization check for user=%s, resource=%s, action=%s", ctx.UserID, ctx.Resource, ctx.Action)
log.Printf("[Handler] Performing authorization check for user=%s, resource=%s, action=%s", ctx.UsersID, ctx.Resource, ctx.Action)
result, err := services.AuthorizeWithCache(authService, &ctx)
if err != nil {
helper.LogError(err, "Authorization service error")
log.Printf("✗ Authorization service error for user=%s: %v", ctx.UserID, err)
log.Printf("✗ Authorization service error for user=%s: %v", ctx.UsersID, err)
helper.RespondWithError(w, http.StatusInternalServerError, "Authorization check failed")
return
}
@@ -102,3 +120,12 @@ func AuthorizeHandler(w http.ResponseWriter, r *http.Request) {
helper.RespondWithJSON(w, http.StatusForbidden, response)
}
}
func containsRole(roles []int, role int) bool {
for _, r := range roles {
if r == role {
return true
}
}
return false
}
+29 -29
View File
@@ -44,8 +44,8 @@ func TestAuthorizeHandlerNoJWTClaims(t *testing.T) {
func TestAuthorizeHandlerInvalidJSON(t *testing.T) {
// Setup - no need to init service, we're testing JSON parsing before auth
claims := &models.Claims{
UserID: "user123",
RoleID: "admin",
UsersID: "user123",
RoleID: models.RoleIDs{1},
}
req := httptest.NewRequest("POST", AuthCheckEndpoint, bytes.NewBufferString("invalid json"))
@@ -73,19 +73,19 @@ func TestAuthorizeHandlerMissingRequiredFields(t *testing.T) {
},
{
name: "Missing Resource",
payload: models.AuthorizationContext{UserID: "user123", Action: "read"},
payload: models.AuthorizationContext{UsersID: "user123", Action: "read"},
},
{
name: "Missing Action",
payload: models.AuthorizationContext{UserID: "user123", Resource: "document"},
payload: models.AuthorizationContext{UsersID: "user123", Resource: "document"},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
claims := &models.Claims{
UserID: "user123",
RoleID: "admin",
UsersID: "user123",
RoleID: models.RoleIDs{1},
}
body, _ := json.Marshal(tc.payload)
@@ -106,12 +106,12 @@ func TestAuthorizeHandlerMissingRequiredFields(t *testing.T) {
func TestAuthorizeHandlerUserIDMismatch(t *testing.T) {
// Setup
claims := &models.Claims{
UserID: "user123",
RoleID: "admin",
UsersID: "user123",
RoleID: models.RoleIDs{1},
}
payload := models.AuthorizationContext{
UserID: "differentUser",
UsersID: "differentUser",
Resource: "document",
Action: "read",
}
@@ -134,12 +134,12 @@ func TestAuthorizeHandlerUserIDMismatch(t *testing.T) {
func TestAuthorizeHandlerNilMaps(t *testing.T) {
// Test that nil maps don't cause additional panics beyond missing authService
claims := &models.Claims{
UserID: "user123",
RoleID: "admin",
UsersID: "user123",
RoleID: models.RoleIDs{1},
}
payload := models.AuthorizationContext{
UserID: "user123",
UsersID: "user123",
Resource: "document",
Action: "read",
ResourceData: nil, // nil map
@@ -171,12 +171,12 @@ func TestAuthorizeHandlerNilMaps(t *testing.T) {
func TestAuthorizeHandlerEmptyUserID(t *testing.T) {
claims := &models.Claims{
UserID: "user123",
RoleID: "admin",
UsersID: "user123",
RoleID: models.RoleIDs{1},
}
payload := models.AuthorizationContext{
UserID: "",
UsersID: "",
Resource: "document",
Action: "read",
}
@@ -196,12 +196,12 @@ func TestAuthorizeHandlerEmptyUserID(t *testing.T) {
func TestAuthorizeHandlerEmptyResource(t *testing.T) {
claims := &models.Claims{
UserID: "user123",
RoleID: "admin",
UsersID: "user123",
RoleID: models.RoleIDs{1},
}
payload := models.AuthorizationContext{
UserID: "user123",
UsersID: "user123",
Resource: "",
Action: "read",
}
@@ -221,12 +221,12 @@ func TestAuthorizeHandlerEmptyResource(t *testing.T) {
func TestAuthorizeHandlerEmptyAction(t *testing.T) {
claims := &models.Claims{
UserID: "user123",
RoleID: "admin",
UsersID: "user123",
RoleID: models.RoleIDs{1},
}
payload := models.AuthorizationContext{
UserID: "user123",
UsersID: "user123",
Resource: "document",
Action: "",
}
@@ -261,8 +261,8 @@ func TestAuthorizeHandlerInvalidClaimsType(t *testing.T) {
func TestAuthorizeHandlerMalformedJSON(t *testing.T) {
claims := &models.Claims{
UserID: "user123",
RoleID: "admin",
UsersID: "user123",
RoleID: models.RoleIDs{1},
}
testCases := []struct {
@@ -307,7 +307,7 @@ func TestAuthorizeHandlerSpecialCharactersInFields(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
payload := models.AuthorizationContext{
UserID: tc.userID,
UsersID: tc.userID,
Resource: tc.resource,
Action: tc.action,
}
@@ -317,8 +317,8 @@ func TestAuthorizeHandlerSpecialCharactersInFields(t *testing.T) {
// Update claims to match userID
testClaims := &models.Claims{
UserID: tc.userID,
RoleID: "admin",
UsersID: tc.userID,
RoleID: models.RoleIDs{1},
}
ctx := context.WithValue(req.Context(), models.ContextKey("claims"), testClaims)
req = req.WithContext(ctx)
@@ -343,12 +343,12 @@ func TestAuthorizeHandlerSpecialCharactersInFields(t *testing.T) {
func TestAuthorizeHandlerWithResourceData(t *testing.T) {
// Test that ResourceData is properly passed through to authorization
claims := &models.Claims{
UserID: "user123",
RoleID: "admin",
UsersID: "user123",
RoleID: models.RoleIDs{1},
}
payload := models.AuthorizationContext{
UserID: "user123",
UsersID: "user123",
Resource: "personnel",
Action: "assign_role",
ResourceData: map[string]string{
+8 -7
View File
@@ -21,7 +21,7 @@ import (
const (
claimsKey models.ContextKey = "claims"
userIDKey models.ContextKey = "user_id"
userIDKey models.ContextKey = "users_id"
roleIDKey models.ContextKey = "role_id"
)
@@ -168,7 +168,7 @@ func parseAndValidateToken(tokenString string) (*models.Claims, error) {
return nil, fmt.Errorf("invalid claims")
}
log.Printf("Token verified successfully for user: (UserID: %s)", claims.UserID)
log.Printf("Token verified successfully for user: (UserID: %s)", claims.UsersID)
return claims, nil
}
@@ -239,8 +239,9 @@ func JWTAuth(next http.HandlerFunc) http.HandlerFunc {
// buildContext efficiently builds context with claims (reduces allocations)
func buildContext(parent context.Context, claims *models.Claims) context.Context {
ctx := context.WithValue(parent, claimsKey, claims)
ctx = context.WithValue(ctx, userIDKey, claims.UserID)
ctx = context.WithValue(ctx, roleIDKey, claims.RoleID)
ctx = context.WithValue(ctx, userIDKey, claims.UsersID)
// Store plain []int in context for roles to keep middleware interfaces simple
ctx = context.WithValue(ctx, roleIDKey, []int(claims.RoleID))
return ctx
}
@@ -256,8 +257,8 @@ func GetUserID(r *http.Request) (string, bool) {
return userID, ok
}
// GetRole retrieves the role from the request context
func GetRole(r *http.Request) (string, bool) {
role, ok := r.Context().Value(roleIDKey).(string)
// GetRole retrieves the roles from the request context
func GetRole(r *http.Request) ([]int, bool) {
role, ok := r.Context().Value(roleIDKey).([]int)
return role, ok
}
+27 -25
View File
@@ -167,15 +167,15 @@ func TestParseAndValidateToken(t *testing.T) {
func TestBuildContext(t *testing.T) {
claims := &models.Claims{
UserID: "user123",
RoleID: "admin",
UsersID: "user123",
RoleID: models.RoleIDs{3},
}
parent := context.Background()
ctx := buildContext(parent, claims)
// Check claims
if val, ok := ctx.Value(claimsKey).(*models.Claims); !ok || val.UserID != "user123" {
if val, ok := ctx.Value(claimsKey).(*models.Claims); !ok || val.UsersID != "user123" {
t.Error("Claims not properly set in context")
}
@@ -185,15 +185,15 @@ func TestBuildContext(t *testing.T) {
}
// Check role
if val, ok := ctx.Value(roleIDKey).(string); !ok || val != "admin" {
if val, ok := ctx.Value(roleIDKey).([]int); !ok || len(val) == 0 || val[0] != 3 {
t.Error("Role not properly set in context")
}
}
func TestGetClaims(t *testing.T) {
claims := &models.Claims{
UserID: "user123",
RoleID: "admin",
UsersID: "user123",
RoleID: models.RoleIDs{3},
}
req := httptest.NewRequest("GET", "/", nil)
@@ -204,8 +204,8 @@ func TestGetClaims(t *testing.T) {
if !ok {
t.Error("Expected claims to be found")
}
if retrievedClaims.UserID != "user123" {
t.Errorf("Expected UserID 'user123', got '%s'", retrievedClaims.UserID)
if retrievedClaims.UsersID != "user123" {
t.Errorf("Expected UserID 'user123', got '%s'", retrievedClaims.UsersID)
}
}
@@ -225,15 +225,17 @@ func TestGetUserID(t *testing.T) {
func TestGetRole(t *testing.T) {
req := httptest.NewRequest("GET", "/", nil)
ctx := context.WithValue(req.Context(), roleIDKey, "admin")
ctx := context.WithValue(req.Context(), roleIDKey, []int{3})
req = req.WithContext(ctx)
role, ok := GetRole(req)
if !ok {
t.Error("Expected role to be found")
}
if role != "admin" {
t.Errorf("Expected 'admin', got '%s'", role)
if len(role) == 0 {
t.Errorf("Expected at least one role, got '%v'", role)
} else if role[0] != 3 {
t.Errorf("Expected first role to be 3, got '%v'", role[0])
}
}
@@ -335,13 +337,13 @@ func TestParseAndValidateTokenMalformedTokens(t *testing.T) {
}
func TestBuildContextWithDifferentRoles(t *testing.T) {
roles := []string{"admin", "user", "guest", "superadmin", "", "role-with-dash"}
roles := []int{3, 4, 5, 6, 7, 8}
for _, role := range roles {
t.Run("Role: "+role, func(t *testing.T) {
t.Run("Role: "+string(rune(role)), func(t *testing.T) {
claims := &models.Claims{
UserID: "user123",
RoleID: role,
UsersID: "user123",
RoleID: models.RoleIDs{role},
}
req := httptest.NewRequest("GET", "/", nil)
@@ -352,8 +354,8 @@ func TestBuildContextWithDifferentRoles(t *testing.T) {
if !ok {
t.Error("Claims not found in context")
}
if retrievedClaims.RoleID != role {
t.Errorf("Role = %q, want %q", retrievedClaims.RoleID, role)
if len(retrievedClaims.RoleID) == 0 || retrievedClaims.RoleID[0] != role {
t.Errorf("Role = %v, want %v", retrievedClaims.RoleID, role)
}
})
}
@@ -404,8 +406,8 @@ func TestGetRoleWithNoClaims(t *testing.T) {
if ok {
t.Error("Expected ok=false when no claims")
}
if role != "" {
t.Errorf("Expected empty string, got %q", role)
if role != nil && len(role) != 0 {
t.Errorf("Expected no roles, got %v", role)
}
}
@@ -444,7 +446,7 @@ func TestJWTAuthTokenWithMissingClaims(t *testing.T) {
{
"Missing UserID",
&models.Claims{
RoleID: "admin",
RoleID: models.RoleIDs{3},
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)),
},
@@ -453,7 +455,7 @@ func TestJWTAuthTokenWithMissingClaims(t *testing.T) {
{
"Missing Role",
&models.Claims{
UserID: "user123",
UsersID: "user123",
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)),
},
@@ -494,8 +496,8 @@ func TestJWTAuthConcurrentRequests(t *testing.T) {
t.Skip("Requires RSA certificate setup - integration test")
claims := &models.Claims{
UserID: "user123",
RoleID: "admin",
UsersID: "user123",
RoleID: models.RoleIDs{3},
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)),
},
@@ -539,8 +541,8 @@ func TestJWTAuthTokenSignedWithWrongKey(t *testing.T) {
// Create token with wrong key
claims := &models.Claims{
UserID: "user123",
RoleID: "admin",
UsersID: "user123",
RoleID: models.RoleIDs{3},
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)),
},
+1 -1
View File
@@ -29,7 +29,7 @@ func RateLimiterMiddleware(config models.RateLimitConfig) func(http.HandlerFunc)
return
}
// Extract user identifier (prefer user_id from JWT, fallback to IP)
// Extract user identifier (prefer users_id from JWT, fallback to IP)
var identifier string
if userID, ok := GetUserID(r); ok {
identifier = "user:" + userID
+83 -4
View File
@@ -1,13 +1,17 @@
package models
import (
"bytes"
"encoding/json"
"fmt"
"strconv"
"time"
"github.com/golang-jwt/jwt/v5"
)
type AuthorizationRequest struct {
UserID string `json:"user_id"`
UsersID string `json:"users_id"`
Resource string `json:"resource"`
Action string `json:"action"`
}
@@ -17,13 +21,88 @@ type AuthorizationResponse struct {
Reason string `json:"reason,omitempty"`
}
// RoleIDs represents one or more role IDs.
// It is defined as a custom type so we can implement flexible JSON unmarshalling
// that accepts a single string ("1"), a single number (1), or an array ([1,2,...]).
type RoleIDs []int
// UnmarshalJSON allows RoleIDs to be populated from different JSON shapes:
// string: "1" -> [1]
// number: 1 -> [1]
// array: [1] or [1,2,...] -> [1] or [1,2,...]
func (r *RoleIDs) UnmarshalJSON(data []byte) error {
// Handle null or empty
trimmed := bytes.TrimSpace(data)
if len(trimmed) == 0 || bytes.Equal(trimmed, []byte("null")) {
*r = nil
return nil
}
switch trimmed[0] {
case '"':
// String value, e.g. "1"
var s string
if err := json.Unmarshal(trimmed, &s); err != nil {
return err
}
if s == "" {
*r = nil
return nil
}
v, err := strconv.Atoi(s)
if err != nil {
return err
}
*r = RoleIDs{v}
return nil
case '[':
// Standard JSON array of ints
var arr []int
if err := json.Unmarshal(trimmed, &arr); err != nil {
return err
}
*r = RoleIDs(arr)
return nil
default:
// Try to decode as a single number
var v int
if err := json.Unmarshal(trimmed, &v); err == nil {
*r = RoleIDs{v}
return nil
}
return fmt.Errorf("unsupported JSON for role_id: %s", string(trimmed))
}
}
type Claims struct {
UserID string `json:"user_id"`
EmailAddress string `json:"email_address"`
RoleID string `json:"role_id"`
UsersID string `json:"users_id,omitempty"`
EmailAddress string `json:"email_address,omitempty"`
RoleID RoleIDs `json:"role_id"`
jwt.RegisteredClaims
}
// UnmarshalJSON handles both "user_id" and "users_id" field names in JWT claims
func (c *Claims) UnmarshalJSON(data []byte) error {
type Alias Claims
aux := &struct {
*Alias
}{
Alias: (*Alias)(c),
}
if err := json.Unmarshal(data, &aux); err != nil {
return err
}
// If UsersID is empty but UserID is set, copy UserID to UsersID
if c.UsersID == "" && c.UsersID != "" {
c.UsersID = c.UsersID
}
// If EmailAddress is empty but Email is set, copy Email to EmailAddress
if c.EmailAddress == "" && c.EmailAddress != "" {
c.EmailAddress = c.EmailAddress
}
return nil
}
// ContextKey is a custom type for context keys to avoid collisions
type ContextKey string
+57 -4
View File
@@ -1,6 +1,11 @@
package models
import "time"
import (
"encoding/json"
"fmt"
"strconv"
"time"
)
// Permission represents a system permission
type Permission struct {
@@ -38,7 +43,7 @@ type UserAttribute struct {
// User represents a system user
type User struct {
UserID string `json:"user_id" db:"user_id"`
UsersID string `json:"users_id" db:"user_id"`
FirstName string `json:"first_name" db:"first_name"`
MiddleInitial string `json:"middle_initial" db:"middle_initial"`
LastName string `json:"last_name" db:"last_name"`
@@ -57,15 +62,63 @@ type User struct {
// AuthorizationContext holds all context needed for authorization decisions
type AuthorizationContext struct {
UserID string `json:"user_id"`
UsersID string `json:"users_id"`
Resource string `json:"resource"`
Action string `json:"action"`
RoleID string `json:"role_id"` // User's role ID
RoleID int `json:"role_id"` // User's role ID
UserAttributes map[string]string `json:"user_attributes"`
ResourceData map[string]string `json:"resource_data"` // Additional resource context
Environment map[string]string `json:"environment"` // Time, location, etc.
}
// UnmarshalJSON handles role_id as either string or int
func (ac *AuthorizationContext) UnmarshalJSON(data []byte) error {
type Alias AuthorizationContext
aux := &struct {
RoleIDRaw json.RawMessage `json:"role_id"`
*Alias
}{
Alias: (*Alias)(ac),
}
if err := json.Unmarshal(data, &aux); err != nil {
return err
}
// Handle role_id as string, int, or array
if len(aux.RoleIDRaw) > 0 {
// Try unmarshaling as int first
var roleInt int
if err := json.Unmarshal(aux.RoleIDRaw, &roleInt); err == nil {
ac.RoleID = roleInt
} else {
// Try as array of ints (take first element)
var roleArray []int
if err := json.Unmarshal(aux.RoleIDRaw, &roleArray); err == nil {
if len(roleArray) > 0 {
ac.RoleID = roleArray[0]
}
} else {
// Try as string
var roleStr string
if err := json.Unmarshal(aux.RoleIDRaw, &roleStr); err == nil {
if roleStr != "" {
var convErr error
ac.RoleID, convErr = strconv.Atoi(roleStr)
if convErr != nil {
return fmt.Errorf("invalid role_id: %s", roleStr)
}
}
} else {
return fmt.Errorf("role_id must be a number, numeric string, or array of numbers")
}
}
}
}
return nil
}
// AuthorizationResult contains the result of an authorization check
type AuthorizationResult struct {
Allowed bool `json:"allowed"`
+28 -29
View File
@@ -5,13 +5,18 @@ import (
"authorization/models"
"database/sql"
"fmt"
"log"
)
func GetPermissionByResourceActionAndRole(resource, action string, roleID int) (*models.Permission, error) {
log.Printf("[Repository] GetPermissionByResourceActionAndRole - resource=%s, action=%s, roleID=%d",
resource, action, roleID)
query := `
SELECT p.id, p.permission_name, p.description, p.resource, p.action
SELECT p.permissions_id, p.permission_name, p.description, p.resource, p.action
FROM permissions p
INNER JOIN role_permissions rp ON p.id = rp.permission_id
INNER JOIN role_permissions rp
ON p.permissions_id = rp.permission_id
WHERE p.resource = ? AND p.action = ? AND rp.role_id = ?
LIMIT 1
`
@@ -27,11 +32,15 @@ func GetPermissionByResourceActionAndRole(resource, action string, roleID int) (
if err != nil {
if err == sql.ErrNoRows {
log.Printf("[Repository] ✗ No permission found for resource=%s, action=%s, roleID=%d",
resource, action, roleID)
return nil, fmt.Errorf("permission not found or not granted to role_id=%d for resource=%s, action=%s", roleID, resource, action)
}
log.Printf("[Repository] ✗ Database error querying permission: %v", err)
return nil, fmt.Errorf("error querying permission: %w", err)
}
log.Printf("[Repository] ✓ Permission found: ID=%d, Name=%s", perm.ID, perm.PermissionName)
return &perm, nil
}
@@ -71,14 +80,17 @@ func GetPolicyAttributesByPermission(permissionID int) ([]models.PolicyAttribute
// GetUserAttributes retrieves all attributes for a user
func GetUserAttributes(userID string) (map[string]string, error) {
log.Printf("[Repository] GetUserAttributes - userID=%s", userID)
query := `
SELECT attribute_name, attribute_value
FROM user_attributes
WHERE user_id = ?
WHERE users_id = ?
`
rows, err := db.DB.Query(query, userID)
if err != nil {
log.Printf("[Repository] ✗ Database error querying user attributes: %v", err)
return nil, fmt.Errorf("error querying user attributes: %w", err)
}
defer rows.Close()
@@ -88,51 +100,38 @@ func GetUserAttributes(userID string) (map[string]string, error) {
var name, value string
err := rows.Scan(&name, &value)
if err != nil {
log.Printf("[Repository] ✗ Error scanning user attribute: %v", err)
return nil, fmt.Errorf("error scanning user attribute: %w", err)
}
attributes[name] = value
}
log.Printf("[Repository] ✓ Retrieved %d user attributes", len(attributes))
return attributes, nil
}
// GetUserByID retrieves user details
func GetUserByID(userID string) (*models.User, error) {
log.Printf("[Repository] GetUserByID - userID=%s", userID)
query := `
SELECT user_id, first_name, middle_initial, last_name, suffix, email_address,
home_address, contact_number,
role_id, is_deleted, created_at, updated_at
FROM uess_user_management.users
WHERE user_id = ? AND is_deleted = 'N'
LIMIT 1
SELECT users_id, email_address
FROM users
WHERE users_id = ?
`
var user models.User
err := db.DB.QueryRow(query, userID).Scan(
&user.UserID,
&user.FirstName,
&user.MiddleInitial,
&user.LastName,
&user.Suffix,
&user.EmailAddress,
&user.HomeAddress,
&user.ContactNumber,
&user.RoleID,
&user.IsDeleted,
&user.CreatedAt,
&user.UpdatedAt,
)
err := db.DB.QueryRow(query, userID).Scan(&user.UsersID, &user.EmailAddress)
if err != nil {
if err == sql.ErrNoRows {
log.Printf("[Repository] ✗ User not found: %s", userID)
return nil, fmt.Errorf("user not found: %s", userID)
}
log.Printf("[Repository] ✗ Database error querying user: %v", err)
return nil, fmt.Errorf("error querying user: %w", err)
}
log.Printf("[Repository] ✓ User found: UsersID=%s", user.UsersID)
return &user, nil
}
@@ -172,9 +171,9 @@ func GetAllPermissions() ([]models.Permission, error) {
// GetAllPolicyAttributes retrieves all policy attributes (for caching)
func GetAllPolicyAttributes() (map[int][]models.PolicyAttribute, error) {
query := `
SELECT id, attribute_name, attribute_type, comparison, attribute_value, permission_id
SELECT policy_attributes_id, attribute_name, attribute_type, comparison, attribute_value, permission_id
FROM policy_attributes
ORDER BY permission_id, id
ORDER BY permission_id, policy_attributes_id
`
rows, err := db.DB.Query(query)
+11 -11
View File
@@ -81,7 +81,7 @@ func TestGetUserAttributesSuccess(t *testing.T) {
AddRow("department", "engineering").
AddRow("level", "5")
mock.ExpectQuery("SELECT attribute_name, attribute_value FROM user_attributes WHERE user_id = \\?").
mock.ExpectQuery("SELECT attribute_name, attribute_value FROM user_attributes WHERE users_id = \\?").
WithArgs("user123").
WillReturnRows(rows)
@@ -108,7 +108,7 @@ func TestGetUserByIDSuccess(t *testing.T) {
testTime := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC)
rows := sqlmock.NewRows([]string{
"user_id", "first_name", "middle_initial", "last_name", "suffix", "email_address",
"users_id", "first_name", "middle_initial", "last_name", "suffix", "email_address",
"home_address", "contact_number",
"role_id", "is_deleted", "created_at", "updated_at",
}).AddRow(
@@ -117,7 +117,7 @@ func TestGetUserByIDSuccess(t *testing.T) {
1, "N", "secret", "Y", testTime, testTime,
)
mock.ExpectQuery("SELECT user_id, first_name").
mock.ExpectQuery("SELECT users_id, first_name").
WithArgs("user123").
WillReturnRows(rows)
@@ -129,8 +129,8 @@ func TestGetUserByIDSuccess(t *testing.T) {
if user == nil {
t.Fatal("Expected user, got nil")
}
if user.UserID != "user123" {
t.Errorf("Expected UserID 'user123', got '%s'", user.UserID)
if user.UsersID != "user123" {
t.Errorf("Expected UsersID 'user123', got '%s'", user.UsersID)
}
if user.FirstName != "John" {
t.Errorf("Expected FirstName 'John', got '%s'", user.FirstName)
@@ -141,7 +141,7 @@ func TestGetUserByIDNotFound(t *testing.T) {
mock, cleanup := setupMockDB(t)
defer cleanup()
mock.ExpectQuery("SELECT user_id, first_name").
mock.ExpectQuery("SELECT users_id, first_name").
WithArgs("nonexistent").
WillReturnError(sql.ErrNoRows)
@@ -269,7 +269,7 @@ func TestGetUserAttributesEmptyUserID(t *testing.T) {
rows := sqlmock.NewRows([]string{"attribute_name", "attribute_value"})
// Match the actual query format
mock.ExpectQuery(`SELECT attribute_name, attribute_value\s+FROM user_attributes\s+WHERE user_id = \?`).
mock.ExpectQuery(`SELECT attribute_name, attribute_value\s+FROM user_attributes\s+WHERE users_id = \?`).
WithArgs("").
WillReturnRows(rows)
@@ -294,7 +294,7 @@ func TestGetUserAttributesMultipleAttributes(t *testing.T) {
AddRow("clearance", "high")
// Match the actual query
mock.ExpectQuery(`SELECT attribute_name, attribute_value\s+FROM user_attributes\s+WHERE user_id = \?`).
mock.ExpectQuery(`SELECT attribute_name, attribute_value\s+FROM user_attributes\s+WHERE users_id = \?`).
WithArgs("user123").
WillReturnRows(rows)
@@ -314,13 +314,13 @@ func TestGetUserByIDEmptyID(t *testing.T) {
defer cleanup()
rows := sqlmock.NewRows([]string{
"user_id", "first_name", "middle_initial", "last_name", "suffix", "email_address",
"users_id", "first_name", "middle_initial", "last_name", "suffix", "email_address",
"home_address", "contact_number",
"role_id", "is_deleted", "created_at", "updated_at",
})
// Match the actual query format with all the fields
mock.ExpectQuery(`SELECT user_id, first_name, middle_initial, last_name, suffix, email_address`).
mock.ExpectQuery(`SELECT users_id, first_name, middle_initial, last_name, suffix, email_address`).
WithArgs("").
WillReturnRows(rows)
@@ -465,7 +465,7 @@ func TestGetUserAttributesDatabaseError(t *testing.T) {
mock, cleanup := setupMockDB(t)
defer cleanup()
mock.ExpectQuery("SELECT attribute_name, attribute_value, attribute_type FROM user_attributes WHERE user_id = \\?").
mock.ExpectQuery("SELECT attribute_name, attribute_value, attribute_type FROM user_attributes WHERE users_id = \\?").
WithArgs("user123").
WillReturnError(errors.New("timeout"))
+9 -9
View File
@@ -12,10 +12,10 @@ import (
func Authorize(ctx *models.AuthorizationContext) (*models.AuthorizationResult, error) {
startTime := time.Now()
log.Printf("[AuthZ Step 0] Fetching user details for userID=%s", ctx.UserID)
user, err := repository.GetUserByID(ctx.UserID)
log.Printf("[AuthZ Step 0] Fetching user details for userID=%s", ctx.UsersID)
user, err := repository.GetUserByID(ctx.UsersID)
if err != nil {
log.Printf("✗ User not found for userID=%s: %v", ctx.UserID, err)
log.Printf("✗ User not found for userID=%s: %v", ctx.UsersID, err)
return &models.AuthorizationResult{
Allowed: false,
Message: fmt.Sprintf("User not found: %v", err),
@@ -35,10 +35,10 @@ func Authorize(ctx *models.AuthorizationContext) (*models.AuthorizationResult, e
log.Printf("[AuthZ Step 1] Permission found: ID=%d, Name=%s", permission.ID, permission.PermissionName)
// Step 2: Get user attributes
log.Printf("[AuthZ Step 2] Fetching user attributes for userID=%s", ctx.UserID)
userAttrs, err := repository.GetUserAttributes(ctx.UserID)
log.Printf("[AuthZ Step 2] Fetching user attributes for userID=%s", ctx.UsersID)
userAttrs, err := repository.GetUserAttributes(ctx.UsersID)
if err != nil {
log.Printf("✗ Failed to get user attributes for userID=%s: %v", ctx.UserID, err)
log.Printf("✗ Failed to get user attributes for userID=%s: %v", ctx.UsersID, err)
return &models.AuthorizationResult{
Allowed: false,
Message: fmt.Sprintf("Failed to get user attributes: %v", err),
@@ -84,10 +84,10 @@ func Authorize(ctx *models.AuthorizationContext) (*models.AuthorizationResult, e
result.RedirectRoute = "dashboard"
result.Message = "Access granted"
log.Printf("✓ Authorization GRANTED for user=%s, resource=%s, action=%s (evaluated in %v)",
ctx.UserID, ctx.Resource, ctx.Action, time.Since(startTime))
ctx.UsersID, ctx.Resource, ctx.Action, time.Since(startTime))
} else {
log.Printf("✗ Authorization DENIED for user=%s, resource=%s, action=%s - Reason: %s (evaluated in %v)",
ctx.UserID, ctx.Resource, ctx.Action, reason, time.Since(startTime))
ctx.UsersID, ctx.Resource, ctx.Action, reason, time.Since(startTime))
result.Message = reason
}
@@ -95,7 +95,7 @@ func Authorize(ctx *models.AuthorizationContext) (*models.AuthorizationResult, e
evalTime := time.Since(startTime)
if evalTime > 100*time.Millisecond {
fmt.Printf("WARN: Slow authorization evaluation: %v for user=%s, resource=%s, action=%s\n",
evalTime, ctx.UserID, ctx.Resource, ctx.Action)
evalTime, ctx.UsersID, ctx.Resource, ctx.Action)
}
return result, nil
+16 -16
View File
@@ -32,7 +32,7 @@ func TestAuthorize_PermissionNotFound(t *testing.T) {
defer cleanup()
ctx := &models.AuthorizationContext{
UserID: "user123",
UsersID: "user123",
Resource: "nonexistent",
Action: "read",
ResourceData: make(map[string]string),
@@ -40,19 +40,19 @@ func TestAuthorize_PermissionNotFound(t *testing.T) {
}
// Mock user query
userRows := sqlmock.NewRows([]string{"user_id", "first_name", "middle_initial", "last_name", "suffix", "email_address",
userRows := sqlmock.NewRows([]string{"users_id", "first_name", "middle_initial", "last_name", "suffix", "email_address",
"home_address", "contact_number",
"role_id", "is_deleted", "created_at", "updated_at"}).
AddRow("user123", "John", "", "Doe", "", "john@example.com",
"EMP123", "Y", "Y", "123 Street", "09123456789", "device1",
1, "N", "secret", "Y", time.Now(), time.Now())
mock.ExpectQuery("SELECT user_id, first_name, middle_initial, last_name, suffix, email_address").
mock.ExpectQuery("SELECT users_id, first_name, middle_initial, last_name, suffix, email_address").
WithArgs("user123").
WillReturnRows(userRows)
// Mock permission query with role check
mock.ExpectQuery("SELECT p.id, p.permission_name, p.description, p.resource, p.action FROM permissions p INNER JOIN role_permissions rp").
mock.ExpectQuery("SELECT p.role_permissions_id, p.permission_name, p.description, p.resource, p.action FROM permissions p INNER JOIN role_permissions rp").
WithArgs("nonexistent", "read", 1).
WillReturnError(errors.New("permission not found"))
@@ -74,7 +74,7 @@ func TestAuthorize_Success(t *testing.T) {
defer cleanup()
ctx := &models.AuthorizationContext{
UserID: "user123",
UsersID: "user123",
Resource: "document",
Action: "read",
ResourceData: make(map[string]string),
@@ -82,14 +82,14 @@ func TestAuthorize_Success(t *testing.T) {
}
// Mock user query
userRows := sqlmock.NewRows([]string{"user_id", "first_name", "middle_initial", "last_name", "suffix", "email_address",
userRows := sqlmock.NewRows([]string{"users_id", "first_name", "middle_initial", "last_name", "suffix", "email_address",
"home_address", "contact_number",
"role_id", "is_deleted", "created_at", "updated_at"}).
AddRow("user123", "John", "", "Doe", "", "john@example.com",
"EMP123", "Y", "Y", "123 Street", "09123456789", "device1",
1, "N", "secret", "Y", time.Now(), time.Now())
mock.ExpectQuery("SELECT user_id, first_name, middle_initial, last_name, suffix, email_address").
mock.ExpectQuery("SELECT users_id, first_name, middle_initial, last_name, suffix, email_address").
WithArgs("user123").
WillReturnRows(userRows)
@@ -105,7 +105,7 @@ func TestAuthorize_Success(t *testing.T) {
attrRows := sqlmock.NewRows([]string{"attribute_name", "attribute_value"}).
AddRow("department", "engineering")
mock.ExpectQuery("SELECT attribute_name, attribute_value FROM user_attributes WHERE user_id = \\?").
mock.ExpectQuery("SELECT attribute_name, attribute_value FROM user_attributes WHERE users_id = \\?").
WithArgs("user123").
WillReturnRows(attrRows)
@@ -134,7 +134,7 @@ func TestAuthorize_UserAttributesError(t *testing.T) {
defer cleanup()
ctx := &models.AuthorizationContext{
UserID: "user123",
UsersID: "user123",
Resource: "document",
Action: "read",
ResourceData: make(map[string]string),
@@ -142,14 +142,14 @@ func TestAuthorize_UserAttributesError(t *testing.T) {
}
// Mock user query
userRows := sqlmock.NewRows([]string{"user_id", "first_name", "middle_initial", "last_name", "suffix", "email_address",
userRows := sqlmock.NewRows([]string{"users_id", "first_name", "middle_initial", "last_name", "suffix", "email_address",
"home_address", "contact_number",
"role_id", "is_deleted", "created_at", "updated_at"}).
AddRow("user123", "John", "", "Doe", "", "john@example.com",
"EMP123", "Y", "Y", "123 Street", "09123456789", "device1",
1, "N", "secret", "Y", time.Now(), time.Now())
mock.ExpectQuery("SELECT user_id, first_name, middle_initial, last_name, suffix, email_address").
mock.ExpectQuery("SELECT users_id, first_name, middle_initial, last_name, suffix, email_address").
WithArgs("user123").
WillReturnRows(userRows)
@@ -162,7 +162,7 @@ func TestAuthorize_UserAttributesError(t *testing.T) {
WillReturnRows(permRows)
// Mock user attributes query with error
mock.ExpectQuery("SELECT attribute_name, attribute_value FROM user_attributes WHERE user_id = \\?").
mock.ExpectQuery("SELECT attribute_name, attribute_value FROM user_attributes WHERE users_id = \\?").
WithArgs("user123").
WillReturnError(errors.New("database error"))
@@ -181,7 +181,7 @@ func TestAuthorize_PolicyAttributesError(t *testing.T) {
defer cleanup()
ctx := &models.AuthorizationContext{
UserID: "user123",
UsersID: "user123",
Resource: "document",
Action: "read",
ResourceData: make(map[string]string),
@@ -189,14 +189,14 @@ func TestAuthorize_PolicyAttributesError(t *testing.T) {
}
// Mock user query
userRows := sqlmock.NewRows([]string{"user_id", "first_name", "middle_initial", "last_name", "suffix", "email_address",
userRows := sqlmock.NewRows([]string{"users_id", "first_name", "middle_initial", "last_name", "suffix", "email_address",
"home_address", "contact_number",
"role_id", "is_deleted", "created_at", "updated_at"}).
AddRow("user123", "John", "", "Doe", "", "john@example.com",
"EMP123", "Y", "Y", "123 Street", "09123456789", "device1",
1, "N", "secret", "Y", time.Now(), time.Now())
mock.ExpectQuery("SELECT user_id, first_name, middle_initial, last_name, suffix, email_address").
mock.ExpectQuery("SELECT users_id, first_name, middle_initial, last_name, suffix, email_address").
WithArgs("user123").
WillReturnRows(userRows)
@@ -212,7 +212,7 @@ func TestAuthorize_PolicyAttributesError(t *testing.T) {
attrRows := sqlmock.NewRows([]string{"attribute_name", "attribute_value"}).
AddRow("department", "engineering")
mock.ExpectQuery("SELECT attribute_name, attribute_value FROM user_attributes WHERE user_id = \\?").
mock.ExpectQuery("SELECT attribute_name, attribute_value FROM user_attributes WHERE users_id = \\?").
WithArgs("user123").
WillReturnRows(attrRows)
+23 -19
View File
@@ -162,7 +162,7 @@ func refreshCache(s *models.CachedAuthorizationService) {
s.LastCacheRefresh = time.Now()
cacheMutex.Unlock()
log.Printf("✓ Cache refreshed: %d policy groups cached", len(policies))
// log.Printf("✓ Cache refreshed: %d policy groups cached", len(policies))
// Store policies in Redis for distributed access (non-blocking)
// Permissions are now cached on-demand with role awareness
@@ -178,7 +178,7 @@ func refreshCache(s *models.CachedAuthorizationService) {
redisclient.RDB.Set(ctx, redisKey, policiesJSON, cacheTTL)
}
log.Printf("INFO: Policy cache synced to Redis - %d policy groups", len(policies))
// log.Printf("INFO: Policy cache synced to Redis - %d policy groups", len(policies))
}()
}
}
@@ -230,13 +230,17 @@ func NewCachedAuthorizationService() *models.CachedAuthorizationService {
func AuthorizeWithCache(s *models.CachedAuthorizationService, ctx *models.AuthorizationContext) (*models.AuthorizationResult, error) {
startTime := time.Now()
log.Printf("[AuthZ Cached] Starting authorization check for user=%s, resource=%s, action=%s", ctx.UserID, ctx.Resource, ctx.Action)
log.Printf("[CACHE-ENTRY] AuthorizeWithCache() called - UsersID=%s, Resource=%s, Action=%s, RoleID=%d",
ctx.UsersID, ctx.Resource, ctx.Action, ctx.RoleID)
log.Printf("[CACHE-ENTRY] Full context: %+v", ctx)
log.Printf("[AuthZ Cached] Starting authorization check for user=%s, resource=%s, action=%s", ctx.UsersID, ctx.Resource, ctx.Action)
// Step 0: Get user to retrieve role_id (needed for role-based permission lookup)
log.Printf("[AuthZ Step 0] Fetching user details for userID=%s", ctx.UserID)
user, err := repository.GetUserByID(ctx.UserID)
log.Printf("[AuthZ Step 0] Fetching user details for userID=%s", ctx.UsersID)
user, err := repository.GetUserByID(ctx.UsersID)
if err != nil {
log.Printf("✗ User not found for userID=%s: %v", ctx.UserID, err)
log.Printf("✗ User not found for userID=%s: %v", ctx.UsersID, err)
return &models.AuthorizationResult{
Allowed: false,
Message: fmt.Sprintf("User not found: %v", err),
@@ -246,34 +250,34 @@ func AuthorizeWithCache(s *models.CachedAuthorizationService, ctx *models.Author
// Step 1: Check if the user's role has the permission (not just if permission exists)
// Use role-aware cache key: roleID:resource:action
cacheKey := fmt.Sprintf("%d:%s:%s", user.RoleID, ctx.Resource, ctx.Action)
cacheKey := fmt.Sprintf("%d:%s:%s", ctx.RoleID, ctx.Resource, ctx.Action)
log.Printf("[AuthZ Step 1] Looking up permission in cache with role: %s", cacheKey)
permission, exists := getPermissionFromCache(s, cacheKey)
if !exists {
// Cache miss - try database lookup with role check
log.Printf("[AuthZ Step 1] Cache miss - querying database for role_id=%d, resource=%s, action=%s", user.RoleID, ctx.Resource, ctx.Action)
permission, err = repository.GetPermissionByResourceActionAndRole(ctx.Resource, ctx.Action, user.RoleID)
log.Printf("[AuthZ Step 1] Cache miss - querying database for role_id=%d, resource=%s, action=%s", ctx.RoleID, ctx.Resource, ctx.Action)
permission, err = repository.GetPermissionByResourceActionAndRole(ctx.Resource, ctx.Action, ctx.RoleID)
if err != nil {
log.Printf("✗ Permission not found or not granted to role_id=%d for resource=%s, action=%s: %v", user.RoleID, ctx.Resource, ctx.Action, err)
log.Printf("✗ [AuthZ Step 1] Permission not found or not granted to role_id=%d for resource=%s, action=%s: %v", ctx.RoleID, ctx.Resource, ctx.Action, err)
return &models.AuthorizationResult{
Allowed: false,
Message: "Permission not granted to your role",
}, nil
}
log.Printf("[AuthZ Step 1] Permission found in DB: ID=%d, Name=%s", permission.ID, permission.PermissionName)
log.Printf("[AuthZ Step 1] Permission found in DB: ID=%d, Name=%s", permission.ID, permission.PermissionName)
// Cache the result for future use
storePermissionInCache(s, cacheKey, permission)
} else {
log.Printf("[AuthZ Step 1] Permission found in cache: ID=%d, Name=%s", permission.ID, permission.PermissionName)
log.Printf("[AuthZ Step 1] Permission found in cache: ID=%d, Name=%s", permission.ID, permission.PermissionName)
}
// Step 2: Get user attributes (with distributed cache)
log.Printf("[AuthZ Step 2] Fetching user attributes for userID=%s", ctx.UserID)
userAttrs, err := getCachedUserAttributes(s, ctx.UserID)
log.Printf("[AuthZ Step 2] Fetching user attributes for userID=%s", ctx.UsersID)
userAttrs, err := getCachedUserAttributes(s, ctx.UsersID)
if err != nil {
log.Printf("✗ Failed to get user attributes for userID=%s: %v", ctx.UserID, err)
log.Printf("✗ Failed to get user attributes for userID=%s: %v", ctx.UsersID, err)
return &models.AuthorizationResult{
Allowed: false,
Message: "Failed to get user attributes",
@@ -311,11 +315,11 @@ func AuthorizeWithCache(s *models.CachedAuthorizationService, ctx *models.Author
if allowed {
result.Message = "Access granted"
log.Printf("✓ Authorization GRANTED for user=%s, resource=%s, action=%s (evaluated in %v)",
ctx.UserID, ctx.Resource, ctx.Action, time.Since(startTime))
ctx.UsersID, ctx.Resource, ctx.Action, time.Since(startTime))
} else {
result.Message = reason
log.Printf("✗ Authorization DENIED for user=%s, resource=%s, action=%s - Reason: %s (evaluated in %v)",
ctx.UserID, ctx.Resource, ctx.Action, reason, time.Since(startTime))
ctx.UsersID, ctx.Resource, ctx.Action, reason, time.Since(startTime))
}
// Performance monitoring
@@ -323,12 +327,12 @@ func AuthorizeWithCache(s *models.CachedAuthorizationService, ctx *models.Author
if evalTime < 50*time.Millisecond {
log.Print("Cached authorization evaluation time: ", evalTime,
" for user=", ctx.UserID, ", resource=", ctx.Resource, ", action=", ctx.Action)
" for user=", ctx.UsersID, ", resource=", ctx.Resource, ", action=", ctx.Action)
}
if evalTime > 50*time.Millisecond {
log.Print("WARN: Slow cached authorization evaluation: ", evalTime,
" for user=", ctx.UserID, ", resource=", ctx.Resource, ", action=", ctx.Action)
" for user=", ctx.UsersID, ", resource=", ctx.Resource, ", action=", ctx.Action)
}
return result, nil
+8 -8
View File
@@ -104,7 +104,7 @@ func TestGetCachedUserAttributes_CacheMiss(t *testing.T) {
attrRows := sqlmock.NewRows([]string{"attribute_name", "attribute_value"}).
AddRow("department", "engineering")
mock.ExpectQuery("SELECT attribute_name, attribute_value FROM user_attributes WHERE user_id = \\?").
mock.ExpectQuery("SELECT attribute_name, attribute_value FROM user_attributes WHERE users_id = \\?").
WithArgs("user123").
WillReturnRows(attrRows)
@@ -219,14 +219,14 @@ func TestAuthorizeWithCache_Success(t *testing.T) {
service.PolicyCache[1] = []models.PolicyAttribute{}
// Mock user query (needed to get role_id)
userRows := sqlmock.NewRows([]string{"user_id", "first_name", "middle_initial", "last_name", "suffix", "email_address",
userRows := sqlmock.NewRows([]string{"users_id", "first_name", "middle_initial", "last_name", "suffix", "email_address",
"home_address", "contact_number",
"role_id", "is_deleted", "created_at", "updated_at"}).
AddRow("user123", "John", "", "Doe", "", "john@example.com",
"EMP123", "Y", "Y", "123 Street", "09123456789", "device1",
1, "N", "secret", "Y", time.Now(), time.Now())
mock.ExpectQuery("SELECT user_id, first_name, middle_initial, last_name, suffix, email_address").
mock.ExpectQuery("SELECT users_id, first_name, middle_initial, last_name, suffix, email_address").
WithArgs("user123").
WillReturnRows(userRows)
@@ -234,12 +234,12 @@ func TestAuthorizeWithCache_Success(t *testing.T) {
attrRows := sqlmock.NewRows([]string{"attribute_name", "attribute_value"}).
AddRow("department", "engineering")
mock.ExpectQuery("SELECT attribute_name, attribute_value FROM user_attributes WHERE user_id = \\?").
mock.ExpectQuery("SELECT attribute_name, attribute_value FROM user_attributes WHERE users_id = \\?").
WithArgs("user123").
WillReturnRows(attrRows)
ctx := &models.AuthorizationContext{
UserID: "user123",
UsersID: "user123",
Resource: "document",
Action: "read",
ResourceData: make(map[string]string),
@@ -268,20 +268,20 @@ func TestAuthorizeWithCache_PermissionNotFound(t *testing.T) {
}
ctx := &models.AuthorizationContext{
UserID: "user123",
UsersID: "user123",
Resource: "nonexistent",
Action: "read",
}
// Mock user query
userRows := sqlmock.NewRows([]string{"user_id", "first_name", "middle_initial", "last_name", "suffix", "email_address",
userRows := sqlmock.NewRows([]string{"users_id", "first_name", "middle_initial", "last_name", "suffix", "email_address",
"home_address", "contact_number",
"role_id", "is_deleted", "created_at", "updated_at"}).
AddRow("user123", "John", "", "Doe", "", "john@example.com",
"EMP123", "Y", "Y", "123 Street", "09123456789", "device1",
1, "N", "secret", "Y", time.Now(), time.Now())
mock.ExpectQuery("SELECT user_id, first_name, middle_initial, last_name, suffix, email_address").
mock.ExpectQuery("SELECT users_id, first_name, middle_initial, last_name, suffix, email_address").
WithArgs("user123").
WillReturnRows(userRows)
+1 -2
View File
@@ -135,8 +135,7 @@ func evaluatePolicy(policyAttribute models.PolicyAttribute, ctx *models.Authoriz
log.Print("Role ID!!!!!: ", ctx.RoleID)
if policyAttribute.AttributeType == "user" &&
policyAttribute.AttributeName == "region" &&
(ctx.RoleID == "1" || ctx.RoleID == "2" || ctx.RoleID == "Super Admin" ||
ctx.RoleID == "System Admin") {
(ctx.RoleID == 1 || ctx.RoleID == 2) {
fmt.Printf("[POLICY EVALUATION] Type: %s, Attribute: %s\n", policyAttribute.AttributeType, policyAttribute.AttributeName)
fmt.Printf(" Skipped for roleID: %s (Super | System Admin bypass)\n\n", ctx.RoleID)
return true, ""
+10 -10
View File
@@ -657,7 +657,7 @@ func TestEvaluatePoliciesComplexConditions(t *testing.T) {
func TestResolveVariablesAllAttributeTypes(t *testing.T) {
ctx := &models.AuthorizationContext{
UserID: "user123",
UsersID: "user123",
Resource: "document",
Action: "read",
UserAttributes: map[string]string{
@@ -719,7 +719,7 @@ func TestEvaluatePolicies_UserRegionMatchesResourceRegion(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := &models.AuthorizationContext{
UserID: "U0000000001",
UsersID: "U0000000001",
Resource: "personnel",
Action: "assign_role",
UserAttributes: map[string]string{
@@ -752,7 +752,7 @@ func TestEvaluatePolicies_MissingResourceAttribute(t *testing.T) {
// The policy should fail because the placeholder cannot be resolved
ctx := &models.AuthorizationContext{
UserID: "U0000000001",
UsersID: "U0000000001",
Resource: "personnel",
Action: "assign_role",
UserAttributes: map[string]string{
@@ -880,7 +880,7 @@ func TestEvaluatePolicies_RegionBypassForAdminRoles(t *testing.T) {
tests := []struct {
name string
roleID string
roleID int
userRegion string
resourceRegion string
shouldBeAllowed bool
@@ -888,7 +888,7 @@ func TestEvaluatePolicies_RegionBypassForAdminRoles(t *testing.T) {
}{
{
name: "roleID 1 bypasses region check",
roleID: "1",
roleID: 1,
userRegion: "02",
resourceRegion: "01",
shouldBeAllowed: true,
@@ -896,7 +896,7 @@ func TestEvaluatePolicies_RegionBypassForAdminRoles(t *testing.T) {
},
{
name: "roleID 2 bypasses region check",
roleID: "2",
roleID: 2,
userRegion: "03",
resourceRegion: "01",
shouldBeAllowed: true,
@@ -904,7 +904,7 @@ func TestEvaluatePolicies_RegionBypassForAdminRoles(t *testing.T) {
},
{
name: "other roleID respects region check",
roleID: "3",
roleID: 3,
userRegion: "02",
resourceRegion: "01",
shouldBeAllowed: false,
@@ -912,7 +912,7 @@ func TestEvaluatePolicies_RegionBypassForAdminRoles(t *testing.T) {
},
{
name: "Super Admin role bypasses region check",
roleID: "Super Admin",
roleID: 1,
userRegion: "02",
resourceRegion: "01",
shouldBeAllowed: true,
@@ -920,7 +920,7 @@ func TestEvaluatePolicies_RegionBypassForAdminRoles(t *testing.T) {
},
{
name: "Admin role does not bypass region check",
roleID: "Admin",
roleID: 2,
userRegion: "03",
resourceRegion: "01",
shouldBeAllowed: false,
@@ -931,7 +931,7 @@ func TestEvaluatePolicies_RegionBypassForAdminRoles(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := &models.AuthorizationContext{
UserID: "U0000000001",
UsersID: "U0000000001",
Resource: "personnel",
Action: "assign_role",
RoleID: tt.roleID,