Refactor and rename ./internal/oidcclient/login to ./internal/oidcclient.

This commit is contained in:
Matt Moyer 2020-10-21 13:04:46 -05:00
parent 4ef41f969d
commit 7f6a82aa91
No known key found for this signature in database
GPG Key ID: EAE88AD172C5AE2D
5 changed files with 102 additions and 39 deletions

View File

@ -10,15 +10,15 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientauthenticationv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1" clientauthenticationv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
"go.pinniped.dev/internal/oidcclient/login" "go.pinniped.dev/internal/oidcclient"
) )
//nolint: gochecknoinits //nolint: gochecknoinits
func init() { func init() {
loginCmd.AddCommand(oidcLoginCommand(login.Run)) loginCmd.AddCommand(oidcLoginCommand(oidcclient.Login))
} }
func oidcLoginCommand(loginFunc func(issuer string, clientID string, opts ...login.Option) (*login.Token, error)) *cobra.Command { func oidcLoginCommand(loginFunc func(issuer string, clientID string, opts ...oidcclient.Option) (*oidcclient.Token, error)) *cobra.Command {
var ( var (
cmd = cobra.Command{ cmd = cobra.Command{
Args: cobra.NoArgs, Args: cobra.NoArgs,
@ -40,18 +40,18 @@ func oidcLoginCommand(loginFunc func(issuer string, clientID string, opts ...log
mustMarkRequired(&cmd, "issuer", "client-id") mustMarkRequired(&cmd, "issuer", "client-id")
cmd.RunE = func(cmd *cobra.Command, args []string) error { cmd.RunE = func(cmd *cobra.Command, args []string) error {
opts := []login.Option{ opts := []oidcclient.Option{
login.WithContext(cmd.Context()), oidcclient.WithContext(cmd.Context()),
login.WithScopes(scopes), oidcclient.WithScopes(scopes),
} }
if listenPort != 0 { if listenPort != 0 {
opts = append(opts, login.WithListenPort(listenPort)) opts = append(opts, oidcclient.WithListenPort(listenPort))
} }
// --skip-browser replaces the default "browser open" function with one that prints to stderr. // --skip-browser replaces the default "browser open" function with one that prints to stderr.
if skipBrowser { if skipBrowser {
opts = append(opts, login.WithBrowserOpen(func(url string) error { opts = append(opts, oidcclient.WithBrowserOpen(func(url string) error {
cmd.PrintErr("Please log in: ", url, "\n") cmd.PrintErr("Please log in: ", url, "\n")
return nil return nil
})) }))
@ -69,8 +69,8 @@ func oidcLoginCommand(loginFunc func(issuer string, clientID string, opts ...log
APIVersion: "client.authentication.k8s.io/v1beta1", APIVersion: "client.authentication.k8s.io/v1beta1",
}, },
Status: &clientauthenticationv1beta1.ExecCredentialStatus{ Status: &clientauthenticationv1beta1.ExecCredentialStatus{
ExpirationTimestamp: &metav1.Time{Time: tok.IDTokenExpiry}, ExpirationTimestamp: &tok.IDToken.Expiry,
Token: tok.IDToken, Token: tok.IDToken.Token,
}, },
}) })
} }

View File

@ -9,9 +9,10 @@ import (
"time" "time"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"go.pinniped.dev/internal/here" "go.pinniped.dev/internal/here"
"go.pinniped.dev/internal/oidcclient/login" "go.pinniped.dev/internal/oidcclient"
) )
func TestLoginOIDCCommand(t *testing.T) { func TestLoginOIDCCommand(t *testing.T) {
@ -87,13 +88,18 @@ func TestLoginOIDCCommand(t *testing.T) {
var ( var (
gotIssuer string gotIssuer string
gotClientID string gotClientID string
gotOptions []login.Option gotOptions []oidcclient.Option
) )
cmd := oidcLoginCommand(func(issuer string, clientID string, opts ...login.Option) (*login.Token, error) { cmd := oidcLoginCommand(func(issuer string, clientID string, opts ...oidcclient.Option) (*oidcclient.Token, error) {
gotIssuer = issuer gotIssuer = issuer
gotClientID = clientID gotClientID = clientID
gotOptions = opts gotOptions = opts
return &login.Token{IDToken: "test-id-token", IDTokenExpiry: time1}, nil return &oidcclient.Token{
IDToken: &oidcclient.IDToken{
Token: "test-id-token",
Expiry: metav1.NewTime(time1),
},
}, nil
}) })
require.NotNil(t, cmd) require.NotNil(t, cmd)

View File

@ -1,8 +1,8 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved. // Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// Package login implements a CLI OIDC login flow. // Package oidcclient implements a CLI OIDC login flow.
package login package oidcclient
import ( import (
"context" "context"
@ -15,6 +15,7 @@ import (
"github.com/coreos/go-oidc" "github.com/coreos/go-oidc"
"github.com/pkg/browser" "github.com/pkg/browser"
"golang.org/x/oauth2" "golang.org/x/oauth2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"go.pinniped.dev/internal/httputil/httperr" "go.pinniped.dev/internal/httputil/httperr"
"go.pinniped.dev/internal/httputil/securityheader" "go.pinniped.dev/internal/httputil/securityheader"
@ -55,13 +56,7 @@ type callbackResult struct {
err error err error
} }
type Token struct { // Option is an optional configuration for Login().
*oauth2.Token
IDToken string `json:"id_token"`
IDTokenExpiry time.Time `json:"id_token_expiry"`
}
// Option is an optional configuration for Run().
type Option func(*handlerState) error type Option func(*handlerState) error
// WithContext specifies a specific context.Context under which to perform the login. If this option is not specified, // WithContext specifies a specific context.Context under which to perform the login. If this option is not specified,
@ -105,8 +100,8 @@ func WithBrowserOpen(openURL func(url string) error) Option {
} }
} }
// Run an OAuth2/OIDC authorization code login using a localhost listener. // Login performs an OAuth2/OIDC authorization code login using a localhost listener.
func Run(issuer string, clientID string, opts ...Option) (*Token, error) { func Login(issuer string, clientID string, opts ...Option) (*Token, error) {
h := handlerState{ h := handlerState{
issuer: issuer, issuer: issuer,
clientID: clientID, clientID: clientID,
@ -262,9 +257,18 @@ func (h *handlerState) handleAuthCodeCallback(w http.ResponseWriter, r *http.Req
} }
h.callbacks <- callbackResult{token: &Token{ h.callbacks <- callbackResult{token: &Token{
Token: oauth2Tok, AccessToken: &AccessToken{
IDToken: idTok, Token: oauth2Tok.AccessToken,
IDTokenExpiry: validated.Expiry, Type: oauth2Tok.TokenType,
Expiry: metav1.NewTime(oauth2Tok.Expiry),
},
RefreshToken: &RefreshToken{
Token: oauth2Tok.RefreshToken,
},
IDToken: &IDToken{
Token: idTok,
Expiry: metav1.NewTime(validated.Expiry),
},
}} }}
_, _ = w.Write([]byte("you have been logged in and may now close this tab")) _, _ = w.Write([]byte("you have been logged in and may now close this tab"))
return nil return nil

View File

@ -1,7 +1,7 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved. // Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package login package oidcclient
import ( import (
"context" "context"
@ -18,6 +18,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"gopkg.in/square/go-jose.v2" "gopkg.in/square/go-jose.v2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"go.pinniped.dev/internal/httputil/httperr" "go.pinniped.dev/internal/httputil/httperr"
"go.pinniped.dev/internal/mocks/mockkeyset" "go.pinniped.dev/internal/mocks/mockkeyset"
@ -26,18 +27,21 @@ import (
"go.pinniped.dev/internal/oidcclient/state" "go.pinniped.dev/internal/oidcclient/state"
) )
func TestRun(t *testing.T) { func TestLogin(t *testing.T) {
time1 := time.Date(3020, 10, 12, 13, 14, 15, 16, time.UTC) time1 := time.Date(3020, 10, 12, 13, 14, 15, 16, time.UTC)
testToken := Token{ testToken := Token{
Token: &oauth2.Token{ AccessToken: &AccessToken{
AccessToken: "test-access-token", Token: "test-access-token",
RefreshToken: "test-refresh-token", Expiry: metav1.NewTime(time1.Add(1 * time.Minute)),
Expiry: time1.Add(1 * time.Minute), },
RefreshToken: &RefreshToken{
Token: "test-refresh-token",
},
IDToken: &IDToken{
Token: "test-id-token",
Expiry: metav1.NewTime(time1.Add(2 * time.Minute)),
}, },
IDToken: "test-id-token",
IDTokenExpiry: time1.Add(2 * time.Minute),
} }
_ = testToken
// Start a test server that returns 500 errors // Start a test server that returns 500 errors
errorServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { errorServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@ -223,7 +227,7 @@ func TestRun(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
tt := tt tt := tt
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
tok, err := Run(tt.issuer, tt.clientID, tok, err := Login(tt.issuer, tt.clientID,
WithContext(context.Background()), WithContext(context.Background()),
WithListenPort(0), WithListenPort(0),
WithScopes([]string{"test-scope"}), WithScopes([]string{"test-scope"}),
@ -393,7 +397,7 @@ func TestHandleAuthCodeCallback(t *testing.T) {
} }
require.NoError(t, result.err) require.NoError(t, result.err)
require.NotNil(t, result.token) require.NotNil(t, result.token)
require.Equal(t, result.token.IDToken, tt.returnIDTok) require.Equal(t, result.token.IDToken.Token, tt.returnIDTok)
} }
}) })
} }

View File

@ -0,0 +1,49 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package oidcclient
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// AccessToken is an OAuth2 access token.
type AccessToken struct {
// Token is the token that authorizes and authenticates the requests.
Token string `json:"token"`
// Type is the type of token.
Type string `json:"type,omitempty"`
// Expiry is the optional expiration time of the access token.
Expiry metav1.Time `json:"expiryTimestamp,omitempty"`
}
// RefreshToken is an OAuth2 refresh token.
type RefreshToken struct {
// Token is a token that's used by the application (as opposed to the user) to refresh the access token if it expires.
Token string `json:"token"`
}
// IDToken is an OpenID Connect ID token.
type IDToken struct {
// Token is an OpenID Connect ID token.
Token string `json:"token"`
// Expiry is the optional expiration time of the ID token.
Expiry metav1.Time `json:"expiryTimestamp,omitempty"`
}
// Token contains the elements of an OIDC session.
type Token struct {
// AccessToken is the token that authorizes and authenticates the requests.
AccessToken *AccessToken `json:"access,omitempty"`
// RefreshToken is a token that's used by the application
// (as opposed to the user) to refresh the access token
// if it expires.
RefreshToken *RefreshToken `json:"refresh,omitempty"`
// IDToken is an OpenID Connect ID token.
IDToken *IDToken `json:"id,omitempty"`
}