Skip to content

Commit 5efd05e

Browse files
committed
Support for OAuth client type public, ie. native apps
1 parent 2d2cf58 commit 5efd05e

File tree

20 files changed

+131
-17
lines changed

20 files changed

+131
-17
lines changed

docs/content/doc/developers/oauth2-provider.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ To use the Authorization Code Grant as a third party application it is required
4444

4545
Currently Gitea does not support scopes (see [#4300](https://github.com/go-gitea/gitea/issues/4300)) and all third party applications will be granted access to all resources of the user and their organizations.
4646

47+
## Client types
48+
49+
Gitea supports both confidential and public client types, [as defined by RFC 6749](https://datatracker.ietf.org/doc/html/rfc6749#section-2.1).
50+
51+
For public clients, a redirect URI of a loopback IP address such as `http://127.0.0.1/` allows any port. Avoid using `localhost`, [as recommended by RFC 8252](https://datatracker.ietf.org/doc/html/rfc8252#section-8.3).
52+
4753
## Example
4854

4955
**Note:** This example does not use PKCE.

models/auth/oauth2.go

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ type OAuth2Application struct {
3131
Name string
3232
ClientID string `xorm:"unique"`
3333
ClientSecret string
34+
// https://datatracker.ietf.org/doc/html/rfc6749#section-2.1
35+
Confidential bool `xorm:"NOT NULL DEFAULT TRUE"`
3436
RedirectURIs []string `xorm:"redirect_uris JSON TEXT"`
3537
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
3638
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
@@ -57,15 +59,17 @@ func (app *OAuth2Application) PrimaryRedirectURI() string {
5759

5860
// ContainsRedirectURI checks if redirectURI is allowed for app
5961
func (app *OAuth2Application) ContainsRedirectURI(redirectURI string) bool {
60-
uri, err := url.Parse(redirectURI)
61-
// ignore port for http loopback uris following https://datatracker.ietf.org/doc/html/rfc8252#section-7.3
62-
if err == nil && uri.Scheme == "http" && uri.Port() != "" {
63-
ip := net.ParseIP(uri.Hostname())
64-
if ip != nil && ip.IsLoopback() {
65-
// strip port
66-
uri.Host = uri.Hostname()
67-
if util.IsStringInSlice(uri.String(), app.RedirectURIs, true) {
68-
return true
62+
if !app.Confidential {
63+
uri, err := url.Parse(redirectURI)
64+
// ignore port for http loopback uris following https://datatracker.ietf.org/doc/html/rfc8252#section-7.3
65+
if err == nil && uri.Scheme == "http" && uri.Port() != "" {
66+
ip := net.ParseIP(uri.Hostname())
67+
if ip != nil && ip.IsLoopback() {
68+
// strip port
69+
uri.Host = uri.Hostname()
70+
if util.IsStringInSlice(uri.String(), app.RedirectURIs, true) {
71+
return true
72+
}
6973
}
7074
}
7175
}
@@ -163,6 +167,7 @@ func GetOAuth2ApplicationsByUserID(ctx context.Context, userID int64) (apps []*O
163167
type CreateOAuth2ApplicationOptions struct {
164168
Name string
165169
UserID int64
170+
Confidential bool
166171
RedirectURIs []string
167172
}
168173

@@ -174,6 +179,7 @@ func CreateOAuth2Application(ctx context.Context, opts CreateOAuth2ApplicationOp
174179
Name: opts.Name,
175180
ClientID: clientID,
176181
RedirectURIs: opts.RedirectURIs,
182+
Confidential: opts.Confidential,
177183
}
178184
if err := db.Insert(ctx, app); err != nil {
179185
return nil, err
@@ -186,6 +192,7 @@ type UpdateOAuth2ApplicationOptions struct {
186192
ID int64
187193
Name string
188194
UserID int64
195+
Confidential bool
189196
RedirectURIs []string
190197
}
191198

@@ -207,6 +214,7 @@ func UpdateOAuth2Application(opts UpdateOAuth2ApplicationOptions) (*OAuth2Applic
207214

208215
app.Name = opts.Name
209216
app.RedirectURIs = opts.RedirectURIs
217+
app.Confidential = opts.Confidential
210218

211219
if err = updateOAuth2Application(ctx, app); err != nil {
212220
return nil, err
@@ -217,7 +225,7 @@ func UpdateOAuth2Application(opts UpdateOAuth2ApplicationOptions) (*OAuth2Applic
217225
}
218226

219227
func updateOAuth2Application(ctx context.Context, app *OAuth2Application) error {
220-
if _, err := db.GetEngine(ctx).ID(app.ID).Update(app); err != nil {
228+
if _, err := db.GetEngine(ctx).ID(app.ID).UseBool("Confidential").Update(app); err != nil {
221229
return err
222230
}
223231
return nil

models/auth/oauth2_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ func TestOAuth2Application_ContainsRedirectURI(t *testing.T) {
4646
func TestOAuth2Application_ContainsRedirectURI_WithPort(t *testing.T) {
4747
app := &auth_model.OAuth2Application{
4848
RedirectURIs: []string{"http://127.0.0.1/", "http://::1/", "http://192.168.0.1/", "http://intranet/", "https://127.0.0.1/"},
49+
Confidential: false,
4950
}
5051

5152
// http loopback uris should ignore port

models/fixtures/oauth2_application.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,14 @@
77
redirect_uris: '["a"]'
88
created_unix: 1546869730
99
updated_unix: 1546869730
10+
confidential: true
11+
-
12+
id: 2
13+
uid: 2
14+
name: "Test native app"
15+
client_id: "ce5a1322-42a7-11ed-b878-0242ac120002"
16+
client_secret: "$2a$10$UYRgUSgekzBp6hYe8pAdc.cgB4Gn06QRKsORUnIYTYQADs.YR/uvi" # bcrypt of "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=
17+
redirect_uris: '["http://127.0.0.1"]'
18+
created_unix: 1546869730
19+
updated_unix: 1546869730
20+
confidential: false

models/fixtures/oauth2_authorization_code.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,10 @@
66
redirect_uri: "a"
77
valid_until: 3546869730
88

9+
- id: 2
10+
grant_id: 4
11+
code: "authcodepublic"
12+
code_challenge: "CjvyTLSdR47G5zYenDA-eDWW4lRrO8yvjcWwbD_deOg" # Code Verifier: N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt
13+
code_challenge_method: "S256"
14+
redirect_uri: "http://127.0.0.1/"
15+
valid_until: 3546869730

models/fixtures/oauth2_grant.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,12 @@
2020
counter: 1
2121
scope: "openid profile email"
2222
created_unix: 1546869730
23-
updated_unix: 1546869730
23+
updated_unix: 1546869730
24+
25+
- id: 4
26+
user_id: 99
27+
application_id: 2
28+
counter: 1
29+
scope: "whatever"
30+
created_unix: 1546869730
31+
updated_unix: 1546869730

models/migrations/migrations.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,8 @@ var migrations = []Migration{
413413
NewMigration("Add badges to users", createUserBadgesTable),
414414
// v225 -> v226
415415
NewMigration("Alter gpg_key/public_key content TEXT fields to MEDIUMTEXT", alterPublicGPGKeyContentFieldsToMediumText),
416+
// v226 -> v227
417+
NewMigration("Add confidential column default true to OAuth2Application table", addConfidentialColumnToOAuth2ApplicationTable),
416418
}
417419

418420
// GetCurrentDBVersion returns the current db version

models/migrations/v226.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2022 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package migrations
6+
7+
import (
8+
"code.gitea.io/gitea/modules/timeutil"
9+
"xorm.io/xorm"
10+
)
11+
12+
// addConfidentialColumnToOAuth2ApplicationTable: add Confidential column, setting existing rows to true
13+
func addConfidentialColumnToOAuth2ApplicationTable(x *xorm.Engine) error {
14+
type OAuth2Application struct {
15+
ID int64 `xorm:"pk autoincr"`
16+
UID int64 `xorm:"INDEX"`
17+
Name string
18+
ClientID string `xorm:"unique"`
19+
ClientSecret string
20+
Confidential bool `xorm:"NOT NULL DEFAULT TRUE"`
21+
RedirectURIs []string `xorm:"redirect_uris JSON TEXT"`
22+
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
23+
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
24+
}
25+
26+
return x.Sync(new(OAuth2Application))
27+
}

modules/convert/convert.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,7 @@ func ToOAuth2Application(app *auth.OAuth2Application) *api.OAuth2Application {
396396
Name: app.Name,
397397
ClientID: app.ClientID,
398398
ClientSecret: app.ClientSecret,
399+
Confidential: app.Confidential,
399400
RedirectURIs: app.RedirectURIs,
400401
Created: app.CreatedUnix.AsTime(),
401402
}

modules/structs/user_app.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ type CreateAccessTokenOption struct {
3131
// CreateOAuth2ApplicationOptions holds options to create an oauth2 application
3232
type CreateOAuth2ApplicationOptions struct {
3333
Name string `json:"name" binding:"Required"`
34+
Confidential bool `json:"confidential"`
3435
RedirectURIs []string `json:"redirect_uris" binding:"Required"`
3536
}
3637

@@ -41,6 +42,7 @@ type OAuth2Application struct {
4142
Name string `json:"name"`
4243
ClientID string `json:"client_id"`
4344
ClientSecret string `json:"client_secret"`
45+
Confidential bool `json:"confidential"`
4446
RedirectURIs []string `json:"redirect_uris"`
4547
Created time.Time `json:"created"`
4648
}

0 commit comments

Comments
 (0)