Skip to content

feat: add oauth2 token exchange #11778

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

Merged
merged 35 commits into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
363005f
Extract auth code helper
code-asher Jan 20, 2024
7000a38
Add static OAuth2 authorize page
code-asher Jan 20, 2024
dc7a246
Add URL query validator
code-asher Jan 20, 2024
48fe155
Allow multiple required query params
code-asher Jan 20, 2024
a4a1dd5
Check that required query params are non-zero
code-asher Jan 23, 2024
586d47d
Refactor test apps
code-asher Jan 20, 2024
d8d2674
Add OAuth2 app filtering by user
code-asher Jan 20, 2024
b23f114
Allow fetching app with query param and form value
code-asher Jan 20, 2024
e6a97bc
Add OAuth2 auth, token, and revoke routes
code-asher Jan 20, 2024
ba95d1b
make gen
code-asher Jan 20, 2024
cbce34a
Delete some oidc helper comments
code-asher Feb 6, 2024
89b281d
s/Required/RequiredNotEmpty
code-asher Feb 6, 2024
6e3940d
No URL for me
code-asher Feb 6, 2024
7b132e4
Move redirect check into query parser
code-asher Feb 7, 2024
035092a
Return err on invalid params
code-asher Feb 7, 2024
3edbc35
Use correct godoc style
code-asher Feb 7, 2024
761c57d
Use userpassword.Hash
code-asher Feb 7, 2024
4769af9
Add comments to code timeout
code-asher Feb 7, 2024
30679d7
Comment on blank origin
code-asher Feb 7, 2024
c06218e
Pass the whole app to db2sdk
code-asher Feb 7, 2024
24e643c
Fix a racy context
code-asher Feb 9, 2024
de037d3
Extract state from authURL
code-asher Feb 9, 2024
19fa030
Implement refresh grant
code-asher Feb 10, 2024
10ab1c9
Fix verbiage in app PUT
code-asher Feb 10, 2024
22ff6c8
Remove extra period
code-asher Feb 10, 2024
cae4e61
Fix test race
code-asher Feb 10, 2024
d854f4d
Apparently browsers do not always set origin
code-asher Feb 13, 2024
9aba07f
Mention redirect URL must be a subset
code-asher Feb 16, 2024
4b975dc
Remove redundant comment
code-asher Feb 16, 2024
5b6a096
Clarify revoke operates on an authorized user
code-asher Feb 16, 2024
f583398
Move cURL comment
code-asher Feb 16, 2024
0161b10
Add error when referer is blank
code-asher Feb 16, 2024
c86e65a
Comment app secret struct
code-asher Feb 16, 2024
2dba243
Use var block
code-asher Feb 16, 2024
cedf7f2
Add error for unhandled grant types
code-asher Feb 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Return err on invalid params
That way the caller can just check err instead of the length of errors.
  • Loading branch information
code-asher committed Feb 10, 2024
commit 035092a31934fe7b86d29d84efb8a7dd0e27d2ec
20 changes: 11 additions & 9 deletions enterprise/coderd/identityprovider/authorize.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,15 @@ type authorizeParams struct {
state string
}

