Refactor: replace DTO with oidc.TokenSet type (#394)

* Refactor: remove IDTokenClaims from TokenSet and decode in use-cases

* Refactor: use oidc.TokenSet for cache repository
This commit is contained in:
Hidetake Iwata
2020-10-03 17:49:21 +09:00
committed by GitHub
parent 1dee4a354e
commit 5b2c82fc33
16 changed files with 186 additions and 232 deletions

View File

@@ -8,7 +8,6 @@ import (
gooidc "github.com/coreos/go-oidc"
"github.com/int128/kubelogin/pkg/adaptors/clock"
"github.com/int128/kubelogin/pkg/adaptors/logger"
"github.com/int128/kubelogin/pkg/jwt"
"github.com/int128/kubelogin/pkg/oidc"
"github.com/int128/kubelogin/pkg/pkce"
"github.com/int128/oauth2cli"
@@ -184,17 +183,8 @@ func (c *client) verifyToken(ctx context.Context, token *oauth2.Token, nonce str
if nonce != "" && nonce != verifiedIDToken.Nonce {
return nil, xerrors.Errorf("nonce did not match (wants %s but got %s)", nonce, verifiedIDToken.Nonce)
}
pretty, err := jwt.DecodePayloadAsPrettyJSON(idToken)
if err != nil {
return nil, xerrors.Errorf("could not decode the token: %w", err)
}
return &oidc.TokenSet{
IDToken: idToken,
IDTokenClaims: jwt.Claims{
Subject: verifiedIDToken.Subject,
Expiry: verifiedIDToken.Expiry,
Pretty: pretty,
},
IDToken: idToken,
RefreshToken: token.RefreshToken,
}, nil
}

View File

@@ -7,6 +7,7 @@ package mock_tokencache
import (
gomock "github.com/golang/mock/gomock"
tokencache "github.com/int128/kubelogin/pkg/adaptors/tokencache"
oidc "github.com/int128/kubelogin/pkg/oidc"
reflect "reflect"
)
@@ -34,10 +35,10 @@ func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder {
}
// FindByKey mocks base method.
func (m *MockInterface) FindByKey(arg0 string, arg1 tokencache.Key) (*tokencache.Value, error) {
func (m *MockInterface) FindByKey(arg0 string, arg1 tokencache.Key) (*oidc.TokenSet, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FindByKey", arg0, arg1)
ret0, _ := ret[0].(*tokencache.Value)
ret0, _ := ret[0].(*oidc.TokenSet)
ret1, _ := ret[1].(error)
return ret0, ret1
}
@@ -49,7 +50,7 @@ func (mr *MockInterfaceMockRecorder) FindByKey(arg0, arg1 interface{}) *gomock.C
}
// Save mocks base method.
func (m *MockInterface) Save(arg0 string, arg1 tokencache.Key, arg2 tokencache.Value) error {
func (m *MockInterface) Save(arg0 string, arg1 tokencache.Key, arg2 oidc.TokenSet) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Save", arg0, arg1, arg2)
ret0, _ := ret[0].(error)

View File

@@ -9,6 +9,7 @@ import (
"path/filepath"
"github.com/google/wire"
"github.com/int128/kubelogin/pkg/oidc"
"golang.org/x/xerrors"
)
@@ -21,8 +22,8 @@ var Set = wire.NewSet(
)
type Interface interface {
FindByKey(dir string, key Key) (*Value, error)
Save(dir string, key Key, value Value) error
FindByKey(dir string, key Key) (*oidc.TokenSet, error)
Save(dir string, key Key, tokenSet oidc.TokenSet) error
}
// Key represents a key of a token cache.
@@ -36,8 +37,7 @@ type Key struct {
SkipTLSVerify bool
}
// Value represents a value of a token cache.
type Value struct {
type entity struct {
IDToken string `json:"id_token,omitempty"`
RefreshToken string `json:"refresh_token,omitempty"`
}
@@ -46,7 +46,7 @@ type Value struct {
// Filename of a token cache is sha256 digest of the issuer, zero-character and client ID.
type Repository struct{}
func (r *Repository) FindByKey(dir string, key Key) (*Value, error) {
func (r *Repository) FindByKey(dir string, key Key) (*oidc.TokenSet, error) {
filename, err := computeFilename(key)
if err != nil {
return nil, xerrors.Errorf("could not compute the key: %w", err)
@@ -58,14 +58,17 @@ func (r *Repository) FindByKey(dir string, key Key) (*Value, error) {
}
defer f.Close()
d := json.NewDecoder(f)
var c Value
if err := d.Decode(&c); err != nil {
var e entity
if err := d.Decode(&e); err != nil {
return nil, xerrors.Errorf("invalid json file %s: %w", p, err)
}
return &c, nil
return &oidc.TokenSet{
IDToken: e.IDToken,
RefreshToken: e.RefreshToken,
}, nil
}
func (r *Repository) Save(dir string, key Key, value Value) error {
func (r *Repository) Save(dir string, key Key, tokenSet oidc.TokenSet) error {
if err := os.MkdirAll(dir, 0700); err != nil {
return xerrors.Errorf("could not create directory %s: %w", dir, err)
}
@@ -79,8 +82,11 @@ func (r *Repository) Save(dir string, key Key, value Value) error {
return xerrors.Errorf("could not create file %s: %w", p, err)
}
defer f.Close()
e := json.NewEncoder(f)
if err := e.Encode(&value); err != nil {
e := entity{
IDToken: tokenSet.IDToken,
RefreshToken: tokenSet.RefreshToken,
}
if err := json.NewEncoder(f).Encode(&e); err != nil {
return xerrors.Errorf("json encode error: %w", err)
}
return nil

View File

@@ -6,6 +6,7 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/int128/kubelogin/pkg/oidc"
)
func TestRepository_FindByKey(t *testing.T) {
@@ -31,12 +32,12 @@ func TestRepository_FindByKey(t *testing.T) {
t.Fatalf("could not write to the temp file: %s", err)
}
value, err := r.FindByKey(dir, key)
got, err := r.FindByKey(dir, key)
if err != nil {
t.Errorf("err wants nil but %+v", err)
}
want := &Value{IDToken: "YOUR_ID_TOKEN", RefreshToken: "YOUR_REFRESH_TOKEN"}
if diff := cmp.Diff(want, value); diff != "" {
want := &oidc.TokenSet{IDToken: "YOUR_ID_TOKEN", RefreshToken: "YOUR_REFRESH_TOKEN"}
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("mismatch (-want +got):\n%s", diff)
}
})
@@ -55,8 +56,8 @@ func TestRepository_Save(t *testing.T) {
CACertFilename: "/path/to/cert",
SkipTLSVerify: false,
}
value := Value{IDToken: "YOUR_ID_TOKEN", RefreshToken: "YOUR_REFRESH_TOKEN"}
if err := r.Save(dir, key, value); err != nil {
tokenSet := oidc.TokenSet{IDToken: "YOUR_ID_TOKEN", RefreshToken: "YOUR_REFRESH_TOKEN"}
if err := r.Save(dir, key, tokenSet); err != nil {
t.Errorf("err wants nil but %+v", err)
}

View File

@@ -20,12 +20,14 @@ type Provider struct {
SkipTLSVerify bool // optional
}
// TokenSet represents an output DTO of
// Interface.GetTokenByAuthCode, Interface.GetTokenByROPC and Interface.Refresh.
// TokenSet represents a set of ID token and refresh token.
type TokenSet struct {
IDToken string
RefreshToken string
IDTokenClaims jwt.Claims
IDToken string
RefreshToken string
}
func (ts TokenSet) DecodeWithoutVerify() (*jwt.Claims, error) {
return jwt.DecodeWithoutVerify(ts.IDToken)
}
func NewState() (string, error) {

View File

@@ -10,17 +10,11 @@ import (
"github.com/int128/kubelogin/pkg/adaptors/browser/mock_browser"
"github.com/int128/kubelogin/pkg/adaptors/oidcclient"
"github.com/int128/kubelogin/pkg/adaptors/oidcclient/mock_oidcclient"
"github.com/int128/kubelogin/pkg/jwt"
"github.com/int128/kubelogin/pkg/oidc"
"github.com/int128/kubelogin/pkg/testing/logger"
)
func TestBrowser_Do(t *testing.T) {
dummyTokenClaims := jwt.Claims{
Subject: "YOUR_SUBJECT",
Expiry: time.Date(2019, 1, 2, 3, 4, 5, 0, time.UTC),
Pretty: "PRETTY_JSON",
}
timeout := 5 * time.Second
t.Run("Success", func(t *testing.T) {
@@ -64,9 +58,8 @@ func TestBrowser_Do(t *testing.T) {
readyChan <- "LOCAL_SERVER_URL"
}).
Return(&oidc.TokenSet{
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenClaims: dummyTokenClaims,
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
}, nil)
u := Browser{
Logger: logger.New(t),
@@ -76,9 +69,8 @@ func TestBrowser_Do(t *testing.T) {
t.Errorf("Do returned error: %+v", err)
}
want := &oidc.TokenSet{
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenClaims: dummyTokenClaims,
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
}
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("mismatch (-want +got):\n%s", diff)
@@ -102,9 +94,8 @@ func TestBrowser_Do(t *testing.T) {
readyChan <- "LOCAL_SERVER_URL"
}).
Return(&oidc.TokenSet{
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenClaims: dummyTokenClaims,
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
}, nil)
mockBrowser := mock_browser.NewMockInterface(ctrl)
mockBrowser.EXPECT().
@@ -118,9 +109,8 @@ func TestBrowser_Do(t *testing.T) {
t.Errorf("Do returned error: %+v", err)
}
want := &oidc.TokenSet{
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenClaims: dummyTokenClaims,
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
}
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("mismatch (-want +got):\n%s", diff)

View File

@@ -10,7 +10,6 @@ import (
"github.com/int128/kubelogin/pkg/adaptors/oidcclient"
"github.com/int128/kubelogin/pkg/adaptors/oidcclient/mock_oidcclient"
"github.com/int128/kubelogin/pkg/adaptors/reader/mock_reader"
"github.com/int128/kubelogin/pkg/jwt"
"github.com/int128/kubelogin/pkg/oidc"
"github.com/int128/kubelogin/pkg/testing/logger"
)
@@ -18,11 +17,6 @@ import (
var nonNil = gomock.Not(gomock.Nil())
func TestKeyboard_Do(t *testing.T) {
dummyTokenClaims := jwt.Claims{
Subject: "YOUR_SUBJECT",
Expiry: time.Date(2019, 1, 2, 3, 4, 5, 0, time.UTC),
Pretty: "PRETTY_JSON",
}
timeout := 5 * time.Second
t.Run("Success", func(t *testing.T) {
@@ -51,9 +45,8 @@ func TestKeyboard_Do(t *testing.T) {
}
}).
Return(&oidc.TokenSet{
IDToken: "YOUR_ID_TOKEN",
IDTokenClaims: dummyTokenClaims,
RefreshToken: "YOUR_REFRESH_TOKEN",
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
}, nil)
mockReader := mock_reader.NewMockInterface(ctrl)
mockReader.EXPECT().
@@ -68,9 +61,8 @@ func TestKeyboard_Do(t *testing.T) {
t.Errorf("Do returned error: %+v", err)
}
want := &oidc.TokenSet{
IDToken: "YOUR_ID_TOKEN",
IDTokenClaims: dummyTokenClaims,
RefreshToken: "YOUR_REFRESH_TOKEN",
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
}
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("mismatch (-want +got):\n%s", diff)

View File

@@ -7,7 +7,6 @@ import (
"github.com/int128/kubelogin/pkg/adaptors/clock"
"github.com/int128/kubelogin/pkg/adaptors/logger"
"github.com/int128/kubelogin/pkg/adaptors/oidcclient"
"github.com/int128/kubelogin/pkg/jwt"
"github.com/int128/kubelogin/pkg/oidc"
"github.com/int128/kubelogin/pkg/usecases/authentication/authcode"
"github.com/int128/kubelogin/pkg/usecases/authentication/ropc"
@@ -32,9 +31,8 @@ type Interface interface {
// Input represents an input DTO of the Authentication use-case.
type Input struct {
Provider oidc.Provider
IDToken string // optional, from the token cache
RefreshToken string // optional, from the token cache
GrantOptionSet GrantOptionSet
CachedTokenSet *oidc.TokenSet // optional
}
type GrantOptionSet struct {
@@ -72,12 +70,12 @@ type Authentication struct {
}
func (u *Authentication) Do(ctx context.Context, in Input) (*Output, error) {
if in.IDToken != "" {
if in.CachedTokenSet != nil {
u.Logger.V(1).Infof("checking expiration of the existing token")
// Skip verification of the token to reduce time of a discovery request.
// Here it trusts the signature and claims and checks only expiration,
// because the token has been verified before caching.
claims, err := jwt.DecodeWithoutVerify(in.IDToken)
claims, err := in.CachedTokenSet.DecodeWithoutVerify()
if err != nil {
return nil, xerrors.Errorf("invalid token cache (you may need to remove): %w", err)
}
@@ -85,11 +83,7 @@ func (u *Authentication) Do(ctx context.Context, in Input) (*Output, error) {
u.Logger.V(1).Infof("you already have a valid token until %s", claims.Expiry)
return &Output{
AlreadyHasValidIDToken: true,
TokenSet: oidc.TokenSet{
IDToken: in.IDToken,
RefreshToken: in.RefreshToken,
IDTokenClaims: *claims,
},
TokenSet: *in.CachedTokenSet,
}, nil
}
u.Logger.V(1).Infof("you have an expired token at %s", claims.Expiry)
@@ -101,17 +95,11 @@ func (u *Authentication) Do(ctx context.Context, in Input) (*Output, error) {
return nil, xerrors.Errorf("oidc error: %w", err)
}
if in.RefreshToken != "" {
if in.CachedTokenSet != nil && in.CachedTokenSet.RefreshToken != "" {
u.Logger.V(1).Infof("refreshing the token")
out, err := client.Refresh(ctx, in.RefreshToken)
tokenSet, err := client.Refresh(ctx, in.CachedTokenSet.RefreshToken)
if err == nil {
return &Output{
TokenSet: oidc.TokenSet{
IDToken: out.IDToken,
IDTokenClaims: out.IDTokenClaims,
RefreshToken: out.RefreshToken,
},
}, nil
return &Output{TokenSet: *tokenSet}, nil
}
u.Logger.V(1).Infof("could not refresh the token: %s", err)
}

View File

@@ -9,7 +9,6 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/int128/kubelogin/pkg/adaptors/oidcclient"
"github.com/int128/kubelogin/pkg/adaptors/oidcclient/mock_oidcclient"
"github.com/int128/kubelogin/pkg/jwt"
"github.com/int128/kubelogin/pkg/oidc"
"github.com/int128/kubelogin/pkg/testing/clock"
testingJWT "github.com/int128/kubelogin/pkg/testing/jwt"
@@ -27,16 +26,11 @@ func TestAuthentication_Do(t *testing.T) {
ClientID: "YOUR_CLIENT_ID",
ClientSecret: "YOUR_CLIENT_SECRET",
}
cachedIDToken := testingJWT.EncodeF(t, func(claims *testingJWT.Claims) {
claims.Issuer = "https://issuer.example.com"
claims.Subject = "SUBJECT"
issuedIDToken := testingJWT.EncodeF(t, func(claims *testingJWT.Claims) {
claims.Issuer = "https://accounts.google.com"
claims.Subject = "YOUR_SUBJECT"
claims.ExpiresAt = expiryTime.Unix()
})
dummyClaims := jwt.Claims{
Subject: "SUBJECT",
Expiry: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
Pretty: "PRETTY_JSON",
}
t.Run("HasValidIDToken", func(t *testing.T) {
ctrl := gomock.NewController(t)
@@ -45,7 +39,9 @@ func TestAuthentication_Do(t *testing.T) {
defer cancel()
in := Input{
Provider: dummyProvider,
IDToken: cachedIDToken,
CachedTokenSet: &oidc.TokenSet{
IDToken: issuedIDToken,
},
}
u := Authentication{
Logger: testingLogger.New(t),
@@ -58,16 +54,7 @@ func TestAuthentication_Do(t *testing.T) {
want := &Output{
AlreadyHasValidIDToken: true,
TokenSet: oidc.TokenSet{
IDToken: cachedIDToken,
IDTokenClaims: jwt.Claims{
Subject: "SUBJECT",
Expiry: expiryTime,
Pretty: `{
"exp": 1577934245,
"iss": "https://issuer.example.com",
"sub": "SUBJECT"
}`,
},
IDToken: issuedIDToken,
},
}
if diff := cmp.Diff(want, got); diff != "" {
@@ -81,17 +68,18 @@ func TestAuthentication_Do(t *testing.T) {
ctx, cancel := context.WithTimeout(context.TODO(), timeout)
defer cancel()
in := Input{
Provider: dummyProvider,
IDToken: cachedIDToken,
RefreshToken: "VALID_REFRESH_TOKEN",
Provider: dummyProvider,
CachedTokenSet: &oidc.TokenSet{
IDToken: issuedIDToken,
RefreshToken: "VALID_REFRESH_TOKEN",
},
}
mockOIDCClient := mock_oidcclient.NewMockInterface(ctrl)
mockOIDCClient.EXPECT().
Refresh(ctx, "VALID_REFRESH_TOKEN").
Return(&oidc.TokenSet{
IDToken: "NEW_ID_TOKEN",
RefreshToken: "NEW_REFRESH_TOKEN",
IDTokenClaims: dummyClaims,
IDToken: "NEW_ID_TOKEN",
RefreshToken: "NEW_REFRESH_TOKEN",
}, nil)
u := Authentication{
OIDCClient: &oidcclientFactory{
@@ -112,9 +100,8 @@ func TestAuthentication_Do(t *testing.T) {
}
want := &Output{
TokenSet: oidc.TokenSet{
IDToken: "NEW_ID_TOKEN",
RefreshToken: "NEW_REFRESH_TOKEN",
IDTokenClaims: dummyClaims,
IDToken: "NEW_ID_TOKEN",
RefreshToken: "NEW_REFRESH_TOKEN",
},
}
if diff := cmp.Diff(want, got); diff != "" {
@@ -128,6 +115,7 @@ func TestAuthentication_Do(t *testing.T) {
ctx, cancel := context.WithTimeout(context.TODO(), timeout)
defer cancel()
in := Input{
Provider: dummyProvider,
GrantOptionSet: GrantOptionSet{
AuthCodeBrowserOption: &authcode.BrowserOption{
BindAddress: []string{"127.0.0.1:8000"},
@@ -135,9 +123,10 @@ func TestAuthentication_Do(t *testing.T) {
AuthenticationTimeout: 10 * time.Second,
},
},
Provider: dummyProvider,
IDToken: cachedIDToken,
RefreshToken: "EXPIRED_REFRESH_TOKEN",
CachedTokenSet: &oidc.TokenSet{
IDToken: issuedIDToken,
RefreshToken: "EXPIRED_REFRESH_TOKEN",
},
}
mockOIDCClient := mock_oidcclient.NewMockInterface(ctrl)
mockOIDCClient.EXPECT().SupportedPKCEMethods()
@@ -150,9 +139,8 @@ func TestAuthentication_Do(t *testing.T) {
readyChan <- "LOCAL_SERVER_URL"
}).
Return(&oidc.TokenSet{
IDToken: "NEW_ID_TOKEN",
RefreshToken: "NEW_REFRESH_TOKEN",
IDTokenClaims: dummyClaims,
IDToken: "NEW_ID_TOKEN",
RefreshToken: "NEW_REFRESH_TOKEN",
}, nil)
u := Authentication{
OIDCClient: &oidcclientFactory{
@@ -176,9 +164,8 @@ func TestAuthentication_Do(t *testing.T) {
}
want := &Output{
TokenSet: oidc.TokenSet{
IDToken: "NEW_ID_TOKEN",
RefreshToken: "NEW_REFRESH_TOKEN",
IDTokenClaims: dummyClaims,
IDToken: "NEW_ID_TOKEN",
RefreshToken: "NEW_REFRESH_TOKEN",
},
}
if diff := cmp.Diff(want, got); diff != "" {
@@ -204,9 +191,8 @@ func TestAuthentication_Do(t *testing.T) {
mockOIDCClient.EXPECT().
GetTokenByROPC(gomock.Any(), "USER", "PASS").
Return(&oidc.TokenSet{
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenClaims: dummyClaims,
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
}, nil)
u := Authentication{
OIDCClient: &oidcclientFactory{
@@ -229,9 +215,8 @@ func TestAuthentication_Do(t *testing.T) {
}
want := &Output{
TokenSet: oidc.TokenSet{
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenClaims: dummyClaims,
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
},
}
if diff := cmp.Diff(want, got); diff != "" {

View File

@@ -9,18 +9,12 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/int128/kubelogin/pkg/adaptors/oidcclient/mock_oidcclient"
"github.com/int128/kubelogin/pkg/adaptors/reader/mock_reader"
"github.com/int128/kubelogin/pkg/jwt"
"github.com/int128/kubelogin/pkg/oidc"
"github.com/int128/kubelogin/pkg/testing/logger"
"golang.org/x/xerrors"
)
func TestROPC_Do(t *testing.T) {
dummyTokenClaims := jwt.Claims{
Subject: "YOUR_SUBJECT",
Expiry: time.Date(2019, 1, 2, 3, 4, 5, 0, time.UTC),
Pretty: "PRETTY_JSON",
}
timeout := 5 * time.Second
t.Run("AskUsernameAndPassword", func(t *testing.T) {
@@ -33,9 +27,8 @@ func TestROPC_Do(t *testing.T) {
mockOIDCClient.EXPECT().
GetTokenByROPC(gomock.Any(), "USER", "PASS").
Return(&oidc.TokenSet{
IDToken: "YOUR_ID_TOKEN",
IDTokenClaims: dummyTokenClaims,
RefreshToken: "YOUR_REFRESH_TOKEN",
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
}, nil)
mockReader := mock_reader.NewMockInterface(ctrl)
mockReader.EXPECT().ReadString(usernamePrompt).Return("USER", nil)
@@ -49,9 +42,8 @@ func TestROPC_Do(t *testing.T) {
t.Errorf("Do returned error: %+v", err)
}
want := &oidc.TokenSet{
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenClaims: dummyTokenClaims,
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
}
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("mismatch (-want +got):\n%s", diff)
@@ -71,9 +63,8 @@ func TestROPC_Do(t *testing.T) {
mockOIDCClient.EXPECT().
GetTokenByROPC(gomock.Any(), "USER", "PASS").
Return(&oidc.TokenSet{
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenClaims: dummyTokenClaims,
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
}, nil)
u := ROPC{
Logger: logger.New(t),
@@ -83,9 +74,8 @@ func TestROPC_Do(t *testing.T) {
t.Errorf("Do returned error: %+v", err)
}
want := &oidc.TokenSet{
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenClaims: dummyTokenClaims,
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
}
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("mismatch (-want +got):\n%s", diff)
@@ -104,9 +94,8 @@ func TestROPC_Do(t *testing.T) {
mockOIDCClient.EXPECT().
GetTokenByROPC(gomock.Any(), "USER", "PASS").
Return(&oidc.TokenSet{
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenClaims: dummyTokenClaims,
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
}, nil)
mockEnv := mock_reader.NewMockInterface(ctrl)
mockEnv.EXPECT().ReadPassword(passwordPrompt).Return("PASS", nil)
@@ -119,9 +108,8 @@ func TestROPC_Do(t *testing.T) {
t.Errorf("Do returned error: %+v", err)
}
want := &oidc.TokenSet{
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenClaims: dummyTokenClaims,
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
}
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("mismatch (-want +got):\n%s", diff)

View File

@@ -50,18 +50,7 @@ type GetToken struct {
func (u *GetToken) Do(ctx context.Context, in Input) error {
u.Logger.V(1).Infof("WARNING: log may contain your secrets such as token or password")
out, err := u.getTokenFromCacheOrProvider(ctx, in)
if err != nil {
return xerrors.Errorf("could not get a token: %w", err)
}
u.Logger.V(1).Infof("writing the token to client-go")
if err := u.Writer.Write(credentialpluginwriter.Output{Token: out.TokenSet.IDToken, Expiry: out.TokenSet.IDTokenClaims.Expiry}); err != nil {
return xerrors.Errorf("could not write the token to client-go: %w", err)
}
return nil
}
func (u *GetToken) getTokenFromCacheOrProvider(ctx context.Context, in Input) (*authentication.Output, error) {
u.Logger.V(1).Infof("finding a token from cache directory %s", in.TokenCacheDir)
tokenCacheKey := tokencache.Key{
IssuerURL: in.IssuerURL,
@@ -72,20 +61,19 @@ func (u *GetToken) getTokenFromCacheOrProvider(ctx context.Context, in Input) (*
CACertData: in.CACertData,
SkipTLSVerify: in.SkipTLSVerify,
}
tokenCacheValue, err := u.TokenCacheRepository.FindByKey(in.TokenCacheDir, tokenCacheKey)
cachedTokenSet, err := u.TokenCacheRepository.FindByKey(in.TokenCacheDir, tokenCacheKey)
if err != nil {
u.Logger.V(1).Infof("could not find a token cache: %s", err)
tokenCacheValue = &tokencache.Value{}
}
certPool := u.NewCertPool()
if in.CACertFilename != "" {
if err := certPool.AddFile(in.CACertFilename); err != nil {
return nil, xerrors.Errorf("could not load the certificate file: %w", err)
return xerrors.Errorf("could not load the certificate file: %w", err)
}
}
if in.CACertData != "" {
if err := certPool.AddBase64Encoded(in.CACertData); err != nil {
return nil, xerrors.Errorf("could not load the certificate data: %w", err)
return xerrors.Errorf("could not load the certificate data: %w", err)
}
}
out, err := u.Authentication.Do(ctx, authentication.Input{
@@ -97,26 +85,33 @@ func (u *GetToken) getTokenFromCacheOrProvider(ctx context.Context, in Input) (*
CertPool: certPool,
SkipTLSVerify: in.SkipTLSVerify,
},
IDToken: tokenCacheValue.IDToken,
RefreshToken: tokenCacheValue.RefreshToken,
GrantOptionSet: in.GrantOptionSet,
CachedTokenSet: cachedTokenSet,
})
if err != nil {
return nil, xerrors.Errorf("authentication error: %w", err)
return xerrors.Errorf("authentication error: %w", err)
}
u.Logger.V(1).Infof("you got a token: %s", out.TokenSet.IDTokenClaims.Pretty)
idTokenClaims, err := out.TokenSet.DecodeWithoutVerify()
if err != nil {
return xerrors.Errorf("you got an invalid token: %w", err)
}
u.Logger.V(1).Infof("you got a token: %s", idTokenClaims.Pretty)
if out.AlreadyHasValidIDToken {
u.Logger.V(1).Infof("you already have a valid token until %s", out.TokenSet.IDTokenClaims.Expiry)
return out, nil
u.Logger.V(1).Infof("you already have a valid token until %s", idTokenClaims.Expiry)
u.Logger.V(1).Infof("writing the token to client-go")
if err := u.Writer.Write(credentialpluginwriter.Output{Token: out.TokenSet.IDToken, Expiry: idTokenClaims.Expiry}); err != nil {
return xerrors.Errorf("could not write the token to client-go: %w", err)
}
return nil
}
u.Logger.V(1).Infof("you got a valid token until %s", out.TokenSet.IDTokenClaims.Expiry)
newTokenCacheValue := tokencache.Value{
IDToken: out.TokenSet.IDToken,
RefreshToken: out.TokenSet.RefreshToken,
u.Logger.V(1).Infof("you got a valid token until %s", idTokenClaims.Expiry)
if err := u.TokenCacheRepository.Save(in.TokenCacheDir, tokenCacheKey, out.TokenSet); err != nil {
return xerrors.Errorf("could not write the token cache: %w", err)
}
if err := u.TokenCacheRepository.Save(in.TokenCacheDir, tokenCacheKey, newTokenCacheValue); err != nil {
return nil, xerrors.Errorf("could not write the token cache: %w", err)
u.Logger.V(1).Infof("writing the token to client-go")
if err := u.Writer.Write(credentialpluginwriter.Output{Token: out.TokenSet.IDToken, Expiry: idTokenClaims.Expiry}); err != nil {
return xerrors.Errorf("could not write the token to client-go: %w", err)
}
return out, nil
return nil
}

View File

@@ -12,8 +12,8 @@ import (
"github.com/int128/kubelogin/pkg/adaptors/credentialpluginwriter/mock_credentialpluginwriter"
"github.com/int128/kubelogin/pkg/adaptors/tokencache"
"github.com/int128/kubelogin/pkg/adaptors/tokencache/mock_tokencache"
"github.com/int128/kubelogin/pkg/jwt"
"github.com/int128/kubelogin/pkg/oidc"
testingJWT "github.com/int128/kubelogin/pkg/testing/jwt"
"github.com/int128/kubelogin/pkg/testing/logger"
"github.com/int128/kubelogin/pkg/usecases/authentication"
"github.com/int128/kubelogin/pkg/usecases/authentication/mock_authentication"
@@ -21,11 +21,12 @@ import (
)
func TestGetToken_Do(t *testing.T) {
dummyTokenClaims := jwt.Claims{
Subject: "YOUR_SUBJECT",
Expiry: time.Date(2019, 1, 2, 3, 4, 5, 0, time.UTC),
Pretty: "PRETTY_JSON",
}
issuedIDTokenExpiration := time.Now().Add(1 * time.Hour).Round(time.Second)
issuedIDToken := testingJWT.EncodeF(t, func(claims *testingJWT.Claims) {
claims.Issuer = "https://accounts.google.com"
claims.Subject = "YOUR_SUBJECT"
claims.ExpiresAt = issuedIDTokenExpiration.Unix()
})
t.Run("FullOptions", func(t *testing.T) {
var grantOptionSet authentication.GrantOptionSet
@@ -61,9 +62,8 @@ func TestGetToken_Do(t *testing.T) {
}).
Return(&authentication.Output{
TokenSet: oidc.TokenSet{
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenClaims: dummyTokenClaims,
IDToken: issuedIDToken,
RefreshToken: "YOUR_REFRESH_TOKEN",
},
}, nil)
tokenCacheRepository := mock_tokencache.NewMockInterface(ctrl)
@@ -88,15 +88,15 @@ func TestGetToken_Do(t *testing.T) {
CACertData: "BASE64ENCODED",
SkipTLSVerify: true,
},
tokencache.Value{
IDToken: "YOUR_ID_TOKEN",
oidc.TokenSet{
IDToken: issuedIDToken,
RefreshToken: "YOUR_REFRESH_TOKEN",
})
credentialPluginWriter := mock_credentialpluginwriter.NewMockInterface(ctrl)
credentialPluginWriter.EXPECT().
Write(credentialpluginwriter.Output{
Token: "YOUR_ID_TOKEN",
Expiry: dummyTokenClaims.Expiry,
Token: issuedIDToken,
Expiry: issuedIDTokenExpiration,
})
u := GetToken{
Authentication: mockAuthentication,
@@ -130,13 +130,14 @@ func TestGetToken_Do(t *testing.T) {
ClientSecret: "YOUR_CLIENT_SECRET",
CertPool: mockCertPool,
},
IDToken: "VALID_ID_TOKEN",
CachedTokenSet: &oidc.TokenSet{
IDToken: issuedIDToken,
},
}).
Return(&authentication.Output{
AlreadyHasValidIDToken: true,
TokenSet: oidc.TokenSet{
IDToken: "VALID_ID_TOKEN",
IDTokenClaims: dummyTokenClaims,
IDToken: issuedIDToken,
},
}, nil)
tokenCacheRepository := mock_tokencache.NewMockInterface(ctrl)
@@ -146,14 +147,14 @@ func TestGetToken_Do(t *testing.T) {
ClientID: "YOUR_CLIENT_ID",
ClientSecret: "YOUR_CLIENT_SECRET",
}).
Return(&tokencache.Value{
IDToken: "VALID_ID_TOKEN",
Return(&oidc.TokenSet{
IDToken: issuedIDToken,
}, nil)
credentialPluginWriter := mock_credentialpluginwriter.NewMockInterface(ctrl)
credentialPluginWriter.EXPECT().
Write(credentialpluginwriter.Output{
Token: "VALID_ID_TOKEN",
Expiry: dummyTokenClaims.Expiry,
Token: issuedIDToken,
Expiry: issuedIDTokenExpiration,
})
u := GetToken{
Authentication: mockAuthentication,

View File

@@ -106,13 +106,17 @@ func (u *Setup) DoStage2(ctx context.Context, in Stage2Input) error {
if err != nil {
return xerrors.Errorf("authentication error: %w", err)
}
idTokenClaims, err := out.TokenSet.DecodeWithoutVerify()
if err != nil {
return xerrors.Errorf("you got an invalid token: %w", err)
}
v := stage2Vars{
IDTokenPrettyJSON: out.TokenSet.IDTokenClaims.Pretty,
IDTokenPrettyJSON: idTokenClaims.Pretty,
IssuerURL: in.IssuerURL,
ClientID: in.ClientID,
Args: makeCredentialPluginArgs(in),
Subject: out.TokenSet.IDTokenClaims.Subject,
Subject: idTokenClaims.Subject,
}
var b strings.Builder
if err := stage2Tpl.Execute(&b, &v); err != nil {

View File

@@ -8,14 +8,20 @@ import (
"github.com/golang/mock/gomock"
"github.com/int128/kubelogin/pkg/adaptors/certpool"
"github.com/int128/kubelogin/pkg/adaptors/certpool/mock_certpool"
"github.com/int128/kubelogin/pkg/jwt"
"github.com/int128/kubelogin/pkg/oidc"
testingJWT "github.com/int128/kubelogin/pkg/testing/jwt"
"github.com/int128/kubelogin/pkg/testing/logger"
"github.com/int128/kubelogin/pkg/usecases/authentication"
"github.com/int128/kubelogin/pkg/usecases/authentication/mock_authentication"
)
func TestSetup_DoStage2(t *testing.T) {
issuedIDToken := testingJWT.EncodeF(t, func(claims *testingJWT.Claims) {
claims.Issuer = "https://issuer.example.com"
claims.Subject = "YOUR_SUBJECT"
claims.ExpiresAt = time.Now().Add(1 * time.Hour).Unix()
})
var grantOptionSet authentication.GrantOptionSet
ctrl := gomock.NewController(t)
defer ctrl.Finish()
@@ -49,13 +55,8 @@ func TestSetup_DoStage2(t *testing.T) {
}).
Return(&authentication.Output{
TokenSet: oidc.TokenSet{
IDToken: "YOUR_ID_TOKEN",
IDToken: issuedIDToken,
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenClaims: jwt.Claims{
Subject: "YOUR_SUBJECT",
Expiry: time.Date(2019, 1, 2, 3, 4, 5, 0, time.UTC),
Pretty: "PRETTY_JSON",
},
},
}, nil)
u := Setup{

View File

@@ -94,6 +94,13 @@ func (u *Standalone) Do(ctx context.Context, in Input) error {
return xerrors.Errorf("could not load the certificate data: %w", err)
}
}
var cachedTokenSet *oidc.TokenSet
if authProvider.IDToken != "" {
cachedTokenSet = &oidc.TokenSet{
IDToken: authProvider.IDToken,
RefreshToken: authProvider.RefreshToken,
}
}
out, err := u.Authentication.Do(ctx, authentication.Input{
Provider: oidc.Provider{
IssuerURL: authProvider.IDPIssuerURL,
@@ -103,20 +110,23 @@ func (u *Standalone) Do(ctx context.Context, in Input) error {
CertPool: certPool,
SkipTLSVerify: in.SkipTLSVerify,
},
IDToken: authProvider.IDToken,
RefreshToken: authProvider.RefreshToken,
GrantOptionSet: in.GrantOptionSet,
CachedTokenSet: cachedTokenSet,
})
if err != nil {
return xerrors.Errorf("authentication error: %w", err)
}
u.Logger.V(1).Infof("you got a token: %s", out.TokenSet.IDTokenClaims.Pretty)
idTokenClaims, err := out.TokenSet.DecodeWithoutVerify()
if err != nil {
return xerrors.Errorf("you got an invalid token: %w", err)
}
u.Logger.V(1).Infof("you got a token: %s", idTokenClaims.Pretty)
if out.AlreadyHasValidIDToken {
u.Logger.Printf("You already have a valid token until %s", out.TokenSet.IDTokenClaims.Expiry)
u.Logger.Printf("You already have a valid token until %s", idTokenClaims.Expiry)
return nil
}
u.Logger.Printf("You got a valid token until %s", out.TokenSet.IDTokenClaims.Expiry)
u.Logger.Printf("You got a valid token until %s", idTokenClaims.Expiry)
authProvider.IDToken = out.TokenSet.IDToken
authProvider.RefreshToken = out.TokenSet.RefreshToken
u.Logger.V(1).Infof("writing the ID token and refresh token to %s", authProvider.LocationOfOrigin)

View File

@@ -10,8 +10,8 @@ import (
"github.com/int128/kubelogin/pkg/adaptors/certpool/mock_certpool"
"github.com/int128/kubelogin/pkg/adaptors/kubeconfig"
"github.com/int128/kubelogin/pkg/adaptors/kubeconfig/mock_kubeconfig"
"github.com/int128/kubelogin/pkg/jwt"
"github.com/int128/kubelogin/pkg/oidc"
testingJWT "github.com/int128/kubelogin/pkg/testing/jwt"
"github.com/int128/kubelogin/pkg/testing/logger"
"github.com/int128/kubelogin/pkg/usecases/authentication"
"github.com/int128/kubelogin/pkg/usecases/authentication/mock_authentication"
@@ -19,11 +19,12 @@ import (
)
func TestStandalone_Do(t *testing.T) {
dummyTokenClaims := jwt.Claims{
Subject: "YOUR_SUBJECT",
Expiry: time.Date(2019, 1, 2, 3, 4, 5, 0, time.UTC),
Pretty: "PRETTY_JSON",
}
issuedIDTokenExpiration := time.Now().Add(1 * time.Hour).Round(time.Second)
issuedIDToken := testingJWT.EncodeF(t, func(claims *testingJWT.Claims) {
claims.Issuer = "https://accounts.google.com"
claims.Subject = "YOUR_SUBJECT"
claims.ExpiresAt = issuedIDTokenExpiration.Unix()
})
t.Run("FullOptions", func(t *testing.T) {
var grantOptionSet authentication.GrantOptionSet
@@ -70,7 +71,7 @@ func TestStandalone_Do(t *testing.T) {
ClientSecret: "YOUR_CLIENT_SECRET",
IDPCertificateAuthority: "/path/to/cert2",
IDPCertificateAuthorityData: "BASE64ENCODED2",
IDToken: "YOUR_ID_TOKEN",
IDToken: issuedIDToken,
RefreshToken: "YOUR_REFRESH_TOKEN",
})
mockAuthentication := mock_authentication.NewMockInterface(ctrl)
@@ -87,9 +88,8 @@ func TestStandalone_Do(t *testing.T) {
}).
Return(&authentication.Output{
TokenSet: oidc.TokenSet{
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenClaims: dummyTokenClaims,
IDToken: issuedIDToken,
RefreshToken: "YOUR_REFRESH_TOKEN",
},
}, nil)
u := Standalone{
@@ -114,7 +114,7 @@ func TestStandalone_Do(t *testing.T) {
IDPIssuerURL: "https://accounts.google.com",
ClientID: "YOUR_CLIENT_ID",
ClientSecret: "YOUR_CLIENT_SECRET",
IDToken: "VALID_ID_TOKEN",
IDToken: issuedIDToken,
}
mockCertPool := mock_certpool.NewMockInterface(ctrl)
mockKubeconfig := mock_kubeconfig.NewMockInterface(ctrl)
@@ -130,13 +130,14 @@ func TestStandalone_Do(t *testing.T) {
ClientSecret: "YOUR_CLIENT_SECRET",
CertPool: mockCertPool,
},
IDToken: "VALID_ID_TOKEN",
CachedTokenSet: &oidc.TokenSet{
IDToken: issuedIDToken,
},
}).
Return(&authentication.Output{
AlreadyHasValidIDToken: true,
TokenSet: oidc.TokenSet{
IDToken: "VALID_ID_TOKEN",
IDTokenClaims: dummyTokenClaims,
IDToken: issuedIDToken,
},
}, nil)
u := Standalone{
@@ -233,7 +234,7 @@ func TestStandalone_Do(t *testing.T) {
IDPIssuerURL: "https://accounts.google.com",
ClientID: "YOUR_CLIENT_ID",
ClientSecret: "YOUR_CLIENT_SECRET",
IDToken: "YOUR_ID_TOKEN",
IDToken: issuedIDToken,
RefreshToken: "YOUR_REFRESH_TOKEN",
}).
Return(xerrors.New("I/O error"))
@@ -249,9 +250,8 @@ func TestStandalone_Do(t *testing.T) {
}).
Return(&authentication.Output{
TokenSet: oidc.TokenSet{
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenClaims: dummyTokenClaims,
IDToken: issuedIDToken,
RefreshToken: "YOUR_REFRESH_TOKEN",
},
}, nil)
u := Standalone{