feat(authz): redirect deleted accounts to /login
detect soft-deleted users during authorization lookup return a dedicated deleted-user result from auth services redirect deleted accounts to /login in the handler update repository, service, and handler tests for the new flow
This commit is contained in:
+19
-14
@@ -126,24 +126,29 @@ func AuthorizeHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return result
|
writeAuthorizationResponse(w, r, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeAuthorizationResponse(w http.ResponseWriter, r *http.Request, result *models.AuthorizationResult) {
|
||||||
|
if result.RedirectRoute != "" {
|
||||||
|
log.Printf("✗ [Handler] Authorization redirect to %s (reason: %s)", result.RedirectRoute, result.Message)
|
||||||
|
http.Redirect(w, r, result.RedirectRoute, http.StatusFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"allowed": result.Allowed,
|
||||||
|
"reason": result.Message,
|
||||||
|
}
|
||||||
|
|
||||||
if result.Allowed {
|
if result.Allowed {
|
||||||
log.Printf("✓ [Handler] Authorization ALLOWED - Returning 200 OK to client")
|
log.Printf("✓ [Handler] Authorization ALLOWED - Returning 200 OK to client")
|
||||||
// Return sabat matching Authorizationsabat model for client compatibility
|
|
||||||
response := map[string]interface{}{
|
|
||||||
"allowed": result.Allowed,
|
|
||||||
"reason": result.Message,
|
|
||||||
}
|
|
||||||
sabat.RespondWithJSON(w, http.StatusOK, response)
|
sabat.RespondWithJSON(w, http.StatusOK, response)
|
||||||
} else {
|
return
|
||||||
log.Printf("✗ [Handler] Authorization DENIED - Returning 403 Forbidden to client (reason: %s)", result.Message)
|
|
||||||
// Return sabat matching Authorizationsabat model for client compatibility
|
|
||||||
response := map[string]interface{}{
|
|
||||||
"allowed": result.Allowed,
|
|
||||||
"reason": result.Message,
|
|
||||||
}
|
|
||||||
sabat.RespondWithJSON(w, http.StatusForbidden, response)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Printf("✗ [Handler] Authorization DENIED - Returning 403 Forbidden to client (reason: %s)", result.Message)
|
||||||
|
sabat.RespondWithJSON(w, http.StatusForbidden, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
func collectClaimRoles(claims *models.Claims) []int {
|
func collectClaimRoles(claims *models.Claims) []int {
|
||||||
|
|||||||
@@ -10,6 +10,24 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestWriteAuthorizationResponseRedirectsToLogin(t *testing.T) {
|
||||||
|
req := httptest.NewRequest("POST", AuthCheckEndpoint, nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
writeAuthorizationResponse(w, req, &models.AuthorizationResult{
|
||||||
|
Allowed: false,
|
||||||
|
RedirectRoute: "/login",
|
||||||
|
Message: "Account deleted",
|
||||||
|
})
|
||||||
|
|
||||||
|
if w.Code != http.StatusFound {
|
||||||
|
t.Fatalf("Expected status %d, got %d", http.StatusFound, w.Code)
|
||||||
|
}
|
||||||
|
if location := w.Header().Get("Location"); location != "/login" {
|
||||||
|
t.Fatalf("Expected redirect location /login, got %q", location)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestInitAuthService(t *testing.T) {
|
func TestInitAuthService(t *testing.T) {
|
||||||
// Test that InitAuthService can be called
|
// Test that InitAuthService can be called
|
||||||
// It may panic if DB is not available, which is expected behavior
|
// It may panic if DB is not available, which is expected behavior
|
||||||
|
|||||||
@@ -4,10 +4,13 @@ import (
|
|||||||
"authorization/db"
|
"authorization/db"
|
||||||
"authorization/models"
|
"authorization/models"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ErrUserDeleted = errors.New("user is deleted")
|
||||||
|
|
||||||
func GetPermissionByResourceActionAndRole(resource, action string, roleID int) (*models.Permission, error) {
|
func GetPermissionByResourceActionAndRole(resource, action string, roleID int) (*models.Permission, error) {
|
||||||
log.Printf("[Repository] GetPermissionByResourceActionAndRole - resource=%s, action=%s, roleID=%d",
|
log.Printf("[Repository] GetPermissionByResourceActionAndRole - resource=%s, action=%s, roleID=%d",
|
||||||
resource, action, roleID)
|
resource, action, roleID)
|
||||||
@@ -128,13 +131,13 @@ func GetUserByID(userID string) (*models.User, error) {
|
|||||||
log.Printf("[Repository] GetUserByID - userID=%s", userID)
|
log.Printf("[Repository] GetUserByID - userID=%s", userID)
|
||||||
|
|
||||||
query := `
|
query := `
|
||||||
SELECT users_id, email_address
|
SELECT users_id, email_address, role_id, is_deleted
|
||||||
FROM uess_user_management.users
|
FROM uess_user_management.users
|
||||||
WHERE users_id = ?
|
WHERE users_id = ?
|
||||||
`
|
`
|
||||||
|
|
||||||
var user models.User
|
var user models.User
|
||||||
err := db.DB.QueryRow(query, userID).Scan(&user.UsersID, &user.EmailAddress)
|
err := db.DB.QueryRow(query, userID).Scan(&user.UsersID, &user.EmailAddress, &user.RoleID, &user.IsDeleted)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
log.Printf("[Repository] ✗ User not found: %s", userID)
|
log.Printf("[Repository] ✗ User not found: %s", userID)
|
||||||
@@ -144,6 +147,11 @@ func GetUserByID(userID string) (*models.User, error) {
|
|||||||
return nil, fmt.Errorf("error querying user: %w", err)
|
return nil, fmt.Errorf("error querying user: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if user.IsDeleted == "1" {
|
||||||
|
log.Printf("[Repository] ✗ User is deleted: %s", userID)
|
||||||
|
return nil, ErrUserDeleted
|
||||||
|
}
|
||||||
|
|
||||||
log.Printf("[Repository] ✓ User found: UsersID=%s", user.UsersID)
|
log.Printf("[Repository] ✓ User found: UsersID=%s", user.UsersID)
|
||||||
return &user, nil
|
return &user, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,8 +104,8 @@ func TestGetUserByIDSuccess(t *testing.T) {
|
|||||||
mock, cleanup := setupMockDB(t)
|
mock, cleanup := setupMockDB(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
rows := sqlmock.NewRows([]string{"users_id", "email_address"}).
|
rows := sqlmock.NewRows([]string{"users_id", "email_address", "role_id", "is_deleted"}).
|
||||||
AddRow("user123", "john@example.com")
|
AddRow("user123", "john@example.com", 7, "0")
|
||||||
|
|
||||||
mock.ExpectQuery("SELECT users_id, email_address").
|
mock.ExpectQuery("SELECT users_id, email_address").
|
||||||
WithArgs("user123").
|
WithArgs("user123").
|
||||||
@@ -125,6 +125,9 @@ func TestGetUserByIDSuccess(t *testing.T) {
|
|||||||
if user.EmailAddress != "john@example.com" {
|
if user.EmailAddress != "john@example.com" {
|
||||||
t.Errorf("Expected EmailAddress 'john@example.com', got '%s'", user.EmailAddress)
|
t.Errorf("Expected EmailAddress 'john@example.com', got '%s'", user.EmailAddress)
|
||||||
}
|
}
|
||||||
|
if user.RoleID != 7 {
|
||||||
|
t.Errorf("Expected RoleID 7, got %d", user.RoleID)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetUserByIDNotFound(t *testing.T) {
|
func TestGetUserByIDNotFound(t *testing.T) {
|
||||||
@@ -145,6 +148,27 @@ func TestGetUserByIDNotFound(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetUserByIDDeletedUserFilteredOut(t *testing.T) {
|
||||||
|
mock, cleanup := setupMockDB(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
rows := sqlmock.NewRows([]string{"users_id", "email_address", "role_id", "is_deleted"}).
|
||||||
|
AddRow("deleted-user", "deleted@example.com", 3, "1")
|
||||||
|
|
||||||
|
mock.ExpectQuery("SELECT users_id, email_address").
|
||||||
|
WithArgs("deleted-user").
|
||||||
|
WillReturnRows(rows)
|
||||||
|
|
||||||
|
user, err := GetUserByID("deleted-user")
|
||||||
|
|
||||||
|
if !errors.Is(err, ErrUserDeleted) {
|
||||||
|
t.Errorf("Expected ErrUserDeleted, got %v", err)
|
||||||
|
}
|
||||||
|
if user != nil {
|
||||||
|
t.Error("Expected nil user for deleted user")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetAllPermissionsSuccess(t *testing.T) {
|
func TestGetAllPermissionsSuccess(t *testing.T) {
|
||||||
mock, cleanup := setupMockDB(t)
|
mock, cleanup := setupMockDB(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package services
|
|||||||
import (
|
import (
|
||||||
"authorization/models"
|
"authorization/models"
|
||||||
"authorization/repository"
|
"authorization/repository"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
@@ -15,6 +16,14 @@ func Authorize(ctx *models.AuthorizationContext) (*models.AuthorizationResult, e
|
|||||||
log.Printf("[AuthZ Step 0] Fetching user details for userID=%s", ctx.UsersID)
|
log.Printf("[AuthZ Step 0] Fetching user details for userID=%s", ctx.UsersID)
|
||||||
user, err := repository.GetUserByID(ctx.UsersID)
|
user, err := repository.GetUserByID(ctx.UsersID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, repository.ErrUserDeleted) {
|
||||||
|
log.Printf("✗ Deleted user attempted authorization for userID=%s", ctx.UsersID)
|
||||||
|
return &models.AuthorizationResult{
|
||||||
|
Allowed: false,
|
||||||
|
RedirectRoute: "/login",
|
||||||
|
Message: "Account deleted",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
log.Printf("✗ User not found for userID=%s: %v", ctx.UsersID, err)
|
log.Printf("✗ User not found for userID=%s: %v", ctx.UsersID, err)
|
||||||
return &models.AuthorizationResult{
|
return &models.AuthorizationResult{
|
||||||
Allowed: false,
|
Allowed: false,
|
||||||
|
|||||||
+53
-20
@@ -40,15 +40,15 @@ func TestAuthorize_PermissionNotFound(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Mock user query
|
// Mock user query
|
||||||
userRows := sqlmock.NewRows([]string{"users_id", "email_address"}).
|
userRows := sqlmock.NewRows([]string{"users_id", "email_address", "role_id", "is_deleted"}).
|
||||||
AddRow("user123", "john@example.com")
|
AddRow("user123", "john@example.com", 1, "0")
|
||||||
|
|
||||||
mock.ExpectQuery("SELECT users_id, email_address").
|
mock.ExpectQuery("SELECT users_id, email_address").
|
||||||
WithArgs("user123").
|
WithArgs("user123").
|
||||||
WillReturnRows(userRows)
|
WillReturnRows(userRows)
|
||||||
|
|
||||||
// Mock permission query with role check
|
// Mock permission query with role check
|
||||||
mock.ExpectQuery("SELECT p.permissions_id, p.permission_name, p.description, p.resource, p.action FROM permissions p INNER JOIN role_permissions rp").
|
mock.ExpectQuery("SELECT p.permissions_id, p.permission_name, p.description, p.resource, p.action FROM uess_user_management.permissions p INNER JOIN uess_user_management.role_permissions rp").
|
||||||
WithArgs("nonexistent", "read", 1).
|
WithArgs("nonexistent", "read", 1).
|
||||||
WillReturnError(errors.New("permission not found"))
|
WillReturnError(errors.New("permission not found"))
|
||||||
|
|
||||||
@@ -79,8 +79,8 @@ func TestAuthorize_Success(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Mock user query
|
// Mock user query
|
||||||
userRows := sqlmock.NewRows([]string{"users_id", "email_address"}).
|
userRows := sqlmock.NewRows([]string{"users_id", "email_address", "role_id", "is_deleted"}).
|
||||||
AddRow("user123", "john@example.com")
|
AddRow("user123", "john@example.com", 1, "0")
|
||||||
|
|
||||||
mock.ExpectQuery("SELECT users_id, email_address").
|
mock.ExpectQuery("SELECT users_id, email_address").
|
||||||
WithArgs("user123").
|
WithArgs("user123").
|
||||||
@@ -90,7 +90,7 @@ func TestAuthorize_Success(t *testing.T) {
|
|||||||
permRows := sqlmock.NewRows([]string{"id", "permission_name", "description", "resource", "action"}).
|
permRows := sqlmock.NewRows([]string{"id", "permission_name", "description", "resource", "action"}).
|
||||||
AddRow(1, "read_document", "Read document permission", "document", "read")
|
AddRow(1, "read_document", "Read document permission", "document", "read")
|
||||||
|
|
||||||
mock.ExpectQuery("SELECT p.permissions_id, p.permission_name, p.description, p.resource, p.action FROM permissions p INNER JOIN role_permissions rp").
|
mock.ExpectQuery("SELECT p.permissions_id, p.permission_name, p.description, p.resource, p.action FROM uess_user_management.permissions p INNER JOIN uess_user_management.role_permissions rp").
|
||||||
WithArgs("document", "read", 1).
|
WithArgs("document", "read", 1).
|
||||||
WillReturnRows(permRows)
|
WillReturnRows(permRows)
|
||||||
|
|
||||||
@@ -98,14 +98,14 @@ func TestAuthorize_Success(t *testing.T) {
|
|||||||
attrRows := sqlmock.NewRows([]string{"attribute_name", "attribute_value"}).
|
attrRows := sqlmock.NewRows([]string{"attribute_name", "attribute_value"}).
|
||||||
AddRow("department", "engineering")
|
AddRow("department", "engineering")
|
||||||
|
|
||||||
mock.ExpectQuery("SELECT attribute_name, attribute_value FROM user_attributes WHERE users_id = \\?").
|
mock.ExpectQuery("SELECT attribute_name, attribute_value FROM uess_user_management.user_attributes WHERE users_id = \\?").
|
||||||
WithArgs("user123").
|
WithArgs("user123", "user123").
|
||||||
WillReturnRows(attrRows)
|
WillReturnRows(attrRows)
|
||||||
|
|
||||||
// Mock policy attributes query (empty for this test)
|
// Mock policy attributes query (empty for this test)
|
||||||
policyRows := sqlmock.NewRows([]string{"id", "attribute_name", "attribute_type", "comparison", "attribute_value", "permission_id"})
|
policyRows := sqlmock.NewRows([]string{"id", "attribute_name", "attribute_type", "comparison", "attribute_value", "permission_id"})
|
||||||
|
|
||||||
mock.ExpectQuery("SELECT id, attribute_name, attribute_type, comparison, attribute_value, permission_id FROM policy_attributes WHERE permission_id = \\?").
|
mock.ExpectQuery("SELECT id, attribute_name, attribute_type, comparison, attribute_value, permission_id FROM uess_user_management.policy_attributes WHERE permission_id = \\?").
|
||||||
WithArgs(1).
|
WithArgs(1).
|
||||||
WillReturnRows(policyRows)
|
WillReturnRows(policyRows)
|
||||||
|
|
||||||
@@ -136,8 +136,8 @@ func TestAuthorize_UserAttributesError(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Mock user query
|
// Mock user query
|
||||||
userRows := sqlmock.NewRows([]string{"users_id", "email_address"}).
|
userRows := sqlmock.NewRows([]string{"users_id", "email_address", "role_id", "is_deleted"}).
|
||||||
AddRow("user123", "john@example.com")
|
AddRow("user123", "john@example.com", 1, "0")
|
||||||
|
|
||||||
mock.ExpectQuery("SELECT users_id, email_address").
|
mock.ExpectQuery("SELECT users_id, email_address").
|
||||||
WithArgs("user123").
|
WithArgs("user123").
|
||||||
@@ -147,13 +147,13 @@ func TestAuthorize_UserAttributesError(t *testing.T) {
|
|||||||
permRows := sqlmock.NewRows([]string{"id", "permission_name", "description", "resource", "action"}).
|
permRows := sqlmock.NewRows([]string{"id", "permission_name", "description", "resource", "action"}).
|
||||||
AddRow(1, "read_document", "Read document permission", "document", "read")
|
AddRow(1, "read_document", "Read document permission", "document", "read")
|
||||||
|
|
||||||
mock.ExpectQuery("SELECT p.permissions_id, p.permission_name, p.description, p.resource, p.action FROM permissions p INNER JOIN role_permissions rp").
|
mock.ExpectQuery("SELECT p.permissions_id, p.permission_name, p.description, p.resource, p.action FROM uess_user_management.permissions p INNER JOIN uess_user_management.role_permissions rp").
|
||||||
WithArgs("document", "read", 1).
|
WithArgs("document", "read", 1).
|
||||||
WillReturnRows(permRows)
|
WillReturnRows(permRows)
|
||||||
|
|
||||||
// Mock user attributes query with error
|
// Mock user attributes query with error
|
||||||
mock.ExpectQuery("SELECT attribute_name, attribute_value FROM user_attributes WHERE users_id = \\?").
|
mock.ExpectQuery("SELECT attribute_name, attribute_value FROM uess_user_management.user_attributes WHERE users_id = \\?").
|
||||||
WithArgs("user123").
|
WithArgs("user123", "user123").
|
||||||
WillReturnError(errors.New("database error"))
|
WillReturnError(errors.New("database error"))
|
||||||
|
|
||||||
result, err := Authorize(ctx)
|
result, err := Authorize(ctx)
|
||||||
@@ -180,8 +180,8 @@ func TestAuthorize_PolicyAttributesError(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Mock user query
|
// Mock user query
|
||||||
userRows := sqlmock.NewRows([]string{"users_id", "email_address"}).
|
userRows := sqlmock.NewRows([]string{"users_id", "email_address", "role_id", "is_deleted"}).
|
||||||
AddRow("user123", "john@example.com")
|
AddRow("user123", "john@example.com", 1, "0")
|
||||||
|
|
||||||
mock.ExpectQuery("SELECT users_id, email_address").
|
mock.ExpectQuery("SELECT users_id, email_address").
|
||||||
WithArgs("user123").
|
WithArgs("user123").
|
||||||
@@ -191,7 +191,7 @@ func TestAuthorize_PolicyAttributesError(t *testing.T) {
|
|||||||
permRows := sqlmock.NewRows([]string{"id", "permission_name", "description", "resource", "action"}).
|
permRows := sqlmock.NewRows([]string{"id", "permission_name", "description", "resource", "action"}).
|
||||||
AddRow(1, "read_document", "Read document permission", "document", "read")
|
AddRow(1, "read_document", "Read document permission", "document", "read")
|
||||||
|
|
||||||
mock.ExpectQuery("SELECT p.permissions_id, p.permission_name, p.description, p.resource, p.action FROM permissions p INNER JOIN role_permissions rp").
|
mock.ExpectQuery("SELECT p.permissions_id, p.permission_name, p.description, p.resource, p.action FROM uess_user_management.permissions p INNER JOIN uess_user_management.role_permissions rp").
|
||||||
WithArgs("document", "read", 1).
|
WithArgs("document", "read", 1).
|
||||||
WillReturnRows(permRows)
|
WillReturnRows(permRows)
|
||||||
|
|
||||||
@@ -199,12 +199,12 @@ func TestAuthorize_PolicyAttributesError(t *testing.T) {
|
|||||||
attrRows := sqlmock.NewRows([]string{"attribute_name", "attribute_value"}).
|
attrRows := sqlmock.NewRows([]string{"attribute_name", "attribute_value"}).
|
||||||
AddRow("department", "engineering")
|
AddRow("department", "engineering")
|
||||||
|
|
||||||
mock.ExpectQuery("SELECT attribute_name, attribute_value FROM user_attributes WHERE users_id = \\?").
|
mock.ExpectQuery("SELECT attribute_name, attribute_value FROM uess_user_management.user_attributes WHERE users_id = \\?").
|
||||||
WithArgs("user123").
|
WithArgs("user123", "user123").
|
||||||
WillReturnRows(attrRows)
|
WillReturnRows(attrRows)
|
||||||
|
|
||||||
// Mock policy attributes query with error
|
// Mock policy attributes query with error
|
||||||
mock.ExpectQuery("SELECT id, attribute_name, attribute_type, comparison, attribute_value, permission_id FROM policy_attributes WHERE permission_id = \\?").
|
mock.ExpectQuery("SELECT id, attribute_name, attribute_type, comparison, attribute_value, permission_id FROM uess_user_management.policy_attributes WHERE permission_id = \\?").
|
||||||
WithArgs(1).
|
WithArgs(1).
|
||||||
WillReturnError(errors.New("database error"))
|
WillReturnError(errors.New("database error"))
|
||||||
|
|
||||||
@@ -252,3 +252,36 @@ func TestGetRoleCandidates_Priority(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAuthorize_DeletedUserRedirectsToLogin(t *testing.T) {
|
||||||
|
mock, cleanup := setupMockDB(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
ctx := &models.AuthorizationContext{
|
||||||
|
UsersID: "deleted-user",
|
||||||
|
Resource: "document",
|
||||||
|
Action: "read",
|
||||||
|
RoleID: 1,
|
||||||
|
ResourceData: make(map[string]string),
|
||||||
|
Environment: make(map[string]string),
|
||||||
|
}
|
||||||
|
|
||||||
|
userRows := sqlmock.NewRows([]string{"users_id", "email_address", "role_id", "is_deleted"}).
|
||||||
|
AddRow("deleted-user", "deleted@example.com", 1, "1")
|
||||||
|
|
||||||
|
mock.ExpectQuery("SELECT users_id, email_address, role_id, is_deleted").
|
||||||
|
WithArgs("deleted-user").
|
||||||
|
WillReturnRows(userRows)
|
||||||
|
|
||||||
|
result, err := Authorize(ctx)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expected no error, got %v", err)
|
||||||
|
}
|
||||||
|
if result.Allowed {
|
||||||
|
t.Error("Expected access denied")
|
||||||
|
}
|
||||||
|
if result.RedirectRoute != "/login" {
|
||||||
|
t.Errorf("Expected redirect to /login, got %q", result.RedirectRoute)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"authorization/repository"
|
"authorization/repository"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -240,6 +241,14 @@ func AuthorizeWithCache(s *models.CachedAuthorizationService, ctx *models.Author
|
|||||||
log.Printf("[AuthZ Step 0] Fetching user details for userID=%s", ctx.UsersID)
|
log.Printf("[AuthZ Step 0] Fetching user details for userID=%s", ctx.UsersID)
|
||||||
user, err := repository.GetUserByID(ctx.UsersID)
|
user, err := repository.GetUserByID(ctx.UsersID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, repository.ErrUserDeleted) {
|
||||||
|
log.Printf("✗ Deleted user attempted authorization for userID=%s", ctx.UsersID)
|
||||||
|
return &models.AuthorizationResult{
|
||||||
|
Allowed: false,
|
||||||
|
RedirectRoute: "/login",
|
||||||
|
Message: "Account deleted",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
log.Printf("✗ User not found for userID=%s: %v", ctx.UsersID, err)
|
log.Printf("✗ User not found for userID=%s: %v", ctx.UsersID, err)
|
||||||
return &models.AuthorizationResult{
|
return &models.AuthorizationResult{
|
||||||
Allowed: false,
|
Allowed: false,
|
||||||
|
|||||||
@@ -104,8 +104,8 @@ func TestGetCachedUserAttributes_CacheMiss(t *testing.T) {
|
|||||||
attrRows := sqlmock.NewRows([]string{"attribute_name", "attribute_value"}).
|
attrRows := sqlmock.NewRows([]string{"attribute_name", "attribute_value"}).
|
||||||
AddRow("department", "engineering")
|
AddRow("department", "engineering")
|
||||||
|
|
||||||
mock.ExpectQuery("SELECT attribute_name, attribute_value FROM user_attributes WHERE users_id = \\?").
|
mock.ExpectQuery("SELECT attribute_name, attribute_value FROM uess_user_management.user_attributes WHERE users_id = \\?").
|
||||||
WithArgs("user123").
|
WithArgs("user123", "user123").
|
||||||
WillReturnRows(attrRows)
|
WillReturnRows(attrRows)
|
||||||
|
|
||||||
attrs, err := getCachedUserAttributes(service, "user123")
|
attrs, err := getCachedUserAttributes(service, "user123")
|
||||||
@@ -219,8 +219,8 @@ func TestAuthorizeWithCache_Success(t *testing.T) {
|
|||||||
service.PolicyCache[1] = []models.PolicyAttribute{}
|
service.PolicyCache[1] = []models.PolicyAttribute{}
|
||||||
|
|
||||||
// Mock user query
|
// Mock user query
|
||||||
userRows := sqlmock.NewRows([]string{"users_id", "email_address"}).
|
userRows := sqlmock.NewRows([]string{"users_id", "email_address", "role_id", "is_deleted"}).
|
||||||
AddRow("user123", "john@example.com")
|
AddRow("user123", "john@example.com", 1, "0")
|
||||||
|
|
||||||
mock.ExpectQuery("SELECT users_id, email_address").
|
mock.ExpectQuery("SELECT users_id, email_address").
|
||||||
WithArgs("user123").
|
WithArgs("user123").
|
||||||
@@ -230,8 +230,8 @@ func TestAuthorizeWithCache_Success(t *testing.T) {
|
|||||||
attrRows := sqlmock.NewRows([]string{"attribute_name", "attribute_value"}).
|
attrRows := sqlmock.NewRows([]string{"attribute_name", "attribute_value"}).
|
||||||
AddRow("department", "engineering")
|
AddRow("department", "engineering")
|
||||||
|
|
||||||
mock.ExpectQuery("SELECT attribute_name, attribute_value FROM user_attributes WHERE users_id = \\?").
|
mock.ExpectQuery("SELECT attribute_name, attribute_value FROM uess_user_management.user_attributes WHERE users_id = \\?").
|
||||||
WithArgs("user123").
|
WithArgs("user123", "user123").
|
||||||
WillReturnRows(attrRows)
|
WillReturnRows(attrRows)
|
||||||
|
|
||||||
ctx := &models.AuthorizationContext{
|
ctx := &models.AuthorizationContext{
|
||||||
@@ -268,18 +268,19 @@ func TestAuthorizeWithCache_PermissionNotFound(t *testing.T) {
|
|||||||
UsersID: "user123",
|
UsersID: "user123",
|
||||||
Resource: "nonexistent",
|
Resource: "nonexistent",
|
||||||
Action: "read",
|
Action: "read",
|
||||||
|
RoleID: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mock user query
|
// Mock user query
|
||||||
userRows := sqlmock.NewRows([]string{"users_id", "email_address"}).
|
userRows := sqlmock.NewRows([]string{"users_id", "email_address", "role_id", "is_deleted"}).
|
||||||
AddRow("user123", "john@example.com")
|
AddRow("user123", "john@example.com", 1, "0")
|
||||||
|
|
||||||
mock.ExpectQuery("SELECT users_id, email_address").
|
mock.ExpectQuery("SELECT users_id, email_address").
|
||||||
WithArgs("user123").
|
WithArgs("user123").
|
||||||
WillReturnRows(userRows)
|
WillReturnRows(userRows)
|
||||||
|
|
||||||
// Permission not in cache, so will query DB and fail
|
// Permission not in cache, so will query DB and fail
|
||||||
mock.ExpectQuery("SELECT p.permissions_id, p.permission_name, p.description, p.resource, p.action FROM permissions p INNER JOIN role_permissions rp").
|
mock.ExpectQuery("SELECT p.permissions_id, p.permission_name, p.description, p.resource, p.action FROM uess_user_management.permissions p INNER JOIN uess_user_management.role_permissions rp").
|
||||||
WithArgs("nonexistent", "read", 1).
|
WithArgs("nonexistent", "read", 1).
|
||||||
WillReturnError(errors.New("permission not found"))
|
WillReturnError(errors.New("permission not found"))
|
||||||
|
|
||||||
@@ -296,6 +297,45 @@ func TestAuthorizeWithCache_PermissionNotFound(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAuthorizeWithCache_DeletedUserRedirectsToLogin(t *testing.T) {
|
||||||
|
mock, cleanup := setupMockDBForCached(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
service := &models.CachedAuthorizationService{
|
||||||
|
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{},
|
||||||
|
}
|
||||||
|
|
||||||
|
userRows := sqlmock.NewRows([]string{"users_id", "email_address", "role_id", "is_deleted"}).
|
||||||
|
AddRow("deleted-user", "deleted@example.com", 1, "1")
|
||||||
|
|
||||||
|
mock.ExpectQuery("SELECT users_id, email_address, role_id, is_deleted").
|
||||||
|
WithArgs("deleted-user").
|
||||||
|
WillReturnRows(userRows)
|
||||||
|
|
||||||
|
ctx := &models.AuthorizationContext{
|
||||||
|
UsersID: "deleted-user",
|
||||||
|
Resource: "document",
|
||||||
|
Action: "read",
|
||||||
|
RoleID: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := AuthorizeWithCache(service, ctx)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expected no error, got %v", err)
|
||||||
|
}
|
||||||
|
if result.Allowed {
|
||||||
|
t.Error("Expected access denied")
|
||||||
|
}
|
||||||
|
if result.RedirectRoute != "/login" {
|
||||||
|
t.Errorf("Expected redirect to /login, got %q", result.RedirectRoute)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestInvalidateUserCache(t *testing.T) {
|
func TestInvalidateUserCache(t *testing.T) {
|
||||||
service := &models.CachedAuthorizationService{
|
service := &models.CachedAuthorizationService{
|
||||||
UserAttrCache: make(map[string]map[string]string),
|
UserAttrCache: make(map[string]map[string]string),
|
||||||
|
|||||||
Reference in New Issue
Block a user