func extractAuthorizeParams(r *http.Request, callbackURL string) (authorizeParams, []codersdk.ValidationError, error) {
func extractAuthorizeParams(r *http.Request, callbackURL *url.URL) (authorizeParams, []codersdk.ValidationError, error) {
p := httpapi.NewQueryParamParser()
vals := r.URL.Query()

p.RequiredNotEmpty("state", "response_type", "client_id")

cb, err := url.Parse(callbackURL)
if err != nil {
return authorizeParams{}, nil, err
}
params := authorizeParams{
clientID: p.String(vals, "", "client_id"),
redirectURL: p.RedirectURL(vals, cb, "redirect_uri"),
redirectURL: p.RedirectURL(vals, callbackURL, "redirect_uri"),
responseType: httpapi.ParseCustom(p, vals, "", "response_type", httpapi.ParseEnum[codersdk.OAuth2ProviderResponseType]),
scope: p.Strings(vals, []string{}, "scope"),
state: p.String(vals, "", "state"),
Expand All @@ -48,7 +44,10 @@ func extractAuthorizeParams(r *http.Request, callbackURL string) (authorizeParam
_ = p.String(vals, "", "redirected")

p.ErrorExcessParams(vals)
return params, p.Errors, nil
if len(p.Errors) > 0 {
return authorizeParams{}, p.Errors, xerrors.Errorf("invalid query params: %w", p.Errors)
}
return params, nil, nil
}

/**
Expand All @@ -63,17 +62,20 @@ func Authorize(db database.Store, accessURL *url.URL) http.HandlerFunc {
apiKey := httpmw.APIKey(r)
app := httpmw.OAuth2ProviderApp(r)

params, validationErrs, err := extractAuthorizeParams(r, app.CallbackURL)
callbackURL, err := url.Parse(app.CallbackURL)
if err != nil {
httpapi.Write(r.Context(), rw, http.StatusInternalServerError, codersdk.Response{
Message: "Failed to validate query parameters.",
Detail: err.Error(),
})
return
}
if len(validationErrs) > 0 {

params, validationErrs, err := extractAuthorizeParams(r, callbackURL)
if err != nil {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Invalid query params.",
Detail: err.Error(),
Validations: validationErrs,
})
return
Expand Down
20 changes: 11 additions & 9 deletions enterprise/coderd/identityprovider/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,7 @@ func authorizeMW(accessURL *url.URL) func(next http.Handler) http.Handler {
return
}

// Extract the form parameters for two reasons:
// 1. We need the redirect URI to build the cancel URI.
// 2. Since validation will run once the user clicks "allow", it is
// better to validate now to avoid wasting the user's time clicking a
// button that will just error anyway.
params, errs, err := extractAuthorizeParams(r, app.CallbackURL)
callbackURL, err := url.Parse(app.CallbackURL)
if err != nil {
site.RenderStaticErrorPage(rw, r, site.ErrorPageData{
Status: http.StatusInternalServerError,
Expand All @@ -75,9 +70,16 @@ func authorizeMW(accessURL *url.URL) func(next http.Handler) http.Handler {
})
return
}
if len(errs) > 0 {
errStr := make([]string, len(errs))
for i, err := range errs {

// Extract the form parameters for two reasons:
// 1. We need the redirect URI to build the cancel URI.
// 2. Since validation will run once the user clicks "allow", it is
// better to validate now to avoid wasting the user's time clicking a
// button that will just error anyway.
params, validationErrs, err := extractAuthorizeParams(r, callbackURL)
if err != nil {
errStr := make([]string, len(validationErrs))
for i, err := range validationErrs {
errStr[i] = err.Detail
}
site.RenderStaticErrorPage(rw, r, site.ErrorPageData{
Expand Down
24 changes: 12 additions & 12 deletions enterprise/coderd/identityprovider/tokens.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,49 +33,49 @@ type tokenParams struct {
redirectURL *url.URL
}

func extractTokenParams(r *http.Request, callbackURL string) (tokenParams, []codersdk.ValidationError, error) {
func extractTokenParams(r *http.Request, callbackURL *url.URL) (tokenParams, []codersdk.ValidationError, error) {
p := httpapi.NewQueryParamParser()
err := r.ParseForm()
if err != nil {
return tokenParams{}, nil, xerrors.Errorf("parse form: %w", err)
}

cb, err := url.Parse(callbackURL)
if err != nil {
return tokenParams{}, nil, err
}

p.RequiredNotEmpty("grant_type", "client_secret", "client_id", "code")

vals := r.Form
params := tokenParams{
clientID: p.String(vals, "", "client_id"),
clientSecret: p.String(vals, "", "client_secret"),
code: p.String(vals, "", "code"),
redirectURL: p.RedirectURL(vals, cb, "redirect_uri"),
redirectURL: p.RedirectURL(vals, callbackURL, "redirect_uri"),
grantType: httpapi.ParseCustom(p, vals, "", "grant_type", httpapi.ParseEnum[codersdk.OAuth2ProviderGrantType]),
}

p.ErrorExcessParams(vals)
return params, p.Errors, nil
if len(p.Errors) > 0 {
return tokenParams{}, p.Errors, xerrors.Errorf("invalid query params: %w", p.Errors)
}
return params, nil, nil
}

func Tokens(db database.Store, defaultLifetime time.Duration) http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
app := httpmw.OAuth2ProviderApp(r)

params, validationErrs, err := extractTokenParams(r, app.CallbackURL)
callbackURL, err := url.Parse(app.CallbackURL)
if err != nil {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Failed to validate form values.",
Detail: err.Error(),
})
return
}
if len(validationErrs) > 0 {

params, validationErrs, err := extractTokenParams(r, callbackURL)
if err != nil {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Invalid query params.",
Detail: err.Error(),
Validations: validationErrs,
})
return
Expand Down