From cf6f757b739c201ee947b09d0a56461c9ffd0cbb Mon Sep 17 00:00:00 2001 From: Jim Date: Thu, 15 Dec 2022 16:36:05 -0500 Subject: [PATCH] feature (auth/ldap): add repo and reading an auth method (#2718) --- internal/auth/ldap/auth_method.go | 2 +- internal/auth/ldap/options.go | 47 ++- internal/auth/ldap/options_test.go | 32 ++ internal/auth/ldap/repository.go | 48 +++ .../auth/ldap/repository_auth_method_read.go | 237 ++++++++++++ .../ldap/repository_auth_method_read_test.go | 351 ++++++++++++++++++ internal/auth/ldap/repository_test.go | 118 ++++++ internal/auth/ldap/rewrapping_test.go | 36 +- internal/auth/ldap/store/ldap.pb.go | 239 ++++++------ internal/auth/ldap/testing.go | 24 +- internal/auth/ldap/testing_test.go | 14 +- .../migrations/oss/postgres/60/01_ldap.up.sql | 66 +++- .../storage/auth/ldap/store/v1/ldap.proto | 5 + 13 files changed, 1082 insertions(+), 137 deletions(-) create mode 100644 internal/auth/ldap/repository.go create mode 100644 internal/auth/ldap/repository_auth_method_read.go create mode 100644 internal/auth/ldap/repository_auth_method_read_test.go create mode 100644 internal/auth/ldap/repository_test.go diff --git a/internal/auth/ldap/auth_method.go b/internal/auth/ldap/auth_method.go index 61a2a198e8..7bfb2c48be 100644 --- a/internal/auth/ldap/auth_method.go +++ b/internal/auth/ldap/auth_method.go @@ -57,7 +57,7 @@ func NewAuthMethod(ctx context.Context, scopeId string, urls []*url.URL, opt ... ScopeId: scopeId, Name: opts.withName, Description: opts.withDescription, - OperationalState: string(InactiveState), // all new auth methods are initially inactive + OperationalState: string(opts.withOperationalState), // if no option is specified, a new auth method is initially inactive Urls: strUrls, StartTls: opts.withStartTls, InsecureTls: opts.withInsecureTls, diff --git a/internal/auth/ldap/options.go b/internal/auth/ldap/options.go index cab9ac5974..68ffcd2d9b 100644 --- a/internal/auth/ldap/options.go +++ b/internal/auth/ldap/options.go @@ -27,13 +27,20 @@ type options struct { withBindPassword string withClientCertificate string withClientCertificateKey []byte + withLimit int + withUnauthenticatedUser bool + withOrderByCreateTime bool + ascending bool + withOperationalState AuthMethodState } // Option - how options are passed as args type Option func(*options) error func getDefaultOptions() options { - return options{} + return options{ + withOperationalState: InactiveState, + } } func getOpts(opt ...Option) (options, error) { @@ -223,3 +230,41 @@ func WithClientCertificate(ctx context.Context, privKey []byte, cert *x509.Certi return nil } } + +// WithLimit provides an option to provide a limit. Intentionally allowing +// negative integers. If WithLimit < 0, then unlimited results are returned. +// If WithLimit == 0, then default limits are used for results. +func WithLimit(l int) Option { + return func(o *options) error { + o.withLimit = l + return nil + } +} + +// WithUnauthenticatedUser provides an option for filtering results for +// an unauthenticated users. +func WithUnauthenticatedUser(enabled bool) Option { + return func(o *options) error { + o.withUnauthenticatedUser = enabled + return nil + } +} + +// WithOrderByCreateTime provides an option to specify ordering by the +// CreateTime field. +func WithOrderByCreateTime(ascending bool) Option { + return func(o *options) error { + o.withOrderByCreateTime = true + o.ascending = ascending + return nil + } +} + +// WithOperationalState provides an option for specifying the auth method's +// operational state +func WithOperationalState(state AuthMethodState) Option { + return func(o *options) error { + o.withOperationalState = state + return nil + } +} diff --git a/internal/auth/ldap/options_test.go b/internal/auth/ldap/options_test.go index f00b8862ad..602cda24df 100644 --- a/internal/auth/ldap/options_test.go +++ b/internal/auth/ldap/options_test.go @@ -204,4 +204,36 @@ func Test_getOpts(t *testing.T) { require.Error(t, err) assert.Contains(err.Error(), "asn1: structure error") }) + t.Run("WithLimit", func(t *testing.T) { + opts, err := getOpts(WithLimit(5)) + require.NoError(t, err) + testOpts := getDefaultOptions() + testOpts.withLimit = 5 + assert.Equal(t, opts, testOpts) + }) + t.Run("WithUnauthenticatedUser", func(t *testing.T) { + assert := assert.New(t) + opts, err := getOpts(WithUnauthenticatedUser(true)) + require.NoError(t, err) + testOpts := getDefaultOptions() + testOpts.withUnauthenticatedUser = true + assert.Equal(opts, testOpts) + }) + t.Run("WithOrderByCreateTime", func(t *testing.T) { + assert := assert.New(t) + opts, err := getOpts(WithOrderByCreateTime(true)) + require.NoError(t, err) + testOpts := getDefaultOptions() + testOpts.withOrderByCreateTime = true + testOpts.ascending = true + assert.Equal(opts, testOpts) + }) + t.Run("WithOperationalState", func(t *testing.T) { + assert := assert.New(t) + opts, err := getOpts(WithOperationalState(ActivePublicState)) + require.NoError(t, err) + testOpts := getDefaultOptions() + testOpts.withOperationalState = ActivePublicState + assert.Equal(opts, testOpts) + }) } diff --git a/internal/auth/ldap/repository.go b/internal/auth/ldap/repository.go new file mode 100644 index 0000000000..f2c8a398e5 --- /dev/null +++ b/internal/auth/ldap/repository.go @@ -0,0 +1,48 @@ +package ldap + +import ( + "context" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/kms" +) + +// Repository is the ldap repository +type Repository struct { + reader db.Reader + writer db.Writer + kms *kms.Kms + + // defaultLimit provides a default for limiting the number of results returned from the repo + defaultLimit int +} + +// NewRepository creates a new ldap Repository. Supports the options: WithLimit +// which sets a default limit on results returned by repo operations. +func NewRepository(ctx context.Context, r db.Reader, w db.Writer, kms *kms.Kms, opt ...Option) (*Repository, error) { + const op = "ldap.NewRepository" + if r == nil { + return nil, errors.New(ctx, errors.InvalidParameter, op, "reader is nil") + } + if w == nil { + return nil, errors.New(ctx, errors.InvalidParameter, op, "writer is nil") + } + if kms == nil { + return nil, errors.New(ctx, errors.InvalidParameter, op, "kms is nil") + } + opts, err := getOpts(opt...) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + if opts.withLimit == 0 { + // zero signals the boundary defaults should be used. + opts.withLimit = db.DefaultLimit + } + return &Repository{ + reader: r, + writer: w, + kms: kms, + defaultLimit: opts.withLimit, + }, nil +} diff --git a/internal/auth/ldap/repository_auth_method_read.go b/internal/auth/ldap/repository_auth_method_read.go new file mode 100644 index 0000000000..410c1f4839 --- /dev/null +++ b/internal/auth/ldap/repository_auth_method_read.go @@ -0,0 +1,237 @@ +package ldap + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/db/timestamp" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/go-kms-wrapping/v2/extras/structwrapping" +) + +// LookupAuthMethod will lookup an auth method in the repo, along with its +// associated Value Objects of SigningAlgs, CallbackUrls, AudClaims and +// Certificates. If it's not found, it will return nil, nil. The +// WithUnauthenticatedUser options is supported and all other options are +// ignored. +func (r *Repository) LookupAuthMethod(ctx context.Context, publicId string, opt ...Option) (*AuthMethod, error) { + const op = "ldap.(Repository).LookupAuthMethod" + if publicId == "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "missing public id") + } + opts, err := getOpts(opt...) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + return r.lookupAuthMethod(ctx, publicId, WithUnauthenticatedUser(opts.withUnauthenticatedUser)) +} + +// ListAuthMethods returns a slice of AuthMethods for the scopeId. The +// WithUnauthenticatedUser, WithLimit and WithOrder options are supported and +// all other options are ignored. +func (r *Repository) ListAuthMethods(ctx context.Context, scopeIds []string, opt ...Option) ([]*AuthMethod, error) { + const op = "ldap.(Repository).ListAuthMethods" + if len(scopeIds) == 0 { + return nil, errors.New(ctx, errors.InvalidParameter, op, "missing scope IDs") + } + authMethods, err := r.getAuthMethods(ctx, "", scopeIds, opt...) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + return authMethods, nil +} + +// lookupAuthMethod will lookup a single auth method +func (r *Repository) lookupAuthMethod(ctx context.Context, authMethodId string, opt ...Option) (*AuthMethod, error) { + const op = "ldap.(Repository).lookupAuthMethod" + var err error + ams, err := r.getAuthMethods(ctx, authMethodId, nil, opt...) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + switch { + case len(ams) == 0: + return nil, nil // not an error to return no rows for a "lookup" + case len(ams) > 1: + return nil, errors.New(ctx, errors.NotSpecificIntegrity, op, fmt.Sprintf("%s matched more than 1 ", authMethodId)) + default: + return ams[0], nil + } +} + +// getAuthMethods allows the caller to either lookup a specific AuthMethod via +// its id or search for a set AuthMethods within a set of scopes. Passing both +// scopeIds and a authMethod is an error. The WithUnauthenticatedUser, +// WithLimit and WithOrder options are supported and all other options are +// ignored. +// +// The AuthMethod returned has its value objects populated (SigningAlgs, +// CallbackUrls, AudClaims and Certificates). The AuthMethod returned has its +// IsPrimaryAuthMethod bool set. +// +// When no record is found it returns nil, nil +func (r *Repository) getAuthMethods(ctx context.Context, authMethodId string, scopeIds []string, opt ...Option) ([]*AuthMethod, error) { + const op = "ldap.(Repository).getAuthMethods" + if authMethodId == "" && len(scopeIds) == 0 { + return nil, errors.New(ctx, errors.InvalidParameter, op, "missing search criteria: both auth method id and scope ids are empty") + } + if authMethodId != "" && len(scopeIds) > 0 { + return nil, errors.New(ctx, errors.InvalidParameter, op, "searching for both an auth method id and scope ids is not supported") + } + + const aggregateDelimiter = "|" + + dbArgs := []db.Option{} + opts, err := getOpts(opt...) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + limit := r.defaultLimit + if opts.withLimit != 0 { + // non-zero signals an override of the default limit for the repo. + limit = opts.withLimit + } + dbArgs = append(dbArgs, db.WithLimit(limit)) + + if opts.withOrderByCreateTime { + if opts.ascending { + dbArgs = append(dbArgs, db.WithOrder("create_time asc")) + } else { + dbArgs = append(dbArgs, db.WithOrder("create_time")) + } + } + + var args []any + var where []string + switch { + case authMethodId != "": + where, args = append(where, "public_id = ?"), append(args, authMethodId) + default: + where, args = append(where, "scope_id in(?)"), append(args, scopeIds) + } + + if opts.withUnauthenticatedUser { + // the caller is asking for a list of auth methods which can be returned + // to unauthenticated users (so they can authen). + where, args = append(where, "state = ?"), append(args, string(ActivePublicState)) + } + + var aggAuthMethods []*authMethodAgg + err = r.reader.SearchWhere(ctx, &aggAuthMethods, strings.Join(where, " and "), args, dbArgs...) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + + if len(aggAuthMethods) == 0 { // we're done if nothing is found. + return nil, nil + } + + authMethods := make([]*AuthMethod, 0, len(aggAuthMethods)) + for _, agg := range aggAuthMethods { + + ccKey := struct { + Ct []byte `wrapping:"ct,certificate_key"` + Pt []byte `wrapping:"pt,certificate_key"` + }{Ct: agg.ClientCertificateKey} + if agg.ClientCertificateCert != nil { + ccWrapper, err := r.kms.GetWrapper(ctx, agg.ScopeId, kms.KeyPurposeDatabase, kms.WithKeyId(agg.ClientCertificateKeyId)) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("failed to get database wrapper for client certificate")) + } + if err := structwrapping.UnwrapStruct(ctx, ccWrapper, &ccKey); err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithCode(errors.Decrypt), errors.WithMsg("failed to decrypt client certificate key")) + } + } + bindPassword := struct { + Ct []byte `wrapping:"ct,password"` + Pt []byte `wrapping:"pt,password"` + }{Ct: agg.BindPassword} + if agg.BindPassword != nil { + bindWrapper, err := r.kms.GetWrapper(ctx, agg.ScopeId, kms.KeyPurposeDatabase, kms.WithKeyId(agg.BindKeyId)) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("failed to get database wrapper for bind password")) + } + if err := structwrapping.UnwrapStruct(ctx, bindWrapper, &bindPassword); err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithCode(errors.Decrypt), errors.WithMsg("failed to decrypt bind password")) + } + } + am := allocAuthMethod() + am.PublicId = agg.PublicId + am.ScopeId = agg.ScopeId + am.IsPrimaryAuthMethod = agg.IsPrimaryAuthMethod + am.Name = agg.Name + am.Description = agg.Description + am.CreateTime = agg.CreateTime + am.UpdateTime = agg.UpdateTime + am.Version = agg.Version + am.OperationalState = agg.State + am.StartTls = agg.StartTLS + am.InsecureTls = agg.InsecureTLS + am.DiscoverDn = agg.DiscoverDn + am.AnonGroupSearch = agg.AnonGroupSearch + am.UpnDomain = agg.UpnDomain + if agg.Urls != "" { + am.Urls = strings.Split(agg.Urls, aggregateDelimiter) + } + if agg.Certs != "" { + am.Certificates = strings.Split(agg.Certs, aggregateDelimiter) + } + am.UserDn = agg.UserDn + am.UserAttr = agg.UserAttr + am.UserFilter = agg.UserFilter + am.GroupDn = agg.GroupDn + am.GroupAttr = agg.GroupAttr + am.GroupFilter = agg.GroupFilter + am.ClientCertificateKey = ccKey.Pt + am.ClientCertificateKeyHmac = agg.ClientCertificateKeyHmac + am.ClientCertificate = string(agg.ClientCertificateCert) + am.BindDn = agg.BindDn + am.BindPassword = string(bindPassword.Pt) + am.BindPasswordHmac = agg.BindPasswordHmac + + authMethods = append(authMethods, &am) + } + return authMethods, nil +} + +// authMethodAgg is a view that aggregates the auth method's value objects. If +// the value object can have multiple values like Urls and Certs, then the +// string field is delimited with the aggregateDelimiter of "|" +type authMethodAgg struct { + PublicId string `gorm:"primary_key"` + ScopeId string + IsPrimaryAuthMethod bool + Name string + Description string + CreateTime *timestamp.Timestamp + UpdateTime *timestamp.Timestamp + Version uint32 + State string + StartTLS bool + InsecureTLS bool + DiscoverDn bool + AnonGroupSearch bool + UpnDomain string + Urls string + Certs string + UserDn string + UserAttr string + UserFilter string + GroupDn string + GroupAttr string + GroupFilter string + ClientCertificateKey []byte + ClientCertificateKeyHmac []byte + ClientCertificateKeyId string + ClientCertificateCert []byte + BindDn string + BindPassword []byte + BindPasswordHmac []byte + BindKeyId string +} + +// TableName returns the table name for gorm +func (agg *authMethodAgg) TableName() string { return "ldap_auth_method_with_value_obj" } diff --git a/internal/auth/ldap/repository_auth_method_read_test.go b/internal/auth/ldap/repository_auth_method_read_test.go new file mode 100644 index 0000000000..6245617ef8 --- /dev/null +++ b/internal/auth/ldap/repository_auth_method_read_test.go @@ -0,0 +1,351 @@ +package ldap + +import ( + "context" + "crypto/ed25519" + "crypto/rand" + "crypto/x509" + "sort" + "testing" + + "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/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRepository_LookupAuthMethod(t *testing.T) { + testConn, _ := db.TestSetup(t, "postgres") + testRw := db.New(testConn) + testWrapper := db.TestWrapper(t) + testKms := kms.TestKms(t, testConn, testWrapper) + testCtx := context.Background() + org, _ := iam.TestScopes(t, iam.TestRepo(t, testConn, testWrapper)) + orgDbWrapper, err := testKms.GetWrapper(context.Background(), org.PublicId, kms.KeyPurposeDatabase) + require.NoError(t, err) + amInactive := TestAuthMethod(t, testConn, orgDbWrapper, org.PublicId, []string{"ldaps://ldap1.alice.com"}, WithOperationalState(InactiveState)) + amActivePriv := TestAuthMethod(t, testConn, orgDbWrapper, org.PublicId, []string{"ldaps://ldap2.alice.com"}, WithOperationalState(ActivePrivateState)) + amActivePub := TestAuthMethod(t, testConn, orgDbWrapper, org.PublicId, []string{"ldaps://ldap3.alice.com"}, WithOperationalState(ActivePublicState)) + amActivePub.IsPrimaryAuthMethod = true + iam.TestSetPrimaryAuthMethod(t, iam.TestRepo(t, testConn, testWrapper), org, amActivePub.PublicId) + + amId, err := newAuthMethodId(testCtx) + require.NoError(t, err) + tests := []struct { + name string + in string + opt []Option + want *AuthMethod + wantIsPrimary bool + wantErrMatch *errors.Template + wantErrContains string + }{ + { + name: "missing-public-id", + wantErrMatch: errors.T(errors.InvalidParameter), + wantErrContains: "missing public id", + }, + { + name: "invalid-opts", + in: amActivePub.GetPublicId(), + opt: []Option{WithBindCredential(testCtx, "", "")}, + wantErrMatch: errors.T(errors.InvalidParameter), + wantErrContains: "missing both dn and password", + }, + { + name: "non-existing-auth-method-id", + in: amId, + }, + { + name: "existing-auth-method-id", + in: amActivePriv.GetPublicId(), + want: amActivePriv, + }, + { + name: "unauthenticated-user-not-found-using-active-priv", + in: amActivePriv.GetPublicId(), + opt: []Option{WithUnauthenticatedUser(true)}, + want: nil, + }, + { + name: "unauthenticated-user-found-active-pub", + in: amActivePub.GetPublicId(), + opt: []Option{WithUnauthenticatedUser(true)}, + want: amActivePub, + wantIsPrimary: true, + }, + { + name: "unauthenticated-user-found-inactive", + in: amInactive.GetPublicId(), + opt: []Option{WithUnauthenticatedUser(true)}, + want: nil, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + repo, err := NewRepository(testCtx, testRw, testRw, testKms) + assert.NoError(err) + require.NotNil(repo) + got, err := repo.LookupAuthMethod(testCtx, tc.in, tc.opt...) + if tc.wantErrMatch != nil { + require.Error(err) + assert.Truef(errors.Match(tc.wantErrMatch, err), "want err code: %q got: %q", tc.wantErrMatch, err) + assert.Nil(got) + if tc.wantErrContains != "" { + assert.Contains(err.Error(), tc.wantErrContains) + } + return + } + require.NoError(err) + assert.EqualValues(tc.want, got) + }) + } +} + +func TestRepository_getAuthMethods(t *testing.T) { + testConn, _ := db.TestSetup(t, "postgres") + testRw := db.New(testConn) + testWrapper := db.TestWrapper(t) + testKms := kms.TestKms(t, testConn, testWrapper) + testCtx := context.Background() + + tests := []struct { + name string + setupFn func() (authMethodId string, scopeIds []string, want []*AuthMethod) + opt []Option + wantErrMatch *errors.Template + wantErrContains string + }{ + { + name: "valid-multi-scopes", + setupFn: func() (string, []string, []*AuthMethod) { + org, _ := iam.TestScopes(t, iam.TestRepo(t, testConn, testWrapper)) + orgDBWrapper, err := testKms.GetWrapper(context.Background(), org.PublicId, kms.KeyPurposeDatabase) + require.NoError(t, err) + org2, _ := iam.TestScopes(t, iam.TestRepo(t, testConn, testWrapper)) + orgDBWrapper2, err := testKms.GetWrapper(context.Background(), org2.PublicId, kms.KeyPurposeDatabase) + require.NoError(t, err) + + // make a test auth method with all options + am1a := TestAuthMethod(t, testConn, orgDBWrapper, org.PublicId, []string{"ldaps://ldap1.alice.com"}, WithOperationalState(InactiveState)) + am1b := TestAuthMethod(t, testConn, orgDBWrapper, org.PublicId, []string{"ldaps://ldap2.alice.com"}, WithOperationalState(InactiveState)) + am2 := TestAuthMethod(t, testConn, orgDBWrapper2, org2.PublicId, []string{"ldaps://ldap3.alice.com"}, WithOperationalState(InactiveState)) + return "", []string{am1a.ScopeId, am1b.ScopeId, am2.ScopeId}, []*AuthMethod{am1a, am1b, am2} + }, + }, + { + name: "valid-single-scopes", + setupFn: func() (string, []string, []*AuthMethod) { + org, _ := iam.TestScopes(t, iam.TestRepo(t, testConn, testWrapper)) + orgDBWrapper, err := testKms.GetWrapper(context.Background(), org.PublicId, kms.KeyPurposeDatabase) + require.NoError(t, err) + am1a := TestAuthMethod(t, testConn, orgDBWrapper, org.PublicId, []string{"ldaps://ldap1.alice.com"}, WithOperationalState(InactiveState)) + am1b := TestAuthMethod(t, testConn, orgDBWrapper, org.PublicId, []string{"ldaps://ldap2.alice.com"}, WithOperationalState(InactiveState)) + + return "", []string{am1a.ScopeId}, []*AuthMethod{am1a, am1b} + }, + }, + { + name: "valid-auth-method-id-all-opts", + setupFn: func() (string, []string, []*AuthMethod) { + org, _ := iam.TestScopes(t, iam.TestRepo(t, testConn, testWrapper)) + orgDbWrapper, err := testKms.GetWrapper(context.Background(), org.PublicId, kms.KeyPurposeDatabase) + require.NoError(t, err) + testCert, _ := testGenerateCA(t, "localhost") + _, testPrivKey, err := ed25519.GenerateKey(rand.Reader) + require.NoError(t, err) + derPrivKey, err := x509.MarshalPKCS8PrivateKey(testPrivKey) + require.NoError(t, err) + + am := TestAuthMethod(t, testConn, orgDbWrapper, org.PublicId, []string{"ldaps://alice.com"}, + WithName(testCtx, "test-name"), + WithDescription(testCtx, "test-description"), + WithStartTLS(testCtx), + WithInsecureTLS(testCtx), + WithDiscoverDn(testCtx), + WithAnonGroupSearch(testCtx), + WithUpnDomain(testCtx, "alice.com"), + WithUserDn(testCtx, "user-dn"), + WithUserAttr(testCtx, "user-attr"), + WithUserFilter(testCtx, "user-filter"), + WithGroupDn(testCtx, "group-dn"), + WithGroupAttr(testCtx, "group-attr"), + WithGroupFilter(testCtx, "group-filter"), + WithBindCredential(testCtx, "bind-dn", "bind-password"), + WithCertificates(testCtx, testCert), + WithClientCertificate(testCtx, derPrivKey, testCert), // not a client cert but good enough for this test.) + ) + return am.PublicId, nil, []*AuthMethod{am} + }, + }, + { + name: "with-limits", + setupFn: func() (string, []string, []*AuthMethod) { + org, _ := iam.TestScopes(t, iam.TestRepo(t, testConn, testWrapper)) + orgDBWrapper, err := testKms.GetWrapper(context.Background(), org.PublicId, kms.KeyPurposeDatabase) + require.NoError(t, err) + am1a := TestAuthMethod(t, testConn, orgDBWrapper, org.PublicId, []string{"ldaps://alice.com"}) + _ = TestAuthMethod(t, testConn, orgDBWrapper, org.PublicId, []string{"ldaps://alice2.com"}) + + return "", []string{am1a.ScopeId}, []*AuthMethod{am1a} + }, + opt: []Option{WithLimit(1), WithOrderByCreateTime(true)}, + }, + { + name: "unauthenticated-user", + setupFn: func() (string, []string, []*AuthMethod) { + org, _ := iam.TestScopes(t, iam.TestRepo(t, testConn, testWrapper)) + databaseWrapper, err := testKms.GetWrapper(context.Background(), org.PublicId, kms.KeyPurposeDatabase) + require.NoError(t, err) + _ = TestAuthMethod(t, testConn, databaseWrapper, org.PublicId, []string{"ldaps://ldap1.alice.com"}, WithOperationalState(InactiveState)) + _ = TestAuthMethod(t, testConn, databaseWrapper, org.PublicId, []string{"ldaps://ldap2.alice.com"}, WithOperationalState(ActivePrivateState)) + amActivePub := TestAuthMethod(t, testConn, databaseWrapper, org.PublicId, []string{"ldaps://ldap3.alice.com"}, WithOperationalState(ActivePublicState)) + + return "", []string{amActivePub.ScopeId}, []*AuthMethod{amActivePub} + }, + opt: []Option{WithUnauthenticatedUser(true)}, + }, + { + name: "not-found-auth-method-id", + setupFn: func() (string, []string, []*AuthMethod) { + return "not-a-valid-id", nil, nil + }, + }, + { + name: "not-found-scope-ids", + setupFn: func() (string, []string, []*AuthMethod) { + return "", []string{"not-valid-scope-1", "not-valid-scope-2"}, nil + }, + }, + { + name: "no-search-criteria", + setupFn: func() (string, []string, []*AuthMethod) { + return "", nil, nil + }, + wantErrMatch: errors.T(errors.InvalidParameter), + wantErrContains: "missing search criteria: both auth method id and scope ids are empty", + }, + { + name: "search-too-many", + setupFn: func() (string, []string, []*AuthMethod) { + return "auth-method-id", []string{"scope-id"}, nil + }, + wantErrMatch: errors.T(errors.InvalidParameter), + wantErrContains: "searching for both an auth method id and scope ids is not supported", + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + r, err := NewRepository(testCtx, testRw, testRw, testKms) + require.NoError(err) + + authMethodId, scopeIds, want := tc.setupFn() + + got, err := r.getAuthMethods(testCtx, authMethodId, scopeIds, tc.opt...) + + if tc.wantErrMatch != nil { + require.Error(err) + assert.Truef(errors.Match(tc.wantErrMatch, err), "want err code: %q got: %q", tc.wantErrMatch, err) + assert.Nil(got) + if tc.wantErrContains != "" { + assert.Contains(err.Error(), tc.wantErrContains) + } + return + } + require.NoError(err) + TestSortAuthMethods(t, want) + TestSortAuthMethods(t, got) + assert.Equal(want, got) + }) + } +} + +// TestRepository_ListAuthMethods only covers error conditions, since all the +// search criteria testing is handled in the TestRepository_getAuthMethods unit +// tests. +func TestRepository_ListAuthMethods(t *testing.T) { + testConn, _ := db.TestSetup(t, "postgres") + testRw := db.New(testConn) + testWrapper := db.TestWrapper(t) + testKms := kms.TestKms(t, testConn, testWrapper) + testCtx := context.Background() + iamRepo := iam.TestRepo(t, testConn, testWrapper) + + tests := []struct { + name string + setupFn func() (scopeIds []string, want []*AuthMethod, wantPrimaryAuthMethodId string) + opt []Option + wantErrMatch *errors.Template + }{ + { + name: "with-limits", + setupFn: func() ([]string, []*AuthMethod, string) { + org, _ := iam.TestScopes(t, iam.TestRepo(t, testConn, testWrapper)) + orgDbWrapper, err := testKms.GetWrapper(context.Background(), org.PublicId, kms.KeyPurposeDatabase) + require.NoError(t, err) + am1a := TestAuthMethod(t, testConn, orgDbWrapper, org.PublicId, []string{"ldaps://alice.com"}) + iam.TestSetPrimaryAuthMethod(t, iamRepo, org, am1a.PublicId) + am1a.IsPrimaryAuthMethod = true + + _ = TestAuthMethod(t, testConn, orgDbWrapper, org.PublicId, []string{"ldaps://alice2.com"}) + + return []string{am1a.ScopeId}, []*AuthMethod{am1a}, am1a.PublicId + }, + opt: []Option{WithLimit(1), WithOrderByCreateTime(true)}, + }, + { + name: "no-search-criteria", + setupFn: func() ([]string, []*AuthMethod, string) { + return nil, nil, "" + }, + wantErrMatch: errors.T(errors.InvalidParameter), + }, + } + + 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(testCtx, testRw, testRw, testKms) + assert.NoError(err) + scopeIds, want, wantPrimaryAuthMethodId := tt.setupFn() + + got, err := repo.ListAuthMethods(testCtx, scopeIds, tt.opt...) + if tt.wantErrMatch != nil { + require.Error(err) + assert.Truef(errors.Match(tt.wantErrMatch, err), "want err code: %q got: %q", tt.wantErrMatch, err) + assert.Nil(got) + return + } + require.NoError(err) + sort.Slice(want, func(a, b int) bool { + return want[a].PublicId < want[b].PublicId + }) + sort.Slice(got, func(a, b int) bool { + return got[a].PublicId < got[b].PublicId + }) + assert.Equal(want, got) + if wantPrimaryAuthMethodId != "" { + found := false + for _, am := range got { + if am.PublicId == wantPrimaryAuthMethodId { + assert.Truef(am.IsPrimaryAuthMethod, "expected IsPrimaryAuthMethod to be true for: %s", am.PublicId) + if am.IsPrimaryAuthMethod { + found = true + } + } + } + assert.Truef(found, "expected to find primary id %s in: %+v", wantPrimaryAuthMethodId, got) + } else { + for _, am := range got { + assert.Falsef(am.IsPrimaryAuthMethod, "did not expect %s to be IsPrimaryAuthMethod", am.PublicId) + } + } + }) + } +} diff --git a/internal/auth/ldap/repository_test.go b/internal/auth/ldap/repository_test.go new file mode 100644 index 0000000000..0987a6b707 --- /dev/null +++ b/internal/auth/ldap/repository_test.go @@ -0,0 +1,118 @@ +package ldap + +import ( + "context" + "testing" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/kms" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewRepository(t *testing.T) { + ctx := context.TODO() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + kmsCache := kms.TestKms(t, conn, wrapper) + + type args struct { + r db.Reader + w db.Writer + kms *kms.Kms + opts []Option + } + tests := []struct { + name string + args args + want *Repository + wantErrMatch *errors.Template + }{ + { + name: "valid", + args: args{ + r: rw, + w: rw, + kms: kmsCache, + }, + want: &Repository{ + reader: rw, + writer: rw, + kms: kmsCache, + defaultLimit: db.DefaultLimit, + }, + }, + { + name: "valid with limit", + args: args{ + r: rw, + w: rw, + kms: kmsCache, + opts: []Option{WithLimit(5)}, + }, + want: &Repository{ + reader: rw, + writer: rw, + kms: kmsCache, + defaultLimit: 5, + }, + }, + { + name: "nil-reader", + args: args{ + r: nil, + w: rw, + kms: kmsCache, + }, + want: nil, + wantErrMatch: errors.T(errors.InvalidParameter), + }, + { + name: "nil-writer", + args: args{ + r: rw, + w: nil, + kms: kmsCache, + }, + want: nil, + wantErrMatch: errors.T(errors.InvalidParameter), + }, + { + name: "nil-wrapper", + args: args{ + r: rw, + w: rw, + kms: nil, + }, + want: nil, + wantErrMatch: errors.T(errors.InvalidParameter), + }, + { + name: "all-nils", + args: args{ + r: nil, + w: nil, + kms: nil, + }, + want: nil, + wantErrMatch: errors.T(errors.InvalidParameter), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + got, err := NewRepository(ctx, tt.args.r, tt.args.w, tt.args.kms, tt.args.opts...) + if tt.wantErrMatch != nil { + require.Error(err) + assert.Truef(errors.Match(tt.wantErrMatch, err), "want err code: %q got: %q", tt.wantErrMatch, err) + assert.Nil(got) + return + } + require.NoError(err) + require.NotNil(got) + assert.Equal(tt.want, got) + }) + } +} diff --git a/internal/auth/ldap/rewrapping_test.go b/internal/auth/ldap/rewrapping_test.go index ad0761b85f..33663e65bb 100644 --- a/internal/auth/ldap/rewrapping_test.go +++ b/internal/auth/ldap/rewrapping_test.go @@ -40,7 +40,7 @@ func TestRewrap_bindCredentialRewrapFn(t *testing.T) { name: "success", ctx: testCtx, setup: func() (string, string, *BindCredential, *AuthMethod) { - am := testAuthMethod(t, conn, orgDBWrapper, org.PublicId, []string{"ldaps://alice.com"}, WithBindCredential(testCtx, "bind-dn", "bind-password")) + am := TestAuthMethod(t, conn, orgDBWrapper, org.PublicId, []string{"ldaps://alice.com"}, WithBindCredential(testCtx, "bind-dn", "bind-password")) bc := allocBindCredential() err = rw.LookupWhere(testCtx, &bc, "ldap_method_id = ?", []any{am.PublicId}) require.NoError(t, err) @@ -59,7 +59,7 @@ func TestRewrap_bindCredentialRewrapFn(t *testing.T) { name: "missing-key-id", ctx: testCtx, setup: func() (string, string, *BindCredential, *AuthMethod) { - am := testAuthMethod(t, conn, orgDBWrapper, org.PublicId, []string{"ldaps://alice.com"}, WithBindCredential(testCtx, "bind-dn", "bind-password")) + am := TestAuthMethod(t, conn, orgDBWrapper, org.PublicId, []string{"ldaps://alice.com"}, WithBindCredential(testCtx, "bind-dn", "bind-password")) bc := allocBindCredential() err = rw.LookupWhere(testCtx, &bc, "ldap_method_id = ?", []any{am.PublicId}) require.NoError(t, err) @@ -81,7 +81,7 @@ func TestRewrap_bindCredentialRewrapFn(t *testing.T) { name: "missing-scope-id", ctx: testCtx, setup: func() (string, string, *BindCredential, *AuthMethod) { - am := testAuthMethod(t, conn, orgDBWrapper, org.PublicId, []string{"ldaps://alice.com"}, WithBindCredential(testCtx, "bind-dn", "bind-password")) + am := TestAuthMethod(t, conn, orgDBWrapper, org.PublicId, []string{"ldaps://alice.com"}, WithBindCredential(testCtx, "bind-dn", "bind-password")) bc := allocBindCredential() err = rw.LookupWhere(testCtx, &bc, "ldap_method_id = ?", []any{am.PublicId}) require.NoError(t, err) @@ -103,7 +103,7 @@ func TestRewrap_bindCredentialRewrapFn(t *testing.T) { name: "missing-reader", ctx: testCtx, setup: func() (string, string, *BindCredential, *AuthMethod) { - am := testAuthMethod(t, conn, orgDBWrapper, org.PublicId, []string{"ldaps://alice.com"}, WithBindCredential(testCtx, "bind-dn", "bind-password")) + am := TestAuthMethod(t, conn, orgDBWrapper, org.PublicId, []string{"ldaps://alice.com"}, WithBindCredential(testCtx, "bind-dn", "bind-password")) bc := allocBindCredential() err = rw.LookupWhere(testCtx, &bc, "ldap_method_id = ?", []any{am.PublicId}) require.NoError(t, err) @@ -125,7 +125,7 @@ func TestRewrap_bindCredentialRewrapFn(t *testing.T) { name: "missing-writer", ctx: testCtx, setup: func() (string, string, *BindCredential, *AuthMethod) { - am := testAuthMethod(t, conn, orgDBWrapper, org.PublicId, []string{"ldaps://alice.com"}, WithBindCredential(testCtx, "bind-dn", "bind-password")) + am := TestAuthMethod(t, conn, orgDBWrapper, org.PublicId, []string{"ldaps://alice.com"}, WithBindCredential(testCtx, "bind-dn", "bind-password")) bc := allocBindCredential() err = rw.LookupWhere(testCtx, &bc, "ldap_method_id = ?", []any{am.PublicId}) require.NoError(t, err) @@ -147,7 +147,7 @@ func TestRewrap_bindCredentialRewrapFn(t *testing.T) { name: "missing-kms", ctx: testCtx, setup: func() (string, string, *BindCredential, *AuthMethod) { - am := testAuthMethod(t, conn, orgDBWrapper, org.PublicId, []string{"ldaps://alice.com"}, WithBindCredential(testCtx, "bind-dn", "bind-password")) + am := TestAuthMethod(t, conn, orgDBWrapper, org.PublicId, []string{"ldaps://alice.com"}, WithBindCredential(testCtx, "bind-dn", "bind-password")) bc := allocBindCredential() err = rw.LookupWhere(testCtx, &bc, "ldap_method_id = ?", []any{am.PublicId}) require.NoError(t, err) @@ -169,7 +169,7 @@ func TestRewrap_bindCredentialRewrapFn(t *testing.T) { name: "GetWrapper-err", ctx: testCtx, setup: func() (string, string, *BindCredential, *AuthMethod) { - am := testAuthMethod(t, conn, orgDBWrapper, org.PublicId, []string{"ldaps://alice.com"}, WithBindCredential(testCtx, "bind-dn", "bind-password")) + am := TestAuthMethod(t, conn, orgDBWrapper, org.PublicId, []string{"ldaps://alice.com"}, WithBindCredential(testCtx, "bind-dn", "bind-password")) bc := allocBindCredential() err = rw.LookupWhere(testCtx, &bc, "ldap_method_id = ?", []any{am.PublicId}) require.NoError(t, err) @@ -193,7 +193,7 @@ func TestRewrap_bindCredentialRewrapFn(t *testing.T) { name: "encrypt-err", ctx: testCtx, setup: func() (string, string, *BindCredential, *AuthMethod) { - am := testAuthMethod(t, conn, orgDBWrapper, org.PublicId, []string{"ldaps://alice.com"}, WithBindCredential(testCtx, "bind-dn", "bind-password")) + am := TestAuthMethod(t, conn, orgDBWrapper, org.PublicId, []string{"ldaps://alice.com"}, WithBindCredential(testCtx, "bind-dn", "bind-password")) bc := allocBindCredential() err = rw.LookupWhere(testCtx, &bc, "ldap_method_id = ?", []any{am.PublicId}) require.NoError(t, err) @@ -220,7 +220,7 @@ func TestRewrap_bindCredentialRewrapFn(t *testing.T) { name: "decrypt-err", ctx: testCtx, setup: func() (string, string, *BindCredential, *AuthMethod) { - am := testAuthMethod(t, conn, orgDBWrapper, org.PublicId, []string{"ldaps://alice.com"}, WithBindCredential(testCtx, "bind-dn", "bind-password")) + am := TestAuthMethod(t, conn, orgDBWrapper, org.PublicId, []string{"ldaps://alice.com"}, WithBindCredential(testCtx, "bind-dn", "bind-password")) bc := allocBindCredential() err = rw.LookupWhere(testCtx, &bc, "ldap_method_id = ?", []any{am.PublicId}) require.NoError(t, err) @@ -321,7 +321,7 @@ func TestRewrap_clientCertificateRewrapFn(t *testing.T) { cert, _ := testGenerateCA(t, "localhost") derPrivKey, err := x509.MarshalPKCS8PrivateKey(privKey) require.NoError(t, err) - am := testAuthMethod(t, conn, orgDBWrapper, org.PublicId, []string{"ldaps://alice.com"}, WithClientCertificate(testCtx, derPrivKey, cert)) + am := TestAuthMethod(t, conn, orgDBWrapper, org.PublicId, []string{"ldaps://alice.com"}, WithClientCertificate(testCtx, derPrivKey, cert)) cc := allocClientCertificate() err = rw.LookupWhere(testCtx, &cc, "ldap_method_id = ?", []any{am.GetPublicId()}) require.NoError(t, err) @@ -345,7 +345,7 @@ func TestRewrap_clientCertificateRewrapFn(t *testing.T) { cert, _ := testGenerateCA(t, "localhost") derPrivKey, err := x509.MarshalPKCS8PrivateKey(privKey) require.NoError(t, err) - am := testAuthMethod(t, conn, orgDBWrapper, org.PublicId, []string{"ldaps://alice.com"}, WithClientCertificate(testCtx, derPrivKey, cert)) + am := TestAuthMethod(t, conn, orgDBWrapper, org.PublicId, []string{"ldaps://alice.com"}, WithClientCertificate(testCtx, derPrivKey, cert)) cc := allocClientCertificate() err = rw.LookupWhere(testCtx, &cc, "ldap_method_id = ?", []any{am.GetPublicId()}) require.NoError(t, err) @@ -372,7 +372,7 @@ func TestRewrap_clientCertificateRewrapFn(t *testing.T) { cert, _ := testGenerateCA(t, "localhost") derPrivKey, err := x509.MarshalPKCS8PrivateKey(privKey) require.NoError(t, err) - am := testAuthMethod(t, conn, orgDBWrapper, org.PublicId, []string{"ldaps://alice.com"}, WithClientCertificate(testCtx, derPrivKey, cert)) + am := TestAuthMethod(t, conn, orgDBWrapper, org.PublicId, []string{"ldaps://alice.com"}, WithClientCertificate(testCtx, derPrivKey, cert)) cc := allocClientCertificate() err = rw.LookupWhere(testCtx, &cc, "ldap_method_id = ?", []any{am.GetPublicId()}) require.NoError(t, err) @@ -399,7 +399,7 @@ func TestRewrap_clientCertificateRewrapFn(t *testing.T) { cert, _ := testGenerateCA(t, "localhost") derPrivKey, err := x509.MarshalPKCS8PrivateKey(privKey) require.NoError(t, err) - am := testAuthMethod(t, conn, orgDBWrapper, org.PublicId, []string{"ldaps://alice.com"}, WithClientCertificate(testCtx, derPrivKey, cert)) + am := TestAuthMethod(t, conn, orgDBWrapper, org.PublicId, []string{"ldaps://alice.com"}, WithClientCertificate(testCtx, derPrivKey, cert)) cc := allocClientCertificate() err = rw.LookupWhere(testCtx, &cc, "ldap_method_id = ?", []any{am.GetPublicId()}) require.NoError(t, err) @@ -426,7 +426,7 @@ func TestRewrap_clientCertificateRewrapFn(t *testing.T) { cert, _ := testGenerateCA(t, "localhost") derPrivKey, err := x509.MarshalPKCS8PrivateKey(privKey) require.NoError(t, err) - am := testAuthMethod(t, conn, orgDBWrapper, org.PublicId, []string{"ldaps://alice.com"}, WithClientCertificate(testCtx, derPrivKey, cert)) + am := TestAuthMethod(t, conn, orgDBWrapper, org.PublicId, []string{"ldaps://alice.com"}, WithClientCertificate(testCtx, derPrivKey, cert)) cc := allocClientCertificate() err = rw.LookupWhere(testCtx, &cc, "ldap_method_id = ?", []any{am.GetPublicId()}) require.NoError(t, err) @@ -453,7 +453,7 @@ func TestRewrap_clientCertificateRewrapFn(t *testing.T) { cert, _ := testGenerateCA(t, "localhost") derPrivKey, err := x509.MarshalPKCS8PrivateKey(privKey) require.NoError(t, err) - am := testAuthMethod(t, conn, orgDBWrapper, org.PublicId, []string{"ldaps://alice.com"}, WithClientCertificate(testCtx, derPrivKey, cert)) + am := TestAuthMethod(t, conn, orgDBWrapper, org.PublicId, []string{"ldaps://alice.com"}, WithClientCertificate(testCtx, derPrivKey, cert)) cc := allocClientCertificate() err = rw.LookupWhere(testCtx, &cc, "ldap_method_id = ?", []any{am.GetPublicId()}) require.NoError(t, err) @@ -480,7 +480,7 @@ func TestRewrap_clientCertificateRewrapFn(t *testing.T) { cert, _ := testGenerateCA(t, "localhost") derPrivKey, err := x509.MarshalPKCS8PrivateKey(privKey) require.NoError(t, err) - am := testAuthMethod(t, conn, orgDBWrapper, org.PublicId, []string{"ldaps://alice.com"}, WithClientCertificate(testCtx, derPrivKey, cert)) + am := TestAuthMethod(t, conn, orgDBWrapper, org.PublicId, []string{"ldaps://alice.com"}, WithClientCertificate(testCtx, derPrivKey, cert)) cc := allocClientCertificate() err = rw.LookupWhere(testCtx, &cc, "ldap_method_id = ?", []any{am.GetPublicId()}) require.NoError(t, err) @@ -509,7 +509,7 @@ func TestRewrap_clientCertificateRewrapFn(t *testing.T) { cert, _ := testGenerateCA(t, "localhost") derPrivKey, err := x509.MarshalPKCS8PrivateKey(privKey) require.NoError(t, err) - am := testAuthMethod(t, conn, orgDBWrapper, org.PublicId, []string{"ldaps://alice.com"}, WithClientCertificate(testCtx, derPrivKey, cert)) + am := TestAuthMethod(t, conn, orgDBWrapper, org.PublicId, []string{"ldaps://alice.com"}, WithClientCertificate(testCtx, derPrivKey, cert)) cc := allocClientCertificate() err = rw.LookupWhere(testCtx, &cc, "ldap_method_id = ?", []any{am.GetPublicId()}) require.NoError(t, err) @@ -541,7 +541,7 @@ func TestRewrap_clientCertificateRewrapFn(t *testing.T) { cert, _ := testGenerateCA(t, "localhost") derPrivKey, err := x509.MarshalPKCS8PrivateKey(privKey) require.NoError(t, err) - am := testAuthMethod(t, conn, orgDBWrapper, org.PublicId, []string{"ldaps://alice.com"}, WithClientCertificate(testCtx, derPrivKey, cert)) + am := TestAuthMethod(t, conn, orgDBWrapper, org.PublicId, []string{"ldaps://alice.com"}, WithClientCertificate(testCtx, derPrivKey, cert)) cc := allocClientCertificate() err = rw.LookupWhere(testCtx, &cc, "ldap_method_id = ?", []any{am.GetPublicId()}) require.NoError(t, err) diff --git a/internal/auth/ldap/store/ldap.pb.go b/internal/auth/ldap/store/ldap.pb.go index 214a75958d..a3bb525d9e 100644 --- a/internal/auth/ldap/store/ldap.pb.go +++ b/internal/auth/ldap/store/ldap.pb.go @@ -152,6 +152,10 @@ type AuthMethod struct { // password is updated in the database. // @inject_tag: `gorm:"-"` BindPasswordHmac []byte `protobuf:"bytes,270,opt,name=bind_password_hmac,json=bindPasswordHmac,proto3" json:"bind_password_hmac,omitempty" gorm:"-"` + // is_primary_auth_method is a read-only output field which indicates if the + // auth method is set as the scope's primary auth method. + // @inject_tag: `gorm:"-"` + IsPrimaryAuthMethod bool `protobuf:"varint,280,opt,name=is_primary_auth_method,json=isPrimaryAuthMethod,proto3" json:"is_primary_auth_method,omitempty" gorm:"-"` } func (x *AuthMethod) Reset() { @@ -375,6 +379,13 @@ func (x *AuthMethod) GetBindPasswordHmac() []byte { return nil } +func (x *AuthMethod) GetIsPrimaryAuthMethod() bool { + if x != nil { + return x.IsPrimaryAuthMethod + } + return false +} + // Url represents LDAP URLs that specify LDAP servers to connection to. There // must be at lease on URL for each LDAP auth method. type Url struct { @@ -1101,7 +1112,7 @@ var file_controller_storage_auth_ldap_store_v1_ldap_proto_rawDesc = []byte{ 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xab, 0x09, 0x0a, 0x0a, 0x41, 0x75, 0x74, 0x68, 0x4d, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe1, 0x09, 0x0a, 0x0a, 0x41, 0x75, 0x74, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 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, @@ -1176,121 +1187,125 @@ var file_controller_storage_auth_ldap_store_v1_ldap_proto_rawDesc = []byte{ 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x2d, 0x0a, 0x12, 0x62, 0x69, 0x6e, 0x64, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x18, 0x8e, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x10, 0x62, 0x69, 0x6e, 0x64, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, - 0x48, 0x6d, 0x61, 0x63, 0x22, 0xc8, 0x01, 0x0a, 0x03, 0x55, 0x72, 0x6c, 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, 0x24, 0x0a, 0x0e, 0x6c, 0x64, 0x61, - 0x70, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0c, 0x6c, 0x64, 0x61, 0x70, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, 0x12, - 0x2f, 0x0a, 0x13, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x72, - 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, 0x63, 0x6f, - 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, - 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x28, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x55, 0x72, 0x6c, 0x22, - 0xdf, 0x01, 0x0a, 0x13, 0x55, 0x73, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x53, 0x65, 0x61, - 0x72, 0x63, 0x68, 0x43, 0x6f, 0x6e, 0x66, 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, 0x24, 0x0a, 0x0e, 0x6c, 0x64, 0x61, 0x70, 0x5f, 0x6d, 0x65, 0x74, - 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6c, 0x64, - 0x61, 0x70, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x64, 0x6e, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, - 0x72, 0x44, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x61, 0x74, 0x74, 0x72, - 0x18, 0x28, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x41, 0x74, 0x74, 0x72, - 0x12, 0x1f, 0x0a, 0x0b, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, - 0x32, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x46, 0x69, 0x6c, 0x74, 0x65, - 0x72, 0x22, 0xe6, 0x01, 0x0a, 0x14, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x43, 0x6f, 0x6e, 0x66, 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, 0x24, 0x0a, 0x0e, 0x6c, 0x64, 0x61, 0x70, 0x5f, - 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0c, 0x6c, 0x64, 0x61, 0x70, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, 0x12, 0x19, 0x0a, - 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x64, 0x6e, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x44, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x72, 0x6f, 0x75, - 0x70, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x18, 0x28, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x67, 0x72, - 0x6f, 0x75, 0x70, 0x41, 0x74, 0x74, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x67, 0x72, 0x6f, 0x75, 0x70, - 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x32, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x67, - 0x72, 0x6f, 0x75, 0x70, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0x94, 0x01, 0x0a, 0x0b, 0x43, - 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x48, 0x6d, 0x61, 0x63, 0x12, 0x34, 0x0a, 0x16, 0x69, 0x73, 0x5f, 0x70, 0x72, 0x69, 0x6d, 0x61, + 0x72, 0x79, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x98, + 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x69, 0x73, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, + 0x41, 0x75, 0x74, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x22, 0xc8, 0x01, 0x0a, 0x03, 0x55, + 0x72, 0x6c, 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, + 0x24, 0x0a, 0x0e, 0x6c, 0x64, 0x61, 0x70, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, + 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6c, 0x64, 0x61, 0x70, 0x4d, 0x65, 0x74, + 0x68, 0x6f, 0x64, 0x49, 0x64, 0x12, 0x2f, 0x0a, 0x13, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x1e, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x12, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, + 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x28, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x55, 0x72, 0x6c, 0x22, 0xdf, 0x01, 0x0a, 0x13, 0x55, 0x73, 0x65, 0x72, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x43, 0x6f, 0x6e, 0x66, 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, 0x24, 0x0a, 0x0e, 0x6c, 0x64, + 0x61, 0x70, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0c, 0x6c, 0x64, 0x61, 0x70, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, + 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x64, 0x6e, 0x18, 0x1e, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x44, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x18, 0x28, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, + 0x65, 0x72, 0x41, 0x74, 0x74, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x66, + 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x32, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x75, 0x73, 0x65, + 0x72, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0xe6, 0x01, 0x0a, 0x14, 0x47, 0x72, 0x6f, 0x75, + 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x43, 0x6f, 0x6e, 0x66, + 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, 0x24, 0x0a, + 0x0e, 0x6c, 0x64, 0x61, 0x70, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, + 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6c, 0x64, 0x61, 0x70, 0x4d, 0x65, 0x74, 0x68, 0x6f, + 0x64, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x64, 0x6e, 0x18, + 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x44, 0x6e, 0x12, 0x1d, + 0x0a, 0x0a, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x18, 0x28, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x41, 0x74, 0x74, 0x72, 0x12, 0x21, 0x0a, + 0x0c, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x32, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, + 0x22, 0x94, 0x01, 0x0a, 0x0b, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, + 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, 0x24, 0x0a, + 0x0e, 0x6c, 0x64, 0x61, 0x70, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, + 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6c, 0x64, 0x61, 0x70, 0x4d, 0x65, 0x74, 0x68, 0x6f, + 0x64, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x65, 0x72, 0x74, 0x18, 0x1e, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x63, 0x65, 0x72, 0x74, 0x22, 0xc8, 0x02, 0x0a, 0x11, 0x43, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 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, 0x24, 0x0a, 0x0e, 0x6c, 0x64, + 0x61, 0x70, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0c, 0x6c, 0x64, 0x61, 0x70, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, + 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x18, + 0x1e, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, + 0x74, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x28, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x63, 0x65, 0x72, + 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x2c, 0x0a, 0x12, 0x63, + 0x74, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, + 0x79, 0x18, 0x32, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x10, 0x63, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x14, 0x63, 0x65, 0x72, + 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x68, 0x6d, 0x61, + 0x63, 0x18, 0x3c, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x12, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x48, 0x6d, 0x61, 0x63, 0x12, 0x15, 0x0a, 0x06, 0x6b, + 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x46, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6b, 0x65, 0x79, + 0x49, 0x64, 0x22, 0x8c, 0x02, 0x0a, 0x0e, 0x42, 0x69, 0x6e, 0x64, 0x43, 0x72, 0x65, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x61, 0x6c, 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, 0x24, 0x0a, 0x0e, 0x6c, 0x64, 0x61, 0x70, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, + 0x64, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6c, 0x64, 0x61, 0x70, + 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x64, 0x6e, 0x18, 0x1e, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x64, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, + 0x77, 0x6f, 0x72, 0x64, 0x18, 0x28, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, + 0x77, 0x6f, 0x72, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x74, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, + 0x6f, 0x72, 0x64, 0x18, 0x32, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x63, 0x74, 0x50, 0x61, 0x73, + 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, + 0x64, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x18, 0x3c, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x70, 0x61, + 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x48, 0x6d, 0x61, 0x63, 0x12, 0x15, 0x0a, 0x06, 0x6b, 0x65, + 0x79, 0x5f, 0x69, 0x64, 0x18, 0x46, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6b, 0x65, 0x79, 0x49, + 0x64, 0x22, 0xd5, 0x02, 0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 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, 0x24, 0x0a, 0x0e, 0x6c, 0x64, 0x61, 0x70, 0x5f, - 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0c, 0x6c, 0x64, 0x61, 0x70, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, 0x12, 0x12, 0x0a, - 0x04, 0x63, 0x65, 0x72, 0x74, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x65, 0x72, - 0x74, 0x22, 0xc8, 0x02, 0x0a, 0x11, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x65, 0x72, 0x74, - 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 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, 0x24, 0x0a, 0x0e, 0x6c, 0x64, 0x61, 0x70, 0x5f, 0x6d, 0x65, 0x74, - 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6c, 0x64, - 0x61, 0x70, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x65, - 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x0b, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x27, 0x0a, 0x0f, - 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, - 0x28, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, - 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x2c, 0x0a, 0x12, 0x63, 0x74, 0x5f, 0x63, 0x65, 0x72, 0x74, - 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x32, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x10, 0x63, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, - 0x4b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x14, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, - 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x18, 0x3c, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x12, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x4b, 0x65, - 0x79, 0x48, 0x6d, 0x61, 0x63, 0x12, 0x15, 0x0a, 0x06, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, - 0x46, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6b, 0x65, 0x79, 0x49, 0x64, 0x22, 0x8c, 0x02, 0x0a, - 0x0e, 0x42, 0x69, 0x6e, 0x64, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 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, 0x24, 0x0a, 0x0e, - 0x6c, 0x64, 0x61, 0x70, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x14, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6c, 0x64, 0x61, 0x70, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, - 0x49, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x64, 0x6e, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, - 0x64, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x28, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x1f, - 0x0a, 0x0b, 0x63, 0x74, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x32, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x63, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, - 0x23, 0x0a, 0x0d, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x68, 0x6d, 0x61, 0x63, - 0x18, 0x3c, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, - 0x48, 0x6d, 0x61, 0x63, 0x12, 0x15, 0x0a, 0x06, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x46, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6b, 0x65, 0x79, 0x49, 0x64, 0x22, 0xd5, 0x02, 0x0a, 0x07, - 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 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, 0x24, 0x0a, 0x0e, 0x6c, 0x64, 0x61, 0x70, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, - 0x5f, 0x69, 0x64, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6c, 0x64, 0x61, 0x70, 0x4d, - 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, 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, - 0x19, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x3c, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, - 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x46, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x5f, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x50, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x4e, - 0x61, 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, 0x6c, 0x64, 0x61, 0x70, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74, - 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0c, 0x6c, 0x64, 0x61, 0x70, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, 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, 0x19, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, + 0x64, 0x18, 0x3c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x64, + 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x46, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x6f, + 0x67, 0x69, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x50, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x4e, 0x61, 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, 0x6c, 0x64, 0x61, 0x70, 0x2f, 0x73, 0x74, + 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( diff --git a/internal/auth/ldap/testing.go b/internal/auth/ldap/testing.go index a9fed09870..a4c2bcc7fe 100644 --- a/internal/auth/ldap/testing.go +++ b/internal/auth/ldap/testing.go @@ -11,6 +11,7 @@ import ( "math/big" "net" "net/url" + "sort" "testing" "time" @@ -23,7 +24,7 @@ const testInvalidPem = `-----BEGIN CERTIFICATE----- MIICUTCCAfugAwIBAgIBADANBgkqhkiG9w0BAQQFADBXMQswCQYDVQQGEwJDTjEL -----END CERTIFICATE-----` -func testAuthMethod(t testing.TB, +func TestAuthMethod(t testing.TB, conn *db.DB, databaseWrapper wrapping.Wrapper, scopeId string, @@ -96,6 +97,7 @@ func testAuthMethod(t testing.TB, if err := w.Create(testCtx, cc); err != nil { return err } + am.ClientCertificateKeyHmac = cc.CertificateKeyHmac } if opts.withBindDn != "" || opts.withBindPassword != "" { bc, err := NewBindCredential(testCtx, am.PublicId, opts.withBindDn, []byte(opts.withBindPassword)) @@ -108,6 +110,7 @@ func testAuthMethod(t testing.TB, if err := w.Create(testCtx, bc); err != nil { return err } + am.BindPasswordHmac = bc.PasswordHmac } return nil }) @@ -185,3 +188,22 @@ func TestConvertToUrls(t testing.TB, urls ...string) []*url.URL { } return convertedUrls } + +// TestSortAuthMethods will sort the provided auth methods by public id and it +// will sort each auth method's embedded value objects +func TestSortAuthMethods(t testing.TB, methods []*AuthMethod) { + // sort them by public id first... + sort.Slice(methods, func(a, b int) bool { + return methods[a].PublicId < methods[b].PublicId + }) + + // sort all the embedded value objects... + for _, am := range methods { + sort.Slice(am.Urls, func(a, b int) bool { + return am.Urls[a] < am.Urls[b] + }) + sort.Slice(am.Certificates, func(a, b int) bool { + return am.Certificates[a] < am.Certificates[b] + }) + } +} diff --git a/internal/auth/ldap/testing_test.go b/internal/auth/ldap/testing_test.go index 756f35f649..05204e6b16 100644 --- a/internal/auth/ldap/testing_test.go +++ b/internal/auth/ldap/testing_test.go @@ -32,7 +32,7 @@ func Test_testAuthMethod(t *testing.T) { derPrivKey, err := x509.MarshalPKCS8PrivateKey(privKey) require.NoError(err) - am := testAuthMethod( + am := TestAuthMethod( t, conn, databaseWrapper, org.PublicId, []string{"ldaps://d1.alice.com", "ldap://d2.alice.com"}, @@ -44,8 +44,12 @@ func Test_testAuthMethod(t *testing.T) { WithAnonGroupSearch(testCtx), WithUpnDomain(testCtx, "alice.com"), WithCertificates(testCtx, c1, c2), - WithUserDn(testCtx, "dn"), + WithUserDn(testCtx, "user-dn"), + WithUserAttr(testCtx, "user-attr"), + WithUserFilter(testCtx, "user-filter"), WithGroupDn(testCtx, "group-dn"), + WithGroupAttr(testCtx, "group-attr"), + WithGroupFilter(testCtx, "group-filter"), WithClientCertificate(testCtx, derPrivKey, c2), // not a client cert, but good enough for the test. WithBindCredential(testCtx, "bind-dn", "bind-password"), ) @@ -83,12 +87,16 @@ func Test_testAuthMethod(t *testing.T) { foundUserSearchConf := allocUserEntrySearchConf() err = rw.LookupWhere(testCtx, &foundUserSearchConf, "ldap_method_id = ?", []any{found.PublicId}) require.NoError(err) - assert.Equal("dn", foundUserSearchConf.GetUserDn()) + assert.Equal("user-dn", foundUserSearchConf.GetUserDn()) + assert.Equal("user-attr", foundUserSearchConf.GetUserAttr()) + assert.Equal("user-filter", foundUserSearchConf.GetUserFilter()) foundGroupSearchConf := allocGroupEntrySearchConf() err = rw.LookupWhere(testCtx, &foundGroupSearchConf, "ldap_method_id = ?", []any{found.PublicId}) require.NoError(err) assert.Equal("group-dn", foundGroupSearchConf.GetGroupDn()) + assert.Equal("group-attr", foundGroupSearchConf.GetGroupAttr()) + assert.Equal("group-filter", foundGroupSearchConf.GetGroupFilter()) foundClientCert := allocClientCertificate() err = rw.LookupWhere(testCtx, &foundClientCert, "ldap_method_id = ?", []any{found.PublicId}) diff --git a/internal/db/schema/migrations/oss/postgres/60/01_ldap.up.sql b/internal/db/schema/migrations/oss/postgres/60/01_ldap.up.sql index 4a0589bf3a..553e41ff77 100644 --- a/internal/db/schema/migrations/oss/postgres/60/01_ldap.up.sql +++ b/internal/db/schema/migrations/oss/postgres/60/01_ldap.up.sql @@ -184,7 +184,7 @@ create table auth_ldap_user_entry_search ( check (length(trim(user_attr)) < 1025), user_filter text constraint user_filter_too_short - check (length(trim(user_filter)) < 0) + check (length(trim(user_filter)) > 0) constraint user_filter_too_long check (length(trim(user_filter)) < 2049), constraint all_fields_are_not_null @@ -368,4 +368,68 @@ values ('auth_ldap_method', 1), -- auth_ldap_method is the root aggregate itself and all of its value objects. ('auth_ldap_account', 1); + +-- ldap_auth_method_with_value_obj is useful for reading an ldap auth method +-- with its associated value objects (urls, certs, search config, etc). The use +-- of the postgres string_agg(...) to aggregate the url and cert value objects +-- into a column works because we are only pulling in one column from the +-- associated tables and that value is part of the primary key and unique. This +-- view will make things like recursive listing of ldap auth methods fairly +-- straightforward to implement for the ldap repo. The view also includes an +-- is_primary_auth_method bool +create view ldap_auth_method_with_value_obj as +select + case when s.primary_auth_method_id is not null then + true + else false end + as is_primary_auth_method, + am.public_id, + am.scope_id, + am.name, + am.description, + am.create_time, + am.update_time, + am.version, + am.state, + am.start_tls, + am.insecure_tls, + am.discover_dn, + am.anon_group_search, + am.upn_domain, + -- the string_agg(..) column will be null if there are no associated value objects + string_agg(distinct url.url, '|') as urls, + string_agg(distinct cert.certificate, '|') as certs, + + -- the rest of the fields are zero to one relationships that are stored in + -- related tables. Since we're outer joining with these tables, we need to + -- either add them to the group by, use an aggregating func, or handle + -- multiple rows returning for each auth method. I've chosen to just use + -- string_agg(...) + string_agg(distinct uc.user_dn, '|') as user_dn, + string_agg(distinct uc.user_attr, '|') as user_attr, + string_agg(distinct uc.user_filter, '|') as user_filter, + string_agg(distinct gc.group_dn, '|') as group_dn, + string_agg(distinct gc.group_attr, '|') as group_attr, + string_agg(distinct gc.group_filter, '|') as group_filter, + string_agg(distinct cc.certificate_key, '|') as client_certificate_key, + string_agg(distinct cc.certificate_key_hmac, '|') as client_certificate_key_hmac, + string_agg(distinct cc.key_id, '|') as client_certificate_key_id, + string_agg(distinct cc.certificate, '|') as client_certificate_cert, + string_agg(distinct bc.dn, '|') as bind_dn, + string_agg(distinct bc.password, '|') as bind_password, + string_agg(distinct bc.password_hmac, '|') as bind_password_hmac, + string_agg(distinct bc.key_id, '|') as bind_password_key_id +from + auth_ldap_method am + left outer join iam_scope s on am.public_id = s.primary_auth_method_id + left outer join auth_ldap_url url on am.public_id = url.ldap_method_id + left outer join auth_ldap_certificate cert on am.public_id = cert.ldap_method_id + left outer join auth_ldap_user_entry_search uc on am.public_id = uc.ldap_method_id + left outer join auth_ldap_group_entry_search gc on am.public_id = gc.ldap_method_id + left outer join auth_ldap_client_certificate cc on am.public_id = cc.ldap_method_id + left outer join auth_ldap_bind_credential bc on am.public_id = bc.ldap_method_id +group by am.public_id, is_primary_auth_method; -- there can be only one public_id + is_primary_auth_method, so group by isn't a problem. +comment on view ldap_auth_method_with_value_obj is + 'ldap auth method with its associated value objects (urls, certs, search config, etc)'; + commit; \ No newline at end of file diff --git a/internal/proto/controller/storage/auth/ldap/store/v1/ldap.proto b/internal/proto/controller/storage/auth/ldap/store/v1/ldap.proto index 1cef141d53..b92021f124 100644 --- a/internal/proto/controller/storage/auth/ldap/store/v1/ldap.proto +++ b/internal/proto/controller/storage/auth/ldap/store/v1/ldap.proto @@ -170,6 +170,11 @@ message AuthMethod { // password is updated in the database. // @inject_tag: `gorm:"-"` bytes bind_password_hmac = 270; + + // is_primary_auth_method is a read-only output field which indicates if the + // auth method is set as the scope's primary auth method. + // @inject_tag: `gorm:"-"` + bool is_primary_auth_method = 280; } // Url represents LDAP URLs that specify LDAP servers to connection to. There