ae1831e61f
- 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.
139 lines
4.9 KiB
Go
139 lines
4.9 KiB
Go
package models
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strconv"
|
|
"time"
|
|
)
|
|
|
|
// Permission represents a system permission
|
|
type Permission struct {
|
|
ID int `json:"id" db:"id"`
|
|
PermissionName string `json:"permission_name" db:"permission_name"`
|
|
Description string `json:"description" db:"description"`
|
|
Resource string `json:"resource" db:"resource"`
|
|
Action string `json:"action" db:"action"`
|
|
}
|
|
|
|
// RolePermission represents the junction table linking roles to permissions
|
|
type RolePermission struct {
|
|
ID int `json:"id" db:"id"`
|
|
RoleID int `json:"role_id" db:"role_id"`
|
|
PermissionID int `json:"permission_id" db:"permission_id"`
|
|
}
|
|
|
|
// PolicyAttribute represents an ABAC policy attribute/constraint
|
|
type PolicyAttribute struct {
|
|
ID int `json:"id" db:"id"`
|
|
AttributeName string `json:"attribute_name" db:"attribute_name"`
|
|
AttributeType string `json:"attribute_type" db:"attribute_type"` // user, resource, environment
|
|
Comparison string `json:"comparison" db:"comparison"` // =, !=, >, <, >=, <=, IN, CONTAINS
|
|
AttributeValue string `json:"attribute_value" db:"attribute_value"`
|
|
PermissionID int `json:"permission_id" db:"permission_id"`
|
|
}
|
|
|
|
// UserAttribute represents user-specific attributes for ABAC
|
|
type UserAttribute struct {
|
|
ID int `json:"id" db:"id"`
|
|
UserID string `json:"user_id" db:"user_id"`
|
|
AttributeName string `json:"attribute_name" db:"attribute_name"`
|
|
AttributeValue string `json:"attribute_value" db:"attribute_value"`
|
|
}
|
|
|
|
// User represents a system user
|
|
type User struct {
|
|
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"`
|
|
Suffix string `json:"suffix" db:"suffix"`
|
|
EmailAddress string `json:"email_address" db:"email_address"`
|
|
|
|
HomeAddress string `json:"home_address" db:"home_address"`
|
|
ContactNumber string `json:"contact_number" db:"contact_number"`
|
|
|
|
RoleID int `json:"role_id" db:"role_id"`
|
|
IsDeleted string `json:"is_deleted" db:"is_deleted"`
|
|
|
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
|
}
|
|
|
|
// AuthorizationContext holds all context needed for authorization decisions
|
|
type AuthorizationContext struct {
|
|
UsersID string `json:"users_id"`
|
|
Resource string `json:"resource"`
|
|
Action string `json:"action"`
|
|
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"`
|
|
RedirectRoute string `json:"redirect_route,omitempty"` // Optional redirect route
|
|
Message string `json:"message,omitempty"` // Optional message
|
|
}
|
|
|
|
// CachedAuthorizationService adds caching layer to authorization
|
|
type CachedAuthorizationService struct {
|
|
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"`
|
|
}
|