Files
threshold/internal/iam/repository.go
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

323 lines
10 KiB
Go
Raw Normal View History

2020-08-07 12:55:02 -04:00
// Copyright (c) HashiCorp, Inc.
2020-06-16 18:59:30 -04:00
// SPDX-License-Identifier: BUSL-1.1
2023-02-02 17:15:17 -08:00
package iam
import (
"context"
"fmt"
2020-05-31 19:55:56 -04:00
"strings"
2020-08-10 17:15:02 -04:00
"github.com/hashicorp/boundary/internal/db"
2020-12-23 11:00:00 -08:00
"github.com/hashicorp/boundary/internal/errors"
2020-08-19 16:47:18 -04:00
"github.com/hashicorp/boundary/internal/kms"
2020-08-10 17:15:02 -04:00
"github.com/hashicorp/boundary/internal/oplog"
"github.com/hashicorp/boundary/internal/types/scope"
"github.com/hashicorp/boundary/internal/util"
)
var ErrMetadataScopeNotFound = errors.New(context.Background(), errors.RecordNotFound, "iam", "scope not found for metadata", errors.WithoutEvent())
// Repository is the iam database repository
type Repository struct {
2020-08-19 16:47:18 -04:00
reader db.Reader
writer db.Writer
kms *kms.Kms
2020-06-16 18:59:30 -04:00
// defaultLimit provides a default for limiting the number of results returned from the repo
defaultLimit int
}
2020-06-16 18:59:30 -04:00
// NewRepository creates a new iam Repository. Supports the options: WithLimit
// which sets a default limit on results returned by repo operations.
func NewRepository(ctx context.Context, r db.Reader, w db.Writer, kms *kms.Kms, opt ...Option) (*Repository, error) {
2020-12-23 11:00:00 -08:00
const op = "iam.NewRepository"
if r == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "nil reader")
}
if w == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "nil writer")
}
2020-08-19 16:47:18 -04:00
if kms == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "nil kms")
}
2020-06-16 18:59:30 -04:00
opts := getOpts(opt...)
if opts.withLimit == 0 {
2020-08-10 17:15:02 -04:00
// zero signals the boundary defaults should be used.
2020-06-16 18:59:30 -04:00
opts.withLimit = db.DefaultLimit
}
return &Repository{
2020-06-16 18:59:30 -04:00
reader: r,
writer: w,
2020-08-19 16:47:18 -04:00
kms: kms,
2020-06-16 18:59:30 -04:00
defaultLimit: opts.withLimit,
}, nil
}
2020-06-16 18:59:30 -04:00
// list will return a listing of resources and honor the WithLimit option or the
// repo defaultLimit
2024-01-23 14:33:09 -05:00
//
// Supported options: WithLimit, WithReaderWriter
2022-10-25 18:29:38 -05:00
func (r *Repository) list(ctx context.Context, resources any, where string, args []any, opt ...Option) error {
2020-06-16 18:59:30 -04:00
opts := getOpts(opt...)
limit := r.defaultLimit
if opts.withLimit != 0 {
// non-zero signals an override of the default limit for the repo.
limit = opts.withLimit
}
2024-01-23 14:33:09 -05:00
reader := r.reader
if !util.IsNil(opts.withReader) {
2024-01-23 14:33:09 -05:00
reader = opts.withReader
}
return reader.SearchWhere(ctx, resources, where, args, db.WithLimit(limit))
2020-06-16 18:59:30 -04:00
}
// create will create a new iam resource in the db repository with an oplog entry
2024-01-23 14:33:09 -05:00
//
// Supported options: WithReaderWriter
func (r *Repository) create(ctx context.Context, resource Resource, opt ...Option) (Resource, error) {
2020-12-23 11:00:00 -08:00
const op = "iam.(Repository).create"
if resource == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing resource")
}
2020-08-07 12:55:02 -04:00
resourceCloner, ok := resource.(Cloneable)
if !ok {
return nil, errors.New(ctx, errors.InvalidParameter, op, "resource is not Cloneable")
}
metadata, err := r.stdMetadata(ctx, resource)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error getting metadata"))
}
metadata["op-type"] = []string{oplog.OpType_OP_TYPE_CREATE.String()}
2020-08-19 16:47:18 -04:00
scope, err := resource.GetScope(ctx, r.reader)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get scope"))
2020-08-19 16:47:18 -04:00
}
oplogWrapper, err := r.kms.GetWrapper(ctx, scope.GetPublicId(), kms.KeyPurposeOplog)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper"))
2020-08-19 16:47:18 -04:00
}
2024-01-23 14:33:09 -05:00
returnedResource := resourceCloner.Clone()
opts := getOpts(opt...)
if opts.withWriter != nil {
err = opts.withWriter.Create(ctx, returnedResource, db.WithOplog(oplogWrapper, metadata))
} else {
_, err = r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) error {
return w.Create(ctx, returnedResource, db.WithOplog(oplogWrapper, metadata))
},
)
}
2020-12-23 11:00:00 -08:00
if err != nil {
return nil, errors.Wrap(ctx, err, op)
2020-12-23 11:00:00 -08:00
}
return returnedResource.(Resource), nil
}
// update will update an iam resource in the db repository with an oplog entry
2024-01-23 14:33:09 -05:00
//
// Supported options: WithReaderWriter
2020-08-07 12:55:02 -04:00
func (r *Repository) update(ctx context.Context, resource Resource, version uint32, fieldMaskPaths []string, setToNullPaths []string, opt ...Option) (Resource, int, error) {
2020-12-23 11:00:00 -08:00
const op = "iam.(Repository).update"
2020-08-07 12:55:02 -04:00
if version == 0 {
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing version")
2020-08-07 12:55:02 -04:00
}
if resource == nil {
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing resource")
}
2020-08-07 12:55:02 -04:00
resourceCloner, ok := resource.(Cloneable)
if !ok {
return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "resource is not Cloneable")
}
metadata, err := r.stdMetadata(ctx, resource)
if err != nil {
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("error getting metadata"))
}
metadata["op-type"] = []string{oplog.OpType_OP_TYPE_UPDATE.String()}
2020-08-07 12:55:02 -04:00
dbOpts := []db.Option{
db.WithVersion(&version),
}
opts := getOpts(opt...)
if opts.withSkipVetForWrite {
dbOpts = append(dbOpts, db.WithSkipVetForWrite(true))
}
2024-01-23 14:33:09 -05:00
reader := r.reader
writer := r.writer
needFreshReaderWriter := true
if !util.IsNil(opts.withReader) && !util.IsNil(opts.withWriter) {
2024-01-23 14:33:09 -05:00
reader = opts.withReader
writer = opts.withWriter
if !writer.IsTx(ctx) {
return nil, db.NoRowsAffected, errors.New(ctx, errors.Internal, op, "writer is not in transaction")
}
needFreshReaderWriter = false
}
2020-08-19 16:47:18 -04:00
var scope *Scope
switch t := resource.(type) {
case *Scope:
scope = t
default:
scope, err = resource.GetScope(ctx, r.reader)
if err != nil {
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get scope"))
2020-08-19 16:47:18 -04:00
}
}
oplogWrapper, err := r.kms.GetWrapper(ctx, scope.GetPublicId(), kms.KeyPurposeOplog)
if err != nil {
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper"))
2020-08-19 16:47:18 -04:00
}
dbOpts = append(dbOpts, db.WithOplog(oplogWrapper, metadata))
var rowsUpdated int
2022-10-25 18:29:38 -05:00
var returnedResource any
2024-01-23 14:33:09 -05:00
txFunc := func(rdr db.Reader, wtr db.Writer) error {
returnedResource = resourceCloner.Clone()
rowsUpdated, err = wtr.Update(
ctx,
returnedResource,
fieldMaskPaths,
setToNullPaths,
dbOpts...,
)
if err != nil {
return errors.Wrap(ctx, err, op)
}
if rowsUpdated > 1 {
// return err, which will result in a rollback of the update
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been updated")
}
return nil
}
if !needFreshReaderWriter {
err = txFunc(reader, writer)
} else {
_, err = r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
txFunc,
)
}
2020-12-23 11:00:00 -08:00
if err != nil {
return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op)
2020-12-23 11:00:00 -08:00
}
return returnedResource.(Resource), rowsUpdated, nil
}
// delete will delete an iam resource in the db repository with an oplog entry
2020-12-23 11:00:00 -08:00
func (r *Repository) delete(ctx context.Context, resource Resource, _ ...Option) (int, error) {
const op = "iam.(Repository).delete"
if resource == nil {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing resource")
}
2020-08-07 12:55:02 -04:00
resourceCloner, ok := resource.(Cloneable)
if !ok {
return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "resource is not Cloneable")
}
metadata, err := r.stdMetadata(ctx, resource)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("error getting metadata"))
}
metadata["op-type"] = []string{oplog.OpType_OP_TYPE_DELETE.String()}
2020-08-19 16:47:18 -04:00
scope, err := resource.GetScope(ctx, r.reader)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get scope"))
2020-08-19 16:47:18 -04:00
}
oplogWrapper, err := r.kms.GetWrapper(ctx, scope.GetPublicId(), kms.KeyPurposeOplog)
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper"))
2020-08-19 16:47:18 -04:00
}
var rowsDeleted int
2022-10-25 18:29:38 -05:00
var deleteResource any
_, err = r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(_ db.Reader, w db.Writer) error {
deleteResource = resourceCloner.Clone()
rowsDeleted, err = w.Delete(
ctx,
deleteResource,
2020-08-19 16:47:18 -04:00
db.WithOplog(oplogWrapper, metadata),
)
2020-12-23 11:00:00 -08:00
if err != nil {
return errors.Wrap(ctx, err, op)
2020-12-23 11:00:00 -08:00
}
if rowsDeleted > 1 {
// return err, which will result in a rollback of the delete
return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been deleted")
}
2020-12-23 11:00:00 -08:00
return nil
},
)
2020-12-23 11:00:00 -08:00
if err != nil {
return db.NoRowsAffected, errors.Wrap(ctx, err, op)
2020-12-23 11:00:00 -08:00
}
return rowsDeleted, nil
}
func (r *Repository) stdMetadata(ctx context.Context, resource Resource) (oplog.Metadata, error) {
2020-12-23 11:00:00 -08:00
const op = "iam.(Repository).stdMetadata"
if s, ok := resource.(*Scope); ok {
2021-04-08 20:54:32 -04:00
newScope := AllocScope()
newScope.PublicId = s.PublicId
newScope.Type = s.Type
if newScope.Type == "" {
if err := r.reader.LookupByPublicId(ctx, &newScope); err != nil {
return nil, errors.Wrap(ctx, ErrMetadataScopeNotFound, op)
}
}
switch newScope.Type {
2021-04-08 20:54:32 -04:00
case scope.Global.String(), scope.Org.String():
return oplog.Metadata{
"resource-public-id": []string{resource.GetPublicId()},
"scope-id": []string{newScope.PublicId},
"scope-type": []string{newScope.Type},
"resource-type": []string{resource.GetResourceType().String()},
}, nil
case scope.Project.String():
return oplog.Metadata{
"resource-public-id": []string{resource.GetPublicId()},
"scope-id": []string{newScope.ParentId},
"scope-type": []string{newScope.Type},
"resource-type": []string{resource.GetResourceType().String()},
}, nil
default:
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("not a supported scope for metadata: %s", s.Type))
}
}
scope, err := resource.GetScope(ctx, r.reader)
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get scope"))
}
if scope == nil {
return nil, errors.E(ctx, errors.WithOp(op), errors.WithMsg("nil scope"))
}
return oplog.Metadata{
"resource-public-id": []string{resource.GetPublicId()},
"scope-id": []string{scope.PublicId},
"scope-type": []string{scope.Type},
"resource-type": []string{resource.GetResourceType().String()},
}, nil
}
2020-05-31 19:55:56 -04:00
func contains(ss []string, t string) bool {
for _, s := range ss {
if strings.EqualFold(s, t) {
return true
}
}
return false
}