Merge branch 'main' into fosite-storage-gc

This commit is contained in:
Ryan Richard 2020-12-10 10:45:26 -08:00
commit 3c6d1a1924
4 changed files with 40 additions and 9 deletions

View File

@ -48,16 +48,16 @@ func oidcLoginCommand(loginFunc func(issuer string, clientID string, opts ...oid
requestAudience string requestAudience string
) )
cmd.Flags().StringVar(&issuer, "issuer", "", "OpenID Connect issuer URL.") cmd.Flags().StringVar(&issuer, "issuer", "", "OpenID Connect issuer URL.")
cmd.Flags().StringVar(&clientID, "client-id", "", "OpenID Connect client ID.") cmd.Flags().StringVar(&clientID, "client-id", "pinniped-cli", "OpenID Connect client ID.")
cmd.Flags().Uint16Var(&listenPort, "listen-port", 0, "TCP port for localhost listener (authorization code flow only).") cmd.Flags().Uint16Var(&listenPort, "listen-port", 0, "TCP port for localhost listener (authorization code flow only).")
cmd.Flags().StringSliceVar(&scopes, "scopes", []string{oidc.ScopeOfflineAccess, oidc.ScopeOpenID}, "OIDC scopes to request during login.") cmd.Flags().StringSliceVar(&scopes, "scopes", []string{oidc.ScopeOfflineAccess, oidc.ScopeOpenID, "pinniped.sts.unrestricted"}, "OIDC scopes to request during login.")
cmd.Flags().BoolVar(&skipBrowser, "skip-browser", false, "Skip opening the browser (just print the URL).") cmd.Flags().BoolVar(&skipBrowser, "skip-browser", false, "Skip opening the browser (just print the URL).")
cmd.Flags().StringVar(&sessionCachePath, "session-cache", filepath.Join(mustGetConfigDir(), "sessions.yaml"), "Path to session cache file.") cmd.Flags().StringVar(&sessionCachePath, "session-cache", filepath.Join(mustGetConfigDir(), "sessions.yaml"), "Path to session cache file.")
cmd.Flags().StringSliceVar(&caBundlePaths, "ca-bundle", nil, "Path to TLS certificate authority bundle (PEM format, optional, can be repeated).") cmd.Flags().StringSliceVar(&caBundlePaths, "ca-bundle", nil, "Path to TLS certificate authority bundle (PEM format, optional, can be repeated).")
cmd.Flags().BoolVar(&debugSessionCache, "debug-session-cache", false, "Print debug logs related to the session cache.") cmd.Flags().BoolVar(&debugSessionCache, "debug-session-cache", false, "Print debug logs related to the session cache.")
cmd.Flags().StringVar(&requestAudience, "request-audience", "", "Request a token with an alternate audience using RF8693 token exchange.") cmd.Flags().StringVar(&requestAudience, "request-audience", "", "Request a token with an alternate audience using RF8693 token exchange.")
mustMarkHidden(&cmd, "debug-session-cache") mustMarkHidden(&cmd, "debug-session-cache")
mustMarkRequired(&cmd, "issuer", "client-id") mustMarkRequired(&cmd, "issuer")
cmd.RunE = func(cmd *cobra.Command, args []string) error { cmd.RunE = func(cmd *cobra.Command, args []string) error {
// Initialize the session cache. // Initialize the session cache.

View File

@ -42,12 +42,12 @@ func TestLoginOIDCCommand(t *testing.T) {
Flags: Flags:
--ca-bundle strings Path to TLS certificate authority bundle (PEM format, optional, can be repeated). --ca-bundle strings Path to TLS certificate authority bundle (PEM format, optional, can be repeated).
--client-id string OpenID Connect client ID. --client-id string OpenID Connect client ID. (default "pinniped-cli")
-h, --help help for oidc -h, --help help for oidc
--issuer string OpenID Connect issuer URL. --issuer string OpenID Connect issuer URL.
--listen-port uint16 TCP port for localhost listener (authorization code flow only). --listen-port uint16 TCP port for localhost listener (authorization code flow only).
--request-audience string Request a token with an alternate audience using RF8693 token exchange. --request-audience string Request a token with an alternate audience using RF8693 token exchange.
--scopes strings OIDC scopes to request during login. (default [offline_access,openid]) --scopes strings OIDC scopes to request during login. (default [offline_access,openid,pinniped.sts.unrestricted])
--session-cache string Path to session cache file. (default "` + cfgDir + `/sessions.yaml") --session-cache string Path to session cache file. (default "` + cfgDir + `/sessions.yaml")
--skip-browser Skip opening the browser (just print the URL). --skip-browser Skip opening the browser (just print the URL).
`), `),
@ -57,7 +57,7 @@ func TestLoginOIDCCommand(t *testing.T) {
args: []string{}, args: []string{},
wantError: true, wantError: true,
wantStdout: here.Doc(` wantStdout: here.Doc(`
Error: required flag(s) "client-id", "issuer" not set Error: required flag(s) "issuer" not set
`), `),
}, },
{ {

View File

@ -8,6 +8,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"mime"
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
@ -364,8 +365,12 @@ func (h *handlerState) tokenExchangeRFC8693(baseToken *oidctypes.Token) (*oidcty
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected HTTP response status %d", resp.StatusCode) return nil, fmt.Errorf("unexpected HTTP response status %d", resp.StatusCode)
} }
if contentType := resp.Header.Get("content-type"); contentType != "application/json" { mediaType, _, err := mime.ParseMediaType(resp.Header.Get("content-type"))
return nil, fmt.Errorf("unexpected HTTP response content type %q", contentType) if err != nil {
return nil, fmt.Errorf("failed to decode content-type header: %w", err)
}
if mediaType != "application/json" {
return nil, fmt.Errorf("unexpected HTTP response content type %q", mediaType)
} }
// Decode the JSON response body. // Decode the JSON response body.

View File

@ -164,11 +164,14 @@ func TestLogin(t *testing.T) {
case "test-audience-produce-http-400": case "test-audience-produce-http-400":
http.Error(w, "some server error", http.StatusBadRequest) http.Error(w, "some server error", http.StatusBadRequest)
return return
case "test-audience-produce-invalid-content-type":
w.Header().Set("content-type", "invalid/invalid;=")
return
case "test-audience-produce-wrong-content-type": case "test-audience-produce-wrong-content-type":
w.Header().Set("content-type", "invalid") w.Header().Set("content-type", "invalid")
return return
case "test-audience-produce-invalid-json": case "test-audience-produce-invalid-json":
w.Header().Set("content-type", "application/json") w.Header().Set("content-type", "application/json;charset=UTF-8")
_, _ = w.Write([]byte(`{`)) _, _ = w.Write([]byte(`{`))
return return
case "test-audience-produce-invalid-tokentype": case "test-audience-produce-invalid-tokentype":
@ -601,6 +604,29 @@ func TestLogin(t *testing.T) {
}, },
wantErr: `failed to exchange token: unexpected HTTP response status 400`, wantErr: `failed to exchange token: unexpected HTTP response status 400`,
}, },
{
name: "with requested audience, session cache hit with valid token, but token exchange request returns invalid content-type header",
issuer: successServer.URL,
clientID: "test-client-id",
opt: func(t *testing.T) Option {
return func(h *handlerState) error {
cache := &mockSessionCache{t: t, getReturnsToken: &testToken}
t.Cleanup(func() {
require.Equal(t, []SessionCacheKey{{
Issuer: successServer.URL,
ClientID: "test-client-id",
Scopes: []string{"test-scope"},
RedirectURI: "http://localhost:0/callback",
}}, cache.sawGetKeys)
require.Empty(t, cache.sawPutTokens)
})
require.NoError(t, WithSessionCache(cache)(h))
require.NoError(t, WithRequestAudience("test-audience-produce-invalid-content-type")(h))
return nil
}
},
wantErr: `failed to exchange token: failed to decode content-type header: mime: invalid media parameter`,
},
{ {
name: "with requested audience, session cache hit with valid token, but token exchange request returns wrong content-type", name: "with requested audience, session cache hit with valid token, but token exchange request returns wrong content-type",
issuer: successServer.URL, issuer: successServer.URL,