Skip to content

Commit

Permalink
feat: cli login & logout (#5693)
Browse files Browse the repository at this point in the history
Co-authored-by: lynnleelhl <[email protected]>
  • Loading branch information
lynnleelhl and lynnleelhl authored Oct 31, 2023
1 parent f0ad783 commit a99cb64
Show file tree
Hide file tree
Showing 13 changed files with 120 additions and 121 deletions.
12 changes: 12 additions & 0 deletions docs/user_docs/cli/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,18 @@ KubeBlocks operation commands.
* [kbcli kubeblocks upgrade](kbcli_kubeblocks_upgrade.md) - Upgrade KubeBlocks.


## [login](kbcli_login.md)

Authenticate with the KubeBlocks Cloud



## [logout](kbcli_logout.md)

Log out of the KubeBlocks Cloud



## [migration](kbcli_migration.md)

Data migration between two data sources.
Expand Down
2 changes: 2 additions & 0 deletions docs/user_docs/cli/kbcli.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ kbcli [flags]
* [kbcli fault](kbcli_fault.md) - Inject faults to pod.
* [kbcli infra](kbcli_infra.md) - infra command
* [kbcli kubeblocks](kbcli_kubeblocks.md) - KubeBlocks operation commands.
* [kbcli login](kbcli_login.md) - Authenticate with the KubeBlocks Cloud
* [kbcli logout](kbcli_logout.md) - Log out of the KubeBlocks Cloud
* [kbcli migration](kbcli_migration.md) - Data migration between two data sources.
* [kbcli options](kbcli_options.md) - Print the list of flags inherited by all commands.
* [kbcli playground](kbcli_playground.md) - Bootstrap or destroy a playground KubeBlocks in local host or cloud.
Expand Down
8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ require (
github.com/manifoldco/promptui v0.9.0
github.com/mattn/go-isatty v0.0.19
github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4
github.com/onsi/ginkgo/v2 v2.11.0
github.com/onsi/gomega v1.27.8
github.com/onsi/ginkgo/v2 v2.13.0
github.com/onsi/gomega v1.27.10
github.com/opencontainers/image-spec v1.1.0-rc5
github.com/pashagolub/pgxmock/v2 v2.11.0
github.com/pkg/errors v0.9.1
Expand Down Expand Up @@ -389,11 +389,11 @@ require (
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go4.org/netipx v0.0.0-20230728184502-ec4c8b891b28 // indirect
golang.org/x/mod v0.11.0 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/term v0.13.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.9.3 // indirect
golang.org/x/tools v0.12.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect
google.golang.org/api v0.124.0 // indirect
Expand Down
16 changes: 8 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1294,14 +1294,14 @@ github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+
github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=
github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc=
github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ=
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
Expand Down Expand Up @@ -1799,8 +1799,8 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
Expand Down Expand Up @@ -2137,8 +2137,8 @@ golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM=
golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss=
golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
Expand Down
28 changes: 5 additions & 23 deletions pkg/cli/cmd/auth/authorize/authenticator/pkce_authenticator.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,10 +195,10 @@ func (p *PKCEAuthenticator) GetToken(ctx context.Context, authorization interfac
}

func (p *PKCEAuthenticator) GetUserInfo(ctx context.Context, token string) (*UserInfoResponse, error) {
URL := fmt.Sprintf("%s/userinfo", p.AuthURL)
req, err := utils.NewRequest(ctx, URL, url.Values{
"access_token": []string{token},
})
URL := fmt.Sprintf("https://%s/api/v1/user", utils.OpenAPIHost)
req, err := utils.NewFullRequest(ctx, URL, http.MethodGet, map[string]string{
"Authorization": "Bearer " + token,
}, "")
if err != nil {
return nil, errors.Wrap(err, "error creating request for userinfo")
}
Expand Down Expand Up @@ -264,26 +264,8 @@ func (p *PKCEAuthenticator) RefreshToken(ctx context.Context, refreshToken strin
}

func (p *PKCEAuthenticator) Logout(ctx context.Context, token string, openURLFunc func(URL string)) error {
URL := fmt.Sprintf("%s/oidc/logout", p.AuthURL)
req, err := utils.NewRequest(ctx, URL, url.Values{
"id_token_hint": []string{token},
"client_id": []string{p.ClientID},
})
if err != nil {
return errors.Wrap(err, "error creating request for logout")
}

res, err := p.client.Do(req)
if err != nil {
return errors.Wrap(err, "error performing http request for logout")
}
defer res.Body.Close()

if _, err = checkErrorResponse(res); err != nil {
return err
}

logoutURL := fmt.Sprintf(p.AuthURL + "/oidc/logout?federated")
logoutURL := fmt.Sprintf(p.AuthURL + "/session/end?id_token_hint=" + token)
openURLFunc(logoutURL)

return nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,8 @@ var _ = Describe("PKCE_Authenticator", func() {

Context("test Authorization", func() {
It("test get userInfo", func() {
ExpectWithOffset(1, func() error {
_, err := a.GetUserInfo(context.TODO(), "test_token")
return err
}()).To(BeNil())
_, err := a.GetUserInfo(context.TODO(), "test_token")
Expect(err).Should(HaveOccurred())
})

It("test get token", func() {
Expand Down
34 changes: 10 additions & 24 deletions pkg/cli/cmd/auth/authorize/token_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,31 +57,17 @@ func newTokenProvider(cached CachedTokenProvider, issued IssuedTokenProvider) Pr
}

func (p *TokenProvider) Login(ctx context.Context) (*authenticator.UserInfoResponse, string, error) {
isAccessTokenValid := func(tokenResponse authenticator.TokenResponse) bool { return IsValidToken(tokenResponse.AccessToken) }
tokenResult, err := p.getTokenFromCache(isAccessTokenValid)
tokenResult, err := p.issued.authenticate(ctx)
if err != nil {
return nil, "", errors.Wrap(err, "could not refresh from cache")
}

var userInfo *authenticator.UserInfoResponse
if tokenResult != nil {
userInfo, err = p.cached.getUserInfo()
if err != nil {
return nil, "", errors.Wrap(err, "could not get user info from cache")
}
} else {
tokenResult, err = p.issued.authenticate(ctx)
if err != nil {
return nil, "", errors.Wrap(err, "could not authenticate with cloud")
}
userInfo, err = p.issued.getUserInfo(tokenResult.AccessToken)
if err != nil {
return nil, "", errors.Wrap(err, "could not get user info from cloud")
}
err = p.cached.cacheUserInfo(userInfo)
if err != nil {
return nil, "", errors.Wrap(err, "could not store user info")
}
return nil, "", errors.Wrap(err, "could not authenticate with cloud")
}
userInfo, err := p.issued.getUserInfo(tokenResult.IDToken)
if err != nil {
return nil, "", errors.Wrap(err, "could not get user info from cloud")
}
err = p.cached.cacheUserInfo(userInfo)
if err != nil {
return nil, "", errors.Wrap(err, "could not store user info")
}

err = p.cached.cacheTokens(tokenResult)
Expand Down
26 changes: 20 additions & 6 deletions pkg/cli/cmd/auth/authorize/token_provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,8 @@ var _ = Describe("token provider", func() {
Context("test token provider", func() {
It("test login", func() {
tokenProvider := newTokenProvider(mockCached, mockIssued)

ExpectWithOffset(1, func() error {
userInfo, _, err := tokenProvider.Login(context.Background())
Expect(userInfo.Name).To(Equal("test_name"))
return err
}()).To(BeNil())
_, _, err := tokenProvider.Login(context.Background())
Expect(err).Should(HaveOccurred())
})

It("test logout", func() {
Expand All @@ -146,5 +142,23 @@ var _ = Describe("token provider", func() {
It("test IsValidToken", func() {
Expect(IsValidToken("test_token")).To(BeFalse())
})

It("test getTokenFromCache", func() {
tokenProvider := &TokenProvider{
cached: mockCached,
issued: mockIssued,
}
isAccessTokenValid := func(tokenResponse authenticator.TokenResponse) bool { return IsValidToken(tokenResponse.AccessToken) }
_, err := tokenProvider.getTokenFromCache(isAccessTokenValid)
Expect(err).ShouldNot(HaveOccurred())
})

It("test getRefreshToken", func() {
tokenProvider := &TokenProvider{
cached: mockCached,
issued: mockIssued,
}
Expect(tokenProvider.getRefreshToken("")).Should(BeNil())
})
})
})
23 changes: 9 additions & 14 deletions pkg/cli/cmd/auth/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import (
"fmt"
"io"
"net/http"
"net/url"

"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericiooptions"
Expand All @@ -36,10 +35,6 @@ import (
"github.com/apecloud/kubeblocks/pkg/cli/cmd/organization"
)

const (
DefaultBaseURL = "https://tenent2.jp.auth0.com"
)

type LoginOptions struct {
authorize.Options
Region string
Expand Down Expand Up @@ -267,19 +262,19 @@ func IsLoggedIn() bool {
return false
}

if !authorize.IsValidToken(tokenResult.AccessToken) {
if !authorize.IsValidToken(tokenResult.IDToken) {
return false
}

return checkTokenAvailable(tokenResult.AccessToken, DefaultBaseURL)
return checkTokenAvailable(tokenResult.IDToken)
}

// CheckTokenAvailable Check whether the token is available by getting user info.
func checkTokenAvailable(token, domain string) bool {
URL := fmt.Sprintf("%s/userinfo", domain)
req, err := utils.NewRequest(context.TODO(), URL, url.Values{
"access_token": []string{token},
})
func checkTokenAvailable(token string) bool {
URL := fmt.Sprintf("https://%s/api/v1/user", utils.OpenAPIHost)
req, err := utils.NewFullRequest(context.TODO(), URL, http.MethodGet, map[string]string{
"Authorization": "Bearer " + token,
}, "")
if err != nil {
return false
}
Expand All @@ -303,9 +298,9 @@ func getAuthURL(region string) string {
var authURL string
switch region {
case "jp":
authURL = DefaultBaseURL
authURL = utils.DefaultBaseURL
default:
authURL = DefaultBaseURL
authURL = utils.DefaultBaseURL
}
return authURL
}
39 changes: 3 additions & 36 deletions pkg/cli/cmd/auth/utils/bindata.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions pkg/cli/cmd/auth/utils/const.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
Copyright (C) 2022-2023 ApeCloud Co., Ltd
This file is part of KubeBlocks project
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package utils

const (
DefaultBaseURL = "https://apecloud.authing.cn/oidc"
OpenAPIHost = "cloudapi.apecloud.cn"
)
Loading

0 comments on commit a99cb64

Please sign in to comment.