Changeset 0.16.0 (#74)
This commit is contained in:
95
server/store/postgres/resource_grants.go
Normal file
95
server/store/postgres/resource_grants.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"git.gobha.me/xcaliber/chat-switchboard/models"
|
||||
|
||||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
// ── ResourceGrantStore ──────────────────────
|
||||
|
||||
type ResourceGrantStore struct{}
|
||||
|
||||
func NewResourceGrantStore() *ResourceGrantStore { return &ResourceGrantStore{} }
|
||||
|
||||
// Set creates or replaces the grant for a resource (upsert on unique resource_type+resource_id).
|
||||
func (s *ResourceGrantStore) Set(ctx context.Context, grant *models.ResourceGrant) error {
|
||||
// Coalesce nil to empty slice — pq.Array(nil) produces SQL NULL, but column expects array.
|
||||
groups := grant.GrantedGroups
|
||||
if groups == nil {
|
||||
groups = []string{}
|
||||
}
|
||||
return DB.QueryRowContext(ctx, `
|
||||
INSERT INTO resource_grants (resource_type, resource_id, grant_scope, granted_groups, created_by)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
ON CONFLICT (resource_type, resource_id) DO UPDATE SET
|
||||
grant_scope = EXCLUDED.grant_scope,
|
||||
granted_groups = EXCLUDED.granted_groups,
|
||||
created_by = EXCLUDED.created_by
|
||||
RETURNING id, created_at, updated_at`,
|
||||
grant.ResourceType, grant.ResourceID, grant.GrantScope,
|
||||
pq.Array(groups), grant.CreatedBy,
|
||||
).Scan(&grant.ID, &grant.CreatedAt, &grant.UpdatedAt)
|
||||
}
|
||||
|
||||
// Get retrieves the grant for a specific resource.
|
||||
func (s *ResourceGrantStore) Get(ctx context.Context, resourceType, resourceID string) (*models.ResourceGrant, error) {
|
||||
var rg models.ResourceGrant
|
||||
err := DB.QueryRowContext(ctx, `
|
||||
SELECT id, resource_type, resource_id, grant_scope, granted_groups, created_by,
|
||||
created_at, updated_at
|
||||
FROM resource_grants
|
||||
WHERE resource_type = $1 AND resource_id = $2`,
|
||||
resourceType, resourceID,
|
||||
).Scan(&rg.ID, &rg.ResourceType, &rg.ResourceID, &rg.GrantScope,
|
||||
pq.Array(&rg.GrantedGroups), &rg.CreatedBy,
|
||||
&rg.CreatedAt, &rg.UpdatedAt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rg, nil
|
||||
}
|
||||
|
||||
// Delete removes the grant for a resource.
|
||||
func (s *ResourceGrantStore) Delete(ctx context.Context, resourceType, resourceID string) error {
|
||||
res, err := DB.ExecContext(ctx,
|
||||
"DELETE FROM resource_grants WHERE resource_type = $1 AND resource_id = $2",
|
||||
resourceType, resourceID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n, _ := res.RowsAffected(); n == 0 {
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UserHasGroupAccess checks whether a user has group-based access to a resource.
|
||||
// Returns true if:
|
||||
// - The resource has grant_scope='global', OR
|
||||
// - The resource has grant_scope='groups' and the user is in at least one
|
||||
// of the granted_groups.
|
||||
func (s *ResourceGrantStore) UserHasGroupAccess(ctx context.Context, userID, resourceType, resourceID string) (bool, error) {
|
||||
var has bool
|
||||
err := DB.QueryRowContext(ctx, `
|
||||
SELECT EXISTS(
|
||||
SELECT 1 FROM resource_grants rg
|
||||
WHERE rg.resource_type = $1
|
||||
AND rg.resource_id = $2
|
||||
AND (
|
||||
rg.grant_scope = 'global'
|
||||
OR (
|
||||
rg.grant_scope = 'groups'
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM group_members gm
|
||||
WHERE gm.user_id = $3
|
||||
AND gm.group_id = ANY(rg.granted_groups)
|
||||
)
|
||||
)
|
||||
)
|
||||
)`, resourceType, resourceID, userID).Scan(&has)
|
||||
return has, err
|
||||
}
|
||||
Reference in New Issue
Block a user