add users logic
This commit is contained in:
@@ -53,6 +53,7 @@ func (uc *CreateJob) Execute(ctx context.Context, in CreateJobInput) (*domain.Jo
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
job.OwnerID = ownerFromContext(ctx)
|
||||
|
||||
err = uc.tx.WithinTx(ctx, func(ctx context.Context) error {
|
||||
if err := uc.jobs.Insert(ctx, job); err != nil {
|
||||
@@ -97,6 +98,9 @@ func (uc *CancelJob) Execute(ctx context.Context, jobID uuid.UUID) (int64, error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := authorizeJobAccess(ctx, job); err != nil {
|
||||
return err
|
||||
}
|
||||
if job.Status == domain.JobCancelled {
|
||||
return nil
|
||||
}
|
||||
@@ -132,6 +136,9 @@ func (uc *GetJobStatus) Execute(ctx context.Context, jobID uuid.UUID) (domain.Jo
|
||||
if err != nil {
|
||||
return domain.JobProgress{}, err
|
||||
}
|
||||
if err := authorizeJobAccess(ctx, job); err != nil {
|
||||
return domain.JobProgress{}, err
|
||||
}
|
||||
counts, err := uc.tasks.CountByStatus(ctx, jobID)
|
||||
if err != nil {
|
||||
return domain.JobProgress{}, err
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package usecase
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/emil28092005/SciMesh/coordinator/internal/authctx"
|
||||
"github.com/emil28092005/SciMesh/coordinator/internal/domain"
|
||||
)
|
||||
|
||||
// ownerFromContext returns the authenticated user id to stamp on a new job, or
|
||||
// nil when the request was not authenticated as a user — worker or legacy
|
||||
// traffic, or user-JWT auth disabled. A nil owner is stored as NULL.
|
||||
func ownerFromContext(ctx context.Context) *uuid.UUID {
|
||||
if r, ok := authctx.From(ctx); ok {
|
||||
id := r.UserID
|
||||
return &id
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// authorizeJobAccess enforces that a non-admin user may only act on their own
|
||||
// job. It returns ErrJobNotFound — not a 403 — on a mismatch, so the response
|
||||
// never reveals that another user's job exists.
|
||||
//
|
||||
// Requests with no authenticated user (worker/legacy traffic, or JWT auth
|
||||
// disabled) are not restricted here: the shared service token already gated
|
||||
// them, and worker endpoints legitimately operate across all jobs.
|
||||
func authorizeJobAccess(ctx context.Context, job *domain.Job) error {
|
||||
r, ok := authctx.From(ctx)
|
||||
if !ok || r.IsAdmin() {
|
||||
return nil
|
||||
}
|
||||
if job.OwnerID == nil || *job.OwnerID != r.UserID {
|
||||
return domain.ErrJobNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package usecase
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/emil28092005/SciMesh/coordinator/internal/authctx"
|
||||
"github.com/emil28092005/SciMesh/coordinator/internal/domain"
|
||||
)
|
||||
|
||||
func TestOwnerFromContext(t *testing.T) {
|
||||
if ownerFromContext(context.Background()) != nil {
|
||||
t.Error("no requester must yield a nil owner")
|
||||
}
|
||||
id := uuid.New()
|
||||
ctx := authctx.With(context.Background(), authctx.Requester{UserID: id, Role: "user"})
|
||||
got := ownerFromContext(ctx)
|
||||
if got == nil || *got != id {
|
||||
t.Errorf("owner = %v, want %v", got, id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthorizeJobAccess(t *testing.T) {
|
||||
owner := uuid.New()
|
||||
other := uuid.New()
|
||||
job := &domain.Job{ID: uuid.New(), OwnerID: &owner}
|
||||
|
||||
ctxOf := func(id uuid.UUID, role string) context.Context {
|
||||
return authctx.With(context.Background(), authctx.Requester{UserID: id, Role: role})
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
wantErr bool
|
||||
}{
|
||||
{"no requester (worker/legacy) allowed", context.Background(), false},
|
||||
{"owner allowed", ctxOf(owner, "user"), false},
|
||||
{"admin allowed", ctxOf(other, "admin"), false},
|
||||
{"non-owner denied", ctxOf(other, "user"), true},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := authorizeJobAccess(tc.ctx, job)
|
||||
if tc.wantErr {
|
||||
if !errors.Is(err, domain.ErrJobNotFound) {
|
||||
t.Errorf("got %v, want ErrJobNotFound", err)
|
||||
}
|
||||
} else if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthorizeJobAccessNilOwner(t *testing.T) {
|
||||
// A legacy job with no owner must not be readable by an arbitrary user.
|
||||
job := &domain.Job{ID: uuid.New(), OwnerID: nil}
|
||||
ctx := authctx.With(context.Background(), authctx.Requester{UserID: uuid.New(), Role: "user"})
|
||||
if err := authorizeJobAccess(ctx, job); !errors.Is(err, domain.ErrJobNotFound) {
|
||||
t.Errorf("got %v, want ErrJobNotFound", err)
|
||||
}
|
||||
}
|
||||
@@ -126,6 +126,9 @@ func (uc *GetJobResult) Execute(ctx context.Context, jobID uuid.UUID) (*domain.A
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := authorizeJobAccess(ctx, job); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if job.Status != domain.JobCompleted || job.ResultArtifactID == nil {
|
||||
return nil, nil, domain.ErrArtifactNotFound
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ func (uc *SubmitDataset) Execute(ctx context.Context, in SubmitDatasetInput) (Su
|
||||
if err != nil {
|
||||
return SubmitDatasetResult{}, err
|
||||
}
|
||||
job.OwnerID = ownerFromContext(ctx)
|
||||
|
||||
// Everything written to blob storage, so a failed transaction can undo it.
|
||||
var putKeys []string
|
||||
|
||||
Reference in New Issue
Block a user