From 97d30ec93f0a16b518d992858071f19541b6692d Mon Sep 17 00:00:00 2001 From: M Hickford Date: Mon, 3 Jul 2023 20:12:06 +0100 Subject: [PATCH 01/10] preconfigure git-credential-oauth --- custom/conf/app.example.ini | 3 +++ .../config-cheat-sheet.en-us.md | 1 + models/auth/oauth2.go | 21 +++++++++++++++++++ modules/setting/oauth2.go | 2 ++ routers/init.go | 2 ++ routers/web/repo/http.go | 2 +- 6 files changed, 30 insertions(+), 1 deletion(-) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index db148c52ade48..b64f1024b6fe1 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -544,6 +544,9 @@ ENABLE = true ;; ;; Maximum length of oauth2 token/cookie stored on server ;MAX_TOKEN_LENGTH = 32767 +;; +;; Register OAuth applications for Git credential helpers +;GIT_CREDENTIAL_HELPERS = true ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/docs/content/administration/config-cheat-sheet.en-us.md b/docs/content/administration/config-cheat-sheet.en-us.md index 6d5789ff0dc5a..d369ef2168c97 100644 --- a/docs/content/administration/config-cheat-sheet.en-us.md +++ b/docs/content/administration/config-cheat-sheet.en-us.md @@ -1099,6 +1099,7 @@ This section only does "set" config, a removed config key from this section won' - `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`) - `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. - `MAX_TOKEN_LENGTH`: **32767**: Maximum length of token/cookie to accept from OAuth2 provider +- `GIT_CREDENTIAL_HELPERS`: **true**: Register OAuth applications for Git credential helpers at startup. ## i18n (`i18n`) diff --git a/models/auth/oauth2.go b/models/auth/oauth2.go index 0f64b56c1635b..7b77fbed6ad4f 100644 --- a/models/auth/oauth2.go +++ b/models/auth/oauth2.go @@ -13,6 +13,7 @@ import ( "strings" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" @@ -46,6 +47,26 @@ func init() { db.RegisterModel(new(OAuth2Grant)) } +func Init(ctx context.Context) error { + if setting.OAuth2.GitCredentialHelpers { + // the following Git credential helpers are universally useful + // https://git-scm.com/doc/credential-helpers + _ = db.Insert(ctx, []OAuth2Application{ + { + Name: "git-credential-oauth", + ClientID: "a4792ccc-144e-407e-86c9-5e7d8d9c3269", + RedirectURIs: []string{"http://127.0.0.1", "https://127.0.0.1"}, + }, + { + Name: "Git Credential Manager", + ClientID: "e90ee53c-94e2-48ac-9358-a874fb9e0662", + RedirectURIs: []string{"http://127.0.0.1", "https://127.0.0.1"}, + }, + }) + } + return nil +} + // TableName sets the table name to `oauth2_application` func (app *OAuth2Application) TableName() string { return "oauth2_application" diff --git a/modules/setting/oauth2.go b/modules/setting/oauth2.go index 78a9462de9a65..38a0532f4c7eb 100644 --- a/modules/setting/oauth2.go +++ b/modules/setting/oauth2.go @@ -100,6 +100,7 @@ var OAuth2 = struct { JWTSecretBase64 string `ini:"JWT_SECRET"` JWTSigningPrivateKeyFile string `ini:"JWT_SIGNING_PRIVATE_KEY_FILE"` MaxTokenLength int + GitCredentialHelpers bool }{ Enable: true, AccessTokenExpirationTime: 3600, @@ -108,6 +109,7 @@ var OAuth2 = struct { JWTSigningAlgorithm: "RS256", JWTSigningPrivateKeyFile: "jwt/private.pem", MaxTokenLength: math.MaxInt16, + GitCredentialHelpers: true, } func loadOAuth2From(rootCfg ConfigProvider) { diff --git a/routers/init.go b/routers/init.go index ddbabcc397447..020fff31c0e38 100644 --- a/routers/init.go +++ b/routers/init.go @@ -10,6 +10,7 @@ import ( "code.gitea.io/gitea/models" asymkey_model "code.gitea.io/gitea/models/asymkey" + authmodel "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/eventsource" "code.gitea.io/gitea/modules/git" @@ -138,6 +139,7 @@ func InitWebInstalled(ctx context.Context) { mustInit(oauth2.Init) mustInitCtx(ctx, models.Init) + mustInitCtx(ctx, authmodel.Init) mustInit(repo_service.Init) // Booting long running goroutines. diff --git a/routers/web/repo/http.go b/routers/web/repo/http.go index 0cae9aeda4549..c8ecb3b1d8ad7 100644 --- a/routers/web/repo/http.go +++ b/routers/web/repo/http.go @@ -147,7 +147,7 @@ func httpBase(ctx *context.Context) *serviceHandler { // rely on the results of Contexter if !ctx.IsSigned { // TODO: support digit auth - which would be Authorization header with digit - ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=\".\"") + ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="Gitea"`) ctx.Error(http.StatusUnauthorized) return nil } From ad62ed506db136a6df90d7e6e7ceeb254f28e921 Mon Sep 17 00:00:00 2001 From: Denys Konovalov Date: Wed, 2 Aug 2023 13:05:30 +0200 Subject: [PATCH 02/10] refactor pre-registered application support --- .../config-cheat-sheet.en-us.md | 2 +- .../development/oauth2-provider.en-us.md | 10 +++ models/auth/oauth2.go | 69 +++++++++++++++---- models/migrations/v1_21/v271.go | 15 ++++ modules/setting/oauth2.go | 4 +- modules/util/slice.go | 10 +++ options/locale/locale_en-US.ini | 1 + .../settings/applications_oauth2_list.tmpl | 5 +- 8 files changed, 96 insertions(+), 20 deletions(-) create mode 100644 models/migrations/v1_21/v271.go diff --git a/docs/content/administration/config-cheat-sheet.en-us.md b/docs/content/administration/config-cheat-sheet.en-us.md index c1c5da3178f6e..4c197592ca5f6 100644 --- a/docs/content/administration/config-cheat-sheet.en-us.md +++ b/docs/content/administration/config-cheat-sheet.en-us.md @@ -1100,7 +1100,7 @@ This section only does "set" config, a removed config key from this section won' - `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`) - `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. - `MAX_TOKEN_LENGTH`: **32767**: Maximum length of token/cookie to accept from OAuth2 provider -- `GIT_CREDENTIAL_HELPERS`: **true**: Register OAuth applications for Git credential helpers at startup. +- `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. ## i18n (`i18n`) diff --git a/docs/content/development/oauth2-provider.en-us.md b/docs/content/development/oauth2-provider.en-us.md index b3824f4b2eacb..8934e8f30aedd 100644 --- a/docs/content/development/oauth2-provider.en-us.md +++ b/docs/content/development/oauth2-provider.en-us.md @@ -78,6 +78,16 @@ Gitea token scopes are as follows: |     **read:user** | Grants read access to user operations, such as getting user repo subscriptions and user settings. | |     **write:user** | Grants read/write/delete access to user operations, such as updating user repo subscriptions, followed users, and user settings. | +## Pre-configured Applications + +Gitea creates OAuth applications for the following services by default on startup: + - [git-credential-oauth](https://github.com/hickford/git-credential-oauth) + - [Git Credential Manager](https://github.com/git-ecosystem/git-credential-manager) + +as we assume that these are universally useful. + +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`. + ## Client types Gitea supports both confidential and public client types, [as defined by RFC 6749](https://datatracker.ietf.org/doc/html/rfc6749#section-2.1). diff --git a/models/auth/oauth2.go b/models/auth/oauth2.go index 7b77fbed6ad4f..8441c1efe8b18 100644 --- a/models/auth/oauth2.go +++ b/models/auth/oauth2.go @@ -39,6 +39,7 @@ type OAuth2Application struct { RedirectURIs []string `xorm:"redirect_uris JSON TEXT"` CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` + Locked bool `xorm:"NOT NULL DEFAULT FALSE"` } func init() { @@ -48,22 +49,60 @@ func init() { } func Init(ctx context.Context) error { - if setting.OAuth2.GitCredentialHelpers { - // the following Git credential helpers are universally useful - // https://git-scm.com/doc/credential-helpers - _ = db.Insert(ctx, []OAuth2Application{ - { - Name: "git-credential-oauth", - ClientID: "a4792ccc-144e-407e-86c9-5e7d8d9c3269", - RedirectURIs: []string{"http://127.0.0.1", "https://127.0.0.1"}, - }, - { - Name: "Git Credential Manager", - ClientID: "e90ee53c-94e2-48ac-9358-a874fb9e0662", - RedirectURIs: []string{"http://127.0.0.1", "https://127.0.0.1"}, - }, - }) + defaultApplications := []*OAuth2Application{ + { + Name: "git-credential-oauth", + ClientID: "a4792ccc-144e-407e-86c9-5e7d8d9c3269", + RedirectURIs: []string{"http://127.0.0.1", "https://127.0.0.1"}, + Locked: true, + }, + { + Name: "Git Credential Manager", + ClientID: "e90ee53c-94e2-48ac-9358-a874fb9e0662", + RedirectURIs: []string{"http://127.0.0.1", "https://127.0.0.1"}, + Locked: true, + }, + } + + registeredOAuth2Apps, _ := GetOAuth2ApplicationsByUserID(ctx, 0) + + var enabledOAuth2Apps []*OAuth2Application + for _, entry := range setting.OAuth2.DefaultApplications { + app := util.SliceFind(defaultApplications, func(a *OAuth2Application) bool { return a.Name == entry }) + if app != -1 { + enabledOAuth2Apps = append(enabledOAuth2Apps, defaultApplications[app]) + } + } + + var disabledOAuth2Apps []*OAuth2Application + for _, entry := range defaultApplications { + app := util.SliceFind(enabledOAuth2Apps, func(a *OAuth2Application) bool { return a.ClientID == entry.ClientID }) + registeredApp := util.SliceFind(registeredOAuth2Apps, func(a *OAuth2Application) bool { return a.ClientID == entry.ClientID }) + if app == -1 && registeredApp != -1 { + disabledOAuth2Apps = append(disabledOAuth2Apps, registeredOAuth2Apps[registeredApp]) + } + } + + var createOAuth2Apps []*OAuth2Application + for _, app := range enabledOAuth2Apps { + registeredApp := util.SliceFind(registeredOAuth2Apps, func(a *OAuth2Application) bool { return a.ClientID == app.ClientID }) + if registeredApp == -1 { + createOAuth2Apps = append(createOAuth2Apps, app) + } } + + if len(createOAuth2Apps) > 0 { + if err := db.Insert(ctx, createOAuth2Apps); err != nil { + return err + } + } + + for _, app := range disabledOAuth2Apps { + if err := deleteOAuth2Application(ctx, app.ID, 0); err != nil { + return err + } + } + return nil } diff --git a/models/migrations/v1_21/v271.go b/models/migrations/v1_21/v271.go new file mode 100644 index 0000000000000..0c9218ed0f69a --- /dev/null +++ b/models/migrations/v1_21/v271.go @@ -0,0 +1,15 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_21 //nolint + +import ( + "xorm.io/xorm" +) + +func OAuth2ApplicationAddLockedProperty(x *xorm.Engine) error { + type OAuth2Application struct { + Locked bool `xorm:"NOT NULL DEFAULT FALSE"` + } + return x.Sync(new(OAuth2Application)) +} diff --git a/modules/setting/oauth2.go b/modules/setting/oauth2.go index 38a0532f4c7eb..ff9e0bd703705 100644 --- a/modules/setting/oauth2.go +++ b/modules/setting/oauth2.go @@ -100,7 +100,7 @@ var OAuth2 = struct { JWTSecretBase64 string `ini:"JWT_SECRET"` JWTSigningPrivateKeyFile string `ini:"JWT_SIGNING_PRIVATE_KEY_FILE"` MaxTokenLength int - GitCredentialHelpers bool + DefaultApplications []string }{ Enable: true, AccessTokenExpirationTime: 3600, @@ -109,7 +109,7 @@ var OAuth2 = struct { JWTSigningAlgorithm: "RS256", JWTSigningPrivateKeyFile: "jwt/private.pem", MaxTokenLength: math.MaxInt16, - GitCredentialHelpers: true, + DefaultApplications: []string{"git-credential-oauth", "Git Credential Manager"}, } func loadOAuth2From(rootCfg ConfigProvider) { diff --git a/modules/util/slice.go b/modules/util/slice.go index 74356f5496205..9fc3270236636 100644 --- a/modules/util/slice.go +++ b/modules/util/slice.go @@ -88,3 +88,13 @@ func SliceRemoveAllFunc[T comparable](slice []T, targetFunc func(T) bool) []T { } return slice[:idx] } + +// SliceFind returns the element id of the first element that matches the condition, otherwise -1 +func SliceFind[T any](slice []T, condFunc func(T) bool) int { + for i, e := range slice { + if condFunc(e) { + return i + } + } + return -1 +} diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 6cb830b6d02c7..1186cfd597e89 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -849,6 +849,7 @@ oauth2_client_secret_hint = The secret won't be visible if you revisit this page oauth2_application_edit = Edit oauth2_application_create_description = OAuth2 applications gives your third-party application access to user accounts on this instance. oauth2_application_remove_description = Removing an OAuth2 application will prevent it to access authorized user accounts on this instance. Continue? +oauth2_application_locked = Gitea pre-registers some OAuth2 applications on startup if enabled in config. To prevent unexpected bahavior, these can be edited, but not removed. Please refer to the OAuth2 documentation for more information. authorized_oauth2_applications = Authorized OAuth2 Applications authorized_oauth2_applications_description = You've granted access to your personal Gitea account to these third party applications. Please revoke access for applications no longer needed. diff --git a/templates/user/settings/applications_oauth2_list.tmpl b/templates/user/settings/applications_oauth2_list.tmpl index be6569c03c03f..9ecccab2aefba 100644 --- a/templates/user/settings/applications_oauth2_list.tmpl +++ b/templates/user/settings/applications_oauth2_list.tmpl @@ -15,13 +15,14 @@ {{.ClientID}} -
+
{{svg "octicon-pencil" 16 "gt-mr-2"}} {{$.locale.Tr "settings.oauth2_application_edit"}} From 80a83b9149569644eae0f0527482fc101ce2050c Mon Sep 17 00:00:00 2001 From: Denys Konovalov Date: Wed, 2 Aug 2023 13:48:11 +0200 Subject: [PATCH 03/10] update app.example.ini --- custom/conf/app.example.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index f87b96a46d02f..e7434c6c8b35f 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -545,8 +545,8 @@ ENABLE = true ;; Maximum length of oauth2 token/cookie stored on server ;MAX_TOKEN_LENGTH = 32767 ;; -;; Register OAuth applications for Git credential helpers -;GIT_CREDENTIAL_HELPERS = true +;; Pre-register OAuth2 applications for some universally useful services +;DEFAULT_APPLICATIONS = git-credential-oauth, Git Credential Manager ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; From 31398251fd5531614ea82557d817924a94a8f256 Mon Sep 17 00:00:00 2001 From: Denys Konovalov Date: Wed, 2 Aug 2023 13:58:35 +0200 Subject: [PATCH 04/10] fmt docs --- docs/content/development/oauth2-provider.en-us.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/content/development/oauth2-provider.en-us.md b/docs/content/development/oauth2-provider.en-us.md index 8934e8f30aedd..07af8252c5284 100644 --- a/docs/content/development/oauth2-provider.en-us.md +++ b/docs/content/development/oauth2-provider.en-us.md @@ -81,8 +81,9 @@ Gitea token scopes are as follows: ## Pre-configured Applications Gitea creates OAuth applications for the following services by default on startup: - - [git-credential-oauth](https://github.com/hickford/git-credential-oauth) - - [Git Credential Manager](https://github.com/git-ecosystem/git-credential-manager) + +- [git-credential-oauth](https://github.com/hickford/git-credential-oauth) +- [Git Credential Manager](https://github.com/git-ecosystem/git-credential-manager) as we assume that these are universally useful. From c1ca86c14caff88e85a870ff5277a96244ba70c7 Mon Sep 17 00:00:00 2001 From: Denys Konovalov Date: Wed, 2 Aug 2023 22:30:36 +0200 Subject: [PATCH 05/10] lookup registered iauth2 apps by client id instead of user --- models/auth/oauth2.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/models/auth/oauth2.go b/models/auth/oauth2.go index 8441c1efe8b18..f709e4ad4633c 100644 --- a/models/auth/oauth2.go +++ b/models/auth/oauth2.go @@ -64,7 +64,15 @@ func Init(ctx context.Context) error { }, } - registeredOAuth2Apps, _ := GetOAuth2ApplicationsByUserID(ctx, 0) + var clientIDs []string + for _, app := range defaultApplications { + clientIDs = append(clientIDs, app.ClientID) + } + + var registeredOAuth2Apps []*OAuth2Application + if err := db.GetEngine(ctx).In("client_id", clientIDs).Find(®isteredOAuth2Apps); err != nil { + return err + } var enabledOAuth2Apps []*OAuth2Application for _, entry := range setting.OAuth2.DefaultApplications { From e6c3192af7b23f8c441d83ea608e6b68aee7dd42 Mon Sep 17 00:00:00 2001 From: Denys Konovalov Date: Thu, 3 Aug 2023 10:44:00 +0200 Subject: [PATCH 06/10] reference migration in migrations.go --- models/migrations/migrations.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index b2140a1eb1327..0528168d85d5f 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -523,6 +523,8 @@ var migrations = []Migration{ NewMigration("Drop deleted branch table", v1_21.DropDeletedBranchTable), // v270 -> v271 NewMigration("Fix PackageProperty typo", v1_21.FixPackagePropertyTypo), + // v271 -> v272 + NewMigration("Add locked property to OAuth2 application", v1_21.OAuth2ApplicationAddLockedProperty), } // GetCurrentDBVersion returns the current db version From b57449421b3d80be92d50c3909e86fe7938f62f6 Mon Sep 17 00:00:00 2001 From: Denys Konovalov Date: Thu, 3 Aug 2023 21:02:52 +0200 Subject: [PATCH 07/10] prevent locked oauth2 apps from being deleted --- models/auth/oauth2.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/models/auth/oauth2.go b/models/auth/oauth2.go index f709e4ad4633c..1ba7294572cbe 100644 --- a/models/auth/oauth2.go +++ b/models/auth/oauth2.go @@ -329,6 +329,13 @@ func DeleteOAuth2Application(id, userid int64) error { return err } defer committer.Close() + app, err := GetOAuth2ApplicationByID(ctx, id) + if err != nil { + return err + } + if app.Locked { + return fmt.Errorf("failed to delete OAuth2 application: application is locked: %s", app.ClientID) + } if err := deleteOAuth2Application(ctx, id, userid); err != nil { return err } From f1bd5a335f36d9414b29191148e1b3699b7fac06 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Fri, 4 Aug 2023 16:31:58 +0800 Subject: [PATCH 08/10] refactor --- custom/conf/app.example.ini | 4 +- .../config-cheat-sheet.en-us.md | 2 +- models/auth/oauth2.go | 110 ++++++++++-------- models/migrations/migrations.go | 2 - models/migrations/v1_21/v271.go | 15 --- modules/setting/oauth2.go | 2 +- modules/util/slice.go | 10 -- routers/web/admin/applications.go | 2 +- .../settings/applications_oauth2_list.tmpl | 5 +- 9 files changed, 70 insertions(+), 82 deletions(-) delete mode 100644 models/migrations/v1_21/v271.go diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index e7434c6c8b35f..cfaf91cddb7f2 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -546,7 +546,9 @@ ENABLE = true ;MAX_TOKEN_LENGTH = 32767 ;; ;; Pre-register OAuth2 applications for some universally useful services -;DEFAULT_APPLICATIONS = git-credential-oauth, Git Credential Manager +;; * https://github.com/hickford/git-credential-oauth +;; * https://github.com/git-ecosystem/git-credential-manager +;DEFAULT_APPLICATIONS = git-credential-oauth, git-credential-manager ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/docs/content/administration/config-cheat-sheet.en-us.md b/docs/content/administration/config-cheat-sheet.en-us.md index 4c197592ca5f6..71ae4f2e30bd2 100644 --- a/docs/content/administration/config-cheat-sheet.en-us.md +++ b/docs/content/administration/config-cheat-sheet.en-us.md @@ -1100,7 +1100,7 @@ This section only does "set" config, a removed config key from this section won' - `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`) - `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. - `MAX_TOKEN_LENGTH`: **32767**: Maximum length of token/cookie to accept from OAuth2 provider -- `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. +- `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. ## i18n (`i18n`) diff --git a/models/auth/oauth2.go b/models/auth/oauth2.go index 1ba7294572cbe..c61711101c7a4 100644 --- a/models/auth/oauth2.go +++ b/models/auth/oauth2.go @@ -13,6 +13,7 @@ import ( "strings" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" @@ -39,7 +40,6 @@ type OAuth2Application struct { RedirectURIs []string `xorm:"redirect_uris JSON TEXT"` CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` - Locked bool `xorm:"NOT NULL DEFAULT FALSE"` } func init() { @@ -48,65 +48,76 @@ func init() { db.RegisterModel(new(OAuth2Grant)) } +type BuiltinOAuth2Application struct { + ConfigName string + DisplayName string + RedirectURIs []string +} + +func BuiltinApplications() map[string]*BuiltinOAuth2Application { + m := make(map[string]*BuiltinOAuth2Application) + m["a4792ccc-144e-407e-86c9-5e7d8d9c3269"] = &BuiltinOAuth2Application{ + ConfigName: "git-credential-oauth", + DisplayName: "git-credential-oauth", + RedirectURIs: []string{"http://127.0.0.1", "https://127.0.0.1"}, + } + m["e90ee53c-94e2-48ac-9358-a874fb9e0662"] = &BuiltinOAuth2Application{ + ConfigName: "git-credential-manager", + DisplayName: "Git Credential Manager", + RedirectURIs: []string{"http://127.0.0.1", "https://127.0.0.1"}, + } + return m +} + func Init(ctx context.Context) error { - defaultApplications := []*OAuth2Application{ - { - Name: "git-credential-oauth", - ClientID: "a4792ccc-144e-407e-86c9-5e7d8d9c3269", - RedirectURIs: []string{"http://127.0.0.1", "https://127.0.0.1"}, - Locked: true, - }, - { - Name: "Git Credential Manager", - ClientID: "e90ee53c-94e2-48ac-9358-a874fb9e0662", - RedirectURIs: []string{"http://127.0.0.1", "https://127.0.0.1"}, - Locked: true, - }, - } - - var clientIDs []string - for _, app := range defaultApplications { - clientIDs = append(clientIDs, app.ClientID) - } - - var registeredOAuth2Apps []*OAuth2Application - if err := db.GetEngine(ctx).In("client_id", clientIDs).Find(®isteredOAuth2Apps); err != nil { - return err + builtinApps := BuiltinApplications() + var builtinAllClientIDs []string + for clientID := range builtinApps { + builtinAllClientIDs = append(builtinAllClientIDs, clientID) } - var enabledOAuth2Apps []*OAuth2Application - for _, entry := range setting.OAuth2.DefaultApplications { - app := util.SliceFind(defaultApplications, func(a *OAuth2Application) bool { return a.Name == entry }) - if app != -1 { - enabledOAuth2Apps = append(enabledOAuth2Apps, defaultApplications[app]) - } + var registeredApps []*OAuth2Application + if err := db.GetEngine(ctx).In("client_id", builtinAllClientIDs).Find(®isteredApps); err != nil { + return err } - var disabledOAuth2Apps []*OAuth2Application - for _, entry := range defaultApplications { - app := util.SliceFind(enabledOAuth2Apps, func(a *OAuth2Application) bool { return a.ClientID == entry.ClientID }) - registeredApp := util.SliceFind(registeredOAuth2Apps, func(a *OAuth2Application) bool { return a.ClientID == entry.ClientID }) - if app == -1 && registeredApp != -1 { - disabledOAuth2Apps = append(disabledOAuth2Apps, registeredOAuth2Apps[registeredApp]) + clientIDsToAdd := container.Set[string]{} + for _, configName := range setting.OAuth2.DefaultApplications { + found := false + for clientID, builtinApp := range builtinApps { + if builtinApp.ConfigName == configName { + clientIDsToAdd.Add(clientID) // add all user-configured apps to the "add" list + found = true + } + } + if !found { + return fmt.Errorf("unknown oauth2 application: %q", configName) } } - - var createOAuth2Apps []*OAuth2Application - for _, app := range enabledOAuth2Apps { - registeredApp := util.SliceFind(registeredOAuth2Apps, func(a *OAuth2Application) bool { return a.ClientID == app.ClientID }) - if registeredApp == -1 { - createOAuth2Apps = append(createOAuth2Apps, app) + clientIDsToDelete := container.Set[string]{} + for _, app := range registeredApps { + if !clientIDsToAdd.Contains(app.ClientID) { + clientIDsToDelete.Add(app.ClientID) // if a registered app is not in the "add" list, it should be deleted } } + for _, app := range registeredApps { + clientIDsToAdd.Remove(app.ClientID) // no need to re-add existing (registered) apps, so remove them from the set + } - if len(createOAuth2Apps) > 0 { - if err := db.Insert(ctx, createOAuth2Apps); err != nil { - return err + for _, app := range registeredApps { + if clientIDsToDelete.Contains(app.ClientID) { + if err := deleteOAuth2Application(ctx, app.ID, 0); err != nil { + return err + } } } - - for _, app := range disabledOAuth2Apps { - if err := deleteOAuth2Application(ctx, app.ID, 0); err != nil { + for clientID := range clientIDsToAdd { + builtinApp := builtinApps[clientID] + if err := db.Insert(ctx, &OAuth2Application{ + Name: builtinApp.DisplayName, + ClientID: clientID, + RedirectURIs: builtinApp.RedirectURIs, + }); err != nil { return err } } @@ -333,7 +344,8 @@ func DeleteOAuth2Application(id, userid int64) error { if err != nil { return err } - if app.Locked { + builtinApps := BuiltinApplications() + if _, builtin := builtinApps[app.ClientID]; builtin { return fmt.Errorf("failed to delete OAuth2 application: application is locked: %s", app.ClientID) } if err := deleteOAuth2Application(ctx, id, userid); err != nil { diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 0528168d85d5f..b2140a1eb1327 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -523,8 +523,6 @@ var migrations = []Migration{ NewMigration("Drop deleted branch table", v1_21.DropDeletedBranchTable), // v270 -> v271 NewMigration("Fix PackageProperty typo", v1_21.FixPackagePropertyTypo), - // v271 -> v272 - NewMigration("Add locked property to OAuth2 application", v1_21.OAuth2ApplicationAddLockedProperty), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v1_21/v271.go b/models/migrations/v1_21/v271.go deleted file mode 100644 index 0c9218ed0f69a..0000000000000 --- a/models/migrations/v1_21/v271.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package v1_21 //nolint - -import ( - "xorm.io/xorm" -) - -func OAuth2ApplicationAddLockedProperty(x *xorm.Engine) error { - type OAuth2Application struct { - Locked bool `xorm:"NOT NULL DEFAULT FALSE"` - } - return x.Sync(new(OAuth2Application)) -} diff --git a/modules/setting/oauth2.go b/modules/setting/oauth2.go index ff9e0bd703705..b88070681a7d8 100644 --- a/modules/setting/oauth2.go +++ b/modules/setting/oauth2.go @@ -109,7 +109,7 @@ var OAuth2 = struct { JWTSigningAlgorithm: "RS256", JWTSigningPrivateKeyFile: "jwt/private.pem", MaxTokenLength: math.MaxInt16, - DefaultApplications: []string{"git-credential-oauth", "Git Credential Manager"}, + DefaultApplications: []string{"git-credential-oauth", "git-credential-manager"}, } func loadOAuth2From(rootCfg ConfigProvider) { diff --git a/modules/util/slice.go b/modules/util/slice.go index 9fc3270236636..74356f5496205 100644 --- a/modules/util/slice.go +++ b/modules/util/slice.go @@ -88,13 +88,3 @@ func SliceRemoveAllFunc[T comparable](slice []T, targetFunc func(T) bool) []T { } return slice[:idx] } - -// SliceFind returns the element id of the first element that matches the condition, otherwise -1 -func SliceFind[T any](slice []T, condFunc func(T) bool) int { - for i, e := range slice { - if condFunc(e) { - return i - } - } - return -1 -} diff --git a/routers/web/admin/applications.go b/routers/web/admin/applications.go index 7b27524340374..b26912db48214 100644 --- a/routers/web/admin/applications.go +++ b/routers/web/admin/applications.go @@ -39,7 +39,7 @@ func Applications(ctx *context.Context) { return } ctx.Data["Applications"] = apps - + ctx.Data["BuiltinApplications"] = auth.BuiltinApplications() ctx.HTML(http.StatusOK, tplSettingsApplications) } diff --git a/templates/user/settings/applications_oauth2_list.tmpl b/templates/user/settings/applications_oauth2_list.tmpl index 9ecccab2aefba..f4f16063af53d 100644 --- a/templates/user/settings/applications_oauth2_list.tmpl +++ b/templates/user/settings/applications_oauth2_list.tmpl @@ -15,14 +15,15 @@ {{.ClientID}}
-
+ {{$isBuiltin := and $.BuiltinApplications (index $.BuiltinApplications .ClientID)}} +
{{svg "octicon-pencil" 16 "gt-mr-2"}} {{$.locale.Tr "settings.oauth2_application_edit"}} From 844f55cd9890f59b58b76122fd1bf9dd435eb08e Mon Sep 17 00:00:00 2001 From: Denys Konovalov Date: Tue, 8 Aug 2023 16:17:18 +0200 Subject: [PATCH 09/10] disallow editing builtin oauth2 applications --- models/auth/oauth2.go | 4 +++ options/locale/locale_en-US.ini | 3 ++- .../settings/applications_oauth2_list.tmpl | 27 ++++++++++--------- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/models/auth/oauth2.go b/models/auth/oauth2.go index c61711101c7a4..1b6d68879a97e 100644 --- a/models/auth/oauth2.go +++ b/models/auth/oauth2.go @@ -284,6 +284,10 @@ func UpdateOAuth2Application(opts UpdateOAuth2ApplicationOptions) (*OAuth2Applic if app.UID != opts.UserID { return nil, fmt.Errorf("UID mismatch") } + builtinApps := BuiltinApplications() + if _, builtin := builtinApps[app.ClientID]; builtin { + return nil, fmt.Errorf("failed to edit OAuth2 application: application is locked: %s", app.ClientID) + } app.Name = opts.Name app.RedirectURIs = opts.RedirectURIs diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 5f1a024183552..b1d42dd7c6fa6 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -93,6 +93,7 @@ edit = Edit enabled = Enabled disabled = Disabled +locked = Locked copy = Copy copy_url = Copy URL @@ -850,7 +851,7 @@ oauth2_client_secret_hint = The secret won't be visible if you revisit this page oauth2_application_edit = Edit oauth2_application_create_description = OAuth2 applications gives your third-party application access to user accounts on this instance. oauth2_application_remove_description = Removing an OAuth2 application will prevent it to access authorized user accounts on this instance. Continue? -oauth2_application_locked = Gitea pre-registers some OAuth2 applications on startup if enabled in config. To prevent unexpected bahavior, these can be edited, but not removed. Please refer to the OAuth2 documentation for more information. +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. authorized_oauth2_applications = Authorized OAuth2 Applications authorized_oauth2_applications_description = You've granted access to your personal Gitea account to these third party applications. Please revoke access for applications no longer needed. diff --git a/templates/user/settings/applications_oauth2_list.tmpl b/templates/user/settings/applications_oauth2_list.tmpl index f4f16063af53d..1a536e50ac0a7 100644 --- a/templates/user/settings/applications_oauth2_list.tmpl +++ b/templates/user/settings/applications_oauth2_list.tmpl @@ -4,7 +4,7 @@ {{.locale.Tr "settings.oauth2_application_create_description"}}
{{range .Applications}} -
+
{{svg "octicon-apps" 32}}
@@ -16,17 +16,20 @@
{{$isBuiltin := and $.BuiltinApplications (index $.BuiltinApplications .ClientID)}} -
- - {{svg "octicon-pencil" 16 "gt-mr-2"}} - {{$.locale.Tr "settings.oauth2_application_edit"}} - - +
+ {{if $isBuiltin}} + {{ctx.Locale.Tr "locked"}} + {{else}} + + {{svg "octicon-pencil" 16 "gt-mr-2"}} + {{$.locale.Tr "settings.oauth2_application_edit"}} + + + {{end}}
{{end}} From 8aa384658da9ea6b09a3781715f81f3e5b2e67ac Mon Sep 17 00:00:00 2001 From: Denys Konovalov Date: Tue, 8 Aug 2023 16:28:07 +0200 Subject: [PATCH 10/10] add oauth2 application description & client ID to docs --- docs/content/development/oauth2-provider.en-us.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/content/development/oauth2-provider.en-us.md b/docs/content/development/oauth2-provider.en-us.md index 07af8252c5284..81fc04bdcf1d0 100644 --- a/docs/content/development/oauth2-provider.en-us.md +++ b/docs/content/development/oauth2-provider.en-us.md @@ -80,12 +80,12 @@ Gitea token scopes are as follows: ## Pre-configured Applications -Gitea creates OAuth applications for the following services by default on startup: +Gitea creates OAuth applications for the following services by default on startup, as we assume that these are universally useful. -- [git-credential-oauth](https://github.com/hickford/git-credential-oauth) -- [Git Credential Manager](https://github.com/git-ecosystem/git-credential-manager) - -as we assume that these are universally useful. +|Application|Description|Client ID| +|-----------|-----------|---------| +|[git-credential-oauth](https://github.com/hickford/git-credential-oauth)|Git credential helper|`a4792ccc-144e-407e-86c9-5e7d8d9c3269`| +|[Git Credential Manager](https://github.com/git-ecosystem/git-credential-manager)|Git credential helper|`e90ee53c-94e2-48ac-9358-a874fb9e0662`| 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`.