package store import ( "context" "database/sql" "fmt" "atlas9.dev/c/core" "atlas9.dev/c/core/dbi" "atlas9.dev/c/core/iam" "atlas9.dev/c/demo/lib" ) type SqliteGrantStore struct { db dbi.DBI guard lib.Guard } var _ iam.GrantStore = (*SqliteGrantStore)(nil) func NewSqliteGrantStore(db dbi.DBI, guard lib.Guard) *SqliteGrantStore { return &SqliteGrantStore{db: db, guard: guard} } func (s *SqliteGrantStore) Add(ctx context.Context, g iam.Grant) error { // TODO this makes managing grants a tenant-wide permission, // which doesn't allow some users to manage a specific folder/directory. if err := s.guard.Check(ctx, iam.CapGrantsAdd, g.Tenant, ""); err != nil { return err } // Ensure the user is a member of the requested tenant. if g.Type == "user" { var exists bool err := s.db.QueryRow(ctx, `SELECT 1 FROM tenant_members WHERE tenant = $1 AND user_id = $2`, g.Tenant, g.Principal).Scan(&exists) if err != nil { return fmt.Errorf("user is not a member of this tenant") } } _, err := s.db.Exec(ctx, ` INSERT INTO grants (tenant, type, principal, role, path, system) VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (tenant, type, principal, role, path) DO NOTHING `, g.Tenant, g.Type, g.Principal, g.Role, g.Path, g.System) return err } func (s *SqliteGrantStore) Remove(ctx context.Context, g iam.Grant) error { if err := s.guard.Check(ctx, iam.CapGrantsRemove, g.Tenant, ""); err != nil { return err } // TODO perhaps this should return whether the grant was actually deleted. _, err := s.db.Exec(ctx, ` DELETE FROM grants WHERE tenant = $1 AND type = $2 AND principal = $3 AND role = $4 AND path = $5 `, g.Tenant, g.Type, g.Principal, g.Role, g.Path) return err } // TODO this is list for user? func (s *SqliteGrantStore) ListForUser(ctx context.Context, userID core.ID, page core.PageReq, out *core.Page[iam.Grant]) error { if err := s.guard.System(ctx, iam.CapGrantsList); err != nil { return err } limit := page.Limit if limit <= 0 { limit = 100 } // TODO wtf. too complicated. rows, err := s.db.Query(ctx, ` SELECT tenant, type, principal, role, path, system FROM grants WHERE (type = 'user' AND principal = $1) OR (type = 'group' AND principal IN ( SELECT group_path FROM group_members WHERE user_id = $1 )) ORDER BY role, path LIMIT $2 `, userID, limit) if err != nil { return err } defer rows.Close() return scanGrants(rows, out) } func (s *SqliteGrantStore) List(ctx context.Context, tenant core.ID, page core.PageReq, out *core.Page[iam.Grant]) error { if err := s.guard.Check(ctx, iam.CapGrantsList, tenant, ""); err != nil { return err } limit := page.Limit if limit <= 0 { limit = 100 } rows, err := s.db.Query(ctx, ` SELECT tenant, type, principal, role, path, system FROM grants WHERE tenant = $1 ORDER BY type, principal, role, path LIMIT $2 `, tenant, limit) if err != nil { return err } defer rows.Close() return scanGrants(rows, out) } func scanGrants(rows *sql.Rows, out *core.Page[iam.Grant]) error { for rows.Next() { var g iam.Grant if err := rows.Scan(&g.Tenant, &g.Type, &g.Principal, &g.Role, &g.Path, &g.System); err != nil { return err } out.Items = append(out.Items, g) } return rows.Err() }