Managed Groups OIDC Repo (#1254)
This commit is contained in:
1 parent
15bd1a5245
commit
7da892a054
17 files changed
+1660
-45
No files matched your search
@@ -116,7 +116,10 @@ func TestFetchActionSetForId(t *testing.T) {
|
||||
|
||||
func TestRecursiveListingDifferentOutputFields(t *testing.T) {
|
||||
require, assert := require.New(t), assert.New(t)
|
||||
tc := controller.NewTestController(t, nil)
|
||||
tc := controller.NewTestController(t, &controller.TestControllerOpts{
|
||||
// Disable this to avoid having to deal with sorting them in the test
|
||||
DisableOidcAuthMethodCreation: true,
|
||||
})
|
||||
defer tc.Shutdown()
|
||||
|
||||
conn := tc.DbConn()
|
||||
|
||||
@@ -10,6 +10,8 @@ const (
|
||||
AuthMethodPrefix = "amoidc"
|
||||
// AccountPrefix defines the prefix for Account public ids.
|
||||
AccountPrefix = "acctoidc"
|
||||
// ManagedGroupPrefix defines the prefix for ManagedGroup public ids.
|
||||
ManagedGroupPrefix = "mgoidc"
|
||||
)
|
||||
|
||||
func newAuthMethodId() (string, error) {
|
||||
@@ -38,3 +40,12 @@ func newAccountId(authMethodId, issuer, sub string) (string, error) {
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func newManagedGroupId() (string, error) {
|
||||
const op = "oidc.newManagedGroupId"
|
||||
id, err := db.NewPublicId(ManagedGroupPrefix)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, op)
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package oidc
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/boundary/internal/auth/oidc/store"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/oplog"
|
||||
"github.com/hashicorp/go-bexpr"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// defaultManagedGroupTableName defines the default table name for a Managed Group
|
||||
const defaultManagedGroupTableName = "auth_oidc_managed_group"
|
||||
|
||||
// ManagedGroup contains an OIDC managed group. It is assigned to an OIDC AuthMethod
|
||||
// and updates/deletes to that AuthMethod are cascaded to its Managed Groups.
|
||||
type ManagedGroup struct {
|
||||
*store.ManagedGroup
|
||||
tableName string
|
||||
}
|
||||
|
||||
// NewManagedGroup creates a new in memory ManagedGroup assigned to OIDC
|
||||
// AuthMethod. Supported options are withName and withDescription.
|
||||
func NewManagedGroup(authMethodId string, filter string, opt ...Option) (*ManagedGroup, error) {
|
||||
const op = "oidc.NewManagedGroup"
|
||||
opts := getOpts(opt...)
|
||||
mg := &ManagedGroup{
|
||||
ManagedGroup: &store.ManagedGroup{
|
||||
AuthMethodId: authMethodId,
|
||||
Name: opts.withName,
|
||||
Description: opts.withDescription,
|
||||
Filter: filter,
|
||||
},
|
||||
}
|
||||
if err := mg.validate(op); err != nil {
|
||||
return nil, err // intentionally not wrapped.
|
||||
}
|
||||
|
||||
return mg, nil
|
||||
}
|
||||
|
||||
// validate the Managed Group. On success, it will return nil.
|
||||
func (mg *ManagedGroup) validate(caller errors.Op) error {
|
||||
if mg.AuthMethodId == "" {
|
||||
return errors.New(errors.InvalidParameter, caller, "missing auth method id")
|
||||
}
|
||||
if mg.Filter == "" {
|
||||
return errors.New(errors.InvalidParameter, caller, "missing filter")
|
||||
}
|
||||
if _, err := bexpr.CreateEvaluator(mg.Filter); err != nil {
|
||||
return errors.New(errors.InvalidParameter, caller, "error evaluating filter expression", errors.WithWrap(err))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AllocManagedGroup makes an empty one in memory
|
||||
func AllocManagedGroup() *ManagedGroup {
|
||||
return &ManagedGroup{
|
||||
ManagedGroup: &store.ManagedGroup{},
|
||||
}
|
||||
}
|
||||
|
||||
// Clone a ManagedGroup.
|
||||
func (mg *ManagedGroup) Clone() *ManagedGroup {
|
||||
cp := proto.Clone(mg.ManagedGroup)
|
||||
return &ManagedGroup{
|
||||
ManagedGroup: cp.(*store.ManagedGroup),
|
||||
}
|
||||
}
|
||||
|
||||
// TableName returns the table name.
|
||||
func (mg *ManagedGroup) TableName() string {
|
||||
if mg.tableName != "" {
|
||||
return mg.tableName
|
||||
}
|
||||
return defaultManagedGroupTableName
|
||||
}
|
||||
|
||||
// SetTableName sets the table name.
|
||||
func (mg *ManagedGroup) SetTableName(n string) {
|
||||
mg.tableName = n
|
||||
}
|
||||
|
||||
// oplog will create oplog metadata for the ManagedGroup.
|
||||
func (c *ManagedGroup) oplog(op oplog.OpType, authMethodScopeId string) oplog.Metadata {
|
||||
metadata := oplog.Metadata{
|
||||
"resource-public-id": []string{c.GetPublicId()},
|
||||
"resource-type": []string{"oidc managed group"},
|
||||
"op-type": []string{op.String()},
|
||||
}
|
||||
if c.AuthMethodId != "" {
|
||||
metadata["auth-method-id"] = []string{c.AuthMethodId}
|
||||
}
|
||||
if authMethodScopeId != "" {
|
||||
metadata["scope-id"] = []string{authMethodScopeId}
|
||||
}
|
||||
return metadata
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package oidc
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_ManagedGroups_RepoValidate(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert := assert.New(t)
|
||||
const op = "test"
|
||||
mg := AllocManagedGroup()
|
||||
t.Run("empty auth method", func(t *testing.T) {
|
||||
assert.Contains(mg.validate(op).Error(), errors.New(errors.InvalidParameter, op, "missing auth method id").Error())
|
||||
})
|
||||
t.Run("empty filter", func(t *testing.T) {
|
||||
mg.AuthMethodId = "amoidc_1234567890"
|
||||
assert.Contains(mg.validate(op).Error(), errors.New(errors.InvalidParameter, op, "missing filter").Error())
|
||||
})
|
||||
t.Run("bad filter", func(t *testing.T) {
|
||||
mg.AuthMethodId = "amoidc_1234567890"
|
||||
mg.Filter = "foobar"
|
||||
assert.Contains(mg.validate(op).Error(), errors.New(errors.InvalidParameter, op, "error evaluating filter expression").Error())
|
||||
})
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
mg.AuthMethodId = "amoidc_1234567890"
|
||||
mg.Filter = testFakeFilter
|
||||
assert.NoError(mg.validate(op))
|
||||
})
|
||||
}
|
||||
@@ -67,7 +67,7 @@ func TestRepository_CreateAccount(t *testing.T) {
|
||||
wantErrMsg: "oidc.(Repository).CreateAccount: missing embedded Account: parameter violation: error #100",
|
||||
},
|
||||
{
|
||||
name: "invalid-no-scope-id",
|
||||
name: "invalid-no-auth-method-id",
|
||||
in: &Account{
|
||||
Account: &store.Account{},
|
||||
},
|
||||
|
||||
@@ -21,6 +21,7 @@ const (
|
||||
VersionField = "Version"
|
||||
NameField = "Name"
|
||||
DescriptionField = "Description"
|
||||
FilterField = "Filter"
|
||||
IssuerField = "Issuer"
|
||||
ClientIdField = "ClientId"
|
||||
ClientSecretField = "ClientSecret"
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
package oidc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
dbcommon "github.com/hashicorp/boundary/internal/db/common"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/oplog"
|
||||
)
|
||||
|
||||
// CreateManagedGroup inserts an ManagedGroup, mg, into the repository and
|
||||
// returns a new ManagedGroup containing its PublicId. mg is not changed. mg
|
||||
// must contain a valid AuthMethodId. mg must not contain a PublicId. The
|
||||
// PublicId is generated and assigned by this method.
|
||||
//
|
||||
// Both mg.Name and mg.Description are optional. If mg.Name is set, it must be
|
||||
// unique within mg.AuthMethodId.
|
||||
func (r *Repository) CreateManagedGroup(ctx context.Context, scopeId string, mg *ManagedGroup, opt ...Option) (*ManagedGroup, error) {
|
||||
const op = "oidc.(Repository).CreateManagedGroup"
|
||||
if mg == nil {
|
||||
return nil, errors.New(errors.InvalidParameter, op, "missing ManagedGroup")
|
||||
}
|
||||
if mg.ManagedGroup == nil {
|
||||
return nil, errors.New(errors.InvalidParameter, op, "missing embedded ManagedGroup")
|
||||
}
|
||||
if mg.AuthMethodId == "" {
|
||||
return nil, errors.New(errors.InvalidParameter, op, "missing auth method id")
|
||||
}
|
||||
if mg.Filter == "" {
|
||||
return nil, errors.New(errors.InvalidParameter, op, "missing filter")
|
||||
}
|
||||
if mg.PublicId != "" {
|
||||
return nil, errors.New(errors.InvalidParameter, op, "public id must be empty")
|
||||
}
|
||||
if scopeId == "" {
|
||||
return nil, errors.New(errors.InvalidParameter, op, "missing scope id")
|
||||
}
|
||||
|
||||
mg = mg.Clone()
|
||||
|
||||
id, err := newManagedGroupId()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op)
|
||||
}
|
||||
mg.PublicId = id
|
||||
|
||||
oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper"), errors.WithCode(errors.Encrypt))
|
||||
}
|
||||
|
||||
var newManagedGroup *ManagedGroup
|
||||
_, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
|
||||
func(_ db.Reader, w db.Writer) error {
|
||||
newManagedGroup = mg.Clone()
|
||||
if err := w.Create(ctx, newManagedGroup, db.WithOplog(oplogWrapper, mg.oplog(oplog.OpType_OP_TYPE_CREATE, scopeId))); err != nil {
|
||||
return errors.Wrap(err, op)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
if errors.IsUniqueError(err) {
|
||||
return nil, errors.New(errors.NotUnique, op, fmt.Sprintf(
|
||||
"in auth method %s: name %q already exists",
|
||||
mg.AuthMethodId, mg.Name))
|
||||
}
|
||||
return nil, errors.Wrap(err, op, errors.WithMsg(mg.AuthMethodId))
|
||||
}
|
||||
return newManagedGroup, nil
|
||||
}
|
||||
|
||||
// LookupManagedGroup will look up a managed group in the repository. If the managed group is not
|
||||
// found, it will return nil, nil. All options are ignored.
|
||||
func (r *Repository) LookupManagedGroup(ctx context.Context, withPublicId string, opt ...Option) (*ManagedGroup, error) {
|
||||
const op = "oidc.(Repository).LookupManagedGroup"
|
||||
if withPublicId == "" {
|
||||
return nil, errors.New(errors.InvalidPublicId, op, "missing public id")
|
||||
}
|
||||
a := AllocManagedGroup()
|
||||
a.PublicId = withPublicId
|
||||
if err := r.reader.LookupByPublicId(ctx, a); err != nil {
|
||||
if errors.IsNotFoundError(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for %s", withPublicId)))
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// ListManagedGroups in an auth method and supports WithLimit option.
|
||||
func (r *Repository) ListManagedGroups(ctx context.Context, withAuthMethodId string, opt ...Option) ([]*ManagedGroup, error) {
|
||||
const op = "oidc.(Repository).ListManagedGroups"
|
||||
if withAuthMethodId == "" {
|
||||
return nil, errors.New(errors.InvalidParameter, op, "missing auth method id")
|
||||
}
|
||||
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
|
||||
}
|
||||
var mgs []*ManagedGroup
|
||||
err := r.reader.SearchWhere(ctx, &mgs, "auth_method_id = ?", []interface{}{withAuthMethodId}, db.WithLimit(limit))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op)
|
||||
}
|
||||
return mgs, nil
|
||||
}
|
||||
|
||||
// DeleteManagedGroup deletes the managed group for the provided id from the
|
||||
// repository returning a count of the number of records deleted. All options
|
||||
// are ignored.
|
||||
func (r *Repository) DeleteManagedGroup(ctx context.Context, scopeId, withPublicId string, opt ...Option) (int, error) {
|
||||
const op = "oidc.(Repository).DeleteManagedGroup"
|
||||
if withPublicId == "" {
|
||||
return db.NoRowsAffected, errors.New(errors.InvalidPublicId, op, "missing public id")
|
||||
}
|
||||
if scopeId == "" {
|
||||
return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing scope id")
|
||||
}
|
||||
mg := AllocManagedGroup()
|
||||
mg.PublicId = withPublicId
|
||||
|
||||
oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog)
|
||||
if err != nil {
|
||||
return db.NoRowsAffected, errors.Wrap(err, op, errors.WithCode(errors.Encrypt), errors.WithMsg("unable to get oplog wrapper"))
|
||||
}
|
||||
|
||||
var rowsDeleted int
|
||||
_, err = r.writer.DoTx(
|
||||
ctx,
|
||||
db.StdRetryCnt,
|
||||
db.ExpBackoff{},
|
||||
func(_ db.Reader, w db.Writer) (err error) {
|
||||
metadata := mg.oplog(oplog.OpType_OP_TYPE_DELETE, scopeId)
|
||||
dMg := mg.Clone()
|
||||
rowsDeleted, err = w.Delete(ctx, dMg, db.WithOplog(oplogWrapper, metadata))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, op)
|
||||
}
|
||||
if rowsDeleted > 1 {
|
||||
return errors.New(errors.MultipleRecords, op, "more than 1 resource would have been deleted")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(withPublicId))
|
||||
}
|
||||
|
||||
return rowsDeleted, nil
|
||||
}
|
||||
|
||||
// UpdateManagedGroup updates the repository entry for mg.PublicId with the
|
||||
// values in mg for the fields listed in fieldMaskPaths. It returns a new
|
||||
// ManagedGroup containing the updated values and a count of the number of
|
||||
// records updated. mg is not changed.
|
||||
//
|
||||
// mg must contain a valid PublicId. Only mg.Name, mg.Description, and mg.Filter
|
||||
// can be updated. If mg.Name is set to a non-empty string, it must be unique
|
||||
// within mg.AuthMethodId.
|
||||
//
|
||||
// An attribute of a will be set to NULL in the database if the attribute in a
|
||||
// is the zero value and it is included in fieldMaskPaths.
|
||||
func (r *Repository) UpdateManagedGroup(ctx context.Context, scopeId string, mg *ManagedGroup, version uint32, fieldMaskPaths []string, opt ...Option) (*ManagedGroup, int, error) {
|
||||
const op = "oidc.(Repository).UpdateManagedGroup"
|
||||
if mg == nil {
|
||||
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing ManagedGroup")
|
||||
}
|
||||
if mg.ManagedGroup == nil {
|
||||
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing embedded ManagedGroup")
|
||||
}
|
||||
if mg.PublicId == "" {
|
||||
return nil, db.NoRowsAffected, errors.New(errors.InvalidPublicId, op, "missing public id")
|
||||
}
|
||||
if version == 0 {
|
||||
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing version")
|
||||
}
|
||||
if scopeId == "" {
|
||||
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing scope id")
|
||||
}
|
||||
|
||||
for _, f := range fieldMaskPaths {
|
||||
switch {
|
||||
case strings.EqualFold(NameField, f):
|
||||
case strings.EqualFold(DescriptionField, f):
|
||||
case strings.EqualFold(FilterField, f):
|
||||
default:
|
||||
return nil, db.NoRowsAffected, errors.New(errors.InvalidFieldMask, op, f)
|
||||
}
|
||||
}
|
||||
var dbMask, nullFields []string
|
||||
dbMask, nullFields = dbcommon.BuildUpdatePaths(
|
||||
map[string]interface{}{
|
||||
NameField: mg.Name,
|
||||
DescriptionField: mg.Description,
|
||||
FilterField: mg.Filter,
|
||||
},
|
||||
fieldMaskPaths,
|
||||
nil,
|
||||
)
|
||||
if len(dbMask) == 0 && len(nullFields) == 0 {
|
||||
return nil, db.NoRowsAffected, errors.New(errors.EmptyFieldMask, op, "missing field mask")
|
||||
}
|
||||
|
||||
oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog)
|
||||
if err != nil {
|
||||
return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithCode(errors.Encrypt),
|
||||
errors.WithMsg(("unable to get oplog wrapper")))
|
||||
}
|
||||
|
||||
mg = mg.Clone()
|
||||
|
||||
metadata := mg.oplog(oplog.OpType_OP_TYPE_UPDATE, scopeId)
|
||||
|
||||
// TODO/FIXME: if the filter is updated, remove all account/mg associations
|
||||
|
||||
var rowsUpdated int
|
||||
var returnedManagedGroup *ManagedGroup
|
||||
_, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{},
|
||||
func(_ db.Reader, w db.Writer) error {
|
||||
returnedManagedGroup = mg.Clone()
|
||||
var err error
|
||||
rowsUpdated, err = w.Update(ctx, returnedManagedGroup, dbMask, nullFields, db.WithOplog(oplogWrapper, metadata), db.WithVersion(&version))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, op)
|
||||
}
|
||||
if rowsUpdated > 1 {
|
||||
return errors.New(errors.MultipleRecords, op, "more than 1 resource would have been updated")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
if errors.IsUniqueError(err) {
|
||||
return nil, db.NoRowsAffected, errors.New(errors.NotUnique, op,
|
||||
fmt.Sprintf("name %s already exists: %s", mg.Name, mg.PublicId))
|
||||
}
|
||||
return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(mg.PublicId))
|
||||
}
|
||||
|
||||
return returnedManagedGroup, rowsUpdated, nil
|
||||
}
|
||||
@@ -0,0 +1,972 @@
|
||||
package oidc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/auth/oidc/store"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
dbassert "github.com/hashicorp/boundary/internal/db/assert"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/oplog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const testFakeFilter = `"/foo" == "bar"`
|
||||
|
||||
func TestRepository_CreateManagedGroup(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
iamRepo := iam.TestRepo(t, conn, wrapper)
|
||||
org, _ := iam.TestScopes(t, iamRepo)
|
||||
|
||||
ctx := context.Background()
|
||||
databaseWrapper, err := kmsCache.GetWrapper(ctx, org.PublicId, kms.KeyPurposeDatabase)
|
||||
require.NoError(t, err)
|
||||
|
||||
authMethod := TestAuthMethod(
|
||||
t, conn, databaseWrapper, org.GetPublicId(), ActivePrivateState,
|
||||
"alice-rp", "fido",
|
||||
WithSigningAlgs(RS256),
|
||||
WithIssuer(TestConvertToUrls(t, "https://www.alice.com")[0]),
|
||||
WithApiUrl(TestConvertToUrls(t, "https://www.alice.com/callback")[0]),
|
||||
)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
scopeId string
|
||||
in *ManagedGroup
|
||||
opts []Option
|
||||
want *ManagedGroup
|
||||
wantIsErr errors.Code
|
||||
wantErrMsg string
|
||||
wantErrContains string
|
||||
}{
|
||||
{
|
||||
name: "nil-ManagedGroup",
|
||||
scopeId: org.GetPublicId(),
|
||||
wantIsErr: errors.InvalidParameter,
|
||||
wantErrMsg: "oidc.(Repository).CreateManagedGroup: missing ManagedGroup: parameter violation: error #100",
|
||||
},
|
||||
{
|
||||
name: "nil-embedded-ManagedGroup",
|
||||
scopeId: org.GetPublicId(),
|
||||
in: &ManagedGroup{},
|
||||
wantIsErr: errors.InvalidParameter,
|
||||
wantErrMsg: "oidc.(Repository).CreateManagedGroup: missing embedded ManagedGroup: parameter violation: error #100",
|
||||
},
|
||||
{
|
||||
name: "invalid-no-auth-method-id",
|
||||
scopeId: org.GetPublicId(),
|
||||
in: &ManagedGroup{
|
||||
ManagedGroup: &store.ManagedGroup{},
|
||||
},
|
||||
wantIsErr: errors.InvalidParameter,
|
||||
wantErrMsg: "oidc.(Repository).CreateManagedGroup: missing auth method id: parameter violation: error #100",
|
||||
},
|
||||
{
|
||||
name: "invalid-no-filter",
|
||||
scopeId: org.GetPublicId(),
|
||||
in: &ManagedGroup{
|
||||
ManagedGroup: &store.ManagedGroup{
|
||||
AuthMethodId: authMethod.PublicId,
|
||||
},
|
||||
},
|
||||
wantIsErr: errors.InvalidParameter,
|
||||
wantErrMsg: "oidc.(Repository).CreateManagedGroup: missing filter: parameter violation: error #100",
|
||||
},
|
||||
{
|
||||
name: "invalid-public-id-set",
|
||||
scopeId: org.GetPublicId(),
|
||||
in: &ManagedGroup{
|
||||
ManagedGroup: &store.ManagedGroup{
|
||||
AuthMethodId: authMethod.PublicId,
|
||||
Filter: testFakeFilter,
|
||||
PublicId: "mgoidc_OOOOOOOOOO",
|
||||
},
|
||||
},
|
||||
wantIsErr: errors.InvalidParameter,
|
||||
wantErrMsg: "oidc.(Repository).CreateManagedGroup: public id must be empty: parameter violation: error #100",
|
||||
},
|
||||
{
|
||||
name: "no-scope",
|
||||
in: &ManagedGroup{
|
||||
ManagedGroup: &store.ManagedGroup{
|
||||
AuthMethodId: authMethod.PublicId,
|
||||
Filter: testFakeFilter,
|
||||
},
|
||||
},
|
||||
wantIsErr: errors.InvalidParameter,
|
||||
wantErrMsg: "oidc.(Repository).CreateManagedGroup: missing scope id: parameter violation: error #100",
|
||||
},
|
||||
{
|
||||
name: "valid-no-options",
|
||||
scopeId: org.GetPublicId(),
|
||||
in: &ManagedGroup{
|
||||
ManagedGroup: &store.ManagedGroup{
|
||||
AuthMethodId: authMethod.PublicId,
|
||||
Filter: testFakeFilter,
|
||||
},
|
||||
},
|
||||
want: &ManagedGroup{
|
||||
ManagedGroup: &store.ManagedGroup{
|
||||
AuthMethodId: authMethod.PublicId,
|
||||
Filter: testFakeFilter,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid-with-name",
|
||||
scopeId: org.GetPublicId(),
|
||||
in: &ManagedGroup{
|
||||
ManagedGroup: &store.ManagedGroup{
|
||||
AuthMethodId: authMethod.PublicId,
|
||||
Filter: testFakeFilter,
|
||||
Name: "test-name-repo",
|
||||
},
|
||||
},
|
||||
want: &ManagedGroup{
|
||||
ManagedGroup: &store.ManagedGroup{
|
||||
AuthMethodId: authMethod.PublicId,
|
||||
Filter: testFakeFilter,
|
||||
Name: "test-name-repo",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid-with-description",
|
||||
scopeId: org.GetPublicId(),
|
||||
in: &ManagedGroup{
|
||||
ManagedGroup: &store.ManagedGroup{
|
||||
AuthMethodId: authMethod.PublicId,
|
||||
Filter: testFakeFilter,
|
||||
Description: ("test-description-repo"),
|
||||
Name: "myname",
|
||||
},
|
||||
},
|
||||
want: &ManagedGroup{
|
||||
ManagedGroup: &store.ManagedGroup{
|
||||
AuthMethodId: authMethod.PublicId,
|
||||
Filter: testFakeFilter,
|
||||
Description: ("test-description-repo"),
|
||||
Name: "myname",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "duplicate-name",
|
||||
scopeId: org.GetPublicId(),
|
||||
in: &ManagedGroup{
|
||||
ManagedGroup: &store.ManagedGroup{
|
||||
AuthMethodId: authMethod.PublicId,
|
||||
Filter: testFakeFilter,
|
||||
Description: ("test-description-repo"),
|
||||
Name: "myname",
|
||||
},
|
||||
},
|
||||
wantIsErr: errors.NotUnique,
|
||||
wantErrContains: `name "myname" already exists`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
repo, err := NewRepository(rw, rw, kmsCache)
|
||||
assert.NoError(err)
|
||||
require.NotNil(repo)
|
||||
got, err := repo.CreateManagedGroup(context.Background(), tt.scopeId, tt.in, tt.opts...)
|
||||
if tt.wantIsErr != 0 {
|
||||
require.Error(err)
|
||||
assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "Unexpected error %s", err)
|
||||
if tt.wantErrContains != "" {
|
||||
assert.True(strings.Contains(err.Error(), tt.wantErrContains))
|
||||
} else {
|
||||
assert.Equal(tt.wantErrMsg, err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.Empty(tt.in.PublicId)
|
||||
require.NotNil(got)
|
||||
assertPublicId(t, ManagedGroupPrefix, got.PublicId)
|
||||
assert.NotSame(tt.in, got)
|
||||
assert.Equal(tt.want.Name, got.Name)
|
||||
assert.Equal(tt.want.Description, got.Description)
|
||||
assert.Equal(got.CreateTime, got.UpdateTime)
|
||||
|
||||
assert.NoError(db.TestVerifyOplog(t, rw, got.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepository_LookupManagedGroup(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
iamRepo := iam.TestRepo(t, conn, wrapper)
|
||||
org, _ := iam.TestScopes(t, iamRepo)
|
||||
|
||||
ctx := context.Background()
|
||||
databaseWrapper, err := kmsCache.GetWrapper(ctx, org.PublicId, kms.KeyPurposeDatabase)
|
||||
require.NoError(t, err)
|
||||
|
||||
authMethod := TestAuthMethod(
|
||||
t, conn, databaseWrapper, org.PublicId, ActivePrivateState,
|
||||
"alice-rp", "fido",
|
||||
WithSigningAlgs(RS256),
|
||||
WithIssuer(TestConvertToUrls(t, "https://www.alice.com")[0]),
|
||||
WithApiUrl(TestConvertToUrls(t, "https://www.alice.com/callback")[0]),
|
||||
)
|
||||
mg := TestManagedGroup(t, conn, authMethod, testFakeFilter)
|
||||
|
||||
newMgId, err := newManagedGroupId()
|
||||
require.NoError(t, err)
|
||||
tests := []struct {
|
||||
name string
|
||||
in string
|
||||
want *ManagedGroup
|
||||
wantIsErr errors.Code
|
||||
wantErrMsg string
|
||||
}{
|
||||
{
|
||||
name: "With no public id",
|
||||
wantIsErr: errors.InvalidPublicId,
|
||||
wantErrMsg: "oidc.(Repository).LookupManagedGroup: missing public id: parameter violation: error #102",
|
||||
},
|
||||
{
|
||||
name: "With non existing mg id",
|
||||
in: newMgId,
|
||||
},
|
||||
{
|
||||
name: "With existing mg id",
|
||||
in: mg.GetPublicId(),
|
||||
want: mg,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
repo, err := NewRepository(rw, rw, kmsCache)
|
||||
assert.NoError(err)
|
||||
require.NotNil(repo)
|
||||
got, err := repo.LookupManagedGroup(context.Background(), tt.in)
|
||||
if tt.wantIsErr != 0 {
|
||||
assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "Unexpected error %s", err)
|
||||
assert.Equal(tt.wantErrMsg, err.Error())
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.EqualValues(tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepository_DeleteManagedGroup(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
|
||||
ctx := context.Background()
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
iamRepo := iam.TestRepo(t, conn, wrapper)
|
||||
org, _ := iam.TestScopes(t, iamRepo)
|
||||
|
||||
databaseWrapper, err := kmsCache.GetWrapper(ctx, org.PublicId, kms.KeyPurposeDatabase)
|
||||
require.NoError(t, err)
|
||||
authMethod := TestAuthMethod(
|
||||
t, conn, databaseWrapper, org.PublicId, ActivePrivateState,
|
||||
"alice-rp", "fido",
|
||||
WithSigningAlgs(RS256),
|
||||
WithIssuer(TestConvertToUrls(t, "https://www.alice.com")[0]),
|
||||
WithApiUrl(TestConvertToUrls(t, "https://www.alice.com/callback")[0]),
|
||||
)
|
||||
mg := TestManagedGroup(t, conn, authMethod, testFakeFilter)
|
||||
newMgId, err := newManagedGroupId()
|
||||
require.NoError(t, err)
|
||||
tests := []struct {
|
||||
name string
|
||||
scopeId string
|
||||
in string
|
||||
want int
|
||||
wantIsErr errors.Code
|
||||
wantErrMsg string
|
||||
}{
|
||||
{
|
||||
name: "With no scope id",
|
||||
scopeId: "",
|
||||
in: mg.GetPublicId(),
|
||||
wantIsErr: errors.InvalidParameter,
|
||||
wantErrMsg: "oidc.(Repository).DeleteManagedGroup: missing scope id: parameter violation: error #100",
|
||||
},
|
||||
{
|
||||
name: "With no public id",
|
||||
scopeId: org.GetPublicId(),
|
||||
wantIsErr: errors.InvalidPublicId,
|
||||
wantErrMsg: "oidc.(Repository).DeleteManagedGroup: missing public id: parameter violation: error #102",
|
||||
},
|
||||
{
|
||||
name: "With non existing managed group id",
|
||||
scopeId: org.GetPublicId(),
|
||||
in: newMgId,
|
||||
want: 0,
|
||||
},
|
||||
{
|
||||
name: "With existing managed group id",
|
||||
scopeId: org.GetPublicId(),
|
||||
in: mg.GetPublicId(),
|
||||
want: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
repo, err := NewRepository(rw, rw, kmsCache)
|
||||
assert.NoError(err)
|
||||
require.NotNil(repo)
|
||||
got, err := repo.DeleteManagedGroup(context.Background(), tt.scopeId, tt.in)
|
||||
if tt.wantIsErr != 0 {
|
||||
assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "Unexpected error %s", err)
|
||||
assert.Equal(tt.wantErrMsg, err.Error())
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.EqualValues(tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepository_ListManagedGroups(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
|
||||
ctx := context.Background()
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
iamRepo := iam.TestRepo(t, conn, wrapper)
|
||||
org, _ := iam.TestScopes(t, iamRepo)
|
||||
|
||||
databaseWrapper, err := kmsCache.GetWrapper(ctx, org.PublicId, kms.KeyPurposeDatabase)
|
||||
require.NoError(t, err)
|
||||
authMethod1 := TestAuthMethod(
|
||||
t, conn, databaseWrapper, org.PublicId, ActivePrivateState,
|
||||
"alice-rp", "fido",
|
||||
WithSigningAlgs(RS256),
|
||||
WithIssuer(TestConvertToUrls(t, "https://www.alice1.com")[0]),
|
||||
WithApiUrl(TestConvertToUrls(t, "https://www.alice.com/callback")[0]),
|
||||
)
|
||||
authMethod2 := TestAuthMethod(
|
||||
t, conn, databaseWrapper, org.PublicId, ActivePrivateState,
|
||||
"alice-rp", "fido",
|
||||
WithSigningAlgs(RS256),
|
||||
WithIssuer(TestConvertToUrls(t, "https://www.alice2.com")[0]),
|
||||
WithApiUrl(TestConvertToUrls(t, "https://www.alice.com/callback")[0]),
|
||||
)
|
||||
authMethod3 := TestAuthMethod(
|
||||
t, conn, databaseWrapper, org.PublicId, ActivePrivateState,
|
||||
"alice-rp", "fido",
|
||||
WithSigningAlgs(RS256),
|
||||
WithIssuer(TestConvertToUrls(t, "https://www.alice3.com")[0]),
|
||||
WithApiUrl(TestConvertToUrls(t, "https://www.alice.com/callback")[0]),
|
||||
)
|
||||
mgs1 := []*ManagedGroup{
|
||||
TestManagedGroup(t, conn, authMethod1, testFakeFilter),
|
||||
TestManagedGroup(t, conn, authMethod1, testFakeFilter),
|
||||
TestManagedGroup(t, conn, authMethod1, testFakeFilter),
|
||||
}
|
||||
sort.Slice(mgs1, func(i, j int) bool {
|
||||
return strings.Compare(mgs1[i].PublicId, mgs1[j].PublicId) < 0
|
||||
})
|
||||
|
||||
mgs2 := []*ManagedGroup{
|
||||
TestManagedGroup(t, conn, authMethod2, testFakeFilter),
|
||||
TestManagedGroup(t, conn, authMethod2, testFakeFilter),
|
||||
TestManagedGroup(t, conn, authMethod2, testFakeFilter),
|
||||
}
|
||||
sort.Slice(mgs2, func(i, j int) bool {
|
||||
return strings.Compare(mgs2[i].PublicId, mgs2[j].PublicId) < 0
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
in string
|
||||
opts []Option
|
||||
want []*ManagedGroup
|
||||
wantIsErr errors.Code
|
||||
wantErrMsg string
|
||||
}{
|
||||
{
|
||||
name: "With no auth method id",
|
||||
wantIsErr: errors.InvalidParameter,
|
||||
wantErrMsg: "oidc.(Repository).ListManagedGroups: missing auth method id: parameter violation: error #100",
|
||||
},
|
||||
{
|
||||
name: "With no managed groups",
|
||||
in: authMethod3.GetPublicId(),
|
||||
want: []*ManagedGroup{},
|
||||
},
|
||||
{
|
||||
name: "With first auth method id",
|
||||
in: authMethod1.GetPublicId(),
|
||||
want: mgs1,
|
||||
},
|
||||
{
|
||||
name: "With first auth method id",
|
||||
in: authMethod2.GetPublicId(),
|
||||
want: mgs2,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
repo, err := NewRepository(rw, rw, kmsCache)
|
||||
assert.NoError(err)
|
||||
require.NotNil(repo)
|
||||
got, err := repo.ListManagedGroups(context.Background(), tt.in, tt.opts...)
|
||||
if tt.wantIsErr != 0 {
|
||||
assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "Unexpected error %s", err)
|
||||
assert.Equal(tt.wantErrMsg, err.Error())
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
|
||||
sort.Slice(got, func(i, j int) bool {
|
||||
return strings.Compare(got[i].PublicId, got[j].PublicId) < 0
|
||||
})
|
||||
|
||||
assert.EqualValues(tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepository_ListManagedGroups_Limits(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
|
||||
ctx := context.Background()
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
iamRepo := iam.TestRepo(t, conn, wrapper)
|
||||
org, _ := iam.TestScopes(t, iamRepo)
|
||||
|
||||
databaseWrapper, err := kmsCache.GetWrapper(ctx, org.PublicId, kms.KeyPurposeDatabase)
|
||||
require.NoError(t, err)
|
||||
|
||||
am := TestAuthMethod(
|
||||
t, conn, databaseWrapper, org.PublicId, ActivePrivateState,
|
||||
"alice-rp", "fido",
|
||||
WithSigningAlgs(RS256),
|
||||
WithIssuer(TestConvertToUrls(t, "https://www.alice1.com")[0]),
|
||||
WithApiUrl(TestConvertToUrls(t, "https://www.alice.com/callback")[0]),
|
||||
)
|
||||
|
||||
mgCount := 10
|
||||
for i := 0; i < mgCount; i++ {
|
||||
TestManagedGroup(t, conn, am, fmt.Sprintf(`"/foo/%d" == "bar"`, i))
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
repoOpts []Option
|
||||
listOpts []Option
|
||||
wantLen int
|
||||
}{
|
||||
{
|
||||
name: "With no limits",
|
||||
wantLen: mgCount,
|
||||
},
|
||||
{
|
||||
name: "With repo limit",
|
||||
repoOpts: []Option{WithLimit(3)},
|
||||
wantLen: 3,
|
||||
},
|
||||
{
|
||||
name: "With negative repo limit",
|
||||
repoOpts: []Option{WithLimit(-1)},
|
||||
wantLen: mgCount,
|
||||
},
|
||||
{
|
||||
name: "With List limit",
|
||||
listOpts: []Option{WithLimit(3)},
|
||||
wantLen: 3,
|
||||
},
|
||||
{
|
||||
name: "With negative List limit",
|
||||
listOpts: []Option{WithLimit(-1)},
|
||||
wantLen: mgCount,
|
||||
},
|
||||
{
|
||||
name: "With repo smaller than list limit",
|
||||
repoOpts: []Option{WithLimit(2)},
|
||||
listOpts: []Option{WithLimit(6)},
|
||||
wantLen: 6,
|
||||
},
|
||||
{
|
||||
name: "With repo larger than list limit",
|
||||
repoOpts: []Option{WithLimit(6)},
|
||||
listOpts: []Option{WithLimit(2)},
|
||||
wantLen: 2,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
repo, err := NewRepository(rw, rw, kmsCache, tt.repoOpts...)
|
||||
assert.NoError(err)
|
||||
require.NotNil(repo)
|
||||
got, err := repo.ListManagedGroups(context.Background(), am.GetPublicId(), tt.listOpts...)
|
||||
require.NoError(err)
|
||||
assert.Len(got, tt.wantLen)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepository_UpdateManagedGroup(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
iamRepo := iam.TestRepo(t, conn, wrapper)
|
||||
org, _ := iam.TestScopes(t, iamRepo)
|
||||
|
||||
databaseWrapper, err := kmsCache.GetWrapper(ctx, org.PublicId, kms.KeyPurposeDatabase)
|
||||
require.NoError(t, err)
|
||||
am := TestAuthMethod(
|
||||
t, conn, databaseWrapper, org.PublicId, ActivePrivateState,
|
||||
"alice-rp", "fido",
|
||||
WithSigningAlgs(RS256),
|
||||
WithIssuer(TestConvertToUrls(t, "https://www.alice.com")[0]),
|
||||
WithApiUrl(TestConvertToUrls(t, "https://www.alice.com/callback")[0]),
|
||||
)
|
||||
|
||||
changeName := func(s string) func(*ManagedGroup) *ManagedGroup {
|
||||
return func(mg *ManagedGroup) *ManagedGroup {
|
||||
mg.Name = s
|
||||
return mg
|
||||
}
|
||||
}
|
||||
|
||||
changeDescription := func(s string) func(*ManagedGroup) *ManagedGroup {
|
||||
return func(mg *ManagedGroup) *ManagedGroup {
|
||||
mg.Description = s
|
||||
return mg
|
||||
}
|
||||
}
|
||||
|
||||
changeFilter := func(s string) func(*ManagedGroup) *ManagedGroup {
|
||||
return func(mg *ManagedGroup) *ManagedGroup {
|
||||
mg.Filter = s
|
||||
return mg
|
||||
}
|
||||
}
|
||||
|
||||
makeNil := func() func(*ManagedGroup) *ManagedGroup {
|
||||
return func(mg *ManagedGroup) *ManagedGroup {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
makeEmbeddedNil := func() func(*ManagedGroup) *ManagedGroup {
|
||||
return func(mg *ManagedGroup) *ManagedGroup {
|
||||
return &ManagedGroup{}
|
||||
}
|
||||
}
|
||||
|
||||
deletePublicId := func() func(*ManagedGroup) *ManagedGroup {
|
||||
return func(mg *ManagedGroup) *ManagedGroup {
|
||||
mg.PublicId = ""
|
||||
return mg
|
||||
}
|
||||
}
|
||||
|
||||
nonExistentPublicId := func() func(*ManagedGroup) *ManagedGroup {
|
||||
return func(mg *ManagedGroup) *ManagedGroup {
|
||||
mg.PublicId = "abcd_OOOOOOOOOO"
|
||||
return mg
|
||||
}
|
||||
}
|
||||
|
||||
combine := func(fns ...func(mg *ManagedGroup) *ManagedGroup) func(*ManagedGroup) *ManagedGroup {
|
||||
return func(mg *ManagedGroup) *ManagedGroup {
|
||||
for _, fn := range fns {
|
||||
mg = fn(mg)
|
||||
}
|
||||
return mg
|
||||
}
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
scopeId string
|
||||
version uint32
|
||||
orig *ManagedGroup
|
||||
chgFn func(*ManagedGroup) *ManagedGroup
|
||||
masks []string
|
||||
want *ManagedGroup
|
||||
wantCount int
|
||||
wantIsErr errors.Code
|
||||
wantErrMsg string
|
||||
wantErrContains string
|
||||
}{
|
||||
{
|
||||
name: "nil-ManagedGroup",
|
||||
scopeId: org.GetPublicId(),
|
||||
version: 1,
|
||||
orig: &ManagedGroup{
|
||||
ManagedGroup: &store.ManagedGroup{},
|
||||
},
|
||||
chgFn: makeNil(),
|
||||
masks: []string{NameField, DescriptionField},
|
||||
wantIsErr: errors.InvalidParameter,
|
||||
wantErrMsg: "oidc.(Repository).UpdateManagedGroup: missing ManagedGroup: parameter violation: error #100",
|
||||
},
|
||||
{
|
||||
name: "nil-embedded-ManagedGroup",
|
||||
scopeId: org.GetPublicId(),
|
||||
version: 1,
|
||||
orig: &ManagedGroup{
|
||||
ManagedGroup: &store.ManagedGroup{},
|
||||
},
|
||||
chgFn: makeEmbeddedNil(),
|
||||
masks: []string{NameField, DescriptionField},
|
||||
wantIsErr: errors.InvalidParameter,
|
||||
wantErrMsg: "oidc.(Repository).UpdateManagedGroup: missing embedded ManagedGroup: parameter violation: error #100",
|
||||
},
|
||||
{
|
||||
name: "no-scope-id",
|
||||
version: 1,
|
||||
orig: &ManagedGroup{
|
||||
ManagedGroup: &store.ManagedGroup{
|
||||
Name: "no-scope-id-test-name-repo",
|
||||
},
|
||||
},
|
||||
chgFn: changeName("no-scope-id-test-update-name-repo"),
|
||||
masks: []string{NameField},
|
||||
wantIsErr: errors.InvalidParameter,
|
||||
wantErrMsg: "oidc.(Repository).UpdateManagedGroup: missing scope id: parameter violation: error #100",
|
||||
},
|
||||
{
|
||||
name: "missing-version",
|
||||
scopeId: org.GetPublicId(),
|
||||
orig: &ManagedGroup{
|
||||
ManagedGroup: &store.ManagedGroup{
|
||||
Name: "missing-version-test-name-repo",
|
||||
},
|
||||
},
|
||||
chgFn: changeName("test-update-name-repo"),
|
||||
masks: []string{NameField},
|
||||
wantIsErr: errors.InvalidParameter,
|
||||
wantErrMsg: "oidc.(Repository).UpdateManagedGroup: missing version: parameter violation: error #100",
|
||||
},
|
||||
{
|
||||
name: "no-public-id",
|
||||
scopeId: org.GetPublicId(),
|
||||
version: 1,
|
||||
orig: &ManagedGroup{
|
||||
ManagedGroup: &store.ManagedGroup{},
|
||||
},
|
||||
chgFn: deletePublicId(),
|
||||
masks: []string{NameField, DescriptionField},
|
||||
wantIsErr: errors.InvalidPublicId,
|
||||
wantErrMsg: "oidc.(Repository).UpdateManagedGroup: missing public id: parameter violation: error #102",
|
||||
},
|
||||
{
|
||||
name: "updating-non-existent-ManagedGroup",
|
||||
scopeId: org.GetPublicId(),
|
||||
version: 1,
|
||||
orig: &ManagedGroup{
|
||||
ManagedGroup: &store.ManagedGroup{
|
||||
Name: "updating-non-existent-ManagedGroup-test-name-repo",
|
||||
},
|
||||
},
|
||||
chgFn: combine(nonExistentPublicId(), changeName("updating-non-existent-ManagedGroup-test-update-name-repo")),
|
||||
masks: []string{NameField},
|
||||
wantIsErr: errors.RecordNotFound,
|
||||
wantErrMsg: "oidc.(Repository).UpdateManagedGroup: abcd_OOOOOOOOOO: db.DoTx: oidc.(Repository).UpdateManagedGroup: db.Update: db.lookupAfterWrite: db.LookupById: record not found, search issue: error #1100",
|
||||
},
|
||||
{
|
||||
name: "empty-field-mask",
|
||||
scopeId: org.GetPublicId(),
|
||||
version: 1,
|
||||
orig: &ManagedGroup{
|
||||
ManagedGroup: &store.ManagedGroup{
|
||||
Name: "empty-field-mask-test-name-repo",
|
||||
},
|
||||
},
|
||||
chgFn: changeName("empty-field-mask-test-update-name-repo"),
|
||||
wantIsErr: errors.EmptyFieldMask,
|
||||
wantErrMsg: "oidc.(Repository).UpdateManagedGroup: missing field mask: parameter violation: error #104",
|
||||
},
|
||||
{
|
||||
name: "read-only-fields-in-field-mask",
|
||||
scopeId: org.GetPublicId(),
|
||||
version: 1,
|
||||
orig: &ManagedGroup{
|
||||
ManagedGroup: &store.ManagedGroup{
|
||||
Name: "read-only-fields-in-field-mask-test-name-repo",
|
||||
},
|
||||
},
|
||||
chgFn: changeName("read-only-fields-in-field-mask-test-update-name-repo"),
|
||||
masks: []string{"PublicId", "CreateTime", "UpdateTime", "AuthMethodId"},
|
||||
wantIsErr: errors.InvalidFieldMask,
|
||||
wantErrMsg: "oidc.(Repository).UpdateManagedGroup: PublicId: parameter violation: error #103",
|
||||
},
|
||||
{
|
||||
name: "unknown-field-in-field-mask",
|
||||
scopeId: org.GetPublicId(),
|
||||
version: 1,
|
||||
orig: &ManagedGroup{
|
||||
ManagedGroup: &store.ManagedGroup{
|
||||
Name: "unknown-field-in-field-mask-test-name-repo",
|
||||
},
|
||||
},
|
||||
chgFn: changeName("unknown-field-in-field-mask-test-update-name-repo"),
|
||||
masks: []string{"Bilbo"},
|
||||
wantIsErr: errors.InvalidFieldMask,
|
||||
wantErrMsg: "oidc.(Repository).UpdateManagedGroup: Bilbo: parameter violation: error #103",
|
||||
},
|
||||
{
|
||||
name: "change-name",
|
||||
scopeId: org.GetPublicId(),
|
||||
version: 1,
|
||||
orig: &ManagedGroup{
|
||||
ManagedGroup: &store.ManagedGroup{
|
||||
Name: "change-name-test-name-repo",
|
||||
},
|
||||
},
|
||||
chgFn: changeName("change-name-test-update-name-repo"),
|
||||
masks: []string{NameField},
|
||||
want: &ManagedGroup{
|
||||
ManagedGroup: &store.ManagedGroup{
|
||||
Name: "change-name-test-update-name-repo",
|
||||
},
|
||||
},
|
||||
wantCount: 1,
|
||||
},
|
||||
{
|
||||
name: "change-description",
|
||||
scopeId: org.GetPublicId(),
|
||||
version: 1,
|
||||
orig: &ManagedGroup{
|
||||
ManagedGroup: &store.ManagedGroup{
|
||||
Description: "test-description-repo",
|
||||
},
|
||||
},
|
||||
chgFn: changeDescription("test-update-description-repo"),
|
||||
masks: []string{DescriptionField},
|
||||
want: &ManagedGroup{
|
||||
ManagedGroup: &store.ManagedGroup{
|
||||
Description: "test-update-description-repo",
|
||||
},
|
||||
},
|
||||
wantCount: 1,
|
||||
},
|
||||
{
|
||||
name: "change-filter",
|
||||
scopeId: org.GetPublicId(),
|
||||
version: 1,
|
||||
orig: &ManagedGroup{
|
||||
ManagedGroup: &store.ManagedGroup{
|
||||
Filter: testFakeFilter,
|
||||
},
|
||||
},
|
||||
chgFn: changeFilter(`"/zip" == "zap"`),
|
||||
masks: []string{FilterField},
|
||||
want: &ManagedGroup{
|
||||
ManagedGroup: &store.ManagedGroup{
|
||||
Filter: `"/zip" == "zap"`,
|
||||
},
|
||||
},
|
||||
wantCount: 1,
|
||||
},
|
||||
{
|
||||
name: "change-name-and-description",
|
||||
scopeId: org.GetPublicId(),
|
||||
version: 1,
|
||||
orig: &ManagedGroup{
|
||||
ManagedGroup: &store.ManagedGroup{
|
||||
Name: "change-name-and-description-test-name-repo",
|
||||
Description: "test-description-repo",
|
||||
},
|
||||
},
|
||||
chgFn: combine(changeDescription("test-update-description-repo"), changeName("change-name-and-description-test-update-name-repo")),
|
||||
masks: []string{NameField, DescriptionField},
|
||||
want: &ManagedGroup{
|
||||
ManagedGroup: &store.ManagedGroup{
|
||||
Name: "change-name-and-description-test-update-name-repo",
|
||||
Description: "test-update-description-repo",
|
||||
},
|
||||
},
|
||||
wantCount: 1,
|
||||
},
|
||||
{
|
||||
name: "delete-name",
|
||||
scopeId: org.GetPublicId(),
|
||||
version: 1,
|
||||
orig: &ManagedGroup{
|
||||
ManagedGroup: &store.ManagedGroup{
|
||||
Name: "delete-name-test-name-repo",
|
||||
Description: "test-description-repo",
|
||||
},
|
||||
},
|
||||
masks: []string{NameField},
|
||||
chgFn: combine(changeDescription("test-update-description-repo"), changeName("")),
|
||||
want: &ManagedGroup{
|
||||
ManagedGroup: &store.ManagedGroup{
|
||||
Description: "test-description-repo",
|
||||
},
|
||||
},
|
||||
wantCount: 1,
|
||||
},
|
||||
{
|
||||
name: "delete-description",
|
||||
scopeId: org.GetPublicId(),
|
||||
version: 1,
|
||||
orig: &ManagedGroup{
|
||||
ManagedGroup: &store.ManagedGroup{
|
||||
Name: "delete-description-test-name-repo",
|
||||
Description: "test-description-repo",
|
||||
},
|
||||
},
|
||||
masks: []string{DescriptionField},
|
||||
chgFn: combine(changeDescription(""), changeName("test-update-name-repo")),
|
||||
want: &ManagedGroup{
|
||||
ManagedGroup: &store.ManagedGroup{
|
||||
Name: "delete-description-test-name-repo",
|
||||
},
|
||||
},
|
||||
wantCount: 1,
|
||||
},
|
||||
{
|
||||
name: "delete-filter",
|
||||
scopeId: org.GetPublicId(),
|
||||
version: 1,
|
||||
orig: &ManagedGroup{
|
||||
ManagedGroup: &store.ManagedGroup{
|
||||
Filter: testFakeFilter,
|
||||
},
|
||||
},
|
||||
masks: []string{FilterField},
|
||||
chgFn: combine(changeFilter("")),
|
||||
wantIsErr: errors.NotNull,
|
||||
wantErrContains: "oidc.(Repository).UpdateManagedGroup: db.Update: filter must not be empty: not null constraint violated: integrity violation: error #1001",
|
||||
},
|
||||
{
|
||||
name: "do-not-delete-name",
|
||||
scopeId: org.GetPublicId(),
|
||||
version: 1,
|
||||
orig: &ManagedGroup{
|
||||
ManagedGroup: &store.ManagedGroup{
|
||||
Name: "do-not-delete-name-test-name-repo",
|
||||
Description: "test-description-repo",
|
||||
},
|
||||
},
|
||||
masks: []string{DescriptionField},
|
||||
chgFn: combine(changeDescription("test-update-description-repo"), changeName("")),
|
||||
want: &ManagedGroup{
|
||||
ManagedGroup: &store.ManagedGroup{
|
||||
Name: "do-not-delete-name-test-name-repo",
|
||||
Description: "test-update-description-repo",
|
||||
},
|
||||
},
|
||||
wantCount: 1,
|
||||
},
|
||||
{
|
||||
name: "do-not-delete-description",
|
||||
scopeId: org.GetPublicId(),
|
||||
version: 1,
|
||||
orig: &ManagedGroup{
|
||||
ManagedGroup: &store.ManagedGroup{
|
||||
Name: "do-not-delete-description-test-name-repo",
|
||||
Description: "test-description-repo",
|
||||
},
|
||||
},
|
||||
masks: []string{NameField},
|
||||
chgFn: combine(changeDescription(""), changeName("do-not-delete-description-test-update-name-repo")),
|
||||
want: &ManagedGroup{
|
||||
ManagedGroup: &store.ManagedGroup{
|
||||
Name: "do-not-delete-description-test-update-name-repo",
|
||||
Description: "test-description-repo",
|
||||
},
|
||||
},
|
||||
wantCount: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
repo, err := NewRepository(rw, rw, kmsCache)
|
||||
assert.NoError(err)
|
||||
require.NotNil(repo)
|
||||
|
||||
orig := TestManagedGroup(t, conn, am, testFakeFilter, WithName(tt.orig.GetName()), WithDescription(tt.orig.GetDescription()))
|
||||
|
||||
tt.orig.AuthMethodId = am.PublicId
|
||||
if tt.chgFn != nil {
|
||||
orig = tt.chgFn(orig)
|
||||
}
|
||||
got, gotCount, err := repo.UpdateManagedGroup(context.Background(), tt.scopeId, orig, tt.version, tt.masks)
|
||||
if tt.wantIsErr != 0 {
|
||||
assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "want err: %q got: %q", tt.wantIsErr, err)
|
||||
if tt.wantErrContains != "" {
|
||||
assert.True(strings.Contains(err.Error(), tt.wantErrContains))
|
||||
} else {
|
||||
assert.Equal(tt.wantErrMsg, err.Error())
|
||||
}
|
||||
assert.Equal(tt.wantCount, gotCount, "row count")
|
||||
assert.Nil(got)
|
||||
return
|
||||
}
|
||||
assert.NoError(err)
|
||||
assert.Empty(tt.orig.PublicId)
|
||||
if tt.wantCount == 0 {
|
||||
assert.Equal(tt.wantCount, gotCount, "row count")
|
||||
assert.Nil(got)
|
||||
return
|
||||
}
|
||||
require.NotNil(got)
|
||||
assertPublicId(t, ManagedGroupPrefix, got.PublicId)
|
||||
assert.Equal(tt.wantCount, gotCount, "row count")
|
||||
assert.NotSame(tt.orig, got)
|
||||
assert.Equal(tt.orig.AuthMethodId, got.AuthMethodId)
|
||||
dbassert := dbassert.New(t, conn.DB())
|
||||
if tt.want.Name == "" {
|
||||
dbassert.IsNull(got, "name")
|
||||
return
|
||||
}
|
||||
assert.Equal(tt.want.Name, got.Name)
|
||||
if tt.want.Description == "" {
|
||||
dbassert.IsNull(got, "description")
|
||||
return
|
||||
}
|
||||
assert.Equal(tt.want.Description, got.Description)
|
||||
if tt.wantCount > 0 {
|
||||
assert.NoError(db.TestVerifyOplog(t, rw, got.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second)))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -829,6 +829,125 @@ func (x *AccountClaimMap) GetCreateTime() *timestamp.Timestamp {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ManagedGroup entries provide an OIDC auth method implementation of managed
|
||||
// groups.
|
||||
type ManagedGroup struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// @inject_tag: `gorm:"primary_key"`
|
||||
PublicId string `protobuf:"bytes,10,opt,name=public_id,json=publicId,proto3" json:"public_id,omitempty" gorm:"primary_key"`
|
||||
// The create_time is set by the database.
|
||||
// @inject_tag: `gorm:"default:current_timestamp"`
|
||||
CreateTime *timestamp.Timestamp `protobuf:"bytes,20,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"`
|
||||
// The update_time is set by the database.
|
||||
// @inject_tag: `gorm:"default:current_timestamp"`
|
||||
UpdateTime *timestamp.Timestamp `protobuf:"bytes,30,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty" gorm:"default:current_timestamp"`
|
||||
// name is optional. If set, it must be unique within auth_method_id.
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
Name string `protobuf:"bytes,40,opt,name=name,proto3" json:"name,omitempty" gorm:"default:null"`
|
||||
// description is optional.
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
Description string `protobuf:"bytes,50,opt,name=description,proto3" json:"description,omitempty" gorm:"default:null"`
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
Version uint32 `protobuf:"varint,60,opt,name=version,proto3" json:"version,omitempty" gorm:"default:null"`
|
||||
// auth_method_id is the fk to the account's auth method.
|
||||
// @inject_tag: `gorm:"not_null"`
|
||||
AuthMethodId string `protobuf:"bytes,70,opt,name=auth_method_id,json=authMethodId,proto3" json:"auth_method_id,omitempty" gorm:"not_null"`
|
||||
// filter is a go-bexpr filter
|
||||
// @inject_tag: `gorm:"not_null"`
|
||||
Filter string `protobuf:"bytes,80,opt,name=filter,proto3" json:"filter,omitempty" gorm:"not_null"`
|
||||
}
|
||||
|
||||
func (x *ManagedGroup) Reset() {
|
||||
*x = ManagedGroup{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_controller_storage_auth_oidc_store_v1_oidc_proto_msgTypes[7]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ManagedGroup) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ManagedGroup) ProtoMessage() {}
|
||||
|
||||
func (x *ManagedGroup) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_controller_storage_auth_oidc_store_v1_oidc_proto_msgTypes[7]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ManagedGroup.ProtoReflect.Descriptor instead.
|
||||
func (*ManagedGroup) Descriptor() ([]byte, []int) {
|
||||
return file_controller_storage_auth_oidc_store_v1_oidc_proto_rawDescGZIP(), []int{7}
|
||||
}
|
||||
|
||||
func (x *ManagedGroup) GetPublicId() string {
|
||||
if x != nil {
|
||||
return x.PublicId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ManagedGroup) GetCreateTime() *timestamp.Timestamp {
|
||||
if x != nil {
|
||||
return x.CreateTime
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ManagedGroup) GetUpdateTime() *timestamp.Timestamp {
|
||||
if x != nil {
|
||||
return x.UpdateTime
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ManagedGroup) GetName() string {
|
||||
if x != nil {
|
||||
return x.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ManagedGroup) GetDescription() string {
|
||||
if x != nil {
|
||||
return x.Description
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ManagedGroup) GetVersion() uint32 {
|
||||
if x != nil {
|
||||
return x.Version
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ManagedGroup) GetAuthMethodId() string {
|
||||
if x != nil {
|
||||
return x.AuthMethodId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ManagedGroup) GetFilter() string {
|
||||
if x != nil {
|
||||
return x.Filter
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_controller_storage_auth_oidc_store_v1_oidc_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_controller_storage_auth_oidc_store_v1_oidc_proto_rawDesc = []byte{
|
||||
@@ -1012,11 +1131,39 @@ var file_controller_storage_auth_oidc_store_v1_oidc_proto_rawDesc = []byte{
|
||||
0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65,
|
||||
0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69,
|
||||
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54,
|
||||
0x69, 0x6d, 0x65, 0x42, 0x3e, 0x5a, 0x3c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
|
||||
0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e,
|
||||
0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x61, 0x75,
|
||||
0x74, 0x68, 0x2f, 0x6f, 0x69, 0x64, 0x63, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74,
|
||||
0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x69, 0x6d, 0x65, 0x22, 0xbf, 0x03, 0x0a, 0x0c, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x47,
|
||||
0x72, 0x6f, 0x75, 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69,
|
||||
0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49,
|
||||
0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65,
|
||||
0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
|
||||
0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65,
|
||||
0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
|
||||
0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x4b,
|
||||
0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x1e, 0x20,
|
||||
0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72,
|
||||
0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
|
||||
0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52,
|
||||
0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x24, 0x0a, 0x04, 0x6e,
|
||||
0x61, 0x6d, 0x65, 0x18, 0x28, 0x20, 0x01, 0x28, 0x09, 0x42, 0x10, 0xc2, 0xdd, 0x29, 0x0c, 0x0a,
|
||||
0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x52, 0x04, 0x6e, 0x61, 0x6d,
|
||||
0x65, 0x12, 0x40, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x18, 0x32, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1e, 0xc2, 0xdd, 0x29, 0x1a, 0x0a, 0x0b, 0x44, 0x65,
|
||||
0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72,
|
||||
0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x3c,
|
||||
0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x48, 0x0a,
|
||||
0x0e, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18,
|
||||
0x46, 0x20, 0x01, 0x28, 0x09, 0x42, 0x22, 0xc2, 0xdd, 0x29, 0x1e, 0x0a, 0x0c, 0x41, 0x75, 0x74,
|
||||
0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, 0x12, 0x0e, 0x61, 0x75, 0x74, 0x68, 0x5f,
|
||||
0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x52, 0x0c, 0x61, 0x75, 0x74, 0x68, 0x4d,
|
||||
0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, 0x12, 0x2c, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65,
|
||||
0x72, 0x18, 0x50, 0x20, 0x01, 0x28, 0x09, 0x42, 0x14, 0xc2, 0xdd, 0x29, 0x10, 0x0a, 0x06, 0x46,
|
||||
0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x06, 0x66,
|
||||
0x69, 0x6c, 0x74, 0x65, 0x72, 0x42, 0x3e, 0x5a, 0x3c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
|
||||
0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f,
|
||||
0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f,
|
||||
0x61, 0x75, 0x74, 0x68, 0x2f, 0x6f, 0x69, 0x64, 0x63, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b,
|
||||
0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -1031,7 +1178,7 @@ func file_controller_storage_auth_oidc_store_v1_oidc_proto_rawDescGZIP() []byte
|
||||
return file_controller_storage_auth_oidc_store_v1_oidc_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_controller_storage_auth_oidc_store_v1_oidc_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
|
||||
var file_controller_storage_auth_oidc_store_v1_oidc_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
|
||||
var file_controller_storage_auth_oidc_store_v1_oidc_proto_goTypes = []interface{}{
|
||||
(*AuthMethod)(nil), // 0: controller.storage.auth.oidc.store.v1.AuthMethod
|
||||
(*Account)(nil), // 1: controller.storage.auth.oidc.store.v1.Account
|
||||
@@ -1040,23 +1187,26 @@ var file_controller_storage_auth_oidc_store_v1_oidc_proto_goTypes = []interface{
|
||||
(*Certificate)(nil), // 4: controller.storage.auth.oidc.store.v1.Certificate
|
||||
(*ClaimsScope)(nil), // 5: controller.storage.auth.oidc.store.v1.ClaimsScope
|
||||
(*AccountClaimMap)(nil), // 6: controller.storage.auth.oidc.store.v1.AccountClaimMap
|
||||
(*timestamp.Timestamp)(nil), // 7: controller.storage.timestamp.v1.Timestamp
|
||||
(*ManagedGroup)(nil), // 7: controller.storage.auth.oidc.store.v1.ManagedGroup
|
||||
(*timestamp.Timestamp)(nil), // 8: controller.storage.timestamp.v1.Timestamp
|
||||
}
|
||||
var file_controller_storage_auth_oidc_store_v1_oidc_proto_depIdxs = []int32{
|
||||
7, // 0: controller.storage.auth.oidc.store.v1.AuthMethod.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
7, // 1: controller.storage.auth.oidc.store.v1.AuthMethod.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
7, // 2: controller.storage.auth.oidc.store.v1.Account.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
7, // 3: controller.storage.auth.oidc.store.v1.Account.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
7, // 4: controller.storage.auth.oidc.store.v1.SigningAlg.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
7, // 5: controller.storage.auth.oidc.store.v1.AudClaim.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
7, // 6: controller.storage.auth.oidc.store.v1.Certificate.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
7, // 7: controller.storage.auth.oidc.store.v1.ClaimsScope.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
7, // 8: controller.storage.auth.oidc.store.v1.AccountClaimMap.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
9, // [9:9] is the sub-list for method output_type
|
||||
9, // [9:9] is the sub-list for method input_type
|
||||
9, // [9:9] is the sub-list for extension type_name
|
||||
9, // [9:9] is the sub-list for extension extendee
|
||||
0, // [0:9] is the sub-list for field type_name
|
||||
8, // 0: controller.storage.auth.oidc.store.v1.AuthMethod.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
8, // 1: controller.storage.auth.oidc.store.v1.AuthMethod.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
8, // 2: controller.storage.auth.oidc.store.v1.Account.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
8, // 3: controller.storage.auth.oidc.store.v1.Account.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
8, // 4: controller.storage.auth.oidc.store.v1.SigningAlg.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
8, // 5: controller.storage.auth.oidc.store.v1.AudClaim.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
8, // 6: controller.storage.auth.oidc.store.v1.Certificate.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
8, // 7: controller.storage.auth.oidc.store.v1.ClaimsScope.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
8, // 8: controller.storage.auth.oidc.store.v1.AccountClaimMap.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
8, // 9: controller.storage.auth.oidc.store.v1.ManagedGroup.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
8, // 10: controller.storage.auth.oidc.store.v1.ManagedGroup.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
11, // [11:11] is the sub-list for method output_type
|
||||
11, // [11:11] is the sub-list for method input_type
|
||||
11, // [11:11] is the sub-list for extension type_name
|
||||
11, // [11:11] is the sub-list for extension extendee
|
||||
0, // [0:11] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_controller_storage_auth_oidc_store_v1_oidc_proto_init() }
|
||||
@@ -1149,6 +1299,18 @@ func file_controller_storage_auth_oidc_store_v1_oidc_proto_init() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_controller_storage_auth_oidc_store_v1_oidc_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ManagedGroup); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
@@ -1156,7 +1318,7 @@ func file_controller_storage_auth_oidc_store_v1_oidc_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_controller_storage_auth_oidc_store_v1_oidc_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 7,
|
||||
NumMessages: 8,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
||||
@@ -176,6 +176,24 @@ func TestAccount(t *testing.T, conn *gorm.DB, am *AuthMethod, subject string, op
|
||||
return a
|
||||
}
|
||||
|
||||
// TestManagedGroup creates a test oidc managed group.
|
||||
func TestManagedGroup(t *testing.T, conn *gorm.DB, am *AuthMethod, filter string, opt ...Option) *ManagedGroup {
|
||||
t.Helper()
|
||||
require := require.New(t)
|
||||
rw := db.New(conn)
|
||||
ctx := context.Background()
|
||||
|
||||
mg, err := NewManagedGroup(am.PublicId, filter, opt...)
|
||||
require.NoError(err)
|
||||
|
||||
id, err := newManagedGroupId()
|
||||
require.NoError(err)
|
||||
mg.PublicId = id
|
||||
|
||||
require.NoError(rw.Create(ctx, mg))
|
||||
return mg
|
||||
}
|
||||
|
||||
// TestConvertToUrls will convert URL string representations to a slice of
|
||||
// *url.URL
|
||||
func TestConvertToUrls(t *testing.T, urls ...string) []*url.URL {
|
||||
|
||||
@@ -58,6 +58,18 @@ var (
|
||||
_ Account = (*password.Account)(nil)
|
||||
)
|
||||
|
||||
type ManagedGroup interface {
|
||||
GetPublicId() string
|
||||
GetCreateTime() *timestamp.Timestamp
|
||||
GetUpdateTime() *timestamp.Timestamp
|
||||
GetName() string
|
||||
GetDescription() string
|
||||
GetAuthMethodId() string
|
||||
GetVersion() uint32
|
||||
}
|
||||
|
||||
var _ ManagedGroup = (*oidc.ManagedGroup)(nil)
|
||||
|
||||
// SubtypeFromType converts a string to a Subtype.
|
||||
// returns UnknownSubtype if no Subtype with that name is found.
|
||||
func SubtypeFromType(t string) Subtype {
|
||||
@@ -79,7 +91,8 @@ func SubtypeFromId(id string) Subtype {
|
||||
strings.HasPrefix(strings.TrimSpace(id), password.AccountPrefix):
|
||||
return PasswordSubtype
|
||||
case strings.HasPrefix(strings.TrimSpace(id), oidc.AuthMethodPrefix),
|
||||
strings.HasPrefix(strings.TrimSpace(id), oidc.AccountPrefix):
|
||||
strings.HasPrefix(strings.TrimSpace(id), oidc.AccountPrefix),
|
||||
strings.HasPrefix(strings.TrimSpace(id), oidc.ManagedGroupPrefix):
|
||||
return OidcSubtype
|
||||
}
|
||||
return UnknownSubtype
|
||||
|
||||
@@ -119,8 +119,10 @@ func (b *Server) CreateDevDatabase(ctx context.Context, opt ...Option) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := b.CreateDevOidcAuthMethod(ctx); err != nil {
|
||||
return err
|
||||
if !opts.withSkipOidcAuthMethodCreation {
|
||||
if err := b.CreateDevOidcAuthMethod(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if opts.withSkipScopesCreation {
|
||||
@@ -225,7 +227,7 @@ func (b *Server) CreateDevOidcAuthMethod(ctx context.Context) error {
|
||||
// re-use it. This is a sort of hacky way to get around the chicken and egg
|
||||
// of the auth method needing to know the discovery URL and the test
|
||||
// provider needing to know the callback URL.
|
||||
l, err := net.Listen("tcp", fmt.Sprintf("%s:0", b.DevOidcSetup.hostAddr))
|
||||
l, err := net.Listen("tcp", net.JoinHostPort(b.DevOidcSetup.hostAddr, "0"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error finding port for oidc test provider: %w", err)
|
||||
}
|
||||
@@ -233,7 +235,7 @@ func (b *Server) CreateDevOidcAuthMethod(ctx context.Context) error {
|
||||
if err := l.Close(); err != nil {
|
||||
return fmt.Errorf("error closing initial test port: %w", err)
|
||||
}
|
||||
b.DevOidcSetup.callbackUrl, err = url.Parse(fmt.Sprintf("http://%s:%s", b.DevOidcSetup.hostAddr, b.DevOidcSetup.callbackPort))
|
||||
b.DevOidcSetup.callbackUrl, err = url.Parse(fmt.Sprintf("http://%s", net.JoinHostPort(b.DevOidcSetup.hostAddr, b.DevOidcSetup.callbackPort)))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing oidc test provider callback url: %w", err)
|
||||
}
|
||||
|
||||
+19
-10
@@ -14,15 +14,16 @@ type Option func(*Options)
|
||||
|
||||
// Options - how Options are represented.
|
||||
type Options struct {
|
||||
withNoTokenScope bool
|
||||
withNoTokenValue bool
|
||||
withSkipDatabaseDestruction bool
|
||||
withSkipAuthMethodCreation bool
|
||||
withSkipScopesCreation bool
|
||||
withSkipHostResourcesCreation bool
|
||||
withSkipTargetCreation bool
|
||||
withContainerImage string
|
||||
withDialect string
|
||||
withNoTokenScope bool
|
||||
withNoTokenValue bool
|
||||
withSkipDatabaseDestruction bool
|
||||
withSkipAuthMethodCreation bool
|
||||
withSkipOidcAuthMethodCreation bool
|
||||
withSkipScopesCreation bool
|
||||
withSkipHostResourcesCreation bool
|
||||
withSkipTargetCreation bool
|
||||
withContainerImage string
|
||||
withDialect string
|
||||
}
|
||||
|
||||
func getDefaultOptions() Options {
|
||||
@@ -57,7 +58,7 @@ func WithNoTokenValue() Option {
|
||||
}
|
||||
}
|
||||
|
||||
// WithSkipAuthMethodCreation tells the command not to instantiate an auth
|
||||
// WithSkipAuthMethodCreation tells the command not to instantiate any auth
|
||||
// method on first run.
|
||||
func WithSkipAuthMethodCreation() Option {
|
||||
return func(o *Options) {
|
||||
@@ -65,6 +66,14 @@ func WithSkipAuthMethodCreation() Option {
|
||||
}
|
||||
}
|
||||
|
||||
// WithSkipOidcAuthMethodCreation tells the command not to instantiate an OIDC auth
|
||||
// method on first run, useful in some tests.
|
||||
func WithSkipOidcAuthMethodCreation() Option {
|
||||
return func(o *Options) {
|
||||
o.withSkipOidcAuthMethodCreation = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithSkipScopesCreation tells the command not to instantiate scopes on first
|
||||
// run.
|
||||
func WithSkipScopesCreation() Option {
|
||||
|
||||
@@ -36,8 +36,7 @@ func testServerCommand(t *testing.T, controllerKey string) *Command {
|
||||
require.NoError(err)
|
||||
require.NoError(cmd.SetupKMSes(cmd.UI, parsedKmsConfig))
|
||||
|
||||
opts := base.WithContainerImage("postgres")
|
||||
err = cmd.CreateDevDatabase(cmd.Context, opts)
|
||||
err = cmd.CreateDevDatabase(cmd.Context, base.WithContainerImage("postgres"), base.WithSkipOidcAuthMethodCreation())
|
||||
if err != nil {
|
||||
if cmd.DevDatabaseCleanupFunc != nil {
|
||||
require.NoError(cmd.DevDatabaseCleanupFunc())
|
||||
|
||||
@@ -251,4 +251,38 @@ message AccountClaimMap {
|
||||
// The create_time is set by the database.
|
||||
// @inject_tag: `gorm:"default:current_timestamp"`
|
||||
timestamp.v1.Timestamp create_time = 40;
|
||||
}
|
||||
|
||||
// ManagedGroup entries provide an OIDC auth method implementation of managed
|
||||
// groups.
|
||||
message ManagedGroup {
|
||||
// @inject_tag: `gorm:"primary_key"`
|
||||
string public_id = 10;
|
||||
|
||||
// The create_time is set by the database.
|
||||
// @inject_tag: `gorm:"default:current_timestamp"`
|
||||
timestamp.v1.Timestamp create_time = 20;
|
||||
|
||||
// The update_time is set by the database.
|
||||
// @inject_tag: `gorm:"default:current_timestamp"`
|
||||
timestamp.v1.Timestamp update_time = 30;
|
||||
|
||||
// name is optional. If set, it must be unique within auth_method_id.
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
string name = 40 [(custom_options.v1.mask_mapping) = { this: "Name" that: "name" }];
|
||||
|
||||
// description is optional.
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
string description = 50 [(custom_options.v1.mask_mapping) = { this: "Description" that: "description" }];
|
||||
|
||||
// @inject_tag: `gorm:"default:null"`
|
||||
uint32 version = 60;
|
||||
|
||||
// auth_method_id is the fk to the account's auth method.
|
||||
// @inject_tag: `gorm:"not_null"`
|
||||
string auth_method_id = 70 [(custom_options.v1.mask_mapping) = { this: "AuthMethodId" that: "auth_method_id" }];
|
||||
|
||||
// filter is a go-bexpr filter
|
||||
// @inject_tag: `gorm:"not_null"`
|
||||
string filter = 80 [(custom_options.v1.mask_mapping) = { this: "Filter" that: "filter" }];
|
||||
}
|
||||
@@ -304,6 +304,10 @@ type TestControllerOpts struct {
|
||||
// method automatically.
|
||||
DisableAuthMethodCreation bool
|
||||
|
||||
// DisableOidcAuthMethodCreation can be set true to disable the built-in
|
||||
// OIDC listener. Useful for e.g. unix listener tests.
|
||||
DisableOidcAuthMethodCreation bool
|
||||
|
||||
// DisableScopesCreation can be set true to disable creating scopes
|
||||
// automatically.
|
||||
DisableScopesCreation bool
|
||||
@@ -510,8 +514,10 @@ func NewTestController(t *testing.T, opts *TestControllerOpts) *TestController {
|
||||
if _, _, err := tc.b.CreateInitialPasswordAuthMethod(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := tc.b.CreateDevOidcAuthMethod(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
if !opts.DisableOidcAuthMethodCreation {
|
||||
if err := tc.b.CreateDevOidcAuthMethod(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if !opts.DisableScopesCreation {
|
||||
if _, _, err := tc.b.CreateInitialScopes(ctx); err != nil {
|
||||
@@ -536,6 +542,9 @@ func NewTestController(t *testing.T, opts *TestControllerOpts) *TestController {
|
||||
if opts.DisableAuthMethodCreation {
|
||||
createOpts = append(createOpts, base.WithSkipAuthMethodCreation())
|
||||
}
|
||||
if opts.DisableOidcAuthMethodCreation {
|
||||
createOpts = append(createOpts, base.WithSkipOidcAuthMethodCreation())
|
||||
}
|
||||
if err := tc.b.CreateDevDatabase(ctx, createOpts...); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -47,8 +47,9 @@ func TestUnixListener(t *testing.T) {
|
||||
}
|
||||
|
||||
c1 := controller.NewTestController(t, &controller.TestControllerOpts{
|
||||
Config: conf,
|
||||
Logger: logger.Named("c1"),
|
||||
Config: conf,
|
||||
Logger: logger.Named("c1"),
|
||||
DisableOidcAuthMethodCreation: true,
|
||||
})
|
||||
defer c1.Shutdown()
|
||||
|
||||
|
||||
Reference in new issue
Block a user