72 lines
2.7 KiB
Go
72 lines
2.7 KiB
Go
package auth
|
|
|
|
import (
|
|
"context"
|
|
|
|
"git.sendico.io/tech/sxchnge/bff/pkg/merrors"
|
|
"git.sendico.io/tech/sxchnge/bff/pkg/model"
|
|
"git.sendico.io/tech/sxchnge/bff/pkg/mservice"
|
|
"go.mongodb.org/mongo-driver/v2/bson"
|
|
)
|
|
|
|
// ScopedAccessParams bundles everything needed to authorize an action against a
|
|
// resource that follows the personal-vs-org ownership model.
|
|
//
|
|
// It is deliberately a plain struct (not an interface over a model) so it works
|
|
// both for locally-stored objects and for objects whose ownership is learned
|
|
// from a downstream service response.
|
|
type ScopedAccessParams struct {
|
|
// Service is the resource type, used only to build the access-denied error.
|
|
Service mservice.Type
|
|
// PolicyRef is the org policy consulted when the object is org-owned.
|
|
PolicyRef bson.ObjectID
|
|
// CallerRef is the subject taken from the auth token. Never from the request body.
|
|
CallerRef bson.ObjectID
|
|
// OrgRef is the organization the object belongs to. For org-owned objects
|
|
// caller membership in it is checked before policy enforcement. Personal
|
|
// objects are deliberately exempt from the membership check so the owner
|
|
// keeps access after leaving the organization; membership for personal
|
|
// objects is enforced once, at create time (see ScopedDBImp.Create).
|
|
OrgRef bson.ObjectID
|
|
// ObjectRef is the concrete object being accessed. Passing it enables
|
|
// object-level checks in the Enforcer; use bson.NilObjectID for scope-only.
|
|
ObjectRef bson.ObjectID
|
|
// OwnerKind / OwnerRef describe the object's actual ownership, read FROM the
|
|
// object — not derived from the request.
|
|
OwnerKind model.OwnerKind
|
|
OwnerRef bson.ObjectID
|
|
// Action is the operation being authorized.
|
|
Action model.Action
|
|
}
|
|
|
|
// enforceOwnershipAccess decides access based on ownership and policy only.
|
|
// It does NOT check organization membership — the caller must verify membership
|
|
// before invoking this. Use ScopedAuthorizer.Authorize for the full
|
|
// membership-aware access check.
|
|
//
|
|
// - personal (OwnerKindAccount): allowed iff OwnerRef == CallerRef.
|
|
// - org (OwnerKindOrganization): allowed iff the Enforcer grants Action within OrgRef.
|
|
// - anything else: denied by default.
|
|
func enforceOwnershipAccess(ctx context.Context, enf Enforcer, p ScopedAccessParams) error {
|
|
switch p.OwnerKind {
|
|
case model.OwnerKindAccount:
|
|
if p.OwnerRef == p.CallerRef {
|
|
return nil
|
|
}
|
|
return merrors.AccessDenied(p.Service, string(p.Action), p.ObjectRef)
|
|
|
|
case model.OwnerKindOrganization:
|
|
ok, err := enf.Enforce(ctx, p.PolicyRef, p.CallerRef, p.OrgRef, p.ObjectRef, p.Action)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !ok {
|
|
return merrors.AccessDenied(p.Service, string(p.Action), p.ObjectRef)
|
|
}
|
|
return nil
|
|
|
|
default:
|
|
return merrors.AccessDenied(p.Service, string(p.Action), p.ObjectRef)
|
|
}
|
|
}
|