Small refactors and comments for LDAP/AD UI
This commit is contained in:
parent
0f2a984308
commit
39fd9ba270
@ -922,7 +922,9 @@ func selectUpstreamIDPFlow(discoveredIDPFlows []idpdiscoveryv1alpha1.IDPFlow, se
|
|||||||
return discoveredIDPFlows[0], nil
|
return discoveredIDPFlows[0], nil
|
||||||
default:
|
default:
|
||||||
// The user did not specify a flow, and more than one was found.
|
// The user did not specify a flow, and more than one was found.
|
||||||
log.Info("multiple client flows found, selecting first value as default: "+discoveredIDPFlows[0].String(), "idpName", selectedIDPName, "idpType", selectedIDPType)
|
log.Info("multiple client flows found, selecting first value as default",
|
||||||
|
"idpName", selectedIDPName, "idpType", selectedIDPType,
|
||||||
|
"selectedFlow", discoveredIDPFlows[0].String(), "availableFlows", discoveredIDPFlows)
|
||||||
return discoveredIDPFlows[0], nil
|
return discoveredIDPFlows[0], nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1305,7 +1305,8 @@ func TestGetKubeconfig(t *testing.T) {
|
|||||||
base64.StdEncoding.EncodeToString([]byte(issuerCABundle)))
|
base64.StdEncoding.EncodeToString([]byte(issuerCABundle)))
|
||||||
},
|
},
|
||||||
wantLogs: func(_ string, _ string) []string {
|
wantLogs: func(_ string, _ string) []string {
|
||||||
return []string{"\"level\"=0 \"msg\"=\"multiple client flows found, selecting first value as default: cli_password\" \"idpName\"=\"some-ldap-idp\" \"idpType\"=\"ldap\""}
|
return []string{`"level"=0 "msg"="multiple client flows found, selecting first value as default" ` +
|
||||||
|
`"availableFlows"=["cli_password","flow2"] "idpName"="some-ldap-idp" "idpType"="ldap" "selectedFlow"="cli_password"`}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -159,7 +159,7 @@ func handleAuthRequestForLDAPUpstreamBrowserFlow(
|
|||||||
upstreamStateEncoder oidc.Encoder,
|
upstreamStateEncoder oidc.Encoder,
|
||||||
cookieCodec oidc.Codec,
|
cookieCodec oidc.Codec,
|
||||||
) error {
|
) error {
|
||||||
encodedStateParamValue, _, _, err := handleBrowserAuthRequest(
|
authRequestState, err := handleBrowserFlowAuthRequest(
|
||||||
r,
|
r,
|
||||||
w,
|
w,
|
||||||
oauthHelper,
|
oauthHelper,
|
||||||
@ -174,11 +174,12 @@ func handleAuthRequestForLDAPUpstreamBrowserFlow(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if encodedStateParamValue == "" {
|
if authRequestState == nil {
|
||||||
|
// There was an error but handleBrowserFlowAuthRequest() already took care of writing the response for it.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return login.RedirectToLoginPage(r, w, downstreamIssuer, encodedStateParamValue, login.ShowNoError)
|
return login.RedirectToLoginPage(r, w, downstreamIssuer, authRequestState.encodedStateParam, login.ShowNoError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleAuthRequestForOIDCUpstreamPasswordGrant(
|
func handleAuthRequestForOIDCUpstreamPasswordGrant(
|
||||||
@ -255,7 +256,7 @@ func handleAuthRequestForOIDCUpstreamBrowserFlow(
|
|||||||
upstreamStateEncoder oidc.Encoder,
|
upstreamStateEncoder oidc.Encoder,
|
||||||
cookieCodec oidc.Codec,
|
cookieCodec oidc.Codec,
|
||||||
) error {
|
) error {
|
||||||
encodedStateParamValue, pkceValue, nonceValue, err := handleBrowserAuthRequest(
|
authRequestState, err := handleBrowserFlowAuthRequest(
|
||||||
r,
|
r,
|
||||||
w,
|
w,
|
||||||
oauthHelper,
|
oauthHelper,
|
||||||
@ -270,7 +271,8 @@ func handleAuthRequestForOIDCUpstreamBrowserFlow(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if encodedStateParamValue == "" {
|
if authRequestState == nil {
|
||||||
|
// There was an error but handleBrowserFlowAuthRequest() already took care of writing the response for it.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -284,9 +286,9 @@ func handleAuthRequestForOIDCUpstreamBrowserFlow(
|
|||||||
}
|
}
|
||||||
|
|
||||||
authCodeOptions := []oauth2.AuthCodeOption{
|
authCodeOptions := []oauth2.AuthCodeOption{
|
||||||
nonceValue.Param(),
|
authRequestState.nonce.Param(),
|
||||||
pkceValue.Challenge(),
|
authRequestState.pkce.Challenge(),
|
||||||
pkceValue.Method(),
|
authRequestState.pkce.Method(),
|
||||||
}
|
}
|
||||||
|
|
||||||
for key, val := range oidcUpstream.GetAdditionalAuthcodeParams() {
|
for key, val := range oidcUpstream.GetAdditionalAuthcodeParams() {
|
||||||
@ -295,7 +297,7 @@ func handleAuthRequestForOIDCUpstreamBrowserFlow(
|
|||||||
|
|
||||||
http.Redirect(w, r,
|
http.Redirect(w, r,
|
||||||
upstreamOAuthConfig.AuthCodeURL(
|
upstreamOAuthConfig.AuthCodeURL(
|
||||||
encodedStateParamValue,
|
authRequestState.encodedStateParam,
|
||||||
authCodeOptions...,
|
authCodeOptions...,
|
||||||
),
|
),
|
||||||
http.StatusSeeOther, // match fosite and https://tools.ietf.org/id/draft-ietf-oauth-security-topics-18.html#section-4.11
|
http.StatusSeeOther, // match fosite and https://tools.ietf.org/id/draft-ietf-oauth-security-topics-18.html#section-4.11
|
||||||
@ -387,10 +389,21 @@ func chooseUpstreamIDP(idpLister oidc.UpstreamIdentityProvidersLister) (provider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleBrowserAuthRequest performs the shared validations and setup between browser based auth requests
|
type browserFlowAuthRequestState struct {
|
||||||
// regardless of IDP type-- LDAP, Active Directory and OIDC.
|
encodedStateParam string
|
||||||
|
pkce pkce.Code
|
||||||
|
nonce nonce.Nonce
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleBrowserFlowAuthRequest performs the shared validations and setup between browser based
|
||||||
|
// auth requests regardless of IDP type-- LDAP, Active Directory and OIDC.
|
||||||
// It generates the state param, sets the CSRF cookie, and validates the prompt param.
|
// It generates the state param, sets the CSRF cookie, and validates the prompt param.
|
||||||
func handleBrowserAuthRequest(
|
// It returns an error when it encounters an error without handling it, leaving it to
|
||||||
|
// the caller to decide how to handle it.
|
||||||
|
// It returns nil with no error when it encounters an error and also has already handled writing
|
||||||
|
// the error response to the ResponseWriter, in which case the caller should not also try to
|
||||||
|
// write the error response.
|
||||||
|
func handleBrowserFlowAuthRequest(
|
||||||
r *http.Request,
|
r *http.Request,
|
||||||
w http.ResponseWriter,
|
w http.ResponseWriter,
|
||||||
oauthHelper fosite.OAuth2Provider,
|
oauthHelper fosite.OAuth2Provider,
|
||||||
@ -401,10 +414,10 @@ func handleBrowserAuthRequest(
|
|||||||
idpType psession.ProviderType,
|
idpType psession.ProviderType,
|
||||||
cookieCodec oidc.Codec,
|
cookieCodec oidc.Codec,
|
||||||
upstreamStateEncoder oidc.Encoder,
|
upstreamStateEncoder oidc.Encoder,
|
||||||
) (string, pkce.Code, nonce.Nonce, error) {
|
) (*browserFlowAuthRequestState, error) {
|
||||||
authorizeRequester, created := newAuthorizeRequest(r, w, oauthHelper, false)
|
authorizeRequester, created := newAuthorizeRequest(r, w, oauthHelper, false)
|
||||||
if !created {
|
if !created {
|
||||||
return "", "", "", nil
|
return nil, nil // already wrote the error response, don't return error
|
||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
@ -420,13 +433,13 @@ func handleBrowserAuthRequest(
|
|||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
oidc.WriteAuthorizeError(w, oauthHelper, authorizeRequester, err, false)
|
oidc.WriteAuthorizeError(w, oauthHelper, authorizeRequester, err, false)
|
||||||
return "", "", "", nil
|
return nil, nil // already wrote the error response, don't return error
|
||||||
}
|
}
|
||||||
|
|
||||||
csrfValue, nonceValue, pkceValue, err := generateValues(generateCSRF, generateNonce, generatePKCE)
|
csrfValue, nonceValue, pkceValue, err := generateValues(generateCSRF, generateNonce, generatePKCE)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
plog.Error("authorize generate error", err)
|
plog.Error("authorize generate error", err)
|
||||||
return "", "", "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
csrfFromCookie := readCSRFCookie(r, cookieCodec)
|
csrfFromCookie := readCSRFCookie(r, cookieCodec)
|
||||||
if csrfFromCookie != "" {
|
if csrfFromCookie != "" {
|
||||||
@ -444,13 +457,13 @@ func handleBrowserAuthRequest(
|
|||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
plog.Error("authorize upstream state param error", err)
|
plog.Error("authorize upstream state param error", err)
|
||||||
return "", "", "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
promptParam := r.Form.Get(promptParamName)
|
promptParam := r.Form.Get(promptParamName)
|
||||||
if promptParam == promptParamNone && oidc.ScopeWasRequested(authorizeRequester, coreosoidc.ScopeOpenID) {
|
if promptParam == promptParamNone && oidc.ScopeWasRequested(authorizeRequester, coreosoidc.ScopeOpenID) {
|
||||||
oidc.WriteAuthorizeError(w, oauthHelper, authorizeRequester, fosite.ErrLoginRequired, false)
|
oidc.WriteAuthorizeError(w, oauthHelper, authorizeRequester, fosite.ErrLoginRequired, false)
|
||||||
return "", "", "", nil
|
return nil, nil // already wrote the error response, don't return error
|
||||||
}
|
}
|
||||||
|
|
||||||
if csrfFromCookie == "" {
|
if csrfFromCookie == "" {
|
||||||
@ -458,10 +471,15 @@ func handleBrowserAuthRequest(
|
|||||||
err = addCSRFSetCookieHeader(w, csrfValue, cookieCodec)
|
err = addCSRFSetCookieHeader(w, csrfValue, cookieCodec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
plog.Error("error setting CSRF cookie", err)
|
plog.Error("error setting CSRF cookie", err)
|
||||||
return "", "", "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return encodedStateParamValue, pkceValue, nonceValue, nil
|
|
||||||
|
return &browserFlowAuthRequestState{
|
||||||
|
encodedStateParam: encodedStateParamValue,
|
||||||
|
pkce: pkceValue,
|
||||||
|
nonce: nonceValue,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateValues(
|
func generateValues(
|
||||||
|
@ -15,12 +15,12 @@ const (
|
|||||||
incorrectUsernameOrPasswordErrorMessage = "Incorrect username or password."
|
incorrectUsernameOrPasswordErrorMessage = "Incorrect username or password."
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewGetHandler() HandlerFunc {
|
func NewGetHandler(loginPath string) HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request, encodedState string, decodedState *oidc.UpstreamStateParamData) error {
|
return func(w http.ResponseWriter, r *http.Request, encodedState string, decodedState *oidc.UpstreamStateParamData) error {
|
||||||
alertMessage, hasAlert := getAlert(r)
|
alertMessage, hasAlert := getAlert(r)
|
||||||
|
|
||||||
pageInputs := &loginhtml.PageData{
|
pageInputs := &loginhtml.PageData{
|
||||||
PostPath: r.URL.Path, // the path for POST is the same as for GET
|
PostPath: loginPath,
|
||||||
State: encodedState,
|
State: encodedState,
|
||||||
IDPName: decodedState.UpstreamName,
|
IDPName: decodedState.UpstreamName,
|
||||||
HasAlertError: hasAlert,
|
HasAlertError: hasAlert,
|
||||||
|
@ -96,7 +96,7 @@ func TestGetLogin(t *testing.T) {
|
|||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
handler := NewGetHandler()
|
handler := NewGetHandler(testPath)
|
||||||
target := testPath + "?state=" + tt.encodedState
|
target := testPath + "?state=" + tt.encodedState
|
||||||
if tt.errParam != "" {
|
if tt.errParam != "" {
|
||||||
target += "&err=" + tt.errParam
|
target += "&err=" + tt.errParam
|
||||||
|
@ -94,6 +94,9 @@ func wrapSecurityHeaders(handler http.Handler) http.Handler {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RedirectToLoginPage redirects to the GET /login page of the specified issuer.
|
||||||
|
// The specified issuer should never end with a "/", which is validated by
|
||||||
|
// provider.FederationDomainIssuer when the issuer string comes from that type.
|
||||||
func RedirectToLoginPage(
|
func RedirectToLoginPage(
|
||||||
r *http.Request,
|
r *http.Request,
|
||||||
w http.ResponseWriter,
|
w http.ResponseWriter,
|
||||||
|
@ -18,7 +18,7 @@ import (
|
|||||||
var (
|
var (
|
||||||
//go:embed login_form.css
|
//go:embed login_form.css
|
||||||
rawCSS string
|
rawCSS string
|
||||||
minifiedCSS = mustMinify(minify.CSS(rawCSS))
|
minifiedCSS = panicOnError(minify.CSS(rawCSS))
|
||||||
|
|
||||||
//go:embed login_form.gohtml
|
//go:embed login_form.gohtml
|
||||||
rawHTMLTemplate string
|
rawHTMLTemplate string
|
||||||
@ -26,7 +26,7 @@ var (
|
|||||||
|
|
||||||
// Parse the Go templated HTML and inject functions providing the minified inline CSS and JS.
|
// Parse the Go templated HTML and inject functions providing the minified inline CSS and JS.
|
||||||
var parsedHTMLTemplate = template.Must(template.New("login_form.gohtml").Funcs(template.FuncMap{
|
var parsedHTMLTemplate = template.Must(template.New("login_form.gohtml").Funcs(template.FuncMap{
|
||||||
"minifiedCSS": func() template.CSS { return template.CSS(minifiedCSS) },
|
"minifiedCSS": func() template.CSS { return template.CSS(CSS()) },
|
||||||
}).Parse(rawHTMLTemplate))
|
}).Parse(rawHTMLTemplate))
|
||||||
|
|
||||||
// Generate the CSP header value once since it's effectively constant.
|
// Generate the CSP header value once since it's effectively constant.
|
||||||
@ -36,7 +36,7 @@ var cspValue = strings.Join([]string{
|
|||||||
`frame-ancestors 'none'`,
|
`frame-ancestors 'none'`,
|
||||||
}, "; ")
|
}, "; ")
|
||||||
|
|
||||||
func mustMinify(s string, err error) string {
|
func panicOnError(s string, err error) string {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -63,6 +63,6 @@ func TestCSS(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestHelpers(t *testing.T) {
|
func TestHelpers(t *testing.T) {
|
||||||
require.Equal(t, "test", mustMinify("test", nil))
|
require.Equal(t, "test", panicOnError("test", nil))
|
||||||
require.PanicsWithError(t, "some error", func() { mustMinify("", fmt.Errorf("some error")) })
|
require.PanicsWithError(t, "some error", func() { panicOnError("", fmt.Errorf("some error")) })
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,9 @@ const (
|
|||||||
// Just in case we need to make a breaking change to the format of the upstream state param,
|
// Just in case we need to make a breaking change to the format of the upstream state param,
|
||||||
// we are including a format version number. This gives the opportunity for a future version of Pinniped
|
// we are including a format version number. This gives the opportunity for a future version of Pinniped
|
||||||
// to have the consumer of this format decide to reject versions that it doesn't understand.
|
// to have the consumer of this format decide to reject versions that it doesn't understand.
|
||||||
|
//
|
||||||
|
// Version 1 was the original version.
|
||||||
|
// Version 2 added the UpstreamType field to the UpstreamStateParamData struct.
|
||||||
UpstreamStateParamFormatVersion = "2"
|
UpstreamStateParamFormatVersion = "2"
|
||||||
|
|
||||||
// The `name` passed to the encoder for encoding the upstream state param value. This name is short
|
// The `name` passed to the encoder for encoding the upstream state param value. This name is short
|
||||||
|
@ -18,11 +18,11 @@ import (
|
|||||||
var (
|
var (
|
||||||
//go:embed form_post.css
|
//go:embed form_post.css
|
||||||
rawCSS string
|
rawCSS string
|
||||||
minifiedCSS = mustMinify(minify.CSS(rawCSS))
|
minifiedCSS = panicOnError(minify.CSS(rawCSS))
|
||||||
|
|
||||||
//go:embed form_post.js
|
//go:embed form_post.js
|
||||||
rawJS string
|
rawJS string
|
||||||
minifiedJS = mustMinify(minify.JS(rawJS))
|
minifiedJS = panicOnError(minify.JS(rawJS))
|
||||||
|
|
||||||
//go:embed form_post.gohtml
|
//go:embed form_post.gohtml
|
||||||
rawHTMLTemplate string
|
rawHTMLTemplate string
|
||||||
@ -44,7 +44,7 @@ var cspValue = strings.Join([]string{
|
|||||||
`frame-ancestors 'none'`,
|
`frame-ancestors 'none'`,
|
||||||
}, "; ")
|
}, "; ")
|
||||||
|
|
||||||
func mustMinify(s string, err error) string {
|
func panicOnError(s string, err error) string {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -93,6 +93,6 @@ func TestContentSecurityPolicyHashes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestHelpers(t *testing.T) {
|
func TestHelpers(t *testing.T) {
|
||||||
require.Equal(t, "test", mustMinify("test", nil))
|
require.Equal(t, "test", panicOnError("test", nil))
|
||||||
require.PanicsWithError(t, "some error", func() { mustMinify("", fmt.Errorf("some error")) })
|
require.PanicsWithError(t, "some error", func() { panicOnError("", fmt.Errorf("some error")) })
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"go.pinniped.dev/internal/oidc/login"
|
|
||||||
|
|
||||||
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
|
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||||
|
|
||||||
"go.pinniped.dev/internal/oidc"
|
"go.pinniped.dev/internal/oidc"
|
||||||
@ -20,6 +18,7 @@ import (
|
|||||||
"go.pinniped.dev/internal/oidc/dynamiccodec"
|
"go.pinniped.dev/internal/oidc/dynamiccodec"
|
||||||
"go.pinniped.dev/internal/oidc/idpdiscovery"
|
"go.pinniped.dev/internal/oidc/idpdiscovery"
|
||||||
"go.pinniped.dev/internal/oidc/jwks"
|
"go.pinniped.dev/internal/oidc/jwks"
|
||||||
|
"go.pinniped.dev/internal/oidc/login"
|
||||||
"go.pinniped.dev/internal/oidc/provider"
|
"go.pinniped.dev/internal/oidc/provider"
|
||||||
"go.pinniped.dev/internal/oidc/token"
|
"go.pinniped.dev/internal/oidc/token"
|
||||||
"go.pinniped.dev/internal/plog"
|
"go.pinniped.dev/internal/plog"
|
||||||
@ -139,7 +138,7 @@ func (m *Manager) SetProviders(federationDomains ...*provider.FederationDomainIs
|
|||||||
m.providerHandlers[(issuerHostWithPath + oidc.PinnipedLoginPath)] = login.NewHandler(
|
m.providerHandlers[(issuerHostWithPath + oidc.PinnipedLoginPath)] = login.NewHandler(
|
||||||
upstreamStateEncoder,
|
upstreamStateEncoder,
|
||||||
csrfCookieEncoder,
|
csrfCookieEncoder,
|
||||||
login.NewGetHandler(),
|
login.NewGetHandler(incomingProvider.IssuerPath()+oidc.PinnipedLoginPath),
|
||||||
login.NewPostHandler(issuer, m.upstreamIDPs, oauthHelperWithKubeStorage),
|
login.NewPostHandler(issuer, m.upstreamIDPs, oauthHelperWithKubeStorage),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -118,10 +118,6 @@ stringData:
|
|||||||
clientSecret: "<your-client-secret>"
|
clientSecret: "<your-client-secret>"
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that the `metadata.name` of the OIDCIdentityProvider resource may be visible to end users at login prompts
|
|
||||||
if you choose to enable `allowPasswordGrant`, so choose a name which will be understood by your end users.
|
|
||||||
For example, if you work at Acme Corp, choose something like `acme-corporate-workspace-one` over `my-idp`.
|
|
||||||
|
|
||||||
The following claims are returned by Workspace ONE Access. The `group` scope is required to use the
|
The following claims are returned by Workspace ONE Access. The `group` scope is required to use the
|
||||||
`group_ids` and `group_names` claims. The `email` scope is required to use the `email` claim. The
|
`group_ids` and `group_names` claims. The `email` scope is required to use the `email` claim. The
|
||||||
remaining claims are always available.
|
remaining claims are always available.
|
||||||
|
Loading…
Reference in New Issue
Block a user