Add membership setting function (#1259)

This commit is contained in:
Jeff Mitchell authored and GitHub committed 2021-06-01 15:18:10 -04:00
1 parent 7da892a054
commit 4363a4084e
14 files changed
+783 -63

No files matched your search

+4 -4
View File
@@ -82,14 +82,14 @@ func (mg *ManagedGroup) SetTableName(n string) {
}
// oplog will create oplog metadata for the ManagedGroup.
func (c *ManagedGroup) oplog(op oplog.OpType, authMethodScopeId string) oplog.Metadata {
func (mg *ManagedGroup) oplog(op oplog.OpType, authMethodScopeId string) oplog.Metadata {
metadata := oplog.Metadata{
"resource-public-id": []string{c.GetPublicId()},
"resource-public-id": []string{mg.GetPublicId()},
"resource-type": []string{"oidc managed group"},
"op-type": []string{op.String()},
}
if c.AuthMethodId != "" {
metadata["auth-method-id"] = []string{c.AuthMethodId}
if mg.AuthMethodId != "" {
metadata["auth-method-id"] = []string{mg.AuthMethodId}
}
if authMethodScopeId != "" {
metadata["scope-id"] = []string{authMethodScopeId}
@@ -0,0 +1,75 @@
package oidc
import (
"github.com/hashicorp/boundary/internal/auth/oidc/store"
"github.com/hashicorp/boundary/internal/errors"
"google.golang.org/protobuf/proto"
)
// defaultManagedGroupMemberAccountTableName defines the default table name for a Managed Group
const defaultManagedGroupMemberAccountTableName = "auth_oidc_managed_group_member_account"
// ManagedGroupMemberAccount contains a mapping between a managed group and a
// member account
type ManagedGroupMemberAccount struct {
*store.ManagedGroupMemberAccount
tableName string
}
// NewManagedGroupMemberAccount creates a new in memory
// ManagedGroupMemberAccount assigned to a managed group within an OIDC
// AuthMethod. Supported options are withName and withDescription.
func NewManagedGroupMemberAccount(managedGroupId string, memberId string, opt ...Option) (*ManagedGroupMemberAccount, error) {
const op = "oidc.NewManagedGroupMemberAccount"
mg := &ManagedGroupMemberAccount{
ManagedGroupMemberAccount: &store.ManagedGroupMemberAccount{
ManagedGroupId: managedGroupId,
MemberId: memberId,
},
}
if err := mg.validate(op); err != nil {
return nil, err // intentionally not wrapped.
}
return mg, nil
}
// validate the ManagedGroupMemberAccount. On success, it will return nil.
func (mg *ManagedGroupMemberAccount) validate(caller errors.Op) error {
if mg.ManagedGroupId == "" {
return errors.New(errors.InvalidParameter, caller, "missing managed group id")
}
if mg.MemberId == "" {
return errors.New(errors.InvalidParameter, caller, "missing member id")
}
return nil
}
// AllocManagedGroupMemberAccount makes an empty one in memory
func AllocManagedGroupMemberAccount() *ManagedGroupMemberAccount {
return &ManagedGroupMemberAccount{
ManagedGroupMemberAccount: &store.ManagedGroupMemberAccount{},
}
}
// Clone a ManagedGroupMemberAccount.
func (mg *ManagedGroupMemberAccount) Clone() *ManagedGroupMemberAccount {
cp := proto.Clone(mg.ManagedGroupMemberAccount)
return &ManagedGroupMemberAccount{
ManagedGroupMemberAccount: cp.(*store.ManagedGroupMemberAccount),
}
}
// TableName returns the table name.
func (mg *ManagedGroupMemberAccount) TableName() string {
if mg.tableName != "" {
return mg.tableName
}
return defaultManagedGroupMemberAccountTableName
}
// SetTableName sets the table name.
func (mg *ManagedGroupMemberAccount) SetTableName(n string) {
mg.tableName = n
}
+11
View File
@@ -3,6 +3,8 @@ package oidc
import (
"crypto/x509"
"net/url"
"github.com/hashicorp/boundary/internal/db"
)
// getOpts - iterate the inbound Options and return a struct.
@@ -42,6 +44,7 @@ type options struct {
withIssuer *url.URL
withOperationalState AuthMethodState
withAccountClaimMap map[string]AccountToClaim
withReader db.Reader
}
func getDefaultOptions() options {
@@ -218,3 +221,11 @@ func WithAccountClaimMap(acm map[string]AccountToClaim) Option {
o.withAccountClaimMap = acm
}
}
// WithReader provides an option for specifying a reader to use for the
// operation.
func WithReader(reader db.Reader) Option {
return func(o *options) {
o.withReader = reader
}
}
+9
View File
@@ -5,6 +5,7 @@ import (
"net/url"
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -194,4 +195,12 @@ func Test_GetOpts(t *testing.T) {
testOpts.withAccountClaimMap = acm
assert.Equal(opts, testOpts)
})
t.Run("WithReader", func(t *testing.T) {
assert := assert.New(t)
testOpts := getDefaultOptions()
assert.Nil(testOpts.withReader)
var r db.Reader
opts := getOpts(WithReader(r))
assert.Equal(r, opts.withReader)
})
}
+6 -6
View File
@@ -2,14 +2,14 @@ package oidc
const (
acctUpsertQuery = `
insert into auth_oidc_account
insert into auth_oidc_account
(%s)
values
values
(%s)
on conflict on constraint
on conflict on constraint
auth_oidc_account_auth_method_id_issuer_subject_uq
do update set
do update set
%s
returning public_id, version
`
returning public_id, version
`
)
@@ -0,0 +1,216 @@
package oidc
import (
"context"
"fmt"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
)
// SetManagedGroupMemberships will set the managed groups for the given account
// ID. If mgs is empty, the set of groups the account belongs to will be
// cleared. It returns the set of managed group IDs.
//
// mgs contains the set of managed groups that matched. It must contain the
// group's version as this is used to ensure consistency between when the filter
// attached to the managed group was run and the point at which we are adding
// the account to the group.
func (r *Repository) SetManagedGroupMemberships(ctx context.Context, am *AuthMethod, acct *Account, mgs []*ManagedGroup, _ ...Option) ([]*ManagedGroupMemberAccount, int, error) {
const op = "oidc.(Repository).SetManagedGroupMemberships"
if am == nil {
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing auth method")
}
if am.AuthMethod == nil {
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing auth method store")
}
if am.PublicId == "" {
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing auth method id")
}
if am.ScopeId == "" {
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing auth method scope id")
}
if acct == nil {
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing account")
}
if acct.Account == nil {
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing account store")
}
if acct.PublicId == "" {
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing account id")
}
oplogWrapper, err := r.kms.GetWrapper(ctx, am.ScopeId, kms.KeyPurposeOplog)
if err != nil {
return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper"))
}
newMgPublicIds := make(map[string]bool, len(mgs))
mgsToUpdate := make([]*ManagedGroup, 0, len(mgs))
for _, mg := range mgs {
if mg.Version == 0 {
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, fmt.Sprintf("missing version for managed group %s", mg.PublicId))
}
if newMgPublicIds[mg.PublicId] {
// We've already seen this -- could be a duplicate in the incoming
// MGs. We don't want to add it again because the version won't be
// correct, and it's unnecessary.
continue
}
newMgPublicIds[mg.PublicId] = true
mgToUpdate := AllocManagedGroup()
mgToUpdate.PublicId = mg.PublicId
mgToUpdate.AuthMethodId = am.PublicId
mgToUpdate.Version = mg.Version + 1
mgsToUpdate = append(mgsToUpdate, mgToUpdate)
}
ticketMg := AllocManagedGroup()
var totalRowsAffected int
var currentMemberships []*ManagedGroupMemberAccount
_, err = r.writer.DoTx(
ctx,
db.StdRetryCnt,
db.ExpBackoff{},
func(reader db.Reader, w db.Writer) error {
// We need a ticket, which won't be redeemed until all the other
// writes are successful. We can't just use a single ticket because
// we need to write oplog entries for deletes and adds.
mgTicket, err := w.GetTicket(ticketMg)
if err != nil {
return errors.Wrap(err, op, errors.WithMsg("unable to get ticket for oidc managed groups"))
}
msgs := make([]*oplog.Message, 0, len(mgs)+5)
metadata := oplog.Metadata{
"op-type": []string{oplog.OpType_OP_TYPE_UPDATE.String()},
"scope-id": []string{am.ScopeId},
"auth-method-id": []string{am.PublicId},
"account-id": []string{acct.PublicId},
}
// Ensure that none of the filters have changed or will change
// during this operation
for _, mgToUpdate := range mgsToUpdate {
var mgOplogMsg oplog.Message
// mgToUpdate will have come in with an incremented version
// already, but WithVersion needs the current version
prevVersion := mgToUpdate.Version - 1
rowsUpdated, err := w.Update(ctx, mgToUpdate, []string{"Version"}, nil, db.NewOplogMsg(&mgOplogMsg), db.WithVersion(&prevVersion))
if err != nil {
return errors.Wrap(err, op)
}
if rowsUpdated != 1 {
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("updated oidc managed group and %d rows updated", rowsUpdated))
}
msgs = append(msgs, &mgOplogMsg)
}
currentMemberships, err = r.ListManagedGroupMembershipsByMember(ctx, acct.PublicId, WithReader(reader))
if err != nil {
return errors.Wrap(err, op, errors.WithMsg("unable to retrieve current managed group memberships before deletion"))
}
// Figure out which ones to delete and which ones we already have
toDelete := make([]interface{}, 0, len(mgs))
for _, currMg := range currentMemberships {
currMgId := currMg.ManagedGroupId
if newMgPublicIds[currMgId] {
// We're slated to add it in, but it's already in there, so
// take it out of the new list
delete(newMgPublicIds, currMgId)
} else {
// It's not currently matching a filter, so needs to be deleted
delMg := AllocManagedGroupMemberAccount()
delMg.ManagedGroupId = currMgId
delMg.MemberId = acct.PublicId
toDelete = append(toDelete, delMg)
}
}
// At this point, anything in toDelete should be deleted, and
// anything left in newMgPublicIds should be added. However, if we
// had no managed group to update, because none were passed in, but
// also none to delete, we return at this point. Nothing will have
// changed and nothing will be changed either.
if len(mgs) == 0 && len(toDelete) == 0 {
return errors.New(errors.GracefullyAborted, op, "nothing to do")
}
// Start with deletion
if len(toDelete) > 0 {
metadata["op-type"] = append(metadata["op-type"], oplog.OpType_OP_TYPE_DELETE.String())
deleteOplogMsgs := make([]*oplog.Message, 0, len(toDelete))
rowsDeleted, err := w.DeleteItems(ctx, toDelete, db.NewOplogMsgs(&deleteOplogMsgs))
if err != nil {
return errors.Wrap(err, op, errors.WithMsg("unable to delete managed group member accounts"))
}
if rowsDeleted != len(toDelete) {
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("managed group member accounts deleted %d did not match request for %d", rowsDeleted, len(toDelete)))
}
totalRowsAffected += rowsDeleted
msgs = append(msgs, deleteOplogMsgs...)
}
// Now do insertion
if len(newMgPublicIds) > 0 {
metadata["op-type"] = append(metadata["op-type"], oplog.OpType_OP_TYPE_CREATE.String())
addOplogMsgs := make([]*oplog.Message, 0, len(newMgPublicIds))
toAdd := make([]interface{}, 0, len(newMgPublicIds))
for mgId := range newMgPublicIds {
newMg := AllocManagedGroupMemberAccount()
newMg.ManagedGroupId = mgId
newMg.MemberId = acct.PublicId
toAdd = append(toAdd, newMg)
}
if err := w.CreateItems(ctx, toAdd, db.NewOplogMsgs(&addOplogMsgs)); err != nil {
return errors.Wrap(err, op, errors.WithMsg("unable to add managed group member accounts"))
}
totalRowsAffected += len(toAdd)
msgs = append(msgs, addOplogMsgs...)
}
if len(msgs) > 0 {
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, mgTicket, metadata, msgs); err != nil {
return errors.Wrap(err, op, errors.WithMsg("unable to write oplog"))
}
}
currentMemberships, err = r.ListManagedGroupMembershipsByMember(ctx, acct.PublicId, WithReader(reader))
if err != nil {
return errors.Wrap(err, op, errors.WithMsg("unable to retrieve current managed group memberships after set"))
}
return nil
})
if err != nil && !errors.Match(errors.T(errors.GracefullyAborted), err) {
return nil, db.NoRowsAffected, errors.Wrap(err, op)
}
return currentMemberships, totalRowsAffected, nil
}
// ListManagedGroupMembershipsByMember lists managed group memberships via the
// member (account) ID and supports WithLimit option.
func (r *Repository) ListManagedGroupMembershipsByMember(ctx context.Context, withAcctId string, opt ...Option) ([]*ManagedGroupMemberAccount, error) {
const op = "oidc.(Repository).ListManagedGroupMembershipsByMember"
if withAcctId == "" {
return nil, errors.New(errors.InvalidParameter, op, "missing account 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
}
reader := r.reader
if opts.withReader != nil {
reader = opts.withReader
}
var mgs []*ManagedGroupMemberAccount
err := reader.SearchWhere(ctx, &mgs, "member_id = ?", []interface{}{withAcctId}, db.WithLimit(limit))
if err != nil {
return nil, errors.Wrap(err, op)
}
return mgs, nil
}
@@ -0,0 +1,281 @@
package oidc_test
import (
"context"
"math/rand"
"strings"
"testing"
"github.com/hashicorp/boundary/internal/auth/oidc"
"github.com/hashicorp/boundary/internal/auth/oidc/store"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/servers/controller"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const testFakeManagedGroupFilter = `"/foo" == "bar"`
func Test_ManagedGroupMemberships(t *testing.T) {
// This tests both managed group membership functions (set/list) as list is
// always called as a return from set and we are validating the values that
// come back against what we expect.
// This test can be run in parallel; the subtests *cannot*.
t.Parallel()
// Note: using a test controller here for ease of setup as we need a working
// dev OIDC auth method and associated accounts. This test is not making API
// calls! It's accessing the repo directly via the test controller's
// exposure of the underlying DB primitives.
tc := controller.NewTestController(t, nil)
defer tc.Shutdown()
conn := tc.DbConn()
rw := db.New(conn)
kmsCache := tc.Kms()
iamRepo := tc.IamRepo()
org, _ := iam.TestScopes(t, iamRepo)
ctx := context.Background()
databaseWrapper, err := kmsCache.GetWrapper(ctx, org.PublicId, kms.KeyPurposeDatabase)
require.NoError(t, err)
authMethod := oidc.TestAuthMethod(
t, conn, databaseWrapper, org.GetPublicId(), oidc.ActivePrivateState,
"alice-rp", "fido",
oidc.WithSigningAlgs(oidc.RS256),
oidc.WithIssuer(oidc.TestConvertToUrls(t, "https://www.alice.com")[0]),
oidc.WithApiUrl(oidc.TestConvertToUrls(t, "https://www.alice.com/callback")[0]),
)
repo, err := oidc.NewRepository(rw, rw, kmsCache)
require.NoError(t, err)
require.NotNil(t, repo)
mgs := make([]*oidc.ManagedGroup, 0, 10)
for i := 0; i < 100; i++ {
mg := oidc.AllocManagedGroup()
mg.AuthMethodId = authMethod.PublicId
mg.Filter = testFakeManagedGroupFilter
got, err := repo.CreateManagedGroup(context.Background(), org.GetPublicId(), mg)
require.NoError(t, err)
mgs = append(mgs, got)
}
// Fetch valid OIDC accounts. One will be "static" where we will simply
// ensure modifying the groups for the other doesn't affect it; the other
// will be used for testing.
var accts []*oidc.Account
err = rw.SearchWhere(ctx, &accts, "", nil, db.WithLimit(2))
require.NoError(t, err)
require.Len(t, accts, 2)
staticAccountId := accts[0].PublicId
staticMembershipCount := 20
accountId := accts[1].PublicId
account := oidc.AllocAccount()
account.PublicId = accountId
tests := []struct {
name string
// If true, we will auto populate necesary values into the function
validPrereqs bool
// Else these can be used for testing
authMethod *oidc.AuthMethod
authMethodId string
account *oidc.Account
accountId string
authMethodScopeId string
wantPreseededMgsCount int
wantMgsCount int
specificMgs []*oidc.ManagedGroup
wantErr errors.Code
wantErrContains string
}{
{
name: "nil auth method",
wantErr: errors.InvalidParameter,
wantErrContains: "missing auth method",
},
{
name: "missing auth method store",
authMethod: &oidc.AuthMethod{},
wantErr: errors.InvalidParameter,
wantErrContains: "missing auth method store",
},
{
name: "missing auth method id",
authMethod: &oidc.AuthMethod{AuthMethod: &store.AuthMethod{}},
wantErr: errors.InvalidParameter,
wantErrContains: "missing auth method id",
},
{
name: "missing auth method scope id",
authMethod: &oidc.AuthMethod{AuthMethod: &store.AuthMethod{PublicId: authMethod.PublicId}},
wantErr: errors.InvalidParameter,
wantErrContains: "missing auth method scope id",
},
{
name: "missing account",
authMethod: authMethod,
wantErr: errors.InvalidParameter,
wantErrContains: "missing account",
},
{
name: "missing account store",
authMethod: authMethod,
account: &oidc.Account{},
wantErr: errors.InvalidParameter,
wantErrContains: "missing account store",
},
{
name: "missing account id",
authMethod: authMethod,
account: &oidc.Account{Account: &store.Account{}},
wantErr: errors.InvalidParameter,
wantErrContains: "missing account id",
},
{
name: "valid fixed, static",
validPrereqs: true,
accountId: staticAccountId,
specificMgs: mgs[0:staticMembershipCount],
},
{
name: "valid fixed",
validPrereqs: true,
specificMgs: mgs[0:20],
},
{
name: "valid fixed, same values",
validPrereqs: true,
specificMgs: mgs[0:20],
},
{
name: "valid fixed, new values",
validPrereqs: true,
specificMgs: mgs[20:40],
},
{
name: "valid none",
validPrereqs: true,
wantMgsCount: 0,
},
{
name: "valid none, second test, testing gracefully aborting",
validPrereqs: true,
wantMgsCount: 0,
},
{
name: "valid fixed, prep for random",
validPrereqs: true,
specificMgs: mgs[20:50],
},
{
name: "valid random",
validPrereqs: true,
wantMgsCount: 30,
},
{
name: "valid random, second test",
validPrereqs: true,
wantMgsCount: 20,
},
{
name: "valid with duplicates",
validPrereqs: true,
specificMgs: append(mgs[0:20], mgs[0:20]...),
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
// We are intentionally carrying things over between tests to be
// more realistic but that means we need correct versions, so update
// them first.
currMgs, err := repo.ListManagedGroups(ctx, authMethod.PublicId)
require.NoError(err)
require.Len(currMgs, 100)
currVersionMap := make(map[string]uint32, len(currMgs))
for _, currMg := range currMgs {
currVersionMap[currMg.PublicId] = currMg.Version
}
for _, mg := range mgs {
mg.Version = currVersionMap[mg.PublicId]
}
var mgsToTest []*oidc.ManagedGroup
var finalMgs map[string]*oidc.ManagedGroup
// If we know the inputs are sane, create the test data
if tt.validPrereqs {
tt.authMethod = authMethod
tt.account = oidc.AllocAccount()
tt.account.PublicId = accountId
if tt.accountId != "" {
// This is for the test where we initially populate the
// static account
tt.account.PublicId = tt.accountId
}
mgsToTest = tt.specificMgs
if mgsToTest == nil {
// Select at random
mgsToTest = make([]*oidc.ManagedGroup, tt.wantMgsCount)
for i := 0; i < tt.wantMgsCount; i++ {
mg := mgs[rand.Int()%len(mgs)]
mgsToTest[i] = mg
}
}
finalMgs = make(map[string]*oidc.ManagedGroup)
for _, v := range mgsToTest {
finalMgs[v.PublicId] = v
}
}
memberships, _, err := repo.SetManagedGroupMemberships(ctx, tt.authMethod, tt.account, mgsToTest)
if tt.wantErr != 0 {
assert.Truef(errors.Match(errors.T(tt.wantErr), err), "Unexpected error %s", err)
if tt.wantErrContains != "" {
assert.True(strings.Contains(err.Error(), tt.wantErrContains))
}
return
}
require.NoError(err)
assert.Len(memberships, len(finalMgs))
// Ensure the same set was found; all memberships found should have
// been in the finalMgs map, and when they are all removed there
// should be nothing left.
for _, mship := range memberships {
assert.Contains(finalMgs, mship.ManagedGroupId)
delete(finalMgs, mship.ManagedGroupId)
}
assert.Len(finalMgs, 0)
// Now check that the static account still has the same memberships
memberships, err = repo.ListManagedGroupMembershipsByMember(ctx, staticAccountId)
require.NoError(err)
assert.Len(memberships, staticMembershipCount)
finalMgs = make(map[string]*oidc.ManagedGroup, staticMembershipCount)
for _, mg := range mgs[0:staticMembershipCount] {
finalMgs[mg.PublicId] = mg
}
for _, mship := range memberships {
assert.Contains(finalMgs, mship.ManagedGroupId)
delete(finalMgs, mship.ManagedGroupId)
}
assert.Len(finalMgs, 0)
})
}
}
@@ -19,7 +19,7 @@ import (
"github.com/stretchr/testify/require"
)
const testFakeFilter = `"/foo" == "bar"`
const testFakeManagedGroupFilter = `"/foo" == "bar"`
func TestRepository_CreateManagedGroup(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
@@ -91,7 +91,7 @@ func TestRepository_CreateManagedGroup(t *testing.T) {
in: &ManagedGroup{
ManagedGroup: &store.ManagedGroup{
AuthMethodId: authMethod.PublicId,
Filter: testFakeFilter,
Filter: testFakeManagedGroupFilter,
PublicId: "mgoidc_OOOOOOOOOO",
},
},
@@ -103,7 +103,7 @@ func TestRepository_CreateManagedGroup(t *testing.T) {
in: &ManagedGroup{
ManagedGroup: &store.ManagedGroup{
AuthMethodId: authMethod.PublicId,
Filter: testFakeFilter,
Filter: testFakeManagedGroupFilter,
},
},
wantIsErr: errors.InvalidParameter,
@@ -115,13 +115,13 @@ func TestRepository_CreateManagedGroup(t *testing.T) {
in: &ManagedGroup{
ManagedGroup: &store.ManagedGroup{
AuthMethodId: authMethod.PublicId,
Filter: testFakeFilter,
Filter: testFakeManagedGroupFilter,
},
},
want: &ManagedGroup{
ManagedGroup: &store.ManagedGroup{
AuthMethodId: authMethod.PublicId,
Filter: testFakeFilter,
Filter: testFakeManagedGroupFilter,
},
},
},
@@ -131,14 +131,14 @@ func TestRepository_CreateManagedGroup(t *testing.T) {
in: &ManagedGroup{
ManagedGroup: &store.ManagedGroup{
AuthMethodId: authMethod.PublicId,
Filter: testFakeFilter,
Filter: testFakeManagedGroupFilter,
Name: "test-name-repo",
},
},
want: &ManagedGroup{
ManagedGroup: &store.ManagedGroup{
AuthMethodId: authMethod.PublicId,
Filter: testFakeFilter,
Filter: testFakeManagedGroupFilter,
Name: "test-name-repo",
},
},
@@ -149,7 +149,7 @@ func TestRepository_CreateManagedGroup(t *testing.T) {
in: &ManagedGroup{
ManagedGroup: &store.ManagedGroup{
AuthMethodId: authMethod.PublicId,
Filter: testFakeFilter,
Filter: testFakeManagedGroupFilter,
Description: ("test-description-repo"),
Name: "myname",
},
@@ -157,7 +157,7 @@ func TestRepository_CreateManagedGroup(t *testing.T) {
want: &ManagedGroup{
ManagedGroup: &store.ManagedGroup{
AuthMethodId: authMethod.PublicId,
Filter: testFakeFilter,
Filter: testFakeManagedGroupFilter,
Description: ("test-description-repo"),
Name: "myname",
},
@@ -169,7 +169,7 @@ func TestRepository_CreateManagedGroup(t *testing.T) {
in: &ManagedGroup{
ManagedGroup: &store.ManagedGroup{
AuthMethodId: authMethod.PublicId,
Filter: testFakeFilter,
Filter: testFakeManagedGroupFilter,
Description: ("test-description-repo"),
Name: "myname",
},
@@ -231,7 +231,7 @@ func TestRepository_LookupManagedGroup(t *testing.T) {
WithIssuer(TestConvertToUrls(t, "https://www.alice.com")[0]),
WithApiUrl(TestConvertToUrls(t, "https://www.alice.com/callback")[0]),
)
mg := TestManagedGroup(t, conn, authMethod, testFakeFilter)
mg := TestManagedGroup(t, conn, authMethod, testFakeManagedGroupFilter)
newMgId, err := newManagedGroupId()
require.NoError(t, err)
@@ -296,7 +296,7 @@ func TestRepository_DeleteManagedGroup(t *testing.T) {
WithIssuer(TestConvertToUrls(t, "https://www.alice.com")[0]),
WithApiUrl(TestConvertToUrls(t, "https://www.alice.com/callback")[0]),
)
mg := TestManagedGroup(t, conn, authMethod, testFakeFilter)
mg := TestManagedGroup(t, conn, authMethod, testFakeManagedGroupFilter)
newMgId, err := newManagedGroupId()
require.NoError(t, err)
tests := []struct {
@@ -387,18 +387,18 @@ func TestRepository_ListManagedGroups(t *testing.T) {
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),
TestManagedGroup(t, conn, authMethod1, testFakeManagedGroupFilter),
TestManagedGroup(t, conn, authMethod1, testFakeManagedGroupFilter),
TestManagedGroup(t, conn, authMethod1, testFakeManagedGroupFilter),
}
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),
TestManagedGroup(t, conn, authMethod2, testFakeManagedGroupFilter),
TestManagedGroup(t, conn, authMethod2, testFakeManagedGroupFilter),
TestManagedGroup(t, conn, authMethod2, testFakeManagedGroupFilter),
}
sort.Slice(mgs2, func(i, j int) bool {
return strings.Compare(mgs2[i].PublicId, mgs2[j].PublicId) < 0
@@ -789,7 +789,7 @@ func TestRepository_UpdateManagedGroup(t *testing.T) {
version: 1,
orig: &ManagedGroup{
ManagedGroup: &store.ManagedGroup{
Filter: testFakeFilter,
Filter: testFakeManagedGroupFilter,
},
},
chgFn: changeFilter(`"/zip" == "zap"`),
@@ -865,7 +865,7 @@ func TestRepository_UpdateManagedGroup(t *testing.T) {
version: 1,
orig: &ManagedGroup{
ManagedGroup: &store.ManagedGroup{
Filter: testFakeFilter,
Filter: testFakeManagedGroupFilter,
},
},
masks: []string{FilterField},
@@ -923,7 +923,7 @@ func TestRepository_UpdateManagedGroup(t *testing.T) {
assert.NoError(err)
require.NotNil(repo)
orig := TestManagedGroup(t, conn, am, testFakeFilter, WithName(tt.orig.GetName()), WithDescription(tt.orig.GetDescription()))
orig := TestManagedGroup(t, conn, am, testFakeManagedGroupFilter, WithName(tt.orig.GetName()), WithDescription(tt.orig.GetDescription()))
tt.orig.AuthMethodId = am.PublicId
if tt.chgFn != nil {
+2
View File
@@ -203,6 +203,8 @@ func Callback(
return "", errors.Wrap(err, op)
}
// Now we need to check filters and assign managed groups by filter.
// wow, we're getting close. we just need to create a pending token for this
// successful authentication process, so it can be retrieved by the polling client
// that initialed the authentication attempt.
+128 -32
View File
@@ -948,6 +948,77 @@ func (x *ManagedGroup) GetFilter() string {
return ""
}
// ManagedGroupMemberAccount contains a mapping between a managed group and a
// member account.
type ManagedGroupMemberAccount struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The create_time is set by the database.
// @inject_tag: `gorm:"default:current_timestamp"`
CreateTime *timestamp.Timestamp `protobuf:"bytes,10,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"`
// managed_group_id is the fk to the oidc managed group public id
// @inject_tag: `gorm:"primary_key"`
ManagedGroupId string `protobuf:"bytes,20,opt,name=managed_group_id,json=managedGroupId,proto3" json:"managed_group_id,omitempty" gorm:"primary_key"`
// member_id is the fk to the oidc account public id
// @inject_tag: `gorm:"primary_key"`
MemberId string `protobuf:"bytes,30,opt,name=member_id,json=memberId,proto3" json:"member_id,omitempty" gorm:"primary_key"`
}
func (x *ManagedGroupMemberAccount) Reset() {
*x = ManagedGroupMemberAccount{}
if protoimpl.UnsafeEnabled {
mi := &file_controller_storage_auth_oidc_store_v1_oidc_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ManagedGroupMemberAccount) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ManagedGroupMemberAccount) ProtoMessage() {}
func (x *ManagedGroupMemberAccount) ProtoReflect() protoreflect.Message {
mi := &file_controller_storage_auth_oidc_store_v1_oidc_proto_msgTypes[8]
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 ManagedGroupMemberAccount.ProtoReflect.Descriptor instead.
func (*ManagedGroupMemberAccount) Descriptor() ([]byte, []int) {
return file_controller_storage_auth_oidc_store_v1_oidc_proto_rawDescGZIP(), []int{8}
}
func (x *ManagedGroupMemberAccount) GetCreateTime() *timestamp.Timestamp {
if x != nil {
return x.CreateTime
}
return nil
}
func (x *ManagedGroupMemberAccount) GetManagedGroupId() string {
if x != nil {
return x.ManagedGroupId
}
return ""
}
func (x *ManagedGroupMemberAccount) GetMemberId() string {
if x != nil {
return x.MemberId
}
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{
@@ -1159,11 +1230,22 @@ var file_controller_storage_auth_oidc_store_v1_oidc_proto_rawDesc = []byte{
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,
0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0xaf, 0x01, 0x0a, 0x19, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65,
0x64, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f,
0x75, 0x6e, 0x74, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69,
0x6d, 0x65, 0x18, 0x0a, 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, 0x28, 0x0a, 0x10, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x5f, 0x67, 0x72, 0x6f, 0x75,
0x70, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6d, 0x61, 0x6e, 0x61,
0x67, 0x65, 0x64, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x65,
0x6d, 0x62, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d,
0x65, 0x6d, 0x62, 0x65, 0x72, 0x49, 0x64, 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 (
@@ -1178,35 +1260,37 @@ 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, 8)
var file_controller_storage_auth_oidc_store_v1_oidc_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
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
(*SigningAlg)(nil), // 2: controller.storage.auth.oidc.store.v1.SigningAlg
(*AudClaim)(nil), // 3: controller.storage.auth.oidc.store.v1.AudClaim
(*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
(*ManagedGroup)(nil), // 7: controller.storage.auth.oidc.store.v1.ManagedGroup
(*timestamp.Timestamp)(nil), // 8: controller.storage.timestamp.v1.Timestamp
(*AuthMethod)(nil), // 0: controller.storage.auth.oidc.store.v1.AuthMethod
(*Account)(nil), // 1: controller.storage.auth.oidc.store.v1.Account
(*SigningAlg)(nil), // 2: controller.storage.auth.oidc.store.v1.SigningAlg
(*AudClaim)(nil), // 3: controller.storage.auth.oidc.store.v1.AudClaim
(*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
(*ManagedGroup)(nil), // 7: controller.storage.auth.oidc.store.v1.ManagedGroup
(*ManagedGroupMemberAccount)(nil), // 8: controller.storage.auth.oidc.store.v1.ManagedGroupMemberAccount
(*timestamp.Timestamp)(nil), // 9: controller.storage.timestamp.v1.Timestamp
}
var file_controller_storage_auth_oidc_store_v1_oidc_proto_depIdxs = []int32{
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
9, // 0: controller.storage.auth.oidc.store.v1.AuthMethod.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 1: controller.storage.auth.oidc.store.v1.AuthMethod.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 2: controller.storage.auth.oidc.store.v1.Account.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 3: controller.storage.auth.oidc.store.v1.Account.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 4: controller.storage.auth.oidc.store.v1.SigningAlg.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 5: controller.storage.auth.oidc.store.v1.AudClaim.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 6: controller.storage.auth.oidc.store.v1.Certificate.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 7: controller.storage.auth.oidc.store.v1.ClaimsScope.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 8: controller.storage.auth.oidc.store.v1.AccountClaimMap.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 9: controller.storage.auth.oidc.store.v1.ManagedGroup.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 10: controller.storage.auth.oidc.store.v1.ManagedGroup.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
9, // 11: controller.storage.auth.oidc.store.v1.ManagedGroupMemberAccount.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
12, // [12:12] is the sub-list for method output_type
12, // [12:12] is the sub-list for method input_type
12, // [12:12] is the sub-list for extension type_name
12, // [12:12] is the sub-list for extension extendee
0, // [0:12] is the sub-list for field type_name
}
func init() { file_controller_storage_auth_oidc_store_v1_oidc_proto_init() }
@@ -1311,6 +1395,18 @@ func file_controller_storage_auth_oidc_store_v1_oidc_proto_init() {
return nil
}
}
file_controller_storage_auth_oidc_store_v1_oidc_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ManagedGroupMemberAccount); 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{
@@ -1318,7 +1414,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: 8,
NumMessages: 9,
NumExtensions: 0,
NumServices: 0,
},
+5
View File
@@ -79,6 +79,11 @@ const (
MaxRetries Code = 1103 // MaxRetries represent that a db Tx hit max retires allowed
Exception Code = 1104 // Exception represent that an underlying db exception was raised
VersionMismatch Code = 1105 // VersionMismatch represents the update version and the db version for an entry do not match.
// GracefullyAborted means we intended to abort a transaction but the
// enclosing function should not treat it as an error; we aborted for
// reasons related to the state of the DDL and/or inputs (such as we're
// already in the right state and don't want to end up writing oplogs).
GracefullyAborted Code = 1106
// Migration setup errors are codes 2000-2999
MigrationIntegrity Code = 2000 // MigrationIntegrity represents an error with the generated migration related code
+5
View File
@@ -252,6 +252,11 @@ func TestCode_Both_String_Info(t *testing.T) {
c: OidcProviderCallbackError,
want: OidcProviderCallbackError,
},
{
name: "GracefullyAborted",
c: GracefullyAborted,
want: GracefullyAborted,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
+4
View File
@@ -196,4 +196,8 @@ var errorCodeInfo = map[Code]Info{
Message: "oidc provider callback error",
Kind: External,
},
GracefullyAborted: {
Message: "purposefully aborted without error",
Kind: Other,
},
}
@@ -285,4 +285,20 @@ message ManagedGroup {
// filter is a go-bexpr filter
// @inject_tag: `gorm:"not_null"`
string filter = 80 [(custom_options.v1.mask_mapping) = { this: "Filter" that: "filter" }];
}
// ManagedGroupMemberAccount contains a mapping between a managed group and a
// member account.
message ManagedGroupMemberAccount {
// The create_time is set by the database.
// @inject_tag: `gorm:"default:current_timestamp"`
timestamp.v1.Timestamp create_time = 10;
// managed_group_id is the fk to the oidc managed group public id
// @inject_tag: `gorm:"primary_key"`
string managed_group_id = 20;
// member_id is the fk to the oidc account public id
// @inject_tag: `gorm:"primary_key"`
string member_id = 30;
}