-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[CLI-2360] Revoking Auth0 refresh token on cloud logout #2216
base: main
Are you sure you want to change the base?
Conversation
pkg/auth/auth_token_handler.go
Outdated
|
||
func (a *AuthTokenHandlerImpl) RevokeRefreshToken(clientFactory CCloudClientFactory, url string, credentials *Credentials) error { | ||
req := &ccloudv1.AuthenticateRequest{IdToken: credentials.AuthToken} | ||
client := clientFactory.JwtHTTPClientFactory(context.Background(), credentials.AuthToken, url) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why can't we just use the default c.Client
in login/command.go?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mentioned this in slack, but that was the reason I was getting Error 401. I was using the AnonHTTPClientFactory
before, which I assume is what you mean by default client. However, the backend handler expects to have a userId in the header, so using the JwtHTTPClient
worked.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, seems like this is due to confluent logout
being an unauthenticated command. Maybe we should switch it to an authenticated command (we should be able to assume that the user is logged in before using it)
@@ -20,7 +20,7 @@ var ( | |||
mockURL = "http://test" | |||
usernameCredentialName = fmt.Sprintf("username-%s-%s", mockEmail, mockURL) | |||
apiKeyCredentialName = fmt.Sprintf("api-key-%s", kafkaAPIKey) | |||
mockContextName = fmt.Sprintf("login-%s-%s", mockEmail, mockURL) | |||
MockContextName = fmt.Sprintf("login-%s-%s", mockEmail, mockURL) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
where else is this var being used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In a logout
unit test.
loginCredentialsManager: loginCredentialsManager, | ||
loginOrganizationManager: loginOrganizationManager, | ||
authTokenHandler: authTokenHandler, | ||
} | ||
if cfg.IsCloudLogin() { | ||
context = "Confluent Cloud" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
context = "Confluent Cloud" | |
context = "Confluent Cloud" | |
AuthenticatedCLICommand: pcmd.NewAuthenticatedCLICommand(cmd, prerunner), |
move it down for readibility
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we're not logged in, we still want to have this struct field set. I originally had the code this way, but it resulted in a panic rather than an error when the user was not logged in.
Have you manually tested, that IsSSO field in get ccloud credentials is always correct? |
yup! It's the same code that used to exist in EDIT: did some refactoring and it turns out we don't even need to call the |
internal/login/command_test.go
Outdated
@@ -811,10 +810,10 @@ func TestLoginWithExistingContext(t *testing.T) { | |||
ctx.KafkaClusterContext.SetActiveKafkaCluster(kafkaCluster.ID) | |||
|
|||
// Executing logout | |||
logoutCmd, _ := newLogoutCmd(cfg, mockNetrcHandler) | |||
logoutCmd, cfg1 := newLogoutCmd(auth, userInterface, s.isCloud, req, mockNetrcHandler, AuthTokenHandler, mockLoginCredentialsManager, LoginOrganizationManager, ctx.Name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logoutCmd, cfg1 := newLogoutCmd(auth, userInterface, s.isCloud, req, mockNetrcHandler, AuthTokenHandler, mockLoginCredentialsManager, LoginOrganizationManager, ctx.Name) | |
logoutCmd, cfg := newLogoutCmd(auth, userInterface, s.isCloud, req, mockNetrcHandler, AuthTokenHandler, mockLoginCredentialsManager, LoginOrganizationManager, ctx.Name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to be changed so that the newLogoutCmd
doesn't generate a new config, but use the same config
as above.
internal/login/command_test.go
Outdated
@@ -903,7 +902,7 @@ func newLoginCmd(auth *ccloudv1mock.Auth, userInterface *ccloudv1mock.UserInterf | |||
if !isCloud { | |||
mdsConfig := mdsv1.NewConfiguration() | |||
mdsClient = mdsv1.NewAPIClient(mdsConfig) | |||
mdsClient.TokensAndAuthenticationApi = &mdsmock.TokensAndAuthenticationApi{ | |||
mdsClient.TokensAndAuthenticationApi = &mdsMock.TokensAndAuthenticationApi{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Package names should always be lowercase.
mdsClient.TokensAndAuthenticationApi = &mdsMock.TokensAndAuthenticationApi{ | |
mdsClient.TokensAndAuthenticationApi = &mdsmock.TokensAndAuthenticationApi{ |
internal/login/command_test.go
Outdated
@@ -937,8 +936,38 @@ func newLoginCmd(auth *ccloudv1mock.Auth, userInterface *ccloudv1mock.UserInterf | |||
return loginCmd, cfg | |||
} | |||
|
|||
func newLogoutCmd(cfg *config.Config, netrcHandler netrc.NetrcHandler) (*cobra.Command, *config.Config) { | |||
logoutCmd := logout.New(cfg, climock.NewPreRunnerMock(nil, nil, nil, nil, cfg), netrcHandler) | |||
func newLogoutCmd(auth *ccloudv1mock.Auth, userInterface *ccloudv1mock.UserInterface, isCloud bool, req *require.Assertions, netrcHandler netrc.NetrcHandler, authTokenHandler pauth.AuthTokenHandler, loginCredentialsManager pauth.LoginCredentialsManager, loginOrganizationManager pauth.LoginOrganizationManager, contextName string) (*cobra.Command, *config.Config) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a crazy number of arguments. Do we really need all of them?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I forgot to remove some of these, as they aren't used anymore.
internal/login/command_test.go
Outdated
mdsConfig := mdsv1.NewConfiguration() | ||
mdsClient = mdsv1.NewAPIClient(mdsConfig) | ||
mdsClient.TokensAndAuthenticationApi = &mdsMock.TokensAndAuthenticationApi{ | ||
GetTokenFunc: func(ctx context.Context) (mdsv1.AuthenticationResponse, *http.Response, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GetTokenFunc: func(ctx context.Context) (mdsv1.AuthenticationResponse, *http.Response, error) { | |
GetTokenFunc: func(_ context.Context) (mdsv1.AuthenticationResponse, *http.Response, error) { |
internal/login/command_test.go
Outdated
return mdsv1.AuthenticationResponse{ | ||
AuthToken: testToken1, | ||
TokenType: "JWT", | ||
ExpiresIn: 100, | ||
}, nil, nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return mdsv1.AuthenticationResponse{ | |
AuthToken: testToken1, | |
TokenType: "JWT", | |
ExpiresIn: 100, | |
}, nil, nil | |
res := mdsv1.AuthenticationResponse{ | |
AuthToken: testToken1, | |
TokenType: "JWT", | |
ExpiresIn: 100, | |
} | |
return res, nil, nil |
pkg/auth/auth_token_handler.go
Outdated
@@ -169,3 +170,11 @@ func login(client *ccloudv1.Client, req *ccloudv1.AuthenticateRequest) (*ccloudv | |||
return client.Auth.Login(req) | |||
} | |||
} | |||
|
|||
func (a *AuthTokenHandlerImpl) RevokeRefreshToken(client *ccloudv1.Client, req *ccloudv1.AuthenticateRequest) (*ccloudv1.AuthenticateReply, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this function need to be inside AuthTokenHandlerImpl
? (I don't see a
being used anywhere here) It looks like you're introducing a dependency for no reason.
internal/login/command_test.go
Outdated
}, | ||
} | ||
} | ||
ccloudClientFactory := &climock.CCloudClientFactory{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like this should only run when isCloud
is true?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like the NewPreRunnerMock
function that takes in this variable runs whether or not isCloud
is true.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds like we should change that too?
internal/login/command_test.go
Outdated
ccloudClientFactory := &climock.CCloudClientFactory{ | ||
AnonHTTPClientFactoryFunc: func(baseURL string) *ccloudv1.Client { | ||
req.Equal("https://confluent.cloud", baseURL) | ||
return &ccloudv1.Client{Params: &ccloudv1.Params{HttpClient: new(http.Client)}, Auth: auth, User: userInterface} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return &ccloudv1.Client{Params: &ccloudv1.Params{HttpClient: new(http.Client)}, Auth: auth, User: userInterface} | |
return &ccloudv1.Client{ | |
Params: &ccloudv1.Params{HttpClient: new(http.Client)}, | |
Auth: auth, | |
User: userInterface, | |
} |
internal/logout/command.go
Outdated
@@ -64,3 +78,18 @@ func (c *Command) logout(cmd *cobra.Command, _ []string) error { | |||
output.Println(errors.LoggedOutMsg) | |||
return nil | |||
} | |||
|
|||
func (c *command) revokeCCloudRefreshToken() (*ccloudv1.AuthenticateReply, error) { | |||
ctx := c.Config.Config.Context() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you know why we need to do this? It looks wrong- can it be shortened?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah we can just pass ctx into the function.
internal/logout/command.go
Outdated
} | ||
|
||
req := &ccloudv1.AuthenticateRequest{IdToken: contextState.AuthToken} | ||
if sso.IsOkta(c.Client.BaseURL) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we get the URL from the configuration instead? This seems hacky
config.SetTempHomeDir() | ||
cfg := config.AuthenticatedConfigMockWithContextName(contextName) | ||
var mdsClient *mdsv1.APIClient | ||
if !isCloud { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like duplicate code. Can we de-duplicate?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a place in the code base where shared unit test helpers already exist? Looks like the ccloudClientFactory
and mdsClient
are used in both the login
and logout
unit tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not yet, but since these tests span two packages, I'd expect it to go somewhere in pkg/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried putting this in existing packages that made sense in pkg/
(auth
and mock
) but both result in circular imports, so I had to create a new directory unfortunately.
internal/logout/command_test.go
Outdated
@@ -21,6 +18,7 @@ import ( | |||
"github.com/confluentinc/cli/v3/pkg/config" | |||
pmock "github.com/confluentinc/cli/v3/pkg/mock" | |||
"github.com/confluentinc/cli/v3/pkg/netrc" | |||
testhelp "github.com/confluentinc/cli/v3/pkg/test-helpers" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
testhelp "github.com/confluentinc/cli/v3/pkg/test-helpers" | |
testhelpers "github.com/confluentinc/cli/v3/pkg/test-helpers" |
pkg/test-helpers/mock.go
Outdated
return mdsv1.AuthenticationResponse{ | ||
AuthToken: token, | ||
TokenType: "JWT", | ||
ExpiresIn: 100, | ||
}, nil, nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return mdsv1.AuthenticationResponse{ | |
AuthToken: token, | |
TokenType: "JWT", | |
ExpiresIn: 100, | |
}, nil, nil | |
res := mdsv1.AuthenticationResponse{ | |
AuthToken: token, | |
TokenType: "JWT", | |
ExpiresIn: 100, | |
} | |
return res, nil, nil |
pkg/test-helpers/mock.go
Outdated
var mdsClient *mdsv1.APIClient | ||
mdsConfig := mdsv1.NewConfiguration() | ||
mdsClient = mdsv1.NewAPIClient(mdsConfig) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var mdsClient *mdsv1.APIClient | |
mdsConfig := mdsv1.NewConfiguration() | |
mdsClient = mdsv1.NewAPIClient(mdsConfig) | |
mdsConfig := mdsv1.NewConfiguration() | |
mdsClient := mdsv1.NewAPIClient(mdsConfig) |
pkg/test-helpers/mock.go
Outdated
@@ -0,0 +1,55 @@ | |||
package test_helpers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
test_helpers
is a really generic name and has the potential to get really big the more things we move into here. Why not put this in pkg/mock
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I get a circular import unfortunately.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
try to fix it another way?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can move it to mock
, just not pkg/mock
.
internal/login/command_test.go
Outdated
} | ||
|
||
mdsClientManager := &climock.MDSClientManager{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Likewise, this looks like it should only be declared inside the if
internal/login/command_test.go
Outdated
logoutCmd := logout.New(cfg, prerunner, netrcHandler, authTokenHandler) | ||
return logoutCmd |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logoutCmd := logout.New(cfg, prerunner, netrcHandler, authTokenHandler) | |
return logoutCmd | |
return logout.New(cfg, prerunner, netrcHandler, authTokenHandler) |
internal/logout/command.go
Outdated
username, err := c.netrcHandler.RemoveNetrcCredentials(c.cfg.IsCloudLogin(), c.Config.Config.Context().GetNetrcMachineName()) | ||
if err == nil { | ||
log.CliLogger.Warnf(errors.RemoveNetrcCredentialsMsg, username, c.netrcHandler.GetFileName()) | ||
} else if !strings.Contains(err.Error(), "login credentials not found") && !strings.Contains(err.Error(), "keyword expected") { | ||
// return err when other than NetrcCredentialsNotFoundErrorMsg or parsing error | ||
return err | ||
} | ||
|
||
if isCCloud := ccloudv2.IsCCloudURL(ctx.Platform.Server, c.cfg.IsTest); isCCloud { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if isCCloud := ccloudv2.IsCCloudURL(ctx.Platform.Server, c.cfg.IsTest); isCCloud { | |
if ccloudv2.IsCCloudURL(ctx.Platform.Server, c.cfg.IsTest) { |
internal/logout/command_test.go
Outdated
contextName := cfg.Context().Name | ||
logoutCmd, cfg := newLogoutCmd(cfg, mockNetrcHandler) | ||
// run login command |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment seems wrong
pkg/cmd/prerunner.go
Outdated
@@ -190,6 +190,10 @@ func (r *PreRun) Authenticated(command *AuthenticatedCLICommand) func(*cobra.Com | |||
setContextErr := r.setAuthenticatedContext(command) | |||
if setContextErr != nil { | |||
if _, ok := setContextErr.(*errors.NotLoggedInError); ok { | |||
if cmd.Use == "logout" { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are there any other errors that could surface and cause a breaking change? We may want to re-evaluate this approach. If not, let's keep as is, but definitely write a comment here explaining why (and open a ticket to remove this hack in v4)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried some other scenarios and it looks like other errors can surface. If the login token expires, logout
throws an expired token
error, and if you try logging out again after that you get the not logged in
error again (which is because although our current context exists, but we're no longer considered logged into it). These errors result from the fact that we're trying to have logout
work exactly the same as before when the user is not logged in, despite it now being an authenticatedCLICommand
.
I think we should probably just wait until v4 to add this since it doesn't look like all the possible breaking changes from errors can be avoided without putting several of these hacky exceptions for the logout
command in the prerunner.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should probably just wait until v4 to add this since it doesn't look like all the possible breaking changes from errors can be avoided without putting several of these hacky exceptions for the logout command in the prerunner.
Labeling as v4!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Going to revert the last two commits since we want the breaking changes to be visible.
Release Notes
Bug Fixes
confluent logout
revokes the refresh token when logging out of Confluent CloudChecklist
What
Running
confluent logout
for cloud users now makes a request toDELETE /api/sessions
.References
https://confluentinc.atlassian.net/browse/CLI-2360
confluentinc/ccloud-sdk-go-v1-public#24
Test & Review
Unit and integration tests still pass
Request returns a reply with code
200 OK
.