Merge branch 'main' into ldap_docs
This commit is contained in:
commit
d7d8630e08
@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/ory/fosite/token/jwt"
|
"github.com/ory/fosite/token/jwt"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
|
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||||
|
|
||||||
"go.pinniped.dev/internal/httputil/httperr"
|
"go.pinniped.dev/internal/httputil/httperr"
|
||||||
"go.pinniped.dev/internal/httputil/securityheader"
|
"go.pinniped.dev/internal/httputil/securityheader"
|
||||||
@ -108,11 +109,10 @@ func handleAuthRequestForLDAPUpstream(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
subject := fmt.Sprintf("%s?%s=%s", ldapUpstream.GetURL(), oidc.IDTokenSubjectClaim, authenticateResponse.User.GetUID())
|
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
openIDSession := &openid.DefaultSession{
|
openIDSession := &openid.DefaultSession{
|
||||||
Claims: &jwt.IDTokenClaims{
|
Claims: &jwt.IDTokenClaims{
|
||||||
Subject: subject,
|
Subject: downstreamSubjectFromUpstreamLDAP(ldapUpstream, authenticateResponse),
|
||||||
RequestedAt: now,
|
RequestedAt: now,
|
||||||
AuthTime: now,
|
AuthTime: now,
|
||||||
},
|
},
|
||||||
@ -359,3 +359,11 @@ func addCSRFSetCookieHeader(w http.ResponseWriter, csrfValue csrftoken.CSRFToken
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func downstreamSubjectFromUpstreamLDAP(ldapUpstream provider.UpstreamLDAPIdentityProviderI, authenticateResponse *authenticator.Response) string {
|
||||||
|
ldapURL := *ldapUpstream.GetURL()
|
||||||
|
q := ldapURL.Query()
|
||||||
|
q.Set(oidc.IDTokenSubjectClaim, authenticateResponse.User.GetUID())
|
||||||
|
ldapURL.RawQuery = q.Encode()
|
||||||
|
return ldapURL.String()
|
||||||
|
}
|
||||||
|
@ -44,7 +44,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
downstreamPKCEChallengeMethod = "S256"
|
downstreamPKCEChallengeMethod = "S256"
|
||||||
happyState = "8b-state"
|
happyState = "8b-state"
|
||||||
downstreamClientID = "pinniped-cli"
|
downstreamClientID = "pinniped-cli"
|
||||||
upstreamLDAPURL = "ldaps://some-ldap-host:123"
|
upstreamLDAPURL = "ldaps://some-ldap-host:123?base=ou%3Dusers%2Cdc%3Dpinniped%2Cdc%3Ddev"
|
||||||
htmlContentType = "text/html; charset=utf-8"
|
htmlContentType = "text/html; charset=utf-8"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -158,9 +158,12 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
happyLDAPUID := "some-ldap-uid"
|
happyLDAPUID := "some-ldap-uid"
|
||||||
happyLDAPGroups := []string{"group1", "group2", "group3"}
|
happyLDAPGroups := []string{"group1", "group2", "group3"}
|
||||||
|
|
||||||
|
parsedUpstreamLDAPURL, err := url.Parse(upstreamLDAPURL)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
upstreamLDAPIdentityProvider := oidctestutil.TestUpstreamLDAPIdentityProvider{
|
upstreamLDAPIdentityProvider := oidctestutil.TestUpstreamLDAPIdentityProvider{
|
||||||
Name: "some-ldap-idp",
|
Name: "some-ldap-idp",
|
||||||
URL: upstreamLDAPURL,
|
URL: parsedUpstreamLDAPURL,
|
||||||
AuthenticateFunc: func(ctx context.Context, username, password string) (*authenticator.Response, bool, error) {
|
AuthenticateFunc: func(ctx context.Context, username, password string) (*authenticator.Response, bool, error) {
|
||||||
if username == "" || password == "" {
|
if username == "" || password == "" {
|
||||||
return nil, false, fmt.Errorf("should not have passed empty username or password to the authenticator")
|
return nil, false, fmt.Errorf("should not have passed empty username or password to the authenticator")
|
||||||
@ -384,7 +387,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
wantContentType: htmlContentType,
|
wantContentType: htmlContentType,
|
||||||
wantRedirectLocationRegexp: happyAuthcodeDownstreamRedirectLocationRegexp,
|
wantRedirectLocationRegexp: happyAuthcodeDownstreamRedirectLocationRegexp,
|
||||||
wantBodyStringWithLocationInHref: false,
|
wantBodyStringWithLocationInHref: false,
|
||||||
wantDownstreamIDTokenSubject: upstreamLDAPURL + "?sub=" + happyLDAPUID,
|
wantDownstreamIDTokenSubject: upstreamLDAPURL + "&sub=" + happyLDAPUID,
|
||||||
wantDownstreamIDTokenUsername: happyLDAPUsernameFromAuthenticator,
|
wantDownstreamIDTokenUsername: happyLDAPUsernameFromAuthenticator,
|
||||||
wantDownstreamIDTokenGroups: happyLDAPGroups,
|
wantDownstreamIDTokenGroups: happyLDAPGroups,
|
||||||
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
||||||
@ -443,7 +446,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
wantContentType: htmlContentType,
|
wantContentType: htmlContentType,
|
||||||
wantRedirectLocationRegexp: happyAuthcodeDownstreamRedirectLocationRegexp,
|
wantRedirectLocationRegexp: happyAuthcodeDownstreamRedirectLocationRegexp,
|
||||||
wantBodyStringWithLocationInHref: false,
|
wantBodyStringWithLocationInHref: false,
|
||||||
wantDownstreamIDTokenSubject: upstreamLDAPURL + "?sub=" + happyLDAPUID,
|
wantDownstreamIDTokenSubject: upstreamLDAPURL + "&sub=" + happyLDAPUID,
|
||||||
wantDownstreamIDTokenUsername: happyLDAPUsernameFromAuthenticator,
|
wantDownstreamIDTokenUsername: happyLDAPUsernameFromAuthenticator,
|
||||||
wantDownstreamIDTokenGroups: happyLDAPGroups,
|
wantDownstreamIDTokenGroups: happyLDAPGroups,
|
||||||
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
||||||
@ -525,7 +528,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
wantContentType: htmlContentType,
|
wantContentType: htmlContentType,
|
||||||
wantRedirectLocationRegexp: downstreamRedirectURIWithDifferentPort + `\?code=([^&]+)&scope=openid&state=` + happyState,
|
wantRedirectLocationRegexp: downstreamRedirectURIWithDifferentPort + `\?code=([^&]+)&scope=openid&state=` + happyState,
|
||||||
wantBodyStringWithLocationInHref: false,
|
wantBodyStringWithLocationInHref: false,
|
||||||
wantDownstreamIDTokenSubject: upstreamLDAPURL + "?sub=" + happyLDAPUID,
|
wantDownstreamIDTokenSubject: upstreamLDAPURL + "&sub=" + happyLDAPUID,
|
||||||
wantDownstreamIDTokenUsername: happyLDAPUsernameFromAuthenticator,
|
wantDownstreamIDTokenUsername: happyLDAPUsernameFromAuthenticator,
|
||||||
wantDownstreamIDTokenGroups: happyLDAPGroups,
|
wantDownstreamIDTokenGroups: happyLDAPGroups,
|
||||||
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
||||||
@ -941,7 +944,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
wantContentType: htmlContentType,
|
wantContentType: htmlContentType,
|
||||||
wantRedirectLocationRegexp: downstreamRedirectURI + `\?code=([^&]+)&scope=&state=` + happyState, // no scopes granted
|
wantRedirectLocationRegexp: downstreamRedirectURI + `\?code=([^&]+)&scope=&state=` + happyState, // no scopes granted
|
||||||
wantBodyStringWithLocationInHref: false,
|
wantBodyStringWithLocationInHref: false,
|
||||||
wantDownstreamIDTokenSubject: upstreamLDAPURL + "?sub=" + happyLDAPUID,
|
wantDownstreamIDTokenSubject: upstreamLDAPURL + "&sub=" + happyLDAPUID,
|
||||||
wantDownstreamIDTokenUsername: happyLDAPUsernameFromAuthenticator,
|
wantDownstreamIDTokenUsername: happyLDAPUsernameFromAuthenticator,
|
||||||
wantDownstreamIDTokenGroups: happyLDAPGroups,
|
wantDownstreamIDTokenGroups: happyLDAPGroups,
|
||||||
wantDownstreamRequestedScopes: []string{"email"}, // only email was requested
|
wantDownstreamRequestedScopes: []string{"email"}, // only email was requested
|
||||||
|
@ -228,7 +228,7 @@ func getSubjectAndUsernameFromUpstreamIDToken(
|
|||||||
return "", "", httperr.New(http.StatusUnprocessableEntity, "subject claim in upstream ID token has invalid format")
|
return "", "", httperr.New(http.StatusUnprocessableEntity, "subject claim in upstream ID token has invalid format")
|
||||||
}
|
}
|
||||||
|
|
||||||
subject := fmt.Sprintf("%s?%s=%s", upstreamIssuerAsString, oidc.IDTokenSubjectClaim, upstreamSubject)
|
subject := downstreamSubjectFromUpstreamOIDC(upstreamIssuerAsString, upstreamSubject)
|
||||||
|
|
||||||
usernameClaimName := upstreamIDPConfig.GetUsernameClaim()
|
usernameClaimName := upstreamIDPConfig.GetUsernameClaim()
|
||||||
if usernameClaimName == "" {
|
if usernameClaimName == "" {
|
||||||
@ -282,6 +282,10 @@ func getSubjectAndUsernameFromUpstreamIDToken(
|
|||||||
return subject, username, nil
|
return subject, username, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func downstreamSubjectFromUpstreamOIDC(upstreamIssuerAsString string, upstreamSubject string) string {
|
||||||
|
return fmt.Sprintf("%s?%s=%s", upstreamIssuerAsString, oidc.IDTokenSubjectClaim, url.QueryEscape(upstreamSubject))
|
||||||
|
}
|
||||||
|
|
||||||
func getGroupsFromUpstreamIDToken(
|
func getGroupsFromUpstreamIDToken(
|
||||||
upstreamIDPConfig provider.UpstreamOIDCIdentityProviderI,
|
upstreamIDPConfig provider.UpstreamOIDCIdentityProviderI,
|
||||||
idTokenClaims map[string]interface{},
|
idTokenClaims map[string]interface{},
|
||||||
|
@ -28,9 +28,10 @@ import (
|
|||||||
const (
|
const (
|
||||||
happyUpstreamIDPName = "upstream-idp-name"
|
happyUpstreamIDPName = "upstream-idp-name"
|
||||||
|
|
||||||
upstreamIssuer = "https://my-upstream-issuer.com"
|
upstreamIssuer = "https://my-upstream-issuer.com"
|
||||||
upstreamSubject = "abc123-some-guid"
|
upstreamSubject = "abc123-some guid" // has a space character which should get escaped in URL
|
||||||
upstreamUsername = "test-pinniped-username"
|
queryEscapedUpstreamSubject = "abc123-some+guid"
|
||||||
|
upstreamUsername = "test-pinniped-username"
|
||||||
|
|
||||||
upstreamUsernameClaim = "the-user-claim"
|
upstreamUsernameClaim = "the-user-claim"
|
||||||
upstreamGroupsClaim = "the-groups-claim"
|
upstreamGroupsClaim = "the-groups-claim"
|
||||||
@ -141,7 +142,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp,
|
wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp,
|
||||||
wantBody: "",
|
wantBody: "",
|
||||||
wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + upstreamSubject,
|
wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + queryEscapedUpstreamSubject,
|
||||||
wantDownstreamIDTokenUsername: upstreamUsername,
|
wantDownstreamIDTokenUsername: upstreamUsername,
|
||||||
wantDownstreamIDTokenGroups: upstreamGroupMembership,
|
wantDownstreamIDTokenGroups: upstreamGroupMembership,
|
||||||
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
||||||
@ -160,8 +161,8 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp,
|
wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp,
|
||||||
wantBody: "",
|
wantBody: "",
|
||||||
wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + upstreamSubject,
|
wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + queryEscapedUpstreamSubject,
|
||||||
wantDownstreamIDTokenUsername: upstreamIssuer + "?sub=" + upstreamSubject,
|
wantDownstreamIDTokenUsername: upstreamIssuer + "?sub=" + queryEscapedUpstreamSubject,
|
||||||
wantDownstreamIDTokenGroups: []string{},
|
wantDownstreamIDTokenGroups: []string{},
|
||||||
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
||||||
wantDownstreamGrantedScopes: happyDownstreamScopesGranted,
|
wantDownstreamGrantedScopes: happyDownstreamScopesGranted,
|
||||||
@ -180,7 +181,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp,
|
wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp,
|
||||||
wantBody: "",
|
wantBody: "",
|
||||||
wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + upstreamSubject,
|
wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + queryEscapedUpstreamSubject,
|
||||||
wantDownstreamIDTokenUsername: "joe@whitehouse.gov",
|
wantDownstreamIDTokenUsername: "joe@whitehouse.gov",
|
||||||
wantDownstreamIDTokenGroups: upstreamGroupMembership,
|
wantDownstreamIDTokenGroups: upstreamGroupMembership,
|
||||||
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
||||||
@ -201,7 +202,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp,
|
wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp,
|
||||||
wantBody: "",
|
wantBody: "",
|
||||||
wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + upstreamSubject,
|
wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + queryEscapedUpstreamSubject,
|
||||||
wantDownstreamIDTokenUsername: "joe@whitehouse.gov",
|
wantDownstreamIDTokenUsername: "joe@whitehouse.gov",
|
||||||
wantDownstreamIDTokenGroups: upstreamGroupMembership,
|
wantDownstreamIDTokenGroups: upstreamGroupMembership,
|
||||||
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
||||||
@ -223,7 +224,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
wantStatus: http.StatusFound, // succeed despite `email_verified=false` because we're not using the email claim for anything
|
wantStatus: http.StatusFound, // succeed despite `email_verified=false` because we're not using the email claim for anything
|
||||||
wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp,
|
wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp,
|
||||||
wantBody: "",
|
wantBody: "",
|
||||||
wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + upstreamSubject,
|
wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + queryEscapedUpstreamSubject,
|
||||||
wantDownstreamIDTokenUsername: "joe",
|
wantDownstreamIDTokenUsername: "joe",
|
||||||
wantDownstreamIDTokenGroups: upstreamGroupMembership,
|
wantDownstreamIDTokenGroups: upstreamGroupMembership,
|
||||||
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
||||||
@ -268,7 +269,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp,
|
wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp,
|
||||||
wantBody: "",
|
wantBody: "",
|
||||||
wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + upstreamSubject,
|
wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + queryEscapedUpstreamSubject,
|
||||||
wantDownstreamIDTokenUsername: upstreamSubject,
|
wantDownstreamIDTokenUsername: upstreamSubject,
|
||||||
wantDownstreamIDTokenGroups: upstreamGroupMembership,
|
wantDownstreamIDTokenGroups: upstreamGroupMembership,
|
||||||
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
||||||
@ -287,7 +288,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp,
|
wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp,
|
||||||
wantBody: "",
|
wantBody: "",
|
||||||
wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + upstreamSubject,
|
wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + queryEscapedUpstreamSubject,
|
||||||
wantDownstreamIDTokenUsername: upstreamUsername,
|
wantDownstreamIDTokenUsername: upstreamUsername,
|
||||||
wantDownstreamIDTokenGroups: []string{"notAnArrayGroup1 notAnArrayGroup2"},
|
wantDownstreamIDTokenGroups: []string{"notAnArrayGroup1 notAnArrayGroup2"},
|
||||||
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
||||||
@ -306,7 +307,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp,
|
wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp,
|
||||||
wantBody: "",
|
wantBody: "",
|
||||||
wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + upstreamSubject,
|
wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + queryEscapedUpstreamSubject,
|
||||||
wantDownstreamIDTokenUsername: upstreamUsername,
|
wantDownstreamIDTokenUsername: upstreamUsername,
|
||||||
wantDownstreamIDTokenGroups: []string{"group1", "group2"},
|
wantDownstreamIDTokenGroups: []string{"group1", "group2"},
|
||||||
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
||||||
@ -445,7 +446,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantRedirectLocationRegexp: downstreamRedirectURI + `\?code=([^&]+)&scope=&state=` + happyDownstreamState,
|
wantRedirectLocationRegexp: downstreamRedirectURI + `\?code=([^&]+)&scope=&state=` + happyDownstreamState,
|
||||||
wantDownstreamIDTokenUsername: upstreamUsername,
|
wantDownstreamIDTokenUsername: upstreamUsername,
|
||||||
wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + upstreamSubject,
|
wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + queryEscapedUpstreamSubject,
|
||||||
wantDownstreamRequestedScopes: []string{"profile", "email"},
|
wantDownstreamRequestedScopes: []string{"profile", "email"},
|
||||||
wantDownstreamIDTokenGroups: upstreamGroupMembership,
|
wantDownstreamIDTokenGroups: upstreamGroupMembership,
|
||||||
wantDownstreamNonce: downstreamNonce,
|
wantDownstreamNonce: downstreamNonce,
|
||||||
@ -467,7 +468,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantRedirectLocationRegexp: downstreamRedirectURI + `\?code=([^&]+)&scope=openid\+offline_access&state=` + happyDownstreamState,
|
wantRedirectLocationRegexp: downstreamRedirectURI + `\?code=([^&]+)&scope=openid\+offline_access&state=` + happyDownstreamState,
|
||||||
wantDownstreamIDTokenUsername: upstreamUsername,
|
wantDownstreamIDTokenUsername: upstreamUsername,
|
||||||
wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + upstreamSubject,
|
wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + queryEscapedUpstreamSubject,
|
||||||
wantDownstreamRequestedScopes: []string{"openid", "offline_access"},
|
wantDownstreamRequestedScopes: []string{"openid", "offline_access"},
|
||||||
wantDownstreamGrantedScopes: []string{"openid", "offline_access"},
|
wantDownstreamGrantedScopes: []string{"openid", "offline_access"},
|
||||||
wantDownstreamIDTokenGroups: upstreamGroupMembership,
|
wantDownstreamIDTokenGroups: upstreamGroupMembership,
|
||||||
@ -548,7 +549,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
wantStatus: http.StatusFound,
|
wantStatus: http.StatusFound,
|
||||||
wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp,
|
wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp,
|
||||||
wantBody: "",
|
wantBody: "",
|
||||||
wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + upstreamSubject,
|
wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + queryEscapedUpstreamSubject,
|
||||||
wantDownstreamIDTokenUsername: upstreamUsername,
|
wantDownstreamIDTokenUsername: upstreamUsername,
|
||||||
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
wantDownstreamRequestedScopes: happyDownstreamScopesRequested,
|
||||||
wantDownstreamGrantedScopes: happyDownstreamScopesGranted,
|
wantDownstreamGrantedScopes: happyDownstreamScopesGranted,
|
||||||
|
@ -56,7 +56,7 @@ type UpstreamLDAPIdentityProviderI interface {
|
|||||||
// Return a URL which uniquely identifies this LDAP provider, e.g. "ldaps://host.example.com:1234".
|
// Return a URL which uniquely identifies this LDAP provider, e.g. "ldaps://host.example.com:1234".
|
||||||
// This URL is not used for connecting to the provider, but rather is used for creating a globally unique user
|
// This URL is not used for connecting to the provider, but rather is used for creating a globally unique user
|
||||||
// identifier by being combined with the user's UID, since user UIDs are only unique within one provider.
|
// identifier by being combined with the user's UID, since user UIDs are only unique within one provider.
|
||||||
GetURL() string
|
GetURL() *url.URL
|
||||||
|
|
||||||
// A method for performing user authentication against the upstream LDAP provider.
|
// A method for performing user authentication against the upstream LDAP provider.
|
||||||
authenticators.UserAuthenticator
|
authenticators.UserAuthenticator
|
||||||
|
@ -51,10 +51,12 @@ type ExchangeAuthcodeAndValidateTokenArgs struct {
|
|||||||
|
|
||||||
type TestUpstreamLDAPIdentityProvider struct {
|
type TestUpstreamLDAPIdentityProvider struct {
|
||||||
Name string
|
Name string
|
||||||
URL string
|
URL *url.URL
|
||||||
AuthenticateFunc func(ctx context.Context, username, password string) (*authenticator.Response, bool, error)
|
AuthenticateFunc func(ctx context.Context, username, password string) (*authenticator.Response, bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ provider.UpstreamLDAPIdentityProviderI = &TestUpstreamLDAPIdentityProvider{}
|
||||||
|
|
||||||
func (u *TestUpstreamLDAPIdentityProvider) GetName() string {
|
func (u *TestUpstreamLDAPIdentityProvider) GetName() string {
|
||||||
return u.Name
|
return u.Name
|
||||||
}
|
}
|
||||||
@ -63,7 +65,7 @@ func (u *TestUpstreamLDAPIdentityProvider) AuthenticateUser(ctx context.Context,
|
|||||||
return u.AuthenticateFunc(ctx, username, password)
|
return u.AuthenticateFunc(ctx, username, password)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *TestUpstreamLDAPIdentityProvider) GetURL() string {
|
func (u *TestUpstreamLDAPIdentityProvider) GetURL() *url.URL {
|
||||||
return u.URL
|
return u.URL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,20 +8,23 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"net/url"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"k8s.io/utils/trace"
|
|
||||||
|
|
||||||
"github.com/go-ldap/ldap/v3"
|
"github.com/go-ldap/ldap/v3"
|
||||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||||
"k8s.io/apiserver/pkg/authentication/user"
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
|
"k8s.io/utils/trace"
|
||||||
|
|
||||||
|
"go.pinniped.dev/internal/authenticators"
|
||||||
"go.pinniped.dev/internal/endpointaddr"
|
"go.pinniped.dev/internal/endpointaddr"
|
||||||
|
"go.pinniped.dev/internal/oidc/provider"
|
||||||
"go.pinniped.dev/internal/plog"
|
"go.pinniped.dev/internal/plog"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -138,6 +141,9 @@ type Provider struct {
|
|||||||
c ProviderConfig
|
c ProviderConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ provider.UpstreamLDAPIdentityProviderI = &Provider{}
|
||||||
|
var _ authenticators.UserAuthenticator = &Provider{}
|
||||||
|
|
||||||
// Create a Provider. The config is not a pointer to ensure that a copy of the config is created,
|
// Create a Provider. The config is not a pointer to ensure that a copy of the config is created,
|
||||||
// making the resulting Provider use an effectively read-only configuration.
|
// making the resulting Provider use an effectively read-only configuration.
|
||||||
func New(config ProviderConfig) *Provider {
|
func New(config ProviderConfig) *Provider {
|
||||||
@ -249,11 +255,15 @@ func (p *Provider) GetName() string {
|
|||||||
return p.c.Name
|
return p.c.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return a URL which uniquely identifies this LDAP provider, e.g. "ldaps://host.example.com:1234".
|
// Return a URL which uniquely identifies this LDAP provider, e.g. "ldaps://host.example.com:1234?base=user-search-base".
|
||||||
// This URL is not used for connecting to the provider, but rather is used for creating a globally unique user
|
// This URL is not used for connecting to the provider, but rather is used for creating a globally unique user
|
||||||
// identifier by being combined with the user's UID, since user UIDs are only unique within one provider.
|
// identifier by being combined with the user's UID, since user UIDs are only unique within one provider.
|
||||||
func (p *Provider) GetURL() string {
|
func (p *Provider) GetURL() *url.URL {
|
||||||
return fmt.Sprintf("%s://%s", ldapsScheme, p.c.Host)
|
u := &url.URL{Scheme: ldapsScheme, Host: p.c.Host}
|
||||||
|
q := u.Query()
|
||||||
|
q.Set("base", p.c.UserSearch.Base)
|
||||||
|
u.RawQuery = q.Encode()
|
||||||
|
return u
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestConnection provides a method for testing the connection and bind settings. It performs a dial and bind
|
// TestConnection provides a method for testing the connection and bind settings. It performs a dial and bind
|
||||||
@ -408,7 +418,9 @@ func (p *Provider) searchAndBindUser(conn Conn, username string, bindFunc func(c
|
|||||||
return "", "", nil, err
|
return "", "", nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
mappedUID, err := p.getSearchResultAttributeValue(p.c.UserSearch.UIDAttribute, userEntry, username)
|
// We would like to support binary typed attributes for UIDs, so always read them as binary and encode them,
|
||||||
|
// even when the attribute may not be binary.
|
||||||
|
mappedUID, err := p.getSearchResultAttributeRawValueEncoded(p.c.UserSearch.UIDAttribute, userEntry, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", nil, err
|
return "", "", nil, err
|
||||||
}
|
}
|
||||||
@ -517,6 +529,30 @@ func (p *Provider) escapeUsernameForSearchFilter(username string) string {
|
|||||||
return ldap.EscapeFilter(username)
|
return ldap.EscapeFilter(username)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the (potentially) binary data of the attribute's value, base64 URL encoded.
|
||||||
|
func (p *Provider) getSearchResultAttributeRawValueEncoded(attributeName string, entry *ldap.Entry, username string) (string, error) {
|
||||||
|
if attributeName == distinguishedNameAttributeName {
|
||||||
|
return base64.RawURLEncoding.EncodeToString([]byte(entry.DN)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
attributeValues := entry.GetRawAttributeValues(attributeName)
|
||||||
|
|
||||||
|
if len(attributeValues) != 1 {
|
||||||
|
return "", fmt.Errorf(`found %d values for attribute "%s" while searching for user "%s", but expected 1 result`,
|
||||||
|
len(attributeValues), attributeName, username,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
attributeValue := attributeValues[0]
|
||||||
|
if len(attributeValue) == 0 {
|
||||||
|
return "", fmt.Errorf(`found empty value for attribute "%s" while searching for user "%s", but expected value to be non-empty`,
|
||||||
|
attributeName, username,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return base64.RawURLEncoding.EncodeToString(attributeValue), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Provider) getSearchResultAttributeValue(attributeName string, entry *ldap.Entry, username string) (string, error) {
|
func (p *Provider) getSearchResultAttributeValue(attributeName string, entry *ldap.Entry, username string) (string, error) {
|
||||||
if attributeName == distinguishedNameAttributeName {
|
if attributeName == distinguishedNameAttributeName {
|
||||||
return entry.DN, nil
|
return entry.DN, nil
|
||||||
|
@ -6,6 +6,7 @@ package upstreamldap
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
@ -153,7 +154,7 @@ func TestEndUserAuthentication(t *testing.T) {
|
|||||||
expectedAuthResponse := func(editFunc func(r *user.DefaultInfo)) *authenticator.Response {
|
expectedAuthResponse := func(editFunc func(r *user.DefaultInfo)) *authenticator.Response {
|
||||||
u := &user.DefaultInfo{
|
u := &user.DefaultInfo{
|
||||||
Name: testUserSearchResultUsernameAttributeValue,
|
Name: testUserSearchResultUsernameAttributeValue,
|
||||||
UID: testUserSearchResultUIDAttributeValue,
|
UID: base64.RawURLEncoding.EncodeToString([]byte(testUserSearchResultUIDAttributeValue)),
|
||||||
Groups: []string{testGroupSearchResultGroupNameAttributeValue1, testGroupSearchResultGroupNameAttributeValue2},
|
Groups: []string{testGroupSearchResultGroupNameAttributeValue1, testGroupSearchResultGroupNameAttributeValue2},
|
||||||
}
|
}
|
||||||
if editFunc != nil {
|
if editFunc != nil {
|
||||||
@ -311,7 +312,7 @@ func TestEndUserAuthentication(t *testing.T) {
|
|||||||
conn.EXPECT().Bind(testUserSearchResultDNValue, testUpstreamPassword).Times(1)
|
conn.EXPECT().Bind(testUserSearchResultDNValue, testUpstreamPassword).Times(1)
|
||||||
},
|
},
|
||||||
wantAuthResponse: expectedAuthResponse(func(r *user.DefaultInfo) {
|
wantAuthResponse: expectedAuthResponse(func(r *user.DefaultInfo) {
|
||||||
r.UID = testUserSearchResultDNValue
|
r.UID = base64.RawURLEncoding.EncodeToString([]byte(testUserSearchResultDNValue))
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -477,7 +478,7 @@ func TestEndUserAuthentication(t *testing.T) {
|
|||||||
wantAuthResponse: &authenticator.Response{
|
wantAuthResponse: &authenticator.Response{
|
||||||
User: &user.DefaultInfo{
|
User: &user.DefaultInfo{
|
||||||
Name: testUserSearchResultUsernameAttributeValue,
|
Name: testUserSearchResultUsernameAttributeValue,
|
||||||
UID: testUserSearchResultUIDAttributeValue,
|
UID: base64.RawURLEncoding.EncodeToString([]byte(testUserSearchResultUIDAttributeValue)),
|
||||||
Groups: []string{"a", "b", "c"},
|
Groups: []string{"a", "b", "c"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1115,8 +1116,19 @@ func TestGetConfig(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGetURL(t *testing.T) {
|
func TestGetURL(t *testing.T) {
|
||||||
require.Equal(t, "ldaps://ldap.example.com:1234", New(ProviderConfig{Host: "ldap.example.com:1234"}).GetURL())
|
require.Equal(t,
|
||||||
require.Equal(t, "ldaps://ldap.example.com", New(ProviderConfig{Host: "ldap.example.com"}).GetURL())
|
"ldaps://ldap.example.com:1234?base=ou%3Dusers%2Cdc%3Dpinniped%2Cdc%3Ddev",
|
||||||
|
New(ProviderConfig{
|
||||||
|
Host: "ldap.example.com:1234",
|
||||||
|
UserSearch: UserSearchConfig{Base: "ou=users,dc=pinniped,dc=dev"},
|
||||||
|
}).GetURL().String())
|
||||||
|
|
||||||
|
require.Equal(t,
|
||||||
|
"ldaps://ldap.example.com?base=ou%3Dusers%2Cdc%3Dpinniped%2Cdc%3Ddev",
|
||||||
|
New(ProviderConfig{
|
||||||
|
Host: "ldap.example.com",
|
||||||
|
UserSearch: UserSearchConfig{Base: "ou=users,dc=pinniped,dc=dev"},
|
||||||
|
}).GetURL().String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Testing of host parsing, TLS negotiation, and CA bundle, etc. for the production code's dialer.
|
// Testing of host parsing, TLS negotiation, and CA bundle, etc. for the production code's dialer.
|
||||||
|
@ -41,7 +41,7 @@ ldap.ldif: |
|
|||||||
objectClass: shadowAccount
|
objectClass: shadowAccount
|
||||||
cn: pinny
|
cn: pinny
|
||||||
sn: Seal
|
sn: Seal
|
||||||
givenName: Pinny
|
givenName: Pinny the 🦭
|
||||||
mail: pinny.ldap@example.com
|
mail: pinny.ldap@example.com
|
||||||
userPassword: (@= data.values.pinny_ldap_password @)
|
userPassword: (@= data.values.pinny_ldap_password @)
|
||||||
uid: pinny
|
uid: pinny
|
||||||
|
@ -5,6 +5,7 @@ package integration
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
@ -58,6 +59,10 @@ func TestLDAPSearch(t *testing.T) {
|
|||||||
|
|
||||||
pinnyPassword := env.SupervisorUpstreamLDAP.TestUserPassword
|
pinnyPassword := env.SupervisorUpstreamLDAP.TestUserPassword
|
||||||
|
|
||||||
|
b64 := func(s string) string {
|
||||||
|
return base64.RawURLEncoding.EncodeToString([]byte(s))
|
||||||
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
username string
|
username string
|
||||||
@ -73,7 +78,7 @@ func TestLDAPSearch(t *testing.T) {
|
|||||||
password: pinnyPassword,
|
password: pinnyPassword,
|
||||||
provider: upstreamldap.New(*providerConfig(nil)),
|
provider: upstreamldap.New(*providerConfig(nil)),
|
||||||
wantAuthResponse: &authenticator.Response{
|
wantAuthResponse: &authenticator.Response{
|
||||||
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{"ball-game-players", "seals"}},
|
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{"ball-game-players", "seals"}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -85,7 +90,7 @@ func TestLDAPSearch(t *testing.T) {
|
|||||||
p.ConnectionProtocol = upstreamldap.StartTLS
|
p.ConnectionProtocol = upstreamldap.StartTLS
|
||||||
})),
|
})),
|
||||||
wantAuthResponse: &authenticator.Response{
|
wantAuthResponse: &authenticator.Response{
|
||||||
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{"ball-game-players", "seals"}},
|
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{"ball-game-players", "seals"}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -94,7 +99,7 @@ func TestLDAPSearch(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: &authenticator.Response{
|
wantAuthResponse: &authenticator.Response{
|
||||||
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{"ball-game-players", "seals"}},
|
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{"ball-game-players", "seals"}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -103,7 +108,7 @@ func TestLDAPSearch(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: &authenticator.Response{
|
wantAuthResponse: &authenticator.Response{
|
||||||
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{"ball-game-players", "seals"}},
|
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{"ball-game-players", "seals"}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -115,7 +120,7 @@ func TestLDAPSearch(t *testing.T) {
|
|||||||
p.UserSearch.Filter = "cn={}"
|
p.UserSearch.Filter = "cn={}"
|
||||||
})),
|
})),
|
||||||
wantAuthResponse: &authenticator.Response{
|
wantAuthResponse: &authenticator.Response{
|
||||||
User: &user.DefaultInfo{Name: "cn=pinny,ou=users,dc=pinniped,dc=dev", UID: "1000", Groups: []string{"ball-game-players", "seals"}},
|
User: &user.DefaultInfo{Name: "cn=pinny,ou=users,dc=pinniped,dc=dev", UID: b64("1000"), Groups: []string{"ball-game-players", "seals"}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -126,7 +131,7 @@ func TestLDAPSearch(t *testing.T) {
|
|||||||
p.UserSearch.Filter = "(|(cn={})(mail={}))"
|
p.UserSearch.Filter = "(|(cn={})(mail={}))"
|
||||||
})),
|
})),
|
||||||
wantAuthResponse: &authenticator.Response{
|
wantAuthResponse: &authenticator.Response{
|
||||||
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{"ball-game-players", "seals"}},
|
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{"ball-game-players", "seals"}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -137,7 +142,7 @@ func TestLDAPSearch(t *testing.T) {
|
|||||||
p.UserSearch.Filter = "(|(cn={})(mail={}))"
|
p.UserSearch.Filter = "(|(cn={})(mail={}))"
|
||||||
})),
|
})),
|
||||||
wantAuthResponse: &authenticator.Response{
|
wantAuthResponse: &authenticator.Response{
|
||||||
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{"ball-game-players", "seals"}},
|
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{"ball-game-players", "seals"}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -146,7 +151,7 @@ func TestLDAPSearch(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: &authenticator.Response{
|
wantAuthResponse: &authenticator.Response{
|
||||||
User: &user.DefaultInfo{Name: "pinny", UID: "cn=pinny,ou=users,dc=pinniped,dc=dev", Groups: []string{"ball-game-players", "seals"}},
|
User: &user.DefaultInfo{Name: "pinny", UID: b64("cn=pinny,ou=users,dc=pinniped,dc=dev"), Groups: []string{"ball-game-players", "seals"}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -155,7 +160,7 @@ func TestLDAPSearch(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: &authenticator.Response{
|
wantAuthResponse: &authenticator.Response{
|
||||||
User: &user.DefaultInfo{Name: "pinny", UID: "Seal", Groups: []string{"ball-game-players", "seals"}},
|
User: &user.DefaultInfo{Name: "pinny", UID: b64("Seal"), Groups: []string{"ball-game-players", "seals"}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -164,7 +169,32 @@ func TestLDAPSearch(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: &authenticator.Response{
|
wantAuthResponse: &authenticator.Response{
|
||||||
User: &user.DefaultInfo{Name: "Seal", UID: "1000", Groups: []string{"ball-game-players", "seals"}}, // 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"}}, // note that the final answer has case preserved from the entry
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "when the UsernameAttribute or UIDAttribute are attributes whose value contains UTF-8 data",
|
||||||
|
username: "pinny",
|
||||||
|
password: pinnyPassword,
|
||||||
|
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) {
|
||||||
|
p.UserSearch.Filter = "cn={}"
|
||||||
|
p.UserSearch.UsernameAttribute = "givenName"
|
||||||
|
p.UserSearch.UIDAttribute = "givenName"
|
||||||
|
})),
|
||||||
|
wantAuthResponse: &authenticator.Response{
|
||||||
|
User: &user.DefaultInfo{Name: "Pinny the 🦭", UID: b64("Pinny the 🦭"), Groups: []string{"ball-game-players", "seals"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "when the search filter is searching on an attribute whose value contains UTF-8 data",
|
||||||
|
username: "Pinny the 🦭",
|
||||||
|
password: pinnyPassword,
|
||||||
|
provider: upstreamldap.New(*providerConfig(func(p *upstreamldap.ProviderConfig) {
|
||||||
|
p.UserSearch.Filter = "givenName={}"
|
||||||
|
p.UserSearch.UsernameAttribute = "cn"
|
||||||
|
})),
|
||||||
|
wantAuthResponse: &authenticator.Response{
|
||||||
|
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{"ball-game-players", "seals"}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -185,7 +215,7 @@ func TestLDAPSearch(t *testing.T) {
|
|||||||
p.GroupSearch.Base = ""
|
p.GroupSearch.Base = ""
|
||||||
})),
|
})),
|
||||||
wantAuthResponse: &authenticator.Response{
|
wantAuthResponse: &authenticator.Response{
|
||||||
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{}},
|
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -196,7 +226,7 @@ func TestLDAPSearch(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: &authenticator.Response{
|
wantAuthResponse: &authenticator.Response{
|
||||||
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{}},
|
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -207,7 +237,7 @@ func TestLDAPSearch(t *testing.T) {
|
|||||||
p.GroupSearch.GroupNameAttribute = "dn"
|
p.GroupSearch.GroupNameAttribute = "dn"
|
||||||
})),
|
})),
|
||||||
wantAuthResponse: &authenticator.Response{
|
wantAuthResponse: &authenticator.Response{
|
||||||
User: &user.DefaultInfo{Name: "pinny", UID: "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",
|
||||||
}},
|
}},
|
||||||
@ -221,7 +251,7 @@ func TestLDAPSearch(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: &authenticator.Response{
|
wantAuthResponse: &authenticator.Response{
|
||||||
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{"groupOfNames", "groupOfNames"}},
|
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{"groupOfNames", "groupOfNames"}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -232,7 +262,7 @@ func TestLDAPSearch(t *testing.T) {
|
|||||||
p.GroupSearch.Filter = "(&(&(objectClass=groupOfNames)(member={}))(cn=seals))"
|
p.GroupSearch.Filter = "(&(&(objectClass=groupOfNames)(member={}))(cn=seals))"
|
||||||
})),
|
})),
|
||||||
wantAuthResponse: &authenticator.Response{
|
wantAuthResponse: &authenticator.Response{
|
||||||
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{"seals"}},
|
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{"seals"}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -243,7 +273,7 @@ func TestLDAPSearch(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: &authenticator.Response{
|
wantAuthResponse: &authenticator.Response{
|
||||||
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{}},
|
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -568,7 +598,7 @@ func TestLDAPSearch(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSimultaneousRequestsOnSingleProvider(t *testing.T) {
|
func TestSimultaneousLDAPRequestsOnSingleProvider(t *testing.T) {
|
||||||
env := library.IntegrationEnv(t)
|
env := library.IntegrationEnv(t)
|
||||||
|
|
||||||
// Note that these tests depend on the values hard-coded in the LDIF file in test/deploy/tools/ldap.yaml.
|
// Note that these tests depend on the values hard-coded in the LDIF file in test/deploy/tools/ldap.yaml.
|
||||||
@ -589,6 +619,10 @@ func TestSimultaneousRequestsOnSingleProvider(t *testing.T) {
|
|||||||
|
|
||||||
provider := upstreamldap.New(*defaultProviderConfig(env, ldapHostPort))
|
provider := upstreamldap.New(*defaultProviderConfig(env, ldapHostPort))
|
||||||
|
|
||||||
|
b64 := func(s string) string {
|
||||||
|
return base64.RawURLEncoding.EncodeToString([]byte(s))
|
||||||
|
}
|
||||||
|
|
||||||
// Making multiple simultaneous requests on the same upstreamldap.Provider instance should all succeed
|
// Making multiple simultaneous requests on the same upstreamldap.Provider instance should all succeed
|
||||||
// without triggering the race detector.
|
// without triggering the race detector.
|
||||||
iterations := 150
|
iterations := 150
|
||||||
@ -614,7 +648,7 @@ func TestSimultaneousRequestsOnSingleProvider(t *testing.T) {
|
|||||||
assert.NoError(t, result.err)
|
assert.NoError(t, result.err)
|
||||||
assert.True(t, result.authenticated, "expected the user to be authenticated, but they were not")
|
assert.True(t, result.authenticated, "expected the user to be authenticated, but they were not")
|
||||||
assert.Equal(t, &authenticator.Response{
|
assert.Equal(t, &authenticator.Response{
|
||||||
User: &user.DefaultInfo{Name: "pinny", UID: "1000", Groups: []string{"ball-game-players", "seals"}},
|
User: &user.DefaultInfo{Name: "pinny", UID: b64("1000"), Groups: []string{"ball-game-players", "seals"}},
|
||||||
}, result.response)
|
}, result.response)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -119,7 +119,9 @@ func TestSupervisorLogin(t *testing.T) {
|
|||||||
},
|
},
|
||||||
// the ID token Subject should be the Host URL plus the value pulled from the requested UserSearch.Attributes.UID attribute
|
// the ID token Subject should be the Host URL plus the value pulled from the requested UserSearch.Attributes.UID attribute
|
||||||
wantDownstreamIDTokenSubjectToMatch: regexp.QuoteMeta(
|
wantDownstreamIDTokenSubjectToMatch: regexp.QuoteMeta(
|
||||||
"ldaps://" + env.SupervisorUpstreamLDAP.Host + "?sub=" + env.SupervisorUpstreamLDAP.TestUserUniqueIDAttributeValue,
|
"ldaps://" + env.SupervisorUpstreamLDAP.Host +
|
||||||
|
"?base=" + url.QueryEscape(env.SupervisorUpstreamLDAP.UserSearchBase) +
|
||||||
|
"&sub=" + base64.RawURLEncoding.EncodeToString([]byte(env.SupervisorUpstreamLDAP.TestUserUniqueIDAttributeValue)),
|
||||||
),
|
),
|
||||||
// the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute
|
// the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute
|
||||||
wantDownstreamIDTokenUsernameToMatch: regexp.QuoteMeta(env.SupervisorUpstreamLDAP.TestUserMailAttributeValue),
|
wantDownstreamIDTokenUsernameToMatch: regexp.QuoteMeta(env.SupervisorUpstreamLDAP.TestUserMailAttributeValue),
|
||||||
@ -176,7 +178,9 @@ func TestSupervisorLogin(t *testing.T) {
|
|||||||
},
|
},
|
||||||
// the ID token Subject should be the Host URL plus the value pulled from the requested UserSearch.Attributes.UID attribute
|
// the ID token Subject should be the Host URL plus the value pulled from the requested UserSearch.Attributes.UID attribute
|
||||||
wantDownstreamIDTokenSubjectToMatch: regexp.QuoteMeta(
|
wantDownstreamIDTokenSubjectToMatch: regexp.QuoteMeta(
|
||||||
"ldaps://" + env.SupervisorUpstreamLDAP.StartTLSOnlyHost + "?sub=" + env.SupervisorUpstreamLDAP.TestUserUniqueIDAttributeValue,
|
"ldaps://" + env.SupervisorUpstreamLDAP.StartTLSOnlyHost +
|
||||||
|
"?base=" + url.QueryEscape(env.SupervisorUpstreamLDAP.UserSearchBase) +
|
||||||
|
"&sub=" + base64.RawURLEncoding.EncodeToString([]byte(env.SupervisorUpstreamLDAP.TestUserUniqueIDAttributeValue)),
|
||||||
),
|
),
|
||||||
// the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute
|
// the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute
|
||||||
wantDownstreamIDTokenUsernameToMatch: regexp.QuoteMeta(env.SupervisorUpstreamLDAP.TestUserDN),
|
wantDownstreamIDTokenUsernameToMatch: regexp.QuoteMeta(env.SupervisorUpstreamLDAP.TestUserDN),
|
||||||
|
Loading…
Reference in New Issue
Block a user