veza/veza-backend-api/internal/services/rbac_service.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
}