Skip to content
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

Css 6646/callback endpoint #1170

Merged
merged 11 commits into from
Mar 6, 2024

Conversation

ale8k
Copy link
Contributor

@ale8k ale8k commented Mar 4, 2024

Description

feat(browser flow for dashboard): implements browser flow (without sessions)

This PR includes a small refactor to the admin device flow, such that it can share the same identity
update logic within the browser flow.

Engineering checklist

Check only items that apply

  • Documentation updated
  • Covered by unit tests
  • Covered by integration tests

Test instructions

Notes for code reviewers

As discussed on call, we integration test only the handler and avoid mocks to see the behaviour is
as expected. This is also true when we come to implements the callback logic and are required to
start the flow from scratch. The current test in auth_handler_test will be updated to cover the
entire flow when implementing /callback. As for the state todo, I need to see how to track the state
between requests and the TODO will be completed in the /callback PR.

6646
…ssions)

This PR includes a small refactor to the admin device flow, such that it can share the same identity
update logic within the browser flow.
internal/auth/oauth2.go Show resolved Hide resolved
internal/jimmhttp/auth_handler.go Outdated Show resolved Hide resolved
// the final callback to the dashboard emulating an endpoint. See setupTestServer
// where we create an additional handler to simulate the final callback to the dashboard
// from JIMM.
func TestBrowserAuth(t *testing.T) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this only tests the "happy path".. we would need additional tests..

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah... But I'm not sure what or how?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exchange fails, update identity fails, etc..

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll try do some more where we check this stuff before merging this PR, just might need to experiment a little and then I'll @ you

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -203,6 +241,34 @@ func (as *AuthenticationService) VerifySessionToken(token string, secretKey stri
return VerifySessionToken(token, secretKey)
}

// UpdateIdentity updates the database with the display name and access token set for the user.
// And, if present, a refresh token.
func (as *AuthenticationService) UpdateIdentity(ctx context.Context, email string, token *oauth2.Token) error {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this unit tested?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also.. it doesn't make sense to pass in email as a separate parameter.. you could just call

	idToken, err := as.ExtractAndVerifyIDToken(ctx, token)
	if err != nil {
		writeError(ctx, w, http.StatusBadRequest, err, "failed to extract and verify id token")
		return
	}

	email, err := email(idToken)

here to get the email from the token

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It isn't tested individually it's part of the TestDevice, I need to come back and mock all of this out afterwards. That's the plan anyways, so I guess view the tests here as temporary, I just written them to help develop. And it's separate as I think the extracting email from a field that can error out is better done outside of the function? Happy to call .Email() within updateidentity but I feel this is clearer / more obvious?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also in next pr we actually use the email in another function within the handler, so i'll leave this as is

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why??

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We pass it over to the session creation

	// If the session is empty, it'll just be an empty session, we only check
	// errors for bad decoding etc.
	session, err := oah.SessionStore.Get(r, "jimm-browser-session")
	if err != nil {
		writeError(ctx, w, http.StatusBadRequest, err, "failed to get session")
	}

	session.Values["jimm-session"] = email

internal/dbmodel/sql/postgres/1_6.sql Show resolved Hide resolved
internal/jimmhttp/auth_handler.go Show resolved Hide resolved
internal/jimmhttp/auth_handler.go Show resolved Hide resolved
AuthCodeURL() string
Exchange(ctx context.Context, code string) (*oauth2.Token, error)
ExtractAndVerifyIDToken(ctx context.Context, oauth2Token *oauth2.Token) (*oidc.IDToken, error)
Email(idToken *oidc.IDToken) (string, error)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Email does not need to be a method of the authenticator.. it can be an independent function because it just extracts an email from the id token that's passed in

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Answered in the other comment why I did this

@@ -71,6 +87,7 @@ func NewAuthenticationService(ctx context.Context, params AuthenticationServiceP
RedirectURL: params.RedirectURL,
},
sessionTokenExpiry: params.SessionTokenExpiry,
db: params.Store,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some validation of input parameters should be done here, no? like.. store should not be nil, sessionTokenExpiry should not be 0.. i'm sure there's other validation we could do

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like validation should be done at the service.go level, but happy to add more in?

internal/jimmhttp/auth_handler.go Show resolved Hide resolved
u := &dbmodel.Identity{
Name: email,
}
// TODO(babakks): If user does not exist, we will create one with an empty
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we create a ticket for this and link it here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think one might exist... i am a bit confused by it though. trhis was more babaks suggestion

internal/auth/oauth2.go Show resolved Hide resolved
internal/auth/oauth2.go Show resolved Hide resolved
internal/jimm/user_test.go Show resolved Hide resolved
return nil, errors.E("nil authenticator")
}
if dashboardFinalRedirectURL == "" {
return nil, errors.E("final redirect url not specified")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is good enough, but I think we should give it another thought. All the tests are created with "no dashboard required for this test", which could have been avoided if this was not required here as we return an error.

Moreover, checking on empty strings shouldn't be enough, we need to actually verify the url is parseable, valid, and reachable. If not, return an error, too

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is because the auth handler has validation, which I agree with Ales it was necessary, just our other handlers don't have the same validation... Because we embed into jimm a lot we end up with this polluted mess of creating stuff for the sake of creating it. And ah yeah definitely, I'll add a url parse to next PR totally agree.

@@ -56,6 +56,7 @@ func TestDefaultService(t *testing.T) {
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
SessionTokenExpiry: time.Duration(time.Hour),
},
DashboardFinalRedirectURL: "<no dashboard needed for this test>",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what I was referring to

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I know this one...

@@ -406,6 +413,7 @@ func TestThirdPartyCaveatDischarge(t *testing.T) {
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
SessionTokenExpiry: time.Duration(time.Hour),
},
DashboardFinalRedirectURL: "<no dashboard needed for this test>",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why force it if it is never used in the tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other comment explains

@ale8k ale8k merged commit 122abfb into canonical:feature-oidc Mar 6, 2024
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants