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

persist oauth2 authorize request approval in session #411

Merged
merged 6 commits into from
May 14, 2024

Conversation

TimShi
Copy link
Contributor

@TimShi TimShi commented Apr 24, 2024

Description

This PR is for #405

  1. Defined interface for approval storage
  2. Approval storage implementation is not required. If no implementation is provided, then the user will be asked for approval every time.
  3. If an approval store implementation is provided by the application, If the same client with the same redirect uri is requesting the same scope and it was approved previously, then the request will be automatically approved.
  4. In the auth service example, an in-memory implementation is provided.

Application is responsible for providing features that allows users to manage their approvals.

Type of Change

  • Bug Fix
  • New Feature
  • Breaking Change
  • Refactor
  • Documentation
  • Other (please describe)

Checklist

  • I have read the contributing guidelines
  • Existing issues have been referenced (where applicable)
  • I have verified this change is not present in other open pull requests
  • Functionality is documented
  • All code style checks pass
  • New code contribution is covered by automated tests
  • All new and existing tests pass

@TimShi TimShi requested a review from stonedu1011 April 24, 2024 15:09
Copy link

github-actions bot commented Apr 24, 2024

PR Coverage Summary
Changed Statements 100
Covered Statements 84
Test Coverage 84.0%

PR Verification Succeeded: Coverage >= 70%

@TimShi TimShi requested a review from newmodelcoder April 24, 2024 16:01
Comment on lines 180 to 183
if remember {
_ = mw.saveApprovedRequest(ctx, request)
}

Copy link
Contributor

Choose a reason for hiding this comment

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

error is ignored here, maybe add a log

Comment on lines +303 to +308
if v, ok := ctx.Request.PostForm[oauth2.ParameterUserApproval]; ok {
approved, _ = strconv.ParseBool(v[len(v)-1])
}
if !approved {
return
}
Copy link
Contributor

Choose a reason for hiding this comment

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

why we need to check this? I don't see approved is used later in this function

for _, r := range approvedRequests {
if request.ClientId == r.ClientId &&
request.RedirectUri == r.RedirectUri &&
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need to check redirect URL? What's the scenario where we have multiple saved requests with same client ID but different redirect URL?

Approval is usually bond to client. Maybe instead of saving the entire request, we could save a map of [client ID]->[approved scopes]?

In addition, our common experience is that user is only asked about scopes once and such decision is saved somewhere across sessions. So maybe this need to be saved somewhere else instead of session?

…ation can implement the storage mechansim as desired.
@TimShi
Copy link
Contributor Author

TimShi commented Apr 29, 2024

I made the following changes in the latest commit:

  1. Defined interface for approval storage
  2. Approval storage implementation is not required. If no implementation is provided, then the user will be asked for approval every time.

In the auth service example, an in-memory implementation is provided.

Comment on lines 9 to 18
type Approval struct {
ClientId string
RedirectUri string
Scopes utils.StringSet
}

type ApprovalStore interface {
SaveApproval(c context.Context, user security.Account, a *Approval) error
LoadApprovalsByClientId(c context.Context, user security.Account, clientId string) ([]*Approval, error)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I suggest Approval to contain both client ID and user identifier (ID or username or both). And ApprovalStore to have:

SaveApproval(context.Context, *Approval) error
LoadApprovals(context.Context, opts...ApprovalLoadOptions) ([]*Approval, error)

where ApprovalLoadOptions is func(ApprovalLoadOption) or simply func(Approval).

The reason to use generic interface is to avoid future interface changes.

Comment on lines 253 to 255
if mw.approvalStore == nil {
return oauth2.NewInternalError("approval store is not available")
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Since we allow approval store is nil, maybe just return nil)

Comment on lines +257 to +259
if !r.Approved {
return oauth2.NewInternalError("attempting to save unapproved request")
}
Copy link
Contributor

Choose a reason for hiding this comment

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

If user denied the requested scopes, should we store this decision?

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 don't think that's necessary because application will continue to ask for approval in that case, so it doesn't matter if the negative decision is saved or not.

Comment on lines 265 to 268
userAccount, ok := u.Principal().(security.Account)
if !ok {
return oauth2.NewInternalError("can't save approval without user account")
}
Copy link
Contributor

Choose a reason for hiding this comment

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

the "principal" might be just an username or ID, depending on the authentication config which is configured by service.

We could use security.GetUsername()

Comment on lines 344 to 351
if request.ClientId == a.ClientId &&
request.RedirectUri == a.RedirectUri &&
request.Scopes.Equals(a.Scopes) {
return true
}
}
return false
Copy link
Contributor

Choose a reason for hiding this comment

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

The approvals are loaded by user and client ID, we should either check both (if we don't trust approval store) or not check.


type ApprovalStore interface {
SaveApproval(c context.Context, a *Approval) error
LoadUserApprovalsByClientId(c context.Context, opts ...ApprovalLoadOptions) ([]*Approval, error)
Copy link
Contributor

@stonedu1011 stonedu1011 May 14, 2024

Choose a reason for hiding this comment

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

Since we use options, The function should be renamed to LoadUserApprovals or LoadApprovals. I also suggest keep interface, save and load function consistent

@stonedu1011 stonedu1011 self-requested a review May 14, 2024 15:30
@TimShi TimShi merged commit 9d452cb into main May 14, 2024
1 check passed
@TimShi TimShi deleted the fix/persist_oauth2_approval branch May 14, 2024 20:16
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.

2 participants