Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitcd5fb8f

Browse files
committed
Implement refresh grant
1 parent2771224 commitcd5fb8f

File tree

8 files changed

+350
-44
lines changed

8 files changed

+350
-44
lines changed

‎coderd/apidoc/docs.go

Lines changed: 14 additions & 10 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎coderd/apidoc/swagger.json

Lines changed: 13 additions & 10 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎codersdk/oauth2.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,12 +184,12 @@ type OAuth2ProviderGrantType string
184184

185185
const (
186186
OAuth2ProviderGrantTypeAuthorizationCodeOAuth2ProviderGrantType="authorization_code"
187+
OAuth2ProviderGrantTypeRefreshTokenOAuth2ProviderGrantType="refresh_token"
187188
)
188189

189190
func (eOAuth2ProviderGrantType)Valid()bool {
190-
//nolint:gocritic,revive // More cases will be added later.
191191
switche {
192-
caseOAuth2ProviderGrantTypeAuthorizationCode:
192+
caseOAuth2ProviderGrantTypeAuthorizationCode,OAuth2ProviderGrantTypeRefreshToken:
193193
returntrue
194194
}
195195
returnfalse

‎docs/api/enterprise.md

Lines changed: 10 additions & 7 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎enterprise/coderd/identityprovider/tokens.go

Lines changed: 128 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,16 @@ var errBadSecret = xerrors.New("Invalid client secret")
3030
// errBadCode means the user provided a bad code.
3131
varerrBadCode=xerrors.New("Invalid code")
3232

33+
// errBadToken means the user provided a bad token.
34+
varerrBadToken=xerrors.New("Invalid token")
35+
3336
typetokenParamsstruct {
3437
clientIDstring
3538
clientSecretstring
3639
codestring
3740
grantType codersdk.OAuth2ProviderGrantType
3841
redirectURL*url.URL
42+
refreshTokenstring
3943
}
4044

4145
funcextractTokenParams(r*http.Request,callbackURL*url.URL) (tokenParams, []codersdk.ValidationError,error) {
@@ -44,15 +48,24 @@ func extractTokenParams(r *http.Request, callbackURL *url.URL) (tokenParams, []c
4448
iferr!=nil {
4549
returntokenParams{},nil,xerrors.Errorf("parse form: %w",err)
4650
}
47-
p.RequiredNotEmpty("grant_type","client_secret","client_id","code")
4851

4952
vals:=r.Form
53+
p.RequiredNotEmpty("grant_type")
54+
grantType:=httpapi.ParseCustom(p,vals,"","grant_type",httpapi.ParseEnum[codersdk.OAuth2ProviderGrantType])
55+
switchgrantType {
56+
casecodersdk.OAuth2ProviderGrantTypeRefreshToken:
57+
p.RequiredNotEmpty("refresh_token")
58+
casecodersdk.OAuth2ProviderGrantTypeAuthorizationCode:
59+
p.RequiredNotEmpty("client_secret","client_id","code")
60+
}
61+
5062
params:=tokenParams{
5163
clientID:p.String(vals,"","client_id"),
5264
clientSecret:p.String(vals,"","client_secret"),
5365
code:p.String(vals,"","code"),
66+
grantType:grantType,
5467
redirectURL:p.RedirectURL(vals,callbackURL,"redirect_uri"),
55-
grantType:httpapi.ParseCustom(p,vals,"","grant_type",httpapi.ParseEnum[codersdk.OAuth2ProviderGrantType]),
68+
refreshToken:p.String(vals,"","refresh_token"),
5669
}
5770

5871
p.ErrorExcessParams(vals)
@@ -89,7 +102,9 @@ func Tokens(db database.Store, defaultLifetime time.Duration) http.HandlerFunc {
89102
vartoken oauth2.Token
90103
//nolint:gocritic,revive // More cases will be added later.
91104
switchparams.grantType {
92-
// TODO: Client creds, device code, refresh.
105+
// TODO: Client creds, device code.
106+
casecodersdk.OAuth2ProviderGrantTypeRefreshToken:
107+
token,err=refreshTokenGrant(ctx,db,app,defaultLifetime,params)
93108
default:
94109
token,err=authorizationCodeGrant(ctx,db,app,defaultLifetime,params)
95110
}
@@ -163,9 +178,6 @@ func authorizationCodeGrant(ctx context.Context, db database.Store, app database
163178
}
164179

165180
// Generate a refresh token.
166-
// The refresh token is not currently used or exposed though as API keys can
167-
// already be refreshed by just using them.
168-
// TODO: However, should we implement the refresh grant anyway?
169181
refreshToken,err:=GenerateSecret()
170182
iferr!=nil {
171183
return oauth2.Token{},err
@@ -244,10 +256,115 @@ func authorizationCodeGrant(ctx context.Context, db database.Store, app database
244256
}
245257

246258
return oauth2.Token{
247-
AccessToken:sessionToken,
248-
TokenType:"Bearer",
249-
// TODO: Exclude until refresh grant is implemented.
250-
// RefreshToken: refreshToken.formatted,
251-
// Expiry: key.ExpiresAt,
259+
AccessToken:sessionToken,
260+
TokenType:"Bearer",
261+
RefreshToken:refreshToken.Formatted,
262+
Expiry:key.ExpiresAt,
263+
},nil
264+
}
265+
266+
funcrefreshTokenGrant(ctx context.Context,db database.Store,app database.OAuth2ProviderApp,defaultLifetime time.Duration,paramstokenParams) (oauth2.Token,error) {
267+
// Validate the token.
268+
token,err:=parseSecret(params.refreshToken)
269+
iferr!=nil {
270+
return oauth2.Token{},errBadToken
271+
}
272+
//nolint:gocritic // There is no user yet so we must use the system.
273+
dbToken,err:=db.GetOAuth2ProviderAppTokenByPrefix(dbauthz.AsSystemRestricted(ctx), []byte(token.prefix))
274+
iferrors.Is(err,sql.ErrNoRows) {
275+
return oauth2.Token{},errBadToken
276+
}
277+
iferr!=nil {
278+
return oauth2.Token{},err
279+
}
280+
equal,err:=userpassword.Compare(string(dbToken.RefreshHash),token.secret)
281+
iferr!=nil {
282+
return oauth2.Token{},xerrors.Errorf("unable to compare token: %w",err)
283+
}
284+
if!equal {
285+
return oauth2.Token{},errBadToken
286+
}
287+
288+
// Ensure the token has not expired.
289+
ifdbToken.ExpiresAt.Before(dbtime.Now()) {
290+
return oauth2.Token{},errBadToken
291+
}
292+
293+
// Grab the user roles so we can perform the refresh as the user.
294+
//nolint:gocritic // There is no user yet so we must use the system.
295+
prevKey,err:=db.GetAPIKeyByID(dbauthz.AsSystemRestricted(ctx),dbToken.APIKeyID)
296+
iferr!=nil {
297+
return oauth2.Token{},err
298+
}
299+
//nolint:gocritic // There is no user yet so we must use the system.
300+
roles,err:=db.GetAuthorizationUserRoles(dbauthz.AsSystemRestricted(ctx),prevKey.UserID)
301+
iferr!=nil {
302+
return oauth2.Token{},err
303+
}
304+
userSubj:= rbac.Subject{
305+
ID:prevKey.UserID.String(),
306+
Roles:rbac.RoleNames(roles.Roles),
307+
Groups:roles.Groups,
308+
Scope:rbac.ScopeAll,
309+
}
310+
311+
// Generate a new refresh token.
312+
refreshToken,err:=GenerateSecret()
313+
iferr!=nil {
314+
return oauth2.Token{},err
315+
}
316+
317+
// Generate the new API key.
318+
// TODO: We are ignoring scopes for now.
319+
tokenName:=fmt.Sprintf("%s_%s_oauth_session_token",prevKey.UserID,app.ID)
320+
key,sessionToken,err:=apikey.Generate(apikey.CreateParams{
321+
UserID:prevKey.UserID,
322+
LoginType:database.LoginTypeOAuth2ProviderApp,
323+
// TODO: This is just the lifetime for api keys, maybe have its own config
324+
// settings. #11693
325+
DefaultLifetime:defaultLifetime,
326+
// For now, we allow only one token per app and user at a time.
327+
TokenName:tokenName,
328+
})
329+
iferr!=nil {
330+
return oauth2.Token{},err
331+
}
332+
333+
// Replace the token.
334+
err=db.InTx(func(tx database.Store)error {
335+
ctx:=dbauthz.As(ctx,userSubj)
336+
err=tx.DeleteAPIKeyByID(ctx,prevKey.ID)// This cascades to the token.
337+
iferr!=nil {
338+
returnxerrors.Errorf("delete oauth2 app token: %w",err)
339+
}
340+
341+
newKey,err:=tx.InsertAPIKey(ctx,key)
342+
iferr!=nil {
343+
returnxerrors.Errorf("insert oauth2 access token: %w",err)
344+
}
345+
346+
_,err=tx.InsertOAuth2ProviderAppToken(ctx, database.InsertOAuth2ProviderAppTokenParams{
347+
ID:uuid.New(),
348+
CreatedAt:dbtime.Now(),
349+
ExpiresAt:key.ExpiresAt,
350+
HashPrefix: []byte(refreshToken.Prefix),
351+
RefreshHash: []byte(refreshToken.Hashed),
352+
AppSecretID:dbToken.AppSecretID,
353+
APIKeyID:newKey.ID,
354+
})
355+
iferr!=nil {
356+
returnxerrors.Errorf("insert oauth2 refresh token: %w",err)
357+
}
358+
returnnil
359+
},nil)
360+
iferr!=nil {
361+
return oauth2.Token{},err
362+
}
363+
364+
return oauth2.Token{
365+
AccessToken:sessionToken,
366+
TokenType:"Bearer",
367+
RefreshToken:refreshToken.Formatted,
368+
Expiry:key.ExpiresAt,
252369
},nil
253370
}

‎enterprise/coderd/oauth2.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -300,9 +300,10 @@ func (api *API) postOAuth2ProviderAppAuthorize() http.HandlerFunc {
300300
// @ID oauth2-token-exchange
301301
// @Produce json
302302
// @Tags Enterprise
303-
// @Param client_id formData string true "Client ID"
304-
// @Param client_secret formData string true "Client secret"
305-
// @Param code formData string true "Authorization code"
303+
// @Param client_id formData string false "Client ID, required if grant_type=authorization_code"
304+
// @Param client_secret formData string false "Client secret, required if grant_type=authorization_code"
305+
// @Param code formData string false "Authorization code, required if grant_type=authorization_code"
306+
// @Param refresh_token formData string false "Refresh token, required if grant_type=refresh_token"
306307
// @Param grant_type formData codersdk.OAuth2ProviderGrantType true "Grant type"
307308
// @Success 200 {object} oauth2.Token
308309
// @Router /login/oauth2/tokens [post]

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp