Move ad specific stuff to controller
also make extra refresh attributes a separate field rather than part of Extra Signed-off-by: Margo Crawford <margaretc@vmware.com>
This commit is contained in:
parent
acaad05341
commit
59d999956c
@ -37,4 +37,5 @@ type UserAuthenticator interface {
|
|||||||
type Response struct {
|
type Response struct {
|
||||||
User user.Info
|
User user.Info
|
||||||
DN string
|
DN string
|
||||||
|
ExtraRefreshAttributes map[string]string
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,12 @@ package activedirectoryupstreamwatcher
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/go-ldap/ldap/v3"
|
"github.com/go-ldap/ldap/v3"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/equality"
|
"k8s.io/apimachinery/pkg/api/equality"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
@ -52,6 +56,21 @@ const (
|
|||||||
// - has a member that matches the DN of the user we successfully logged in as.
|
// - has a member that matches the DN of the user we successfully logged in as.
|
||||||
// - perform nested group search by default.
|
// - perform nested group search by default.
|
||||||
defaultActiveDirectoryGroupSearchFilter = "(&(objectClass=group)(member:1.2.840.113556.1.4.1941:={}))"
|
defaultActiveDirectoryGroupSearchFilter = "(&(objectClass=group)(member:1.2.840.113556.1.4.1941:={}))"
|
||||||
|
|
||||||
|
sAMAccountNameAttribute = "sAMAccountName"
|
||||||
|
// PwdLastSetAttribute is the date and time that the password for this account was last changed.
|
||||||
|
// https://docs.microsoft.com/en-us/windows/win32/adschema/a-pwdlastset
|
||||||
|
PwdLastSetAttribute = "pwdLastSet"
|
||||||
|
// UserAccountControlAttribute represents a bitmap of user properties.
|
||||||
|
// https://docs.microsoft.com/en-us/troubleshoot/windows-server/identity/useraccountcontrol-manipulate-account-properties
|
||||||
|
UserAccountControlAttribute = "userAccountControl"
|
||||||
|
// UserAccountControlComputedAttribute represents a bitmap of user properties.
|
||||||
|
// https://docs.microsoft.com/en-us/windows/win32/adschema/a-msds-user-account-control-computed
|
||||||
|
UserAccountControlComputedAttribute = "msDS-User-Account-Control-Computed"
|
||||||
|
// 0x0002 ACCOUNTDISABLE in userAccountControl bitmap.
|
||||||
|
accountDisabledBitmapValue = 2
|
||||||
|
// 0x0010 UF_LOCKOUT in msDS-User-Account-Control-Computed bitmap.
|
||||||
|
accountLockedBitmapValue = 16
|
||||||
)
|
)
|
||||||
|
|
||||||
type activeDirectoryUpstreamGenericLDAPImpl struct {
|
type activeDirectoryUpstreamGenericLDAPImpl struct {
|
||||||
@ -316,16 +335,16 @@ func (c *activeDirectoryWatcherController) validateUpstream(ctx context.Context,
|
|||||||
GroupNameAttribute: adUpstreamImpl.Spec().GroupSearch().GroupNameAttribute(),
|
GroupNameAttribute: adUpstreamImpl.Spec().GroupSearch().GroupNameAttribute(),
|
||||||
},
|
},
|
||||||
Dialer: c.ldapDialer,
|
Dialer: c.ldapDialer,
|
||||||
UIDAttributeParsingOverrides: map[string]func(*ldap.Entry) (string, error){"objectGUID": upstreamldap.MicrosoftUUIDFromBinary("objectGUID")},
|
UIDAttributeParsingOverrides: map[string]func(*ldap.Entry) (string, error){"objectGUID": MicrosoftUUIDFromBinary("objectGUID")},
|
||||||
RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{
|
RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{
|
||||||
upstreamldap.PwdLastSetAttribute: upstreamldap.AttributeUnchangedSinceLogin(upstreamldap.PwdLastSetAttribute),
|
PwdLastSetAttribute: upstreamldap.AttributeUnchangedSinceLogin(PwdLastSetAttribute),
|
||||||
upstreamldap.UserAccountControlAttribute: upstreamldap.ValidUserAccountControl,
|
UserAccountControlAttribute: ValidUserAccountControl,
|
||||||
upstreamldap.UserAccountControlComputedAttribute: upstreamldap.ValidComputedUserAccountControl,
|
UserAccountControlComputedAttribute: ValidComputedUserAccountControl,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if spec.GroupSearch.Attributes.GroupName == "" {
|
if spec.GroupSearch.Attributes.GroupName == "" {
|
||||||
config.GroupAttributeParsingOverrides = map[string]func(*ldap.Entry) (string, error){defaultActiveDirectoryGroupNameAttributeName: upstreamldap.GroupSAMAccountNameWithDomainSuffix}
|
config.GroupAttributeParsingOverrides = map[string]func(*ldap.Entry) (string, error){defaultActiveDirectoryGroupNameAttributeName: GroupSAMAccountNameWithDomainSuffix}
|
||||||
}
|
}
|
||||||
|
|
||||||
conditions := upstreamwatchers.ValidateGenericLDAP(ctx, adUpstreamImpl, c.secretInformer, c.validatedSecretVersionsCache, config)
|
conditions := upstreamwatchers.ValidateGenericLDAP(ctx, adUpstreamImpl, c.secretInformer, c.validatedSecretVersionsCache, config)
|
||||||
@ -358,3 +377,84 @@ func (c *activeDirectoryWatcherController) updateStatus(ctx context.Context, ups
|
|||||||
log.Error(err, "failed to update status")
|
log.Error(err, "failed to update status")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func MicrosoftUUIDFromBinary(attributeName string) func(entry *ldap.Entry) (string, error) {
|
||||||
|
// validation has already been done so we can just get the attribute...
|
||||||
|
return func(entry *ldap.Entry) (string, error) {
|
||||||
|
binaryUUID := entry.GetRawAttributeValue(attributeName)
|
||||||
|
return microsoftUUIDFromBinary(binaryUUID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func microsoftUUIDFromBinary(binaryUUID []byte) (string, error) {
|
||||||
|
uuidVal, err := uuid.FromBytes(binaryUUID) // start out with the RFC4122 version
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// then swap it because AD stores the first 3 fields little-endian rather than the expected
|
||||||
|
// big-endian.
|
||||||
|
uuidVal[0], uuidVal[1], uuidVal[2], uuidVal[3] = uuidVal[3], uuidVal[2], uuidVal[1], uuidVal[0]
|
||||||
|
uuidVal[4], uuidVal[5] = uuidVal[5], uuidVal[4]
|
||||||
|
uuidVal[6], uuidVal[7] = uuidVal[7], uuidVal[6]
|
||||||
|
return uuidVal.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GroupSAMAccountNameWithDomainSuffix(entry *ldap.Entry) (string, error) {
|
||||||
|
sAMAccountNameAttributeValues := entry.GetAttributeValues(sAMAccountNameAttribute)
|
||||||
|
|
||||||
|
if len(sAMAccountNameAttributeValues) != 1 {
|
||||||
|
return "", fmt.Errorf(`found %d values for attribute "%s", but expected 1 result`,
|
||||||
|
len(sAMAccountNameAttributeValues), sAMAccountNameAttribute,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
sAMAccountName := sAMAccountNameAttributeValues[0]
|
||||||
|
if len(sAMAccountName) == 0 {
|
||||||
|
return "", fmt.Errorf(`found empty value for attribute "%s", but expected value to be non-empty`,
|
||||||
|
sAMAccountNameAttribute,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
distinguishedName := entry.DN
|
||||||
|
domain, err := getDomainFromDistinguishedName(distinguishedName)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return sAMAccountName + "@" + domain, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var domainComponentsRegexp = regexp.MustCompile(",DC=|,dc=")
|
||||||
|
|
||||||
|
func getDomainFromDistinguishedName(distinguishedName string) (string, error) {
|
||||||
|
domainComponents := domainComponentsRegexp.Split(distinguishedName, -1)
|
||||||
|
if len(domainComponents) == 1 {
|
||||||
|
return "", fmt.Errorf("did not find domain components in group dn: %s", distinguishedName)
|
||||||
|
}
|
||||||
|
return strings.Join(domainComponents[1:], "."), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidUserAccountControl(entry *ldap.Entry, _ provider.StoredRefreshAttributes) error {
|
||||||
|
userAccountControl, err := strconv.Atoi(entry.GetAttributeValue(UserAccountControlAttribute))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
deactivated := userAccountControl & accountDisabledBitmapValue // bitwise and.
|
||||||
|
if deactivated != 0 {
|
||||||
|
return fmt.Errorf("user has been deactivated")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidComputedUserAccountControl(entry *ldap.Entry, _ provider.StoredRefreshAttributes) error {
|
||||||
|
userAccountControl, err := strconv.Atoi(entry.GetAttributeValue(UserAccountControlComputedAttribute))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
locked := userAccountControl & accountLockedBitmapValue // bitwise and
|
||||||
|
if locked != 0 {
|
||||||
|
return fmt.Errorf("user has been locked")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -220,11 +220,11 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
Filter: testGroupSearchFilter,
|
Filter: testGroupSearchFilter,
|
||||||
GroupNameAttribute: testGroupNameAttrName,
|
GroupNameAttribute: testGroupNameAttrName,
|
||||||
},
|
},
|
||||||
UIDAttributeParsingOverrides: map[string]func(*ldap.Entry) (string, error){"objectGUID": upstreamldap.MicrosoftUUIDFromBinary("objectGUID")},
|
UIDAttributeParsingOverrides: map[string]func(*ldap.Entry) (string, error){"objectGUID": MicrosoftUUIDFromBinary("objectGUID")},
|
||||||
RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{
|
RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{
|
||||||
"pwdLastSet": upstreamldap.AttributeUnchangedSinceLogin("pwdLastSet"),
|
"pwdLastSet": upstreamldap.AttributeUnchangedSinceLogin("pwdLastSet"),
|
||||||
"userAccountControl": upstreamldap.ValidUserAccountControl,
|
"userAccountControl": ValidUserAccountControl,
|
||||||
"msDS-User-Account-Control-Computed": upstreamldap.ValidComputedUserAccountControl,
|
"msDS-User-Account-Control-Computed": ValidComputedUserAccountControl,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -541,11 +541,11 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
Filter: testGroupSearchFilter,
|
Filter: testGroupSearchFilter,
|
||||||
GroupNameAttribute: testGroupNameAttrName,
|
GroupNameAttribute: testGroupNameAttrName,
|
||||||
},
|
},
|
||||||
UIDAttributeParsingOverrides: map[string]func(*ldap.Entry) (string, error){"objectGUID": upstreamldap.MicrosoftUUIDFromBinary("objectGUID")},
|
UIDAttributeParsingOverrides: map[string]func(*ldap.Entry) (string, error){"objectGUID": MicrosoftUUIDFromBinary("objectGUID")},
|
||||||
RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{
|
RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{
|
||||||
"pwdLastSet": upstreamldap.AttributeUnchangedSinceLogin("pwdLastSet"),
|
"pwdLastSet": upstreamldap.AttributeUnchangedSinceLogin("pwdLastSet"),
|
||||||
"userAccountControl": upstreamldap.ValidUserAccountControl,
|
"userAccountControl": ValidUserAccountControl,
|
||||||
"msDS-User-Account-Control-Computed": upstreamldap.ValidComputedUserAccountControl,
|
"msDS-User-Account-Control-Computed": ValidComputedUserAccountControl,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -602,11 +602,11 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
Filter: testGroupSearchFilter,
|
Filter: testGroupSearchFilter,
|
||||||
GroupNameAttribute: "sAMAccountName",
|
GroupNameAttribute: "sAMAccountName",
|
||||||
},
|
},
|
||||||
UIDAttributeParsingOverrides: map[string]func(*ldap.Entry) (string, error){"objectGUID": upstreamldap.MicrosoftUUIDFromBinary("objectGUID")},
|
UIDAttributeParsingOverrides: map[string]func(*ldap.Entry) (string, error){"objectGUID": MicrosoftUUIDFromBinary("objectGUID")},
|
||||||
RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{
|
RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{
|
||||||
"pwdLastSet": upstreamldap.AttributeUnchangedSinceLogin("pwdLastSet"),
|
"pwdLastSet": upstreamldap.AttributeUnchangedSinceLogin("pwdLastSet"),
|
||||||
"userAccountControl": upstreamldap.ValidUserAccountControl,
|
"userAccountControl": ValidUserAccountControl,
|
||||||
"msDS-User-Account-Control-Computed": upstreamldap.ValidComputedUserAccountControl,
|
"msDS-User-Account-Control-Computed": ValidComputedUserAccountControl,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -666,11 +666,11 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
Filter: testGroupSearchFilter,
|
Filter: testGroupSearchFilter,
|
||||||
GroupNameAttribute: testGroupNameAttrName,
|
GroupNameAttribute: testGroupNameAttrName,
|
||||||
},
|
},
|
||||||
UIDAttributeParsingOverrides: map[string]func(*ldap.Entry) (string, error){"objectGUID": upstreamldap.MicrosoftUUIDFromBinary("objectGUID")},
|
UIDAttributeParsingOverrides: map[string]func(*ldap.Entry) (string, error){"objectGUID": MicrosoftUUIDFromBinary("objectGUID")},
|
||||||
RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{
|
RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{
|
||||||
"pwdLastSet": upstreamldap.AttributeUnchangedSinceLogin("pwdLastSet"),
|
"pwdLastSet": upstreamldap.AttributeUnchangedSinceLogin("pwdLastSet"),
|
||||||
"userAccountControl": upstreamldap.ValidUserAccountControl,
|
"userAccountControl": ValidUserAccountControl,
|
||||||
"msDS-User-Account-Control-Computed": upstreamldap.ValidComputedUserAccountControl,
|
"msDS-User-Account-Control-Computed": ValidComputedUserAccountControl,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -730,11 +730,11 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
Filter: testGroupSearchFilter,
|
Filter: testGroupSearchFilter,
|
||||||
GroupNameAttribute: testGroupNameAttrName,
|
GroupNameAttribute: testGroupNameAttrName,
|
||||||
},
|
},
|
||||||
UIDAttributeParsingOverrides: map[string]func(*ldap.Entry) (string, error){"objectGUID": upstreamldap.MicrosoftUUIDFromBinary("objectGUID")},
|
UIDAttributeParsingOverrides: map[string]func(*ldap.Entry) (string, error){"objectGUID": MicrosoftUUIDFromBinary("objectGUID")},
|
||||||
RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{
|
RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{
|
||||||
"pwdLastSet": upstreamldap.AttributeUnchangedSinceLogin("pwdLastSet"),
|
"pwdLastSet": upstreamldap.AttributeUnchangedSinceLogin("pwdLastSet"),
|
||||||
"userAccountControl": upstreamldap.ValidUserAccountControl,
|
"userAccountControl": ValidUserAccountControl,
|
||||||
"msDS-User-Account-Control-Computed": upstreamldap.ValidComputedUserAccountControl,
|
"msDS-User-Account-Control-Computed": ValidComputedUserAccountControl,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -793,11 +793,11 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
Filter: testGroupSearchFilter,
|
Filter: testGroupSearchFilter,
|
||||||
GroupNameAttribute: testGroupNameAttrName,
|
GroupNameAttribute: testGroupNameAttrName,
|
||||||
},
|
},
|
||||||
UIDAttributeParsingOverrides: map[string]func(*ldap.Entry) (string, error){"objectGUID": upstreamldap.MicrosoftUUIDFromBinary("objectGUID")},
|
UIDAttributeParsingOverrides: map[string]func(*ldap.Entry) (string, error){"objectGUID": MicrosoftUUIDFromBinary("objectGUID")},
|
||||||
RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{
|
RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{
|
||||||
"pwdLastSet": upstreamldap.AttributeUnchangedSinceLogin("pwdLastSet"),
|
"pwdLastSet": upstreamldap.AttributeUnchangedSinceLogin("pwdLastSet"),
|
||||||
"userAccountControl": upstreamldap.ValidUserAccountControl,
|
"userAccountControl": ValidUserAccountControl,
|
||||||
"msDS-User-Account-Control-Computed": upstreamldap.ValidComputedUserAccountControl,
|
"msDS-User-Account-Control-Computed": ValidComputedUserAccountControl,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -927,11 +927,11 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
Filter: testGroupSearchFilter,
|
Filter: testGroupSearchFilter,
|
||||||
GroupNameAttribute: testGroupNameAttrName,
|
GroupNameAttribute: testGroupNameAttrName,
|
||||||
},
|
},
|
||||||
UIDAttributeParsingOverrides: map[string]func(*ldap.Entry) (string, error){"objectGUID": upstreamldap.MicrosoftUUIDFromBinary("objectGUID")},
|
UIDAttributeParsingOverrides: map[string]func(*ldap.Entry) (string, error){"objectGUID": MicrosoftUUIDFromBinary("objectGUID")},
|
||||||
RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{
|
RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{
|
||||||
"pwdLastSet": upstreamldap.AttributeUnchangedSinceLogin("pwdLastSet"),
|
"pwdLastSet": upstreamldap.AttributeUnchangedSinceLogin("pwdLastSet"),
|
||||||
"userAccountControl": upstreamldap.ValidUserAccountControl,
|
"userAccountControl": ValidUserAccountControl,
|
||||||
"msDS-User-Account-Control-Computed": upstreamldap.ValidComputedUserAccountControl,
|
"msDS-User-Account-Control-Computed": ValidComputedUserAccountControl,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1056,11 +1056,11 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
Filter: testGroupSearchFilter,
|
Filter: testGroupSearchFilter,
|
||||||
GroupNameAttribute: testGroupNameAttrName,
|
GroupNameAttribute: testGroupNameAttrName,
|
||||||
},
|
},
|
||||||
UIDAttributeParsingOverrides: map[string]func(*ldap.Entry) (string, error){"objectGUID": upstreamldap.MicrosoftUUIDFromBinary("objectGUID")},
|
UIDAttributeParsingOverrides: map[string]func(*ldap.Entry) (string, error){"objectGUID": MicrosoftUUIDFromBinary("objectGUID")},
|
||||||
RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{
|
RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{
|
||||||
"pwdLastSet": upstreamldap.AttributeUnchangedSinceLogin("pwdLastSet"),
|
"pwdLastSet": upstreamldap.AttributeUnchangedSinceLogin("pwdLastSet"),
|
||||||
"userAccountControl": upstreamldap.ValidUserAccountControl,
|
"userAccountControl": ValidUserAccountControl,
|
||||||
"msDS-User-Account-Control-Computed": upstreamldap.ValidComputedUserAccountControl,
|
"msDS-User-Account-Control-Computed": ValidComputedUserAccountControl,
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{
|
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{
|
||||||
@ -1111,11 +1111,11 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
Filter: testGroupSearchFilter,
|
Filter: testGroupSearchFilter,
|
||||||
GroupNameAttribute: testGroupNameAttrName,
|
GroupNameAttribute: testGroupNameAttrName,
|
||||||
},
|
},
|
||||||
UIDAttributeParsingOverrides: map[string]func(*ldap.Entry) (string, error){"objectGUID": upstreamldap.MicrosoftUUIDFromBinary("objectGUID")},
|
UIDAttributeParsingOverrides: map[string]func(*ldap.Entry) (string, error){"objectGUID": MicrosoftUUIDFromBinary("objectGUID")},
|
||||||
RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{
|
RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{
|
||||||
"pwdLastSet": upstreamldap.AttributeUnchangedSinceLogin("pwdLastSet"),
|
"pwdLastSet": upstreamldap.AttributeUnchangedSinceLogin("pwdLastSet"),
|
||||||
"userAccountControl": upstreamldap.ValidUserAccountControl,
|
"userAccountControl": ValidUserAccountControl,
|
||||||
"msDS-User-Account-Control-Computed": upstreamldap.ValidComputedUserAccountControl,
|
"msDS-User-Account-Control-Computed": ValidComputedUserAccountControl,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1315,12 +1315,12 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
Filter: "(&(objectClass=group)(member:1.2.840.113556.1.4.1941:={}))",
|
Filter: "(&(objectClass=group)(member:1.2.840.113556.1.4.1941:={}))",
|
||||||
GroupNameAttribute: "sAMAccountName",
|
GroupNameAttribute: "sAMAccountName",
|
||||||
},
|
},
|
||||||
UIDAttributeParsingOverrides: map[string]func(*ldap.Entry) (string, error){"objectGUID": upstreamldap.MicrosoftUUIDFromBinary("objectGUID")},
|
UIDAttributeParsingOverrides: map[string]func(*ldap.Entry) (string, error){"objectGUID": MicrosoftUUIDFromBinary("objectGUID")},
|
||||||
GroupAttributeParsingOverrides: map[string]func(*ldap.Entry) (string, error){"sAMAccountName": upstreamldap.GroupSAMAccountNameWithDomainSuffix},
|
GroupAttributeParsingOverrides: map[string]func(*ldap.Entry) (string, error){"sAMAccountName": GroupSAMAccountNameWithDomainSuffix},
|
||||||
RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{
|
RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{
|
||||||
"pwdLastSet": upstreamldap.AttributeUnchangedSinceLogin("pwdLastSet"),
|
"pwdLastSet": upstreamldap.AttributeUnchangedSinceLogin("pwdLastSet"),
|
||||||
"userAccountControl": upstreamldap.ValidUserAccountControl,
|
"userAccountControl": ValidUserAccountControl,
|
||||||
"msDS-User-Account-Control-Computed": upstreamldap.ValidComputedUserAccountControl,
|
"msDS-User-Account-Control-Computed": ValidComputedUserAccountControl,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1373,11 +1373,11 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
Filter: testGroupSearchFilter,
|
Filter: testGroupSearchFilter,
|
||||||
GroupNameAttribute: testGroupNameAttrName,
|
GroupNameAttribute: testGroupNameAttrName,
|
||||||
},
|
},
|
||||||
UIDAttributeParsingOverrides: map[string]func(*ldap.Entry) (string, error){"objectGUID": upstreamldap.MicrosoftUUIDFromBinary("objectGUID")},
|
UIDAttributeParsingOverrides: map[string]func(*ldap.Entry) (string, error){"objectGUID": MicrosoftUUIDFromBinary("objectGUID")},
|
||||||
RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{
|
RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{
|
||||||
"pwdLastSet": upstreamldap.AttributeUnchangedSinceLogin("pwdLastSet"),
|
"pwdLastSet": upstreamldap.AttributeUnchangedSinceLogin("pwdLastSet"),
|
||||||
"userAccountControl": upstreamldap.ValidUserAccountControl,
|
"userAccountControl": ValidUserAccountControl,
|
||||||
"msDS-User-Account-Control-Computed": upstreamldap.ValidComputedUserAccountControl,
|
"msDS-User-Account-Control-Computed": ValidComputedUserAccountControl,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1434,11 +1434,11 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
Filter: testGroupSearchFilter,
|
Filter: testGroupSearchFilter,
|
||||||
GroupNameAttribute: testGroupNameAttrName,
|
GroupNameAttribute: testGroupNameAttrName,
|
||||||
},
|
},
|
||||||
UIDAttributeParsingOverrides: map[string]func(*ldap.Entry) (string, error){"objectGUID": upstreamldap.MicrosoftUUIDFromBinary("objectGUID")},
|
UIDAttributeParsingOverrides: map[string]func(*ldap.Entry) (string, error){"objectGUID": MicrosoftUUIDFromBinary("objectGUID")},
|
||||||
RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{
|
RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{
|
||||||
"pwdLastSet": upstreamldap.AttributeUnchangedSinceLogin("pwdLastSet"),
|
"pwdLastSet": upstreamldap.AttributeUnchangedSinceLogin("pwdLastSet"),
|
||||||
"userAccountControl": upstreamldap.ValidUserAccountControl,
|
"userAccountControl": ValidUserAccountControl,
|
||||||
"msDS-User-Account-Control-Computed": upstreamldap.ValidComputedUserAccountControl,
|
"msDS-User-Account-Control-Computed": ValidComputedUserAccountControl,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1489,11 +1489,11 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
Filter: testGroupSearchFilter,
|
Filter: testGroupSearchFilter,
|
||||||
GroupNameAttribute: testGroupNameAttrName,
|
GroupNameAttribute: testGroupNameAttrName,
|
||||||
},
|
},
|
||||||
UIDAttributeParsingOverrides: map[string]func(*ldap.Entry) (string, error){"objectGUID": upstreamldap.MicrosoftUUIDFromBinary("objectGUID")},
|
UIDAttributeParsingOverrides: map[string]func(*ldap.Entry) (string, error){"objectGUID": MicrosoftUUIDFromBinary("objectGUID")},
|
||||||
RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{
|
RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{
|
||||||
"pwdLastSet": upstreamldap.AttributeUnchangedSinceLogin("pwdLastSet"),
|
"pwdLastSet": upstreamldap.AttributeUnchangedSinceLogin("pwdLastSet"),
|
||||||
"userAccountControl": upstreamldap.ValidUserAccountControl,
|
"userAccountControl": ValidUserAccountControl,
|
||||||
"msDS-User-Account-Control-Computed": upstreamldap.ValidComputedUserAccountControl,
|
"msDS-User-Account-Control-Computed": ValidComputedUserAccountControl,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1690,11 +1690,11 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
|||||||
Filter: testGroupSearchFilter,
|
Filter: testGroupSearchFilter,
|
||||||
GroupNameAttribute: testGroupNameAttrName,
|
GroupNameAttribute: testGroupNameAttrName,
|
||||||
},
|
},
|
||||||
UIDAttributeParsingOverrides: map[string]func(*ldap.Entry) (string, error){"objectGUID": upstreamldap.MicrosoftUUIDFromBinary("objectGUID")},
|
UIDAttributeParsingOverrides: map[string]func(*ldap.Entry) (string, error){"objectGUID": MicrosoftUUIDFromBinary("objectGUID")},
|
||||||
RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{
|
RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{
|
||||||
"pwdLastSet": upstreamldap.AttributeUnchangedSinceLogin("pwdLastSet"),
|
"pwdLastSet": upstreamldap.AttributeUnchangedSinceLogin("pwdLastSet"),
|
||||||
"userAccountControl": upstreamldap.ValidUserAccountControl,
|
"userAccountControl": ValidUserAccountControl,
|
||||||
"msDS-User-Account-Control-Computed": upstreamldap.ValidComputedUserAccountControl,
|
"msDS-User-Account-Control-Computed": ValidComputedUserAccountControl,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1879,3 +1879,270 @@ func normalizeActiveDirectoryUpstreams(upstreams []v1alpha1.ActiveDirectoryIdent
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGroupSAMAccountNameWithDomainSuffix(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
entry *ldap.Entry
|
||||||
|
wantResult string
|
||||||
|
wantErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "happy path with DN and valid sAMAccountName",
|
||||||
|
entry: &ldap.Entry{
|
||||||
|
DN: "CN=animals,OU=Users,OU=pinniped-ad,DC=mycompany,DC=example,DC=com",
|
||||||
|
Attributes: []*ldap.EntryAttribute{
|
||||||
|
ldap.NewEntryAttribute("sAMAccountName", []string{"Mammals"}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantResult: "Mammals@mycompany.example.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no domain components in DN",
|
||||||
|
entry: &ldap.Entry{
|
||||||
|
DN: "no-domain-components",
|
||||||
|
Attributes: []*ldap.EntryAttribute{
|
||||||
|
ldap.NewEntryAttribute("sAMAccountName", []string{"Mammals"}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: "did not find domain components in group dn: no-domain-components",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple values for sAMAccountName attribute",
|
||||||
|
entry: &ldap.Entry{
|
||||||
|
DN: "CN=animals,OU=Users,OU=pinniped-ad,DC=mycompany,DC=example,DC=com",
|
||||||
|
Attributes: []*ldap.EntryAttribute{
|
||||||
|
ldap.NewEntryAttribute("sAMAccountName", []string{"Mammals", "Eukaryotes"}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: "found 2 values for attribute \"sAMAccountName\", but expected 1 result",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no values for sAMAccountName attribute",
|
||||||
|
entry: &ldap.Entry{
|
||||||
|
DN: "CN=animals,OU=Users,OU=pinniped-ad,DC=mycompany,DC=example,DC=com",
|
||||||
|
Attributes: []*ldap.EntryAttribute{
|
||||||
|
ldap.NewEntryAttribute("sAMAccountName", []string{}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: "found 0 values for attribute \"sAMAccountName\", but expected 1 result",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
tt := test
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
suffixedSAMAccountName, err := GroupSAMAccountNameWithDomainSuffix(tt.entry)
|
||||||
|
if tt.wantErr != "" {
|
||||||
|
require.EqualError(t, err, tt.wantErr)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
require.Equal(t, tt.wantResult, suffixedSAMAccountName)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetMicrosoftFormattedUUID(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
binaryUUID []byte
|
||||||
|
wantString string
|
||||||
|
wantErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "happy path",
|
||||||
|
binaryUUID: []byte("\x01\x02\x03\x04\x05\x06\x07\x08\x09\x10\x11\x12\x13\x14\x15\x16"),
|
||||||
|
wantString: "04030201-0605-0807-0910-111213141516",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "not the right length",
|
||||||
|
binaryUUID: []byte("2\xf8\xb0\xaa\xb6V\xb1D\x8b(\xee"),
|
||||||
|
wantErr: "invalid UUID (got 11 bytes)",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
tt := test
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
actualUUIDString, err := microsoftUUIDFromBinary(tt.binaryUUID)
|
||||||
|
if tt.wantErr != "" {
|
||||||
|
require.EqualError(t, err, tt.wantErr)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
require.Equal(t, tt.wantString, actualUUIDString)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetDomainFromDistinguishedName(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
distinguishedName string
|
||||||
|
wantDomain string
|
||||||
|
wantErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "happy path",
|
||||||
|
distinguishedName: "CN=Mammals,OU=Users,OU=pinniped-ad,DC=activedirectory,DC=mycompany,DC=example,DC=com",
|
||||||
|
wantDomain: "activedirectory.mycompany.example.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "lowercased happy path",
|
||||||
|
distinguishedName: "cn=Mammals,ou=Users,ou=pinniped-ad,dc=activedirectory,dc=mycompany,dc=example,dc=com",
|
||||||
|
wantDomain: "activedirectory.mycompany.example.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no domain components",
|
||||||
|
distinguishedName: "not-a-dn",
|
||||||
|
wantErr: "did not find domain components in group dn: not-a-dn",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
tt := test
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
actualDomain, err := getDomainFromDistinguishedName(tt.distinguishedName)
|
||||||
|
if tt.wantErr != "" {
|
||||||
|
require.EqualError(t, err, tt.wantErr)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
require.Equal(t, tt.wantDomain, actualDomain)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidUserAccountControl(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
entry *ldap.Entry
|
||||||
|
wantErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "happy normal user",
|
||||||
|
entry: &ldap.Entry{
|
||||||
|
DN: "some-dn",
|
||||||
|
Attributes: []*ldap.EntryAttribute{
|
||||||
|
{
|
||||||
|
Name: "userAccountControl",
|
||||||
|
Values: []string{"512"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "happy user whose password doesn't expire",
|
||||||
|
entry: &ldap.Entry{
|
||||||
|
DN: "some-dn",
|
||||||
|
Attributes: []*ldap.EntryAttribute{
|
||||||
|
{
|
||||||
|
Name: "userAccountControl",
|
||||||
|
Values: []string{"65536"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "deactivated user",
|
||||||
|
entry: &ldap.Entry{
|
||||||
|
DN: "some-dn",
|
||||||
|
Attributes: []*ldap.EntryAttribute{
|
||||||
|
{
|
||||||
|
Name: "userAccountControl",
|
||||||
|
Values: []string{"514"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: "user has been deactivated",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-integer result",
|
||||||
|
entry: &ldap.Entry{
|
||||||
|
DN: "some-dn",
|
||||||
|
Attributes: []*ldap.EntryAttribute{
|
||||||
|
{
|
||||||
|
Name: "userAccountControl",
|
||||||
|
Values: []string{"not-an-int"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: "strconv.Atoi: parsing \"not-an-int\": invalid syntax",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
tt := test
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := ValidUserAccountControl(tt.entry, provider.StoredRefreshAttributes{})
|
||||||
|
|
||||||
|
if tt.wantErr != "" {
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Equal(t, tt.wantErr, err.Error())
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidComputedUserAccountControl(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
entry *ldap.Entry
|
||||||
|
wantErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "happy normal user",
|
||||||
|
entry: &ldap.Entry{
|
||||||
|
DN: "some-dn",
|
||||||
|
Attributes: []*ldap.EntryAttribute{
|
||||||
|
{
|
||||||
|
Name: "msDS-User-Account-Control-Computed",
|
||||||
|
Values: []string{"0"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "locked user",
|
||||||
|
entry: &ldap.Entry{
|
||||||
|
DN: "some-dn",
|
||||||
|
Attributes: []*ldap.EntryAttribute{
|
||||||
|
{
|
||||||
|
Name: "msDS-User-Account-Control-Computed",
|
||||||
|
Values: []string{"16"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: "user has been locked",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-integer result",
|
||||||
|
entry: &ldap.Entry{
|
||||||
|
DN: "some-dn",
|
||||||
|
Attributes: []*ldap.EntryAttribute{
|
||||||
|
{
|
||||||
|
Name: "msDS-User-Account-Control-Computed",
|
||||||
|
Values: []string{"not-an-int"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: "strconv.Atoi: parsing \"not-an-int\": invalid syntax",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
tt := test
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := ValidComputedUserAccountControl(tt.entry, provider.StoredRefreshAttributes{})
|
||||||
|
|
||||||
|
if tt.wantErr != "" {
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Equal(t, tt.wantErr, err.Error())
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -350,45 +350,23 @@ const ExpectedAuthorizeCodeSessionJSONFromFuzzing = `{
|
|||||||
"ldap": {
|
"ldap": {
|
||||||
"userDN": "6鉢緋uƴŤȱʀļÂ?墖\u003cƬb獭潜Ʃ饾",
|
"userDN": "6鉢緋uƴŤȱʀļÂ?墖\u003cƬb獭潜Ʃ饾",
|
||||||
"extraRefreshAttributes": {
|
"extraRefreshAttributes": {
|
||||||
"ĝ": [
|
"ď逳鞪?3)藵睋邔\u0026Ű惫蜀Ģ¡圔": "墀jMʥ",
|
||||||
"IȽ齤士bEǎ",
|
"齁š%OpKȱ藚ɏ¬Ê蒭堜]ȗ韚ʫ": "鷞aŚB碠k9帴ʘ赱"
|
||||||
"跞@)¿,ɭS隑ip偶宾儮猷V麹"
|
|
||||||
],
|
|
||||||
"齁š%OpKȱ藚ɏ¬Ê蒭堜]ȗ韚ʫ": [
|
|
||||||
"aŚB碠k9帴ʘ赱ŕ瑹xȢ~1Į",
|
|
||||||
"睋邔\u0026Ű惫蜀Ģ¡圔鎥墀jMʥ",
|
|
||||||
"+î艔垎0"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"activedirectory": {
|
"activedirectory": {
|
||||||
"userDN": "ȝƋ鬯犦獢9c5¤.岵",
|
"userDN": "0D餹sêĝɓ",
|
||||||
"extraRefreshAttributes": {
|
"extraRefreshAttributes": {
|
||||||
"\u0026錝D肁Ŷɽ蔒PR}Ųʓ": [
|
"摱ì": "bEǎ儯惝Io"
|
||||||
"_º$+溪ŸȢŒų崓ļ"
|
|
||||||
],
|
|
||||||
"P姧骦:駝重Eȫ": [
|
|
||||||
"ɂ/",
|
|
||||||
"Ƀɫ囤",
|
|
||||||
"鉌X縆跣ŠɞɮƎ賿礣©硇"
|
|
||||||
],
|
|
||||||
"a齙\\蹼偦歛ơ": [
|
|
||||||
"y衑拁Ȃ縅",
|
|
||||||
"Vƅȭǝ*擦28Dž ",
|
|
||||||
"ã置bņ抰蛖"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"requestedAudience": [
|
"requestedAudience": [
|
||||||
"õC嶃",
|
"Ł"
|
||||||
"ɣƜ/気ū齢q萮左/篣AÚƄŕ~čfV",
|
|
||||||
"x荃墎]ac["
|
|
||||||
],
|
],
|
||||||
"grantedAudience": [
|
"grantedAudience": [
|
||||||
"XôĖ给溬d鞕",
|
"r"
|
||||||
"腿tʏƲ%}ſ¯Ɣ 籌Tǘ乚Ȥ2Ķě"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"version": "2"
|
"version": "2"
|
||||||
|
@ -396,7 +396,6 @@ func TestFuzzAndJSONNewValidEmptyAuthorizeCodeSession(t *testing.T) {
|
|||||||
// while the fuzzer will panic if AuthorizeRequest changes in a way that cannot be fuzzed,
|
// while the fuzzer will panic if AuthorizeRequest changes in a way that cannot be fuzzed,
|
||||||
// if it adds a new field that can be fuzzed, this check will fail
|
// if it adds a new field that can be fuzzed, this check will fail
|
||||||
// thus if AuthorizeRequest changes, we will detect it here (though we could possibly miss an omitempty field)
|
// thus if AuthorizeRequest changes, we will detect it here (though we could possibly miss an omitempty field)
|
||||||
t.Log(authorizeCodeSessionJSONFromFuzzing)
|
|
||||||
require.JSONEq(t, ExpectedAuthorizeCodeSessionJSONFromFuzzing, authorizeCodeSessionJSONFromFuzzing)
|
require.JSONEq(t, ExpectedAuthorizeCodeSessionJSONFromFuzzing, authorizeCodeSessionJSONFromFuzzing)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,13 +123,13 @@ func handleAuthRequestForLDAPUpstream(
|
|||||||
if idpType == psession.ProviderTypeLDAP {
|
if idpType == psession.ProviderTypeLDAP {
|
||||||
customSessionData.LDAP = &psession.LDAPSessionData{
|
customSessionData.LDAP = &psession.LDAPSessionData{
|
||||||
UserDN: dn,
|
UserDN: dn,
|
||||||
ExtraRefreshAttributes: authenticateResponse.User.GetExtra(),
|
ExtraRefreshAttributes: authenticateResponse.ExtraRefreshAttributes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if idpType == psession.ProviderTypeActiveDirectory {
|
if idpType == psession.ProviderTypeActiveDirectory {
|
||||||
customSessionData.ActiveDirectory = &psession.ActiveDirectorySessionData{
|
customSessionData.ActiveDirectory = &psession.ActiveDirectorySessionData{
|
||||||
UserDN: dn,
|
UserDN: dn,
|
||||||
ExtraRefreshAttributes: authenticateResponse.User.GetExtra(),
|
ExtraRefreshAttributes: authenticateResponse.ExtraRefreshAttributes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -269,6 +269,8 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
happyLDAPUID := "some-ldap-uid"
|
happyLDAPUID := "some-ldap-uid"
|
||||||
happyLDAPUserDN := "cn=foo,dn=bar"
|
happyLDAPUserDN := "cn=foo,dn=bar"
|
||||||
happyLDAPGroups := []string{"group1", "group2", "group3"}
|
happyLDAPGroups := []string{"group1", "group2", "group3"}
|
||||||
|
happyLDAPExtraRefreshAttribute := "some-refresh-attribute"
|
||||||
|
happyLDAPExtraRefreshValue := "some-refresh-attribute-value"
|
||||||
|
|
||||||
parsedUpstreamLDAPURL, err := url.Parse(upstreamLDAPURL)
|
parsedUpstreamLDAPURL, err := url.Parse(upstreamLDAPURL)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -283,9 +285,11 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
Name: happyLDAPUsernameFromAuthenticator,
|
Name: happyLDAPUsernameFromAuthenticator,
|
||||||
UID: happyLDAPUID,
|
UID: happyLDAPUID,
|
||||||
Groups: happyLDAPGroups,
|
Groups: happyLDAPGroups,
|
||||||
Extra: map[string][]string{"some-refresh-attribute": {"some-refresh-attribute-value"}},
|
|
||||||
},
|
},
|
||||||
DN: happyLDAPUserDN,
|
DN: happyLDAPUserDN,
|
||||||
|
ExtraRefreshAttributes: map[string]string{
|
||||||
|
happyLDAPExtraRefreshAttribute: happyLDAPExtraRefreshValue,
|
||||||
|
},
|
||||||
}, true, nil
|
}, true, nil
|
||||||
}
|
}
|
||||||
return nil, false, nil
|
return nil, false, nil
|
||||||
@ -444,7 +448,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
LDAP: nil,
|
LDAP: nil,
|
||||||
ActiveDirectory: &psession.ActiveDirectorySessionData{
|
ActiveDirectory: &psession.ActiveDirectorySessionData{
|
||||||
UserDN: happyLDAPUserDN,
|
UserDN: happyLDAPUserDN,
|
||||||
ExtraRefreshAttributes: map[string][]string{"some-refresh-attribute": {"some-refresh-attribute-value"}},
|
ExtraRefreshAttributes: map[string]string{happyLDAPExtraRefreshAttribute: happyLDAPExtraRefreshValue},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -455,7 +459,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
OIDC: nil,
|
OIDC: nil,
|
||||||
LDAP: &psession.LDAPSessionData{
|
LDAP: &psession.LDAPSessionData{
|
||||||
UserDN: happyLDAPUserDN,
|
UserDN: happyLDAPUserDN,
|
||||||
ExtraRefreshAttributes: map[string][]string{"some-refresh-attribute": {"some-refresh-attribute-value"}},
|
ExtraRefreshAttributes: map[string]string{happyLDAPExtraRefreshAttribute: happyLDAPExtraRefreshValue},
|
||||||
},
|
},
|
||||||
ActiveDirectory: nil,
|
ActiveDirectory: nil,
|
||||||
}
|
}
|
||||||
|
@ -102,7 +102,7 @@ type StoredRefreshAttributes struct {
|
|||||||
Subject string
|
Subject string
|
||||||
DN string
|
DN string
|
||||||
AuthTime time.Time
|
AuthTime time.Time
|
||||||
AdditionalAttributes map[string][]string
|
AdditionalAttributes map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
type DynamicUpstreamIDPProvider interface {
|
type DynamicUpstreamIDPProvider interface {
|
||||||
|
@ -180,7 +180,7 @@ func upstreamLDAPRefresh(ctx context.Context, providerCache oidc.UpstreamIdentit
|
|||||||
return errorsx.WithStack(errMissingUpstreamSessionInternalError)
|
return errorsx.WithStack(errMissingUpstreamSessionInternalError)
|
||||||
}
|
}
|
||||||
|
|
||||||
var additionalAttributes map[string][]string
|
var additionalAttributes map[string]string
|
||||||
if s.ProviderType == psession.ProviderTypeLDAP {
|
if s.ProviderType == psession.ProviderTypeLDAP {
|
||||||
additionalAttributes = s.LDAP.ExtraRefreshAttributes
|
additionalAttributes = s.LDAP.ExtraRefreshAttributes
|
||||||
} else {
|
} else {
|
||||||
@ -200,7 +200,6 @@ func upstreamLDAPRefresh(ctx context.Context, providerCache oidc.UpstreamIdentit
|
|||||||
Username: username,
|
Username: username,
|
||||||
Subject: subject,
|
Subject: subject,
|
||||||
DN: dn,
|
DN: dn,
|
||||||
AuthTime: session.IDTokenClaims().AuthTime,
|
|
||||||
AdditionalAttributes: additionalAttributes,
|
AdditionalAttributes: additionalAttributes,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -67,13 +67,13 @@ type OIDCSessionData struct {
|
|||||||
// LDAPSessionData is the additional data needed by Pinniped when the upstream IDP is an LDAP provider.
|
// LDAPSessionData is the additional data needed by Pinniped when the upstream IDP is an LDAP provider.
|
||||||
type LDAPSessionData struct {
|
type LDAPSessionData struct {
|
||||||
UserDN string `json:"userDN"`
|
UserDN string `json:"userDN"`
|
||||||
ExtraRefreshAttributes map[string][]string `json:"extraRefreshAttributes,omitempty"`
|
ExtraRefreshAttributes map[string]string `json:"extraRefreshAttributes,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ActiveDirectorySessionData is the additional data needed by Pinniped when the upstream IDP is an Active Directory provider.
|
// ActiveDirectorySessionData is the additional data needed by Pinniped when the upstream IDP is an Active Directory provider.
|
||||||
type ActiveDirectorySessionData struct {
|
type ActiveDirectorySessionData struct {
|
||||||
UserDN string `json:"userDN"`
|
UserDN string `json:"userDN"`
|
||||||
ExtraRefreshAttributes map[string][]string `json:"extraRefreshAttributes,omitempty"`
|
ExtraRefreshAttributes map[string]string `json:"extraRefreshAttributes,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPinnipedSession returns a new empty session.
|
// NewPinnipedSession returns a new empty session.
|
||||||
|
@ -13,14 +13,11 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-ldap/ldap/v3"
|
"github.com/go-ldap/ldap/v3"
|
||||||
"github.com/google/uuid"
|
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/apiserver/pkg/authentication/user"
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
"k8s.io/utils/trace"
|
"k8s.io/utils/trace"
|
||||||
@ -40,20 +37,6 @@ const (
|
|||||||
groupSearchPageSize = uint32(250)
|
groupSearchPageSize = uint32(250)
|
||||||
defaultLDAPPort = uint16(389)
|
defaultLDAPPort = uint16(389)
|
||||||
defaultLDAPSPort = uint16(636)
|
defaultLDAPSPort = uint16(636)
|
||||||
sAMAccountNameAttribute = "sAMAccountName"
|
|
||||||
// PwdLastSetAttribute is the date and time that the password for this account was last changed.
|
|
||||||
// https://docs.microsoft.com/en-us/windows/win32/adschema/a-pwdlastset
|
|
||||||
PwdLastSetAttribute = "pwdLastSet"
|
|
||||||
// UserAccountControlAttribute represents a bitmap of user properties.
|
|
||||||
// https://docs.microsoft.com/en-us/troubleshoot/windows-server/identity/useraccountcontrol-manipulate-account-properties
|
|
||||||
UserAccountControlAttribute = "userAccountControl"
|
|
||||||
// UserAccountControlComputedAttribute represents a bitmap of user properties.
|
|
||||||
// https://docs.microsoft.com/en-us/windows/win32/adschema/a-msds-user-account-control-computed
|
|
||||||
UserAccountControlComputedAttribute = "msDS-User-Account-Control-Computed"
|
|
||||||
// 0x0002 ACCOUNTDISABLE in userAccountControl bitmap.
|
|
||||||
accountDisabledBitmapValue = 2
|
|
||||||
// 0x0010 UF_LOCKOUT in msDS-User-Account-Control-Computed bitmap.
|
|
||||||
accountLockedBitmapValue = 16
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Conn abstracts the upstream LDAP communication protocol (mostly for testing).
|
// Conn abstracts the upstream LDAP communication protocol (mostly for testing).
|
||||||
@ -593,13 +576,13 @@ func (p *Provider) searchAndBindUser(conn Conn, username string, bindFunc func(c
|
|||||||
}
|
}
|
||||||
sort.Strings(mappedGroupNames)
|
sort.Strings(mappedGroupNames)
|
||||||
|
|
||||||
mappedRefreshAttributes := make(map[string][]string)
|
mappedRefreshAttributes := make(map[string]string)
|
||||||
for k := range p.c.RefreshAttributeChecks {
|
for k := range p.c.RefreshAttributeChecks {
|
||||||
mappedVal, err := p.getSearchResultAttributeValue(k, userEntry, username)
|
mappedVal, err := p.getSearchResultAttributeRawValueEncoded(k, userEntry, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
mappedRefreshAttributes[k] = []string{mappedVal}
|
mappedRefreshAttributes[k] = mappedVal
|
||||||
}
|
}
|
||||||
|
|
||||||
// Caution: Note that any other LDAP commands after this bind will be run as this user instead of as the configured BindUsername!
|
// Caution: Note that any other LDAP commands after this bind will be run as this user instead of as the configured BindUsername!
|
||||||
@ -624,9 +607,9 @@ func (p *Provider) searchAndBindUser(conn Conn, username string, bindFunc func(c
|
|||||||
Name: mappedUsername,
|
Name: mappedUsername,
|
||||||
UID: mappedUID,
|
UID: mappedUID,
|
||||||
Groups: mappedGroupNames,
|
Groups: mappedGroupNames,
|
||||||
Extra: mappedRefreshAttributes,
|
|
||||||
},
|
},
|
||||||
DN: userEntry.DN,
|
DN: userEntry.DN,
|
||||||
|
ExtraRefreshAttributes: mappedRefreshAttributes,
|
||||||
}
|
}
|
||||||
|
|
||||||
return response, nil
|
return response, nil
|
||||||
@ -819,101 +802,18 @@ func (p *Provider) traceRefreshFailure(t *trace.Trace, err error) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func MicrosoftUUIDFromBinary(attributeName string) func(entry *ldap.Entry) (string, error) {
|
|
||||||
// validation has already been done so we can just get the attribute...
|
|
||||||
return func(entry *ldap.Entry) (string, error) {
|
|
||||||
binaryUUID := entry.GetRawAttributeValue(attributeName)
|
|
||||||
return microsoftUUIDFromBinary(binaryUUID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func microsoftUUIDFromBinary(binaryUUID []byte) (string, error) {
|
|
||||||
uuidVal, err := uuid.FromBytes(binaryUUID) // start out with the RFC4122 version
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
// then swap it because AD stores the first 3 fields little-endian rather than the expected
|
|
||||||
// big-endian.
|
|
||||||
uuidVal[0], uuidVal[1], uuidVal[2], uuidVal[3] = uuidVal[3], uuidVal[2], uuidVal[1], uuidVal[0]
|
|
||||||
uuidVal[4], uuidVal[5] = uuidVal[5], uuidVal[4]
|
|
||||||
uuidVal[6], uuidVal[7] = uuidVal[7], uuidVal[6]
|
|
||||||
return uuidVal.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GroupSAMAccountNameWithDomainSuffix(entry *ldap.Entry) (string, error) {
|
|
||||||
sAMAccountNameAttributeValues := entry.GetAttributeValues(sAMAccountNameAttribute)
|
|
||||||
|
|
||||||
if len(sAMAccountNameAttributeValues) != 1 {
|
|
||||||
return "", fmt.Errorf(`found %d values for attribute "%s", but expected 1 result`,
|
|
||||||
len(sAMAccountNameAttributeValues), sAMAccountNameAttribute,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
sAMAccountName := sAMAccountNameAttributeValues[0]
|
|
||||||
if len(sAMAccountName) == 0 {
|
|
||||||
return "", fmt.Errorf(`found empty value for attribute "%s", but expected value to be non-empty`,
|
|
||||||
sAMAccountNameAttribute,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
distinguishedName := entry.DN
|
|
||||||
domain, err := getDomainFromDistinguishedName(distinguishedName)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return sAMAccountName + "@" + domain, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var domainComponentsRegexp = regexp.MustCompile(",DC=|,dc=")
|
|
||||||
|
|
||||||
func getDomainFromDistinguishedName(distinguishedName string) (string, error) {
|
|
||||||
domainComponents := domainComponentsRegexp.Split(distinguishedName, -1)
|
|
||||||
if len(domainComponents) == 1 {
|
|
||||||
return "", fmt.Errorf("did not find domain components in group dn: %s", distinguishedName)
|
|
||||||
}
|
|
||||||
return strings.Join(domainComponents[1:], "."), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func AttributeUnchangedSinceLogin(attribute string) func(*ldap.Entry, provider.StoredRefreshAttributes) error {
|
func AttributeUnchangedSinceLogin(attribute string) func(*ldap.Entry, provider.StoredRefreshAttributes) error {
|
||||||
return func(entry *ldap.Entry, storedAttributes provider.StoredRefreshAttributes) error {
|
return func(entry *ldap.Entry, storedAttributes provider.StoredRefreshAttributes) error {
|
||||||
prevAttributeValues := storedAttributes.AdditionalAttributes[attribute]
|
prevAttributeValue := storedAttributes.AdditionalAttributes[attribute]
|
||||||
newValues := entry.GetAttributeValues(attribute)
|
newValues := entry.GetAttributeValues(attribute)
|
||||||
|
|
||||||
if len(newValues) != 1 {
|
if len(newValues) != 1 {
|
||||||
return fmt.Errorf(`expected to find 1 value for "%s" attribute, but found %d`, attribute, len(newValues))
|
return fmt.Errorf(`expected to find 1 value for "%s" attribute, but found %d`, attribute, len(newValues))
|
||||||
}
|
}
|
||||||
if len(prevAttributeValues) != 1 {
|
encodedNewValue := base64.RawURLEncoding.EncodeToString(entry.GetRawAttributeValue(attribute))
|
||||||
return fmt.Errorf(`expected to find 1 stored value for "%s" attribute, but found %d`, attribute, len(prevAttributeValues))
|
if prevAttributeValue != encodedNewValue {
|
||||||
}
|
return fmt.Errorf(`value for attribute "%s" has changed since initial value at login`, attribute)
|
||||||
if prevAttributeValues[0] != newValues[0] {
|
|
||||||
return fmt.Errorf(`value for attribute "%s" has changed since initial value at login. Previous value: "%s", new value: "%s"`, attribute, prevAttributeValues[0], newValues[0])
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ValidUserAccountControl(entry *ldap.Entry, _ provider.StoredRefreshAttributes) error {
|
|
||||||
userAccountControl, err := strconv.Atoi(entry.GetAttributeValue(UserAccountControlAttribute))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
deactivated := userAccountControl & accountDisabledBitmapValue // bitwise and.
|
|
||||||
if deactivated != 0 {
|
|
||||||
return fmt.Errorf("user has been deactivated")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ValidComputedUserAccountControl(entry *ldap.Entry, _ provider.StoredRefreshAttributes) error {
|
|
||||||
userAccountControl, err := strconv.Atoi(entry.GetAttributeValue(UserAccountControlComputedAttribute))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
locked := userAccountControl & accountLockedBitmapValue // bitwise and
|
|
||||||
if locked != 0 {
|
|
||||||
return fmt.Errorf("user has been locked")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
@ -155,17 +155,17 @@ func TestEndUserAuthentication(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The auth response which matches the exampleUserSearchResult and exampleGroupSearchResult.
|
// The auth response which matches the exampleUserSearchResult and exampleGroupSearchResult.
|
||||||
expectedAuthResponse := func(editFunc func(r *user.DefaultInfo)) *authenticators.Response {
|
expectedAuthResponse := func(editFunc func(r *authenticators.Response)) *authenticators.Response {
|
||||||
u := &user.DefaultInfo{
|
u := &user.DefaultInfo{
|
||||||
Name: testUserSearchResultUsernameAttributeValue,
|
Name: testUserSearchResultUsernameAttributeValue,
|
||||||
UID: base64.RawURLEncoding.EncodeToString([]byte(testUserSearchResultUIDAttributeValue)),
|
UID: base64.RawURLEncoding.EncodeToString([]byte(testUserSearchResultUIDAttributeValue)),
|
||||||
Groups: []string{testGroupSearchResultGroupNameAttributeValue1, testGroupSearchResultGroupNameAttributeValue2},
|
Groups: []string{testGroupSearchResultGroupNameAttributeValue1, testGroupSearchResultGroupNameAttributeValue2},
|
||||||
Extra: map[string][]string{},
|
|
||||||
}
|
}
|
||||||
|
response := &authenticators.Response{User: u, DN: testUserSearchResultDNValue, ExtraRefreshAttributes: map[string]string{}}
|
||||||
if editFunc != nil {
|
if editFunc != nil {
|
||||||
editFunc(u)
|
editFunc(response)
|
||||||
}
|
}
|
||||||
return &authenticators.Response{User: u, DN: testUserSearchResultDNValue}
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@ -252,8 +252,9 @@ func TestEndUserAuthentication(t *testing.T) {
|
|||||||
bindEndUserMocks: func(conn *mockldapconn.MockConn) {
|
bindEndUserMocks: func(conn *mockldapconn.MockConn) {
|
||||||
conn.EXPECT().Bind(testUserSearchResultDNValue, testUpstreamPassword).Times(1)
|
conn.EXPECT().Bind(testUserSearchResultDNValue, testUpstreamPassword).Times(1)
|
||||||
},
|
},
|
||||||
wantAuthResponse: expectedAuthResponse(func(r *user.DefaultInfo) {
|
wantAuthResponse: expectedAuthResponse(func(r *authenticators.Response) {
|
||||||
r.Groups = []string{}
|
info := r.User.(*user.DefaultInfo)
|
||||||
|
info.Groups = []string{}
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -284,8 +285,9 @@ func TestEndUserAuthentication(t *testing.T) {
|
|||||||
bindEndUserMocks: func(conn *mockldapconn.MockConn) {
|
bindEndUserMocks: func(conn *mockldapconn.MockConn) {
|
||||||
conn.EXPECT().Bind(testUserSearchResultDNValue, testUpstreamPassword).Times(1)
|
conn.EXPECT().Bind(testUserSearchResultDNValue, testUpstreamPassword).Times(1)
|
||||||
},
|
},
|
||||||
wantAuthResponse: expectedAuthResponse(func(r *user.DefaultInfo) {
|
wantAuthResponse: expectedAuthResponse(func(r *authenticators.Response) {
|
||||||
r.Name = testUserSearchResultDNValue
|
info := r.User.(*user.DefaultInfo)
|
||||||
|
info.Name = testUserSearchResultDNValue
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -316,8 +318,9 @@ func TestEndUserAuthentication(t *testing.T) {
|
|||||||
bindEndUserMocks: func(conn *mockldapconn.MockConn) {
|
bindEndUserMocks: func(conn *mockldapconn.MockConn) {
|
||||||
conn.EXPECT().Bind(testUserSearchResultDNValue, testUpstreamPassword).Times(1)
|
conn.EXPECT().Bind(testUserSearchResultDNValue, testUpstreamPassword).Times(1)
|
||||||
},
|
},
|
||||||
wantAuthResponse: expectedAuthResponse(func(r *user.DefaultInfo) {
|
wantAuthResponse: expectedAuthResponse(func(r *authenticators.Response) {
|
||||||
r.UID = base64.RawURLEncoding.EncodeToString([]byte(testUserSearchResultDNValue))
|
info := r.User.(*user.DefaultInfo)
|
||||||
|
info.UID = base64.RawURLEncoding.EncodeToString([]byte(testUserSearchResultDNValue))
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -339,8 +342,9 @@ func TestEndUserAuthentication(t *testing.T) {
|
|||||||
bindEndUserMocks: func(conn *mockldapconn.MockConn) {
|
bindEndUserMocks: func(conn *mockldapconn.MockConn) {
|
||||||
conn.EXPECT().Bind(testUserSearchResultDNValue, testUpstreamPassword).Times(1)
|
conn.EXPECT().Bind(testUserSearchResultDNValue, testUpstreamPassword).Times(1)
|
||||||
},
|
},
|
||||||
wantAuthResponse: expectedAuthResponse(func(r *user.DefaultInfo) {
|
wantAuthResponse: expectedAuthResponse(func(r *authenticators.Response) {
|
||||||
r.Groups = []string{testGroupSearchResultDNValue1, testGroupSearchResultDNValue2}
|
info := r.User.(*user.DefaultInfo)
|
||||||
|
info.Groups = []string{testGroupSearchResultDNValue1, testGroupSearchResultDNValue2}
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -362,8 +366,9 @@ func TestEndUserAuthentication(t *testing.T) {
|
|||||||
bindEndUserMocks: func(conn *mockldapconn.MockConn) {
|
bindEndUserMocks: func(conn *mockldapconn.MockConn) {
|
||||||
conn.EXPECT().Bind(testUserSearchResultDNValue, testUpstreamPassword).Times(1)
|
conn.EXPECT().Bind(testUserSearchResultDNValue, testUpstreamPassword).Times(1)
|
||||||
},
|
},
|
||||||
wantAuthResponse: expectedAuthResponse(func(r *user.DefaultInfo) {
|
wantAuthResponse: expectedAuthResponse(func(r *authenticators.Response) {
|
||||||
r.Groups = []string{testGroupSearchResultDNValue1, testGroupSearchResultDNValue2}
|
info := r.User.(*user.DefaultInfo)
|
||||||
|
info.Groups = []string{testGroupSearchResultDNValue1, testGroupSearchResultDNValue2}
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -508,110 +513,11 @@ func TestEndUserAuthentication(t *testing.T) {
|
|||||||
Name: testUserSearchResultUsernameAttributeValue,
|
Name: testUserSearchResultUsernameAttributeValue,
|
||||||
UID: base64.RawURLEncoding.EncodeToString([]byte(testUserSearchResultUIDAttributeValue)),
|
UID: base64.RawURLEncoding.EncodeToString([]byte(testUserSearchResultUIDAttributeValue)),
|
||||||
Groups: []string{"a", "b", "c"},
|
Groups: []string{"a", "b", "c"},
|
||||||
Extra: map[string][]string{},
|
|
||||||
},
|
},
|
||||||
DN: testUserSearchResultDNValue,
|
DN: testUserSearchResultDNValue,
|
||||||
|
ExtraRefreshAttributes: map[string]string{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "override UID parsing to work with microsoft style objectGUIDs",
|
|
||||||
username: testUpstreamUsername,
|
|
||||||
password: testUpstreamPassword,
|
|
||||||
providerConfig: providerConfig(func(p *ProviderConfig) {
|
|
||||||
p.UIDAttributeParsingOverrides = map[string]func(entry *ldap.Entry) (string, error){
|
|
||||||
"objectGUID": MicrosoftUUIDFromBinary("objectGUID"),
|
|
||||||
}
|
|
||||||
p.UserSearch.UIDAttribute = "objectGUID"
|
|
||||||
}),
|
|
||||||
searchMocks: func(conn *mockldapconn.MockConn) {
|
|
||||||
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
|
|
||||||
conn.EXPECT().Search(expectedUserSearch(func(r *ldap.SearchRequest) {
|
|
||||||
r.Attributes = []string{testUserSearchUsernameAttribute, "objectGUID"}
|
|
||||||
})).Return(&ldap.SearchResult{
|
|
||||||
Entries: []*ldap.Entry{
|
|
||||||
{
|
|
||||||
DN: testUserSearchResultDNValue,
|
|
||||||
Attributes: []*ldap.EntryAttribute{
|
|
||||||
ldap.NewEntryAttribute(testUserSearchUsernameAttribute, []string{testUserSearchResultUsernameAttributeValue}),
|
|
||||||
ldap.NewEntryAttribute("objectGUID", []string{"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x10\x11\x12\x13\x14\x15\x16"}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}, nil).Times(1)
|
|
||||||
conn.EXPECT().SearchWithPaging(expectedGroupSearch(nil), expectedGroupSearchPageSize).
|
|
||||||
Return(exampleGroupSearchResult, nil).Times(1)
|
|
||||||
conn.EXPECT().Close().Times(1)
|
|
||||||
},
|
|
||||||
bindEndUserMocks: func(conn *mockldapconn.MockConn) {
|
|
||||||
conn.EXPECT().Bind(testUserSearchResultDNValue, testUpstreamPassword).Times(1)
|
|
||||||
},
|
|
||||||
wantAuthResponse: expectedAuthResponse(func(r *user.DefaultInfo) {
|
|
||||||
r.UID = "04030201-0605-0807-0910-111213141516"
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "override UID parsing when the attribute name doesn't match what's returned does default parsing",
|
|
||||||
username: testUpstreamUsername,
|
|
||||||
password: testUpstreamPassword,
|
|
||||||
providerConfig: providerConfig(func(p *ProviderConfig) {
|
|
||||||
p.UIDAttributeParsingOverrides = map[string]func(entry *ldap.Entry) (string, error){
|
|
||||||
"objectGUID": MicrosoftUUIDFromBinary("objectGUID"),
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
searchMocks: func(conn *mockldapconn.MockConn) {
|
|
||||||
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
|
|
||||||
conn.EXPECT().Search(expectedUserSearch(nil)).Return(exampleUserSearchResult, nil).Times(1)
|
|
||||||
conn.EXPECT().SearchWithPaging(expectedGroupSearch(nil), expectedGroupSearchPageSize).
|
|
||||||
Return(exampleGroupSearchResult, nil).Times(1)
|
|
||||||
conn.EXPECT().Close().Times(1)
|
|
||||||
},
|
|
||||||
bindEndUserMocks: func(conn *mockldapconn.MockConn) {
|
|
||||||
conn.EXPECT().Bind(testUserSearchResultDNValue, testUpstreamPassword).Times(1)
|
|
||||||
},
|
|
||||||
wantAuthResponse: expectedAuthResponse(nil),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "override group parsing to create new group names",
|
|
||||||
username: testUpstreamUsername,
|
|
||||||
password: testUpstreamPassword,
|
|
||||||
providerConfig: providerConfig(func(p *ProviderConfig) {
|
|
||||||
p.GroupSearch.GroupNameAttribute = "sAMAccountName"
|
|
||||||
p.GroupAttributeParsingOverrides = map[string]func(*ldap.Entry) (string, error){
|
|
||||||
"sAMAccountName": GroupSAMAccountNameWithDomainSuffix,
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
searchMocks: func(conn *mockldapconn.MockConn) {
|
|
||||||
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
|
|
||||||
conn.EXPECT().Search(expectedUserSearch(nil)).Return(exampleUserSearchResult, nil).Times(1)
|
|
||||||
conn.EXPECT().SearchWithPaging(expectedGroupSearch(func(r *ldap.SearchRequest) {
|
|
||||||
r.Attributes = []string{"sAMAccountName"}
|
|
||||||
}), expectedGroupSearchPageSize).
|
|
||||||
Return(&ldap.SearchResult{
|
|
||||||
Entries: []*ldap.Entry{
|
|
||||||
{
|
|
||||||
DN: "CN=Mammals,OU=Users,OU=pinniped-ad,DC=activedirectory,DC=mycompany,DC=example,DC=com",
|
|
||||||
Attributes: []*ldap.EntryAttribute{
|
|
||||||
ldap.NewEntryAttribute("sAMAccountName", []string{"Mammals"}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
DN: "CN=Animals,OU=Users,OU=pinniped-ad,DC=activedirectory,DC=mycompany,DC=example,DC=com",
|
|
||||||
Attributes: []*ldap.EntryAttribute{
|
|
||||||
ldap.NewEntryAttribute("sAMAccountName", []string{"Animals"}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Referrals: []string{}, // note that we are not following referrals at this time
|
|
||||||
Controls: []ldap.Control{},
|
|
||||||
}, nil).Times(1)
|
|
||||||
conn.EXPECT().Close().Times(1)
|
|
||||||
},
|
|
||||||
bindEndUserMocks: func(conn *mockldapconn.MockConn) {
|
|
||||||
conn.EXPECT().Bind(testUserSearchResultDNValue, testUpstreamPassword).Times(1)
|
|
||||||
},
|
|
||||||
wantAuthResponse: expectedAuthResponse(func(r *user.DefaultInfo) {
|
|
||||||
r.Groups = []string{"Animals@activedirectory.mycompany.example.com", "Mammals@activedirectory.mycompany.example.com"}
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "requesting additional refresh related attributes",
|
name: "requesting additional refresh related attributes",
|
||||||
username: testUpstreamUsername,
|
username: testUpstreamUsername,
|
||||||
@ -646,8 +552,8 @@ func TestEndUserAuthentication(t *testing.T) {
|
|||||||
bindEndUserMocks: func(conn *mockldapconn.MockConn) {
|
bindEndUserMocks: func(conn *mockldapconn.MockConn) {
|
||||||
conn.EXPECT().Bind(testUserSearchResultDNValue, testUpstreamPassword).Times(1)
|
conn.EXPECT().Bind(testUserSearchResultDNValue, testUpstreamPassword).Times(1)
|
||||||
},
|
},
|
||||||
wantAuthResponse: expectedAuthResponse(func(r *user.DefaultInfo) {
|
wantAuthResponse: expectedAuthResponse(func(r *authenticators.Response) {
|
||||||
r.Extra = map[string][]string{"some-attribute-to-check-during-refresh": {"some-attribute-value"}}
|
r.ExtraRefreshAttributes = map[string]string{"some-attribute-to-check-during-refresh": "c29tZS1hdHRyaWJ1dGUtdmFsdWU"}
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -672,105 +578,6 @@ func TestEndUserAuthentication(t *testing.T) {
|
|||||||
},
|
},
|
||||||
wantError: "found 0 values for attribute \"some-attribute-to-check-during-refresh\" while searching for user \"some-upstream-username\", but expected 1 result",
|
wantError: "found 0 values for attribute \"some-attribute-to-check-during-refresh\" while searching for user \"some-upstream-username\", but expected 1 result",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "override group parsing when domain can't be determined from dn",
|
|
||||||
username: testUpstreamUsername,
|
|
||||||
password: testUpstreamPassword,
|
|
||||||
providerConfig: providerConfig(func(p *ProviderConfig) {
|
|
||||||
p.GroupSearch.GroupNameAttribute = "sAMAccountName"
|
|
||||||
p.GroupAttributeParsingOverrides = map[string]func(*ldap.Entry) (string, error){
|
|
||||||
"sAMAccountName": GroupSAMAccountNameWithDomainSuffix,
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
searchMocks: func(conn *mockldapconn.MockConn) {
|
|
||||||
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
|
|
||||||
conn.EXPECT().Search(expectedUserSearch(nil)).Return(exampleUserSearchResult, nil).Times(1)
|
|
||||||
conn.EXPECT().SearchWithPaging(expectedGroupSearch(func(r *ldap.SearchRequest) {
|
|
||||||
r.Attributes = []string{"sAMAccountName"}
|
|
||||||
}), expectedGroupSearchPageSize).
|
|
||||||
Return(&ldap.SearchResult{
|
|
||||||
Entries: []*ldap.Entry{
|
|
||||||
{
|
|
||||||
DN: "no-domain-components",
|
|
||||||
Attributes: []*ldap.EntryAttribute{
|
|
||||||
ldap.NewEntryAttribute("sAMAccountName", []string{"Mammals"}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
DN: "CN=Animals,OU=Users,OU=pinniped-ad,DC=activedirectory,DC=mycompany,DC=example,DC=com",
|
|
||||||
Attributes: []*ldap.EntryAttribute{
|
|
||||||
ldap.NewEntryAttribute("sAMAccountName", []string{"Animals"}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Referrals: []string{}, // note that we are not following referrals at this time
|
|
||||||
Controls: []ldap.Control{},
|
|
||||||
}, nil).Times(1)
|
|
||||||
conn.EXPECT().Close().Times(1)
|
|
||||||
},
|
|
||||||
wantError: "error finding groups for user some-upstream-user-dn: did not find domain components in group dn: no-domain-components",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "override group parsing when entry has multiple values for attribute",
|
|
||||||
username: testUpstreamUsername,
|
|
||||||
password: testUpstreamPassword,
|
|
||||||
providerConfig: providerConfig(func(p *ProviderConfig) {
|
|
||||||
p.GroupSearch.GroupNameAttribute = "sAMAccountName"
|
|
||||||
p.GroupAttributeParsingOverrides = map[string]func(*ldap.Entry) (string, error){
|
|
||||||
"sAMAccountName": GroupSAMAccountNameWithDomainSuffix,
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
searchMocks: func(conn *mockldapconn.MockConn) {
|
|
||||||
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
|
|
||||||
conn.EXPECT().Search(expectedUserSearch(nil)).Return(exampleUserSearchResult, nil).Times(1)
|
|
||||||
conn.EXPECT().SearchWithPaging(expectedGroupSearch(func(r *ldap.SearchRequest) {
|
|
||||||
r.Attributes = []string{"sAMAccountName"}
|
|
||||||
}), expectedGroupSearchPageSize).
|
|
||||||
Return(&ldap.SearchResult{
|
|
||||||
Entries: []*ldap.Entry{
|
|
||||||
{
|
|
||||||
DN: "no-domain-components",
|
|
||||||
Attributes: []*ldap.EntryAttribute{
|
|
||||||
ldap.NewEntryAttribute("sAMAccountName", []string{"Mammals", "Eukaryotes"}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Referrals: []string{}, // note that we are not following referrals at this time
|
|
||||||
Controls: []ldap.Control{},
|
|
||||||
}, nil).Times(1)
|
|
||||||
conn.EXPECT().Close().Times(1)
|
|
||||||
},
|
|
||||||
wantError: "error finding groups for user some-upstream-user-dn: found 2 values for attribute \"sAMAccountName\", but expected 1 result",
|
|
||||||
}, {
|
|
||||||
name: "override group parsing when entry has no values for attribute",
|
|
||||||
username: testUpstreamUsername,
|
|
||||||
password: testUpstreamPassword,
|
|
||||||
providerConfig: providerConfig(func(p *ProviderConfig) {
|
|
||||||
p.GroupSearch.GroupNameAttribute = "sAMAccountName"
|
|
||||||
p.GroupAttributeParsingOverrides = map[string]func(*ldap.Entry) (string, error){
|
|
||||||
"sAMAccountName": GroupSAMAccountNameWithDomainSuffix,
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
searchMocks: func(conn *mockldapconn.MockConn) {
|
|
||||||
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
|
|
||||||
conn.EXPECT().Search(expectedUserSearch(nil)).Return(exampleUserSearchResult, nil).Times(1)
|
|
||||||
conn.EXPECT().SearchWithPaging(expectedGroupSearch(func(r *ldap.SearchRequest) {
|
|
||||||
r.Attributes = []string{"sAMAccountName"}
|
|
||||||
}), expectedGroupSearchPageSize).
|
|
||||||
Return(&ldap.SearchResult{
|
|
||||||
Entries: []*ldap.Entry{
|
|
||||||
{
|
|
||||||
DN: "no-domain-components",
|
|
||||||
Attributes: []*ldap.EntryAttribute{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Referrals: []string{}, // note that we are not following referrals at this time
|
|
||||||
Controls: []ldap.Control{},
|
|
||||||
}, nil).Times(1)
|
|
||||||
conn.EXPECT().Close().Times(1)
|
|
||||||
},
|
|
||||||
wantError: "error finding groups for user some-upstream-user-dn: found 0 values for attribute \"sAMAccountName\", but expected 1 result",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "when dial fails",
|
name: "when dial fails",
|
||||||
username: testUpstreamUsername,
|
username: testUpstreamUsername,
|
||||||
@ -1309,6 +1116,7 @@ func TestUpstreamRefresh(t *testing.T) {
|
|||||||
{
|
{
|
||||||
Name: pwdLastSetAttribute,
|
Name: pwdLastSetAttribute,
|
||||||
Values: []string{"132801740800000000"},
|
Values: []string{"132801740800000000"},
|
||||||
|
ByteValues: [][]byte{[]byte("132801740800000000")},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1611,7 +1419,7 @@ func TestUpstreamRefresh(t *testing.T) {
|
|||||||
}, nil).Times(1)
|
}, nil).Times(1)
|
||||||
conn.EXPECT().Close().Times(1)
|
conn.EXPECT().Close().Times(1)
|
||||||
},
|
},
|
||||||
wantErr: "validation for attribute \"pwdLastSet\" failed during upstream refresh: value for attribute \"pwdLastSet\" has changed since initial value at login. Previous value: \"132801740800000000\", new value: \"132801740800000001\"",
|
wantErr: "validation for attribute \"pwdLastSet\" failed during upstream refresh: value for attribute \"pwdLastSet\" has changed since initial value at login",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1637,15 +1445,14 @@ func TestUpstreamRefresh(t *testing.T) {
|
|||||||
return conn, nil
|
return conn, nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
initialPwdLastSetEncoded := base64.RawURLEncoding.EncodeToString([]byte("132801740800000000"))
|
||||||
ldapProvider := New(*providerConfig)
|
ldapProvider := New(*providerConfig)
|
||||||
subject := "ldaps://ldap.example.com:8443?base=some-upstream-user-base-dn&sub=c29tZS11cHN0cmVhbS11aWQtdmFsdWU"
|
subject := "ldaps://ldap.example.com:8443?base=some-upstream-user-base-dn&sub=c29tZS11cHN0cmVhbS11aWQtdmFsdWU"
|
||||||
authTime := time.Date(2021, time.November, 1, 23, 43, 19, 0, time.UTC)
|
|
||||||
err := ldapProvider.PerformRefresh(context.Background(), provider.StoredRefreshAttributes{
|
err := ldapProvider.PerformRefresh(context.Background(), provider.StoredRefreshAttributes{
|
||||||
Username: testUserSearchResultUsernameAttributeValue,
|
Username: testUserSearchResultUsernameAttributeValue,
|
||||||
Subject: subject,
|
Subject: subject,
|
||||||
DN: testUserSearchResultDNValue,
|
DN: testUserSearchResultDNValue,
|
||||||
AuthTime: authTime,
|
AdditionalAttributes: map[string]string{pwdLastSetAttribute: initialPwdLastSetEncoded},
|
||||||
AdditionalAttributes: map[string][]string{pwdLastSetAttribute: {"132801740800000000"}},
|
|
||||||
})
|
})
|
||||||
if tt.wantErr != "" {
|
if tt.wantErr != "" {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
@ -1954,77 +1761,6 @@ func TestRealTLSDialing(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetMicrosoftFormattedUUID(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
binaryUUID []byte
|
|
||||||
wantString string
|
|
||||||
wantErr string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "happy path",
|
|
||||||
binaryUUID: []byte("\x01\x02\x03\x04\x05\x06\x07\x08\x09\x10\x11\x12\x13\x14\x15\x16"),
|
|
||||||
wantString: "04030201-0605-0807-0910-111213141516",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "not the right length",
|
|
||||||
binaryUUID: []byte("2\xf8\xb0\xaa\xb6V\xb1D\x8b(\xee"),
|
|
||||||
wantErr: "invalid UUID (got 11 bytes)",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
tt := test
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
actualUUIDString, err := microsoftUUIDFromBinary(tt.binaryUUID)
|
|
||||||
if tt.wantErr != "" {
|
|
||||||
require.EqualError(t, err, tt.wantErr)
|
|
||||||
} else {
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
require.Equal(t, tt.wantString, actualUUIDString)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetDomainFromDistinguishedName(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
distinguishedName string
|
|
||||||
wantDomain string
|
|
||||||
wantErr string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "happy path",
|
|
||||||
distinguishedName: "CN=Mammals,OU=Users,OU=pinniped-ad,DC=activedirectory,DC=mycompany,DC=example,DC=com",
|
|
||||||
wantDomain: "activedirectory.mycompany.example.com",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "lowercased happy path",
|
|
||||||
distinguishedName: "cn=Mammals,ou=Users,ou=pinniped-ad,dc=activedirectory,dc=mycompany,dc=example,dc=com",
|
|
||||||
wantDomain: "activedirectory.mycompany.example.com",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "no domain components",
|
|
||||||
distinguishedName: "not-a-dn",
|
|
||||||
wantErr: "did not find domain components in group dn: not-a-dn",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
tt := test
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
actualDomain, err := getDomainFromDistinguishedName(tt.distinguishedName)
|
|
||||||
if tt.wantErr != "" {
|
|
||||||
require.EqualError(t, err, tt.wantErr)
|
|
||||||
} else {
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
require.Equal(t, tt.wantDomain, actualDomain)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAttributeUnchangedSinceLogin(t *testing.T) {
|
func TestAttributeUnchangedSinceLogin(t *testing.T) {
|
||||||
initialVal := "some-attribute-value"
|
initialVal := "some-attribute-value"
|
||||||
changedVal := "some-different-attribute-value"
|
changedVal := "some-different-attribute-value"
|
||||||
@ -2043,6 +1779,7 @@ func TestAttributeUnchangedSinceLogin(t *testing.T) {
|
|||||||
{
|
{
|
||||||
Name: attributeName,
|
Name: attributeName,
|
||||||
Values: []string{initialVal},
|
Values: []string{initialVal},
|
||||||
|
ByteValues: [][]byte{[]byte(initialVal)},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -2055,10 +1792,11 @@ func TestAttributeUnchangedSinceLogin(t *testing.T) {
|
|||||||
{
|
{
|
||||||
Name: attributeName,
|
Name: attributeName,
|
||||||
Values: []string{changedVal},
|
Values: []string{changedVal},
|
||||||
|
ByteValues: [][]byte{[]byte(changedVal)},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wantErr: "value for attribute \"some-attribute-name\" has changed since initial value at login. Previous value: \"some-attribute-value\", new value: \"some-different-attribute-value\"",
|
wantErr: "value for attribute \"some-attribute-name\" has changed since initial value at login",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no value for attribute attribute",
|
name: "no value for attribute attribute",
|
||||||
@ -2085,141 +1823,8 @@ func TestAttributeUnchangedSinceLogin(t *testing.T) {
|
|||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
tt := test
|
tt := test
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
err := AttributeUnchangedSinceLogin(attributeName)(tt.entry, provider.StoredRefreshAttributes{AdditionalAttributes: map[string][]string{attributeName: {initialVal}}})
|
initialValRawEncoded := base64.RawURLEncoding.EncodeToString([]byte(initialVal))
|
||||||
if tt.wantErr != "" {
|
err := AttributeUnchangedSinceLogin(attributeName)(tt.entry, provider.StoredRefreshAttributes{AdditionalAttributes: map[string]string{attributeName: initialValRawEncoded}})
|
||||||
require.Error(t, err)
|
|
||||||
require.Equal(t, tt.wantErr, err.Error())
|
|
||||||
} else {
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestValidUserAccountControl(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
entry *ldap.Entry
|
|
||||||
wantErr string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "happy normal user",
|
|
||||||
entry: &ldap.Entry{
|
|
||||||
DN: "some-dn",
|
|
||||||
Attributes: []*ldap.EntryAttribute{
|
|
||||||
{
|
|
||||||
Name: "userAccountControl",
|
|
||||||
Values: []string{"512"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "happy user whose password doesn't expire",
|
|
||||||
entry: &ldap.Entry{
|
|
||||||
DN: "some-dn",
|
|
||||||
Attributes: []*ldap.EntryAttribute{
|
|
||||||
{
|
|
||||||
Name: "userAccountControl",
|
|
||||||
Values: []string{"65536"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "deactivated user",
|
|
||||||
entry: &ldap.Entry{
|
|
||||||
DN: "some-dn",
|
|
||||||
Attributes: []*ldap.EntryAttribute{
|
|
||||||
{
|
|
||||||
Name: "userAccountControl",
|
|
||||||
Values: []string{"514"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantErr: "user has been deactivated",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "non-integer result",
|
|
||||||
entry: &ldap.Entry{
|
|
||||||
DN: "some-dn",
|
|
||||||
Attributes: []*ldap.EntryAttribute{
|
|
||||||
{
|
|
||||||
Name: "userAccountControl",
|
|
||||||
Values: []string{"not-an-int"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantErr: "strconv.Atoi: parsing \"not-an-int\": invalid syntax",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
tt := test
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
err := ValidUserAccountControl(tt.entry, provider.StoredRefreshAttributes{})
|
|
||||||
|
|
||||||
if tt.wantErr != "" {
|
|
||||||
require.Error(t, err)
|
|
||||||
require.Equal(t, tt.wantErr, err.Error())
|
|
||||||
} else {
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestValidComputedUserAccountControl(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
entry *ldap.Entry
|
|
||||||
wantErr string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "happy normal user",
|
|
||||||
entry: &ldap.Entry{
|
|
||||||
DN: "some-dn",
|
|
||||||
Attributes: []*ldap.EntryAttribute{
|
|
||||||
{
|
|
||||||
Name: "msDS-User-Account-Control-Computed",
|
|
||||||
Values: []string{"0"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "locked user",
|
|
||||||
entry: &ldap.Entry{
|
|
||||||
DN: "some-dn",
|
|
||||||
Attributes: []*ldap.EntryAttribute{
|
|
||||||
{
|
|
||||||
Name: "msDS-User-Account-Control-Computed",
|
|
||||||
Values: []string{"16"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantErr: "user has been locked",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "non-integer result",
|
|
||||||
entry: &ldap.Entry{
|
|
||||||
DN: "some-dn",
|
|
||||||
Attributes: []*ldap.EntryAttribute{
|
|
||||||
{
|
|
||||||
Name: "msDS-User-Account-Control-Computed",
|
|
||||||
Values: []string{"not-an-int"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantErr: "strconv.Atoi: parsing \"not-an-int\": invalid syntax",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
tt := test
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
err := ValidComputedUserAccountControl(tt.entry, provider.StoredRefreshAttributes{})
|
|
||||||
|
|
||||||
if tt.wantErr != "" {
|
if tt.wantErr != "" {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Equal(t, tt.wantErr, err.Error())
|
require.Equal(t, tt.wantErr, err.Error())
|
||||||
|
@ -83,7 +83,9 @@ func TestLDAPSearch_Parallel(t *testing.T) {
|
|||||||
password: pinnyPassword,
|
password: pinnyPassword,
|
||||||
provider: upstreamldap.New(*providerConfig(nil)),
|
provider: upstreamldap.New(*providerConfig(nil)),
|
||||||
wantAuthResponse: &authenticators.Response{
|
wantAuthResponse: &authenticators.Response{
|
||||||
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{"ball-game-players", "seals"}}, DN: "cn=pinny,ou=users,dc=pinniped,dc=dev",
|
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{"ball-game-players", "seals"}},
|
||||||
|
DN: "cn=pinny,ou=users,dc=pinniped,dc=dev",
|
||||||
|
ExtraRefreshAttributes: map[string]string{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -95,7 +97,9 @@ func TestLDAPSearch_Parallel(t *testing.T) {
|
|||||||
p.ConnectionProtocol = upstreamldap.StartTLS
|
p.ConnectionProtocol = upstreamldap.StartTLS
|
||||||
})),
|
})),
|
||||||
wantAuthResponse: &authenticators.Response{
|
wantAuthResponse: &authenticators.Response{
|
||||||
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{"ball-game-players", "seals"}}, DN: "cn=pinny,ou=users,dc=pinniped,dc=dev",
|
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{"ball-game-players", "seals"}},
|
||||||
|
DN: "cn=pinny,ou=users,dc=pinniped,dc=dev",
|
||||||
|
ExtraRefreshAttributes: map[string]string{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -104,7 +108,9 @@ func TestLDAPSearch_Parallel(t *testing.T) {
|
|||||||
password: pinnyPassword,
|
password: pinnyPassword,
|
||||||
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.UserSearch.Base = "dc=pinniped,dc=dev" })),
|
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.UserSearch.Base = "dc=pinniped,dc=dev" })),
|
||||||
wantAuthResponse: &authenticators.Response{
|
wantAuthResponse: &authenticators.Response{
|
||||||
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{"ball-game-players", "seals"}}, DN: "cn=pinny,ou=users,dc=pinniped,dc=dev",
|
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{"ball-game-players", "seals"}},
|
||||||
|
DN: "cn=pinny,ou=users,dc=pinniped,dc=dev",
|
||||||
|
ExtraRefreshAttributes: map[string]string{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -113,7 +119,9 @@ func TestLDAPSearch_Parallel(t *testing.T) {
|
|||||||
password: pinnyPassword,
|
password: pinnyPassword,
|
||||||
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.UserSearch.Filter = "(cn={})" })),
|
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.UserSearch.Filter = "(cn={})" })),
|
||||||
wantAuthResponse: &authenticators.Response{
|
wantAuthResponse: &authenticators.Response{
|
||||||
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{"ball-game-players", "seals"}}, DN: "cn=pinny,ou=users,dc=pinniped,dc=dev",
|
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{"ball-game-players", "seals"}},
|
||||||
|
DN: "cn=pinny,ou=users,dc=pinniped,dc=dev",
|
||||||
|
ExtraRefreshAttributes: map[string]string{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -125,7 +133,9 @@ func TestLDAPSearch_Parallel(t *testing.T) {
|
|||||||
p.UserSearch.Filter = "cn={}"
|
p.UserSearch.Filter = "cn={}"
|
||||||
})),
|
})),
|
||||||
wantAuthResponse: &authenticators.Response{
|
wantAuthResponse: &authenticators.Response{
|
||||||
User: &user.DefaultInfo{Name: "cn=pinny,ou=users,dc=pinniped,dc=dev", UID: b64("1000"), Groups: []string{"ball-game-players", "seals"}}, DN: "cn=pinny,ou=users,dc=pinniped,dc=dev",
|
User: &user.DefaultInfo{Name: "cn=pinny,ou=users,dc=pinniped,dc=dev", UID: b64("1000"), Groups: []string{"ball-game-players", "seals"}},
|
||||||
|
DN: "cn=pinny,ou=users,dc=pinniped,dc=dev",
|
||||||
|
ExtraRefreshAttributes: map[string]string{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -136,7 +146,9 @@ func TestLDAPSearch_Parallel(t *testing.T) {
|
|||||||
p.UserSearch.Filter = "(|(cn={})(mail={}))"
|
p.UserSearch.Filter = "(|(cn={})(mail={}))"
|
||||||
})),
|
})),
|
||||||
wantAuthResponse: &authenticators.Response{
|
wantAuthResponse: &authenticators.Response{
|
||||||
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{"ball-game-players", "seals"}}, DN: "cn=pinny,ou=users,dc=pinniped,dc=dev",
|
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{"ball-game-players", "seals"}},
|
||||||
|
DN: "cn=pinny,ou=users,dc=pinniped,dc=dev",
|
||||||
|
ExtraRefreshAttributes: map[string]string{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -147,7 +159,9 @@ func TestLDAPSearch_Parallel(t *testing.T) {
|
|||||||
p.UserSearch.Filter = "(|(cn={})(mail={}))"
|
p.UserSearch.Filter = "(|(cn={})(mail={}))"
|
||||||
})),
|
})),
|
||||||
wantAuthResponse: &authenticators.Response{
|
wantAuthResponse: &authenticators.Response{
|
||||||
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{"ball-game-players", "seals"}}, DN: "cn=pinny,ou=users,dc=pinniped,dc=dev",
|
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{"ball-game-players", "seals"}},
|
||||||
|
DN: "cn=pinny,ou=users,dc=pinniped,dc=dev",
|
||||||
|
ExtraRefreshAttributes: map[string]string{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -156,7 +170,9 @@ func TestLDAPSearch_Parallel(t *testing.T) {
|
|||||||
password: pinnyPassword,
|
password: pinnyPassword,
|
||||||
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.UserSearch.UIDAttribute = "dn" })),
|
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.UserSearch.UIDAttribute = "dn" })),
|
||||||
wantAuthResponse: &authenticators.Response{
|
wantAuthResponse: &authenticators.Response{
|
||||||
User: &user.DefaultInfo{Name: "pinny", UID: b64("cn=pinny,ou=users,dc=pinniped,dc=dev"), Groups: []string{"ball-game-players", "seals"}}, DN: "cn=pinny,ou=users,dc=pinniped,dc=dev",
|
User: &user.DefaultInfo{Name: "pinny", UID: b64("cn=pinny,ou=users,dc=pinniped,dc=dev"), Groups: []string{"ball-game-players", "seals"}},
|
||||||
|
DN: "cn=pinny,ou=users,dc=pinniped,dc=dev",
|
||||||
|
ExtraRefreshAttributes: map[string]string{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -165,7 +181,9 @@ func TestLDAPSearch_Parallel(t *testing.T) {
|
|||||||
password: pinnyPassword,
|
password: pinnyPassword,
|
||||||
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.UserSearch.UIDAttribute = "sn" })),
|
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.UserSearch.UIDAttribute = "sn" })),
|
||||||
wantAuthResponse: &authenticators.Response{
|
wantAuthResponse: &authenticators.Response{
|
||||||
User: &user.DefaultInfo{Name: "pinny", UID: b64("Seal"), Groups: []string{"ball-game-players", "seals"}}, DN: "cn=pinny,ou=users,dc=pinniped,dc=dev",
|
User: &user.DefaultInfo{Name: "pinny", UID: b64("Seal"), Groups: []string{"ball-game-players", "seals"}},
|
||||||
|
DN: "cn=pinny,ou=users,dc=pinniped,dc=dev",
|
||||||
|
ExtraRefreshAttributes: map[string]string{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -174,7 +192,9 @@ func TestLDAPSearch_Parallel(t *testing.T) {
|
|||||||
password: pinnyPassword,
|
password: pinnyPassword,
|
||||||
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.UserSearch.UsernameAttribute = "sn" })),
|
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) { p.UserSearch.UsernameAttribute = "sn" })),
|
||||||
wantAuthResponse: &authenticators.Response{
|
wantAuthResponse: &authenticators.Response{
|
||||||
User: &user.DefaultInfo{Name: "Seal", UID: b64("1000"), Groups: []string{"ball-game-players", "seals"}}, DN: "cn=pinny,ou=users,dc=pinniped,dc=dev", // note that the final answer has case preserved from the entry
|
User: &user.DefaultInfo{Name: "Seal", UID: b64("1000"), Groups: []string{"ball-game-players", "seals"}},
|
||||||
|
DN: "cn=pinny,ou=users,dc=pinniped,dc=dev", // note that the final answer has case preserved from the entry
|
||||||
|
ExtraRefreshAttributes: map[string]string{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -187,7 +207,9 @@ func TestLDAPSearch_Parallel(t *testing.T) {
|
|||||||
p.UserSearch.UIDAttribute = "givenName"
|
p.UserSearch.UIDAttribute = "givenName"
|
||||||
})),
|
})),
|
||||||
wantAuthResponse: &authenticators.Response{
|
wantAuthResponse: &authenticators.Response{
|
||||||
User: &user.DefaultInfo{Name: "Pinny the 🦭", UID: b64("Pinny the 🦭"), Groups: []string{"ball-game-players", "seals"}}, DN: "cn=pinny,ou=users,dc=pinniped,dc=dev",
|
User: &user.DefaultInfo{Name: "Pinny the 🦭", UID: b64("Pinny the 🦭"), Groups: []string{"ball-game-players", "seals"}},
|
||||||
|
DN: "cn=pinny,ou=users,dc=pinniped,dc=dev",
|
||||||
|
ExtraRefreshAttributes: map[string]string{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -199,7 +221,9 @@ func TestLDAPSearch_Parallel(t *testing.T) {
|
|||||||
p.UserSearch.UsernameAttribute = "cn"
|
p.UserSearch.UsernameAttribute = "cn"
|
||||||
})),
|
})),
|
||||||
wantAuthResponse: &authenticators.Response{
|
wantAuthResponse: &authenticators.Response{
|
||||||
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{"ball-game-players", "seals"}}, DN: "cn=pinny,ou=users,dc=pinniped,dc=dev",
|
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{"ball-game-players", "seals"}},
|
||||||
|
DN: "cn=pinny,ou=users,dc=pinniped,dc=dev",
|
||||||
|
ExtraRefreshAttributes: map[string]string{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -220,7 +244,9 @@ func TestLDAPSearch_Parallel(t *testing.T) {
|
|||||||
p.GroupSearch.Base = ""
|
p.GroupSearch.Base = ""
|
||||||
})),
|
})),
|
||||||
wantAuthResponse: &authenticators.Response{
|
wantAuthResponse: &authenticators.Response{
|
||||||
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{}}, DN: "cn=pinny,ou=users,dc=pinniped,dc=dev",
|
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{}},
|
||||||
|
DN: "cn=pinny,ou=users,dc=pinniped,dc=dev",
|
||||||
|
ExtraRefreshAttributes: map[string]string{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -231,7 +257,9 @@ func TestLDAPSearch_Parallel(t *testing.T) {
|
|||||||
p.GroupSearch.Base = "ou=users,dc=pinniped,dc=dev" // there are no groups under this part of the tree
|
p.GroupSearch.Base = "ou=users,dc=pinniped,dc=dev" // there are no groups under this part of the tree
|
||||||
})),
|
})),
|
||||||
wantAuthResponse: &authenticators.Response{
|
wantAuthResponse: &authenticators.Response{
|
||||||
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{}}, DN: "cn=pinny,ou=users,dc=pinniped,dc=dev",
|
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{}},
|
||||||
|
DN: "cn=pinny,ou=users,dc=pinniped,dc=dev",
|
||||||
|
ExtraRefreshAttributes: map[string]string{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -245,7 +273,9 @@ func TestLDAPSearch_Parallel(t *testing.T) {
|
|||||||
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{
|
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{
|
||||||
"cn=ball-game-players,ou=beach-groups,ou=groups,dc=pinniped,dc=dev",
|
"cn=ball-game-players,ou=beach-groups,ou=groups,dc=pinniped,dc=dev",
|
||||||
"cn=seals,ou=groups,dc=pinniped,dc=dev",
|
"cn=seals,ou=groups,dc=pinniped,dc=dev",
|
||||||
}}, DN: "cn=pinny,ou=users,dc=pinniped,dc=dev",
|
}},
|
||||||
|
DN: "cn=pinny,ou=users,dc=pinniped,dc=dev",
|
||||||
|
ExtraRefreshAttributes: map[string]string{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -259,7 +289,9 @@ func TestLDAPSearch_Parallel(t *testing.T) {
|
|||||||
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{
|
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{
|
||||||
"cn=ball-game-players,ou=beach-groups,ou=groups,dc=pinniped,dc=dev",
|
"cn=ball-game-players,ou=beach-groups,ou=groups,dc=pinniped,dc=dev",
|
||||||
"cn=seals,ou=groups,dc=pinniped,dc=dev",
|
"cn=seals,ou=groups,dc=pinniped,dc=dev",
|
||||||
}}, DN: "cn=pinny,ou=users,dc=pinniped,dc=dev",
|
}},
|
||||||
|
DN: "cn=pinny,ou=users,dc=pinniped,dc=dev",
|
||||||
|
ExtraRefreshAttributes: map[string]string{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -270,7 +302,9 @@ func TestLDAPSearch_Parallel(t *testing.T) {
|
|||||||
p.GroupSearch.GroupNameAttribute = "objectClass" // silly example, but still a meaningful test
|
p.GroupSearch.GroupNameAttribute = "objectClass" // silly example, but still a meaningful test
|
||||||
})),
|
})),
|
||||||
wantAuthResponse: &authenticators.Response{
|
wantAuthResponse: &authenticators.Response{
|
||||||
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{"groupOfNames", "groupOfNames"}}, DN: "cn=pinny,ou=users,dc=pinniped,dc=dev",
|
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{"groupOfNames", "groupOfNames"}},
|
||||||
|
DN: "cn=pinny,ou=users,dc=pinniped,dc=dev",
|
||||||
|
ExtraRefreshAttributes: map[string]string{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -281,7 +315,9 @@ func TestLDAPSearch_Parallel(t *testing.T) {
|
|||||||
p.GroupSearch.Filter = "(&(&(objectClass=groupOfNames)(member={}))(cn=seals))"
|
p.GroupSearch.Filter = "(&(&(objectClass=groupOfNames)(member={}))(cn=seals))"
|
||||||
})),
|
})),
|
||||||
wantAuthResponse: &authenticators.Response{
|
wantAuthResponse: &authenticators.Response{
|
||||||
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{"seals"}}, DN: "cn=pinny,ou=users,dc=pinniped,dc=dev",
|
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{"seals"}},
|
||||||
|
DN: "cn=pinny,ou=users,dc=pinniped,dc=dev",
|
||||||
|
ExtraRefreshAttributes: map[string]string{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -292,7 +328,9 @@ func TestLDAPSearch_Parallel(t *testing.T) {
|
|||||||
p.GroupSearch.Filter = "foobar={}" // foobar is not a valid attribute name for this LDAP server's schema
|
p.GroupSearch.Filter = "foobar={}" // foobar is not a valid attribute name for this LDAP server's schema
|
||||||
})),
|
})),
|
||||||
wantAuthResponse: &authenticators.Response{
|
wantAuthResponse: &authenticators.Response{
|
||||||
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{}}, DN: "cn=pinny,ou=users,dc=pinniped,dc=dev",
|
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{}},
|
||||||
|
DN: "cn=pinny,ou=users,dc=pinniped,dc=dev",
|
||||||
|
ExtraRefreshAttributes: map[string]string{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -673,6 +711,7 @@ func TestSimultaneousLDAPRequestsOnSingleProvider(t *testing.T) {
|
|||||||
assert.Equal(t, &authenticators.Response{
|
assert.Equal(t, &authenticators.Response{
|
||||||
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{"ball-game-players", "seals"}},
|
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{"ball-game-players", "seals"}},
|
||||||
DN: "cn=pinny,ou=users,dc=pinniped,dc=dev",
|
DN: "cn=pinny,ou=users,dc=pinniped,dc=dev",
|
||||||
|
ExtraRefreshAttributes: map[string]string{},
|
||||||
}, result.response)
|
}, result.response)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,8 +23,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.pinniped.dev/internal/crypto/ptls"
|
|
||||||
|
|
||||||
coreosoidc "github.com/coreos/go-oidc/v3/oidc"
|
coreosoidc "github.com/coreos/go-oidc/v3/oidc"
|
||||||
"github.com/go-ldap/ldap/v3"
|
"github.com/go-ldap/ldap/v3"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -37,6 +35,7 @@ import (
|
|||||||
configv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/config/v1alpha1"
|
configv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/config/v1alpha1"
|
||||||
idpv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/idp/v1alpha1"
|
idpv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/idp/v1alpha1"
|
||||||
"go.pinniped.dev/internal/certauthority"
|
"go.pinniped.dev/internal/certauthority"
|
||||||
|
"go.pinniped.dev/internal/crypto/ptls"
|
||||||
"go.pinniped.dev/internal/oidc"
|
"go.pinniped.dev/internal/oidc"
|
||||||
"go.pinniped.dev/internal/psession"
|
"go.pinniped.dev/internal/psession"
|
||||||
"go.pinniped.dev/internal/testutil"
|
"go.pinniped.dev/internal/testutil"
|
||||||
|
Loading…
Reference in New Issue
Block a user