feat(api): RequireAuth+RequireProjectAccess middleware, IDOR-scope check/apply по projectID
This commit is contained in:
@@ -21,7 +21,7 @@ type DomainRef struct {
|
||||
}
|
||||
|
||||
type Loader interface {
|
||||
LoadDomain(ctx context.Context, domainID uuid.UUID) (DomainRef, error)
|
||||
LoadDomain(ctx context.Context, projectID, domainID uuid.UUID) (DomainRef, error)
|
||||
}
|
||||
|
||||
type Recorder interface {
|
||||
@@ -45,8 +45,10 @@ func New(loader Loader, rec Recorder, reg *registry.Registry, cipher *crypto.Cip
|
||||
}
|
||||
|
||||
// resolve loads the domain, its provider and decrypted credentials, and computes the diff.
|
||||
func (s *DomainService) resolve(ctx context.Context, domainID uuid.UUID) (provider.Provider, provider.Credentials, DomainRef, diff.Changeset, error) {
|
||||
ref, err := s.loader.LoadDomain(ctx, domainID)
|
||||
// projectID scopes the lookup so a domainID belonging to another tenant's
|
||||
// project can never be resolved here (closes IDOR).
|
||||
func (s *DomainService) resolve(ctx context.Context, projectID, domainID uuid.UUID) (provider.Provider, provider.Credentials, DomainRef, diff.Changeset, error) {
|
||||
ref, err := s.loader.LoadDomain(ctx, projectID, domainID)
|
||||
if err != nil {
|
||||
return nil, provider.Credentials{}, ref, diff.Changeset{}, err
|
||||
}
|
||||
@@ -68,8 +70,8 @@ func (s *DomainService) resolve(ctx context.Context, domainID uuid.UUID) (provid
|
||||
}
|
||||
|
||||
// Check computes and records the diff between template and zone.
|
||||
func (s *DomainService) Check(ctx context.Context, domainID uuid.UUID) (diff.Changeset, error) {
|
||||
_, _, _, cs, err := s.resolve(ctx, domainID)
|
||||
func (s *DomainService) Check(ctx context.Context, projectID, domainID uuid.UUID) (diff.Changeset, error) {
|
||||
_, _, _, cs, err := s.resolve(ctx, projectID, domainID)
|
||||
if err != nil {
|
||||
return diff.Changeset{}, err
|
||||
}
|
||||
@@ -80,8 +82,8 @@ func (s *DomainService) Check(ctx context.Context, domainID uuid.UUID) (diff.Cha
|
||||
}
|
||||
|
||||
// Apply applies updates always (when ApplyUpdates) and prunes only when ApplyPrunes.
|
||||
func (s *DomainService) Apply(ctx context.Context, domainID uuid.UUID, req ApplyRequest) (diff.Changeset, error) {
|
||||
p, creds, ref, cs, err := s.resolve(ctx, domainID)
|
||||
func (s *DomainService) Apply(ctx context.Context, projectID, domainID uuid.UUID, req ApplyRequest) (diff.Changeset, error) {
|
||||
p, creds, ref, cs, err := s.resolve(ctx, projectID, domainID)
|
||||
if err != nil {
|
||||
return diff.Changeset{}, err
|
||||
}
|
||||
|
||||
@@ -44,7 +44,9 @@ func (f *fakeProvider) ApplyChanges(_ context.Context, _ provider.Credentials, _
|
||||
|
||||
type fakeLoader struct{ ref DomainRef }
|
||||
|
||||
func (l fakeLoader) LoadDomain(context.Context, uuid.UUID) (DomainRef, error) { return l.ref, nil }
|
||||
func (l fakeLoader) LoadDomain(context.Context, uuid.UUID, uuid.UUID) (DomainRef, error) {
|
||||
return l.ref, nil
|
||||
}
|
||||
|
||||
type nopRecorder struct{}
|
||||
|
||||
@@ -66,7 +68,7 @@ func TestCheckProducesDiff(t *testing.T) {
|
||||
{Type: "A", Name: "a.example.com.", TTL: 300, Values: []string{"1.1.1.1"}}, // update
|
||||
}}
|
||||
svc, _ := setup(t, actual, tmpl)
|
||||
cs, err := svc.Check(context.Background(), uuid.New())
|
||||
cs, err := svc.Check(context.Background(), uuid.New(), uuid.New())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -87,7 +89,7 @@ func TestApplyRespectsPruneGuard(t *testing.T) {
|
||||
|
||||
// applyPrunes=false → удаление b НЕ применяется
|
||||
svc, fp := setup(t, actual, tmpl)
|
||||
if _, err := svc.Apply(context.Background(), uuid.New(), ApplyRequest{ApplyUpdates: true, ApplyPrunes: false}); err != nil {
|
||||
if _, err := svc.Apply(context.Background(), uuid.New(), uuid.New(), ApplyRequest{ApplyUpdates: true, ApplyPrunes: false}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, d := range fp.applied.Diffs {
|
||||
@@ -98,7 +100,7 @@ func TestApplyRespectsPruneGuard(t *testing.T) {
|
||||
|
||||
// applyPrunes=true → удаление b применяется
|
||||
svc2, fp2 := setup(t, actual, tmpl)
|
||||
if _, err := svc2.Apply(context.Background(), uuid.New(), ApplyRequest{ApplyUpdates: true, ApplyPrunes: true}); err != nil {
|
||||
if _, err := svc2.Apply(context.Background(), uuid.New(), uuid.New(), ApplyRequest{ApplyUpdates: true, ApplyPrunes: true}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var sawDelete bool
|
||||
|
||||
Reference in New Issue
Block a user