Skip to content

Commit 63ab92d

Browse files
denyskonhickfordwxiaoguang
authored
Pre-register OAuth2 applications for git credential helpers (#26291)
This PR is an extended implementation of #25189 and builds upon the proposal by @hickford in #25653, utilizing some ideas proposed internally by @wxiaoguang. Mainly, this PR consists of a mechanism to pre-register OAuth2 applications on startup, which can be enabled or disabled by modifying the `[oauth2].DEFAULT_APPLICATIONS` parameter in app.ini. The OAuth2 applications registered this way are being marked as "locked" and neither be deleted nor edited over UI to prevent confusing/unexpected behavior. Instead, they're being removed if no longer enabled in config. ![grafik](https://github.com/go-gitea/gitea/assets/47871822/81a78b1c-4b68-40a7-9e99-c272ebb8f62e) The implemented mechanism can also be used to pre-register other OAuth2 applications in the future, if wanted. Co-authored-by: hickford <[email protected]> Co-authored-by: wxiaoguang <[email protected]> --------- Co-authored-by: M Hickford <[email protected]> Co-authored-by: wxiaoguang <[email protected]>
1 parent d41aee1 commit 63ab92d

File tree

10 files changed

+131
-12
lines changed

10 files changed

+131
-12
lines changed

custom/conf/app.example.ini

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,11 @@ ENABLE = true
544544
;;
545545
;; Maximum length of oauth2 token/cookie stored on server
546546
;MAX_TOKEN_LENGTH = 32767
547+
;;
548+
;; Pre-register OAuth2 applications for some universally useful services
549+
;; * https://github.com/hickford/git-credential-oauth
550+
;; * https://github.com/git-ecosystem/git-credential-manager
551+
;DEFAULT_APPLICATIONS = git-credential-oauth, git-credential-manager
547552

548553
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
549554
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

docs/content/administration/config-cheat-sheet.en-us.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1100,6 +1100,7 @@ This section only does "set" config, a removed config key from this section won'
11001100
- `JWT_SECRET_URI`: **_empty_**: Instead of defining JWT_SECRET in the configuration, this configuration option can be used to give Gitea a path to a file that contains the secret (example value: `file:/etc/gitea/oauth2_jwt_secret`)
11011101
- `JWT_SIGNING_PRIVATE_KEY_FILE`: **jwt/private.pem**: Private key file path used to sign OAuth2 tokens. The path is relative to `APP_DATA_PATH`. This setting is only needed if `JWT_SIGNING_ALGORITHM` is set to `RS256`, `RS384`, `RS512`, `ES256`, `ES384` or `ES512`. The file must contain a RSA or ECDSA private key in the PKCS8 format. If no key exists a 4096 bit key will be created for you.
11021102
- `MAX_TOKEN_LENGTH`: **32767**: Maximum length of token/cookie to accept from OAuth2 provider
1103+
- `DEFAULT_APPLICATIONS`: **git-credential-oauth, git-credential-manager**: Pre-register OAuth applications for some services on startup. See the [OAuth2 documentation](/development/oauth2-provider.md) for the list of available options.
11031104

11041105
## i18n (`i18n`)
11051106

docs/content/development/oauth2-provider.en-us.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,17 @@ Gitea token scopes are as follows:
7878
| &nbsp;&nbsp;&nbsp; **read:user** | Grants read access to user operations, such as getting user repo subscriptions and user settings. |
7979
| &nbsp;&nbsp;&nbsp; **write:user** | Grants read/write/delete access to user operations, such as updating user repo subscriptions, followed users, and user settings. |
8080

81+
## Pre-configured Applications
82+
83+
Gitea creates OAuth applications for the following services by default on startup, as we assume that these are universally useful.
84+
85+
|Application|Description|Client ID|
86+
|-----------|-----------|---------|
87+
|[git-credential-oauth](https://github.com/hickford/git-credential-oauth)|Git credential helper|`a4792ccc-144e-407e-86c9-5e7d8d9c3269`|
88+
|[Git Credential Manager](https://github.com/git-ecosystem/git-credential-manager)|Git credential helper|`e90ee53c-94e2-48ac-9358-a874fb9e0662`|
89+
90+
To prevent unexpected behavior, they are being displayed as locked in the UI and their creation can instead be controlled by the `DEFAULT_APPLICATIONS` parameter in `app.ini`.
91+
8192
## Client types
8293

8394
Gitea supports both confidential and public client types, [as defined by RFC 6749](https://datatracker.ietf.org/doc/html/rfc6749#section-2.1).

models/auth/oauth2.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import (
1313
"strings"
1414

1515
"code.gitea.io/gitea/models/db"
16+
"code.gitea.io/gitea/modules/container"
17+
"code.gitea.io/gitea/modules/setting"
1618
"code.gitea.io/gitea/modules/timeutil"
1719
"code.gitea.io/gitea/modules/util"
1820

@@ -46,6 +48,83 @@ func init() {
4648
db.RegisterModel(new(OAuth2Grant))
4749
}
4850

51+
type BuiltinOAuth2Application struct {
52+
ConfigName string
53+
DisplayName string
54+
RedirectURIs []string
55+
}
56+
57+
func BuiltinApplications() map[string]*BuiltinOAuth2Application {
58+
m := make(map[string]*BuiltinOAuth2Application)
59+
m["a4792ccc-144e-407e-86c9-5e7d8d9c3269"] = &BuiltinOAuth2Application{
60+
ConfigName: "git-credential-oauth",
61+
DisplayName: "git-credential-oauth",
62+
RedirectURIs: []string{"http://127.0.0.1", "https://127.0.0.1"},
63+
}
64+
m["e90ee53c-94e2-48ac-9358-a874fb9e0662"] = &BuiltinOAuth2Application{
65+
ConfigName: "git-credential-manager",
66+
DisplayName: "Git Credential Manager",
67+
RedirectURIs: []string{"http://127.0.0.1", "https://127.0.0.1"},
68+
}
69+
return m
70+
}
71+
72+
func Init(ctx context.Context) error {
73+
builtinApps := BuiltinApplications()
74+
var builtinAllClientIDs []string
75+
for clientID := range builtinApps {
76+
builtinAllClientIDs = append(builtinAllClientIDs, clientID)
77+
}
78+
79+
var registeredApps []*OAuth2Application
80+
if err := db.GetEngine(ctx).In("client_id", builtinAllClientIDs).Find(&registeredApps); err != nil {
81+
return err
82+
}
83+
84+
clientIDsToAdd := container.Set[string]{}
85+
for _, configName := range setting.OAuth2.DefaultApplications {
86+
found := false
87+
for clientID, builtinApp := range builtinApps {
88+
if builtinApp.ConfigName == configName {
89+
clientIDsToAdd.Add(clientID) // add all user-configured apps to the "add" list
90+
found = true
91+
}
92+
}
93+
if !found {
94+
return fmt.Errorf("unknown oauth2 application: %q", configName)
95+
}
96+
}
97+
clientIDsToDelete := container.Set[string]{}
98+
for _, app := range registeredApps {
99+
if !clientIDsToAdd.Contains(app.ClientID) {
100+
clientIDsToDelete.Add(app.ClientID) // if a registered app is not in the "add" list, it should be deleted
101+
}
102+
}
103+
for _, app := range registeredApps {
104+
clientIDsToAdd.Remove(app.ClientID) // no need to re-add existing (registered) apps, so remove them from the set
105+
}
106+
107+
for _, app := range registeredApps {
108+
if clientIDsToDelete.Contains(app.ClientID) {
109+
if err := deleteOAuth2Application(ctx, app.ID, 0); err != nil {
110+
return err
111+
}
112+
}
113+
}
114+
for clientID := range clientIDsToAdd {
115+
builtinApp := builtinApps[clientID]
116+
if err := db.Insert(ctx, &OAuth2Application{
117+
Name: builtinApp.DisplayName,
118+
ClientID: clientID,
119+
RedirectURIs: builtinApp.RedirectURIs,
120+
}); err != nil {
121+
return err
122+
}
123+
}
124+
125+
return nil
126+
}
127+
49128
// TableName sets the table name to `oauth2_application`
50129
func (app *OAuth2Application) TableName() string {
51130
return "oauth2_application"
@@ -205,6 +284,10 @@ func UpdateOAuth2Application(opts UpdateOAuth2ApplicationOptions) (*OAuth2Applic
205284
if app.UID != opts.UserID {
206285
return nil, fmt.Errorf("UID mismatch")
207286
}
287+
builtinApps := BuiltinApplications()
288+
if _, builtin := builtinApps[app.ClientID]; builtin {
289+
return nil, fmt.Errorf("failed to edit OAuth2 application: application is locked: %s", app.ClientID)
290+
}
208291

209292
app.Name = opts.Name
210293
app.RedirectURIs = opts.RedirectURIs
@@ -261,6 +344,14 @@ func DeleteOAuth2Application(id, userid int64) error {
261344
return err
262345
}
263346
defer committer.Close()
347+
app, err := GetOAuth2ApplicationByID(ctx, id)
348+
if err != nil {
349+
return err
350+
}
351+
builtinApps := BuiltinApplications()
352+
if _, builtin := builtinApps[app.ClientID]; builtin {
353+
return fmt.Errorf("failed to delete OAuth2 application: application is locked: %s", app.ClientID)
354+
}
264355
if err := deleteOAuth2Application(ctx, id, userid); err != nil {
265356
return err
266357
}

modules/setting/oauth2.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ var OAuth2 = struct {
100100
JWTSecretBase64 string `ini:"JWT_SECRET"`
101101
JWTSigningPrivateKeyFile string `ini:"JWT_SIGNING_PRIVATE_KEY_FILE"`
102102
MaxTokenLength int
103+
DefaultApplications []string
103104
}{
104105
Enable: true,
105106
AccessTokenExpirationTime: 3600,
@@ -108,6 +109,7 @@ var OAuth2 = struct {
108109
JWTSigningAlgorithm: "RS256",
109110
JWTSigningPrivateKeyFile: "jwt/private.pem",
110111
MaxTokenLength: math.MaxInt16,
112+
DefaultApplications: []string{"git-credential-oauth", "git-credential-manager"},
111113
}
112114

113115
func loadOAuth2From(rootCfg ConfigProvider) {

options/locale/locale_en-US.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ edit = Edit
9393

9494
enabled = Enabled
9595
disabled = Disabled
96+
locked = Locked
9697

9798
copy = Copy
9899
copy_url = Copy URL
@@ -850,6 +851,7 @@ oauth2_client_secret_hint = The secret will not be shown again after you leave o
850851
oauth2_application_edit = Edit
851852
oauth2_application_create_description = OAuth2 applications gives your third-party application access to user accounts on this instance.
852853
oauth2_application_remove_description = Removing an OAuth2 application will prevent it from accessing authorized user accounts on this instance. Continue?
854+
oauth2_application_locked = Gitea pre-registers some OAuth2 applications on startup if enabled in config. To prevent unexpected bahavior, these can neither be edited nor removed. Please refer to the OAuth2 documentation for more information.
853855

854856
authorized_oauth2_applications = Authorized OAuth2 Applications
855857
authorized_oauth2_applications_description = You have granted access to your personal Gitea account to these third party applications. Please revoke access for applications you no longer need.

routers/init.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
"code.gitea.io/gitea/models"
1212
asymkey_model "code.gitea.io/gitea/models/asymkey"
13+
authmodel "code.gitea.io/gitea/models/auth"
1314
"code.gitea.io/gitea/modules/cache"
1415
"code.gitea.io/gitea/modules/eventsource"
1516
"code.gitea.io/gitea/modules/git"
@@ -138,6 +139,7 @@ func InitWebInstalled(ctx context.Context) {
138139
mustInit(oauth2.Init)
139140

140141
mustInitCtx(ctx, models.Init)
142+
mustInitCtx(ctx, authmodel.Init)
141143
mustInit(repo_service.Init)
142144

143145
// Booting long running goroutines.

routers/web/admin/applications.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ func Applications(ctx *context.Context) {
3939
return
4040
}
4141
ctx.Data["Applications"] = apps
42-
42+
ctx.Data["BuiltinApplications"] = auth.BuiltinApplications()
4343
ctx.HTML(http.StatusOK, tplSettingsApplications)
4444
}
4545

routers/web/repo/http.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ func httpBase(ctx *context.Context) *serviceHandler {
147147
// rely on the results of Contexter
148148
if !ctx.IsSigned {
149149
// TODO: support digit auth - which would be Authorization header with digit
150-
ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=\".\"")
150+
ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="Gitea"`)
151151
ctx.Error(http.StatusUnauthorized)
152152
return nil
153153
}

templates/user/settings/applications_oauth2_list.tmpl

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
{{.locale.Tr "settings.oauth2_application_create_description"}}
55
</div>
66
{{range .Applications}}
7-
<div class="flex-item">
7+
<div class="flex-item flex-item-center">
88
<div class="flex-item-leading">
99
{{svg "octicon-apps" 32}}
1010
</div>
@@ -15,16 +15,21 @@
1515
<span class="ui label">{{.ClientID}}</span>
1616
</div>
1717
</div>
18+
{{$isBuiltin := and $.BuiltinApplications (index $.BuiltinApplications .ClientID)}}
1819
<div class="flex-item-trailing">
19-
<a href="{{$.Link}}/oauth2/{{.ID}}" class="ui primary tiny button">
20-
{{svg "octicon-pencil" 16 "gt-mr-2"}}
21-
{{$.locale.Tr "settings.oauth2_application_edit"}}
22-
</a>
23-
<button class="ui red tiny button delete-button" data-modal-id="remove-gitea-oauth2-application"
24-
data-url="{{$.Link}}/oauth2/{{.ID}}/delete">
25-
{{svg "octicon-trash" 16 "gt-mr-2"}}
26-
{{$.locale.Tr "settings.delete_key"}}
27-
</button>
20+
{{if $isBuiltin}}
21+
<span class="ui basic label" data-tooltip-content="{{$.locale.Tr "settings.oauth2_application_locked"}}">{{ctx.Locale.Tr "locked"}}</span>
22+
{{else}}
23+
<a href="{{$.Link}}/oauth2/{{.ID}}" class="ui primary tiny button">
24+
{{svg "octicon-pencil" 16 "gt-mr-2"}}
25+
{{$.locale.Tr "settings.oauth2_application_edit"}}
26+
</a>
27+
<button class="ui red tiny button delete-button" data-modal-id="remove-gitea-oauth2-application"
28+
data-url="{{$.Link}}/oauth2/{{.ID}}/delete">
29+
{{svg "octicon-trash" 16 "gt-mr-2"}}
30+
{{$.locale.Tr "settings.delete_key"}}
31+
</button>
32+
{{end}}
2833
</div>
2934
</div>
3035
{{end}}

0 commit comments

Comments
 (0)