This commit is contained in:
2025-08-22 14:40:59 -04:00
parent 98a299e7b2
commit 141b1e936d
12 changed files with 1973 additions and 61 deletions

View File

@ -2,10 +2,14 @@ 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
@ -20,20 +24,116 @@ func NewPermissionRepository(db repository.DatabaseProvider) repository.Permissi
// CreateAvailablePermission creates a new available permission
func (r *PermissionRepository) CreateAvailablePermission(ctx context.Context, permission *domain.AvailablePermission) error {
// TODO: Implement actual permission creation
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) {
// TODO: Implement actual permission retrieval
return nil, nil
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) {
// TODO: Implement actual permission retrieval by scope
return nil, nil
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
@ -56,9 +156,44 @@ func (r *PermissionRepository) DeleteAvailablePermission(ctx context.Context, pe
// ValidatePermissionScopes checks if all given scopes exist and are valid
func (r *PermissionRepository) ValidatePermissionScopes(ctx context.Context, scopes []string) ([]string, error) {
// TODO: Implement actual scope validation
// For now, assume all scopes are valid
return []string{}, nil
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
@ -79,7 +214,56 @@ func NewGrantedPermissionRepository(db repository.DatabaseProvider) repository.G
// GrantPermissions grants multiple permissions to a token
func (r *GrantedPermissionRepository) GrantPermissions(ctx context.Context, grants []*domain.GrantedPermission) error {
// TODO: Implement actual permission granting
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
}
@ -109,16 +293,72 @@ func (r *GrantedPermissionRepository) RevokeAllPermissions(ctx context.Context,
// 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) {
// TODO: Implement actual permission checking
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) {
// TODO: Implement actual permission checking
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
}

View File

@ -57,26 +57,196 @@ func (r *StaticTokenRepository) Create(ctx context.Context, token *domain.Static
// GetByID retrieves a static token by its ID
func (r *StaticTokenRepository) GetByID(ctx context.Context, tokenID uuid.UUID) (*domain.StaticToken, error) {
// TODO: Implement actual token retrieval
return nil, nil
query := `
SELECT id, app_id, owner_type, owner_name, owner_owner,
key_hash, type, created_at, updated_at
FROM static_tokens
WHERE id = $1
`
db := r.db.GetDB().(*sql.DB)
row := db.QueryRowContext(ctx, query, tokenID)
token := &domain.StaticToken{}
var ownerType, ownerName, ownerOwner string
err := row.Scan(
&token.ID,
&token.AppID,
&ownerType,
&ownerName,
&ownerOwner,
&token.KeyHash,
&token.Type,
&token.CreatedAt,
&token.UpdatedAt,
)
if err != nil {
if err == sql.ErrNoRows {
return nil, fmt.Errorf("static token with ID '%s' not found", tokenID)
}
return nil, fmt.Errorf("failed to get static token: %w", err)
}
token.Owner = domain.Owner{
Type: domain.OwnerType(ownerType),
Name: ownerName,
Owner: ownerOwner,
}
return token, nil
}
// GetByKeyHash retrieves a static token by its key hash
func (r *StaticTokenRepository) GetByKeyHash(ctx context.Context, keyHash string) (*domain.StaticToken, error) {
// TODO: Implement actual token retrieval by hash
return nil, nil
query := `
SELECT id, app_id, owner_type, owner_name, owner_owner,
key_hash, type, created_at, updated_at
FROM static_tokens
WHERE key_hash = $1
`
db := r.db.GetDB().(*sql.DB)
row := db.QueryRowContext(ctx, query, keyHash)
token := &domain.StaticToken{}
var ownerType, ownerName, ownerOwner string
err := row.Scan(
&token.ID,
&token.AppID,
&ownerType,
&ownerName,
&ownerOwner,
&token.KeyHash,
&token.Type,
&token.CreatedAt,
&token.UpdatedAt,
)
if err != nil {
if err == sql.ErrNoRows {
return nil, fmt.Errorf("static token with hash not found")
}
return nil, fmt.Errorf("failed to get static token by hash: %w", err)
}
token.Owner = domain.Owner{
Type: domain.OwnerType(ownerType),
Name: ownerName,
Owner: ownerOwner,
}
return token, nil
}
// GetByAppID retrieves all static tokens for an application
func (r *StaticTokenRepository) GetByAppID(ctx context.Context, appID string) ([]*domain.StaticToken, error) {
// TODO: Implement actual token listing
return []*domain.StaticToken{}, nil
query := `
SELECT id, app_id, owner_type, owner_name, owner_owner,
key_hash, type, created_at, updated_at
FROM static_tokens
WHERE app_id = $1
ORDER BY created_at DESC
`
db := r.db.GetDB().(*sql.DB)
rows, err := db.QueryContext(ctx, query, appID)
if err != nil {
return nil, fmt.Errorf("failed to query static tokens: %w", err)
}
defer rows.Close()
var tokens []*domain.StaticToken
for rows.Next() {
token := &domain.StaticToken{}
var ownerType, ownerName, ownerOwner string
err := rows.Scan(
&token.ID,
&token.AppID,
&ownerType,
&ownerName,
&ownerOwner,
&token.KeyHash,
&token.Type,
&token.CreatedAt,
&token.UpdatedAt,
)
if err != nil {
return nil, fmt.Errorf("failed to scan static token: %w", err)
}
token.Owner = domain.Owner{
Type: domain.OwnerType(ownerType),
Name: ownerName,
Owner: ownerOwner,
}
tokens = append(tokens, token)
}
if err = rows.Err(); err != nil {
return nil, fmt.Errorf("error iterating static tokens: %w", err)
}
return tokens, nil
}
// List retrieves static tokens with pagination
func (r *StaticTokenRepository) List(ctx context.Context, limit, offset int) ([]*domain.StaticToken, error) {
// TODO: Implement actual token listing
return []*domain.StaticToken{}, nil
query := `
SELECT id, app_id, owner_type, owner_name, owner_owner,
key_hash, type, created_at, updated_at
FROM static_tokens
ORDER BY created_at DESC
LIMIT $1 OFFSET $2
`
db := r.db.GetDB().(*sql.DB)
rows, err := db.QueryContext(ctx, query, limit, offset)
if err != nil {
return nil, fmt.Errorf("failed to query static tokens: %w", err)
}
defer rows.Close()
var tokens []*domain.StaticToken
for rows.Next() {
token := &domain.StaticToken{}
var ownerType, ownerName, ownerOwner string
err := rows.Scan(
&token.ID,
&token.AppID,
&ownerType,
&ownerName,
&ownerOwner,
&token.KeyHash,
&token.Type,
&token.CreatedAt,
&token.UpdatedAt,
)
if err != nil {
return nil, fmt.Errorf("failed to scan static token: %w", err)
}
token.Owner = domain.Owner{
Type: domain.OwnerType(ownerType),
Name: ownerName,
Owner: ownerOwner,
}
tokens = append(tokens, token)
}
if err = rows.Err(); err != nil {
return nil, fmt.Errorf("error iterating static tokens: %w", err)
}
return tokens, nil
}
// Delete deletes a static token