package postgres import ( "context" "database/sql" "fmt" "time" "github.com/google/uuid" "github.com/kms/api-key-service/internal/domain" "github.com/kms/api-key-service/internal/repository" "github.com/lib/pq" ) // PermissionRepository implements the PermissionRepository interface for PostgreSQL type PermissionRepository struct { db repository.DatabaseProvider } // NewPermissionRepository creates a new PostgreSQL permission repository func NewPermissionRepository(db repository.DatabaseProvider) repository.PermissionRepository { return &PermissionRepository{db: db} } // CreateAvailablePermission creates a new available permission func (r *PermissionRepository) CreateAvailablePermission(ctx context.Context, permission *domain.AvailablePermission) error { query := ` INSERT INTO available_permissions ( id, scope, name, description, category, parent_scope, is_system, created_by, updated_by, created_at, updated_at ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) ` db := r.db.GetDB().(*sql.DB) now := time.Now() if permission.ID == uuid.Nil { permission.ID = uuid.New() } _, err := db.ExecContext(ctx, query, permission.ID, permission.Scope, permission.Name, permission.Description, permission.Category, permission.ParentScope, permission.IsSystem, permission.CreatedBy, permission.UpdatedBy, now, now, ) if err != nil { return fmt.Errorf("failed to create available permission: %w", err) } permission.CreatedAt = now permission.UpdatedAt = now return nil } // GetAvailablePermission retrieves an available permission by ID func (r *PermissionRepository) GetAvailablePermission(ctx context.Context, permissionID uuid.UUID) (*domain.AvailablePermission, error) { query := ` SELECT id, scope, name, description, category, parent_scope, is_system, created_at, created_by, updated_at, updated_by FROM available_permissions WHERE id = $1 ` db := r.db.GetDB().(*sql.DB) row := db.QueryRowContext(ctx, query, permissionID) permission := &domain.AvailablePermission{} err := row.Scan( &permission.ID, &permission.Scope, &permission.Name, &permission.Description, &permission.Category, &permission.ParentScope, &permission.IsSystem, &permission.CreatedAt, &permission.CreatedBy, &permission.UpdatedAt, &permission.UpdatedBy, ) if err != nil { if err == sql.ErrNoRows { return nil, fmt.Errorf("permission with ID '%s' not found", permissionID) } return nil, fmt.Errorf("failed to get available permission: %w", err) } return permission, nil } // GetAvailablePermissionByScope retrieves an available permission by scope func (r *PermissionRepository) GetAvailablePermissionByScope(ctx context.Context, scope string) (*domain.AvailablePermission, error) { query := ` SELECT id, scope, name, description, category, parent_scope, is_system, created_at, created_by, updated_at, updated_by FROM available_permissions WHERE scope = $1 ` db := r.db.GetDB().(*sql.DB) row := db.QueryRowContext(ctx, query, scope) permission := &domain.AvailablePermission{} err := row.Scan( &permission.ID, &permission.Scope, &permission.Name, &permission.Description, &permission.Category, &permission.ParentScope, &permission.IsSystem, &permission.CreatedAt, &permission.CreatedBy, &permission.UpdatedAt, &permission.UpdatedBy, ) if err != nil { if err == sql.ErrNoRows { return nil, fmt.Errorf("permission with scope '%s' not found", scope) } return nil, fmt.Errorf("failed to get available permission by scope: %w", err) } return permission, nil } // ListAvailablePermissions retrieves available permissions with pagination and filtering func (r *PermissionRepository) ListAvailablePermissions(ctx context.Context, category string, includeSystem bool, limit, offset int) ([]*domain.AvailablePermission, error) { // TODO: Implement actual permission listing return []*domain.AvailablePermission{}, nil } // UpdateAvailablePermission updates an available permission func (r *PermissionRepository) UpdateAvailablePermission(ctx context.Context, permissionID uuid.UUID, permission *domain.AvailablePermission) error { // TODO: Implement actual permission update return nil } // DeleteAvailablePermission deletes an available permission func (r *PermissionRepository) DeleteAvailablePermission(ctx context.Context, permissionID uuid.UUID) error { // TODO: Implement actual permission deletion return nil } // ValidatePermissionScopes checks if all given scopes exist and are valid func (r *PermissionRepository) ValidatePermissionScopes(ctx context.Context, scopes []string) ([]string, error) { if len(scopes) == 0 { return []string{}, nil } query := ` SELECT scope FROM available_permissions WHERE scope = ANY($1) ` db := r.db.GetDB().(*sql.DB) rows, err := db.QueryContext(ctx, query, pq.Array(scopes)) if err != nil { return nil, fmt.Errorf("failed to validate permission scopes: %w", err) } defer rows.Close() validScopes := make(map[string]bool) for rows.Next() { var scope string if err := rows.Scan(&scope); err != nil { return nil, fmt.Errorf("failed to scan scope: %w", err) } validScopes[scope] = true } if err = rows.Err(); err != nil { return nil, fmt.Errorf("error iterating scopes: %w", err) } var result []string for _, scope := range scopes { if validScopes[scope] { result = append(result, scope) } } return result, nil } // GetPermissionHierarchy returns all parent and child permissions for given scopes func (r *PermissionRepository) GetPermissionHierarchy(ctx context.Context, scopes []string) ([]*domain.AvailablePermission, error) { // TODO: Implement actual permission hierarchy retrieval return []*domain.AvailablePermission{}, nil } // GrantedPermissionRepository implements the GrantedPermissionRepository interface for PostgreSQL type GrantedPermissionRepository struct { db repository.DatabaseProvider } // NewGrantedPermissionRepository creates a new PostgreSQL granted permission repository func NewGrantedPermissionRepository(db repository.DatabaseProvider) repository.GrantedPermissionRepository { return &GrantedPermissionRepository{db: db} } // GrantPermissions grants multiple permissions to a token func (r *GrantedPermissionRepository) GrantPermissions(ctx context.Context, grants []*domain.GrantedPermission) error { if len(grants) == 0 { return nil } db := r.db.GetDB().(*sql.DB) tx, err := db.BeginTx(ctx, nil) if err != nil { return fmt.Errorf("failed to begin transaction: %w", err) } defer tx.Rollback() query := ` INSERT INTO granted_permissions ( id, token_type, token_id, permission_id, scope, created_by, created_at ) VALUES ($1, $2, $3, $4, $5, $6, $7) ON CONFLICT (token_type, token_id, permission_id) DO NOTHING ` stmt, err := tx.PrepareContext(ctx, query) if err != nil { return fmt.Errorf("failed to prepare statement: %w", err) } defer stmt.Close() now := time.Now() for _, grant := range grants { if grant.ID == uuid.Nil { grant.ID = uuid.New() } _, err = stmt.ExecContext(ctx, grant.ID, string(grant.TokenType), grant.TokenID, grant.PermissionID, grant.Scope, grant.CreatedBy, now, ) if err != nil { return fmt.Errorf("failed to grant permission: %w", err) } grant.CreatedAt = now } if err = tx.Commit(); err != nil { return fmt.Errorf("failed to commit transaction: %w", err) } return nil } // GetGrantedPermissions retrieves all granted permissions for a token func (r *GrantedPermissionRepository) GetGrantedPermissions(ctx context.Context, tokenType domain.TokenType, tokenID uuid.UUID) ([]*domain.GrantedPermission, error) { // TODO: Implement actual granted permissions retrieval return []*domain.GrantedPermission{}, nil } // GetGrantedPermissionScopes retrieves only the scopes for a token (more efficient) func (r *GrantedPermissionRepository) GetGrantedPermissionScopes(ctx context.Context, tokenType domain.TokenType, tokenID uuid.UUID) ([]string, error) { // TODO: Implement actual scope retrieval return []string{}, nil } // RevokePermission revokes a specific permission from a token func (r *GrantedPermissionRepository) RevokePermission(ctx context.Context, grantID uuid.UUID, revokedBy string) error { // TODO: Implement actual permission revocation return nil } // RevokeAllPermissions revokes all permissions from a token func (r *GrantedPermissionRepository) RevokeAllPermissions(ctx context.Context, tokenType domain.TokenType, tokenID uuid.UUID, revokedBy string) error { // TODO: Implement actual permission revocation return nil } // HasPermission checks if a token has a specific permission func (r *GrantedPermissionRepository) HasPermission(ctx context.Context, tokenType domain.TokenType, tokenID uuid.UUID, scope string) (bool, error) { query := ` SELECT 1 FROM granted_permissions gp JOIN available_permissions ap ON gp.permission_id = ap.id WHERE gp.token_type = $1 AND gp.token_id = $2 AND gp.scope = $3 AND gp.revoked = false LIMIT 1 ` db := r.db.GetDB().(*sql.DB) var exists int err := db.QueryRowContext(ctx, query, string(tokenType), tokenID, scope).Scan(&exists) if err != nil { if err == sql.ErrNoRows { return false, nil } return false, fmt.Errorf("failed to check permission: %w", err) } return true, nil } // HasAnyPermission checks if a token has any of the specified permissions func (r *GrantedPermissionRepository) HasAnyPermission(ctx context.Context, tokenType domain.TokenType, tokenID uuid.UUID, scopes []string) (map[string]bool, error) { if len(scopes) == 0 { return make(map[string]bool), nil } query := ` SELECT gp.scope FROM granted_permissions gp JOIN available_permissions ap ON gp.permission_id = ap.id WHERE gp.token_type = $1 AND gp.token_id = $2 AND gp.scope = ANY($3) AND gp.revoked = false ` db := r.db.GetDB().(*sql.DB) rows, err := db.QueryContext(ctx, query, string(tokenType), tokenID, pq.Array(scopes)) if err != nil { return nil, fmt.Errorf("failed to check permissions: %w", err) } defer rows.Close() result := make(map[string]bool) // Initialize all scopes as false for _, scope := range scopes { result[scope] = false } // Mark found permissions as true for rows.Next() { var scope string if err := rows.Scan(&scope); err != nil { return nil, fmt.Errorf("failed to scan permission scope: %w", err) } result[scope] = true } if err = rows.Err(); err != nil { return nil, fmt.Errorf("error iterating permission results: %w", err) } return result, nil }