397 lines
No EOL
11 KiB
Go
397 lines
No EOL
11 KiB
Go
package services
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"github.com/google/uuid"
|
|
|
|
"veza-backend-api/internal/database"
|
|
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// RBACService handles role-based access control
|
|
type RBACService struct {
|
|
db *database.Database
|
|
logger *zap.Logger
|
|
}
|
|
|
|
// NewRBACService creates a new RBAC service
|
|
func NewRBACService(db *database.Database, logger *zap.Logger) *RBACService {
|
|
return &RBACService{
|
|
db: db,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// Role represents a user role
|
|
type Role struct {
|
|
ID uuid.UUID `json:"id"`
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
Permissions []Permission `json:"permissions"`
|
|
IsSystem bool `json:"is_system"`
|
|
CreatedAt string `json:"created_at"`
|
|
UpdatedAt string `json:"updated_at"`
|
|
}
|
|
|
|
// Permission represents a permission
|
|
type Permission struct {
|
|
ID uuid.UUID `json:"id"`
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
Resource string `json:"resource"`
|
|
Action string `json:"action"`
|
|
CreatedAt string `json:"created_at"`
|
|
}
|
|
|
|
// UserRole represents a user's role assignment
|
|
type UserRole struct {
|
|
ID uuid.UUID `json:"id"`
|
|
UserID uuid.UUID `json:"user_id"`
|
|
RoleID uuid.UUID `json:"role_id"`
|
|
Role *Role `json:"role,omitempty"`
|
|
}
|
|
|
|
// CreateRole creates a new role
|
|
func (s *RBACService) CreateRole(ctx context.Context, name, description string, permissions []uuid.UUID) (*Role, error) {
|
|
// Check if role already exists
|
|
var count int
|
|
err := s.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM roles WHERE name = $1", name).Scan(&count)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to check role existence: %w", err)
|
|
}
|
|
if count > 0 {
|
|
return nil, fmt.Errorf("role with name '%s' already exists", name)
|
|
}
|
|
|
|
// Create role
|
|
var roleID uuid.UUID
|
|
query := `
|
|
INSERT INTO roles (id, name, description, is_system, created_at, updated_at)
|
|
VALUES (gen_random_uuid(), $1, $2, false, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
|
RETURNING id
|
|
`
|
|
|
|
err = s.db.QueryRowContext(ctx, query, name, description).Scan(&roleID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create role: %w", err)
|
|
}
|
|
|
|
// Assign permissions to role
|
|
if len(permissions) > 0 {
|
|
for _, permID := range permissions {
|
|
_, err = s.db.ExecContext(ctx, `
|
|
INSERT INTO role_permissions (role_id, permission_id, created_at)
|
|
VALUES ($1, $2, CURRENT_TIMESTAMP)
|
|
`, roleID, permID)
|
|
if err != nil {
|
|
s.logger.Error("Failed to assign permission to role", zap.Error(err))
|
|
// Continue with other permissions
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get the created role with permissions
|
|
role, err := s.GetRoleByID(ctx, roleID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get created role: %w", err)
|
|
}
|
|
|
|
s.logger.Info("Role created successfully", zap.String("role_name", name), zap.String("role_id", roleID.String()))
|
|
return role, nil
|
|
}
|
|
|
|
// GetRoleByID gets a role by ID
|
|
func (s *RBACService) GetRoleByID(ctx context.Context, roleID uuid.UUID) (*Role, error) {
|
|
query := `
|
|
SELECT r.id, r.name, r.description, r.is_system, r.created_at, r.updated_at
|
|
FROM roles r
|
|
WHERE r.id = $1
|
|
`
|
|
|
|
var role Role
|
|
err := s.db.QueryRowContext(ctx, query, roleID).Scan(
|
|
&role.ID, &role.Name, &role.Description, &role.IsSystem, &role.CreatedAt, &role.UpdatedAt,
|
|
)
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return nil, fmt.Errorf("role not found")
|
|
}
|
|
return nil, fmt.Errorf("failed to get role: %w", err)
|
|
}
|
|
|
|
// Get permissions for this role
|
|
permissions, err := s.GetRolePermissions(ctx, roleID)
|
|
if err != nil {
|
|
s.logger.Error("Failed to get role permissions", zap.Error(err))
|
|
} else {
|
|
role.Permissions = permissions
|
|
}
|
|
|
|
return &role, nil
|
|
}
|
|
|
|
// GetRolePermissions gets permissions for a role
|
|
func (s *RBACService) GetRolePermissions(ctx context.Context, roleID uuid.UUID) ([]Permission, error) {
|
|
query := `
|
|
SELECT p.id, p.name, p.description, p.resource, p.action, p.created_at
|
|
FROM permissions p
|
|
JOIN role_permissions rp ON p.id = rp.permission_id
|
|
WHERE rp.role_id = $1
|
|
ORDER BY p.name
|
|
`
|
|
|
|
rows, err := s.db.QueryContext(ctx, query, roleID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get role permissions: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var permissions []Permission
|
|
for rows.Next() {
|
|
var perm Permission
|
|
err := rows.Scan(&perm.ID, &perm.Name, &perm.Description, &perm.Resource, &perm.Action, &perm.CreatedAt)
|
|
if err != nil {
|
|
s.logger.Error("Failed to scan permission", zap.Error(err))
|
|
continue
|
|
}
|
|
permissions = append(permissions, perm)
|
|
}
|
|
|
|
return permissions, nil
|
|
}
|
|
|
|
// AssignRoleToUser assigns a role to a user
|
|
// MIGRATION UUID: userID migré vers uuid.UUID, roleID aussi
|
|
func (s *RBACService) AssignRoleToUser(ctx context.Context, userID uuid.UUID, roleID uuid.UUID) error {
|
|
// Check if user exists
|
|
var userCount int
|
|
err := s.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM users WHERE id = $1", userID).Scan(&userCount)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to check user existence: %w", err)
|
|
}
|
|
if userCount == 0 {
|
|
return fmt.Errorf("user not found")
|
|
}
|
|
|
|
// Check if role exists
|
|
var roleCount int
|
|
err = s.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM roles WHERE id = $1", roleID).Scan(&roleCount)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to check role existence: %w", err)
|
|
}
|
|
if roleCount == 0 {
|
|
return fmt.Errorf("role not found")
|
|
}
|
|
|
|
// Check if role is already assigned
|
|
var assignmentCount int
|
|
err = s.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM user_roles WHERE user_id = $1 AND role_id = $2", userID, roleID).Scan(&assignmentCount)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to check role assignment: %w", err)
|
|
}
|
|
if assignmentCount > 0 {
|
|
return fmt.Errorf("role already assigned to user")
|
|
}
|
|
|
|
// Assign role to user
|
|
_, err = s.db.ExecContext(ctx, `
|
|
INSERT INTO user_roles (id, user_id, role_id, created_at)
|
|
VALUES (gen_random_uuid(), $1, $2, CURRENT_TIMESTAMP)
|
|
`, userID, roleID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to assign role to user: %w", err)
|
|
}
|
|
|
|
s.logger.Info("Role assigned to user successfully", zap.String("user_id", userID.String()), zap.String("role_id", roleID.String()))
|
|
return nil
|
|
}
|
|
|
|
// RemoveRoleFromUser removes a role from a user
|
|
// MIGRATION UUID: userID migré vers uuid.UUID, roleID aussi
|
|
func (s *RBACService) RemoveRoleFromUser(ctx context.Context, userID uuid.UUID, roleID uuid.UUID) error {
|
|
result, err := s.db.ExecContext(ctx, `
|
|
DELETE FROM user_roles
|
|
WHERE user_id = $1 AND role_id = $2
|
|
`, userID, roleID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to remove role from user: %w", err)
|
|
}
|
|
|
|
rowsAffected, err := result.RowsAffected()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get rows affected: %w", err)
|
|
}
|
|
|
|
if rowsAffected == 0 {
|
|
return fmt.Errorf("role not assigned to user")
|
|
}
|
|
|
|
s.logger.Info("Role removed from user successfully", zap.String("user_id", userID.String()), zap.String("role_id", roleID.String()))
|
|
return nil
|
|
}
|
|
|
|
// GetUserRoles gets all roles for a user
|
|
func (s *RBACService) GetUserRoles(ctx context.Context, userID uuid.UUID) ([]*Role, error) {
|
|
query := `
|
|
SELECT r.id, r.name, r.description, r.is_system, r.created_at, r.updated_at
|
|
FROM roles r
|
|
JOIN user_roles ur ON r.id = ur.role_id
|
|
WHERE ur.user_id = $1
|
|
ORDER BY r.name
|
|
`
|
|
|
|
rows, err := s.db.QueryContext(ctx, query, userID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get user roles: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var roles []*Role
|
|
for rows.Next() {
|
|
var role Role
|
|
err := rows.Scan(&role.ID, &role.Name, &role.Description, &role.IsSystem, &role.CreatedAt, &role.UpdatedAt)
|
|
if err != nil {
|
|
s.logger.Error("Failed to scan role", zap.Error(err))
|
|
continue
|
|
}
|
|
|
|
// Get permissions for this role
|
|
permissions, err := s.GetRolePermissions(ctx, role.ID)
|
|
if err != nil {
|
|
s.logger.Error("Failed to get role permissions", zap.Error(err))
|
|
} else {
|
|
role.Permissions = permissions
|
|
}
|
|
|
|
roles = append(roles, &role)
|
|
}
|
|
|
|
return roles, nil
|
|
}
|
|
|
|
// CheckPermission checks if a user has a specific permission
|
|
func (s *RBACService) CheckPermission(ctx context.Context, userID uuid.UUID, resource, action string) (bool, error) {
|
|
query := `
|
|
SELECT COUNT(*)
|
|
FROM permissions p
|
|
JOIN role_permissions rp ON p.id = rp.permission_id
|
|
JOIN user_roles ur ON rp.role_id = ur.role_id
|
|
WHERE ur.user_id = $1 AND p.resource = $2 AND p.action = $3
|
|
`
|
|
|
|
var count int
|
|
err := s.db.QueryRowContext(ctx, query, userID, resource, action).Scan(&count)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to check permission: %w", err)
|
|
}
|
|
|
|
return count > 0, nil
|
|
}
|
|
|
|
// GetUserPermissions gets all permissions for a user
|
|
func (s *RBACService) GetUserPermissions(ctx context.Context, userID uuid.UUID) ([]Permission, error) {
|
|
query := `
|
|
SELECT DISTINCT p.id, p.name, p.description, p.resource, p.action, p.created_at
|
|
FROM permissions p
|
|
JOIN role_permissions rp ON p.id = rp.permission_id
|
|
JOIN user_roles ur ON rp.role_id = ur.role_id
|
|
WHERE ur.user_id = $1
|
|
ORDER BY p.resource, p.action
|
|
`
|
|
|
|
rows, err := s.db.QueryContext(ctx, query, userID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get user permissions: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var permissions []Permission
|
|
for rows.Next() {
|
|
var perm Permission
|
|
err := rows.Scan(&perm.ID, &perm.Name, &perm.Description, &perm.Resource, &perm.Action, &perm.CreatedAt)
|
|
if err != nil {
|
|
s.logger.Error("Failed to scan permission", zap.Error(err))
|
|
continue
|
|
}
|
|
permissions = append(permissions, perm)
|
|
}
|
|
|
|
return permissions, nil
|
|
}
|
|
|
|
// CreatePermission creates a new permission
|
|
func (s *RBACService) CreatePermission(ctx context.Context, name, description, resource, action string) (*Permission, error) {
|
|
// Check if permission already exists
|
|
var count int
|
|
err := s.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM permissions WHERE resource = $1 AND action = $2", resource, action).Scan(&count)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to check permission existence: %w", err)
|
|
}
|
|
if count > 0 {
|
|
return nil, fmt.Errorf("permission with resource '%s' and action '%s' already exists", resource, action)
|
|
}
|
|
|
|
// Create permission
|
|
var permID uuid.UUID
|
|
query := `
|
|
INSERT INTO permissions (id, name, description, resource, action, created_at)
|
|
VALUES (gen_random_uuid(), $1, $2, $3, $4, CURRENT_TIMESTAMP)
|
|
RETURNING id
|
|
`
|
|
|
|
err = s.db.QueryRowContext(ctx, query, name, description, resource, action).Scan(&permID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create permission: %w", err)
|
|
}
|
|
|
|
permission := &Permission{
|
|
ID: permID,
|
|
Name: name,
|
|
Description: description,
|
|
Resource: resource,
|
|
Action: action,
|
|
}
|
|
|
|
s.logger.Info("Permission created successfully", zap.String("permission_name", name))
|
|
return permission, nil
|
|
}
|
|
|
|
// GetAllRoles gets all roles
|
|
func (s *RBACService) GetAllRoles(ctx context.Context) ([]*Role, error) {
|
|
query := `
|
|
SELECT id, name, description, is_system, created_at, updated_at
|
|
FROM roles
|
|
ORDER BY name
|
|
`
|
|
|
|
rows, err := s.db.QueryContext(ctx, query)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get roles: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var roles []*Role
|
|
for rows.Next() {
|
|
var role Role
|
|
err := rows.Scan(&role.ID, &role.Name, &role.Description, &role.IsSystem, &role.CreatedAt, &role.UpdatedAt)
|
|
if err != nil {
|
|
s.logger.Error("Failed to scan role", zap.Error(err))
|
|
continue
|
|
}
|
|
|
|
// Get permissions for this role
|
|
permissions, err := s.GetRolePermissions(ctx, role.ID)
|
|
if err != nil {
|
|
s.logger.Error("Failed to get role permissions", zap.Error(err))
|
|
} else {
|
|
role.Permissions = permissions
|
|
}
|
|
|
|
roles = append(roles, &role)
|
|
}
|
|
|
|
return roles, nil
|
|
} |