Refactor: merge interface and implementation package (#141)

* Refactor: move logger interfaces

* Refactor: move oidc interfaces

* Refactor: move env interface

* Refactor: move credential plugin interface

* Refactor: move token cache interface

* Refactor: move kubeconfig interface

* Refactor: move cmd interface

* Refactor: move use-cases interfaces
This commit is contained in:
Hidetake Iwata
2019-08-28 22:55:28 +09:00
committed by GitHub
parent 53e8284b63
commit bf02210f2a
55 changed files with 1339 additions and 1409 deletions

View File

@@ -11,12 +11,11 @@ import (
"github.com/int128/kubelogin/e2e_test/idp"
"github.com/int128/kubelogin/e2e_test/idp/mock_idp"
"github.com/int128/kubelogin/e2e_test/localserver"
"github.com/int128/kubelogin/e2e_test/logger"
"github.com/int128/kubelogin/pkg/adaptors"
"github.com/int128/kubelogin/pkg/adaptors/mock_adaptors"
"github.com/int128/kubelogin/pkg/adaptors/credentialplugin"
"github.com/int128/kubelogin/pkg/adaptors/credentialplugin/mock_credentialplugin"
"github.com/int128/kubelogin/pkg/adaptors/logger/mock_logger"
"github.com/int128/kubelogin/pkg/di"
"github.com/int128/kubelogin/pkg/models/credentialplugin"
"github.com/int128/kubelogin/pkg/usecases"
"github.com/int128/kubelogin/pkg/usecases/auth"
)
// Run the integration tests of the credential plugin use-case.
@@ -51,7 +50,7 @@ func TestCmd_Run_CredentialPlugin(t *testing.T) {
var idToken string
setupMockIDPForCodeFlow(t, service, serverURL, "openid", &idToken)
credentialPluginInteraction := mock_adaptors.NewMockCredentialPluginInteraction(ctrl)
credentialPluginInteraction := mock_credentialplugin.NewMockInterface(ctrl)
credentialPluginInteraction.EXPECT().
Write(gomock.Any()).
Do(func(out credentialplugin.Output) {
@@ -75,9 +74,9 @@ func TestCmd_Run_CredentialPlugin(t *testing.T) {
})
}
func runGetTokenCmd(t *testing.T, ctx context.Context, s usecases.LoginShowLocalServerURL, interaction adaptors.CredentialPluginInteraction, args ...string) {
func runGetTokenCmd(t *testing.T, ctx context.Context, s auth.ShowLocalServerURLInterface, interaction credentialplugin.Interface, args ...string) {
t.Helper()
cmd := di.NewCmdForHeadless(logger.New(t), s, interaction)
cmd := di.NewCmdForHeadless(mock_logger.New(t), s, interaction)
exitCode := cmd.Run(ctx, append([]string{"kubelogin", "get-token", "--v=1"}, args...), "HEAD")
if exitCode != 0 {
t.Errorf("exit status wants 0 but %d", exitCode)

View File

@@ -16,9 +16,9 @@ import (
"github.com/int128/kubelogin/e2e_test/keys"
"github.com/int128/kubelogin/e2e_test/kubeconfig"
"github.com/int128/kubelogin/e2e_test/localserver"
"github.com/int128/kubelogin/e2e_test/logger"
"github.com/int128/kubelogin/pkg/adaptors/logger/mock_logger"
"github.com/int128/kubelogin/pkg/di"
"github.com/int128/kubelogin/pkg/usecases"
"github.com/int128/kubelogin/pkg/usecases/auth"
)
var (
@@ -33,7 +33,7 @@ var (
// 3. Open a request for the local server.
// 4. Verify the kubeconfig.
//
func TestCmd_Run_Login(t *testing.T) {
func TestCmd_Run_Standalone(t *testing.T) {
timeout := 1 * time.Second
type testParameter struct {
@@ -306,9 +306,9 @@ func setupMockIDPForCodeFlow(t *testing.T, service *mock_idp.MockService, server
})
}
func runCmd(t *testing.T, ctx context.Context, s usecases.LoginShowLocalServerURL, args ...string) {
func runCmd(t *testing.T, ctx context.Context, s auth.ShowLocalServerURLInterface, args ...string) {
t.Helper()
cmd := di.NewCmdForHeadless(logger.New(t), s, nil)
cmd := di.NewCmdForHeadless(mock_logger.New(t), s, nil)
exitCode := cmd.Run(ctx, append([]string{"kubelogin", "--v=1"}, args...), "HEAD")
if exitCode != 0 {
t.Errorf("exit status wants 0 but %d", exitCode)

View File

@@ -5,7 +5,7 @@ import (
"path/filepath"
"github.com/google/wire"
"github.com/int128/kubelogin/pkg/adaptors"
"github.com/int128/kubelogin/pkg/adaptors/logger"
"github.com/spf13/cobra"
"k8s.io/client-go/util/homedir"
)
@@ -13,11 +13,15 @@ import (
// Set provides an implementation and interface for Cmd.
var Set = wire.NewSet(
wire.Struct(new(Cmd), "*"),
wire.Bind(new(adaptors.Cmd), new(*Cmd)),
wire.Bind(new(Interface), new(*Cmd)),
wire.Struct(new(Root), "*"),
wire.Struct(new(GetToken), "*"),
)
type Interface interface {
Run(ctx context.Context, args []string, version string) int
}
const examples = ` # Login to the provider using the authorization code flow.
%[1]s
@@ -34,7 +38,7 @@ var defaultTokenCacheDir = homedir.HomeDir() + "/.kube/cache/oidc-login"
type Cmd struct {
Root *Root
GetToken *GetToken
Logger adaptors.Logger
Logger logger.Interface
}
// Run parses the command line arguments and executes the specified use-case.

View File

@@ -5,9 +5,11 @@ import (
"testing"
"github.com/golang/mock/gomock"
"github.com/int128/kubelogin/pkg/adaptors/mock_adaptors"
"github.com/int128/kubelogin/pkg/usecases"
"github.com/int128/kubelogin/pkg/usecases/mock_usecases"
"github.com/int128/kubelogin/pkg/adaptors/logger/mock_logger"
"github.com/int128/kubelogin/pkg/usecases/credentialplugin"
"github.com/int128/kubelogin/pkg/usecases/credentialplugin/mock_credentialplugin"
"github.com/int128/kubelogin/pkg/usecases/standalone"
"github.com/int128/kubelogin/pkg/usecases/standalone/mock_standalone"
)
func TestCmd_Run(t *testing.T) {
@@ -19,18 +21,18 @@ func TestCmd_Run(t *testing.T) {
defer ctrl.Finish()
ctx := context.TODO()
login := mock_usecases.NewMockLogin(ctrl)
login.EXPECT().
Do(ctx, usecases.LoginIn{
mockStandalone := mock_standalone.NewMockInterface(ctrl)
mockStandalone.EXPECT().
Do(ctx, standalone.Input{
ListenPort: defaultListenPort,
})
cmd := Cmd{
Root: &Root{
Login: login,
Logger: mock_adaptors.NewLogger(t),
Standalone: mockStandalone,
Logger: mock_logger.New(t),
},
Logger: mock_adaptors.NewLogger(t),
Logger: mock_logger.New(t),
}
exitCode := cmd.Run(ctx, []string{executable}, version)
if exitCode != 0 {
@@ -43,9 +45,9 @@ func TestCmd_Run(t *testing.T) {
defer ctrl.Finish()
ctx := context.TODO()
login := mock_usecases.NewMockLogin(ctrl)
login.EXPECT().
Do(ctx, usecases.LoginIn{
mockStandalone := mock_standalone.NewMockInterface(ctrl)
mockStandalone.EXPECT().
Do(ctx, standalone.Input{
KubeconfigFilename: "/path/to/kubeconfig",
KubeconfigContext: "hello.k8s.local",
KubeconfigUser: "google",
@@ -59,10 +61,10 @@ func TestCmd_Run(t *testing.T) {
cmd := Cmd{
Root: &Root{
Login: login,
Logger: mock_adaptors.NewLogger(t),
Standalone: mockStandalone,
Logger: mock_logger.New(t),
},
Logger: mock_adaptors.NewLogger(t),
Logger: mock_logger.New(t),
}
exitCode := cmd.Run(ctx, []string{executable,
"--kubeconfig", "/path/to/kubeconfig",
@@ -87,10 +89,10 @@ func TestCmd_Run(t *testing.T) {
defer ctrl.Finish()
cmd := Cmd{
Root: &Root{
Login: mock_usecases.NewMockLogin(ctrl),
Logger: mock_adaptors.NewLogger(t),
Standalone: mock_standalone.NewMockInterface(ctrl),
Logger: mock_logger.New(t),
},
Logger: mock_adaptors.NewLogger(t),
Logger: mock_logger.New(t),
}
exitCode := cmd.Run(context.TODO(), []string{executable, "some"}, version)
if exitCode != 1 {
@@ -103,9 +105,9 @@ func TestCmd_Run(t *testing.T) {
defer ctrl.Finish()
ctx := context.TODO()
getToken := mock_usecases.NewMockGetToken(ctrl)
getToken := mock_credentialplugin.NewMockInterface(ctrl)
getToken.EXPECT().
Do(ctx, usecases.GetTokenIn{
Do(ctx, credentialplugin.Input{
ListenPort: defaultListenPort,
TokenCacheDir: defaultTokenCacheDir,
IssuerURL: "https://issuer.example.com",
@@ -114,13 +116,13 @@ func TestCmd_Run(t *testing.T) {
cmd := Cmd{
Root: &Root{
Logger: mock_adaptors.NewLogger(t),
Logger: mock_logger.New(t),
},
GetToken: &GetToken{
GetToken: getToken,
Logger: mock_adaptors.NewLogger(t),
Logger: mock_logger.New(t),
},
Logger: mock_adaptors.NewLogger(t),
Logger: mock_logger.New(t),
}
exitCode := cmd.Run(ctx, []string{executable,
"get-token",
@@ -137,9 +139,9 @@ func TestCmd_Run(t *testing.T) {
defer ctrl.Finish()
ctx := context.TODO()
getToken := mock_usecases.NewMockGetToken(ctrl)
getToken := mock_credentialplugin.NewMockInterface(ctrl)
getToken.EXPECT().
Do(ctx, usecases.GetTokenIn{
Do(ctx, credentialplugin.Input{
TokenCacheDir: defaultTokenCacheDir,
IssuerURL: "https://issuer.example.com",
ClientID: "YOUR_CLIENT_ID",
@@ -155,13 +157,13 @@ func TestCmd_Run(t *testing.T) {
cmd := Cmd{
Root: &Root{
Logger: mock_adaptors.NewLogger(t),
Logger: mock_logger.New(t),
},
GetToken: &GetToken{
GetToken: getToken,
Logger: mock_adaptors.NewLogger(t),
Logger: mock_logger.New(t),
},
Logger: mock_adaptors.NewLogger(t),
Logger: mock_logger.New(t),
}
exitCode := cmd.Run(ctx, []string{executable,
"get-token",
@@ -190,13 +192,13 @@ func TestCmd_Run(t *testing.T) {
ctx := context.TODO()
cmd := Cmd{
Root: &Root{
Logger: mock_adaptors.NewLogger(t),
Logger: mock_logger.New(t),
},
GetToken: &GetToken{
GetToken: mock_usecases.NewMockGetToken(ctrl),
Logger: mock_adaptors.NewLogger(t),
GetToken: mock_credentialplugin.NewMockInterface(ctrl),
Logger: mock_logger.New(t),
},
Logger: mock_adaptors.NewLogger(t),
Logger: mock_logger.New(t),
}
exitCode := cmd.Run(ctx, []string{executable, "get-token"}, version)
if exitCode != 1 {
@@ -210,13 +212,13 @@ func TestCmd_Run(t *testing.T) {
ctx := context.TODO()
cmd := Cmd{
Root: &Root{
Logger: mock_adaptors.NewLogger(t),
Logger: mock_logger.New(t),
},
GetToken: &GetToken{
GetToken: mock_usecases.NewMockGetToken(ctrl),
Logger: mock_adaptors.NewLogger(t),
GetToken: mock_credentialplugin.NewMockInterface(ctrl),
Logger: mock_logger.New(t),
},
Logger: mock_adaptors.NewLogger(t),
Logger: mock_logger.New(t),
}
exitCode := cmd.Run(ctx, []string{executable, "get-token", "foo"}, version)
if exitCode != 1 {

View File

@@ -3,8 +3,8 @@ package cmd
import (
"context"
"github.com/int128/kubelogin/pkg/adaptors"
"github.com/int128/kubelogin/pkg/usecases"
"github.com/int128/kubelogin/pkg/adaptors/logger"
"github.com/int128/kubelogin/pkg/usecases/credentialplugin"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"golang.org/x/xerrors"
@@ -36,8 +36,8 @@ func (o *getTokenOptions) register(f *pflag.FlagSet) {
}
type GetToken struct {
GetToken usecases.GetToken
Logger adaptors.Logger
GetToken credentialplugin.Interface
Logger logger.Interface
}
func (cmd *GetToken) New(ctx context.Context) *cobra.Command {
@@ -58,7 +58,7 @@ func (cmd *GetToken) New(ctx context.Context) *cobra.Command {
return nil
},
RunE: func(*cobra.Command, []string) error {
in := usecases.GetTokenIn{
in := credentialplugin.Input{
IssuerURL: o.IssuerURL,
ClientID: o.ClientID,
ClientSecret: o.ClientSecret,

View File

@@ -4,9 +4,9 @@ import (
"context"
"fmt"
"github.com/int128/kubelogin/pkg/adaptors"
"github.com/int128/kubelogin/pkg/models/kubeconfig"
"github.com/int128/kubelogin/pkg/usecases"
"github.com/int128/kubelogin/pkg/adaptors/kubeconfig"
"github.com/int128/kubelogin/pkg/adaptors/logger"
"github.com/int128/kubelogin/pkg/usecases/standalone"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"golang.org/x/xerrors"
@@ -47,8 +47,8 @@ func (o *loginOptions) register(f *pflag.FlagSet) {
}
type Root struct {
Login usecases.Login
Logger adaptors.Logger
Standalone standalone.Interface
Logger logger.Interface
}
func (cmd *Root) New(ctx context.Context, executable string) *cobra.Command {
@@ -62,7 +62,7 @@ func (cmd *Root) New(ctx context.Context, executable string) *cobra.Command {
Example: fmt.Sprintf(examples, executable),
Args: cobra.NoArgs,
RunE: func(*cobra.Command, []string) error {
in := usecases.LoginIn{
in := standalone.Input{
KubeconfigFilename: o.Kubeconfig,
KubeconfigContext: kubeconfig.ContextName(o.Context),
KubeconfigUser: kubeconfig.UserName(o.User),
@@ -73,7 +73,7 @@ func (cmd *Root) New(ctx context.Context, executable string) *cobra.Command {
Username: o.Username,
Password: o.Password,
}
if err := cmd.Login.Do(ctx, in); err != nil {
if err := cmd.Standalone.Do(ctx, in); err != nil {
return xerrors.Errorf("error: %w", err)
}
return nil

View File

@@ -4,24 +4,35 @@ package credentialplugin
import (
"encoding/json"
"os"
"time"
"github.com/google/wire"
"github.com/int128/kubelogin/pkg/adaptors"
"github.com/int128/kubelogin/pkg/models/credentialplugin"
"golang.org/x/xerrors"
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
)
//go:generate mockgen -destination mock_credentialplugin/mock_credentialplugin.go github.com/int128/kubelogin/pkg/adaptors/credentialplugin Interface
var Set = wire.NewSet(
wire.Struct(new(Interaction), "*"),
wire.Bind(new(adaptors.CredentialPluginInteraction), new(*Interaction)),
wire.Bind(new(Interface), new(*Interaction)),
)
type Interface interface {
Write(out Output) error
}
// Output represents an output object of the credential plugin.
type Output struct {
Token string
Expiry time.Time
}
type Interaction struct{}
// Write writes the ExecCredential to standard output for kubectl.
func (*Interaction) Write(out credentialplugin.Output) error {
func (*Interaction) Write(out Output) error {
ec := &v1beta1.ExecCredential{
TypeMeta: v1.TypeMeta{
APIVersion: "client.authentication.k8s.io/v1beta1",

View File

@@ -0,0 +1,46 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/int128/kubelogin/pkg/adaptors/credentialplugin (interfaces: Interface)
// Package mock_credentialplugin is a generated GoMock package.
package mock_credentialplugin
import (
gomock "github.com/golang/mock/gomock"
credentialplugin "github.com/int128/kubelogin/pkg/adaptors/credentialplugin"
reflect "reflect"
)
// MockInterface is a mock of Interface interface
type MockInterface struct {
ctrl *gomock.Controller
recorder *MockInterfaceMockRecorder
}
// MockInterfaceMockRecorder is the mock recorder for MockInterface
type MockInterfaceMockRecorder struct {
mock *MockInterface
}
// NewMockInterface creates a new mock instance
func NewMockInterface(ctrl *gomock.Controller) *MockInterface {
mock := &MockInterface{ctrl: ctrl}
mock.recorder = &MockInterfaceMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder {
return m.recorder
}
// Write mocks base method
func (m *MockInterface) Write(arg0 credentialplugin.Output) error {
ret := m.ctrl.Call(m, "Write", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// Write indicates an expected call of Write
func (mr *MockInterfaceMockRecorder) Write(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockInterface)(nil).Write), arg0)
}

View File

@@ -6,17 +6,22 @@ import (
"syscall"
"github.com/google/wire"
"github.com/int128/kubelogin/pkg/adaptors"
"golang.org/x/crypto/ssh/terminal"
"golang.org/x/xerrors"
)
//go:generate mockgen -destination mock_env/mock_env.go github.com/int128/kubelogin/pkg/adaptors/env Interface
// Set provides an implementation and interface for Env.
var Set = wire.NewSet(
wire.Struct(new(Env), "*"),
wire.Bind(new(adaptors.Env), new(*Env)),
wire.Bind(new(Interface), new(*Env)),
)
type Interface interface {
ReadPassword(prompt string) (string, error)
}
// Env provides environment specific facilities.
type Env struct{}

46
pkg/adaptors/env/mock_env/mock_env.go vendored Normal file
View File

@@ -0,0 +1,46 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/int128/kubelogin/pkg/adaptors/env (interfaces: Interface)
// Package mock_env is a generated GoMock package.
package mock_env
import (
gomock "github.com/golang/mock/gomock"
reflect "reflect"
)
// MockInterface is a mock of Interface interface
type MockInterface struct {
ctrl *gomock.Controller
recorder *MockInterfaceMockRecorder
}
// MockInterfaceMockRecorder is the mock recorder for MockInterface
type MockInterfaceMockRecorder struct {
mock *MockInterface
}
// NewMockInterface creates a new mock instance
func NewMockInterface(ctrl *gomock.Controller) *MockInterface {
mock := &MockInterface{ctrl: ctrl}
mock.recorder = &MockInterfaceMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder {
return m.recorder
}
// ReadPassword mocks base method
func (m *MockInterface) ReadPassword(arg0 string) (string, error) {
ret := m.ctrl.Call(m, "ReadPassword", arg0)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ReadPassword indicates an expected call of ReadPassword
func (mr *MockInterfaceMockRecorder) ReadPassword(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadPassword", reflect.TypeOf((*MockInterface)(nil).ReadPassword), arg0)
}

View File

@@ -1,107 +0,0 @@
package adaptors
import (
"context"
"time"
"github.com/int128/kubelogin/pkg/models/credentialplugin"
"github.com/int128/kubelogin/pkg/models/kubeconfig"
"github.com/spf13/pflag"
)
//go:generate mockgen -destination mock_adaptors/mock_adaptors.go github.com/int128/kubelogin/pkg/adaptors Kubeconfig,TokenCacheRepository,CredentialPluginInteraction,OIDC,OIDCClient,OIDCDecoder,Env,Logger
type Cmd interface {
Run(ctx context.Context, args []string, version string) int
}
type Kubeconfig interface {
GetCurrentAuthProvider(explicitFilename string, contextName kubeconfig.ContextName, userName kubeconfig.UserName) (*kubeconfig.AuthProvider, error)
UpdateAuthProvider(auth *kubeconfig.AuthProvider) error
}
type TokenCacheRepository interface {
FindByKey(dir string, key credentialplugin.TokenCacheKey) (*credentialplugin.TokenCache, error)
Save(dir string, key credentialplugin.TokenCacheKey, cache credentialplugin.TokenCache) error
}
type CredentialPluginInteraction interface {
Write(out credentialplugin.Output) error
}
type OIDC interface {
New(ctx context.Context, config OIDCClientConfig) (OIDCClient, error)
}
// OIDCClientConfig represents a configuration of an OIDCClient to create.
type OIDCClientConfig struct {
Config kubeconfig.OIDCConfig
CACertFilename string
SkipTLSVerify bool
}
type OIDCClient interface {
AuthenticateByCode(ctx context.Context, in OIDCAuthenticateByCodeIn) (*OIDCAuthenticateOut, error)
AuthenticateByPassword(ctx context.Context, in OIDCAuthenticateByPasswordIn) (*OIDCAuthenticateOut, error)
Refresh(ctx context.Context, in OIDCRefreshIn) (*OIDCAuthenticateOut, error)
}
// OIDCAuthenticateByCodeIn represents an input DTO of OIDCClient.AuthenticateByCode.
type OIDCAuthenticateByCodeIn struct {
LocalServerPort []int // HTTP server port candidates
SkipOpenBrowser bool // skip opening browser if true
ShowLocalServerURL interface{ ShowLocalServerURL(url string) }
}
// OIDCAuthenticateByPasswordIn represents an input DTO of OIDCClient.AuthenticateByPassword.
type OIDCAuthenticateByPasswordIn struct {
Username string
Password string
}
// OIDCAuthenticateOut represents an output DTO of
// OIDCClient.AuthenticateByCode, OIDCClient.AuthenticateByPassword and OIDCClient.Refresh.
type OIDCAuthenticateOut struct {
IDToken string
RefreshToken string
IDTokenExpiry time.Time
IDTokenClaims map[string]string // string representation of claims for logging
}
// OIDCRefreshIn represents an input DTO of OIDCClient.Refresh.
type OIDCRefreshIn struct {
RefreshToken string
}
type OIDCDecoder interface {
DecodeIDToken(t string) (*DecodedIDToken, error)
}
type DecodedIDToken struct {
IDTokenExpiry time.Time
IDTokenClaims map[string]string // string representation of claims for logging
}
type Env interface {
ReadPassword(prompt string) (string, error)
}
type Logger interface {
AddFlags(f *pflag.FlagSet)
Printf(format string, args ...interface{})
V(level int) Verbose
IsEnabled(level int) bool
}
type Verbose interface {
Infof(format string, args ...interface{})
}
// LogLevel represents a log level for debug.
//
// 0 = None
// 1 = Including in/out
// 2 = Including transport headers
// 3 = Including transport body
//
type LogLevel int

View File

@@ -2,13 +2,46 @@ package kubeconfig
import (
"github.com/google/wire"
"github.com/int128/kubelogin/pkg/adaptors"
)
//go:generate mockgen -destination mock_kubeconfig/mock_kubeconfig.go github.com/int128/kubelogin/pkg/adaptors/kubeconfig Interface
// Set provides an implementation and interface for Kubeconfig.
var Set = wire.NewSet(
wire.Struct(new(Kubeconfig), "*"),
wire.Bind(new(adaptors.Kubeconfig), new(*Kubeconfig)),
wire.Bind(new(Interface), new(*Kubeconfig)),
)
type Interface interface {
GetCurrentAuthProvider(explicitFilename string, contextName ContextName, userName UserName) (*AuthProvider, error)
UpdateAuthProvider(auth *AuthProvider) error
}
// ContextName represents name of a context.
type ContextName string
// UserName represents name of a user.
type UserName string
// AuthProvider represents the authentication provider,
// i.e. context, user and auth-provider in a kubeconfig.
type AuthProvider struct {
LocationOfOrigin string // Path to the kubeconfig file which contains the user
UserName UserName // User name
ContextName ContextName // Context name (optional)
OIDCConfig OIDCConfig
}
// OIDCConfig represents a configuration of an OIDC provider.
type OIDCConfig struct {
IDPIssuerURL string // idp-issuer-url
ClientID string // client-id
ClientSecret string // client-secret
IDPCertificateAuthority string // (optional) idp-certificate-authority
IDPCertificateAuthorityData string // (optional) idp-certificate-authority-data
ExtraScopes []string // (optional) extra-scopes
IDToken string // (optional) id-token
RefreshToken string // (optional) refresh-token
}
type Kubeconfig struct{}

View File

@@ -3,13 +3,12 @@ package kubeconfig
import (
"strings"
"github.com/int128/kubelogin/pkg/models/kubeconfig"
"golang.org/x/xerrors"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/clientcmd/api"
)
func (*Kubeconfig) GetCurrentAuthProvider(explicitFilename string, contextName kubeconfig.ContextName, userName kubeconfig.UserName) (*kubeconfig.AuthProvider, error) {
func (*Kubeconfig) GetCurrentAuthProvider(explicitFilename string, contextName ContextName, userName UserName) (*AuthProvider, error) {
config, err := loadByDefaultRules(explicitFilename)
if err != nil {
return nil, xerrors.Errorf("could not load kubeconfig: %w", err)
@@ -35,16 +34,16 @@ func loadByDefaultRules(explicitFilename string) (*api.Config, error) {
// If contextName is given, this returns the user of the context.
// If userName is given, this ignores the context and returns the user.
// If any context or user is not found, this returns an error.
func findCurrentAuthProvider(config *api.Config, contextName kubeconfig.ContextName, userName kubeconfig.UserName) (*kubeconfig.AuthProvider, error) {
func findCurrentAuthProvider(config *api.Config, contextName ContextName, userName UserName) (*AuthProvider, error) {
if userName == "" {
if contextName == "" {
contextName = kubeconfig.ContextName(config.CurrentContext)
contextName = ContextName(config.CurrentContext)
}
contextNode, ok := config.Contexts[string(contextName)]
if !ok {
return nil, xerrors.Errorf("context %s does not exist", contextName)
}
userName = kubeconfig.UserName(contextNode.AuthInfo)
userName = UserName(contextNode.AuthInfo)
}
userNode, ok := config.AuthInfos[string(userName)]
if !ok {
@@ -59,7 +58,7 @@ func findCurrentAuthProvider(config *api.Config, contextName kubeconfig.ContextN
if userNode.AuthProvider.Config == nil {
return nil, xerrors.New("auth-provider.config is missing")
}
return &kubeconfig.AuthProvider{
return &AuthProvider{
LocationOfOrigin: userNode.LocationOfOrigin,
UserName: userName,
ContextName: contextName,
@@ -67,12 +66,12 @@ func findCurrentAuthProvider(config *api.Config, contextName kubeconfig.ContextN
}, nil
}
func makeOIDCConfig(m map[string]string) kubeconfig.OIDCConfig {
func makeOIDCConfig(m map[string]string) OIDCConfig {
var extraScopes []string
if m["extra-scopes"] != "" {
extraScopes = strings.Split(m["extra-scopes"], ",")
}
return kubeconfig.OIDCConfig{
return OIDCConfig{
IDPIssuerURL: m["idp-issuer-url"],
ClientID: m["client-id"],
ClientSecret: m["client-secret"],

View File

@@ -5,7 +5,6 @@ import (
"testing"
"github.com/go-test/deep"
"github.com/int128/kubelogin/pkg/models/kubeconfig"
"k8s.io/client-go/tools/clientcmd/api"
)
@@ -106,11 +105,11 @@ func Test_findCurrentAuthProvider(t *testing.T) {
if err != nil {
t.Fatalf("Could not find the current auth: %s", err)
}
want := &kubeconfig.AuthProvider{
want := &AuthProvider{
LocationOfOrigin: "/path/to/kubeconfig",
UserName: "theUser",
ContextName: "theContext",
OIDCConfig: kubeconfig.OIDCConfig{
OIDCConfig: OIDCConfig{
IDPIssuerURL: "https://accounts.google.com",
ClientID: "GOOGLE_CLIENT_ID",
ClientSecret: "GOOGLE_CLIENT_SECRET",
@@ -148,11 +147,11 @@ func Test_findCurrentAuthProvider(t *testing.T) {
if err != nil {
t.Fatalf("Could not find the current auth: %s", err)
}
want := &kubeconfig.AuthProvider{
want := &AuthProvider{
LocationOfOrigin: "/path/to/kubeconfig",
UserName: "theUser",
ContextName: "theContext",
OIDCConfig: kubeconfig.OIDCConfig{
OIDCConfig: OIDCConfig{
IDPIssuerURL: "https://accounts.google.com",
},
}
@@ -178,10 +177,10 @@ func Test_findCurrentAuthProvider(t *testing.T) {
if err != nil {
t.Fatalf("Could not find the current auth: %s", err)
}
want := &kubeconfig.AuthProvider{
want := &AuthProvider{
LocationOfOrigin: "/path/to/kubeconfig",
UserName: "theUser",
OIDCConfig: kubeconfig.OIDCConfig{
OIDCConfig: OIDCConfig{
IDPIssuerURL: "https://accounts.google.com",
},
}

View File

@@ -0,0 +1,59 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/int128/kubelogin/pkg/adaptors/kubeconfig (interfaces: Interface)
// Package mock_kubeconfig is a generated GoMock package.
package mock_kubeconfig
import (
gomock "github.com/golang/mock/gomock"
kubeconfig "github.com/int128/kubelogin/pkg/adaptors/kubeconfig"
reflect "reflect"
)
// MockInterface is a mock of Interface interface
type MockInterface struct {
ctrl *gomock.Controller
recorder *MockInterfaceMockRecorder
}
// MockInterfaceMockRecorder is the mock recorder for MockInterface
type MockInterfaceMockRecorder struct {
mock *MockInterface
}
// NewMockInterface creates a new mock instance
func NewMockInterface(ctrl *gomock.Controller) *MockInterface {
mock := &MockInterface{ctrl: ctrl}
mock.recorder = &MockInterfaceMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder {
return m.recorder
}
// GetCurrentAuthProvider mocks base method
func (m *MockInterface) GetCurrentAuthProvider(arg0 string, arg1 kubeconfig.ContextName, arg2 kubeconfig.UserName) (*kubeconfig.AuthProvider, error) {
ret := m.ctrl.Call(m, "GetCurrentAuthProvider", arg0, arg1, arg2)
ret0, _ := ret[0].(*kubeconfig.AuthProvider)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetCurrentAuthProvider indicates an expected call of GetCurrentAuthProvider
func (mr *MockInterfaceMockRecorder) GetCurrentAuthProvider(arg0, arg1, arg2 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCurrentAuthProvider", reflect.TypeOf((*MockInterface)(nil).GetCurrentAuthProvider), arg0, arg1, arg2)
}
// UpdateAuthProvider mocks base method
func (m *MockInterface) UpdateAuthProvider(arg0 *kubeconfig.AuthProvider) error {
ret := m.ctrl.Call(m, "UpdateAuthProvider", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateAuthProvider indicates an expected call of UpdateAuthProvider
func (mr *MockInterfaceMockRecorder) UpdateAuthProvider(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAuthProvider", reflect.TypeOf((*MockInterface)(nil).UpdateAuthProvider), arg0)
}

View File

@@ -3,12 +3,11 @@ package kubeconfig
import (
"strings"
"github.com/int128/kubelogin/pkg/models/kubeconfig"
"golang.org/x/xerrors"
"k8s.io/client-go/tools/clientcmd"
)
func (*Kubeconfig) UpdateAuthProvider(auth *kubeconfig.AuthProvider) error {
func (*Kubeconfig) UpdateAuthProvider(auth *AuthProvider) error {
config, err := clientcmd.LoadFromFile(auth.LocationOfOrigin)
if err != nil {
return xerrors.Errorf("could not load %s: %w", auth.LocationOfOrigin, err)
@@ -30,7 +29,7 @@ func (*Kubeconfig) UpdateAuthProvider(auth *kubeconfig.AuthProvider) error {
return nil
}
func copyOIDCConfig(config kubeconfig.OIDCConfig, m map[string]string) {
func copyOIDCConfig(config OIDCConfig, m map[string]string) {
setOrDeleteKey(m, "idp-issuer-url", config.IDPIssuerURL)
setOrDeleteKey(m, "client-id", config.ClientID)
setOrDeleteKey(m, "client-secret", config.ClientSecret)

View File

@@ -4,8 +4,6 @@ import (
"io/ioutil"
"os"
"testing"
"github.com/int128/kubelogin/pkg/models/kubeconfig"
)
func TestKubeconfig_UpdateAuth(t *testing.T) {
@@ -18,10 +16,10 @@ func TestKubeconfig_UpdateAuth(t *testing.T) {
t.Errorf("Could not remove the temp file: %s", err)
}
}()
if err := k.UpdateAuthProvider(&kubeconfig.AuthProvider{
if err := k.UpdateAuthProvider(&AuthProvider{
LocationOfOrigin: f.Name(),
UserName: "google",
OIDCConfig: kubeconfig.OIDCConfig{
OIDCConfig: OIDCConfig{
IDPIssuerURL: "https://accounts.google.com",
ClientID: "GOOGLE_CLIENT_ID",
ClientSecret: "GOOGLE_CLIENT_SECRET",
@@ -66,10 +64,10 @@ users:
t.Errorf("Could not remove the temp file: %s", err)
}
}()
if err := k.UpdateAuthProvider(&kubeconfig.AuthProvider{
if err := k.UpdateAuthProvider(&AuthProvider{
LocationOfOrigin: f.Name(),
UserName: "google",
OIDCConfig: kubeconfig.OIDCConfig{
OIDCConfig: OIDCConfig{
IDPIssuerURL: "https://accounts.google.com",
ClientID: "GOOGLE_CLIENT_ID",
ClientSecret: "GOOGLE_CLIENT_SECRET",

View File

@@ -6,7 +6,6 @@ import (
"os"
"github.com/google/wire"
"github.com/int128/kubelogin/pkg/adaptors"
"github.com/spf13/pflag"
"k8s.io/klog"
)
@@ -17,12 +16,23 @@ var Set = wire.NewSet(
)
// New returns a Logger with the standard log.Logger and klog.
func New() adaptors.Logger {
func New() Interface {
return &Logger{
goLogger: log.New(os.Stderr, "", 0),
}
}
type Interface interface {
AddFlags(f *pflag.FlagSet)
Printf(format string, args ...interface{})
V(level int) Verbose
IsEnabled(level int) bool
}
type Verbose interface {
Infof(format string, args ...interface{})
}
type goLogger interface {
Printf(format string, v ...interface{})
}
@@ -40,7 +50,7 @@ func (*Logger) AddFlags(f *pflag.FlagSet) {
}
// V returns a logger enabled only if the level is enabled.
func (*Logger) V(level int) adaptors.Verbose {
func (*Logger) V(level int) Verbose {
return klog.V(klog.Level(level))
}

View File

@@ -1,9 +1,9 @@
package logger
package mock_logger
import (
"fmt"
"github.com/int128/kubelogin/pkg/adaptors"
"github.com/int128/kubelogin/pkg/adaptors/logger"
"github.com/spf13/pflag"
)
@@ -37,7 +37,7 @@ func (v *Verbose) Infof(format string, args ...interface{}) {
v.t.Logf(fmt.Sprintf("I%d] ", v.level)+format, args...)
}
func (l *Logger) V(level int) adaptors.Verbose {
func (l *Logger) V(level int) logger.Verbose {
return &Verbose{l.t, level}
}

View File

@@ -1,46 +0,0 @@
package mock_adaptors
import (
"fmt"
"github.com/int128/kubelogin/pkg/adaptors"
"github.com/spf13/pflag"
)
func NewLogger(t testingLogger) *Logger {
return &Logger{t}
}
type testingLogger interface {
Logf(format string, v ...interface{})
}
// Logger provides logging facility using testing.T.
type Logger struct {
t testingLogger
}
func (*Logger) AddFlags(f *pflag.FlagSet) {
f.IntP("v", "v", 0, "dummy flag used in the tests")
}
func (l *Logger) Printf(format string, args ...interface{}) {
l.t.Logf(format, args...)
}
type Verbose struct {
t testingLogger
level int
}
func (v *Verbose) Infof(format string, args ...interface{}) {
v.t.Logf(fmt.Sprintf("I%d] ", v.level)+format, args...)
}
func (l *Logger) V(level int) adaptors.Verbose {
return &Verbose{l.t, level}
}
func (*Logger) IsEnabled(level int) bool {
return true
}

View File

@@ -1,388 +0,0 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/int128/kubelogin/pkg/adaptors (interfaces: Kubeconfig,TokenCacheRepository,CredentialPluginInteraction,OIDC,OIDCClient,OIDCDecoder,Env,Logger)
// Package mock_adaptors is a generated GoMock package.
package mock_adaptors
import (
context "context"
gomock "github.com/golang/mock/gomock"
adaptors "github.com/int128/kubelogin/pkg/adaptors"
credentialplugin "github.com/int128/kubelogin/pkg/models/credentialplugin"
kubeconfig "github.com/int128/kubelogin/pkg/models/kubeconfig"
pflag "github.com/spf13/pflag"
reflect "reflect"
)
// MockKubeconfig is a mock of Kubeconfig interface
type MockKubeconfig struct {
ctrl *gomock.Controller
recorder *MockKubeconfigMockRecorder
}
// MockKubeconfigMockRecorder is the mock recorder for MockKubeconfig
type MockKubeconfigMockRecorder struct {
mock *MockKubeconfig
}
// NewMockKubeconfig creates a new mock instance
func NewMockKubeconfig(ctrl *gomock.Controller) *MockKubeconfig {
mock := &MockKubeconfig{ctrl: ctrl}
mock.recorder = &MockKubeconfigMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockKubeconfig) EXPECT() *MockKubeconfigMockRecorder {
return m.recorder
}
// GetCurrentAuthProvider mocks base method
func (m *MockKubeconfig) GetCurrentAuthProvider(arg0 string, arg1 kubeconfig.ContextName, arg2 kubeconfig.UserName) (*kubeconfig.AuthProvider, error) {
ret := m.ctrl.Call(m, "GetCurrentAuthProvider", arg0, arg1, arg2)
ret0, _ := ret[0].(*kubeconfig.AuthProvider)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetCurrentAuthProvider indicates an expected call of GetCurrentAuthProvider
func (mr *MockKubeconfigMockRecorder) GetCurrentAuthProvider(arg0, arg1, arg2 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCurrentAuthProvider", reflect.TypeOf((*MockKubeconfig)(nil).GetCurrentAuthProvider), arg0, arg1, arg2)
}
// UpdateAuthProvider mocks base method
func (m *MockKubeconfig) UpdateAuthProvider(arg0 *kubeconfig.AuthProvider) error {
ret := m.ctrl.Call(m, "UpdateAuthProvider", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateAuthProvider indicates an expected call of UpdateAuthProvider
func (mr *MockKubeconfigMockRecorder) UpdateAuthProvider(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAuthProvider", reflect.TypeOf((*MockKubeconfig)(nil).UpdateAuthProvider), arg0)
}
// MockTokenCacheRepository is a mock of TokenCacheRepository interface
type MockTokenCacheRepository struct {
ctrl *gomock.Controller
recorder *MockTokenCacheRepositoryMockRecorder
}
// MockTokenCacheRepositoryMockRecorder is the mock recorder for MockTokenCacheRepository
type MockTokenCacheRepositoryMockRecorder struct {
mock *MockTokenCacheRepository
}
// NewMockTokenCacheRepository creates a new mock instance
func NewMockTokenCacheRepository(ctrl *gomock.Controller) *MockTokenCacheRepository {
mock := &MockTokenCacheRepository{ctrl: ctrl}
mock.recorder = &MockTokenCacheRepositoryMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockTokenCacheRepository) EXPECT() *MockTokenCacheRepositoryMockRecorder {
return m.recorder
}
// FindByKey mocks base method
func (m *MockTokenCacheRepository) FindByKey(arg0 string, arg1 credentialplugin.TokenCacheKey) (*credentialplugin.TokenCache, error) {
ret := m.ctrl.Call(m, "FindByKey", arg0, arg1)
ret0, _ := ret[0].(*credentialplugin.TokenCache)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindByKey indicates an expected call of FindByKey
func (mr *MockTokenCacheRepositoryMockRecorder) FindByKey(arg0, arg1 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindByKey", reflect.TypeOf((*MockTokenCacheRepository)(nil).FindByKey), arg0, arg1)
}
// Save mocks base method
func (m *MockTokenCacheRepository) Save(arg0 string, arg1 credentialplugin.TokenCacheKey, arg2 credentialplugin.TokenCache) error {
ret := m.ctrl.Call(m, "Save", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
return ret0
}
// Save indicates an expected call of Save
func (mr *MockTokenCacheRepositoryMockRecorder) Save(arg0, arg1, arg2 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Save", reflect.TypeOf((*MockTokenCacheRepository)(nil).Save), arg0, arg1, arg2)
}
// MockCredentialPluginInteraction is a mock of CredentialPluginInteraction interface
type MockCredentialPluginInteraction struct {
ctrl *gomock.Controller
recorder *MockCredentialPluginInteractionMockRecorder
}
// MockCredentialPluginInteractionMockRecorder is the mock recorder for MockCredentialPluginInteraction
type MockCredentialPluginInteractionMockRecorder struct {
mock *MockCredentialPluginInteraction
}
// NewMockCredentialPluginInteraction creates a new mock instance
func NewMockCredentialPluginInteraction(ctrl *gomock.Controller) *MockCredentialPluginInteraction {
mock := &MockCredentialPluginInteraction{ctrl: ctrl}
mock.recorder = &MockCredentialPluginInteractionMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockCredentialPluginInteraction) EXPECT() *MockCredentialPluginInteractionMockRecorder {
return m.recorder
}
// Write mocks base method
func (m *MockCredentialPluginInteraction) Write(arg0 credentialplugin.Output) error {
ret := m.ctrl.Call(m, "Write", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// Write indicates an expected call of Write
func (mr *MockCredentialPluginInteractionMockRecorder) Write(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockCredentialPluginInteraction)(nil).Write), arg0)
}
// MockOIDC is a mock of OIDC interface
type MockOIDC struct {
ctrl *gomock.Controller
recorder *MockOIDCMockRecorder
}
// MockOIDCMockRecorder is the mock recorder for MockOIDC
type MockOIDCMockRecorder struct {
mock *MockOIDC
}
// NewMockOIDC creates a new mock instance
func NewMockOIDC(ctrl *gomock.Controller) *MockOIDC {
mock := &MockOIDC{ctrl: ctrl}
mock.recorder = &MockOIDCMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockOIDC) EXPECT() *MockOIDCMockRecorder {
return m.recorder
}
// New mocks base method
func (m *MockOIDC) New(arg0 context.Context, arg1 adaptors.OIDCClientConfig) (adaptors.OIDCClient, error) {
ret := m.ctrl.Call(m, "New", arg0, arg1)
ret0, _ := ret[0].(adaptors.OIDCClient)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// New indicates an expected call of New
func (mr *MockOIDCMockRecorder) New(arg0, arg1 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "New", reflect.TypeOf((*MockOIDC)(nil).New), arg0, arg1)
}
// MockOIDCClient is a mock of OIDCClient interface
type MockOIDCClient struct {
ctrl *gomock.Controller
recorder *MockOIDCClientMockRecorder
}
// MockOIDCClientMockRecorder is the mock recorder for MockOIDCClient
type MockOIDCClientMockRecorder struct {
mock *MockOIDCClient
}
// NewMockOIDCClient creates a new mock instance
func NewMockOIDCClient(ctrl *gomock.Controller) *MockOIDCClient {
mock := &MockOIDCClient{ctrl: ctrl}
mock.recorder = &MockOIDCClientMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockOIDCClient) EXPECT() *MockOIDCClientMockRecorder {
return m.recorder
}
// AuthenticateByCode mocks base method
func (m *MockOIDCClient) AuthenticateByCode(arg0 context.Context, arg1 adaptors.OIDCAuthenticateByCodeIn) (*adaptors.OIDCAuthenticateOut, error) {
ret := m.ctrl.Call(m, "AuthenticateByCode", arg0, arg1)
ret0, _ := ret[0].(*adaptors.OIDCAuthenticateOut)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// AuthenticateByCode indicates an expected call of AuthenticateByCode
func (mr *MockOIDCClientMockRecorder) AuthenticateByCode(arg0, arg1 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthenticateByCode", reflect.TypeOf((*MockOIDCClient)(nil).AuthenticateByCode), arg0, arg1)
}
// AuthenticateByPassword mocks base method
func (m *MockOIDCClient) AuthenticateByPassword(arg0 context.Context, arg1 adaptors.OIDCAuthenticateByPasswordIn) (*adaptors.OIDCAuthenticateOut, error) {
ret := m.ctrl.Call(m, "AuthenticateByPassword", arg0, arg1)
ret0, _ := ret[0].(*adaptors.OIDCAuthenticateOut)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// AuthenticateByPassword indicates an expected call of AuthenticateByPassword
func (mr *MockOIDCClientMockRecorder) AuthenticateByPassword(arg0, arg1 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthenticateByPassword", reflect.TypeOf((*MockOIDCClient)(nil).AuthenticateByPassword), arg0, arg1)
}
// Refresh mocks base method
func (m *MockOIDCClient) Refresh(arg0 context.Context, arg1 adaptors.OIDCRefreshIn) (*adaptors.OIDCAuthenticateOut, error) {
ret := m.ctrl.Call(m, "Refresh", arg0, arg1)
ret0, _ := ret[0].(*adaptors.OIDCAuthenticateOut)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Refresh indicates an expected call of Refresh
func (mr *MockOIDCClientMockRecorder) Refresh(arg0, arg1 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Refresh", reflect.TypeOf((*MockOIDCClient)(nil).Refresh), arg0, arg1)
}
// MockOIDCDecoder is a mock of OIDCDecoder interface
type MockOIDCDecoder struct {
ctrl *gomock.Controller
recorder *MockOIDCDecoderMockRecorder
}
// MockOIDCDecoderMockRecorder is the mock recorder for MockOIDCDecoder
type MockOIDCDecoderMockRecorder struct {
mock *MockOIDCDecoder
}
// NewMockOIDCDecoder creates a new mock instance
func NewMockOIDCDecoder(ctrl *gomock.Controller) *MockOIDCDecoder {
mock := &MockOIDCDecoder{ctrl: ctrl}
mock.recorder = &MockOIDCDecoderMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockOIDCDecoder) EXPECT() *MockOIDCDecoderMockRecorder {
return m.recorder
}
// DecodeIDToken mocks base method
func (m *MockOIDCDecoder) DecodeIDToken(arg0 string) (*adaptors.DecodedIDToken, error) {
ret := m.ctrl.Call(m, "DecodeIDToken", arg0)
ret0, _ := ret[0].(*adaptors.DecodedIDToken)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// DecodeIDToken indicates an expected call of DecodeIDToken
func (mr *MockOIDCDecoderMockRecorder) DecodeIDToken(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecodeIDToken", reflect.TypeOf((*MockOIDCDecoder)(nil).DecodeIDToken), arg0)
}
// MockEnv is a mock of Env interface
type MockEnv struct {
ctrl *gomock.Controller
recorder *MockEnvMockRecorder
}
// MockEnvMockRecorder is the mock recorder for MockEnv
type MockEnvMockRecorder struct {
mock *MockEnv
}
// NewMockEnv creates a new mock instance
func NewMockEnv(ctrl *gomock.Controller) *MockEnv {
mock := &MockEnv{ctrl: ctrl}
mock.recorder = &MockEnvMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockEnv) EXPECT() *MockEnvMockRecorder {
return m.recorder
}
// ReadPassword mocks base method
func (m *MockEnv) ReadPassword(arg0 string) (string, error) {
ret := m.ctrl.Call(m, "ReadPassword", arg0)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ReadPassword indicates an expected call of ReadPassword
func (mr *MockEnvMockRecorder) ReadPassword(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadPassword", reflect.TypeOf((*MockEnv)(nil).ReadPassword), arg0)
}
// MockLogger is a mock of Logger interface
type MockLogger struct {
ctrl *gomock.Controller
recorder *MockLoggerMockRecorder
}
// MockLoggerMockRecorder is the mock recorder for MockLogger
type MockLoggerMockRecorder struct {
mock *MockLogger
}
// NewMockLogger creates a new mock instance
func NewMockLogger(ctrl *gomock.Controller) *MockLogger {
mock := &MockLogger{ctrl: ctrl}
mock.recorder = &MockLoggerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockLogger) EXPECT() *MockLoggerMockRecorder {
return m.recorder
}
// AddFlags mocks base method
func (m *MockLogger) AddFlags(arg0 *pflag.FlagSet) {
m.ctrl.Call(m, "AddFlags", arg0)
}
// AddFlags indicates an expected call of AddFlags
func (mr *MockLoggerMockRecorder) AddFlags(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddFlags", reflect.TypeOf((*MockLogger)(nil).AddFlags), arg0)
}
// IsEnabled mocks base method
func (m *MockLogger) IsEnabled(arg0 int) bool {
ret := m.ctrl.Call(m, "IsEnabled", arg0)
ret0, _ := ret[0].(bool)
return ret0
}
// IsEnabled indicates an expected call of IsEnabled
func (mr *MockLoggerMockRecorder) IsEnabled(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsEnabled", reflect.TypeOf((*MockLogger)(nil).IsEnabled), arg0)
}
// Printf mocks base method
func (m *MockLogger) Printf(arg0 string, arg1 ...interface{}) {
varargs := []interface{}{arg0}
for _, a := range arg1 {
varargs = append(varargs, a)
}
m.ctrl.Call(m, "Printf", varargs...)
}
// Printf indicates an expected call of Printf
func (mr *MockLoggerMockRecorder) Printf(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
varargs := append([]interface{}{arg0}, arg1...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Printf", reflect.TypeOf((*MockLogger)(nil).Printf), varargs...)
}
// V mocks base method
func (m *MockLogger) V(arg0 int) adaptors.Verbose {
ret := m.ctrl.Call(m, "V", arg0)
ret0, _ := ret[0].(adaptors.Verbose)
return ret0
}
// V indicates an expected call of V
func (mr *MockLoggerMockRecorder) V(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "V", reflect.TypeOf((*MockLogger)(nil).V), arg0)
}

180
pkg/adaptors/oidc/client.go Normal file
View File

@@ -0,0 +1,180 @@
package oidc
import (
"context"
"crypto/rand"
"encoding/binary"
"fmt"
"net/http"
"time"
"github.com/coreos/go-oidc"
"github.com/int128/kubelogin/pkg/adaptors/logger"
"github.com/int128/oauth2cli"
"golang.org/x/oauth2"
"golang.org/x/xerrors"
)
type Interface interface {
AuthenticateByCode(ctx context.Context, in AuthenticateByCodeIn) (*AuthenticateOut, error)
AuthenticateByPassword(ctx context.Context, in AuthenticateByPasswordIn) (*AuthenticateOut, error)
Refresh(ctx context.Context, in RefreshIn) (*AuthenticateOut, error)
}
// AuthenticateByCodeIn represents an input DTO of Interface.AuthenticateByCode.
type AuthenticateByCodeIn struct {
LocalServerPort []int // HTTP server port candidates
SkipOpenBrowser bool // skip opening browser if true
ShowLocalServerURL interface{ ShowLocalServerURL(url string) }
}
// AuthenticateByPasswordIn represents an input DTO of Interface.AuthenticateByPassword.
type AuthenticateByPasswordIn struct {
Username string
Password string
}
// AuthenticateOut represents an output DTO of
// Interface.AuthenticateByCode, Interface.AuthenticateByPassword and Interface.Refresh.
type AuthenticateOut struct {
IDToken string
RefreshToken string
IDTokenExpiry time.Time
IDTokenClaims map[string]string // string representation of claims for logging
}
// RefreshIn represents an input DTO of Interface.Refresh.
type RefreshIn struct {
RefreshToken string
}
type client struct {
httpClient *http.Client
provider *oidc.Provider
oauth2Config oauth2.Config
logger logger.Interface
}
func (c *client) wrapContext(ctx context.Context) context.Context {
if c.httpClient != nil {
ctx = context.WithValue(ctx, oauth2.HTTPClient, c.httpClient)
}
return ctx
}
// AuthenticateByCode performs the authorization code flow.
func (c *client) AuthenticateByCode(ctx context.Context, in AuthenticateByCodeIn) (*AuthenticateOut, error) {
ctx = c.wrapContext(ctx)
nonce, err := newNonce()
if err != nil {
return nil, xerrors.Errorf("could not generate a nonce parameter")
}
config := oauth2cli.Config{
OAuth2Config: c.oauth2Config,
LocalServerPort: in.LocalServerPort,
SkipOpenBrowser: in.SkipOpenBrowser,
AuthCodeOptions: []oauth2.AuthCodeOption{oauth2.AccessTypeOffline, oidc.Nonce(nonce)},
ShowLocalServerURL: in.ShowLocalServerURL.ShowLocalServerURL,
}
token, err := oauth2cli.GetToken(ctx, config)
if err != nil {
return nil, xerrors.Errorf("could not get a token: %w", err)
}
idToken, ok := token.Extra("id_token").(string)
if !ok {
return nil, xerrors.Errorf("id_token is missing in the token response: %s", token)
}
verifier := c.provider.Verifier(&oidc.Config{ClientID: c.oauth2Config.ClientID})
verifiedIDToken, err := verifier.Verify(ctx, idToken)
if err != nil {
return nil, xerrors.Errorf("could not verify the id_token: %w", err)
}
if verifiedIDToken.Nonce != nonce {
return nil, xerrors.Errorf("nonce of ID token did not match (want %s but was %s)", nonce, verifiedIDToken.Nonce)
}
claims, err := dumpClaims(verifiedIDToken)
if err != nil {
c.logger.V(1).Infof("incomplete claims of the ID token: %w", err)
}
return &AuthenticateOut{
IDToken: idToken,
RefreshToken: token.RefreshToken,
IDTokenExpiry: verifiedIDToken.Expiry,
IDTokenClaims: claims,
}, nil
}
func newNonce() (string, error) {
var n uint64
if err := binary.Read(rand.Reader, binary.LittleEndian, &n); err != nil {
return "", xerrors.Errorf("error while reading random: %w", err)
}
return fmt.Sprintf("%x", n), nil
}
// AuthenticateByPassword performs the resource owner password credentials flow.
func (c *client) AuthenticateByPassword(ctx context.Context, in AuthenticateByPasswordIn) (*AuthenticateOut, error) {
ctx = c.wrapContext(ctx)
token, err := c.oauth2Config.PasswordCredentialsToken(ctx, in.Username, in.Password)
if err != nil {
return nil, xerrors.Errorf("could not get a token: %w", err)
}
idToken, ok := token.Extra("id_token").(string)
if !ok {
return nil, xerrors.Errorf("id_token is missing in the token response: %s", token)
}
verifier := c.provider.Verifier(&oidc.Config{ClientID: c.oauth2Config.ClientID})
verifiedIDToken, err := verifier.Verify(ctx, idToken)
if err != nil {
return nil, xerrors.Errorf("could not verify the id_token: %w", err)
}
claims, err := dumpClaims(verifiedIDToken)
if err != nil {
c.logger.V(1).Infof("incomplete claims of the ID token: %w", err)
}
return &AuthenticateOut{
IDToken: idToken,
RefreshToken: token.RefreshToken,
IDTokenExpiry: verifiedIDToken.Expiry,
IDTokenClaims: claims,
}, nil
}
// Refresh sends a refresh token request and returns a token set.
func (c *client) Refresh(ctx context.Context, in RefreshIn) (*AuthenticateOut, error) {
ctx = c.wrapContext(ctx)
currentToken := &oauth2.Token{
Expiry: time.Now(),
RefreshToken: in.RefreshToken,
}
source := c.oauth2Config.TokenSource(ctx, currentToken)
token, err := source.Token()
if err != nil {
return nil, xerrors.Errorf("could not refresh the token: %w", err)
}
idToken, ok := token.Extra("id_token").(string)
if !ok {
return nil, xerrors.Errorf("id_token is missing in the token response: %s", token)
}
verifier := c.provider.Verifier(&oidc.Config{ClientID: c.oauth2Config.ClientID})
verifiedIDToken, err := verifier.Verify(ctx, idToken)
if err != nil {
return nil, xerrors.Errorf("could not verify the id_token: %w", err)
}
claims, err := dumpClaims(verifiedIDToken)
if err != nil {
c.logger.V(1).Infof("incomplete claims of the ID token: %w", err)
}
return &AuthenticateOut{
IDToken: idToken,
RefreshToken: token.RefreshToken,
IDTokenExpiry: verifiedIDToken.Expiry,
IDTokenClaims: claims,
}, nil
}
func dumpClaims(token *oidc.IDToken) (map[string]string, error) {
var rawClaims map[string]interface{}
err := token.Claims(&rawClaims)
return dumpRawClaims(rawClaims), err
}

View File

@@ -8,15 +8,23 @@ import (
"time"
"github.com/dgrijalva/jwt-go"
"github.com/int128/kubelogin/pkg/adaptors"
"golang.org/x/xerrors"
)
type DecoderInterface interface {
DecodeIDToken(t string) (*DecodedIDToken, error)
}
type DecodedIDToken struct {
IDTokenExpiry time.Time
IDTokenClaims map[string]string // string representation of claims for logging
}
type Decoder struct{}
// DecodeIDToken returns the claims of the ID token.
// Note that this method does not verify the signature and always trust it.
func (d *Decoder) DecodeIDToken(t string) (*adaptors.DecodedIDToken, error) {
func (d *Decoder) DecodeIDToken(t string) (*DecodedIDToken, error) {
parts := strings.Split(t, ".")
if len(parts) != 3 {
return nil, xerrors.Errorf("token contains an invalid number of segments")
@@ -33,7 +41,7 @@ func (d *Decoder) DecodeIDToken(t string) (*adaptors.DecodedIDToken, error) {
if err := json.NewDecoder(bytes.NewBuffer(b)).Decode(&rawClaims); err != nil {
return nil, xerrors.Errorf("could not decode the json of token: %w", err)
}
return &adaptors.DecodedIDToken{
return &DecodedIDToken{
IDTokenExpiry: time.Unix(claims.ExpiresAt, 0),
IDTokenClaims: dumpRawClaims(rawClaims),
}, nil

View File

@@ -0,0 +1,122 @@
package oidc
import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"io/ioutil"
"net/http"
"github.com/coreos/go-oidc"
"github.com/int128/kubelogin/pkg/adaptors/kubeconfig"
"github.com/int128/kubelogin/pkg/adaptors/logger"
"github.com/int128/kubelogin/pkg/adaptors/oidc/logging"
"golang.org/x/oauth2"
"golang.org/x/xerrors"
)
type FactoryInterface interface {
New(ctx context.Context, config ClientConfig) (Interface, error)
}
// ClientConfig represents a configuration of an Interface to create.
type ClientConfig struct {
Config kubeconfig.OIDCConfig
CACertFilename string
SkipTLSVerify bool
}
type Factory struct {
Logger logger.Interface
}
// New returns an instance of adaptors.Interface with the given configuration.
func (f *Factory) New(ctx context.Context, config ClientConfig) (Interface, error) {
tlsConfig, err := f.tlsConfigFor(config)
if err != nil {
return nil, xerrors.Errorf("could not initialize TLS config: %w", err)
}
baseTransport := &http.Transport{
TLSClientConfig: tlsConfig,
Proxy: http.ProxyFromEnvironment,
}
loggingTransport := &logging.Transport{
Base: baseTransport,
Logger: f.Logger,
}
httpClient := &http.Client{
Transport: loggingTransport,
}
ctx = context.WithValue(ctx, oauth2.HTTPClient, httpClient)
provider, err := oidc.NewProvider(ctx, config.Config.IDPIssuerURL)
if err != nil {
return nil, xerrors.Errorf("could not discovery the OIDCFactory issuer: %w", err)
}
return &client{
httpClient: httpClient,
provider: provider,
oauth2Config: oauth2.Config{
Endpoint: provider.Endpoint(),
ClientID: config.Config.ClientID,
ClientSecret: config.Config.ClientSecret,
Scopes: append(config.Config.ExtraScopes, oidc.ScopeOpenID),
},
logger: f.Logger,
}, nil
}
func (f *Factory) tlsConfigFor(config ClientConfig) (*tls.Config, error) {
pool := x509.NewCertPool()
if config.Config.IDPCertificateAuthority != "" {
f.Logger.V(1).Infof("loading the certificate %s", config.Config.IDPCertificateAuthority)
err := appendCertificateFromFile(pool, config.Config.IDPCertificateAuthority)
if err != nil {
return nil, xerrors.Errorf("could not load the certificate of idp-certificate-authority: %w", err)
}
}
if config.Config.IDPCertificateAuthorityData != "" {
f.Logger.V(1).Infof("loading the certificate of idp-certificate-authority-data")
err := appendEncodedCertificate(pool, config.Config.IDPCertificateAuthorityData)
if err != nil {
return nil, xerrors.Errorf("could not load the certificate of idp-certificate-authority-data: %w", err)
}
}
if config.CACertFilename != "" {
f.Logger.V(1).Infof("loading the certificate %s", config.CACertFilename)
err := appendCertificateFromFile(pool, config.CACertFilename)
if err != nil {
return nil, xerrors.Errorf("could not load the certificate: %w", err)
}
}
c := &tls.Config{
InsecureSkipVerify: config.SkipTLSVerify,
}
if len(pool.Subjects()) > 0 {
c.RootCAs = pool
}
return c, nil
}
func appendCertificateFromFile(pool *x509.CertPool, filename string) error {
b, err := ioutil.ReadFile(filename)
if err != nil {
return xerrors.Errorf("could not read %s: %w", filename, err)
}
if !pool.AppendCertsFromPEM(b) {
return xerrors.Errorf("could not append certificate from %s", filename)
}
return nil
}
func appendEncodedCertificate(pool *x509.CertPool, base64String string) error {
b, err := base64.StdEncoding.DecodeString(base64String)
if err != nil {
return xerrors.Errorf("could not decode base64: %w", err)
}
if !pool.AppendCertsFromPEM(b) {
return xerrors.Errorf("could not append certificate")
}
return nil
}

View File

@@ -1,19 +1,19 @@
package tls
package oidc
import (
"io/ioutil"
"testing"
"github.com/int128/kubelogin/e2e_test/logger"
"github.com/int128/kubelogin/pkg/adaptors"
"github.com/int128/kubelogin/pkg/models/kubeconfig"
"github.com/int128/kubelogin/pkg/adaptors/kubeconfig"
"github.com/int128/kubelogin/pkg/adaptors/logger/mock_logger"
)
func TestNewConfig(t *testing.T) {
testingLogger := logger.New(t)
func TestFactory_tlsConfigFor(t *testing.T) {
testingLogger := mock_logger.New(t)
factory := &Factory{Logger: testingLogger}
t.Run("Defaults", func(t *testing.T) {
c, err := NewConfig(adaptors.OIDCClientConfig{}, testingLogger)
c, err := factory.tlsConfigFor(ClientConfig{})
if err != nil {
t.Fatalf("NewConfig error: %+v", err)
}
@@ -25,10 +25,10 @@ func TestNewConfig(t *testing.T) {
}
})
t.Run("SkipTLSVerify", func(t *testing.T) {
config := adaptors.OIDCClientConfig{
config := ClientConfig{
SkipTLSVerify: true,
}
c, err := NewConfig(config, testingLogger)
c, err := factory.tlsConfigFor(config)
if err != nil {
t.Fatalf("NewConfig error: %+v", err)
}
@@ -40,14 +40,14 @@ func TestNewConfig(t *testing.T) {
}
})
t.Run("AllCertificates", func(t *testing.T) {
config := adaptors.OIDCClientConfig{
config := ClientConfig{
Config: kubeconfig.OIDCConfig{
IDPCertificateAuthority: "testdata/ca1.crt",
IDPCertificateAuthorityData: string(readFile(t, "testdata/ca2.crt.base64")),
IDPCertificateAuthority: "testdata/tls/ca1.crt",
IDPCertificateAuthorityData: string(readFile(t, "testdata/tls/ca2.crt.base64")),
},
CACertFilename: "testdata/ca3.crt",
CACertFilename: "testdata/tls/ca3.crt",
}
c, err := NewConfig(config, testingLogger)
c, err := factory.tlsConfigFor(config)
if err != nil {
t.Fatalf("NewConfig error: %+v", err)
}
@@ -63,14 +63,14 @@ func TestNewConfig(t *testing.T) {
}
})
t.Run("InvalidCertificate", func(t *testing.T) {
config := adaptors.OIDCClientConfig{
config := ClientConfig{
Config: kubeconfig.OIDCConfig{
IDPCertificateAuthority: "testdata/ca1.crt",
IDPCertificateAuthorityData: string(readFile(t, "testdata/ca2.crt.base64")),
IDPCertificateAuthority: "testdata/tls/ca1.crt",
IDPCertificateAuthorityData: string(readFile(t, "testdata/tls/ca2.crt.base64")),
},
CACertFilename: "testdata/Makefile", // invalid cert
}
_, err := NewConfig(config, testingLogger)
_, err := factory.tlsConfigFor(config)
if err == nil {
t.Fatalf("NewConfig wants non-nil but nil")
}

View File

@@ -4,7 +4,7 @@ import (
"net/http"
"net/http/httputil"
"github.com/int128/kubelogin/pkg/adaptors"
"github.com/int128/kubelogin/pkg/adaptors/logger"
)
const (
@@ -14,7 +14,7 @@ const (
type Transport struct {
Base http.RoundTripper
Logger adaptors.Logger
Logger logger.Interface
}
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {

View File

@@ -8,7 +8,7 @@ import (
"testing"
"github.com/golang/mock/gomock"
"github.com/int128/kubelogin/pkg/adaptors/mock_adaptors"
"github.com/int128/kubelogin/pkg/adaptors/logger/mock_logger"
)
type mockTransport struct {
@@ -37,7 +37,7 @@ dummy`)), req)
transport := &Transport{
Base: &mockTransport{resp: resp},
Logger: mock_adaptors.NewLogger(t),
Logger: mock_logger.New(t),
}
gotResp, err := transport.RoundTrip(req)
if err != nil {

View File

@@ -0,0 +1,146 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/int128/kubelogin/pkg/adaptors/oidc (interfaces: FactoryInterface,Interface,DecoderInterface)
// Package mock_oidc is a generated GoMock package.
package mock_oidc
import (
context "context"
gomock "github.com/golang/mock/gomock"
oidc "github.com/int128/kubelogin/pkg/adaptors/oidc"
reflect "reflect"
)
// MockFactoryInterface is a mock of FactoryInterface interface
type MockFactoryInterface struct {
ctrl *gomock.Controller
recorder *MockFactoryInterfaceMockRecorder
}
// MockFactoryInterfaceMockRecorder is the mock recorder for MockFactoryInterface
type MockFactoryInterfaceMockRecorder struct {
mock *MockFactoryInterface
}
// NewMockFactoryInterface creates a new mock instance
func NewMockFactoryInterface(ctrl *gomock.Controller) *MockFactoryInterface {
mock := &MockFactoryInterface{ctrl: ctrl}
mock.recorder = &MockFactoryInterfaceMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockFactoryInterface) EXPECT() *MockFactoryInterfaceMockRecorder {
return m.recorder
}
// New mocks base method
func (m *MockFactoryInterface) New(arg0 context.Context, arg1 oidc.ClientConfig) (oidc.Interface, error) {
ret := m.ctrl.Call(m, "New", arg0, arg1)
ret0, _ := ret[0].(oidc.Interface)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// New indicates an expected call of New
func (mr *MockFactoryInterfaceMockRecorder) New(arg0, arg1 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "New", reflect.TypeOf((*MockFactoryInterface)(nil).New), arg0, arg1)
}
// MockInterface is a mock of Interface interface
type MockInterface struct {
ctrl *gomock.Controller
recorder *MockInterfaceMockRecorder
}
// MockInterfaceMockRecorder is the mock recorder for MockInterface
type MockInterfaceMockRecorder struct {
mock *MockInterface
}
// NewMockInterface creates a new mock instance
func NewMockInterface(ctrl *gomock.Controller) *MockInterface {
mock := &MockInterface{ctrl: ctrl}
mock.recorder = &MockInterfaceMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder {
return m.recorder
}
// AuthenticateByCode mocks base method
func (m *MockInterface) AuthenticateByCode(arg0 context.Context, arg1 oidc.AuthenticateByCodeIn) (*oidc.AuthenticateOut, error) {
ret := m.ctrl.Call(m, "AuthenticateByCode", arg0, arg1)
ret0, _ := ret[0].(*oidc.AuthenticateOut)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// AuthenticateByCode indicates an expected call of AuthenticateByCode
func (mr *MockInterfaceMockRecorder) AuthenticateByCode(arg0, arg1 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthenticateByCode", reflect.TypeOf((*MockInterface)(nil).AuthenticateByCode), arg0, arg1)
}
// AuthenticateByPassword mocks base method
func (m *MockInterface) AuthenticateByPassword(arg0 context.Context, arg1 oidc.AuthenticateByPasswordIn) (*oidc.AuthenticateOut, error) {
ret := m.ctrl.Call(m, "AuthenticateByPassword", arg0, arg1)
ret0, _ := ret[0].(*oidc.AuthenticateOut)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// AuthenticateByPassword indicates an expected call of AuthenticateByPassword
func (mr *MockInterfaceMockRecorder) AuthenticateByPassword(arg0, arg1 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthenticateByPassword", reflect.TypeOf((*MockInterface)(nil).AuthenticateByPassword), arg0, arg1)
}
// Refresh mocks base method
func (m *MockInterface) Refresh(arg0 context.Context, arg1 oidc.RefreshIn) (*oidc.AuthenticateOut, error) {
ret := m.ctrl.Call(m, "Refresh", arg0, arg1)
ret0, _ := ret[0].(*oidc.AuthenticateOut)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Refresh indicates an expected call of Refresh
func (mr *MockInterfaceMockRecorder) Refresh(arg0, arg1 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Refresh", reflect.TypeOf((*MockInterface)(nil).Refresh), arg0, arg1)
}
// MockDecoderInterface is a mock of DecoderInterface interface
type MockDecoderInterface struct {
ctrl *gomock.Controller
recorder *MockDecoderInterfaceMockRecorder
}
// MockDecoderInterfaceMockRecorder is the mock recorder for MockDecoderInterface
type MockDecoderInterfaceMockRecorder struct {
mock *MockDecoderInterface
}
// NewMockDecoderInterface creates a new mock instance
func NewMockDecoderInterface(ctrl *gomock.Controller) *MockDecoderInterface {
mock := &MockDecoderInterface{ctrl: ctrl}
mock.recorder = &MockDecoderInterfaceMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockDecoderInterface) EXPECT() *MockDecoderInterfaceMockRecorder {
return m.recorder
}
// DecodeIDToken mocks base method
func (m *MockDecoderInterface) DecodeIDToken(arg0 string) (*oidc.DecodedIDToken, error) {
ret := m.ctrl.Call(m, "DecodeIDToken", arg0)
ret0, _ := ret[0].(*oidc.DecodedIDToken)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// DecodeIDToken indicates an expected call of DecodeIDToken
func (mr *MockDecoderInterfaceMockRecorder) DecodeIDToken(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecodeIDToken", reflect.TypeOf((*MockDecoderInterface)(nil).DecodeIDToken), arg0)
}

View File

@@ -1,25 +1,14 @@
package oidc
import (
"context"
"crypto/rand"
"encoding/binary"
"fmt"
"net/http"
"os"
"time"
"github.com/coreos/go-oidc"
"github.com/google/wire"
"github.com/int128/kubelogin/pkg/adaptors"
"github.com/int128/kubelogin/pkg/adaptors/oidc/logging"
"github.com/int128/kubelogin/pkg/adaptors/oidc/tls"
"github.com/int128/oauth2cli"
"github.com/pkg/browser"
"golang.org/x/oauth2"
"golang.org/x/xerrors"
)
//go:generate mockgen -destination mock_oidc/mock_oidc.go github.com/int128/kubelogin/pkg/adaptors/oidc FactoryInterface,Interface,DecoderInterface
func init() {
// In credential plugin mode, some browser launcher writes a message to stdout
// and it may break the credential json for client-go.
@@ -30,178 +19,7 @@ func init() {
// Set provides an implementation and interface for OIDC.
var Set = wire.NewSet(
wire.Struct(new(Factory), "*"),
wire.Bind(new(adaptors.OIDC), new(*Factory)),
wire.Bind(new(FactoryInterface), new(*Factory)),
wire.Struct(new(Decoder)),
wire.Bind(new(adaptors.OIDCDecoder), new(*Decoder)),
wire.Bind(new(DecoderInterface), new(*Decoder)),
)
type Factory struct {
Logger adaptors.Logger
}
// New returns an instance of adaptors.OIDCClient with the given configuration.
func (f *Factory) New(ctx context.Context, config adaptors.OIDCClientConfig) (adaptors.OIDCClient, error) {
tlsConfig, err := tls.NewConfig(config, f.Logger)
if err != nil {
return nil, xerrors.Errorf("could not initialize TLS config: %w", err)
}
baseTransport := &http.Transport{
TLSClientConfig: tlsConfig,
Proxy: http.ProxyFromEnvironment,
}
loggingTransport := &logging.Transport{
Base: baseTransport,
Logger: f.Logger,
}
httpClient := &http.Client{
Transport: loggingTransport,
}
ctx = context.WithValue(ctx, oauth2.HTTPClient, httpClient)
provider, err := oidc.NewProvider(ctx, config.Config.IDPIssuerURL)
if err != nil {
return nil, xerrors.Errorf("could not discovery the OIDC issuer: %w", err)
}
return &client{
httpClient: httpClient,
provider: provider,
oauth2Config: oauth2.Config{
Endpoint: provider.Endpoint(),
ClientID: config.Config.ClientID,
ClientSecret: config.Config.ClientSecret,
Scopes: append(config.Config.ExtraScopes, oidc.ScopeOpenID),
},
logger: f.Logger,
}, nil
}
type client struct {
httpClient *http.Client
provider *oidc.Provider
oauth2Config oauth2.Config
logger adaptors.Logger
}
func (c *client) wrapContext(ctx context.Context) context.Context {
if c.httpClient != nil {
ctx = context.WithValue(ctx, oauth2.HTTPClient, c.httpClient)
}
return ctx
}
// AuthenticateByCode performs the authorization code flow.
func (c *client) AuthenticateByCode(ctx context.Context, in adaptors.OIDCAuthenticateByCodeIn) (*adaptors.OIDCAuthenticateOut, error) {
ctx = c.wrapContext(ctx)
nonce, err := newNonce()
if err != nil {
return nil, xerrors.Errorf("could not generate a nonce parameter")
}
config := oauth2cli.Config{
OAuth2Config: c.oauth2Config,
LocalServerPort: in.LocalServerPort,
SkipOpenBrowser: in.SkipOpenBrowser,
AuthCodeOptions: []oauth2.AuthCodeOption{oauth2.AccessTypeOffline, oidc.Nonce(nonce)},
ShowLocalServerURL: in.ShowLocalServerURL.ShowLocalServerURL,
}
token, err := oauth2cli.GetToken(ctx, config)
if err != nil {
return nil, xerrors.Errorf("could not get a token: %w", err)
}
idToken, ok := token.Extra("id_token").(string)
if !ok {
return nil, xerrors.Errorf("id_token is missing in the token response: %s", token)
}
verifier := c.provider.Verifier(&oidc.Config{ClientID: c.oauth2Config.ClientID})
verifiedIDToken, err := verifier.Verify(ctx, idToken)
if err != nil {
return nil, xerrors.Errorf("could not verify the id_token: %w", err)
}
if verifiedIDToken.Nonce != nonce {
return nil, xerrors.Errorf("nonce of ID token did not match (want %s but was %s)", nonce, verifiedIDToken.Nonce)
}
claims, err := dumpClaims(verifiedIDToken)
if err != nil {
c.logger.V(1).Infof("incomplete claims of the ID token: %w", err)
}
return &adaptors.OIDCAuthenticateOut{
IDToken: idToken,
RefreshToken: token.RefreshToken,
IDTokenExpiry: verifiedIDToken.Expiry,
IDTokenClaims: claims,
}, nil
}
func newNonce() (string, error) {
var n uint64
if err := binary.Read(rand.Reader, binary.LittleEndian, &n); err != nil {
return "", xerrors.Errorf("error while reading random: %w", err)
}
return fmt.Sprintf("%x", n), nil
}
// AuthenticateByPassword performs the resource owner password credentials flow.
func (c *client) AuthenticateByPassword(ctx context.Context, in adaptors.OIDCAuthenticateByPasswordIn) (*adaptors.OIDCAuthenticateOut, error) {
ctx = c.wrapContext(ctx)
token, err := c.oauth2Config.PasswordCredentialsToken(ctx, in.Username, in.Password)
if err != nil {
return nil, xerrors.Errorf("could not get a token: %w", err)
}
idToken, ok := token.Extra("id_token").(string)
if !ok {
return nil, xerrors.Errorf("id_token is missing in the token response: %s", token)
}
verifier := c.provider.Verifier(&oidc.Config{ClientID: c.oauth2Config.ClientID})
verifiedIDToken, err := verifier.Verify(ctx, idToken)
if err != nil {
return nil, xerrors.Errorf("could not verify the id_token: %w", err)
}
claims, err := dumpClaims(verifiedIDToken)
if err != nil {
c.logger.V(1).Infof("incomplete claims of the ID token: %w", err)
}
return &adaptors.OIDCAuthenticateOut{
IDToken: idToken,
RefreshToken: token.RefreshToken,
IDTokenExpiry: verifiedIDToken.Expiry,
IDTokenClaims: claims,
}, nil
}
// Refresh sends a refresh token request and returns a token set.
func (c *client) Refresh(ctx context.Context, in adaptors.OIDCRefreshIn) (*adaptors.OIDCAuthenticateOut, error) {
ctx = c.wrapContext(ctx)
currentToken := &oauth2.Token{
Expiry: time.Now(),
RefreshToken: in.RefreshToken,
}
source := c.oauth2Config.TokenSource(ctx, currentToken)
token, err := source.Token()
if err != nil {
return nil, xerrors.Errorf("could not refresh the token: %w", err)
}
idToken, ok := token.Extra("id_token").(string)
if !ok {
return nil, xerrors.Errorf("id_token is missing in the token response: %s", token)
}
verifier := c.provider.Verifier(&oidc.Config{ClientID: c.oauth2Config.ClientID})
verifiedIDToken, err := verifier.Verify(ctx, idToken)
if err != nil {
return nil, xerrors.Errorf("could not verify the id_token: %w", err)
}
claims, err := dumpClaims(verifiedIDToken)
if err != nil {
c.logger.V(1).Infof("incomplete claims of the ID token: %w", err)
}
return &adaptors.OIDCAuthenticateOut{
IDToken: idToken,
RefreshToken: token.RefreshToken,
IDTokenExpiry: verifiedIDToken.Expiry,
IDTokenClaims: claims,
}, nil
}
func dumpClaims(token *oidc.IDToken) (map[string]string, error) {
var rawClaims map[string]interface{}
err := token.Claims(&rawClaims)
return dumpRawClaims(rawClaims), err
}

View File

@@ -1,66 +0,0 @@
package tls
import (
"crypto/tls"
"crypto/x509"
"encoding/base64"
"io/ioutil"
"github.com/int128/kubelogin/pkg/adaptors"
"golang.org/x/xerrors"
)
// NewConfig returns a tls.Config with the given certificates and options.
func NewConfig(config adaptors.OIDCClientConfig, logger adaptors.Logger) (*tls.Config, error) {
pool := x509.NewCertPool()
if config.Config.IDPCertificateAuthority != "" {
logger.V(1).Infof("Loading the certificate %s", config.Config.IDPCertificateAuthority)
err := appendCertificateFromFile(pool, config.Config.IDPCertificateAuthority)
if err != nil {
return nil, xerrors.Errorf("could not load the certificate of idp-certificate-authority: %w", err)
}
}
if config.Config.IDPCertificateAuthorityData != "" {
logger.V(1).Infof("Loading the certificate of idp-certificate-authority-data")
err := appendEncodedCertificate(pool, config.Config.IDPCertificateAuthorityData)
if err != nil {
return nil, xerrors.Errorf("could not load the certificate of idp-certificate-authority-data: %w", err)
}
}
if config.CACertFilename != "" {
logger.V(1).Infof("Loading the certificate %s", config.CACertFilename)
err := appendCertificateFromFile(pool, config.CACertFilename)
if err != nil {
return nil, xerrors.Errorf("could not load the certificate: %w", err)
}
}
c := &tls.Config{
InsecureSkipVerify: config.SkipTLSVerify,
}
if len(pool.Subjects()) > 0 {
c.RootCAs = pool
}
return c, nil
}
func appendCertificateFromFile(pool *x509.CertPool, filename string) error {
b, err := ioutil.ReadFile(filename)
if err != nil {
return xerrors.Errorf("could not read %s: %w", filename, err)
}
if !pool.AppendCertsFromPEM(b) {
return xerrors.Errorf("could not append certificate from %s", filename)
}
return nil
}
func appendEncodedCertificate(pool *x509.CertPool, base64String string) error {
b, err := base64.StdEncoding.DecodeString(base64String)
if err != nil {
return xerrors.Errorf("could not decode base64: %w", err)
}
if !pool.AppendCertsFromPEM(b) {
return xerrors.Errorf("could not append certificate")
}
return nil
}

View File

@@ -0,0 +1,59 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/int128/kubelogin/pkg/adaptors/tokencache (interfaces: Interface)
// Package mock_tokencache is a generated GoMock package.
package mock_tokencache
import (
gomock "github.com/golang/mock/gomock"
tokencache "github.com/int128/kubelogin/pkg/adaptors/tokencache"
reflect "reflect"
)
// MockInterface is a mock of Interface interface
type MockInterface struct {
ctrl *gomock.Controller
recorder *MockInterfaceMockRecorder
}
// MockInterfaceMockRecorder is the mock recorder for MockInterface
type MockInterfaceMockRecorder struct {
mock *MockInterface
}
// NewMockInterface creates a new mock instance
func NewMockInterface(ctrl *gomock.Controller) *MockInterface {
mock := &MockInterface{ctrl: ctrl}
mock.recorder = &MockInterfaceMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder {
return m.recorder
}
// FindByKey mocks base method
func (m *MockInterface) FindByKey(arg0 string, arg1 tokencache.Key) (*tokencache.TokenCache, error) {
ret := m.ctrl.Call(m, "FindByKey", arg0, arg1)
ret0, _ := ret[0].(*tokencache.TokenCache)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindByKey indicates an expected call of FindByKey
func (mr *MockInterfaceMockRecorder) FindByKey(arg0, arg1 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindByKey", reflect.TypeOf((*MockInterface)(nil).FindByKey), arg0, arg1)
}
// Save mocks base method
func (m *MockInterface) Save(arg0 string, arg1 tokencache.Key, arg2 tokencache.TokenCache) error {
ret := m.ctrl.Call(m, "Save", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
return ret0
}
// Save indicates an expected call of Save
func (mr *MockInterfaceMockRecorder) Save(arg0, arg1, arg2 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Save", reflect.TypeOf((*MockInterface)(nil).Save), arg0, arg1, arg2)
}

View File

@@ -8,22 +8,39 @@ import (
"path/filepath"
"github.com/google/wire"
"github.com/int128/kubelogin/pkg/adaptors"
"github.com/int128/kubelogin/pkg/models/credentialplugin"
"golang.org/x/xerrors"
)
//go:generate mockgen -destination mock_tokencache/mock_tokencache.go github.com/int128/kubelogin/pkg/adaptors/tokencache Interface
// Set provides an implementation and interface for Kubeconfig.
var Set = wire.NewSet(
wire.Struct(new(Repository), "*"),
wire.Bind(new(adaptors.TokenCacheRepository), new(*Repository)),
wire.Bind(new(Interface), new(*Repository)),
)
type Interface interface {
FindByKey(dir string, key Key) (*TokenCache, error)
Save(dir string, key Key, cache TokenCache) error
}
// Key represents a key of a token cache.
type Key struct {
IssuerURL string
ClientID string
}
// TokenCache represents a token cache.
type TokenCache struct {
IDToken string `json:"id_token,omitempty"`
RefreshToken string `json:"refresh_token,omitempty"`
}
// Repository provides access to the token cache on the local filesystem.
// 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 credentialplugin.TokenCacheKey) (*credentialplugin.TokenCache, error) {
func (r *Repository) FindByKey(dir string, key Key) (*TokenCache, error) {
filename := filepath.Join(dir, computeFilename(key))
f, err := os.Open(filename)
if err != nil {
@@ -31,14 +48,14 @@ func (r *Repository) FindByKey(dir string, key credentialplugin.TokenCacheKey) (
}
defer f.Close()
d := json.NewDecoder(f)
var c credentialplugin.TokenCache
var c TokenCache
if err := d.Decode(&c); err != nil {
return nil, xerrors.Errorf("could not decode json file %s: %w", filename, err)
}
return &c, nil
}
func (r *Repository) Save(dir string, key credentialplugin.TokenCacheKey, cache credentialplugin.TokenCache) error {
func (r *Repository) Save(dir string, key Key, cache TokenCache) error {
if err := os.MkdirAll(dir, 0700); err != nil {
return xerrors.Errorf("could not create directory %s: %w", dir, err)
}
@@ -55,7 +72,7 @@ func (r *Repository) Save(dir string, key credentialplugin.TokenCacheKey, cache
return nil
}
func computeFilename(key credentialplugin.TokenCacheKey) string {
func computeFilename(key Key) string {
s := sha256.New()
_, _ = s.Write([]byte(key.IssuerURL))
_, _ = s.Write([]byte{0x00})

View File

@@ -7,7 +7,6 @@ import (
"testing"
"github.com/go-test/deep"
"github.com/int128/kubelogin/pkg/models/credentialplugin"
)
func TestRepository_FindByKey(t *testing.T) {
@@ -23,7 +22,7 @@ func TestRepository_FindByKey(t *testing.T) {
t.Errorf("could not clean up the temp dir: %s", err)
}
}()
key := credentialplugin.TokenCacheKey{
key := Key{
IssuerURL: "YOUR_ISSUER",
ClientID: "YOUR_CLIENT_ID",
}
@@ -37,7 +36,7 @@ func TestRepository_FindByKey(t *testing.T) {
if err != nil {
t.Errorf("err wants nil but %+v", err)
}
want := &credentialplugin.TokenCache{IDToken: "YOUR_ID_TOKEN", RefreshToken: "YOUR_REFRESH_TOKEN"}
want := &TokenCache{IDToken: "YOUR_ID_TOKEN", RefreshToken: "YOUR_REFRESH_TOKEN"}
if diff := deep.Equal(tokenCache, want); diff != nil {
t.Error(diff)
}
@@ -58,11 +57,11 @@ func TestRepository_Save(t *testing.T) {
}
}()
key := credentialplugin.TokenCacheKey{
key := Key{
IssuerURL: "YOUR_ISSUER",
ClientID: "YOUR_CLIENT_ID",
}
tokenCache := credentialplugin.TokenCache{IDToken: "YOUR_ID_TOKEN", RefreshToken: "YOUR_REFRESH_TOKEN"}
tokenCache := TokenCache{IDToken: "YOUR_ID_TOKEN", RefreshToken: "YOUR_REFRESH_TOKEN"}
if err := r.Save(dir, key, tokenCache); err != nil {
t.Errorf("err wants nil but %+v", err)
}

View File

@@ -5,7 +5,6 @@ package di
import (
"github.com/google/wire"
"github.com/int128/kubelogin/pkg/adaptors"
"github.com/int128/kubelogin/pkg/adaptors/cmd"
credentialPluginAdaptor "github.com/int128/kubelogin/pkg/adaptors/credentialplugin"
"github.com/int128/kubelogin/pkg/adaptors/env"
@@ -13,19 +12,21 @@ import (
"github.com/int128/kubelogin/pkg/adaptors/logger"
"github.com/int128/kubelogin/pkg/adaptors/oidc"
"github.com/int128/kubelogin/pkg/adaptors/tokencache"
"github.com/int128/kubelogin/pkg/usecases"
"github.com/int128/kubelogin/pkg/usecases/auth"
credentialPluginUseCase "github.com/int128/kubelogin/pkg/usecases/credentialplugin"
"github.com/int128/kubelogin/pkg/usecases/login"
"github.com/int128/kubelogin/pkg/usecases/standalone"
)
// NewCmd returns an instance of adaptors.Cmd.
func NewCmd() adaptors.Cmd {
func NewCmd() cmd.Interface {
wire.Build(
// use-cases
auth.Set,
auth.ExtraSet,
login.Set,
standalone.Set,
credentialPluginUseCase.Set,
// adaptors
cmd.Set,
env.Set,
kubeconfig.Set,
@@ -39,13 +40,13 @@ func NewCmd() adaptors.Cmd {
// NewCmdForHeadless returns an instance of adaptors.Cmd for headless testing.
func NewCmdForHeadless(
adaptors.Logger,
usecases.LoginShowLocalServerURL,
adaptors.CredentialPluginInteraction,
) adaptors.Cmd {
logger.Interface,
auth.ShowLocalServerURLInterface,
credentialPluginAdaptor.Interface,
) cmd.Interface {
wire.Build(
auth.Set,
login.Set,
standalone.Set,
credentialPluginUseCase.Set,
cmd.Set,
env.Set,

View File

@@ -6,7 +6,6 @@
package di
import (
"github.com/int128/kubelogin/pkg/adaptors"
"github.com/int128/kubelogin/pkg/adaptors/cmd"
"github.com/int128/kubelogin/pkg/adaptors/credentialplugin"
"github.com/int128/kubelogin/pkg/adaptors/env"
@@ -14,40 +13,39 @@ import (
"github.com/int128/kubelogin/pkg/adaptors/logger"
"github.com/int128/kubelogin/pkg/adaptors/oidc"
"github.com/int128/kubelogin/pkg/adaptors/tokencache"
"github.com/int128/kubelogin/pkg/usecases"
"github.com/int128/kubelogin/pkg/usecases/auth"
credentialplugin2 "github.com/int128/kubelogin/pkg/usecases/credentialplugin"
"github.com/int128/kubelogin/pkg/usecases/login"
"github.com/int128/kubelogin/pkg/usecases/standalone"
)
// Injectors from di.go:
func NewCmd() adaptors.Cmd {
adaptorsLogger := logger.New()
func NewCmd() cmd.Interface {
loggerInterface := logger.New()
factory := &oidc.Factory{
Logger: adaptorsLogger,
Logger: loggerInterface,
}
decoder := &oidc.Decoder{}
envEnv := &env.Env{}
showLocalServerURL := &auth.ShowLocalServerURL{
Logger: adaptorsLogger,
Logger: loggerInterface,
}
authentication := &auth.Authentication{
OIDC: factory,
OIDCFactory: factory,
OIDCDecoder: decoder,
Env: envEnv,
Logger: adaptorsLogger,
Logger: loggerInterface,
ShowLocalServerURL: showLocalServerURL,
}
kubeconfigKubeconfig := &kubeconfig.Kubeconfig{}
loginLogin := &login.Login{
standaloneStandalone := &standalone.Standalone{
Authentication: authentication,
Kubeconfig: kubeconfigKubeconfig,
Logger: adaptorsLogger,
Logger: loggerInterface,
}
root := &cmd.Root{
Login: loginLogin,
Logger: adaptorsLogger,
Standalone: standaloneStandalone,
Logger: loggerInterface,
}
repository := &tokencache.Repository{}
interaction := &credentialplugin.Interaction{}
@@ -55,58 +53,58 @@ func NewCmd() adaptors.Cmd {
Authentication: authentication,
TokenCacheRepository: repository,
Interaction: interaction,
Logger: adaptorsLogger,
Logger: loggerInterface,
}
cmdGetToken := &cmd.GetToken{
GetToken: getToken,
Logger: adaptorsLogger,
Logger: loggerInterface,
}
cmdCmd := &cmd.Cmd{
Root: root,
GetToken: cmdGetToken,
Logger: adaptorsLogger,
Logger: loggerInterface,
}
return cmdCmd
}
func NewCmdForHeadless(adaptorsLogger adaptors.Logger, loginShowLocalServerURL usecases.LoginShowLocalServerURL, credentialPluginInteraction adaptors.CredentialPluginInteraction) adaptors.Cmd {
func NewCmdForHeadless(loggerInterface logger.Interface, showLocalServerURLInterface auth.ShowLocalServerURLInterface, credentialpluginInterface credentialplugin.Interface) cmd.Interface {
factory := &oidc.Factory{
Logger: adaptorsLogger,
Logger: loggerInterface,
}
decoder := &oidc.Decoder{}
envEnv := &env.Env{}
authentication := &auth.Authentication{
OIDC: factory,
OIDCFactory: factory,
OIDCDecoder: decoder,
Env: envEnv,
Logger: adaptorsLogger,
ShowLocalServerURL: loginShowLocalServerURL,
Logger: loggerInterface,
ShowLocalServerURL: showLocalServerURLInterface,
}
kubeconfigKubeconfig := &kubeconfig.Kubeconfig{}
loginLogin := &login.Login{
standaloneStandalone := &standalone.Standalone{
Authentication: authentication,
Kubeconfig: kubeconfigKubeconfig,
Logger: adaptorsLogger,
Logger: loggerInterface,
}
root := &cmd.Root{
Login: loginLogin,
Logger: adaptorsLogger,
Standalone: standaloneStandalone,
Logger: loggerInterface,
}
repository := &tokencache.Repository{}
getToken := &credentialplugin2.GetToken{
Authentication: authentication,
TokenCacheRepository: repository,
Interaction: credentialPluginInteraction,
Logger: adaptorsLogger,
Interaction: credentialpluginInterface,
Logger: loggerInterface,
}
cmdGetToken := &cmd.GetToken{
GetToken: getToken,
Logger: adaptorsLogger,
Logger: loggerInterface,
}
cmdCmd := &cmd.Cmd{
Root: root,
GetToken: cmdGetToken,
Logger: adaptorsLogger,
Logger: loggerInterface,
}
return cmdCmd
}

View File

@@ -1,22 +0,0 @@
// Package credentialplugin provides models for the credential plugin.
package credentialplugin
import "time"
// TokenCacheKey represents a key of a token cache.
type TokenCacheKey struct {
IssuerURL string
ClientID string
}
// TokenCache represents a token cache.
type TokenCache struct {
IDToken string `json:"id_token,omitempty"`
RefreshToken string `json:"refresh_token,omitempty"`
}
// Output represents an output object of the credential plugin.
type Output struct {
Token string
Expiry time.Time
}

View File

@@ -1,28 +0,0 @@
package kubeconfig
// ContextName represents name of a context.
type ContextName string
// UserName represents name of a user.
type UserName string
// AuthProvider represents the authentication provider,
// i.e. context, user and auth-provider in a kubeconfig.
type AuthProvider struct {
LocationOfOrigin string // Path to the kubeconfig file which contains the user
UserName UserName // User name
ContextName ContextName // Context name (optional)
OIDCConfig OIDCConfig
}
// OIDCConfig represents a configuration of an OIDC provider.
type OIDCConfig struct {
IDPIssuerURL string // idp-issuer-url
ClientID string // client-id
ClientSecret string // client-secret
IDPCertificateAuthority string // (optional) idp-certificate-authority
IDPCertificateAuthorityData string // (optional) idp-certificate-authority-data
ExtraScopes []string // (optional) extra-scopes
IDToken string // (optional) id-token
RefreshToken string // (optional) refresh-token
}

View File

@@ -5,23 +5,57 @@ import (
"time"
"github.com/google/wire"
"github.com/int128/kubelogin/pkg/adaptors"
"github.com/int128/kubelogin/pkg/usecases"
"github.com/int128/kubelogin/pkg/adaptors/env"
"github.com/int128/kubelogin/pkg/adaptors/kubeconfig"
"github.com/int128/kubelogin/pkg/adaptors/logger"
"github.com/int128/kubelogin/pkg/adaptors/oidc"
"golang.org/x/xerrors"
)
//go:generate mockgen -destination mock_auth/mock_auth.go github.com/int128/kubelogin/pkg/usecases/auth Interface
// Set provides the use-case of Authentication.
var Set = wire.NewSet(
wire.Struct(new(Authentication), "*"),
wire.Bind(new(usecases.Authentication), new(*Authentication)),
wire.Bind(new(Interface), new(*Authentication)),
)
// ExtraSet is a set of interaction components for e2e testing.
var ExtraSet = wire.NewSet(
wire.Struct(new(ShowLocalServerURL), "*"),
wire.Bind(new(usecases.LoginShowLocalServerURL), new(*ShowLocalServerURL)),
wire.Bind(new(ShowLocalServerURLInterface), new(*ShowLocalServerURL)),
)
type Interface interface {
Do(ctx context.Context, in Input) (*Output, error)
}
// ShowLocalServerURLInterface provides an interface to notify the URL of local server.
// It is needed for the end-to-end tests.
type ShowLocalServerURLInterface interface {
ShowLocalServerURL(url string)
}
// Input represents an input DTO of the Authentication use-case.
type Input struct {
OIDCConfig kubeconfig.OIDCConfig
SkipOpenBrowser bool
ListenPort []int
Username string // If set, perform the resource owner password credentials grant
Password string // If empty, read a password using Env.ReadPassword()
CACertFilename string // If set, use the CA cert
SkipTLSVerify bool
}
// Output represents an output DTO of the Authentication use-case.
type Output struct {
AlreadyHasValidIDToken bool
IDTokenExpiry time.Time
IDTokenClaims map[string]string
IDToken string
RefreshToken string
}
const passwordPrompt = "Password: "
// Authentication provides the internal use-case of authentication.
@@ -38,14 +72,14 @@ const passwordPrompt = "Password: "
// If the Password is not set, it asks a password by the prompt.
//
type Authentication struct {
OIDC adaptors.OIDC
OIDCDecoder adaptors.OIDCDecoder
Env adaptors.Env
Logger adaptors.Logger
ShowLocalServerURL usecases.LoginShowLocalServerURL
OIDCFactory oidc.FactoryInterface
OIDCDecoder oidc.DecoderInterface
Env env.Interface
Logger logger.Interface
ShowLocalServerURL ShowLocalServerURLInterface
}
func (u *Authentication) Do(ctx context.Context, in usecases.AuthenticationIn) (*usecases.AuthenticationOut, error) {
func (u *Authentication) Do(ctx context.Context, in Input) (*Output, error) {
if in.OIDCConfig.IDToken != "" {
u.Logger.V(1).Infof("checking expiration of the existing token")
// Skip verification of the token to reduce time of a discovery request.
@@ -57,7 +91,7 @@ func (u *Authentication) Do(ctx context.Context, in usecases.AuthenticationIn) (
}
if token.IDTokenExpiry.After(time.Now()) { //TODO: inject time service
u.Logger.V(1).Infof("you already have a valid token until %s", token.IDTokenExpiry)
return &usecases.AuthenticationOut{
return &Output{
AlreadyHasValidIDToken: true,
IDToken: in.OIDCConfig.IDToken,
RefreshToken: in.OIDCConfig.RefreshToken,
@@ -68,23 +102,23 @@ func (u *Authentication) Do(ctx context.Context, in usecases.AuthenticationIn) (
u.Logger.V(1).Infof("you have an expired token at %s", token.IDTokenExpiry)
}
u.Logger.V(1).Infof("initializing an OIDC client")
client, err := u.OIDC.New(ctx, adaptors.OIDCClientConfig{
u.Logger.V(1).Infof("initializing an OIDCFactory client")
client, err := u.OIDCFactory.New(ctx, oidc.ClientConfig{
Config: in.OIDCConfig,
CACertFilename: in.CACertFilename,
SkipTLSVerify: in.SkipTLSVerify,
})
if err != nil {
return nil, xerrors.Errorf("could not create an OIDC client: %w", err)
return nil, xerrors.Errorf("could not create an OIDCFactory client: %w", err)
}
if in.OIDCConfig.RefreshToken != "" {
u.Logger.V(1).Infof("refreshing the token")
out, err := client.Refresh(ctx, adaptors.OIDCRefreshIn{
out, err := client.Refresh(ctx, oidc.RefreshIn{
RefreshToken: in.OIDCConfig.RefreshToken,
})
if err == nil {
return &usecases.AuthenticationOut{
return &Output{
IDToken: out.IDToken,
RefreshToken: out.RefreshToken,
IDTokenExpiry: out.IDTokenExpiry,
@@ -96,7 +130,7 @@ func (u *Authentication) Do(ctx context.Context, in usecases.AuthenticationIn) (
if in.Username == "" {
u.Logger.V(1).Infof("performing the authentication code flow")
out, err := client.AuthenticateByCode(ctx, adaptors.OIDCAuthenticateByCodeIn{
out, err := client.AuthenticateByCode(ctx, oidc.AuthenticateByCodeIn{
LocalServerPort: in.ListenPort,
SkipOpenBrowser: in.SkipOpenBrowser,
ShowLocalServerURL: u.ShowLocalServerURL,
@@ -104,7 +138,7 @@ func (u *Authentication) Do(ctx context.Context, in usecases.AuthenticationIn) (
if err != nil {
return nil, xerrors.Errorf("error while the authorization code flow: %w", err)
}
return &usecases.AuthenticationOut{
return &Output{
IDToken: out.IDToken,
RefreshToken: out.RefreshToken,
IDTokenExpiry: out.IDTokenExpiry,
@@ -119,14 +153,14 @@ func (u *Authentication) Do(ctx context.Context, in usecases.AuthenticationIn) (
return nil, xerrors.Errorf("could not read a password: %w", err)
}
}
out, err := client.AuthenticateByPassword(ctx, adaptors.OIDCAuthenticateByPasswordIn{
out, err := client.AuthenticateByPassword(ctx, oidc.AuthenticateByPasswordIn{
Username: in.Username,
Password: in.Password,
})
if err != nil {
return nil, xerrors.Errorf("error while the resource owner password credentials flow: %w", err)
}
return &usecases.AuthenticationOut{
return &Output{
IDToken: out.IDToken,
RefreshToken: out.RefreshToken,
IDTokenExpiry: out.IDTokenExpiry,
@@ -136,7 +170,7 @@ func (u *Authentication) Do(ctx context.Context, in usecases.AuthenticationIn) (
// ShowLocalServerURL just shows the URL of local server to console.
type ShowLocalServerURL struct {
Logger adaptors.Logger
Logger logger.Interface
}
func (s *ShowLocalServerURL) ShowLocalServerURL(url string) {

View File

@@ -7,10 +7,11 @@ import (
"github.com/go-test/deep"
"github.com/golang/mock/gomock"
"github.com/int128/kubelogin/pkg/adaptors"
"github.com/int128/kubelogin/pkg/adaptors/mock_adaptors"
"github.com/int128/kubelogin/pkg/models/kubeconfig"
"github.com/int128/kubelogin/pkg/usecases"
"github.com/int128/kubelogin/pkg/adaptors/env/mock_env"
"github.com/int128/kubelogin/pkg/adaptors/kubeconfig"
"github.com/int128/kubelogin/pkg/adaptors/logger/mock_logger"
"github.com/int128/kubelogin/pkg/adaptors/oidc"
"github.com/int128/kubelogin/pkg/adaptors/oidc/mock_oidc"
"golang.org/x/xerrors"
)
@@ -23,7 +24,7 @@ func TestAuthentication_Do(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.TODO()
in := usecases.AuthenticationIn{
in := Input{
ListenPort: []int{10000},
SkipOpenBrowser: true,
CACertFilename: "/path/to/cert",
@@ -33,35 +34,35 @@ func TestAuthentication_Do(t *testing.T) {
ClientSecret: "YOUR_CLIENT_SECRET",
},
}
mockOIDCClient := mock_adaptors.NewMockOIDCClient(ctrl)
mockOIDCClient := mock_oidc.NewMockInterface(ctrl)
mockOIDCClient.EXPECT().
AuthenticateByCode(ctx, adaptors.OIDCAuthenticateByCodeIn{
AuthenticateByCode(ctx, oidc.AuthenticateByCodeIn{
LocalServerPort: []int{10000},
SkipOpenBrowser: true,
}).
Return(&adaptors.OIDCAuthenticateOut{
Return(&oidc.AuthenticateOut{
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenExpiry: futureTime,
IDTokenClaims: dummyTokenClaims,
}, nil)
mockOIDC := mock_adaptors.NewMockOIDC(ctrl)
mockOIDC.EXPECT().
New(ctx, adaptors.OIDCClientConfig{
mockOIDCFactory := mock_oidc.NewMockFactoryInterface(ctrl)
mockOIDCFactory.EXPECT().
New(ctx, oidc.ClientConfig{
Config: in.OIDCConfig,
CACertFilename: "/path/to/cert",
SkipTLSVerify: true,
}).
Return(mockOIDCClient, nil)
u := Authentication{
OIDC: mockOIDC,
Logger: mock_adaptors.NewLogger(t),
OIDCFactory: mockOIDCFactory,
Logger: mock_logger.New(t),
}
out, err := u.Do(ctx, in)
if err != nil {
t.Errorf("Do returned error: %+v", err)
}
want := &usecases.AuthenticationOut{
want := &Output{
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenExpiry: futureTime,
@@ -76,7 +77,7 @@ func TestAuthentication_Do(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.TODO()
in := usecases.AuthenticationIn{
in := Input{
Username: "USER",
Password: "PASS",
CACertFilename: "/path/to/cert",
@@ -86,35 +87,35 @@ func TestAuthentication_Do(t *testing.T) {
ClientSecret: "YOUR_CLIENT_SECRET",
},
}
mockOIDCClient := mock_adaptors.NewMockOIDCClient(ctrl)
mockOIDCClient := mock_oidc.NewMockInterface(ctrl)
mockOIDCClient.EXPECT().
AuthenticateByPassword(ctx, adaptors.OIDCAuthenticateByPasswordIn{
AuthenticateByPassword(ctx, oidc.AuthenticateByPasswordIn{
Username: "USER",
Password: "PASS",
}).
Return(&adaptors.OIDCAuthenticateOut{
Return(&oidc.AuthenticateOut{
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenExpiry: futureTime,
IDTokenClaims: dummyTokenClaims,
}, nil)
mockOIDC := mock_adaptors.NewMockOIDC(ctrl)
mockOIDC.EXPECT().
New(ctx, adaptors.OIDCClientConfig{
mockOIDCFactory := mock_oidc.NewMockFactoryInterface(ctrl)
mockOIDCFactory.EXPECT().
New(ctx, oidc.ClientConfig{
Config: in.OIDCConfig,
CACertFilename: "/path/to/cert",
SkipTLSVerify: true,
}).
Return(mockOIDCClient, nil)
u := Authentication{
OIDC: mockOIDC,
Logger: mock_adaptors.NewLogger(t),
OIDCFactory: mockOIDCFactory,
Logger: mock_logger.New(t),
}
out, err := u.Do(ctx, in)
if err != nil {
t.Errorf("Do returned error: %+v", err)
}
want := &usecases.AuthenticationOut{
want := &Output{
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenExpiry: futureTime,
@@ -129,43 +130,43 @@ func TestAuthentication_Do(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.TODO()
in := usecases.AuthenticationIn{
in := Input{
Username: "USER",
OIDCConfig: kubeconfig.OIDCConfig{
ClientID: "YOUR_CLIENT_ID",
ClientSecret: "YOUR_CLIENT_SECRET",
},
}
mockOIDCClient := mock_adaptors.NewMockOIDCClient(ctrl)
mockOIDCClient := mock_oidc.NewMockInterface(ctrl)
mockOIDCClient.EXPECT().
AuthenticateByPassword(ctx, adaptors.OIDCAuthenticateByPasswordIn{
AuthenticateByPassword(ctx, oidc.AuthenticateByPasswordIn{
Username: "USER",
Password: "PASS",
}).
Return(&adaptors.OIDCAuthenticateOut{
Return(&oidc.AuthenticateOut{
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenExpiry: futureTime,
IDTokenClaims: dummyTokenClaims,
}, nil)
mockOIDC := mock_adaptors.NewMockOIDC(ctrl)
mockOIDC.EXPECT().
New(ctx, adaptors.OIDCClientConfig{
mockOIDCFactory := mock_oidc.NewMockFactoryInterface(ctrl)
mockOIDCFactory.EXPECT().
New(ctx, oidc.ClientConfig{
Config: in.OIDCConfig,
}).
Return(mockOIDCClient, nil)
mockEnv := mock_adaptors.NewMockEnv(ctrl)
mockEnv := mock_env.NewMockInterface(ctrl)
mockEnv.EXPECT().ReadPassword(passwordPrompt).Return("PASS", nil)
u := Authentication{
OIDC: mockOIDC,
Env: mockEnv,
Logger: mock_adaptors.NewLogger(t),
OIDCFactory: mockOIDCFactory,
Env: mockEnv,
Logger: mock_logger.New(t),
}
out, err := u.Do(ctx, in)
if err != nil {
t.Errorf("Do returned error: %+v", err)
}
want := &usecases.AuthenticationOut{
want := &Output{
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenExpiry: futureTime,
@@ -180,25 +181,25 @@ func TestAuthentication_Do(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.TODO()
in := usecases.AuthenticationIn{
in := Input{
Username: "USER",
OIDCConfig: kubeconfig.OIDCConfig{
ClientID: "YOUR_CLIENT_ID",
ClientSecret: "YOUR_CLIENT_SECRET",
},
}
mockOIDC := mock_adaptors.NewMockOIDC(ctrl)
mockOIDC.EXPECT().
New(ctx, adaptors.OIDCClientConfig{
mockOIDCFactory := mock_oidc.NewMockFactoryInterface(ctrl)
mockOIDCFactory.EXPECT().
New(ctx, oidc.ClientConfig{
Config: in.OIDCConfig,
}).
Return(mock_adaptors.NewMockOIDCClient(ctrl), nil)
mockEnv := mock_adaptors.NewMockEnv(ctrl)
Return(mock_oidc.NewMockInterface(ctrl), nil)
mockEnv := mock_env.NewMockInterface(ctrl)
mockEnv.EXPECT().ReadPassword(passwordPrompt).Return("", xerrors.New("error"))
u := Authentication{
OIDC: mockOIDC,
Env: mockEnv,
Logger: mock_adaptors.NewLogger(t),
OIDCFactory: mockOIDCFactory,
Env: mockEnv,
Logger: mock_logger.New(t),
}
out, err := u.Do(ctx, in)
if err == nil {
@@ -213,30 +214,30 @@ func TestAuthentication_Do(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.TODO()
in := usecases.AuthenticationIn{
in := Input{
OIDCConfig: kubeconfig.OIDCConfig{
ClientID: "YOUR_CLIENT_ID",
ClientSecret: "YOUR_CLIENT_SECRET",
IDToken: "VALID_ID_TOKEN",
},
}
mockOIDCDecoder := mock_adaptors.NewMockOIDCDecoder(ctrl)
mockOIDCDecoder := mock_oidc.NewMockDecoderInterface(ctrl)
mockOIDCDecoder.EXPECT().
DecodeIDToken("VALID_ID_TOKEN").
Return(&adaptors.DecodedIDToken{
Return(&oidc.DecodedIDToken{
IDTokenExpiry: futureTime,
IDTokenClaims: dummyTokenClaims,
}, nil)
u := Authentication{
OIDC: mock_adaptors.NewMockOIDC(ctrl),
OIDCFactory: mock_oidc.NewMockFactoryInterface(ctrl),
OIDCDecoder: mockOIDCDecoder,
Logger: mock_adaptors.NewLogger(t),
Logger: mock_logger.New(t),
}
out, err := u.Do(ctx, in)
if err != nil {
t.Errorf("Do returned error: %+v", err)
}
want := &usecases.AuthenticationOut{
want := &Output{
AlreadyHasValidIDToken: true,
IDToken: "VALID_ID_TOKEN",
IDTokenExpiry: futureTime,
@@ -251,7 +252,7 @@ func TestAuthentication_Do(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.TODO()
in := usecases.AuthenticationIn{
in := Input{
OIDCConfig: kubeconfig.OIDCConfig{
ClientID: "YOUR_CLIENT_ID",
ClientSecret: "YOUR_CLIENT_SECRET",
@@ -259,40 +260,40 @@ func TestAuthentication_Do(t *testing.T) {
RefreshToken: "VALID_REFRESH_TOKEN",
},
}
mockOIDCDecoder := mock_adaptors.NewMockOIDCDecoder(ctrl)
mockOIDCDecoder := mock_oidc.NewMockDecoderInterface(ctrl)
mockOIDCDecoder.EXPECT().
DecodeIDToken("EXPIRED_ID_TOKEN").
Return(&adaptors.DecodedIDToken{
Return(&oidc.DecodedIDToken{
IDTokenExpiry: pastTime,
IDTokenClaims: dummyTokenClaims,
}, nil)
mockOIDCClient := mock_adaptors.NewMockOIDCClient(ctrl)
mockOIDCClient := mock_oidc.NewMockInterface(ctrl)
mockOIDCClient.EXPECT().
Refresh(ctx, adaptors.OIDCRefreshIn{
Refresh(ctx, oidc.RefreshIn{
RefreshToken: "VALID_REFRESH_TOKEN",
}).
Return(&adaptors.OIDCAuthenticateOut{
Return(&oidc.AuthenticateOut{
IDToken: "NEW_ID_TOKEN",
RefreshToken: "NEW_REFRESH_TOKEN",
IDTokenExpiry: futureTime,
IDTokenClaims: dummyTokenClaims,
}, nil)
mockOIDC := mock_adaptors.NewMockOIDC(ctrl)
mockOIDC.EXPECT().
New(ctx, adaptors.OIDCClientConfig{
mockOIDCFactory := mock_oidc.NewMockFactoryInterface(ctrl)
mockOIDCFactory.EXPECT().
New(ctx, oidc.ClientConfig{
Config: in.OIDCConfig,
}).
Return(mockOIDCClient, nil)
u := Authentication{
OIDC: mockOIDC,
OIDCFactory: mockOIDCFactory,
OIDCDecoder: mockOIDCDecoder,
Logger: mock_adaptors.NewLogger(t),
Logger: mock_logger.New(t),
}
out, err := u.Do(ctx, in)
if err != nil {
t.Errorf("Do returned error: %+v", err)
}
want := &usecases.AuthenticationOut{
want := &Output{
IDToken: "NEW_ID_TOKEN",
RefreshToken: "NEW_REFRESH_TOKEN",
IDTokenExpiry: futureTime,
@@ -307,7 +308,7 @@ func TestAuthentication_Do(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.TODO()
in := usecases.AuthenticationIn{
in := Input{
ListenPort: []int{10000},
OIDCConfig: kubeconfig.OIDCConfig{
ClientID: "YOUR_CLIENT_ID",
@@ -316,45 +317,45 @@ func TestAuthentication_Do(t *testing.T) {
RefreshToken: "EXPIRED_REFRESH_TOKEN",
},
}
mockOIDCDecoder := mock_adaptors.NewMockOIDCDecoder(ctrl)
mockOIDCDecoder := mock_oidc.NewMockDecoderInterface(ctrl)
mockOIDCDecoder.EXPECT().
DecodeIDToken("EXPIRED_ID_TOKEN").
Return(&adaptors.DecodedIDToken{
Return(&oidc.DecodedIDToken{
IDTokenExpiry: pastTime,
IDTokenClaims: dummyTokenClaims,
}, nil)
mockOIDCClient := mock_adaptors.NewMockOIDCClient(ctrl)
mockOIDCClient := mock_oidc.NewMockInterface(ctrl)
mockOIDCClient.EXPECT().
Refresh(ctx, adaptors.OIDCRefreshIn{
Refresh(ctx, oidc.RefreshIn{
RefreshToken: "EXPIRED_REFRESH_TOKEN",
}).
Return(nil, xerrors.New("token has expired"))
mockOIDCClient.EXPECT().
AuthenticateByCode(ctx, adaptors.OIDCAuthenticateByCodeIn{
AuthenticateByCode(ctx, oidc.AuthenticateByCodeIn{
LocalServerPort: []int{10000},
}).
Return(&adaptors.OIDCAuthenticateOut{
Return(&oidc.AuthenticateOut{
IDToken: "NEW_ID_TOKEN",
RefreshToken: "NEW_REFRESH_TOKEN",
IDTokenExpiry: futureTime,
IDTokenClaims: dummyTokenClaims,
}, nil)
mockOIDC := mock_adaptors.NewMockOIDC(ctrl)
mockOIDC.EXPECT().
New(ctx, adaptors.OIDCClientConfig{
mockOIDCFactory := mock_oidc.NewMockFactoryInterface(ctrl)
mockOIDCFactory.EXPECT().
New(ctx, oidc.ClientConfig{
Config: in.OIDCConfig,
}).
Return(mockOIDCClient, nil)
u := Authentication{
OIDC: mockOIDC,
OIDCFactory: mockOIDCFactory,
OIDCDecoder: mockOIDCDecoder,
Logger: mock_adaptors.NewLogger(t),
Logger: mock_logger.New(t),
}
out, err := u.Do(ctx, in)
if err != nil {
t.Errorf("Do returned error: %+v", err)
}
want := &usecases.AuthenticationOut{
want := &Output{
IDToken: "NEW_ID_TOKEN",
RefreshToken: "NEW_REFRESH_TOKEN",
IDTokenExpiry: futureTime,

View File

@@ -0,0 +1,48 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/int128/kubelogin/pkg/usecases/auth (interfaces: Interface)
// Package mock_auth is a generated GoMock package.
package mock_auth
import (
context "context"
gomock "github.com/golang/mock/gomock"
auth "github.com/int128/kubelogin/pkg/usecases/auth"
reflect "reflect"
)
// MockInterface is a mock of Interface interface
type MockInterface struct {
ctrl *gomock.Controller
recorder *MockInterfaceMockRecorder
}
// MockInterfaceMockRecorder is the mock recorder for MockInterface
type MockInterfaceMockRecorder struct {
mock *MockInterface
}
// NewMockInterface creates a new mock instance
func NewMockInterface(ctrl *gomock.Controller) *MockInterface {
mock := &MockInterface{ctrl: ctrl}
mock.recorder = &MockInterfaceMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder {
return m.recorder
}
// Do mocks base method
func (m *MockInterface) Do(arg0 context.Context, arg1 auth.Input) (*auth.Output, error) {
ret := m.ctrl.Call(m, "Do", arg0, arg1)
ret0, _ := ret[0].(*auth.Output)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Do indicates an expected call of Do
func (mr *MockInterfaceMockRecorder) Do(arg0, arg1 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Do", reflect.TypeOf((*MockInterface)(nil).Do), arg0, arg1)
}

View File

@@ -7,36 +7,58 @@ import (
"context"
"github.com/google/wire"
"github.com/int128/kubelogin/pkg/adaptors"
"github.com/int128/kubelogin/pkg/models/credentialplugin"
"github.com/int128/kubelogin/pkg/models/kubeconfig"
"github.com/int128/kubelogin/pkg/usecases"
"github.com/int128/kubelogin/pkg/adaptors/credentialplugin"
"github.com/int128/kubelogin/pkg/adaptors/kubeconfig"
"github.com/int128/kubelogin/pkg/adaptors/logger"
"github.com/int128/kubelogin/pkg/adaptors/tokencache"
"github.com/int128/kubelogin/pkg/usecases/auth"
"golang.org/x/xerrors"
)
//go:generate mockgen -destination mock_credentialplugin/mock_credentialplugin.go github.com/int128/kubelogin/pkg/usecases/credentialplugin Interface
var Set = wire.NewSet(
wire.Struct(new(GetToken), "*"),
wire.Bind(new(usecases.GetToken), new(*GetToken)),
wire.Bind(new(Interface), new(*GetToken)),
)
type GetToken struct {
Authentication usecases.Authentication
TokenCacheRepository adaptors.TokenCacheRepository
Interaction adaptors.CredentialPluginInteraction
Logger adaptors.Logger
type Interface interface {
Do(ctx context.Context, in Input) error
}
func (u *GetToken) Do(ctx context.Context, in usecases.GetTokenIn) error {
// Input represents an input DTO of the GetToken use-case.
type Input struct {
IssuerURL string
ClientID string
ClientSecret string
ExtraScopes []string // optional
SkipOpenBrowser bool
ListenPort []int
Username string // If set, perform the resource owner password credentials grant
Password string // If empty, read a password using Env.ReadPassword()
CACertFilename string // If set, use the CA cert
SkipTLSVerify bool
TokenCacheDir string
}
type GetToken struct {
Authentication auth.Interface
TokenCacheRepository tokencache.Interface
Interaction credentialplugin.Interface
Logger logger.Interface
}
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")
u.Logger.V(1).Infof("finding a token from cache directory %s", in.TokenCacheDir)
cacheKey := credentialplugin.TokenCacheKey{IssuerURL: in.IssuerURL, ClientID: in.ClientID}
cacheKey := tokencache.Key{IssuerURL: in.IssuerURL, ClientID: in.ClientID}
cache, err := u.TokenCacheRepository.FindByKey(in.TokenCacheDir, cacheKey)
if err != nil {
u.Logger.V(1).Infof("could not find a token cache: %s", err)
cache = &credentialplugin.TokenCache{}
cache = &tokencache.TokenCache{}
}
out, err := u.Authentication.Do(ctx, usecases.AuthenticationIn{
out, err := u.Authentication.Do(ctx, auth.Input{
OIDCConfig: kubeconfig.OIDCConfig{
IDPIssuerURL: in.IssuerURL,
ClientID: in.ClientID,
@@ -60,7 +82,7 @@ func (u *GetToken) Do(ctx context.Context, in usecases.GetTokenIn) error {
}
if !out.AlreadyHasValidIDToken {
u.Logger.Printf("You got a valid token until %s", out.IDTokenExpiry)
cache := credentialplugin.TokenCache{
cache := tokencache.TokenCache{
IDToken: out.IDToken,
RefreshToken: out.RefreshToken,
}

View File

@@ -6,11 +6,14 @@ import (
"time"
"github.com/golang/mock/gomock"
"github.com/int128/kubelogin/pkg/adaptors/mock_adaptors"
"github.com/int128/kubelogin/pkg/models/credentialplugin"
"github.com/int128/kubelogin/pkg/models/kubeconfig"
"github.com/int128/kubelogin/pkg/usecases"
"github.com/int128/kubelogin/pkg/usecases/mock_usecases"
"github.com/int128/kubelogin/pkg/adaptors/credentialplugin"
"github.com/int128/kubelogin/pkg/adaptors/credentialplugin/mock_credentialplugin"
"github.com/int128/kubelogin/pkg/adaptors/kubeconfig"
"github.com/int128/kubelogin/pkg/adaptors/logger/mock_logger"
"github.com/int128/kubelogin/pkg/adaptors/tokencache"
"github.com/int128/kubelogin/pkg/adaptors/tokencache/mock_tokencache"
"github.com/int128/kubelogin/pkg/usecases/auth"
"github.com/int128/kubelogin/pkg/usecases/auth/mock_auth"
"golang.org/x/xerrors"
)
@@ -22,7 +25,7 @@ func TestGetToken_Do(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.TODO()
in := usecases.GetTokenIn{
in := Input{
IssuerURL: "https://accounts.google.com",
ClientID: "YOUR_CLIENT_ID",
ClientSecret: "YOUR_CLIENT_SECRET",
@@ -34,9 +37,9 @@ func TestGetToken_Do(t *testing.T) {
CACertFilename: "/path/to/cert",
SkipTLSVerify: true,
}
mockAuthentication := mock_usecases.NewMockAuthentication(ctrl)
mockAuthentication := mock_auth.NewMockInterface(ctrl)
mockAuthentication.EXPECT().
Do(ctx, usecases.AuthenticationIn{
Do(ctx, auth.Input{
OIDCConfig: kubeconfig.OIDCConfig{
IDPIssuerURL: "https://accounts.google.com",
ClientID: "YOUR_CLIENT_ID",
@@ -49,30 +52,30 @@ func TestGetToken_Do(t *testing.T) {
CACertFilename: "/path/to/cert",
SkipTLSVerify: true,
}).
Return(&usecases.AuthenticationOut{
Return(&auth.Output{
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenExpiry: futureTime,
IDTokenClaims: dummyTokenClaims,
}, nil)
tokenCacheRepository := mock_adaptors.NewMockTokenCacheRepository(ctrl)
tokenCacheRepository := mock_tokencache.NewMockInterface(ctrl)
tokenCacheRepository.EXPECT().
FindByKey("/path/to/token-cache", credentialplugin.TokenCacheKey{
FindByKey("/path/to/token-cache", tokencache.Key{
IssuerURL: "https://accounts.google.com",
ClientID: "YOUR_CLIENT_ID",
}).
Return(nil, xerrors.New("file not found"))
tokenCacheRepository.EXPECT().
Save("/path/to/token-cache",
credentialplugin.TokenCacheKey{
tokencache.Key{
IssuerURL: "https://accounts.google.com",
ClientID: "YOUR_CLIENT_ID",
},
credentialplugin.TokenCache{
tokencache.TokenCache{
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
})
credentialPluginInteraction := mock_adaptors.NewMockCredentialPluginInteraction(ctrl)
credentialPluginInteraction := mock_credentialplugin.NewMockInterface(ctrl)
credentialPluginInteraction.EXPECT().
Write(credentialplugin.Output{
Token: "YOUR_ID_TOKEN",
@@ -82,7 +85,7 @@ func TestGetToken_Do(t *testing.T) {
Authentication: mockAuthentication,
TokenCacheRepository: tokenCacheRepository,
Interaction: credentialPluginInteraction,
Logger: mock_adaptors.NewLogger(t),
Logger: mock_logger.New(t),
}
if err := u.Do(ctx, in); err != nil {
t.Errorf("Do returned error: %+v", err)
@@ -93,15 +96,15 @@ func TestGetToken_Do(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.TODO()
in := usecases.GetTokenIn{
in := Input{
IssuerURL: "https://accounts.google.com",
ClientID: "YOUR_CLIENT_ID",
ClientSecret: "YOUR_CLIENT_SECRET",
TokenCacheDir: "/path/to/token-cache",
}
mockAuthentication := mock_usecases.NewMockAuthentication(ctrl)
mockAuthentication := mock_auth.NewMockInterface(ctrl)
mockAuthentication.EXPECT().
Do(ctx, usecases.AuthenticationIn{
Do(ctx, auth.Input{
OIDCConfig: kubeconfig.OIDCConfig{
IDPIssuerURL: "https://accounts.google.com",
ClientID: "YOUR_CLIENT_ID",
@@ -109,22 +112,22 @@ func TestGetToken_Do(t *testing.T) {
IDToken: "VALID_ID_TOKEN",
},
}).
Return(&usecases.AuthenticationOut{
Return(&auth.Output{
AlreadyHasValidIDToken: true,
IDToken: "VALID_ID_TOKEN",
IDTokenExpiry: futureTime,
IDTokenClaims: dummyTokenClaims,
}, nil)
tokenCacheRepository := mock_adaptors.NewMockTokenCacheRepository(ctrl)
tokenCacheRepository := mock_tokencache.NewMockInterface(ctrl)
tokenCacheRepository.EXPECT().
FindByKey("/path/to/token-cache", credentialplugin.TokenCacheKey{
FindByKey("/path/to/token-cache", tokencache.Key{
IssuerURL: "https://accounts.google.com",
ClientID: "YOUR_CLIENT_ID",
}).
Return(&credentialplugin.TokenCache{
Return(&tokencache.TokenCache{
IDToken: "VALID_ID_TOKEN",
}, nil)
credentialPluginInteraction := mock_adaptors.NewMockCredentialPluginInteraction(ctrl)
credentialPluginInteraction := mock_credentialplugin.NewMockInterface(ctrl)
credentialPluginInteraction.EXPECT().
Write(credentialplugin.Output{
Token: "VALID_ID_TOKEN",
@@ -134,7 +137,7 @@ func TestGetToken_Do(t *testing.T) {
Authentication: mockAuthentication,
TokenCacheRepository: tokenCacheRepository,
Interaction: credentialPluginInteraction,
Logger: mock_adaptors.NewLogger(t),
Logger: mock_logger.New(t),
}
if err := u.Do(ctx, in); err != nil {
t.Errorf("Do returned error: %+v", err)
@@ -145,15 +148,15 @@ func TestGetToken_Do(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.TODO()
in := usecases.GetTokenIn{
in := Input{
IssuerURL: "https://accounts.google.com",
ClientID: "YOUR_CLIENT_ID",
ClientSecret: "YOUR_CLIENT_SECRET",
TokenCacheDir: "/path/to/token-cache",
}
mockAuthentication := mock_usecases.NewMockAuthentication(ctrl)
mockAuthentication := mock_auth.NewMockInterface(ctrl)
mockAuthentication.EXPECT().
Do(ctx, usecases.AuthenticationIn{
Do(ctx, auth.Input{
OIDCConfig: kubeconfig.OIDCConfig{
IDPIssuerURL: "https://accounts.google.com",
ClientID: "YOUR_CLIENT_ID",
@@ -161,9 +164,9 @@ func TestGetToken_Do(t *testing.T) {
},
}).
Return(nil, xerrors.New("authentication error"))
tokenCacheRepository := mock_adaptors.NewMockTokenCacheRepository(ctrl)
tokenCacheRepository := mock_tokencache.NewMockInterface(ctrl)
tokenCacheRepository.EXPECT().
FindByKey("/path/to/token-cache", credentialplugin.TokenCacheKey{
FindByKey("/path/to/token-cache", tokencache.Key{
IssuerURL: "https://accounts.google.com",
ClientID: "YOUR_CLIENT_ID",
}).
@@ -171,8 +174,8 @@ func TestGetToken_Do(t *testing.T) {
u := GetToken{
Authentication: mockAuthentication,
TokenCacheRepository: tokenCacheRepository,
Interaction: mock_adaptors.NewMockCredentialPluginInteraction(ctrl),
Logger: mock_adaptors.NewLogger(t),
Interaction: mock_credentialplugin.NewMockInterface(ctrl),
Logger: mock_logger.New(t),
}
if err := u.Do(ctx, in); err == nil {
t.Errorf("err wants non-nil but nil")

View File

@@ -0,0 +1,47 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/int128/kubelogin/pkg/usecases/credentialplugin (interfaces: Interface)
// Package mock_credentialplugin is a generated GoMock package.
package mock_credentialplugin
import (
context "context"
gomock "github.com/golang/mock/gomock"
credentialplugin "github.com/int128/kubelogin/pkg/usecases/credentialplugin"
reflect "reflect"
)
// MockInterface is a mock of Interface interface
type MockInterface struct {
ctrl *gomock.Controller
recorder *MockInterfaceMockRecorder
}
// MockInterfaceMockRecorder is the mock recorder for MockInterface
type MockInterfaceMockRecorder struct {
mock *MockInterface
}
// NewMockInterface creates a new mock instance
func NewMockInterface(ctrl *gomock.Controller) *MockInterface {
mock := &MockInterface{ctrl: ctrl}
mock.recorder = &MockInterfaceMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder {
return m.recorder
}
// Do mocks base method
func (m *MockInterface) Do(arg0 context.Context, arg1 credentialplugin.Input) error {
ret := m.ctrl.Call(m, "Do", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// Do indicates an expected call of Do
func (mr *MockInterfaceMockRecorder) Do(arg0, arg1 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Do", reflect.TypeOf((*MockInterface)(nil).Do), arg0, arg1)
}

View File

@@ -1,76 +0,0 @@
package usecases
import (
"context"
"time"
"github.com/int128/kubelogin/pkg/models/kubeconfig"
)
//go:generate mockgen -destination mock_usecases/mock_usecases.go github.com/int128/kubelogin/pkg/usecases Login,GetToken,Authentication
type Login interface {
Do(ctx context.Context, in LoginIn) error
}
// LoginIn represents an input DTO of the Login use-case.
type LoginIn struct {
KubeconfigFilename string // Default to the environment variable or global config as kubectl
KubeconfigContext kubeconfig.ContextName // Default to the current context but ignored if KubeconfigUser is set
KubeconfigUser kubeconfig.UserName // Default to the user of the context
SkipOpenBrowser bool
ListenPort []int
Username string // If set, perform the resource owner password credentials grant
Password string // If empty, read a password using Env.ReadPassword()
CACertFilename string // If set, use the CA cert
SkipTLSVerify bool
}
// LoginShowLocalServerURL provides an interface to notify the URL of local server.
// It is needed for the end-to-end tests.
type LoginShowLocalServerURL interface {
ShowLocalServerURL(url string)
}
type GetToken interface {
Do(ctx context.Context, in GetTokenIn) error
}
// GetTokenIn represents an input DTO of the GetToken use-case.
type GetTokenIn struct {
IssuerURL string
ClientID string
ClientSecret string
ExtraScopes []string // optional
SkipOpenBrowser bool
ListenPort []int
Username string // If set, perform the resource owner password credentials grant
Password string // If empty, read a password using Env.ReadPassword()
CACertFilename string // If set, use the CA cert
SkipTLSVerify bool
TokenCacheDir string
}
type Authentication interface {
Do(ctx context.Context, in AuthenticationIn) (*AuthenticationOut, error)
}
// AuthenticationIn represents an input DTO of the Authentication use-case.
type AuthenticationIn struct {
OIDCConfig kubeconfig.OIDCConfig
SkipOpenBrowser bool
ListenPort []int
Username string // If set, perform the resource owner password credentials grant
Password string // If empty, read a password using Env.ReadPassword()
CACertFilename string // If set, use the CA cert
SkipTLSVerify bool
}
// AuthenticationIn represents an output DTO of the Authentication use-case.
type AuthenticationOut struct {
AlreadyHasValidIDToken bool
IDTokenExpiry time.Time
IDTokenClaims map[string]string
IDToken string
RefreshToken string
}

View File

@@ -1,118 +0,0 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/int128/kubelogin/pkg/usecases (interfaces: Login,GetToken,Authentication)
// Package mock_usecases is a generated GoMock package.
package mock_usecases
import (
context "context"
gomock "github.com/golang/mock/gomock"
usecases "github.com/int128/kubelogin/pkg/usecases"
reflect "reflect"
)
// MockLogin is a mock of Login interface
type MockLogin struct {
ctrl *gomock.Controller
recorder *MockLoginMockRecorder
}
// MockLoginMockRecorder is the mock recorder for MockLogin
type MockLoginMockRecorder struct {
mock *MockLogin
}
// NewMockLogin creates a new mock instance
func NewMockLogin(ctrl *gomock.Controller) *MockLogin {
mock := &MockLogin{ctrl: ctrl}
mock.recorder = &MockLoginMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockLogin) EXPECT() *MockLoginMockRecorder {
return m.recorder
}
// Do mocks base method
func (m *MockLogin) Do(arg0 context.Context, arg1 usecases.LoginIn) error {
ret := m.ctrl.Call(m, "Do", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// Do indicates an expected call of Do
func (mr *MockLoginMockRecorder) Do(arg0, arg1 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Do", reflect.TypeOf((*MockLogin)(nil).Do), arg0, arg1)
}
// MockGetToken is a mock of GetToken interface
type MockGetToken struct {
ctrl *gomock.Controller
recorder *MockGetTokenMockRecorder
}
// MockGetTokenMockRecorder is the mock recorder for MockGetToken
type MockGetTokenMockRecorder struct {
mock *MockGetToken
}
// NewMockGetToken creates a new mock instance
func NewMockGetToken(ctrl *gomock.Controller) *MockGetToken {
mock := &MockGetToken{ctrl: ctrl}
mock.recorder = &MockGetTokenMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockGetToken) EXPECT() *MockGetTokenMockRecorder {
return m.recorder
}
// Do mocks base method
func (m *MockGetToken) Do(arg0 context.Context, arg1 usecases.GetTokenIn) error {
ret := m.ctrl.Call(m, "Do", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// Do indicates an expected call of Do
func (mr *MockGetTokenMockRecorder) Do(arg0, arg1 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Do", reflect.TypeOf((*MockGetToken)(nil).Do), arg0, arg1)
}
// MockAuthentication is a mock of Authentication interface
type MockAuthentication struct {
ctrl *gomock.Controller
recorder *MockAuthenticationMockRecorder
}
// MockAuthenticationMockRecorder is the mock recorder for MockAuthentication
type MockAuthenticationMockRecorder struct {
mock *MockAuthentication
}
// NewMockAuthentication creates a new mock instance
func NewMockAuthentication(ctrl *gomock.Controller) *MockAuthentication {
mock := &MockAuthentication{ctrl: ctrl}
mock.recorder = &MockAuthenticationMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockAuthentication) EXPECT() *MockAuthenticationMockRecorder {
return m.recorder
}
// Do mocks base method
func (m *MockAuthentication) Do(arg0 context.Context, arg1 usecases.AuthenticationIn) (*usecases.AuthenticationOut, error) {
ret := m.ctrl.Call(m, "Do", arg0, arg1)
ret0, _ := ret[0].(*usecases.AuthenticationOut)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Do indicates an expected call of Do
func (mr *MockAuthenticationMockRecorder) Do(arg0, arg1 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Do", reflect.TypeOf((*MockAuthentication)(nil).Do), arg0, arg1)
}

View File

@@ -0,0 +1,47 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/int128/kubelogin/pkg/usecases/standalone (interfaces: Interface)
// Package mock_standalone is a generated GoMock package.
package mock_standalone
import (
context "context"
gomock "github.com/golang/mock/gomock"
standalone "github.com/int128/kubelogin/pkg/usecases/standalone"
reflect "reflect"
)
// MockInterface is a mock of Interface interface
type MockInterface struct {
ctrl *gomock.Controller
recorder *MockInterfaceMockRecorder
}
// MockInterfaceMockRecorder is the mock recorder for MockInterface
type MockInterfaceMockRecorder struct {
mock *MockInterface
}
// NewMockInterface creates a new mock instance
func NewMockInterface(ctrl *gomock.Controller) *MockInterface {
mock := &MockInterface{ctrl: ctrl}
mock.recorder = &MockInterfaceMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder {
return m.recorder
}
// Do mocks base method
func (m *MockInterface) Do(arg0 context.Context, arg1 standalone.Input) error {
ret := m.ctrl.Call(m, "Do", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// Do indicates an expected call of Do
func (mr *MockInterfaceMockRecorder) Do(arg0, arg1 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Do", reflect.TypeOf((*MockInterface)(nil).Do), arg0, arg1)
}

View File

@@ -1,20 +1,40 @@
package login
package standalone
import (
"context"
"github.com/google/wire"
"github.com/int128/kubelogin/pkg/adaptors"
"github.com/int128/kubelogin/pkg/usecases"
"github.com/int128/kubelogin/pkg/adaptors/kubeconfig"
"github.com/int128/kubelogin/pkg/adaptors/logger"
"github.com/int128/kubelogin/pkg/usecases/auth"
"golang.org/x/xerrors"
)
// Set provides the use-cases of logging in.
//go:generate mockgen -destination mock_standalone/mock_standalone.go github.com/int128/kubelogin/pkg/usecases/standalone Interface
// Set provides the use-case.
var Set = wire.NewSet(
wire.Struct(new(Login), "*"),
wire.Bind(new(usecases.Login), new(*Login)),
wire.Struct(new(Standalone), "*"),
wire.Bind(new(Interface), new(*Standalone)),
)
type Interface interface {
Do(ctx context.Context, in Input) error
}
// Input represents an input DTO of the use-case.
type Input struct {
KubeconfigFilename string // Default to the environment variable or global config as kubectl
KubeconfigContext kubeconfig.ContextName // Default to the current context but ignored if KubeconfigUser is set
KubeconfigUser kubeconfig.UserName // Default to the user of the context
SkipOpenBrowser bool
ListenPort []int
Username string // If set, perform the resource owner password credentials grant
Password string // If empty, read a password using Env.ReadPassword()
CACertFilename string // If set, use the CA cert
SkipTLSVerify bool
}
const oidcConfigErrorMessage = `No OIDC configuration found. Did you setup kubectl for OIDC authentication?
kubectl config set-credentials CONTEXT_NAME \
--auth-provider oidc \
@@ -22,19 +42,19 @@ const oidcConfigErrorMessage = `No OIDC configuration found. Did you setup kubec
--auth-provider-arg client-id=YOUR_CLIENT_ID \
--auth-provider-arg client-secret=YOUR_CLIENT_SECRET`
// Login provides the use case of explicit login.
// Standalone provides the use case of explicit login.
//
// If the current auth provider is not oidc, show the error.
// If the kubeconfig has a valid token, do nothing.
// Otherwise, update the kubeconfig.
//
type Login struct {
Authentication usecases.Authentication
Kubeconfig adaptors.Kubeconfig
Logger adaptors.Logger
type Standalone struct {
Authentication auth.Interface
Kubeconfig kubeconfig.Interface
Logger logger.Interface
}
func (u *Login) Do(ctx context.Context, in usecases.LoginIn) error {
func (u *Standalone) Do(ctx context.Context, in Input) error {
u.Logger.V(1).Infof("WARNING: log may contain your secrets such as token or password")
authProvider, err := u.Kubeconfig.GetCurrentAuthProvider(in.KubeconfigFilename, in.KubeconfigContext, in.KubeconfigUser)
@@ -45,7 +65,7 @@ func (u *Login) Do(ctx context.Context, in usecases.LoginIn) error {
u.Logger.V(1).Infof("using the authentication provider of the user %s", authProvider.UserName)
u.Logger.V(1).Infof("a token will be written to %s", authProvider.LocationOfOrigin)
out, err := u.Authentication.Do(ctx, usecases.AuthenticationIn{
out, err := u.Authentication.Do(ctx, auth.Input{
OIDCConfig: authProvider.OIDCConfig,
SkipOpenBrowser: in.SkipOpenBrowser,
ListenPort: in.ListenPort,

View File

@@ -1,4 +1,4 @@
package login
package standalone
import (
"context"
@@ -6,14 +6,15 @@ import (
"time"
"github.com/golang/mock/gomock"
"github.com/int128/kubelogin/pkg/adaptors/mock_adaptors"
"github.com/int128/kubelogin/pkg/models/kubeconfig"
"github.com/int128/kubelogin/pkg/usecases"
"github.com/int128/kubelogin/pkg/usecases/mock_usecases"
"github.com/int128/kubelogin/pkg/adaptors/kubeconfig"
"github.com/int128/kubelogin/pkg/adaptors/kubeconfig/mock_kubeconfig"
"github.com/int128/kubelogin/pkg/adaptors/logger/mock_logger"
"github.com/int128/kubelogin/pkg/usecases/auth"
"github.com/int128/kubelogin/pkg/usecases/auth/mock_auth"
"golang.org/x/xerrors"
)
func TestLogin_Do(t *testing.T) {
func TestStandalone_Do(t *testing.T) {
dummyTokenClaims := map[string]string{"sub": "YOUR_SUBJECT"}
futureTime := time.Now().Add(time.Hour) //TODO: inject time service
@@ -21,7 +22,7 @@ func TestLogin_Do(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.TODO()
in := usecases.LoginIn{
in := Input{
KubeconfigFilename: "/path/to/kubeconfig",
KubeconfigContext: "theContext",
KubeconfigUser: "theUser",
@@ -41,7 +42,7 @@ func TestLogin_Do(t *testing.T) {
ClientSecret: "YOUR_CLIENT_SECRET",
},
}
mockKubeconfig := mock_adaptors.NewMockKubeconfig(ctrl)
mockKubeconfig := mock_kubeconfig.NewMockInterface(ctrl)
mockKubeconfig.EXPECT().
GetCurrentAuthProvider("/path/to/kubeconfig", kubeconfig.ContextName("theContext"), kubeconfig.UserName("theUser")).
Return(currentAuthProvider, nil)
@@ -57,9 +58,9 @@ func TestLogin_Do(t *testing.T) {
RefreshToken: "YOUR_REFRESH_TOKEN",
},
})
mockAuthentication := mock_usecases.NewMockAuthentication(ctrl)
mockAuthentication := mock_auth.NewMockInterface(ctrl)
mockAuthentication.EXPECT().
Do(ctx, usecases.AuthenticationIn{
Do(ctx, auth.Input{
OIDCConfig: currentAuthProvider.OIDCConfig,
ListenPort: []int{10000},
SkipOpenBrowser: true,
@@ -68,16 +69,16 @@ func TestLogin_Do(t *testing.T) {
CACertFilename: "/path/to/cert",
SkipTLSVerify: true,
}).
Return(&usecases.AuthenticationOut{
Return(&auth.Output{
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenExpiry: futureTime,
IDTokenClaims: dummyTokenClaims,
}, nil)
u := Login{
u := Standalone{
Authentication: mockAuthentication,
Kubeconfig: mockKubeconfig,
Logger: mock_adaptors.NewLogger(t),
Logger: mock_logger.New(t),
}
if err := u.Do(ctx, in); err != nil {
t.Errorf("Do returned error: %+v", err)
@@ -88,7 +89,7 @@ func TestLogin_Do(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.TODO()
in := usecases.LoginIn{}
in := Input{}
currentAuthProvider := &kubeconfig.AuthProvider{
LocationOfOrigin: "/path/to/kubeconfig",
UserName: "theUser",
@@ -98,23 +99,23 @@ func TestLogin_Do(t *testing.T) {
IDToken: "VALID_ID_TOKEN",
},
}
mockKubeconfig := mock_adaptors.NewMockKubeconfig(ctrl)
mockKubeconfig := mock_kubeconfig.NewMockInterface(ctrl)
mockKubeconfig.EXPECT().
GetCurrentAuthProvider("", kubeconfig.ContextName(""), kubeconfig.UserName("")).
Return(currentAuthProvider, nil)
mockAuthentication := mock_usecases.NewMockAuthentication(ctrl)
mockAuthentication := mock_auth.NewMockInterface(ctrl)
mockAuthentication.EXPECT().
Do(ctx, usecases.AuthenticationIn{OIDCConfig: currentAuthProvider.OIDCConfig}).
Return(&usecases.AuthenticationOut{
Do(ctx, auth.Input{OIDCConfig: currentAuthProvider.OIDCConfig}).
Return(&auth.Output{
AlreadyHasValidIDToken: true,
IDToken: "VALID_ID_TOKEN",
IDTokenExpiry: futureTime,
IDTokenClaims: dummyTokenClaims,
}, nil)
u := Login{
u := Standalone{
Authentication: mockAuthentication,
Kubeconfig: mockKubeconfig,
Logger: mock_adaptors.NewLogger(t),
Logger: mock_logger.New(t),
}
if err := u.Do(ctx, in); err != nil {
t.Errorf("Do returned error: %+v", err)
@@ -125,16 +126,16 @@ func TestLogin_Do(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.TODO()
in := usecases.LoginIn{}
mockKubeconfig := mock_adaptors.NewMockKubeconfig(ctrl)
in := Input{}
mockKubeconfig := mock_kubeconfig.NewMockInterface(ctrl)
mockKubeconfig.EXPECT().
GetCurrentAuthProvider("", kubeconfig.ContextName(""), kubeconfig.UserName("")).
Return(nil, xerrors.New("no oidc config"))
mockAuthentication := mock_usecases.NewMockAuthentication(ctrl)
u := Login{
mockAuthentication := mock_auth.NewMockInterface(ctrl)
u := Standalone{
Authentication: mockAuthentication,
Kubeconfig: mockKubeconfig,
Logger: mock_adaptors.NewLogger(t),
Logger: mock_logger.New(t),
}
if err := u.Do(ctx, in); err == nil {
t.Errorf("err wants non-nil but nil")
@@ -145,7 +146,7 @@ func TestLogin_Do(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.TODO()
in := usecases.LoginIn{}
in := Input{}
currentAuthProvider := &kubeconfig.AuthProvider{
LocationOfOrigin: "/path/to/kubeconfig",
UserName: "google",
@@ -155,18 +156,18 @@ func TestLogin_Do(t *testing.T) {
ClientSecret: "YOUR_CLIENT_SECRET",
},
}
mockKubeconfig := mock_adaptors.NewMockKubeconfig(ctrl)
mockKubeconfig := mock_kubeconfig.NewMockInterface(ctrl)
mockKubeconfig.EXPECT().
GetCurrentAuthProvider("", kubeconfig.ContextName(""), kubeconfig.UserName("")).
Return(currentAuthProvider, nil)
mockAuthentication := mock_usecases.NewMockAuthentication(ctrl)
mockAuthentication := mock_auth.NewMockInterface(ctrl)
mockAuthentication.EXPECT().
Do(ctx, usecases.AuthenticationIn{OIDCConfig: currentAuthProvider.OIDCConfig}).
Do(ctx, auth.Input{OIDCConfig: currentAuthProvider.OIDCConfig}).
Return(nil, xerrors.New("authentication error"))
u := Login{
u := Standalone{
Authentication: mockAuthentication,
Kubeconfig: mockKubeconfig,
Logger: mock_adaptors.NewLogger(t),
Logger: mock_logger.New(t),
}
if err := u.Do(ctx, in); err == nil {
t.Errorf("err wants non-nil but nil")
@@ -177,7 +178,7 @@ func TestLogin_Do(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := context.TODO()
in := usecases.LoginIn{}
in := Input{}
currentAuthProvider := &kubeconfig.AuthProvider{
LocationOfOrigin: "/path/to/kubeconfig",
UserName: "google",
@@ -187,7 +188,7 @@ func TestLogin_Do(t *testing.T) {
ClientSecret: "YOUR_CLIENT_SECRET",
},
}
mockKubeconfig := mock_adaptors.NewMockKubeconfig(ctrl)
mockKubeconfig := mock_kubeconfig.NewMockInterface(ctrl)
mockKubeconfig.EXPECT().
GetCurrentAuthProvider("", kubeconfig.ContextName(""), kubeconfig.UserName("")).
Return(currentAuthProvider, nil)
@@ -204,19 +205,19 @@ func TestLogin_Do(t *testing.T) {
},
}).
Return(xerrors.New("I/O error"))
mockAuthentication := mock_usecases.NewMockAuthentication(ctrl)
mockAuthentication := mock_auth.NewMockInterface(ctrl)
mockAuthentication.EXPECT().
Do(ctx, usecases.AuthenticationIn{OIDCConfig: currentAuthProvider.OIDCConfig}).
Return(&usecases.AuthenticationOut{
Do(ctx, auth.Input{OIDCConfig: currentAuthProvider.OIDCConfig}).
Return(&auth.Output{
IDToken: "YOUR_ID_TOKEN",
RefreshToken: "YOUR_REFRESH_TOKEN",
IDTokenExpiry: futureTime,
IDTokenClaims: dummyTokenClaims,
}, nil)
u := Login{
u := Standalone{
Authentication: mockAuthentication,
Kubeconfig: mockKubeconfig,
Logger: mock_adaptors.NewLogger(t),
Logger: mock_logger.New(t),
}
if err := u.Do(ctx, in); err == nil {
t.Errorf("err wants non-nil but nil")