You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
boundary/internal/auth/ldap/repository_auth_method_upda...

1266 lines
38 KiB

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package ldap
import (
"context"
"crypto/ed25519"
"crypto/rand"
"crypto/x509"
"fmt"
"sort"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"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/oplog"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/testing/protocmp"
)
func TestRepository_UpdateAuthMethod(t *testing.T) {
t.Parallel()
testCtx := context.Background()
testConn, _ := db.TestSetup(t, "postgres")
testWrapper := db.TestWrapper(t)
testKms := kms.TestKms(t, testConn, testWrapper)
testRw := db.New(testConn)
testRepo, err := NewRepository(testCtx, testRw, testRw, testKms)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, testConn, testWrapper))
databaseWrapper, err := testKms.GetWrapper(context.Background(), org.PublicId, kms.KeyPurposeDatabase)
require.NoError(t, err)
testCert, testCertEncoded := TestGenerateCA(t, "localhost")
_, testPrivKey, err := ed25519.GenerateKey(rand.Reader)
require.NoError(t, err)
derPrivKey, err := x509.MarshalPKCS8PrivateKey(testPrivKey)
require.NoError(t, err)
_, testCertEncoded2 := TestGenerateCA(t, "localhost")
_, testPrivKey2, err := ed25519.GenerateKey(rand.Reader)
require.NoError(t, err)
derPrivKey2, err := x509.MarshalPKCS8PrivateKey(testPrivKey2)
require.NoError(t, err)
tests := []struct {
name string
ctx context.Context
repo *Repository
setup func() *AuthMethod
updateWith func(orig *AuthMethod) *AuthMethod
fieldMasks []string
version uint32
opt []Option
want func(orig, updateWith *AuthMethod) *AuthMethod
wantErrMatch *errors.Template
wantErrContains string
wantNoRowsUpdated bool
}{
{
name: "update-everything",
ctx: testCtx,
repo: testRepo,
setup: func() *AuthMethod {
return TestAuthMethod(t,
testConn, databaseWrapper,
org.PublicId,
[]string{"ldaps://ldap1", "ldap://ldap2"},
WithName(testCtx, "update-everything-test-name"),
WithDescription(testCtx, "update-everything-test-description"),
WithUpnDomain(testCtx, "orig.alice.com"),
WithUserDn(testCtx, "orig-user-dn"),
WithUserAttr(testCtx, "orig-user-attr"),
WithUserFilter(testCtx, "orig-user-filter"),
WithGroupDn(testCtx, "orig-group-dn"),
WithGroupAttr(testCtx, "orig-group-attr"),
WithGroupFilter(testCtx, "orig-group-filter"),
WithBindCredential(testCtx, "orig-bind-dn", "orig-bind-password"),
WithCertificates(testCtx, testCert),
WithClientCertificate(testCtx, derPrivKey, testCert), // not a client cert but good enough for this test.
WithAccountAttributeMap(testCtx, map[string]AccountToAttribute{
"displayName": ToFullNameAttribute,
"mail": ToEmailAttribute,
}),
WithDerefAliases(testCtx, DerefAlways),
WithMaximumPageSize(testCtx, 10),
)
},
updateWith: func(orig *AuthMethod) *AuthMethod {
am := AllocAuthMethod()
am.PublicId = orig.PublicId
am.Urls = []string{"ldaps://ldap1.alice.com", "ldaps://ldap2.alice.com"}
am.OperationalState = string(ActivePublicState)
am.Name = "update-everything-updated-name"
am.Description = "update-everything-updated-description"
am.StartTls = true
am.InsecureTls = true
am.DiscoverDn = true
am.AnonGroupSearch = true
am.EnableGroups = true
am.UseTokenGroups = true
am.UpnDomain = "alice.com"
am.UserDn = "user-dn"
am.UserAttr = "user-attr"
am.UserFilter = "user-filter"
am.GroupDn = "group-dn"
am.GroupAttr = "group-attr"
am.GroupFilter = "group-filter"
am.BindDn = "bind-dn"
am.BindPassword = "bind-password"
am.Certificates = []string{testCertEncoded2}
am.ClientCertificate = testCertEncoded2
am.ClientCertificateKey = derPrivKey2
am.AccountAttributeMaps = []string{
fmt.Sprintf("%s=%s", "cn", ToFullNameAttribute),
}
am.DereferenceAliases = string(NeverDerefAliases)
am.MaximumPageSize = 100
return &am
},
fieldMasks: []string{
OperationalStateField,
NameField,
DescriptionField,
UrlsField,
StartTlsField,
InsecureTlsField,
DiscoverDnField,
AnonGroupSearchField,
UpnDomainField,
UserDnField,
UserAttrField,
UserFilterField,
EnableGroupsField,
UseTokenGroupsField,
GroupDnField,
GroupAttrField,
GroupFilterField,
BindDnField,
BindPasswordField,
CertificatesField,
ClientCertificateField,
ClientCertificateKeyField,
AccountAttributeMapsField,
DerefAliasesField,
MaximumPageSizeField,
},
version: 1,
want: func(orig, updateWith *AuthMethod) *AuthMethod {
am := orig.clone()
am.OperationalState = string(ActivePublicState)
am.Name = updateWith.Name
am.Description = updateWith.Description
am.Urls = updateWith.Urls
am.StartTls = updateWith.StartTls
am.InsecureTls = updateWith.InsecureTls
am.DiscoverDn = updateWith.DiscoverDn
am.AnonGroupSearch = updateWith.AnonGroupSearch
am.UpnDomain = updateWith.UpnDomain
am.UserDn = updateWith.UserDn
am.UserAttr = updateWith.UserAttr
am.UserFilter = updateWith.UserFilter
am.EnableGroups = updateWith.EnableGroups
am.UseTokenGroups = updateWith.UseTokenGroups
am.GroupDn = updateWith.GroupDn
am.GroupAttr = updateWith.GroupAttr
am.GroupFilter = updateWith.GroupFilter
am.BindDn = updateWith.BindDn
am.BindPassword = updateWith.BindPassword
am.BindPasswordHmac = updateWith.BindPasswordHmac
am.Certificates = updateWith.Certificates
am.ClientCertificateKey = updateWith.ClientCertificateKey
am.ClientCertificate = updateWith.ClientCertificate
am.ClientCertificateKeyHmac = updateWith.ClientCertificateKeyHmac
am.AccountAttributeMaps = updateWith.AccountAttributeMaps
am.DereferenceAliases = string(NeverDerefAliases)
am.MaximumPageSize = 100
return am
},
},
{
name: "update-nothing",
ctx: testCtx,
repo: testRepo,
setup: func() *AuthMethod {
return TestAuthMethod(t,
testConn, databaseWrapper,
org.PublicId,
[]string{"ldaps://ldap1", "ldap://ldap2"},
WithName(testCtx, "update-nothing-test-name"),
WithDescription(testCtx, "update-nothing-test-description"),
WithUpnDomain(testCtx, "orig.alice.com"),
WithUserDn(testCtx, "orig-user-dn"),
WithUserAttr(testCtx, "orig-user-attr"),
WithUserFilter(testCtx, "orig-user-filter"),
WithGroupDn(testCtx, "orig-group-dn"),
WithGroupAttr(testCtx, "orig-group-attr"),
WithGroupFilter(testCtx, "orig-group-filter"),
WithBindCredential(testCtx, "orig-bind-dn", "orig-bind-password"),
WithCertificates(testCtx, testCert),
WithClientCertificate(testCtx, derPrivKey, testCert), // not a client cert but good enough for this test.
WithAccountAttributeMap(testCtx, map[string]AccountToAttribute{
"mail": ToEmailAttribute,
"cn": ToFullNameAttribute,
}),
)
},
updateWith: func(orig *AuthMethod) *AuthMethod {
return orig.clone()
},
fieldMasks: []string{
NameField,
DescriptionField,
UrlsField,
StartTlsField,
InsecureTlsField,
DiscoverDnField,
AnonGroupSearchField,
UpnDomainField,
UserDnField,
UserAttrField,
UserFilterField,
EnableGroupsField,
UseTokenGroupsField,
GroupDnField,
GroupAttrField,
GroupFilterField,
BindDnField,
BindPasswordField,
CertificatesField,
ClientCertificateField,
AccountAttributeMapsField,
},
version: 1,
want: func(orig, updateWith *AuthMethod) *AuthMethod {
return orig.clone()
},
},
{
name: "only-update-attributes",
ctx: testCtx,
repo: testRepo,
setup: func() *AuthMethod {
return TestAuthMethod(t,
testConn, databaseWrapper,
org.PublicId,
[]string{"ldaps://ldap1", "ldap://ldap2"},
WithName(testCtx, "only-update-attributes-test-name"),
WithDescription(testCtx, "only-update-attributes-test-description"),
WithUpnDomain(testCtx, "orig.alice.com"),
WithUserDn(testCtx, "orig-user-dn"),
WithUserAttr(testCtx, "orig-user-attr"),
WithUserFilter(testCtx, "orig-user-filter"),
WithGroupDn(testCtx, "orig-group-dn"),
WithGroupAttr(testCtx, "orig-group-attr"),
WithGroupFilter(testCtx, "orig-group-filter"),
WithBindCredential(testCtx, "orig-bind-dn", "orig-bind-password"),
WithCertificates(testCtx, testCert),
WithClientCertificate(testCtx, derPrivKey, testCert), // not a client cert but good enough for this test.
)
},
updateWith: func(orig *AuthMethod) *AuthMethod {
am := AllocAuthMethod()
am.PublicId = orig.PublicId
am.OperationalState = string(ActivePublicState)
am.Name = "only-update-attributes-updated-name"
am.Description = "only-update-attributes-updated-description"
am.StartTls = true
am.InsecureTls = true
am.DiscoverDn = true
am.AnonGroupSearch = true
am.EnableGroups = true
am.UseTokenGroups = true
am.UpnDomain = "alice.com"
return &am
},
fieldMasks: []string{
NameField,
DescriptionField,
StartTlsField,
InsecureTlsField,
DiscoverDnField,
AnonGroupSearchField,
EnableGroupsField,
UseTokenGroupsField,
UpnDomainField,
},
version: 1,
want: func(orig, updateWith *AuthMethod) *AuthMethod {
am := orig.clone()
am.Name = updateWith.Name
am.Description = updateWith.Description
am.StartTls = updateWith.StartTls
am.InsecureTls = updateWith.InsecureTls
am.DiscoverDn = updateWith.DiscoverDn
am.AnonGroupSearch = updateWith.AnonGroupSearch
am.UpnDomain = updateWith.UpnDomain
am.EnableGroups = updateWith.EnableGroups
am.UseTokenGroups = updateWith.UseTokenGroups
return am
},
},
{
name: "all-attributes-set-to-null-or-empty",
ctx: testCtx,
repo: testRepo,
setup: func() *AuthMethod {
return TestAuthMethod(t,
testConn, databaseWrapper,
org.PublicId,
[]string{"ldaps://ldap1", "ldap://ldap2"},
WithName(testCtx, "all-attributes-set-to-null-or-empty-test-name"),
WithDescription(testCtx, "all-attributes-set-to-null-or-empty-description"),
WithUpnDomain(testCtx, "orig.alice.com"),
WithUserDn(testCtx, "orig-user-dn"),
WithUserAttr(testCtx, "orig-user-attr"),
WithUserFilter(testCtx, "orig-user-filter"),
WithGroupDn(testCtx, "orig-group-dn"),
WithGroupAttr(testCtx, "orig-group-attr"),
WithGroupFilter(testCtx, "orig-group-filter"),
WithBindCredential(testCtx, "orig-bind-dn", "orig-bind-password"),
WithCertificates(testCtx, testCert),
WithClientCertificate(testCtx, derPrivKey, testCert), // not a client cert but good enough for this test.
WithDerefAliases(testCtx, DerefAlways),
WithMaximumPageSize(testCtx, 10),
)
},
updateWith: func(orig *AuthMethod) *AuthMethod {
am := AllocAuthMethod()
am.PublicId = orig.PublicId
return &am
},
fieldMasks: []string{
NameField,
DescriptionField,
StartTlsField,
InsecureTlsField,
DiscoverDnField,
AnonGroupSearchField,
UpnDomainField,
EnableGroupsField,
UseTokenGroupsField,
DerefAliasesField,
MaximumPageSizeField,
},
version: 1,
want: func(orig, updateWith *AuthMethod) *AuthMethod {
am := orig.clone()
am.Name = updateWith.Name
am.Description = updateWith.Description
am.StartTls = updateWith.StartTls
am.InsecureTls = updateWith.InsecureTls
am.DiscoverDn = updateWith.DiscoverDn
am.AnonGroupSearch = updateWith.AnonGroupSearch
am.UpnDomain = updateWith.UpnDomain
am.EnableGroups = updateWith.EnableGroups
am.UseTokenGroups = updateWith.UseTokenGroups
am.MaximumPageSize = updateWith.MaximumPageSize
am.DereferenceAliases = updateWith.DereferenceAliases
return am
},
},
{
name: "only-update-value-objects",
ctx: testCtx,
repo: testRepo,
setup: func() *AuthMethod {
return TestAuthMethod(t,
testConn, databaseWrapper,
org.PublicId,
[]string{"ldaps://ldap1", "ldap://ldap2"},
WithName(testCtx, "only-update-value-objects-test-name"),
WithDescription(testCtx, "orig-test-description"),
WithUpnDomain(testCtx, "orig.alice.com"),
WithUserDn(testCtx, "orig-user-dn"),
WithUserAttr(testCtx, "orig-user-attr"),
WithUserFilter(testCtx, "orig-user-filter"),
WithGroupDn(testCtx, "orig-group-dn"),
WithGroupAttr(testCtx, "orig-group-attr"),
WithGroupFilter(testCtx, "orig-group-filter"),
WithBindCredential(testCtx, "orig-bind-dn", "orig-bind-password"),
WithCertificates(testCtx, testCert),
WithClientCertificate(testCtx, derPrivKey, testCert), // not a client cert but good enough for this test.
WithAccountAttributeMap(testCtx, map[string]AccountToAttribute{
"mail": ToEmailAttribute,
"cn": ToFullNameAttribute,
}),
WithDerefAliases(testCtx, NeverDerefAliases),
)
},
updateWith: func(orig *AuthMethod) *AuthMethod {
am := AllocAuthMethod()
am.PublicId = orig.PublicId
am.Urls = []string{"ldaps://ldap3", "ldaps://ldap4"}
am.UserDn = "user-dn"
am.UserAttr = "user-attr"
am.UserFilter = "user-filter"
am.GroupDn = "group-dn"
am.GroupAttr = "group-attr"
am.GroupFilter = "group-filter"
am.BindDn = "bind-dn"
am.BindPassword = "bind-password"
am.Certificates = []string{testCertEncoded}
am.ClientCertificate = testCertEncoded
am.ClientCertificateKey = derPrivKey
am.AccountAttributeMaps = []string{"cn=fullName"}
am.DereferenceAliases = string(DerefAlways)
return &am
},
fieldMasks: []string{
UrlsField,
UserDnField,
UserAttrField,
UserFilterField,
GroupDnField,
GroupAttrField,
GroupFilterField,
BindDnField,
BindPasswordField,
CertificatesField,
ClientCertificateField,
ClientCertificateKeyField,
AccountAttributeMapsField,
DerefAliasesField,
},
version: 1,
want: func(orig, updateWith *AuthMethod) *AuthMethod {
am := orig.clone()
am.Urls = updateWith.Urls
am.UserDn = updateWith.UserDn
am.UserAttr = updateWith.UserAttr
am.UserFilter = updateWith.UserFilter
am.GroupDn = updateWith.GroupDn
am.GroupAttr = updateWith.GroupAttr
am.GroupFilter = updateWith.GroupFilter
am.BindDn = updateWith.BindDn
am.BindPassword = updateWith.BindPassword
am.BindPasswordHmac = updateWith.BindPasswordHmac
am.ClientCertificateKey = updateWith.ClientCertificateKey
am.ClientCertificate = updateWith.ClientCertificate
am.ClientCertificateKeyHmac = updateWith.ClientCertificateKeyHmac
am.AccountAttributeMaps = updateWith.AccountAttributeMaps
am.DereferenceAliases = updateWith.DereferenceAliases
return am
},
},
{
name: "remove-value-objects",
ctx: testCtx,
repo: testRepo,
setup: func() *AuthMethod {
return TestAuthMethod(t,
testConn, databaseWrapper,
org.PublicId,
[]string{"ldaps://ldap1", "ldap://ldap2"},
WithUserDn(testCtx, "orig-user-dn"),
WithUserAttr(testCtx, "orig-user-attr"),
WithUserFilter(testCtx, "orig-user-filter"),
WithGroupDn(testCtx, "orig-group-dn"),
WithGroupAttr(testCtx, "orig-group-attr"),
WithGroupFilter(testCtx, "orig-group-filter"),
WithBindCredential(testCtx, "orig-bind-dn", "orig-bind-password"),
WithCertificates(testCtx, testCert),
WithClientCertificate(testCtx, derPrivKey, testCert), // not a client cert but good enough for this test.
WithAccountAttributeMap(testCtx, map[string]AccountToAttribute{
"mail": ToEmailAttribute,
"cn": ToFullNameAttribute,
}),
WithDerefAliases(testCtx, DerefAlways),
)
},
updateWith: func(orig *AuthMethod) *AuthMethod {
am := AllocAuthMethod()
am.PublicId = orig.PublicId
return &am
},
fieldMasks: []string{
UserDnField,
UserAttrField,
UserFilterField,
GroupDnField,
GroupAttrField,
GroupFilterField,
BindDnField,
BindPasswordField,
CertificatesField,
ClientCertificateField,
ClientCertificateKeyField,
AccountAttributeMapsField,
DerefAliasesField,
},
version: 1,
want: func(orig, updateWith *AuthMethod) *AuthMethod {
am := orig.clone()
am.Certificates = updateWith.Certificates
am.UserDn = updateWith.UserDn
am.UserAttr = updateWith.UserAttr
am.UserFilter = updateWith.UserFilter
am.GroupDn = updateWith.GroupDn
am.GroupAttr = updateWith.GroupAttr
am.GroupFilter = updateWith.GroupFilter
am.BindDn = updateWith.BindDn
am.BindPassword = updateWith.BindPassword
am.BindPasswordHmac = updateWith.BindPasswordHmac
am.ClientCertificateKey = updateWith.ClientCertificateKey
am.ClientCertificate = updateWith.ClientCertificate
am.ClientCertificateKeyHmac = updateWith.ClientCertificateKeyHmac
am.AccountAttributeMaps = updateWith.AccountAttributeMaps
am.DereferenceAliases = updateWith.DereferenceAliases
return am
},
},
{
name: "update-just-binddn",
ctx: testCtx,
repo: testRepo,
setup: func() *AuthMethod {
return TestAuthMethod(t,
testConn, databaseWrapper,
org.PublicId,
[]string{"ldaps://ldap1", "ldap://ldap2"},
WithBindCredential(testCtx, "orig-bind-dn", "orig-bind-password"),
)
},
updateWith: func(orig *AuthMethod) *AuthMethod {
am := AllocAuthMethod()
am.PublicId = orig.PublicId
am.BindDn = "bind-dn"
return &am
},
fieldMasks: []string{
BindDnField,
},
version: 1,
want: func(orig, updateWith *AuthMethod) *AuthMethod {
am := orig.clone()
am.BindDn = updateWith.BindDn
return am
},
},
{
name: "update-just-bind-password",
ctx: testCtx,
repo: testRepo,
setup: func() *AuthMethod {
return TestAuthMethod(t,
testConn, databaseWrapper,
org.PublicId,
[]string{"ldaps://ldap1", "ldap://ldap2"},
WithBindCredential(testCtx, "orig-bind-dn", "orig-bind-password"),
)
},
updateWith: func(orig *AuthMethod) *AuthMethod {
am := AllocAuthMethod()
am.PublicId = orig.PublicId
am.BindPassword = "bind-password"
return &am
},
fieldMasks: []string{
BindPasswordField,
},
version: 1,
want: func(orig, updateWith *AuthMethod) *AuthMethod {
am := orig.clone()
am.BindPassword = updateWith.BindPassword
return am
},
},
{
name: "missing-auth-method",
ctx: testCtx,
repo: testRepo,
setup: func() *AuthMethod {
return nil
},
updateWith: func(orig *AuthMethod) *AuthMethod {
return orig
},
wantErrMatch: errors.T(errors.InvalidParameter),
wantErrContains: "missing auth method",
},
{
name: "missing-auth-method-store",
ctx: testCtx,
repo: testRepo,
setup: func() *AuthMethod {
return &AuthMethod{}
},
updateWith: func(orig *AuthMethod) *AuthMethod {
return orig
},
wantErrMatch: errors.T(errors.InvalidParameter),
wantErrContains: "missing auth method store",
},
{
name: "missing-public-id",
ctx: testCtx,
repo: testRepo,
setup: func() *AuthMethod {
am := AllocAuthMethod()
return &am
},
updateWith: func(orig *AuthMethod) *AuthMethod {
return orig
},
wantErrMatch: errors.T(errors.InvalidParameter),
wantErrContains: "missing public id",
},
{
name: "invalid-field-mask",
ctx: testCtx,
repo: testRepo,
fieldMasks: []string{"CreateTime"},
setup: func() *AuthMethod {
am := AllocAuthMethod()
am.PublicId = "test-id"
return &am
},
updateWith: func(orig *AuthMethod) *AuthMethod {
return orig
},
wantErrMatch: errors.T(errors.InvalidParameter),
wantErrContains: "invalid field mask: \"CreateTime\"",
},
{
name: "no-field-mask",
ctx: testCtx,
repo: testRepo,
setup: func() *AuthMethod {
am := AllocAuthMethod()
am.PublicId = "test-id"
return &am
},
updateWith: func(orig *AuthMethod) *AuthMethod {
return orig
},
wantErrMatch: errors.T(errors.EmptyFieldMask),
wantErrContains: "empty field mask",
},
{
name: "missing-urls",
ctx: testCtx,
repo: testRepo,
fieldMasks: []string{"Urls"},
setup: func() *AuthMethod {
am := AllocAuthMethod()
am.PublicId = "test-id"
return &am
},
updateWith: func(orig *AuthMethod) *AuthMethod {
return orig
},
wantErrMatch: errors.T(errors.InvalidParameter),
wantErrContains: "missing urls (you cannot delete all of them; there must be at least one)",
},
{
name: "lookup-err",
ctx: testCtx,
repo: func() *Repository {
conn, mock := db.TestSetupWithMock(t)
mock.ExpectQuery(`SELECT`).WillReturnError(fmt.Errorf("lookup-err"))
mockRw := db.New(conn)
testRepo, err := NewRepository(testCtx, mockRw, mockRw, testKms)
require.NoError(t, err)
return testRepo
}(),
fieldMasks: []string{"UserDn"},
setup: func() *AuthMethod {
am := AllocAuthMethod()
am.PublicId = "test-id"
return &am
},
updateWith: func(orig *AuthMethod) *AuthMethod {
return orig
},
wantErrMatch: errors.T(errors.Unknown),
wantErrContains: "lookup-err",
},
{
name: "not-found",
ctx: testCtx,
repo: testRepo,
fieldMasks: []string{"UserDn"},
setup: func() *AuthMethod {
am := AllocAuthMethod()
am.PublicId = "test-id"
return &am
},
updateWith: func(orig *AuthMethod) *AuthMethod {
return orig
},
wantErrMatch: errors.T(errors.RecordNotFound),
wantErrContains: "auth method \"test-id\": search issue",
},
{
name: "version-mismatch",
ctx: testCtx,
repo: testRepo,
fieldMasks: []string{"UserDn"},
setup: func() *AuthMethod {
am := TestAuthMethod(t, testConn, databaseWrapper, org.PublicId, []string{"ldaps://ldap1"})
am.Version += 1
return am
},
updateWith: func(orig *AuthMethod) *AuthMethod {
return orig
},
wantErrMatch: errors.T(errors.Integrity),
wantErrContains: "update version 0 doesn't match db version 1",
},
{
name: "getWrapper-err",
ctx: testCtx,
repo: func() *Repository {
testKms := &kms.MockGetWrapperer{
GetErr: fmt.Errorf("getWrapper-err"),
}
testRepo, err := NewRepository(testCtx, testRw, testRw, testKms)
require.NoError(t, err)
return testRepo
}(),
version: 1,
fieldMasks: []string{"UserDn"},
setup: func() *AuthMethod {
return TestAuthMethod(t, testConn, databaseWrapper, org.PublicId, []string{"ldaps://ldap1"})
},
updateWith: func(orig *AuthMethod) *AuthMethod {
return orig
},
wantErrMatch: errors.T(errors.Unknown),
wantErrContains: "getWrapper-err",
},
{
name: "urls-conversion-err",
ctx: testCtx,
repo: testRepo,
version: 1,
fieldMasks: []string{"Urls"},
setup: func() *AuthMethod {
am := TestAuthMethod(t, testConn, databaseWrapper, org.PublicId, []string{"ldaps://ldap1"})
am.Urls = []string{"https://not-valid.com"}
return am
},
updateWith: func(orig *AuthMethod) *AuthMethod {
return orig
},
wantErrMatch: errors.T(errors.Unknown),
wantErrContains: "valueObjectChanges: ldap.NewUrl: scheme \"https\" is not ldap or ldaps",
},
{
name: "certs-conversion-err",
ctx: testCtx,
repo: testRepo,
version: 1,
fieldMasks: []string{"Certificates"},
setup: func() *AuthMethod {
am := TestAuthMethod(t, testConn, databaseWrapper, org.PublicId, []string{"ldaps://ldap1"})
am.Certificates = []string{TestInvalidPem}
return am
},
updateWith: func(orig *AuthMethod) *AuthMethod {
return orig
},
wantErrMatch: errors.T(errors.Unknown),
wantErrContains: "valueObjectChanges: ldap.NewCertificate: failed to parse certificate",
},
{
name: "account-maps-conversion-err",
ctx: testCtx,
repo: testRepo,
version: 1,
fieldMasks: []string{AccountAttributeMapsField},
setup: func() *AuthMethod {
am := TestAuthMethod(t, testConn, databaseWrapper, org.PublicId, []string{"ldaps://ldap1"})
am.AccountAttributeMaps = []string{"invalid-map"}
return am
},
updateWith: func(orig *AuthMethod) *AuthMethod {
return orig
},
wantErrMatch: errors.T(errors.Unknown),
wantErrContains: "ldap.ParseAccountAttributeMaps: error parsing attribute",
},
{
name: "use-token-groups-update",
ctx: testCtx,
repo: testRepo,
version: 1,
fieldMasks: []string{"UseTokenGroups"},
setup: func() *AuthMethod {
am := TestAuthMethod(t, testConn, databaseWrapper, org.PublicId, []string{"ldaps://ldap1"})
return am
},
updateWith: func(orig *AuthMethod) *AuthMethod {
am := orig.clone()
am.UseTokenGroups = true
return am
},
want: func(orig, updateWith *AuthMethod) *AuthMethod {
am := orig.clone()
am.UseTokenGroups = true
return am
},
},
{
name: "use-token-groups-update-false",
ctx: testCtx,
repo: testRepo,
version: 1,
fieldMasks: []string{"UseTokenGroups"},
setup: func() *AuthMethod {
am := TestAuthMethod(t, testConn, databaseWrapper, org.PublicId, []string{"ldaps://ldap1"}, WithUseTokenGroups(testCtx))
return am
},
updateWith: func(orig *AuthMethod) *AuthMethod {
am := orig.clone()
am.UseTokenGroups = false
return am
},
want: func(orig, updateWith *AuthMethod) *AuthMethod {
am := orig.clone()
am.UseTokenGroups = false
return am
},
},
{
name: "start-tls-false",
ctx: testCtx,
repo: testRepo,
version: 1,
fieldMasks: []string{"StartTls"},
setup: func() *AuthMethod {
am := TestAuthMethod(t, testConn, databaseWrapper, org.PublicId, []string{"ldaps://ldap1"}, WithStartTLS(testCtx))
return am
},
updateWith: func(orig *AuthMethod) *AuthMethod {
am := orig.clone()
am.StartTls = false
return am
},
want: func(orig, updateWith *AuthMethod) *AuthMethod {
am := orig.clone()
am.StartTls = false
return am
},
},
{
name: "user-dn-update",
ctx: testCtx,
repo: testRepo,
version: 1,
fieldMasks: []string{"UserDn"},
setup: func() *AuthMethod {
am := TestAuthMethod(t, testConn, databaseWrapper, org.PublicId, []string{"ldaps://ldap1"}, WithUserDn(testCtx, "orig-user-dn"))
return am
},
updateWith: func(orig *AuthMethod) *AuthMethod {
am := orig.clone()
am.UserDn = "updated-user-dn"
return am
},
want: func(orig, updateWith *AuthMethod) *AuthMethod {
am := orig.clone()
am.UserDn = "updated-user-dn"
return am
},
},
{
name: "user-attr-update",
ctx: testCtx,
repo: testRepo,
version: 1,
fieldMasks: []string{"UserAttr"},
setup: func() *AuthMethod {
am := TestAuthMethod(t, testConn, databaseWrapper, org.PublicId, []string{"ldaps://ldap1"}, WithUserDn(testCtx, "orig-user-dn"), WithUserAttr(testCtx, "orig-user-attr"))
return am
},
updateWith: func(orig *AuthMethod) *AuthMethod {
am := orig.clone()
am.UserAttr = "updated-user-attr"
return am
},
want: func(orig, updateWith *AuthMethod) *AuthMethod {
am := orig.clone()
am.UserAttr = "updated-user-attr"
am.UserDn = "orig-user-dn"
return am
},
},
{
name: "user-filter-update",
ctx: testCtx,
repo: testRepo,
version: 1,
fieldMasks: []string{"UserFilter"},
setup: func() *AuthMethod {
am := TestAuthMethod(t, testConn, databaseWrapper, org.PublicId, []string{"ldaps://ldap1"}, WithUserFilter(testCtx, "orig-user-filter"))
return am
},
updateWith: func(orig *AuthMethod) *AuthMethod {
am := orig.clone()
am.UserFilter = "updated-user-filter"
return am
},
want: func(orig, updateWith *AuthMethod) *AuthMethod {
am := orig.clone()
am.UserFilter = "updated-user-filter"
return am
},
},
{
name: "enable-groups-err",
ctx: testCtx,
repo: testRepo,
version: 1,
fieldMasks: []string{"EnableGroups"},
setup: func() *AuthMethod {
am := TestAuthMethod(t, testConn, databaseWrapper, org.PublicId, []string{"ldaps://ldap1"})
return am
},
updateWith: func(orig *AuthMethod) *AuthMethod {
am := orig.clone()
am.EnableGroups = true
return am
},
wantErrMatch: errors.T(errors.Integrity),
wantErrContains: "must have a configured group_dn when enable_groups = true and use_token_groups = false",
},
{
name: "group-dn-update",
ctx: testCtx,
repo: testRepo,
version: 1,
fieldMasks: []string{"GroupDn"},
setup: func() *AuthMethod {
am := TestAuthMethod(t, testConn, databaseWrapper, org.PublicId, []string{"ldaps://ldap1"}, WithUserDn(testCtx, "orig-group-dn"))
return am
},
updateWith: func(orig *AuthMethod) *AuthMethod {
am := orig.clone()
am.GroupDn = "updated-group-dn"
return am
},
want: func(orig, updateWith *AuthMethod) *AuthMethod {
am := orig.clone()
am.GroupDn = "updated-group-dn"
return am
},
},
{
name: "group-attr-update",
ctx: testCtx,
repo: testRepo,
version: 1,
fieldMasks: []string{"GroupAttr"},
setup: func() *AuthMethod {
am := TestAuthMethod(t, testConn, databaseWrapper, org.PublicId, []string{"ldaps://ldap1"}, WithGroupDn(testCtx, "orig-group-dn"), WithGroupAttr(testCtx, "orig-group-attr"))
return am
},
updateWith: func(orig *AuthMethod) *AuthMethod {
am := orig.clone()
am.GroupAttr = "updated-group-attr"
return am
},
want: func(orig, updateWith *AuthMethod) *AuthMethod {
am := orig.clone()
am.GroupAttr = "updated-group-attr"
return am
},
},
{
name: "group-filter-update",
ctx: testCtx,
repo: testRepo,
version: 1,
fieldMasks: []string{"GroupAttr"},
setup: func() *AuthMethod {
am := TestAuthMethod(t, testConn, databaseWrapper, org.PublicId, []string{"ldaps://ldap1"}, WithGroupDn(testCtx, "orig-group-dn"), WithGroupFilter(testCtx, "orig-group-filter"))
return am
},
updateWith: func(orig *AuthMethod) *AuthMethod {
am := orig.clone()
am.GroupAttr = "updated-group-filter"
return am
},
want: func(orig, updateWith *AuthMethod) *AuthMethod {
am := orig.clone()
am.GroupAttr = "updated-group-filter"
return am
},
},
{
name: "user-entry-search-conversion-no-update",
ctx: testCtx,
repo: testRepo,
version: 1,
fieldMasks: []string{"UserDn"},
setup: func() *AuthMethod {
am := TestAuthMethod(t, testConn, databaseWrapper, org.PublicId, []string{"ldaps://ldap1"})
return am
},
updateWith: func(orig *AuthMethod) *AuthMethod {
return orig
},
want: func(orig, updateWith *AuthMethod) *AuthMethod {
return orig.clone()
},
wantNoRowsUpdated: true,
},
{
name: "group-entry-search-conversion-no-update",
ctx: testCtx,
repo: testRepo,
version: 1,
fieldMasks: []string{"GroupDn"},
setup: func() *AuthMethod {
am := TestAuthMethod(t, testConn, databaseWrapper, org.PublicId, []string{"ldaps://ldap1"})
return am
},
updateWith: func(orig *AuthMethod) *AuthMethod {
return orig
},
want: func(orig, updateWith *AuthMethod) *AuthMethod {
return orig.clone()
},
wantNoRowsUpdated: true,
},
{
name: "client-search-conversion-no-update",
ctx: testCtx,
repo: testRepo,
version: 1,
fieldMasks: []string{"ClientCertificate"},
setup: func() *AuthMethod {
am := TestAuthMethod(t, testConn, databaseWrapper, org.PublicId, []string{"ldaps://ldap1"})
return am
},
updateWith: func(orig *AuthMethod) *AuthMethod {
return orig
},
want: func(orig, updateWith *AuthMethod) *AuthMethod {
return orig.clone()
},
wantNoRowsUpdated: true,
},
{
name: "bind-credential-conversion-no-update",
ctx: testCtx,
repo: testRepo,
version: 1,
fieldMasks: []string{"BindDn"},
setup: func() *AuthMethod {
am := TestAuthMethod(t, testConn, databaseWrapper, org.PublicId, []string{"ldaps://ldap1"})
return am
},
updateWith: func(orig *AuthMethod) *AuthMethod {
return orig
},
want: func(orig, updateWith *AuthMethod) *AuthMethod {
return orig.clone()
},
wantNoRowsUpdated: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
orig := tc.setup()
updateWith := tc.updateWith(orig)
updated, rowsUpdated, err := tc.repo.UpdateAuthMethod(tc.ctx, updateWith, tc.version, tc.fieldMasks, tc.opt...)
if tc.wantErrMatch != nil {
require.Error(err)
assert.Empty(updated)
assert.Zero(rowsUpdated)
assert.Truef(errors.Match(tc.wantErrMatch, err), "want err code: %q got: %q", tc.wantErrMatch.Code, err)
if tc.wantErrContains != "" {
assert.Contains(err.Error(), tc.wantErrContains)
}
return
}
require.NoError(err)
require.NotNil(updated)
require.NotNil(tc.want)
want := tc.want(orig, updateWith)
want.CreateTime = updated.CreateTime
want.UpdateTime = updated.UpdateTime
want.Version = updated.Version
want.BindPasswordHmac = updated.BindPasswordHmac
want.ClientCertificateKeyHmac = updated.ClientCertificateKeyHmac
TestSortAuthMethods(t, []*AuthMethod{want, updated})
assert.Empty(cmp.Diff(updated.AuthMethod, want.AuthMethod, protocmp.Transform()))
if !tc.wantNoRowsUpdated {
assert.Equal(1, rowsUpdated)
err = db.TestVerifyOplog(t, testRw, updateWith.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second))
require.NoErrorf(err, "unexpected error verifying oplog entry: %s", err)
}
found, err := tc.repo.LookupAuthMethod(tc.ctx, want.PublicId)
require.NoError(err)
TestSortAuthMethods(t, []*AuthMethod{found})
assert.Empty(cmp.Diff(found.AuthMethod, want.AuthMethod, protocmp.Transform()))
})
}
}
func Test_validateFieldMask(t *testing.T) {
t.Parallel()
tests := []struct {
name string
fieldMask []string
wantErr bool
}{
{
name: "all-valid-fields",
fieldMask: []string{
NameField,
DescriptionField,
StartTlsField,
InsecureTlsField,
DiscoverDnField,
AnonGroupSearchField,
UpnDomainField,
UrlsField,
UserDnField,
UserAttrField,
UserFilterField,
GroupDnField,
GroupAttrField,
GroupFilterField,
CertificatesField,
ClientCertificateField,
ClientCertificateKeyField,
BindDnField,
BindPasswordField,
AccountAttributeMapsField,
},
},
{
name: "invalid",
fieldMask: []string{"Invalid", NameField},
wantErr: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
require := require.New(t)
err := validateFieldMask(context.TODO(), tc.fieldMask)
if tc.wantErr {
require.Error(err)
return
}
require.NoError(err)
})
}
}
// Test_valueObjectChanges is just being used to test failure conditions primarily
func Test_valueObjectChanges(t *testing.T) {
t.Parallel()
testCtx := context.Background()
_, pem1 := TestGenerateCA(t, "localhost")
_, pem2 := TestGenerateCA(t, "127.0.0.1")
_, pem3 := TestGenerateCA(t, "www.example.com")
tests := []struct {
name string
ctx context.Context
id string
voName voName
new []string
old []string
dbMask []string
nullFields []string
wantAdd []any
wantDel []any
wantErrMatch *errors.Template
wantErrContains string
}{
{
name: "missing-public-id",
ctx: testCtx,
voName: CertificateVO,
new: nil,
old: []string{pem1, pem2, pem3},
nullFields: []string{string(CertificateVO)},
wantErrMatch: errors.T(errors.InvalidParameter),
wantErrContains: "missing public id",
},
{
name: "invalid-vo-name",
ctx: testCtx,
voName: voName("invalid-name"),
id: "am-public-id",
new: nil,
old: []string{pem1, pem2},
nullFields: []string{string(CertificateVO)},
wantErrMatch: errors.T(errors.InvalidParameter),
wantErrContains: "invalid value object name",
},
{
name: "dup-new",
ctx: testCtx,
id: "am-public-id",
voName: CertificateVO,
new: []string{pem1, pem1},
old: []string{pem1},
dbMask: []string{string(CertificateVO)},
wantErrMatch: errors.T(errors.InvalidParameter),
wantErrContains: "duplicate new Certificates",
},
{
name: "dup-old",
ctx: testCtx,
id: "am-public-id",
voName: CertificateVO,
new: []string{pem1},
old: []string{pem2, pem2},
dbMask: []string{string(CertificateVO)},
wantErrMatch: errors.T(errors.InvalidParameter),
wantErrContains: "duplicate old Certificates",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
gotAdd, gotDel, err := valueObjectChanges(tc.ctx, tc.id, tc.voName, tc.new, tc.old, tc.dbMask, tc.nullFields)
if tc.wantErrMatch != nil {
require.Error(err)
assert.Truef(errors.Match(tc.wantErrMatch, err), "want err code: %q got: %q", tc.wantErrMatch.Code, err)
if tc.wantErrContains != "" {
assert.Contains(err.Error(), tc.wantErrContains)
}
return
}
require.NoError(err)
assert.Equal(tc.wantAdd, gotAdd)
switch tc.voName {
case CertificateVO:
sort.Slice(gotDel, func(a, b int) bool {
aa := gotDel[a]
bb := gotDel[b]
return aa.(*Certificate).Cert < bb.(*Certificate).Cert
})
case UrlVO:
sort.Slice(gotDel, func(a, b int) bool {
aa := gotDel[a]
bb := gotDel[b]
return aa.(*Url).ServerUrl < bb.(*Url).ServerUrl
})
case AccountAttributeMapsVO:
sort.Slice(gotDel, func(a, b int) bool {
aa := gotDel[a]
bb := gotDel[b]
return aa.(*AccountAttributeMap).ToAttribute < bb.(*AccountAttributeMap).ToAttribute
})
}
assert.Equalf(tc.wantDel, gotDel, "wantDel: %s\ngotDel: %s\n", tc.wantDel, gotDel)
})
}
}