Bootstrap copied BFF and local accounts with public Sendico contracts (#34)
This commit is contained in:
1 parent
67dc5b5b1e
commit
7062672a87
556 files changed
+42291
-777
No files matched your search
@@ -0,0 +1,129 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/db/repository"
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder"
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/merrors"
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/model"
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/mutil/mzap"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// Patch applies a partial update after verifying ActionUpdate.
|
||||
func (db *ScopedDBImp[T]) Patch(ctx context.Context, accountRef, objectRef bson.ObjectID, patch builder.Patch) error {
|
||||
db.DBImp.Logger.Debug("attempting to patch scoped object",
|
||||
mzap.AccRef(accountRef), mzap.ObjRef("object_ref", objectRef))
|
||||
|
||||
if err := denyImmutablePatch(patch); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := db.loadAndEnforce(ctx, accountRef, objectRef, model.ActionUpdate); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := db.DBImp.Patch(ctx, objectRef, patch); err != nil {
|
||||
db.DBImp.Logger.Warn("failed to patch scoped object",
|
||||
zap.Error(err), mzap.AccRef(accountRef), mzap.ObjRef("object_ref", objectRef))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetArchived patches the isArchived field after verifying ActionUpdate.
|
||||
//
|
||||
// organizationRef is accepted for interface compatibility but not used for
|
||||
// enforcement — ownership is read from the DB via LoadAndEnforce.
|
||||
// cascade is accepted but not applied; resources that require cascading archived
|
||||
// status must implement cascade logic in a dedicated wrapper.
|
||||
func (db *ScopedDBImp[T]) SetArchived(ctx context.Context, accountRef, _ bson.ObjectID, objectRef bson.ObjectID, isArchived, _ bool) error {
|
||||
db.DBImp.Logger.Debug("attempting to change archive status of scoped object",
|
||||
mzap.AccRef(accountRef), mzap.ObjRef("object_ref", objectRef),
|
||||
zap.Bool("archived", isArchived))
|
||||
|
||||
if _, err := db.loadAndEnforce(ctx, accountRef, objectRef, model.ActionUpdate); err != nil {
|
||||
return err
|
||||
}
|
||||
patch := repository.Patch().Set(repository.IsArchivedField(), isArchived)
|
||||
if err := db.DBImp.Patch(ctx, objectRef, patch); err != nil {
|
||||
db.DBImp.Logger.Warn("failed to change archive status of scoped object",
|
||||
zap.Error(err), mzap.AccRef(accountRef), mzap.ObjRef("object_ref", objectRef))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListIDs returns the authorized subset of IDs matching query for the given
|
||||
// action. Each candidate is checked via LoadAndEnforce; unauthorized IDs are
|
||||
// silently skipped. Non-access-denied errors abort the call.
|
||||
func (db *ScopedDBImp[T]) ListIDs(ctx context.Context, action model.Action, accountRef bson.ObjectID, query builder.Query) ([]bson.ObjectID, error) {
|
||||
db.DBImp.Logger.Debug("attempting to list scoped object IDs",
|
||||
mzap.AccRef(accountRef), zap.String("collection", db.Collection))
|
||||
|
||||
rawIDs, err := db.DBImp.ListIDs(ctx, query)
|
||||
if err != nil {
|
||||
db.DBImp.Logger.Warn("failed to list raw object IDs",
|
||||
zap.Error(err), mzap.AccRef(accountRef), zap.String("collection", db.Collection))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
allowed := make([]bson.ObjectID, 0, len(rawIDs))
|
||||
for _, id := range rawIDs {
|
||||
if _, authErr := db.loadAndEnforce(ctx, accountRef, id, action); authErr == nil {
|
||||
allowed = append(allowed, id)
|
||||
} else if !errors.Is(authErr, merrors.ErrAccessDenied) {
|
||||
db.DBImp.Logger.Warn("enforcer error during ListIDs",
|
||||
zap.Error(authErr), mzap.AccRef(accountRef), mzap.ObjRef("object_ref", id))
|
||||
return nil, authErr
|
||||
}
|
||||
// ErrAccessDenied: silently skip
|
||||
}
|
||||
|
||||
db.DBImp.Logger.Debug("completed ListIDs",
|
||||
mzap.AccRef(accountRef), zap.Int("total", len(rawIDs)), zap.Int("allowed", len(allowed)))
|
||||
return allowed, nil
|
||||
}
|
||||
|
||||
// PatchMany applies patch to all resources in query that accountRef is
|
||||
// authorized to update. Authorization is resolved via ListIDs; the write is a
|
||||
// single bulk operation using originalQuery AND _id IN (authorizedIDs) so
|
||||
// records that leave the original query between the read and write are not
|
||||
// inadvertently mutated.
|
||||
func (db *ScopedDBImp[T]) PatchMany(ctx context.Context, accountRef bson.ObjectID, query builder.Query, patch builder.Patch) (int, error) {
|
||||
db.DBImp.Logger.Debug("attempting to patch many scoped objects",
|
||||
mzap.AccRef(accountRef), zap.String("collection", db.Collection))
|
||||
|
||||
if err := denyImmutablePatch(patch); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
ids, err := db.ListIDs(ctx, model.ActionUpdate, accountRef, query)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(ids) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
values := make([]any, len(ids))
|
||||
for i, id := range ids {
|
||||
values[i] = id
|
||||
}
|
||||
idFilter := repository.Query().In(repository.IDField(), values...)
|
||||
finalQuery := idFilter
|
||||
if query != nil {
|
||||
finalQuery = query.And(idFilter)
|
||||
}
|
||||
modified, err := db.DBImp.PatchMany(ctx, finalQuery, patch)
|
||||
if err != nil {
|
||||
db.DBImp.Logger.Warn("failed to patch many scoped objects",
|
||||
zap.Error(err), mzap.AccRef(accountRef))
|
||||
return 0, err
|
||||
}
|
||||
|
||||
db.DBImp.Logger.Debug("patched many scoped objects",
|
||||
mzap.AccRef(accountRef), zap.Int("count", modified))
|
||||
return modified, nil
|
||||
}
|
||||
Reference in new issue
Block a user