From b2728cfd68987e1390b87b542ed25b39aa15c236 Mon Sep 17 00:00:00 2001 From: Henning Bopp Date: Fri, 19 Feb 2021 20:47:31 +0100 Subject: [PATCH 1/9] Introduce ability to update HashAlgo - Add Defaults in settings - Add Flag to Update Password on next use in settings - Create Crypto-Fallbacks in structs - Update Hashing Generation --- models/fixtures/user.yml | 18 +++- models/user.go | 174 +++++++++++++++++++++++++++++++++---- models/user_test.go | 64 +++++++++++++- modules/setting/setting.go | 30 +++++++ modules/structs/crypt.go | 67 ++++++++++++++ 5 files changed, 331 insertions(+), 22 deletions(-) create mode 100644 modules/structs/crypt.go diff --git a/models/fixtures/user.yml b/models/fixtures/user.yml index d903a7942f81b..1e8bc116eabb7 100644 --- a/models/fixtures/user.yml +++ b/models/fixtures/user.yml @@ -508,7 +508,6 @@ num_repos: 0 is_active: true - - id: 30 lower_name: user30 @@ -525,3 +524,20 @@ avatar_email: user30@example.com num_repos: 2 is_active: true + +- + id: 31 + lower_name: user31 + name: user31 + full_name: User ThirtyOne + email: user31@example.com + passwd_hash_algo: argon2 + passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" + type: 0 # individual + salt: ZogKvWdyEx + is_admin: false + is_restricted: true + avatar: avatar29 + avatar_email: user31@example.com + num_repos: 0 + is_active: true diff --git a/models/user.go b/models/user.go index 495fed1ff4d03..78b404af1c3f5 100644 --- a/models/user.go +++ b/models/user.go @@ -17,6 +17,7 @@ import ( "os" "path/filepath" "regexp" + "strconv" "strings" "time" "unicode/utf8" @@ -52,10 +53,13 @@ const ( ) const ( - algoBcrypt = "bcrypt" - algoScrypt = "scrypt" - algoArgon2 = "argon2" - algoPbkdf2 = "pbkdf2" + algoBcrypt = "bcrypt" + algoScrypt = "scrypt" + formatScrypt = "$%s$%d$%d$%d$%d$%x" + algoArgon2 = "argon2" + formatArgon2 = "$%s$%d$%d$%d$%d$%x" + algoPbkdf2 = "pbkdf2" + formatPbkdf2 = "$%s$%d$%d$%x" // EmailNotificationsEnabled indicates that the user would like to receive all email notifications EmailNotificationsEnabled = "enabled" @@ -374,24 +378,137 @@ func (u *User) NewGitSig() *git.Signature { } } -func hashPassword(passwd, salt, algo string) string { +func hashPasswordBcrypt(passwd string, params structs.CryptBCrypt) (string, string) { var tempPasswd []byte + tempPasswd, _ = bcrypt.GenerateFromPassword([]byte(passwd), params.Cost) + return string(tempPasswd), + string(tempPasswd) +} +func hashPasswordScrypt(passwd string, salt string, params structs.CryptSCrypt) (string, string) { + var tempPasswd []byte + tempPasswd, _ = scrypt.Key([]byte(passwd), []byte(salt), params.N, params.R, params.P, params.KeyLength) + return fmt.Sprintf(formatScrypt, algoScrypt, params.N, params.R, params.P, params.KeyLength, tempPasswd), + fmt.Sprintf("%x", tempPasswd) +} +func hashPasswordArgon2(passwd string, salt string, params structs.CryptArgon2) (string, string) { + var tempPasswd = argon2.IDKey([]byte(passwd), []byte(salt), params.Iterations, params.Memory, params.Parallelism, params.KeyLength) + return fmt.Sprintf(formatArgon2, algoArgon2, params.Memory, params.Iterations, params.Parallelism, params.KeyLength, tempPasswd), + fmt.Sprintf("%x", tempPasswd) +} +func hashPasswordPbkdf2(passwd string, salt string, params structs.CryptPbkdf2) (string, string) { + var tempPasswd = pbkdf2.Key([]byte(passwd), []byte(salt), params.Iterations, params.KeyLength, sha256.New) + return fmt.Sprintf(formatPbkdf2, algoPbkdf2, params.Iterations, params.KeyLength, tempPasswd), + fmt.Sprintf("%x", tempPasswd) +} +func hashPasswordWithConfig(passwd, salt, algo string) (string, string) { switch algo { case algoBcrypt: - tempPasswd, _ = bcrypt.GenerateFromPassword([]byte(passwd), bcrypt.DefaultCost) - return string(tempPasswd) + return hashPasswordBcrypt(passwd, setting.BCryptParams) case algoScrypt: - tempPasswd, _ = scrypt.Key([]byte(passwd), []byte(salt), 65536, 16, 2, 50) + return hashPasswordScrypt(passwd, salt, setting.SCryptParams) case algoArgon2: - tempPasswd = argon2.IDKey([]byte(passwd), []byte(salt), 2, 65536, 8, 50) + return hashPasswordArgon2(passwd, salt, setting.Argon2Params) case algoPbkdf2: fallthrough default: - tempPasswd = pbkdf2.Key([]byte(passwd), []byte(salt), 10000, 50, sha256.New) + return hashPasswordPbkdf2(passwd, salt, setting.Pbkdf2Params) } +} +func hashPasswordWithFallbackDefaults(passwd, salt, algo string) (string, string) { + switch algo { + case algoBcrypt: + return hashPasswordBcrypt(passwd, structs.BCryptFallback) + case algoScrypt: + return hashPasswordScrypt(passwd, salt, structs.SCryptFallback) + case algoArgon2: + return hashPasswordArgon2(passwd, salt, structs.Argon2Fallback) + case algoPbkdf2: + fallthrough + default: + return hashPasswordPbkdf2(passwd, salt, structs.Pbkdf2Fallback) + } +} + +func hashPasswordWithCurrentDefaults(passwd string, u *User) (string, string, bool) { + currentPasswd := u.Passwd + salt := u.Salt + algo := u.PasswdHashAlgo + split := strings.Split(currentPasswd, "$") + + var hash string + var matchesActiveSettings bool + + switch algo { + case algoBcrypt: + cost, _ := strconv.Atoi(split[2]) + + params := structs.CryptBCrypt{ + Cost: cost, + } + matchesActiveSettings = u.PasswdHashAlgo == algo && setting.BCryptParams == params + hash, _ = hashPasswordBcrypt(passwd, params) + + case algoScrypt: + var n, r, p, keyLength int + + n, _ = strconv.Atoi(split[2]) + r, _ = strconv.Atoi(split[3]) + p, _ = strconv.Atoi(split[4]) + keyLength, _ = strconv.Atoi(split[5]) + + params := structs.CryptSCrypt{ + N: n, + R: r, + P: p, + KeyLength: keyLength, + } + matchesActiveSettings = u.PasswdHashAlgo == algo && setting.SCryptParams == params + hash, _ = hashPasswordScrypt(passwd, salt, params) - return fmt.Sprintf("%x", tempPasswd) + case algoArgon2: + var iterations, memory, keyLength uint32 + var parallelism uint8 + var tempInt int + + tempInt, _ = strconv.Atoi(split[2]) + memory = uint32(tempInt) + + tempInt, _ = strconv.Atoi(split[3]) + iterations = uint32(tempInt) + + tempInt, _ = strconv.Atoi(split[4]) + parallelism = uint8(tempInt) + + tempInt, _ = strconv.Atoi(split[5]) + keyLength = uint32(tempInt) + + params := structs.CryptArgon2{ + Iterations: iterations, + Memory: memory, + Parallelism: parallelism, + KeyLength: keyLength, + } + matchesActiveSettings = u.PasswdHashAlgo == algo && setting.Argon2Params == params + hash, _ = hashPasswordArgon2(passwd, salt, params) + + case algoPbkdf2: + fallthrough + default: + var iterations, keyLength int + + iterations, _ = strconv.Atoi(split[2]) + keyLength, _ = strconv.Atoi(split[3]) + + params := structs.CryptPbkdf2{ + Iterations: iterations, + KeyLength: keyLength, + } + matchesActiveSettings = u.PasswdHashAlgo == algo && setting.Pbkdf2Params == params + hash, _ = hashPasswordPbkdf2(passwd, salt, params) + } + + return hash, algo, matchesActiveSettings } // SetPassword hashes a password using the algorithm defined in the config value of PASSWORD_HASH_ALGO @@ -407,23 +524,42 @@ func (u *User) SetPassword(passwd string) (err error) { if u.Salt, err = GetUserSalt(); err != nil { return err } + + // Force algo Update u.PasswdHashAlgo = setting.PasswordHashAlgo - u.Passwd = hashPassword(passwd, u.Salt, setting.PasswordHashAlgo) + u.Passwd, _ = hashPasswordWithConfig(passwd, u.Salt, u.PasswdHashAlgo) return nil } -// ValidatePassword checks if given password matches the one belongs to the user. +// ValidatePassword checks if given password matches the one belonging to the user. func (u *User) ValidatePassword(passwd string) bool { - tempHash := hashPassword(passwd, u.Salt, u.PasswdHashAlgo) + var tempHash, algo string + var matchesSettings bool + if u.Passwd[:1] == "$" { + // Hash with known settings + tempHash, algo, matchesSettings = hashPasswordWithCurrentDefaults(passwd, u) + } else { + // Hash with defaults + algo = u.PasswdHashAlgo + matchesSettings = false // always false, because new password format should be enforced + _, tempHash = hashPasswordWithFallbackDefaults(passwd, u.Salt, u.PasswdHashAlgo) + } - if u.PasswdHashAlgo != algoBcrypt && subtle.ConstantTimeCompare([]byte(u.Passwd), []byte(tempHash)) == 1 { - return true + matches := false + if algo != algoBcrypt && subtle.ConstantTimeCompare([]byte(u.Passwd), []byte(tempHash)) == 1 { + matches = true } - if u.PasswdHashAlgo == algoBcrypt && bcrypt.CompareHashAndPassword([]byte(u.Passwd), []byte(passwd)) == nil { - return true + if algo == algoBcrypt && bcrypt.CompareHashAndPassword([]byte(u.Passwd), []byte(passwd)) == nil { + matches = true } - return false + + if matches && setting.PasswordUpdateAlgoToCurrent && (!matchesSettings || algo != setting.PasswordHashAlgo) { + // @todo: need to handle this error? As far as I can see, passwd is always set at this point... + _ = u.SetPassword(passwd) + } + + return matches } // IsPasswordSet checks if the password is set or left empty diff --git a/models/user_test.go b/models/user_test.go index ac40015969aed..089a33af61251 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -136,13 +136,13 @@ func TestSearchUsers(t *testing.T) { } testUserSuccess(&SearchUserOptions{OrderBy: "id ASC", ListOptions: ListOptions{Page: 1}}, - []int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30}) + []int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30, 31}) testUserSuccess(&SearchUserOptions{ListOptions: ListOptions{Page: 1}, IsActive: util.OptionalBoolFalse}, []int64{9}) testUserSuccess(&SearchUserOptions{OrderBy: "id ASC", ListOptions: ListOptions{Page: 1}, IsActive: util.OptionalBoolTrue}, - []int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 28, 29, 30}) + []int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 28, 29, 30, 31}) testUserSuccess(&SearchUserOptions{Keyword: "user1", OrderBy: "id ASC", ListOptions: ListOptions{Page: 1}, IsActive: util.OptionalBoolTrue}, []int64{1, 10, 11, 12, 13, 14, 15, 16, 18}) @@ -243,6 +243,66 @@ func TestHashPasswordDeterministic(t *testing.T) { } } +func TestOldPasswordMatchAndUpdate(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + u := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) + + setting.PasswordHashAlgo = algoArgon2 + + matchingPass := "password" + oldPass := u.Passwd + + // Without update function + setting.PasswordUpdateAlgoToCurrent = false + validates := u.ValidatePassword(matchingPass) + newPass := u.Passwd + // Should match with old algo + assert.True(t, validates) + // Should not be updated to new format + assert.Equal(t, oldPass, newPass) + + // With update function + setting.PasswordUpdateAlgoToCurrent = true + validates = u.ValidatePassword(matchingPass) + newPass = u.Passwd + + // Should match with old algo + assert.True(t, validates) + // Should be updated to new format + assert.NotEqual(t, oldPass, newPass) +} + +func TestNewPasswordMatch(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + u := AssertExistsAndLoadBean(t, &User{ID: 31}).(*User) + + setting.PasswordHashAlgo = algoArgon2 + + matchingPass := "password" + oldPass := u.Passwd + + // Without update function + setting.PasswordUpdateAlgoToCurrent = false + validates := u.ValidatePassword(matchingPass) + newPass := u.Passwd + + // Should match + assert.True(t, validates) + // Should not be updated + assert.Equal(t, oldPass, newPass) + + // With update function and different default HashAlgo + setting.PasswordUpdateAlgoToCurrent = true + setting.PasswordHashAlgo = algoBcrypt + passwordMatches := u.ValidatePassword(matchingPass) + newPass = u.Passwd + + // Should match + assert.True(t, passwordMatches) + // Should not be updated + assert.NotEqual(t, oldPass, newPass) +} + func BenchmarkHashPassword(b *testing.B) { // BenchmarkHashPassword ensures that it takes a reasonable amount of time // to hash a password - in order to protect from brute-force attacks. diff --git a/modules/setting/setting.go b/modules/setting/setting.go index be7ec16e10cc1..f2dd4e3f415a2 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -25,11 +25,13 @@ import ( "code.gitea.io/gitea/modules/generate" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/user" "code.gitea.io/gitea/modules/util" shellquote "github.com/kballard/go-shellquote" "github.com/unknwon/com" + "golang.org/x/crypto/bcrypt" gossh "golang.org/x/crypto/ssh" ini "gopkg.in/ini.v1" ) @@ -161,6 +163,34 @@ var ( PasswordComplexity []string PasswordHashAlgo string PasswordCheckPwn bool + PasswordUpdateAlgoToCurrent bool + + // BCryptParams stores parameters for bcrypt algo + BCryptParams = structs.CryptBCrypt{ + Cost: bcrypt.DefaultCost, + } + + // SCryptParams stores parameters for scrypt algo + SCryptParams = structs.CryptSCrypt{ + N: 65536, + R: 16, + P: 2, + KeyLength: 50, + } + + // Argon2Params stores params for argon2 algo + Argon2Params = structs.CryptArgon2{ + Iterations: 2, + Memory: 65536, + Parallelism: 8, + KeyLength: 50, + } + + // Pbkdf2Params stores parameters for pbkdf2 algo + Pbkdf2Params = structs.CryptPbkdf2{ + Iterations: 10000, + KeyLength: 50, + } // UI settings UI = struct { diff --git a/modules/structs/crypt.go b/modules/structs/crypt.go new file mode 100644 index 0000000000000..f9ebe94ac8677 --- /dev/null +++ b/modules/structs/crypt.go @@ -0,0 +1,67 @@ +// Copyright 2021 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package structs + +// @todo: are the swagger models needed? + +// CryptBCrypt represents a BCrypt parameter set +// swagger:model +type CryptBCrypt struct { + Cost int +} + +// CryptSCrypt represents a SCrypt parameter set +// swagger:model +type CryptSCrypt struct { + N int + R int + P int + KeyLength int +} + +// CryptArgon2 represents a Argon2 parameter set +// swagger:model +type CryptArgon2 struct { + Iterations uint32 + Memory uint32 + Parallelism uint8 + KeyLength uint32 +} + +// CryptPbkdf2 represents a Pbkdf2 parameter set +// swagger:model +type CryptPbkdf2 struct { + Iterations int + KeyLength int +} + +var ( + // BCryptFallback stores parameters for bcrypt algo + BCryptFallback = CryptBCrypt{ + Cost: 10, + } + + // SCryptFallback stores parameters for scrypt algo + SCryptFallback = CryptSCrypt{ + N: 65536, + R: 16, + P: 2, + KeyLength: 50, + } + + // Argon2Fallback stores params for argon2 algo + Argon2Fallback = CryptArgon2{ + Iterations: 2, + Memory: 65536, + Parallelism: 8, + KeyLength: 50, + } + + // Pbkdf2Fallback stores parameters for pbkdf2 algo + Pbkdf2Fallback = CryptPbkdf2{ + Iterations: 10000, + KeyLength: 50, + } +) From f2207f10207b236d330cf25ab56284db1d168261 Mon Sep 17 00:00:00 2001 From: Henning Bopp Date: Sun, 21 Feb 2021 01:44:23 +0100 Subject: [PATCH 2/9] Update ability to better password hash handing - Introduce hashing module with 4 submodules, one for each hashing algo - Migrate User table: - Update Password format - Remove HashAlgo field - Remove all usages of passwdHashAlgo field - Update config file - Update all usages of password verification - Update the detection of password resetting need - Set defaults if hashing conf is n/a in config file - Update user fixture to mirror changes - Append user in fixture with not-default password setting - Update test for the changes above --- cmd/admin.go | 2 +- custom/conf/app.example.ini | 19 ++++ models/fixtures/user.yml | 94 ++++++---------- models/login_source.go | 6 +- models/migrations/migrations.go | 3 + models/migrations/v172.go | 87 +++++++++++++++ models/user.go | 184 +------------------------------- models/user_test.go | 55 +++------- modules/auth/db/hash.go | 67 ++++++++++++ modules/auth/db/hash_argon2.go | 88 +++++++++++++++ modules/auth/db/hash_bcrypt.go | 56 ++++++++++ modules/auth/db/hash_pbkdf2.go | 68 ++++++++++++ modules/auth/db/hash_scrypt.go | 77 +++++++++++++ modules/setting/setting.go | 55 +++++----- modules/structs/crypt.go | 67 ------------ routers/user/auth.go | 4 +- routers/user/setting/account.go | 2 +- 17 files changed, 551 insertions(+), 383 deletions(-) create mode 100644 models/migrations/v172.go create mode 100644 modules/auth/db/hash.go create mode 100644 modules/auth/db/hash_argon2.go create mode 100644 modules/auth/db/hash_bcrypt.go create mode 100644 modules/auth/db/hash_pbkdf2.go create mode 100644 modules/auth/db/hash_scrypt.go delete mode 100644 modules/structs/crypt.go diff --git a/cmd/admin.go b/cmd/admin.go index 3ddf8eddf9e6c..5fc99f7b86b9d 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -353,7 +353,7 @@ func runChangePassword(c *cli.Context) error { return err } - if err = models.UpdateUserCols(user, "passwd", "passwd_hash_algo", "salt"); err != nil { + if err = models.UpdateUserCols(user, "passwd", "salt"); err != nil { return err } diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 276b3cb5e8097..df8d6e0facfe4 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -571,6 +571,25 @@ CSRF_COOKIE_HTTP_ONLY = true ; Validate against https://haveibeenpwned.com/Passwords to see if a password has been exposed PASSWORD_CHECK_PWN = false +[security.hash] +BCRYPT_COST = 10 + +; Parameters for scrypt +SCRYPT_N = 65536 +SCRYPT_R = 16 +SCRYPT_P = 2 +SCRYPT_KEY_LENGTH = 50 + +; Parameters for Argon2id +ARGON2_ITERATIONS = 2 +ARGON2_MEMORY = 65536 +ARGON2_PARALLELISM = 8 +ARGON2_KEY_LENGTH = 50 + +; Parameters for Pbkdf2 +PBKDF2_ITERATIONS = 10000 +PBKDF2_KEY_LENGTH = 50 + [openid] ; ; OpenID is an open, standard and decentralized authentication protocol. diff --git a/models/fixtures/user.yml b/models/fixtures/user.yml index 1e8bc116eabb7..c424a8d24f94b 100644 --- a/models/fixtures/user.yml +++ b/models/fixtures/user.yml @@ -7,8 +7,7 @@ full_name: User One email: user1@example.com email_notifications_preference: enabled - passwd_hash_algo: argon2 - passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password + passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password type: 0 # individual salt: ZogKvWdyEx is_admin: true @@ -25,8 +24,7 @@ email: user2@example.com keep_email_private: true email_notifications_preference: enabled - passwd_hash_algo: argon2 - passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password + passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -45,8 +43,7 @@ full_name: " <<<< >> >> > >> > >>> >> " email: user3@example.com email_notifications_preference: onmention - passwd_hash_algo: argon2 - passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password + passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password type: 1 # organization salt: ZogKvWdyEx is_admin: false @@ -63,8 +60,7 @@ full_name: " " email: user4@example.com email_notifications_preference: onmention - passwd_hash_algo: argon2 - passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password + passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -81,8 +77,7 @@ full_name: User Five email: user5@example.com email_notifications_preference: enabled - passwd_hash_algo: argon2 - passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password + passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -100,8 +95,7 @@ full_name: User Six email: user6@example.com email_notifications_preference: enabled - passwd_hash_algo: argon2 - passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password + passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password type: 1 # organization salt: ZogKvWdyEx is_admin: false @@ -118,8 +112,7 @@ full_name: User Seven email: user7@example.com email_notifications_preference: disabled - passwd_hash_algo: argon2 - passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password + passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password type: 1 # organization salt: ZogKvWdyEx is_admin: false @@ -136,8 +129,7 @@ full_name: User Eight email: user8@example.com email_notifications_preference: enabled - passwd_hash_algo: argon2 - passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password + passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -155,8 +147,7 @@ full_name: User Nine email: user9@example.com email_notifications_preference: onmention - passwd_hash_algo: argon2 - passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password + passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -171,8 +162,7 @@ name: user10 full_name: User Ten email: user10@example.com - passwd_hash_algo: argon2 - passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password + passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -187,8 +177,7 @@ name: user11 full_name: User Eleven email: user11@example.com - passwd_hash_algo: argon2 - passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password + passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -203,8 +192,7 @@ name: user12 full_name: User 12 email: user12@example.com - passwd_hash_algo: argon2 - passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password + passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -219,8 +207,7 @@ name: user13 full_name: User 13 email: user13@example.com - passwd_hash_algo: argon2 - passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password + passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -235,8 +222,7 @@ name: user14 full_name: User 14 email: user14@example.com - passwd_hash_algo: argon2 - passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password + passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -251,8 +237,7 @@ name: user15 full_name: User 15 email: user15@example.com - passwd_hash_algo: argon2 - passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password + passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -267,8 +252,7 @@ name: user16 full_name: User 16 email: user16@example.com - passwd_hash_algo: argon2 - passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password + passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -283,8 +267,7 @@ name: user17 full_name: User 17 email: user17@example.com - passwd_hash_algo: argon2 - passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password + passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password type: 1 # organization salt: ZogKvWdyEx is_admin: false @@ -301,8 +284,7 @@ name: user18 full_name: User 18 email: user18@example.com - passwd_hash_algo: argon2 - passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password + passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -317,8 +299,7 @@ name: user19 full_name: User 19 email: user19@example.com - passwd_hash_algo: argon2 - passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password + passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password type: 1 # organization salt: ZogKvWdyEx is_admin: false @@ -335,8 +316,7 @@ name: user20 full_name: User 20 email: user20@example.com - passwd_hash_algo: argon2 - passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password + passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -351,8 +331,7 @@ name: user21 full_name: User 21 email: user21@example.com - passwd_hash_algo: argon2 - passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password + passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -367,8 +346,7 @@ name: limited_org full_name: Limited Org email: limited_org@example.com - passwd_hash_algo: argon2 - passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password + passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password type: 1 # organization salt: ZogKvWdyEx is_admin: false @@ -386,8 +364,7 @@ name: privated_org full_name: Privated Org email: privated_org@example.com - passwd_hash_algo: argon2 - passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password + passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password type: 1 # organization salt: ZogKvWdyEx is_admin: false @@ -406,8 +383,7 @@ full_name: "user24" email: user24@example.com keep_email_private: true - passwd_hash_algo: argon2 - passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password + passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -425,8 +401,7 @@ name: org25 full_name: "org25" email: org25@example.com - passwd_hash_algo: argon2 - passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password + passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password type: 1 # organization salt: ZogKvWdyEx is_admin: false @@ -443,8 +418,7 @@ full_name: "Org26" email: org26@example.com email_notifications_preference: onmention - passwd_hash_algo: argon2 - passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password + passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password type: 1 # organization salt: ZogKvWdyEx is_admin: false @@ -462,8 +436,7 @@ full_name: User Twenty-Seven email: user27@example.com email_notifications_preference: enabled - passwd_hash_algo: argon2 - passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password + passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -478,8 +451,7 @@ full_name: "user27" email: user28@example.com keep_email_private: true - passwd_hash_algo: argon2 - passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password + passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -497,8 +469,7 @@ name: user29 full_name: User 29 email: user29@example.com - passwd_hash_algo: argon2 - passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password + passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -514,8 +485,7 @@ name: user30 full_name: User Thirty email: user30@example.com - passwd_hash_algo: argon2 - passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password + passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -531,11 +501,11 @@ name: user31 full_name: User ThirtyOne email: user31@example.com - passwd_hash_algo: argon2 - passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" + passwd: "$argon2$2$65536$12$50$1d2304e48d92db2fed37bdb4b3e7fca97fc41f2454b32de343007f0acb4623f04b07a477759fa83df487d12310a2c4c1ab25" # password type: 0 # individual salt: ZogKvWdyEx is_admin: false + login_type: 1 is_restricted: true avatar: avatar29 avatar_email: user31@example.com diff --git a/models/login_source.go b/models/login_source.go index d351f12861f79..81bf298a658a3 100644 --- a/models/login_source.go +++ b/models/login_source.go @@ -6,6 +6,7 @@ package models import ( + "code.gitea.io/gitea/modules/auth/db" "crypto/tls" "encoding/json" "errors" @@ -19,7 +20,6 @@ import ( "code.gitea.io/gitea/modules/auth/oauth2" "code.gitea.io/gitea/modules/auth/pam" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" @@ -770,11 +770,11 @@ func UserSignIn(username, password string) (*User, error) { if user.IsPasswordSet() && user.ValidatePassword(password) { // Update password hash if server password hash algorithm have changed - if user.PasswdHashAlgo != setting.PasswordHashAlgo { + if db.DefaultHasher.PasswordNeedUpdate(user.Passwd) { if err = user.SetPassword(password); err != nil { return nil, err } - if err = UpdateUserCols(user, "passwd", "passwd_hash_algo", "salt"); err != nil { + if err = UpdateUserCols(user, "passwd", "salt"); err != nil { return nil, err } } diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 66a44a27f8973..8cdcc3621da22 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -290,6 +290,9 @@ var migrations = []Migration{ NewMigration("Add Dismissed to Review table", addDismissedReviewColumn), // v171 -> v172 NewMigration("Add Sorting to ProjectBoard table", addSortingColToProjectBoard), + // v172 -> v173 + NewMigration("Update User passwords to new format", updateUserPasswords), + NewMigration("Remove User passwordHashAlgo field", removeUserHashAlgo), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v172.go b/models/migrations/v172.go new file mode 100644 index 0000000000000..4575590618354 --- /dev/null +++ b/models/migrations/v172.go @@ -0,0 +1,87 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package migrations + +import ( + "xorm.io/builder" + "xorm.io/xorm" +) + +func removeUserHashAlgo(x *xorm.Engine) (err error) { + // Make sure the columns exist before dropping them + type User struct { + passwdHashAlgo string + } + if err := x.Sync2(new(User)); err != nil { + return err + } + + sess := x.NewSession() + defer sess.Close() + if err := sess.Begin(); err != nil { + return err + } + if err := dropTableColumns(sess, "user", "passwdHashAlgo"); err != nil { + return err + } + return sess.Commit() +} +func updateUserPasswords(x *xorm.Engine) (err error) { + const ( + algoBcrypt = "bcrypt" + algoScrypt = "scrypt" + algoArgon2 = "argon2" + algoPbkdf2 = "pbkdf2" + ) + + type User struct { + ID int64 `xorm:"pk autoincr"` + Passwd string `xorm:"NOT NULL"` + PasswdHashAlgo string `xorm:"NOT NULL DEFAULT 'argon2'"` + } + + sess := x.NewSession() + defer sess.Close() + + const batchSize = 500 + + for start := 0; ; start += batchSize { + users := make([]*User, 0, batchSize) + if err = sess.Limit(batchSize, start).Where(builder.And(builder.Neq{"passwd": ""}, builder.Neq{"passwd_hash_algo": ""}), 0).Find(&users); err != nil { + return + } + if len(users) == 0 { + break + } + + if err = sess.Begin(); err != nil { + return + } + + for _, user := range users { + switch user.PasswdHashAlgo { + case algoBcrypt: + user.Passwd = "$bcrypt$" + user.Passwd + case algoScrypt: + user.Passwd = "$scrypt$65536$16$2$50" + user.Passwd + case algoArgon2: + user.Passwd = "$argon2$2$65536$8$50$" + user.Passwd + case algoPbkdf2: + fallthrough + default: + user.Passwd = "$pbkdf2$10000$50$" + user.Passwd + } + if _, err = sess.ID(user.ID).Cols("passwd").Update(user); err != nil { + return err + } + } + + if err = sess.Commit(); err != nil { + return + } + } + + return sess.Commit() +} diff --git a/models/user.go b/models/user.go index 78b404af1c3f5..aee49d8aca945 100644 --- a/models/user.go +++ b/models/user.go @@ -6,10 +6,9 @@ package models import ( + "code.gitea.io/gitea/modules/auth/db" "container/list" "context" - "crypto/sha256" - "crypto/subtle" "encoding/hex" "errors" "fmt" @@ -17,7 +16,6 @@ import ( "os" "path/filepath" "regexp" - "strconv" "strings" "time" "unicode/utf8" @@ -33,10 +31,6 @@ import ( "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" - "golang.org/x/crypto/argon2" - "golang.org/x/crypto/bcrypt" - "golang.org/x/crypto/pbkdf2" - "golang.org/x/crypto/scrypt" "golang.org/x/crypto/ssh" "xorm.io/builder" ) @@ -53,14 +47,6 @@ const ( ) const ( - algoBcrypt = "bcrypt" - algoScrypt = "scrypt" - formatScrypt = "$%s$%d$%d$%d$%d$%x" - algoArgon2 = "argon2" - formatArgon2 = "$%s$%d$%d$%d$%d$%x" - algoPbkdf2 = "pbkdf2" - formatPbkdf2 = "$%s$%d$%d$%x" - // EmailNotificationsEnabled indicates that the user would like to receive all email notifications EmailNotificationsEnabled = "enabled" // EmailNotificationsOnMention indicates that the user would like to be notified via email when mentioned. @@ -103,7 +89,6 @@ type User struct { KeepEmailPrivate bool EmailNotificationsPreference string `xorm:"VARCHAR(20) NOT NULL DEFAULT 'enabled'"` Passwd string `xorm:"NOT NULL"` - PasswdHashAlgo string `xorm:"NOT NULL DEFAULT 'argon2'"` // MustChangePassword is an attribute that determines if a user // is to change his/her password after registration. @@ -378,146 +363,12 @@ func (u *User) NewGitSig() *git.Signature { } } -func hashPasswordBcrypt(passwd string, params structs.CryptBCrypt) (string, string) { - var tempPasswd []byte - tempPasswd, _ = bcrypt.GenerateFromPassword([]byte(passwd), params.Cost) - return string(tempPasswd), - string(tempPasswd) -} -func hashPasswordScrypt(passwd string, salt string, params structs.CryptSCrypt) (string, string) { - var tempPasswd []byte - tempPasswd, _ = scrypt.Key([]byte(passwd), []byte(salt), params.N, params.R, params.P, params.KeyLength) - return fmt.Sprintf(formatScrypt, algoScrypt, params.N, params.R, params.P, params.KeyLength, tempPasswd), - fmt.Sprintf("%x", tempPasswd) -} -func hashPasswordArgon2(passwd string, salt string, params structs.CryptArgon2) (string, string) { - var tempPasswd = argon2.IDKey([]byte(passwd), []byte(salt), params.Iterations, params.Memory, params.Parallelism, params.KeyLength) - return fmt.Sprintf(formatArgon2, algoArgon2, params.Memory, params.Iterations, params.Parallelism, params.KeyLength, tempPasswd), - fmt.Sprintf("%x", tempPasswd) -} -func hashPasswordPbkdf2(passwd string, salt string, params structs.CryptPbkdf2) (string, string) { - var tempPasswd = pbkdf2.Key([]byte(passwd), []byte(salt), params.Iterations, params.KeyLength, sha256.New) - return fmt.Sprintf(formatPbkdf2, algoPbkdf2, params.Iterations, params.KeyLength, tempPasswd), - fmt.Sprintf("%x", tempPasswd) -} - -func hashPasswordWithConfig(passwd, salt, algo string) (string, string) { - switch algo { - case algoBcrypt: - return hashPasswordBcrypt(passwd, setting.BCryptParams) - case algoScrypt: - return hashPasswordScrypt(passwd, salt, setting.SCryptParams) - case algoArgon2: - return hashPasswordArgon2(passwd, salt, setting.Argon2Params) - case algoPbkdf2: - fallthrough - default: - return hashPasswordPbkdf2(passwd, salt, setting.Pbkdf2Params) - } -} -func hashPasswordWithFallbackDefaults(passwd, salt, algo string) (string, string) { - switch algo { - case algoBcrypt: - return hashPasswordBcrypt(passwd, structs.BCryptFallback) - case algoScrypt: - return hashPasswordScrypt(passwd, salt, structs.SCryptFallback) - case algoArgon2: - return hashPasswordArgon2(passwd, salt, structs.Argon2Fallback) - case algoPbkdf2: - fallthrough - default: - return hashPasswordPbkdf2(passwd, salt, structs.Pbkdf2Fallback) - } -} - -func hashPasswordWithCurrentDefaults(passwd string, u *User) (string, string, bool) { - currentPasswd := u.Passwd - salt := u.Salt - algo := u.PasswdHashAlgo - split := strings.Split(currentPasswd, "$") - - var hash string - var matchesActiveSettings bool - - switch algo { - case algoBcrypt: - cost, _ := strconv.Atoi(split[2]) - - params := structs.CryptBCrypt{ - Cost: cost, - } - matchesActiveSettings = u.PasswdHashAlgo == algo && setting.BCryptParams == params - hash, _ = hashPasswordBcrypt(passwd, params) - - case algoScrypt: - var n, r, p, keyLength int - - n, _ = strconv.Atoi(split[2]) - r, _ = strconv.Atoi(split[3]) - p, _ = strconv.Atoi(split[4]) - keyLength, _ = strconv.Atoi(split[5]) - - params := structs.CryptSCrypt{ - N: n, - R: r, - P: p, - KeyLength: keyLength, - } - matchesActiveSettings = u.PasswdHashAlgo == algo && setting.SCryptParams == params - hash, _ = hashPasswordScrypt(passwd, salt, params) - - case algoArgon2: - var iterations, memory, keyLength uint32 - var parallelism uint8 - var tempInt int - - tempInt, _ = strconv.Atoi(split[2]) - memory = uint32(tempInt) - - tempInt, _ = strconv.Atoi(split[3]) - iterations = uint32(tempInt) - - tempInt, _ = strconv.Atoi(split[4]) - parallelism = uint8(tempInt) - - tempInt, _ = strconv.Atoi(split[5]) - keyLength = uint32(tempInt) - - params := structs.CryptArgon2{ - Iterations: iterations, - Memory: memory, - Parallelism: parallelism, - KeyLength: keyLength, - } - matchesActiveSettings = u.PasswdHashAlgo == algo && setting.Argon2Params == params - hash, _ = hashPasswordArgon2(passwd, salt, params) - - case algoPbkdf2: - fallthrough - default: - var iterations, keyLength int - - iterations, _ = strconv.Atoi(split[2]) - keyLength, _ = strconv.Atoi(split[3]) - - params := structs.CryptPbkdf2{ - Iterations: iterations, - KeyLength: keyLength, - } - matchesActiveSettings = u.PasswdHashAlgo == algo && setting.Pbkdf2Params == params - hash, _ = hashPasswordPbkdf2(passwd, salt, params) - } - - return hash, algo, matchesActiveSettings -} - // SetPassword hashes a password using the algorithm defined in the config value of PASSWORD_HASH_ALGO -// change passwd, salt and passwd_hash_algo fields +// change passwd and salt fields func (u *User) SetPassword(passwd string) (err error) { if len(passwd) == 0 { u.Passwd = "" u.Salt = "" - u.PasswdHashAlgo = "" return nil } @@ -525,41 +376,14 @@ func (u *User) SetPassword(passwd string) (err error) { return err } - // Force algo Update - u.PasswdHashAlgo = setting.PasswordHashAlgo - u.Passwd, _ = hashPasswordWithConfig(passwd, u.Salt, u.PasswdHashAlgo) + u.Passwd, _ = db.DefaultHasher.HashPassword(passwd, u.Salt, "") return nil } // ValidatePassword checks if given password matches the one belonging to the user. func (u *User) ValidatePassword(passwd string) bool { - var tempHash, algo string - var matchesSettings bool - if u.Passwd[:1] == "$" { - // Hash with known settings - tempHash, algo, matchesSettings = hashPasswordWithCurrentDefaults(passwd, u) - } else { - // Hash with defaults - algo = u.PasswdHashAlgo - matchesSettings = false // always false, because new password format should be enforced - _, tempHash = hashPasswordWithFallbackDefaults(passwd, u.Salt, u.PasswdHashAlgo) - } - - matches := false - if algo != algoBcrypt && subtle.ConstantTimeCompare([]byte(u.Passwd), []byte(tempHash)) == 1 { - matches = true - } - if algo == algoBcrypt && bcrypt.CompareHashAndPassword([]byte(u.Passwd), []byte(passwd)) == nil { - matches = true - } - - if matches && setting.PasswordUpdateAlgoToCurrent && (!matchesSettings || algo != setting.PasswordHashAlgo) { - // @todo: need to handle this error? As far as I can see, passwd is always set at this point... - _ = u.SetPassword(passwd) - } - - return matches + return db.DefaultHasher.Validate(passwd, u.Salt, u.Passwd) } // IsPasswordSet checks if the password is set or left empty diff --git a/models/user_test.go b/models/user_test.go index 089a33af61251..b3ff4cf2166fb 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -223,7 +223,7 @@ func TestHashPasswordDeterministic(t *testing.T) { u := &User{} algos := []string{"argon2", "pbkdf2", "scrypt", "bcrypt"} for j := 0; j < len(algos); j++ { - u.PasswdHashAlgo = algos[j] + setting.PasswordHashAlgo = algos[j] for i := 0; i < 50; i++ { // generate a random password rand.Read(b) @@ -245,61 +245,34 @@ func TestHashPasswordDeterministic(t *testing.T) { func TestOldPasswordMatchAndUpdate(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) - u := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) + u := AssertExistsAndLoadBean(t, &User{ID: 31}).(*User) - setting.PasswordHashAlgo = algoArgon2 + setting.PasswordHashAlgo = "argon2" matchingPass := "password" oldPass := u.Passwd - // Without update function - setting.PasswordUpdateAlgoToCurrent = false validates := u.ValidatePassword(matchingPass) newPass := u.Passwd - // Should match with old algo + // Should match even with not matching current config assert.True(t, validates) - // Should not be updated to new format + // Should not be altered assert.Equal(t, oldPass, newPass) // With update function - setting.PasswordUpdateAlgoToCurrent = true - validates = u.ValidatePassword(matchingPass) - newPass = u.Passwd - - // Should match with old algo - assert.True(t, validates) - // Should be updated to new format - assert.NotEqual(t, oldPass, newPass) -} - -func TestNewPasswordMatch(t *testing.T) { - assert.NoError(t, PrepareTestDatabase()) - u := AssertExistsAndLoadBean(t, &User{ID: 31}).(*User) + setting.Argon2Iterations = 2 + setting.Argon2Memory = 65536 + setting.Argon2Parallelism = 8 + setting.Argon2KeyLength = 50 - setting.PasswordHashAlgo = algoArgon2 + user, _ := UserSignIn("user31", matchingPass) - matchingPass := "password" - oldPass := u.Passwd - - // Without update function - setting.PasswordUpdateAlgoToCurrent = false - validates := u.ValidatePassword(matchingPass) - newPass := u.Passwd + validates = user.ValidatePassword(matchingPass) + newPass = user.Passwd - // Should match + // Should still match after config update assert.True(t, validates) - // Should not be updated - assert.Equal(t, oldPass, newPass) - - // With update function and different default HashAlgo - setting.PasswordUpdateAlgoToCurrent = true - setting.PasswordHashAlgo = algoBcrypt - passwordMatches := u.ValidatePassword(matchingPass) - newPass = u.Passwd - - // Should match - assert.True(t, passwordMatches) - // Should not be updated + // Should be updated to new config assert.NotEqual(t, oldPass, newPass) } diff --git a/modules/auth/db/hash.go b/modules/auth/db/hash.go new file mode 100644 index 0000000000000..21ca359d4c43f --- /dev/null +++ b/modules/auth/db/hash.go @@ -0,0 +1,67 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package db + +import ( + "strings" + + "code.gitea.io/gitea/modules/setting" +) + +type DefaultHasherStruct struct { + Hashers map[string]Hasher +} + +type Hasher interface { + Validate(password, salt, hash string) bool + HashPassword(password, salt, config string) (string, error) + getConfigFromSetting() string + getConfigFromHash(hash string) string +} + +func (d DefaultHasherStruct) Validate(password, salt, hash string) bool { + var typ, tail string + var hasher Hasher + var ok bool + split := strings.SplitN(hash[1:], "$", 2) + typ, tail = split[0], split[1] + + if len(tail) == 0 || len(typ) == 0 { + return false + } + + if hasher, ok = d.Hashers[typ]; ok { + return hasher.Validate(password, salt, hash) + } + return false +} + +func (d DefaultHasherStruct) HashPassword(password, salt, config string) (string, error) { + return d.Hashers[setting.PasswordHashAlgo].HashPassword(password, salt, config) +} + +func (d DefaultHasherStruct) PasswordNeedUpdate(hash string) bool { + var typ, tail string + var hasher Hasher + var ok bool + split := strings.SplitN(hash[1:], "$", 2) + typ, tail = split[0], split[1] + + if len(tail) == 0 || len(typ) == 0 || typ != setting.PasswordHashAlgo { + return true + } + + if hasher, ok = d.Hashers[typ]; ok { + return hasher.getConfigFromHash(hash) != hasher.getConfigFromSetting() + } + return true +} + +var DefaultHasher DefaultHasherStruct + +func init() { + DefaultHasher = DefaultHasherStruct{} + DefaultHasher.Hashers = make(map[string]Hasher) +} diff --git a/modules/auth/db/hash_argon2.go b/modules/auth/db/hash_argon2.go new file mode 100644 index 0000000000000..dbabbc3b2455a --- /dev/null +++ b/modules/auth/db/hash_argon2.go @@ -0,0 +1,88 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package db + +import ( + "crypto/subtle" + "fmt" + "strconv" + "strings" + + "code.gitea.io/gitea/modules/setting" + + "golang.org/x/crypto/argon2" +) + +type Argon2Hasher struct { + Iterations uint32 + Memory uint32 + Parallelism uint8 + KeyLength uint32 +} + +func (h Argon2Hasher) HashPassword(password, salt, config string) (string, error) { + var tempPasswd []byte + split := strings.Split(config, "$") + if len(split) != 4 { + return h.HashPassword(password, salt, h.getConfigFromSetting()) + } + + var iterations, memory, keyLength uint32 + var parallelism uint8 + var tmp int + + var err error + + if tmp, err = strconv.Atoi(split[0]); err != nil { + return "", err + } else { + iterations = uint32(tmp) + } + if tmp, err = strconv.Atoi(split[1]); err != nil { + return "", err + } else { + memory = uint32(tmp) + } + if tmp, err = strconv.Atoi(split[2]); err != nil { + return "", err + } else { + parallelism = uint8(tmp) + } + if tmp, err = strconv.Atoi(split[3]); err != nil { + return "", err + } else { + keyLength = uint32(tmp) + } + + tempPasswd = argon2.IDKey([]byte(password), []byte(salt), iterations, memory, parallelism, keyLength) + return fmt.Sprintf("$argon2$%d$%d$%d$%d$%x", iterations, memory, parallelism, keyLength, tempPasswd), nil +} + +func (h Argon2Hasher) Validate(password, salt, hash string) bool { + tempHash, _ := h.HashPassword(password, salt, h.getConfigFromHash(hash)) + if subtle.ConstantTimeCompare([]byte(hash), []byte(tempHash)) == 1 { + return true + } + return false +} + +func (h Argon2Hasher) getConfigFromHash(hash string) string { + configEnd := strings.LastIndex(hash, "$") + return hash[8:configEnd] +} + +func (h Argon2Hasher) getConfigFromSetting() string { + if h.Iterations == 0 { + h.Iterations = setting.Argon2Iterations + h.Memory = setting.Argon2Memory + h.Parallelism = setting.Argon2Parallelism + h.KeyLength = setting.Argon2KeyLength + } + return fmt.Sprintf("%d$%d$%d$%d", h.Iterations, h.Memory, h.Parallelism, h.KeyLength) +} + +func init() { + DefaultHasher.Hashers["argon2"] = Argon2Hasher{0, 0, 0, 0} +} diff --git a/modules/auth/db/hash_bcrypt.go b/modules/auth/db/hash_bcrypt.go new file mode 100644 index 0000000000000..3916aa96b71f3 --- /dev/null +++ b/modules/auth/db/hash_bcrypt.go @@ -0,0 +1,56 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package db + +import ( + "fmt" + "strconv" + "strings" + + "code.gitea.io/gitea/modules/setting" + + "golang.org/x/crypto/bcrypt" +) + +type BCryptHasher struct { + Cost int +} + +func (h BCryptHasher) HashPassword(password, salt, config string) (string, error) { + if config == "" { + return h.HashPassword(password, salt, h.getConfigFromSetting()) + } + if cost, err := strconv.Atoi(config); err == nil { + var tempPasswd []byte + tempPasswd, _ = bcrypt.GenerateFromPassword([]byte(password), cost) + return fmt.Sprintf("$bcrypt$%s", tempPasswd), nil + } else { + return "", err + } +} + +func (h BCryptHasher) Validate(password, salt, hash string) bool { + split := strings.SplitN(hash[1:], "$", 2) + if bcrypt.CompareHashAndPassword([]byte(split[1]), []byte(password)) == nil { + return true + } + return false +} + +func (h BCryptHasher) getConfigFromHash(hash string) string { + split := strings.SplitN(hash[1:], "$", 5) + return split[3] +} + +func (h BCryptHasher) getConfigFromSetting() string { + if h.Cost == 0 { + h.Cost = setting.BcryptCost + } + return strconv.Itoa(h.Cost) +} + +func init() { + DefaultHasher.Hashers["bcrypt"] = BCryptHasher{0} +} diff --git a/modules/auth/db/hash_pbkdf2.go b/modules/auth/db/hash_pbkdf2.go new file mode 100644 index 0000000000000..c9fcf35d7e200 --- /dev/null +++ b/modules/auth/db/hash_pbkdf2.go @@ -0,0 +1,68 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package db + +import ( + "crypto/sha256" + "crypto/subtle" + "fmt" + "strconv" + "strings" + + "code.gitea.io/gitea/modules/setting" + + "golang.org/x/crypto/pbkdf2" +) + +type Pbkdf2Hasher struct { + Iterations int + KeyLength int +} + +func (h Pbkdf2Hasher) HashPassword(password, salt, config string) (string, error) { + var tempPasswd []byte + split := strings.Split(config, "$") + if len(split) != 2 { + return h.HashPassword(password, salt, h.getConfigFromSetting()) + } + + var iterations, parallelism int + var err error + + if iterations, err = strconv.Atoi(split[0]); err != nil { + return "", err + } + if parallelism, err = strconv.Atoi(split[1]); err != nil { + return "", err + } + + tempPasswd = pbkdf2.Key([]byte(password), []byte(salt), iterations, parallelism, sha256.New) + return fmt.Sprintf("$pbkdf2$%d$%d$%x", iterations, parallelism, tempPasswd), nil +} + +func (h Pbkdf2Hasher) Validate(password, salt, hash string) bool { + tempHash, _ := h.HashPassword(password, salt, h.getConfigFromHash(hash)) + if subtle.ConstantTimeCompare([]byte(hash), []byte(tempHash)) == 1 { + return true + } + return false +} + +func (h Pbkdf2Hasher) getConfigFromHash(hash string) string { + configEnd := strings.LastIndex(hash, "$") + return hash[8:configEnd] +} + +func (h Pbkdf2Hasher) getConfigFromSetting() string { + if h.KeyLength == 0 { + h.Iterations = setting.Pbkdf2Iterations + h.KeyLength = setting.Pbkdf2KeyLength + } + return fmt.Sprintf("%d$%d", h.Iterations, h.KeyLength) +} + +func init() { + DefaultHasher.Hashers["pbkdf2"] = Pbkdf2Hasher{0, 0} +} diff --git a/modules/auth/db/hash_scrypt.go b/modules/auth/db/hash_scrypt.go new file mode 100644 index 0000000000000..cc9eb28892b5c --- /dev/null +++ b/modules/auth/db/hash_scrypt.go @@ -0,0 +1,77 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package db + +import ( + "crypto/subtle" + "fmt" + "strconv" + "strings" + + "code.gitea.io/gitea/modules/setting" + + "golang.org/x/crypto/scrypt" +) + +type SCryptHasher struct { + N int + R int + P int + KeyLength int +} + +func (h SCryptHasher) HashPassword(password, salt, config string) (string, error) { + var tempPasswd []byte + split := strings.Split(config, "$") + if len(split) != 4 { + return h.HashPassword(password, salt, h.getConfigFromSetting()) + } + + var n, r, p, keyLength int + var err error + + if n, err = strconv.Atoi(split[0]); err != nil { + return "", err + } + if r, err = strconv.Atoi(split[1]); err != nil { + return "", err + } + if p, err = strconv.Atoi(split[2]); err != nil { + return "", err + } + if keyLength, err = strconv.Atoi(split[3]); err != nil { + return "", err + } + + tempPasswd, _ = scrypt.Key([]byte(password), []byte(salt), n, r, p, keyLength) + return fmt.Sprintf("$scrypt$%d$%d$%d$%d$%x", n, r, p, keyLength, tempPasswd), nil +} + +func (h SCryptHasher) Validate(password, salt, hash string) bool { + tempHash, _ := h.HashPassword(password, salt, h.getConfigFromHash(hash)) + if subtle.ConstantTimeCompare([]byte(hash), []byte(tempHash)) == 1 { + return true + } + return false +} + +func (h SCryptHasher) getConfigFromHash(hash string) string { + configEnd := strings.LastIndex(hash, "$") + return hash[8:configEnd] +} + +func (h SCryptHasher) getConfigFromSetting() string { + if h.KeyLength == 0 { + h.N = setting.ScryptN + h.R = setting.ScryptR + h.P = setting.ScryptP + h.KeyLength = setting.ScryptKeyLength + } + return fmt.Sprintf("%d$%d$%d$%d", h.N, h.R, h.P, h.KeyLength) +} + +func init() { + DefaultHasher.Hashers["scrypt"] = SCryptHasher{0, 0, 0, 0} +} diff --git a/modules/setting/setting.go b/modules/setting/setting.go index f2dd4e3f415a2..0271c92f380c4 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -25,13 +25,11 @@ import ( "code.gitea.io/gitea/modules/generate" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/user" "code.gitea.io/gitea/modules/util" shellquote "github.com/kballard/go-shellquote" "github.com/unknwon/com" - "golang.org/x/crypto/bcrypt" gossh "golang.org/x/crypto/ssh" ini "gopkg.in/ini.v1" ) @@ -163,34 +161,25 @@ var ( PasswordComplexity []string PasswordHashAlgo string PasswordCheckPwn bool - PasswordUpdateAlgoToCurrent bool - // BCryptParams stores parameters for bcrypt algo - BCryptParams = structs.CryptBCrypt{ - Cost: bcrypt.DefaultCost, - } + // BCryptParams for parameters for bcrypt algo + BcryptCost int - // SCryptParams stores parameters for scrypt algo - SCryptParams = structs.CryptSCrypt{ - N: 65536, - R: 16, - P: 2, - KeyLength: 50, - } + // SCryptParams for parameters for scrypt algo + ScryptN int + ScryptR int + ScryptP int + ScryptKeyLength int - // Argon2Params stores params for argon2 algo - Argon2Params = structs.CryptArgon2{ - Iterations: 2, - Memory: 65536, - Parallelism: 8, - KeyLength: 50, - } + // Argon2Params for params for argon2 algo + Argon2Iterations uint32 + Argon2Memory uint32 + Argon2Parallelism uint8 + Argon2KeyLength uint32 - // Pbkdf2Params stores parameters for pbkdf2 algo - Pbkdf2Params = structs.CryptPbkdf2{ - Iterations: 10000, - KeyLength: 50, - } + // Pbkdf2Params for parameters for pbkdf2 algo + Pbkdf2Iterations int + Pbkdf2KeyLength int // UI settings UI = struct { @@ -838,6 +827,20 @@ func NewContext() { CSRFCookieHTTPOnly = sec.Key("CSRF_COOKIE_HTTP_ONLY").MustBool(true) PasswordCheckPwn = sec.Key("PASSWORD_CHECK_PWN").MustBool(false) + sec = Cfg.Section("security.hash") + BcryptCost = sec.Key("BCRYPT_COST").MustInt(10) + ScryptN = sec.Key("SCRYPT_N").MustInt(65536) + ScryptR = sec.Key("SCRYPT_R").MustInt(16) + ScryptP = sec.Key("SCRYPT_P").MustInt(2) + ScryptKeyLength = sec.Key("SCRYPT_KEY_LENGTH").MustInt(50) + // ini.v1 doesn't support MustUInt32 nor MustUInt8 + Argon2Iterations = uint32(sec.Key("ARGON2_ITERATIONS").MustInt(2)) + Argon2Memory = uint32(sec.Key("ARGON2_MEMORY").MustInt(65536)) + Argon2Parallelism = uint8(sec.Key("ARGON2_PARALLELISM").MustInt(8)) + Argon2KeyLength = uint32(sec.Key("ARGON2_KEY_LENGTH").MustInt(50)) + Pbkdf2Iterations = sec.Key("PBKDF2_ITERATIONS").MustInt(10000) + Pbkdf2KeyLength = sec.Key("PBKDF2_KEY_LENGTH").MustInt(50) + InternalToken = loadInternalToken(sec) cfgdata := sec.Key("PASSWORD_COMPLEXITY").Strings(",") diff --git a/modules/structs/crypt.go b/modules/structs/crypt.go deleted file mode 100644 index f9ebe94ac8677..0000000000000 --- a/modules/structs/crypt.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2021 The Gogs Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package structs - -// @todo: are the swagger models needed? - -// CryptBCrypt represents a BCrypt parameter set -// swagger:model -type CryptBCrypt struct { - Cost int -} - -// CryptSCrypt represents a SCrypt parameter set -// swagger:model -type CryptSCrypt struct { - N int - R int - P int - KeyLength int -} - -// CryptArgon2 represents a Argon2 parameter set -// swagger:model -type CryptArgon2 struct { - Iterations uint32 - Memory uint32 - Parallelism uint8 - KeyLength uint32 -} - -// CryptPbkdf2 represents a Pbkdf2 parameter set -// swagger:model -type CryptPbkdf2 struct { - Iterations int - KeyLength int -} - -var ( - // BCryptFallback stores parameters for bcrypt algo - BCryptFallback = CryptBCrypt{ - Cost: 10, - } - - // SCryptFallback stores parameters for scrypt algo - SCryptFallback = CryptSCrypt{ - N: 65536, - R: 16, - P: 2, - KeyLength: 50, - } - - // Argon2Fallback stores params for argon2 algo - Argon2Fallback = CryptArgon2{ - Iterations: 2, - Memory: 65536, - Parallelism: 8, - KeyLength: 50, - } - - // Pbkdf2Fallback stores parameters for pbkdf2 algo - Pbkdf2Fallback = CryptPbkdf2{ - Iterations: 10000, - KeyLength: 50, - } -) diff --git a/routers/user/auth.go b/routers/user/auth.go index bb877767aef8a..bfb453399da9d 100644 --- a/routers/user/auth.go +++ b/routers/user/auth.go @@ -1534,7 +1534,7 @@ func ResetPasswdPost(ctx *context.Context) { return } u.MustChangePassword = false - if err := models.UpdateUserCols(u, "must_change_password", "passwd", "passwd_hash_algo", "rands", "salt"); err != nil { + if err := models.UpdateUserCols(u, "must_change_password", "passwd", "rands", "salt"); err != nil { ctx.ServerError("UpdateUser", err) return } @@ -1610,7 +1610,7 @@ func MustChangePasswordPost(ctx *context.Context) { u.MustChangePassword = false - if err := models.UpdateUserCols(u, "must_change_password", "passwd", "passwd_hash_algo", "salt"); err != nil { + if err := models.UpdateUserCols(u, "must_change_password", "passwd", "salt"); err != nil { ctx.ServerError("UpdateUser", err) return } diff --git a/routers/user/setting/account.go b/routers/user/setting/account.go index 4900bba14ac0c..c7d1f4a0dbf0d 100644 --- a/routers/user/setting/account.go +++ b/routers/user/setting/account.go @@ -70,7 +70,7 @@ func AccountPost(ctx *context.Context) { ctx.ServerError("UpdateUser", err) return } - if err := models.UpdateUserCols(ctx.User, "salt", "passwd_hash_algo", "passwd"); err != nil { + if err := models.UpdateUserCols(ctx.User, "salt", "passwd"); err != nil { ctx.ServerError("UpdateUser", err) return } From 9dedf7d9734f353f25693358c744111448b809e4 Mon Sep 17 00:00:00 2001 From: Henning Bopp Date: Sun, 21 Feb 2021 01:55:53 +0100 Subject: [PATCH 3/9] Readd fix --- models/migrations/v174.go | 87 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 models/migrations/v174.go diff --git a/models/migrations/v174.go b/models/migrations/v174.go new file mode 100644 index 0000000000000..4575590618354 --- /dev/null +++ b/models/migrations/v174.go @@ -0,0 +1,87 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package migrations + +import ( + "xorm.io/builder" + "xorm.io/xorm" +) + +func removeUserHashAlgo(x *xorm.Engine) (err error) { + // Make sure the columns exist before dropping them + type User struct { + passwdHashAlgo string + } + if err := x.Sync2(new(User)); err != nil { + return err + } + + sess := x.NewSession() + defer sess.Close() + if err := sess.Begin(); err != nil { + return err + } + if err := dropTableColumns(sess, "user", "passwdHashAlgo"); err != nil { + return err + } + return sess.Commit() +} +func updateUserPasswords(x *xorm.Engine) (err error) { + const ( + algoBcrypt = "bcrypt" + algoScrypt = "scrypt" + algoArgon2 = "argon2" + algoPbkdf2 = "pbkdf2" + ) + + type User struct { + ID int64 `xorm:"pk autoincr"` + Passwd string `xorm:"NOT NULL"` + PasswdHashAlgo string `xorm:"NOT NULL DEFAULT 'argon2'"` + } + + sess := x.NewSession() + defer sess.Close() + + const batchSize = 500 + + for start := 0; ; start += batchSize { + users := make([]*User, 0, batchSize) + if err = sess.Limit(batchSize, start).Where(builder.And(builder.Neq{"passwd": ""}, builder.Neq{"passwd_hash_algo": ""}), 0).Find(&users); err != nil { + return + } + if len(users) == 0 { + break + } + + if err = sess.Begin(); err != nil { + return + } + + for _, user := range users { + switch user.PasswdHashAlgo { + case algoBcrypt: + user.Passwd = "$bcrypt$" + user.Passwd + case algoScrypt: + user.Passwd = "$scrypt$65536$16$2$50" + user.Passwd + case algoArgon2: + user.Passwd = "$argon2$2$65536$8$50$" + user.Passwd + case algoPbkdf2: + fallthrough + default: + user.Passwd = "$pbkdf2$10000$50$" + user.Passwd + } + if _, err = sess.ID(user.ID).Cols("passwd").Update(user); err != nil { + return err + } + } + + if err = sess.Commit(); err != nil { + return + } + } + + return sess.Commit() +} From a153f21c6f359d74ad64f4057fb51924bd49e4e2 Mon Sep 17 00:00:00 2001 From: Henning Bopp Date: Sun, 28 Feb 2021 19:23:27 +0100 Subject: [PATCH 4/9] Update ability to better password hash handing - Fix CR by @lunny - Revert migration to User table - Revert remove all usages of passwdHashAlgo field - Update relevant parts for password setting to write passwdHashAlgo --- cmd/admin.go | 2 +- models/fixtures/user.yml | 93 ++++++++++++++++++++++----------- models/login_source.go | 6 +-- models/migrations/migrations.go | 3 -- models/migrations/v174.go | 87 ------------------------------ models/user.go | 10 ++-- models/user_test.go | 8 +++ modules/auth/db/hash.go | 49 +++++++++++------ modules/auth/db/hash_argon2.go | 55 ++++++++++--------- modules/auth/db/hash_bcrypt.go | 34 ++++++------ modules/auth/db/hash_pbkdf2.go | 37 ++++++++----- modules/auth/db/hash_scrypt.go | 41 +++++++++------ routers/user/auth.go | 4 +- routers/user/setting/account.go | 2 +- 14 files changed, 213 insertions(+), 218 deletions(-) delete mode 100644 models/migrations/v174.go diff --git a/cmd/admin.go b/cmd/admin.go index 5fc99f7b86b9d..3ddf8eddf9e6c 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -353,7 +353,7 @@ func runChangePassword(c *cli.Context) error { return err } - if err = models.UpdateUserCols(user, "passwd", "salt"); err != nil { + if err = models.UpdateUserCols(user, "passwd", "passwd_hash_algo", "salt"); err != nil { return err } diff --git a/models/fixtures/user.yml b/models/fixtures/user.yml index c424a8d24f94b..31cf58d412937 100644 --- a/models/fixtures/user.yml +++ b/models/fixtures/user.yml @@ -7,7 +7,8 @@ full_name: User One email: user1@example.com email_notifications_preference: enabled - passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: true @@ -24,7 +25,8 @@ email: user2@example.com keep_email_private: true email_notifications_preference: enabled - passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -43,7 +45,8 @@ full_name: " <<<< >> >> > >> > >>> >> " email: user3@example.com email_notifications_preference: onmention - passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 1 # organization salt: ZogKvWdyEx is_admin: false @@ -60,7 +63,8 @@ full_name: " " email: user4@example.com email_notifications_preference: onmention - passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -77,7 +81,8 @@ full_name: User Five email: user5@example.com email_notifications_preference: enabled - passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -95,7 +100,8 @@ full_name: User Six email: user6@example.com email_notifications_preference: enabled - passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 1 # organization salt: ZogKvWdyEx is_admin: false @@ -112,7 +118,8 @@ full_name: User Seven email: user7@example.com email_notifications_preference: disabled - passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 1 # organization salt: ZogKvWdyEx is_admin: false @@ -129,7 +136,8 @@ full_name: User Eight email: user8@example.com email_notifications_preference: enabled - passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -147,7 +155,8 @@ full_name: User Nine email: user9@example.com email_notifications_preference: onmention - passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -162,7 +171,8 @@ name: user10 full_name: User Ten email: user10@example.com - passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -177,7 +187,8 @@ name: user11 full_name: User Eleven email: user11@example.com - passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -192,7 +203,8 @@ name: user12 full_name: User 12 email: user12@example.com - passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -207,7 +219,8 @@ name: user13 full_name: User 13 email: user13@example.com - passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -222,7 +235,8 @@ name: user14 full_name: User 14 email: user14@example.com - passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -237,7 +251,8 @@ name: user15 full_name: User 15 email: user15@example.com - passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -252,7 +267,8 @@ name: user16 full_name: User 16 email: user16@example.com - passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -267,7 +283,8 @@ name: user17 full_name: User 17 email: user17@example.com - passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 1 # organization salt: ZogKvWdyEx is_admin: false @@ -284,7 +301,8 @@ name: user18 full_name: User 18 email: user18@example.com - passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -299,7 +317,8 @@ name: user19 full_name: User 19 email: user19@example.com - passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 1 # organization salt: ZogKvWdyEx is_admin: false @@ -316,7 +335,8 @@ name: user20 full_name: User 20 email: user20@example.com - passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -331,7 +351,8 @@ name: user21 full_name: User 21 email: user21@example.com - passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -346,7 +367,8 @@ name: limited_org full_name: Limited Org email: limited_org@example.com - passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 1 # organization salt: ZogKvWdyEx is_admin: false @@ -364,7 +386,8 @@ name: privated_org full_name: Privated Org email: privated_org@example.com - passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 1 # organization salt: ZogKvWdyEx is_admin: false @@ -383,7 +406,8 @@ full_name: "user24" email: user24@example.com keep_email_private: true - passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -401,7 +425,8 @@ name: org25 full_name: "org25" email: org25@example.com - passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 1 # organization salt: ZogKvWdyEx is_admin: false @@ -418,7 +443,8 @@ full_name: "Org26" email: org26@example.com email_notifications_preference: onmention - passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 1 # organization salt: ZogKvWdyEx is_admin: false @@ -436,7 +462,8 @@ full_name: User Twenty-Seven email: user27@example.com email_notifications_preference: enabled - passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -451,7 +478,8 @@ full_name: "user27" email: user28@example.com keep_email_private: true - passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -469,7 +497,8 @@ name: user29 full_name: User 29 email: user29@example.com - passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -485,7 +514,8 @@ name: user30 full_name: User Thirty email: user30@example.com - passwd: "$argon2$65536$2$8$50$a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b" # password + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password type: 0 # individual salt: ZogKvWdyEx is_admin: false @@ -501,7 +531,8 @@ name: user31 full_name: User ThirtyOne email: user31@example.com - passwd: "$argon2$2$65536$12$50$1d2304e48d92db2fed37bdb4b3e7fca97fc41f2454b32de343007f0acb4623f04b07a477759fa83df487d12310a2c4c1ab25" # password + passwd_hash_algo: argon2$2$65536$12$50 + passwd: 1d2304e48d92db2fed37bdb4b3e7fca97fc41f2454b32de343007f0acb4623f04b07a477759fa83df487d12310a2c4c1ab25 # password type: 0 # individual salt: ZogKvWdyEx is_admin: false diff --git a/models/login_source.go b/models/login_source.go index 81bf298a658a3..20f21cb5162b1 100644 --- a/models/login_source.go +++ b/models/login_source.go @@ -6,7 +6,6 @@ package models import ( - "code.gitea.io/gitea/modules/auth/db" "crypto/tls" "encoding/json" "errors" @@ -16,6 +15,7 @@ import ( "strconv" "strings" + "code.gitea.io/gitea/modules/auth/db" "code.gitea.io/gitea/modules/auth/ldap" "code.gitea.io/gitea/modules/auth/oauth2" "code.gitea.io/gitea/modules/auth/pam" @@ -770,11 +770,11 @@ func UserSignIn(username, password string) (*User, error) { if user.IsPasswordSet() && user.ValidatePassword(password) { // Update password hash if server password hash algorithm have changed - if db.DefaultHasher.PasswordNeedUpdate(user.Passwd) { + if db.DefaultHasher.PasswordNeedUpdate(user.PasswdHashAlgo) { if err = user.SetPassword(password); err != nil { return nil, err } - if err = UpdateUserCols(user, "passwd", "salt"); err != nil { + if err = UpdateUserCols(user, "passwd", "passwd_hash_algo", "salt"); err != nil { return nil, err } } diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index ce4df6b70c15e..4fc737e1bfe42 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -294,9 +294,6 @@ var migrations = []Migration{ NewMigration("Add sessions table for go-chi/session", addSessionTable), // v173 -> v174 NewMigration("Add time_id column to Comment", addTimeIDCommentColumn), - // v174 -> v175 - NewMigration("Update User passwords to new format", updateUserPasswords), - NewMigration("Remove User passwordHashAlgo field", removeUserHashAlgo), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v174.go b/models/migrations/v174.go deleted file mode 100644 index 4575590618354..0000000000000 --- a/models/migrations/v174.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2021 The Gitea Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package migrations - -import ( - "xorm.io/builder" - "xorm.io/xorm" -) - -func removeUserHashAlgo(x *xorm.Engine) (err error) { - // Make sure the columns exist before dropping them - type User struct { - passwdHashAlgo string - } - if err := x.Sync2(new(User)); err != nil { - return err - } - - sess := x.NewSession() - defer sess.Close() - if err := sess.Begin(); err != nil { - return err - } - if err := dropTableColumns(sess, "user", "passwdHashAlgo"); err != nil { - return err - } - return sess.Commit() -} -func updateUserPasswords(x *xorm.Engine) (err error) { - const ( - algoBcrypt = "bcrypt" - algoScrypt = "scrypt" - algoArgon2 = "argon2" - algoPbkdf2 = "pbkdf2" - ) - - type User struct { - ID int64 `xorm:"pk autoincr"` - Passwd string `xorm:"NOT NULL"` - PasswdHashAlgo string `xorm:"NOT NULL DEFAULT 'argon2'"` - } - - sess := x.NewSession() - defer sess.Close() - - const batchSize = 500 - - for start := 0; ; start += batchSize { - users := make([]*User, 0, batchSize) - if err = sess.Limit(batchSize, start).Where(builder.And(builder.Neq{"passwd": ""}, builder.Neq{"passwd_hash_algo": ""}), 0).Find(&users); err != nil { - return - } - if len(users) == 0 { - break - } - - if err = sess.Begin(); err != nil { - return - } - - for _, user := range users { - switch user.PasswdHashAlgo { - case algoBcrypt: - user.Passwd = "$bcrypt$" + user.Passwd - case algoScrypt: - user.Passwd = "$scrypt$65536$16$2$50" + user.Passwd - case algoArgon2: - user.Passwd = "$argon2$2$65536$8$50$" + user.Passwd - case algoPbkdf2: - fallthrough - default: - user.Passwd = "$pbkdf2$10000$50$" + user.Passwd - } - if _, err = sess.ID(user.ID).Cols("passwd").Update(user); err != nil { - return err - } - } - - if err = sess.Commit(); err != nil { - return - } - } - - return sess.Commit() -} diff --git a/models/user.go b/models/user.go index f968a30d4689f..8117735976bf7 100644 --- a/models/user.go +++ b/models/user.go @@ -6,7 +6,6 @@ package models import ( - "code.gitea.io/gitea/modules/auth/db" "container/list" "context" "encoding/hex" @@ -20,6 +19,7 @@ import ( "time" "unicode/utf8" + "code.gitea.io/gitea/modules/auth/db" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/generate" "code.gitea.io/gitea/modules/git" @@ -104,6 +104,7 @@ type User struct { KeepEmailPrivate bool EmailNotificationsPreference string `xorm:"VARCHAR(20) NOT NULL DEFAULT 'enabled'"` Passwd string `xorm:"NOT NULL"` + PasswdHashAlgo string `xorm:"NOT NULL DEFAULT 'argon2'"` // MustChangePassword is an attribute that determines if a user // is to change his/her password after registration. @@ -379,10 +380,11 @@ func (u *User) NewGitSig() *git.Signature { } // SetPassword hashes a password using the algorithm defined in the config value of PASSWORD_HASH_ALGO -// change passwd and salt fields +// change passwd, salt and passwd_hash_algo fields func (u *User) SetPassword(passwd string) (err error) { if len(passwd) == 0 { u.Passwd = "" + u.PasswdHashAlgo = "" u.Salt = "" return nil } @@ -391,14 +393,14 @@ func (u *User) SetPassword(passwd string) (err error) { return err } - u.Passwd, _ = db.DefaultHasher.HashPassword(passwd, u.Salt, "") + u.Passwd, u.PasswdHashAlgo, _ = db.DefaultHasher.HashPassword(passwd, u.Salt, "") return nil } // ValidatePassword checks if given password matches the one belonging to the user. func (u *User) ValidatePassword(passwd string) bool { - return db.DefaultHasher.Validate(passwd, u.Salt, u.Passwd) + return db.DefaultHasher.Validate(passwd, u.Passwd, u.Salt, u.PasswdHashAlgo) } // IsPasswordSet checks if the password is set or left empty diff --git a/models/user_test.go b/models/user_test.go index b3ff4cf2166fb..ce5c93f71a2dc 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -232,12 +232,16 @@ func TestHashPasswordDeterministic(t *testing.T) { // save the current password in the user - hash it and store the result u.SetPassword(pass) r1 := u.Passwd + a1 := u.PasswdHashAlgo // run again u.SetPassword(pass) r2 := u.Passwd + a2 := u.PasswdHashAlgo assert.NotEqual(t, r1, r2) + assert.NotEqual(t, a2, algos[j]) + assert.Equal(t, a1, a2) assert.True(t, u.ValidatePassword(pass)) } } @@ -251,6 +255,7 @@ func TestOldPasswordMatchAndUpdate(t *testing.T) { matchingPass := "password" oldPass := u.Passwd + oldAlgo := u.PasswdHashAlgo validates := u.ValidatePassword(matchingPass) newPass := u.Passwd @@ -269,11 +274,14 @@ func TestOldPasswordMatchAndUpdate(t *testing.T) { validates = user.ValidatePassword(matchingPass) newPass = user.Passwd + newAlgo := user.PasswdHashAlgo // Should still match after config update assert.True(t, validates) // Should be updated to new config assert.NotEqual(t, oldPass, newPass) + // Should not be equal - test users Parallelism is not matching + assert.NotEqual(t, oldAlgo, newAlgo) } func BenchmarkHashPassword(b *testing.B) { diff --git a/modules/auth/db/hash.go b/modules/auth/db/hash.go index 21ca359d4c43f..6974b5f0c0da3 100644 --- a/modules/auth/db/hash.go +++ b/modules/auth/db/hash.go @@ -10,43 +10,59 @@ import ( "code.gitea.io/gitea/modules/setting" ) +// DefaultHasherStruct stores the available hashing instances type DefaultHasherStruct struct { Hashers map[string]Hasher } +// Hasher is the interface for a single hash implementation type Hasher interface { - Validate(password, salt, hash string) bool - HashPassword(password, salt, config string) (string, error) + Validate(password, hash, salt, config string) bool + HashPassword(password, salt, config string) (string, string, error) getConfigFromSetting() string - getConfigFromHash(hash string) string + getConfigFromAlgo(algo string) string } -func (d DefaultHasherStruct) Validate(password, salt, hash string) bool { - var typ, tail string +// HashPassword returns a PasswordHash, PassWordAlgo (and optionally an error) +func (d DefaultHasherStruct) HashPassword(password, salt, config string) (string, string, error) { + if setting.PasswordHashAlgo == "" { + setting.PasswordHashAlgo = "pbkdf2" + } + return d.Hashers[setting.PasswordHashAlgo].HashPassword(password, salt, config) +} + +// Validate validates a plain-text password +func (d DefaultHasherStruct) Validate(password, hash, salt, algo string) bool { + var typ, config string var hasher Hasher var ok bool - split := strings.SplitN(hash[1:], "$", 2) - typ, tail = split[0], split[1] + split := strings.SplitN(algo, "$", 2) + if len(split) == 1 { + typ = split[0] + config = "fallback" + } else { + typ, config = split[0], split[1] + } - if len(tail) == 0 || len(typ) == 0 { + if len(config) == 0 || len(typ) == 0 { return false } if hasher, ok = d.Hashers[typ]; ok { - return hasher.Validate(password, salt, hash) + return hasher.Validate(password, hash, salt, config) } return false } -func (d DefaultHasherStruct) HashPassword(password, salt, config string) (string, error) { - return d.Hashers[setting.PasswordHashAlgo].HashPassword(password, salt, config) -} - -func (d DefaultHasherStruct) PasswordNeedUpdate(hash string) bool { +// PasswordNeedUpdate determines if a password needs an update +func (d DefaultHasherStruct) PasswordNeedUpdate(algo string) bool { var typ, tail string var hasher Hasher var ok bool - split := strings.SplitN(hash[1:], "$", 2) + split := strings.SplitN(algo, "$", 2) + if len(split) == 1 { + return true + } typ, tail = split[0], split[1] if len(tail) == 0 || len(typ) == 0 || typ != setting.PasswordHashAlgo { @@ -54,11 +70,12 @@ func (d DefaultHasherStruct) PasswordNeedUpdate(hash string) bool { } if hasher, ok = d.Hashers[typ]; ok { - return hasher.getConfigFromHash(hash) != hasher.getConfigFromSetting() + return hasher.getConfigFromAlgo(algo) != hasher.getConfigFromSetting() } return true } +// DefaultHasher is the instance of the HashSet var DefaultHasher DefaultHasherStruct func init() { diff --git a/modules/auth/db/hash_argon2.go b/modules/auth/db/hash_argon2.go index dbabbc3b2455a..5b490b66c111a 100644 --- a/modules/auth/db/hash_argon2.go +++ b/modules/auth/db/hash_argon2.go @@ -15,6 +15,7 @@ import ( "golang.org/x/crypto/argon2" ) +// Argon2Hasher is a Hash implementation for Argon2 type Argon2Hasher struct { Iterations uint32 Memory uint32 @@ -22,11 +23,17 @@ type Argon2Hasher struct { KeyLength uint32 } -func (h Argon2Hasher) HashPassword(password, salt, config string) (string, error) { +// HashPassword returns a PasswordHash, PassWordAlgo (and optionally an error) +func (h Argon2Hasher) HashPassword(password, salt, config string) (string, string, error) { var tempPasswd []byte + if config == "fallback" { + config = "2$65536$8$50" + } + split := strings.Split(config, "$") if len(split) != 4 { - return h.HashPassword(password, salt, h.getConfigFromSetting()) + fmt.Printf("Take from Config: %v", h.getConfigFromSetting()) + split = strings.Split(h.getConfigFromSetting(), "$") } var iterations, memory, keyLength uint32 @@ -36,41 +43,41 @@ func (h Argon2Hasher) HashPassword(password, salt, config string) (string, error var err error if tmp, err = strconv.Atoi(split[0]); err != nil { - return "", err - } else { - iterations = uint32(tmp) + return "", "", err } + iterations = uint32(tmp) + if tmp, err = strconv.Atoi(split[1]); err != nil { - return "", err - } else { - memory = uint32(tmp) + return "", "", err } + memory = uint32(tmp) if tmp, err = strconv.Atoi(split[2]); err != nil { - return "", err - } else { - parallelism = uint8(tmp) + return "", "", err } + parallelism = uint8(tmp) if tmp, err = strconv.Atoi(split[3]); err != nil { - return "", err - } else { - keyLength = uint32(tmp) + return "", "", err } + keyLength = uint32(tmp) tempPasswd = argon2.IDKey([]byte(password), []byte(salt), iterations, memory, parallelism, keyLength) - return fmt.Sprintf("$argon2$%d$%d$%d$%d$%x", iterations, memory, parallelism, keyLength, tempPasswd), nil + return fmt.Sprintf("%x", tempPasswd), + fmt.Sprintf("argon2$%d$%d$%d$%d", iterations, memory, parallelism, keyLength), + nil } -func (h Argon2Hasher) Validate(password, salt, hash string) bool { - tempHash, _ := h.HashPassword(password, salt, h.getConfigFromHash(hash)) - if subtle.ConstantTimeCompare([]byte(hash), []byte(tempHash)) == 1 { - return true - } - return false +// Validate validates a plain-text password +func (h Argon2Hasher) Validate(password, hash, salt, config string) bool { + tempHash, _, _ := h.HashPassword(password, salt, config) + return subtle.ConstantTimeCompare([]byte(hash), []byte(tempHash)) == 1 } -func (h Argon2Hasher) getConfigFromHash(hash string) string { - configEnd := strings.LastIndex(hash, "$") - return hash[8:configEnd] +func (h Argon2Hasher) getConfigFromAlgo(algo string) string { + split := strings.SplitN(algo, "$", 2) + if len(split) == 1 { + split[1] = "fallback" + } + return split[1] } func (h Argon2Hasher) getConfigFromSetting() string { diff --git a/modules/auth/db/hash_bcrypt.go b/modules/auth/db/hash_bcrypt.go index 3916aa96b71f3..ea027e1503978 100644 --- a/modules/auth/db/hash_bcrypt.go +++ b/modules/auth/db/hash_bcrypt.go @@ -14,34 +14,36 @@ import ( "golang.org/x/crypto/bcrypt" ) +// BCryptHasher is a Hash implementation for BCrypt type BCryptHasher struct { Cost int } -func (h BCryptHasher) HashPassword(password, salt, config string) (string, error) { - if config == "" { - return h.HashPassword(password, salt, h.getConfigFromSetting()) +// HashPassword returns a PasswordHash, PassWordAlgo (and optionally an error) +func (h BCryptHasher) HashPassword(password, salt, config string) (string, string, error) { + if config == "fallback" { + config = "10" + } else if config == "" { + config = h.getConfigFromSetting() } - if cost, err := strconv.Atoi(config); err == nil { + + cost, err := strconv.Atoi(config) + if err == nil { var tempPasswd []byte tempPasswd, _ = bcrypt.GenerateFromPassword([]byte(password), cost) - return fmt.Sprintf("$bcrypt$%s", tempPasswd), nil - } else { - return "", err + return string(tempPasswd), fmt.Sprintf("bcrypt$%d", cost), nil } + return "", "", err } -func (h BCryptHasher) Validate(password, salt, hash string) bool { - split := strings.SplitN(hash[1:], "$", 2) - if bcrypt.CompareHashAndPassword([]byte(split[1]), []byte(password)) == nil { - return true - } - return false +// Validate validates a plain-text password +func (h BCryptHasher) Validate(password, hash, salt, config string) bool { + return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) == nil } -func (h BCryptHasher) getConfigFromHash(hash string) string { - split := strings.SplitN(hash[1:], "$", 5) - return split[3] +func (h BCryptHasher) getConfigFromAlgo(algo string) string { + split := strings.SplitN(algo, "$", 2) + return split[1] } func (h BCryptHasher) getConfigFromSetting() string { diff --git a/modules/auth/db/hash_pbkdf2.go b/modules/auth/db/hash_pbkdf2.go index c9fcf35d7e200..e729b06722680 100644 --- a/modules/auth/db/hash_pbkdf2.go +++ b/modules/auth/db/hash_pbkdf2.go @@ -16,43 +16,52 @@ import ( "golang.org/x/crypto/pbkdf2" ) +// Pbkdf2Hasher is a Hash implementation for Pbkdf2 type Pbkdf2Hasher struct { Iterations int KeyLength int } -func (h Pbkdf2Hasher) HashPassword(password, salt, config string) (string, error) { +// HashPassword returns a PasswordHash, PassWordAlgo (and optionally an error) +func (h Pbkdf2Hasher) HashPassword(password, salt, config string) (string, string, error) { var tempPasswd []byte + if config == "fallback" { + config = "10000$50" + } + split := strings.Split(config, "$") if len(split) != 2 { - return h.HashPassword(password, salt, h.getConfigFromSetting()) + split = strings.Split(h.getConfigFromSetting(), "$") } var iterations, parallelism int var err error if iterations, err = strconv.Atoi(split[0]); err != nil { - return "", err + return "", "", err } if parallelism, err = strconv.Atoi(split[1]); err != nil { - return "", err + return "", "", err } tempPasswd = pbkdf2.Key([]byte(password), []byte(salt), iterations, parallelism, sha256.New) - return fmt.Sprintf("$pbkdf2$%d$%d$%x", iterations, parallelism, tempPasswd), nil + return fmt.Sprintf("%x", tempPasswd), + fmt.Sprintf("pbkdf2$%d$%d", iterations, parallelism), + nil } -func (h Pbkdf2Hasher) Validate(password, salt, hash string) bool { - tempHash, _ := h.HashPassword(password, salt, h.getConfigFromHash(hash)) - if subtle.ConstantTimeCompare([]byte(hash), []byte(tempHash)) == 1 { - return true - } - return false +// Validate validates a plain-text password +func (h Pbkdf2Hasher) Validate(password, hash, salt, config string) bool { + tempHash, _, _ := h.HashPassword(password, salt, config) + return subtle.ConstantTimeCompare([]byte(hash), []byte(tempHash)) == 1 } -func (h Pbkdf2Hasher) getConfigFromHash(hash string) string { - configEnd := strings.LastIndex(hash, "$") - return hash[8:configEnd] +func (h Pbkdf2Hasher) getConfigFromAlgo(algo string) string { + split := strings.SplitN(algo, "$", 2) + if len(split) == 1 { + split[1] = "fallback" + } + return split[1] } func (h Pbkdf2Hasher) getConfigFromSetting() string { diff --git a/modules/auth/db/hash_scrypt.go b/modules/auth/db/hash_scrypt.go index cc9eb28892b5c..40729754f8b03 100644 --- a/modules/auth/db/hash_scrypt.go +++ b/modules/auth/db/hash_scrypt.go @@ -15,6 +15,7 @@ import ( "golang.org/x/crypto/scrypt" ) +// SCryptHasher is a Hash implementation for SCrypt type SCryptHasher struct { N int R int @@ -22,44 +23,52 @@ type SCryptHasher struct { KeyLength int } -func (h SCryptHasher) HashPassword(password, salt, config string) (string, error) { +// HashPassword returns a PasswordHash, PassWordAlgo (and optionally an error) +func (h SCryptHasher) HashPassword(password, salt, config string) (string, string, error) { var tempPasswd []byte + if config == "fallback" { + config = "65536$16$2$50" + } + split := strings.Split(config, "$") if len(split) != 4 { - return h.HashPassword(password, salt, h.getConfigFromSetting()) + split = strings.Split(h.getConfigFromSetting(), "$") } var n, r, p, keyLength int var err error if n, err = strconv.Atoi(split[0]); err != nil { - return "", err + return "", "", err } if r, err = strconv.Atoi(split[1]); err != nil { - return "", err + return "", "", err } if p, err = strconv.Atoi(split[2]); err != nil { - return "", err + return "", "", err } if keyLength, err = strconv.Atoi(split[3]); err != nil { - return "", err + return "", "", err } tempPasswd, _ = scrypt.Key([]byte(password), []byte(salt), n, r, p, keyLength) - return fmt.Sprintf("$scrypt$%d$%d$%d$%d$%x", n, r, p, keyLength, tempPasswd), nil + return fmt.Sprintf("%x", tempPasswd), + fmt.Sprintf("scrypt$%d$%d$%d$%d", n, r, p, keyLength), + nil } -func (h SCryptHasher) Validate(password, salt, hash string) bool { - tempHash, _ := h.HashPassword(password, salt, h.getConfigFromHash(hash)) - if subtle.ConstantTimeCompare([]byte(hash), []byte(tempHash)) == 1 { - return true - } - return false +// Validate validates a plain-text password +func (h SCryptHasher) Validate(password, hash, salt, config string) bool { + tempHash, _, _ := h.HashPassword(password, salt, config) + return subtle.ConstantTimeCompare([]byte(hash), []byte(tempHash)) == 1 } -func (h SCryptHasher) getConfigFromHash(hash string) string { - configEnd := strings.LastIndex(hash, "$") - return hash[8:configEnd] +func (h SCryptHasher) getConfigFromAlgo(algo string) string { + split := strings.SplitN(algo, "$", 2) + if len(split) == 1 { + split[1] = "fallback" + } + return split[1] } func (h SCryptHasher) getConfigFromSetting() string { diff --git a/routers/user/auth.go b/routers/user/auth.go index a84f17b4dc477..de74055d565f5 100644 --- a/routers/user/auth.go +++ b/routers/user/auth.go @@ -1534,7 +1534,7 @@ func ResetPasswdPost(ctx *context.Context) { return } u.MustChangePassword = false - if err := models.UpdateUserCols(u, "must_change_password", "passwd", "rands", "salt"); err != nil { + if err := models.UpdateUserCols(u, "must_change_password", "passwd", "passwd_hash_algo", "rands", "salt"); err != nil { ctx.ServerError("UpdateUser", err) return } @@ -1610,7 +1610,7 @@ func MustChangePasswordPost(ctx *context.Context) { u.MustChangePassword = false - if err := models.UpdateUserCols(u, "must_change_password", "passwd", "salt"); err != nil { + if err := models.UpdateUserCols(u, "must_change_password", "passwd", "passwd_hash_algo", "salt"); err != nil { ctx.ServerError("UpdateUser", err) return } diff --git a/routers/user/setting/account.go b/routers/user/setting/account.go index c7d1f4a0dbf0d..118b33e2fdb06 100644 --- a/routers/user/setting/account.go +++ b/routers/user/setting/account.go @@ -70,7 +70,7 @@ func AccountPost(ctx *context.Context) { ctx.ServerError("UpdateUser", err) return } - if err := models.UpdateUserCols(ctx.User, "salt", "passwd"); err != nil { + if err := models.UpdateUserCols(ctx.User, "salt", "passwd", "passwd_hash_algo"); err != nil { ctx.ServerError("UpdateUser", err) return } From 57c66594d9682992fb9483451b9c2106abd9db53 Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Sat, 6 Mar 2021 14:06:42 +0000 Subject: [PATCH 5/9] adjust module name and setting configuration Signed-off-by: Andrew Thornton --- models/login_source.go | 2 +- models/user.go | 6 ++-- models/user_test.go | 14 ++++++---- modules/auth/{db => hash}/hash.go | 26 ++++++++++++------ modules/auth/{db => hash}/hash_argon2.go | 21 +++++--------- modules/auth/{db => hash}/hash_bcrypt.go | 12 +++----- modules/auth/{db => hash}/hash_pbkdf2.go | 15 ++++------ modules/auth/{db => hash}/hash_scrypt.go | 21 +++++--------- modules/setting/password_hash.go | 25 +++++++++++++++++ modules/setting/setting.go | 35 +----------------------- routers/install.go | 5 ++-- 11 files changed, 81 insertions(+), 101 deletions(-) rename modules/auth/{db => hash}/hash.go (76%) rename modules/auth/{db => hash}/hash_argon2.go (84%) rename modules/auth/{db => hash}/hash_bcrypt.go (88%) rename modules/auth/{db => hash}/hash_pbkdf2.go (87%) rename modules/auth/{db => hash}/hash_scrypt.go (85%) create mode 100644 modules/setting/password_hash.go diff --git a/models/login_source.go b/models/login_source.go index 000a0d8620b1e..4ce2744bfdc16 100644 --- a/models/login_source.go +++ b/models/login_source.go @@ -14,7 +14,7 @@ import ( "strconv" "strings" - "code.gitea.io/gitea/modules/auth/db" + db "code.gitea.io/gitea/modules/auth/hash" "code.gitea.io/gitea/modules/auth/ldap" "code.gitea.io/gitea/modules/auth/oauth2" "code.gitea.io/gitea/modules/auth/pam" diff --git a/models/user.go b/models/user.go index 8117735976bf7..8e012313c39ed 100644 --- a/models/user.go +++ b/models/user.go @@ -19,7 +19,7 @@ import ( "time" "unicode/utf8" - "code.gitea.io/gitea/modules/auth/db" + db "code.gitea.io/gitea/modules/auth/hash" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/generate" "code.gitea.io/gitea/modules/git" @@ -393,9 +393,9 @@ func (u *User) SetPassword(passwd string) (err error) { return err } - u.Passwd, u.PasswdHashAlgo, _ = db.DefaultHasher.HashPassword(passwd, u.Salt, "") + u.Passwd, u.PasswdHashAlgo, err = db.DefaultHasher.HashPassword(passwd, u.Salt, "") - return nil + return err } // ValidatePassword checks if given password matches the one belonging to the user. diff --git a/models/user_test.go b/models/user_test.go index ce5c93f71a2dc..7f0c8e34d8f63 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -10,6 +10,7 @@ import ( "strings" "testing" + "code.gitea.io/gitea/modules/auth/hash" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" @@ -223,7 +224,7 @@ func TestHashPasswordDeterministic(t *testing.T) { u := &User{} algos := []string{"argon2", "pbkdf2", "scrypt", "bcrypt"} for j := 0; j < len(algos); j++ { - setting.PasswordHashAlgo = algos[j] + hash.DefaultHasher.DefaultAlgorithm = algos[j] for i := 0; i < 50; i++ { // generate a random password rand.Read(b) @@ -251,7 +252,7 @@ func TestOldPasswordMatchAndUpdate(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) u := AssertExistsAndLoadBean(t, &User{ID: 31}).(*User) - setting.PasswordHashAlgo = "argon2" + hash.DefaultHasher.DefaultAlgorithm = "argon2" matchingPass := "password" oldPass := u.Passwd @@ -265,10 +266,11 @@ func TestOldPasswordMatchAndUpdate(t *testing.T) { assert.Equal(t, oldPass, newPass) // With update function - setting.Argon2Iterations = 2 - setting.Argon2Memory = 65536 - setting.Argon2Parallelism = 8 - setting.Argon2KeyLength = 50 + argonHasher := hash.DefaultHasher.Hashers["argon2"].(hash.Argon2Hasher) + argonHasher.Iterations = 2 + argonHasher.Memory = 65536 + argonHasher.Parallelism = 8 + argonHasher.KeyLength = 50 user, _ := UserSignIn("user31", matchingPass) diff --git a/modules/auth/db/hash.go b/modules/auth/hash/hash.go similarity index 76% rename from modules/auth/db/hash.go rename to modules/auth/hash/hash.go index 6974b5f0c0da3..433c3fb4c0d8a 100644 --- a/modules/auth/db/hash.go +++ b/modules/auth/hash/hash.go @@ -2,16 +2,20 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package db +package hash import ( "strings" - - "code.gitea.io/gitea/modules/setting" ) +const defaultAlgorithm = "pbkdf2" + // DefaultHasherStruct stores the available hashing instances type DefaultHasherStruct struct { + // DefaultAlgorithm is the default hashing algorithm + DefaultAlgorithm string + // Hashers is a map of algorithm name to Hasher implementation + // We use a map as it is easier to use Hashers map[string]Hasher } @@ -25,10 +29,12 @@ type Hasher interface { // HashPassword returns a PasswordHash, PassWordAlgo (and optionally an error) func (d DefaultHasherStruct) HashPassword(password, salt, config string) (string, string, error) { - if setting.PasswordHashAlgo == "" { - setting.PasswordHashAlgo = "pbkdf2" + hasher, ok := d.Hashers[d.DefaultAlgorithm] + if !ok { + hasher = d.Hashers[defaultAlgorithm] } - return d.Hashers[setting.PasswordHashAlgo].HashPassword(password, salt, config) + + return hasher.HashPassword(password, salt, config) } // Validate validates a plain-text password @@ -65,7 +71,7 @@ func (d DefaultHasherStruct) PasswordNeedUpdate(algo string) bool { } typ, tail = split[0], split[1] - if len(tail) == 0 || len(typ) == 0 || typ != setting.PasswordHashAlgo { + if len(tail) == 0 || len(typ) == 0 || typ != d.DefaultAlgorithm { return true } @@ -79,6 +85,8 @@ func (d DefaultHasherStruct) PasswordNeedUpdate(algo string) bool { var DefaultHasher DefaultHasherStruct func init() { - DefaultHasher = DefaultHasherStruct{} - DefaultHasher.Hashers = make(map[string]Hasher) + DefaultHasher = DefaultHasherStruct{ + DefaultAlgorithm: defaultAlgorithm, + Hashers: make(map[string]Hasher), + } } diff --git a/modules/auth/db/hash_argon2.go b/modules/auth/hash/hash_argon2.go similarity index 84% rename from modules/auth/db/hash_argon2.go rename to modules/auth/hash/hash_argon2.go index 5b490b66c111a..5af36b0cd06c1 100644 --- a/modules/auth/db/hash_argon2.go +++ b/modules/auth/hash/hash_argon2.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package db +package hash import ( "crypto/subtle" @@ -10,23 +10,22 @@ import ( "strconv" "strings" - "code.gitea.io/gitea/modules/setting" - "golang.org/x/crypto/argon2" ) // Argon2Hasher is a Hash implementation for Argon2 type Argon2Hasher struct { - Iterations uint32 - Memory uint32 - Parallelism uint8 - KeyLength uint32 + Iterations uint32 `ini:"ARGON2_ITERATIONS"` + Memory uint32 `ini:"ARGON2_MEMORY"` + Parallelism uint8 `ini:"ARGON2_PARALLELISM"` + KeyLength uint32 `ini:"ARGON2_KEY_LENGTH"` } // HashPassword returns a PasswordHash, PassWordAlgo (and optionally an error) func (h Argon2Hasher) HashPassword(password, salt, config string) (string, string, error) { var tempPasswd []byte if config == "fallback" { + // Fixed default config to match with original configuration config = "2$65536$8$50" } @@ -81,15 +80,9 @@ func (h Argon2Hasher) getConfigFromAlgo(algo string) string { } func (h Argon2Hasher) getConfigFromSetting() string { - if h.Iterations == 0 { - h.Iterations = setting.Argon2Iterations - h.Memory = setting.Argon2Memory - h.Parallelism = setting.Argon2Parallelism - h.KeyLength = setting.Argon2KeyLength - } return fmt.Sprintf("%d$%d$%d$%d", h.Iterations, h.Memory, h.Parallelism, h.KeyLength) } func init() { - DefaultHasher.Hashers["argon2"] = Argon2Hasher{0, 0, 0, 0} + DefaultHasher.Hashers["argon2"] = Argon2Hasher{2, 65536, 8, 50} } diff --git a/modules/auth/db/hash_bcrypt.go b/modules/auth/hash/hash_bcrypt.go similarity index 88% rename from modules/auth/db/hash_bcrypt.go rename to modules/auth/hash/hash_bcrypt.go index ea027e1503978..9da3e3b4c97c0 100644 --- a/modules/auth/db/hash_bcrypt.go +++ b/modules/auth/hash/hash_bcrypt.go @@ -2,26 +2,25 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package db +package hash import ( "fmt" "strconv" "strings" - "code.gitea.io/gitea/modules/setting" - "golang.org/x/crypto/bcrypt" ) // BCryptHasher is a Hash implementation for BCrypt type BCryptHasher struct { - Cost int + Cost int `ini:"BCRYPT_COST"` } // HashPassword returns a PasswordHash, PassWordAlgo (and optionally an error) func (h BCryptHasher) HashPassword(password, salt, config string) (string, string, error) { if config == "fallback" { + // Fixed default config to match with original configuration config = "10" } else if config == "" { config = h.getConfigFromSetting() @@ -47,12 +46,9 @@ func (h BCryptHasher) getConfigFromAlgo(algo string) string { } func (h BCryptHasher) getConfigFromSetting() string { - if h.Cost == 0 { - h.Cost = setting.BcryptCost - } return strconv.Itoa(h.Cost) } func init() { - DefaultHasher.Hashers["bcrypt"] = BCryptHasher{0} + DefaultHasher.Hashers["bcrypt"] = BCryptHasher{10} } diff --git a/modules/auth/db/hash_pbkdf2.go b/modules/auth/hash/hash_pbkdf2.go similarity index 87% rename from modules/auth/db/hash_pbkdf2.go rename to modules/auth/hash/hash_pbkdf2.go index e729b06722680..d50bf97c6d278 100644 --- a/modules/auth/db/hash_pbkdf2.go +++ b/modules/auth/hash/hash_pbkdf2.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package db +package hash import ( "crypto/sha256" @@ -11,21 +11,20 @@ import ( "strconv" "strings" - "code.gitea.io/gitea/modules/setting" - "golang.org/x/crypto/pbkdf2" ) // Pbkdf2Hasher is a Hash implementation for Pbkdf2 type Pbkdf2Hasher struct { - Iterations int - KeyLength int + Iterations int `ini:"PBKDF2_ITERATIONS"` + KeyLength int `ini:"PBKDF2_KEY_LENGTH"` } // HashPassword returns a PasswordHash, PassWordAlgo (and optionally an error) func (h Pbkdf2Hasher) HashPassword(password, salt, config string) (string, string, error) { var tempPasswd []byte if config == "fallback" { + // Fixed default config to match with original configuration config = "10000$50" } @@ -65,13 +64,9 @@ func (h Pbkdf2Hasher) getConfigFromAlgo(algo string) string { } func (h Pbkdf2Hasher) getConfigFromSetting() string { - if h.KeyLength == 0 { - h.Iterations = setting.Pbkdf2Iterations - h.KeyLength = setting.Pbkdf2KeyLength - } return fmt.Sprintf("%d$%d", h.Iterations, h.KeyLength) } func init() { - DefaultHasher.Hashers["pbkdf2"] = Pbkdf2Hasher{0, 0} + DefaultHasher.Hashers["pbkdf2"] = Pbkdf2Hasher{10000, 50} } diff --git a/modules/auth/db/hash_scrypt.go b/modules/auth/hash/hash_scrypt.go similarity index 85% rename from modules/auth/db/hash_scrypt.go rename to modules/auth/hash/hash_scrypt.go index 40729754f8b03..ec36e6f03d422 100644 --- a/modules/auth/db/hash_scrypt.go +++ b/modules/auth/hash/hash_scrypt.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package db +package hash import ( "crypto/subtle" @@ -10,23 +10,22 @@ import ( "strconv" "strings" - "code.gitea.io/gitea/modules/setting" - "golang.org/x/crypto/scrypt" ) // SCryptHasher is a Hash implementation for SCrypt type SCryptHasher struct { - N int - R int - P int - KeyLength int + N int `ini:"SCRYPT_N"` + R int `ini:"SCRYPT_R"` + P int `ini:"SCRYPT_P"` + KeyLength int `ini:"SCRYPT_KEY_LENGTH"` } // HashPassword returns a PasswordHash, PassWordAlgo (and optionally an error) func (h SCryptHasher) HashPassword(password, salt, config string) (string, string, error) { var tempPasswd []byte if config == "fallback" { + // Fixed default config to match with original configuration config = "65536$16$2$50" } @@ -72,15 +71,9 @@ func (h SCryptHasher) getConfigFromAlgo(algo string) string { } func (h SCryptHasher) getConfigFromSetting() string { - if h.KeyLength == 0 { - h.N = setting.ScryptN - h.R = setting.ScryptR - h.P = setting.ScryptP - h.KeyLength = setting.ScryptKeyLength - } return fmt.Sprintf("%d$%d$%d$%d", h.N, h.R, h.P, h.KeyLength) } func init() { - DefaultHasher.Hashers["scrypt"] = SCryptHasher{0, 0, 0, 0} + DefaultHasher.Hashers["scrypt"] = SCryptHasher{65536, 16, 2, 50} } diff --git a/modules/setting/password_hash.go b/modules/setting/password_hash.go new file mode 100644 index 0000000000000..b9212b0d7823f --- /dev/null +++ b/modules/setting/password_hash.go @@ -0,0 +1,25 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package setting + +import ( + "code.gitea.io/gitea/modules/auth/hash" + "code.gitea.io/gitea/modules/log" +) + +func newPasswordHashService() { + passwordHashAlgo := Cfg.Section("security").Key("PASSWORD_HASH_ALGO").MustString("pbkdf2") + + if _, ok := hash.DefaultHasher.Hashers[passwordHashAlgo]; !ok { + log.Error("Unknown default hashing algorithm: %s. Keeping default: %s", passwordHashAlgo, hash.DefaultHasher.DefaultAlgorithm) + } else { + hash.DefaultHasher.DefaultAlgorithm = passwordHashAlgo + } + + sec := Cfg.Section("security.password_hash") + for _, hasher := range hash.DefaultHasher.Hashers { + sec.MapTo(hasher) + } +} diff --git a/modules/setting/setting.go b/modules/setting/setting.go index b5afe77d40869..3d33a7c805972 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -173,28 +173,8 @@ var ( DisableWebhooks bool OnlyAllowPushIfGiteaEnvironmentSet bool PasswordComplexity []string - PasswordHashAlgo string PasswordCheckPwn bool - // BCryptParams for parameters for bcrypt algo - BcryptCost int - - // SCryptParams for parameters for scrypt algo - ScryptN int - ScryptR int - ScryptP int - ScryptKeyLength int - - // Argon2Params for params for argon2 algo - Argon2Iterations uint32 - Argon2Memory uint32 - Argon2Parallelism uint8 - Argon2KeyLength uint32 - - // Pbkdf2Params for parameters for pbkdf2 algo - Pbkdf2Iterations int - Pbkdf2KeyLength int - // UI settings UI = struct { ExplorePagingNum int @@ -838,23 +818,10 @@ func NewContext() { DisableGitHooks = sec.Key("DISABLE_GIT_HOOKS").MustBool(true) DisableWebhooks = sec.Key("DISABLE_WEBHOOKS").MustBool(false) OnlyAllowPushIfGiteaEnvironmentSet = sec.Key("ONLY_ALLOW_PUSH_IF_GITEA_ENVIRONMENT_SET").MustBool(true) - PasswordHashAlgo = sec.Key("PASSWORD_HASH_ALGO").MustString("pbkdf2") CSRFCookieHTTPOnly = sec.Key("CSRF_COOKIE_HTTP_ONLY").MustBool(true) PasswordCheckPwn = sec.Key("PASSWORD_CHECK_PWN").MustBool(false) - sec = Cfg.Section("security.hash") - BcryptCost = sec.Key("BCRYPT_COST").MustInt(10) - ScryptN = sec.Key("SCRYPT_N").MustInt(65536) - ScryptR = sec.Key("SCRYPT_R").MustInt(16) - ScryptP = sec.Key("SCRYPT_P").MustInt(2) - ScryptKeyLength = sec.Key("SCRYPT_KEY_LENGTH").MustInt(50) - // ini.v1 doesn't support MustUInt32 nor MustUInt8 - Argon2Iterations = uint32(sec.Key("ARGON2_ITERATIONS").MustInt(2)) - Argon2Memory = uint32(sec.Key("ARGON2_MEMORY").MustInt(65536)) - Argon2Parallelism = uint8(sec.Key("ARGON2_PARALLELISM").MustInt(8)) - Argon2KeyLength = uint32(sec.Key("ARGON2_KEY_LENGTH").MustInt(50)) - Pbkdf2Iterations = sec.Key("PBKDF2_ITERATIONS").MustInt(10000) - Pbkdf2KeyLength = sec.Key("PBKDF2_KEY_LENGTH").MustInt(50) + newPasswordHashService() InternalToken = loadInternalToken(sec) diff --git a/routers/install.go b/routers/install.go index cfe5582f2b55a..da2adebb8d1f5 100644 --- a/routers/install.go +++ b/routers/install.go @@ -14,6 +14,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/auth/hash" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" auth "code.gitea.io/gitea/modules/forms" @@ -143,7 +144,7 @@ func Install(ctx *context.Context) { form.DefaultAllowCreateOrganization = setting.Service.DefaultAllowCreateOrganization form.DefaultEnableTimetracking = setting.Service.DefaultEnableTimetracking form.NoReplyAddress = setting.Service.NoReplyAddress - form.PasswordAlgorithm = setting.PasswordHashAlgo + form.PasswordAlgorithm = hash.DefaultHasher.DefaultAlgorithm middleware.AssignForm(form, ctx.Data) ctx.HTML(200, tplInstall) @@ -187,7 +188,7 @@ func InstallPost(ctx *context.Context) { setting.Database.Charset = form.Charset setting.Database.Path = form.DbPath - setting.PasswordHashAlgo = form.PasswordAlgorithm + hash.DefaultHasher.DefaultAlgorithm = form.PasswordAlgorithm if (setting.Database.Type == "sqlite3") && len(setting.Database.Path) == 0 { From 8996ea704512643310584c9c608edda301e4acbf Mon Sep 17 00:00:00 2001 From: Henning Bopp Date: Sat, 6 Mar 2021 18:19:17 +0100 Subject: [PATCH 6/9] Add docs and settings page output, and fix inputs from PR Add password_hash options to - config-cheat-sheet - /admin/config - default ini Add passing of error if hashing fails --- custom/conf/app.example.ini | 8 ++--- .../doc/advanced/config-cheat-sheet.en-us.md | 14 ++++++++ models/login_source.go | 4 +-- models/user.go | 6 ++-- models/user_test.go | 2 +- modules/auth/hash/hash.go | 10 +++--- modules/auth/hash/hash_argon2.go | 10 +++--- modules/auth/hash/hash_bcrypt.go | 10 +++--- modules/auth/hash/hash_pbkdf2.go | 10 +++--- modules/auth/hash/hash_scrypt.go | 10 +++--- modules/setting/password_hash.go | 2 +- options/locale/locale_en-US.ini | 10 ++++++ routers/admin/admin.go | 3 ++ templates/admin/config.tmpl | 32 +++++++++++++++++++ 14 files changed, 95 insertions(+), 36 deletions(-) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 79f0f827a544c..fb34145da614a 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -571,21 +571,21 @@ CSRF_COOKIE_HTTP_ONLY = true ; Validate against https://haveibeenpwned.com/Passwords to see if a password has been exposed PASSWORD_CHECK_PWN = false -[security.hash] +[security.password_hash] +; Every parameter for the built in hash algorithms can be changed in this section. +; Handle with care! Changing the values can massively change the hash calculation time and/or memory needed for the hashing process. +; After changing a value, the user password hash will be recalculated on next successful login. BCRYPT_COST = 10 - ; Parameters for scrypt SCRYPT_N = 65536 SCRYPT_R = 16 SCRYPT_P = 2 SCRYPT_KEY_LENGTH = 50 - ; Parameters for Argon2id ARGON2_ITERATIONS = 2 ARGON2_MEMORY = 65536 ARGON2_PARALLELISM = 8 ARGON2_KEY_LENGTH = 50 - ; Parameters for Pbkdf2 PBKDF2_ITERATIONS = 10000 PBKDF2_KEY_LENGTH = 50 diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index 9a9eb1d466b2c..2cdcccc71940a 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -412,6 +412,20 @@ relation to port exhaustion. - off - do not check password complexity - `PASSWORD_CHECK_PWN`: **false**: Check [HaveIBeenPwned](https://haveibeenpwned.com/Passwords) to see if a password has been exposed. +## Password Hash Algo (`password_hash`) + +- `BCRYPT_COST`: **10**: The `cost` parameter for Bcrypt hashing. Values from 4 to 31 are allowed. [More info on the bcrypt hashing algorithm.](https://en.wikipedia.org/wiki/Bcrypt#Algorithm) +- `SCRYPT_N`: **65536**: The `CostFactor N` for Scrypt hashing. Must be a power of two. [More info on the scrypt hashing algorithm.](https://en.wikipedia.org/wiki/Scrypt#Algorithm) +- `SCRYPT_R`: **16**: The `BlockSizeFactor r` for Scrypt hashing. +- `SCRYPT_P`: **2**: The `ParallelizationFactor p` for Scrypt hashing. +- `SCRYPT_KEY_LENGTH`: **50**: Desired key length for Scrypt hashing. +- `ARGON2_ITERATIONS`: **2**: Number of iterations for Argon2id hashing. [More info on the Argon2 hashing algorithm.](https://en.wikipedia.org/wiki/Argon2#Algorithm) +- `ARGON2_MEMORY`: **65536**: Amount of memory to use for Argon2id hashing. +- `ARGON2_PARALLELISM`: **8**: Number of threads to use for Argon2id hashing. +- `ARGON2_KEY_LENGTH`: **50**: Desired key length for Argon2id hashing. +- `PBKDF2_ITERATIONS`: **10000**: Number of iterations for PBKDF2 hashing. [More info on the PBKDF2 hashing algorithm.](https://en.wikipedia.org/wiki/PBKDF2) +- `PBKDF2_KEY_LENGTH`: **50**: Desired key length for PBKDF2 hashing. + ## OpenID (`openid`) - `ENABLE_OPENID_SIGNIN`: **false**: Allow authentication in via OpenID. diff --git a/models/login_source.go b/models/login_source.go index 4ce2744bfdc16..b96f72e95c3fb 100644 --- a/models/login_source.go +++ b/models/login_source.go @@ -14,7 +14,7 @@ import ( "strconv" "strings" - db "code.gitea.io/gitea/modules/auth/hash" + "code.gitea.io/gitea/modules/auth/hash" "code.gitea.io/gitea/modules/auth/ldap" "code.gitea.io/gitea/modules/auth/oauth2" "code.gitea.io/gitea/modules/auth/pam" @@ -780,7 +780,7 @@ func UserSignIn(username, password string) (*User, error) { if user.IsPasswordSet() && user.ValidatePassword(password) { // Update password hash if server password hash algorithm have changed - if db.DefaultHasher.PasswordNeedUpdate(user.PasswdHashAlgo) { + if hash.DefaultHasher.PasswordNeedUpdate(user.PasswdHashAlgo) { if err = user.SetPassword(password); err != nil { return nil, err } diff --git a/models/user.go b/models/user.go index 8e012313c39ed..cbf85ea166030 100644 --- a/models/user.go +++ b/models/user.go @@ -19,7 +19,7 @@ import ( "time" "unicode/utf8" - db "code.gitea.io/gitea/modules/auth/hash" + "code.gitea.io/gitea/modules/auth/hash" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/generate" "code.gitea.io/gitea/modules/git" @@ -393,14 +393,14 @@ func (u *User) SetPassword(passwd string) (err error) { return err } - u.Passwd, u.PasswdHashAlgo, err = db.DefaultHasher.HashPassword(passwd, u.Salt, "") + u.Passwd, u.PasswdHashAlgo, err = hash.DefaultHasher.HashPassword(passwd, u.Salt, "") return err } // ValidatePassword checks if given password matches the one belonging to the user. func (u *User) ValidatePassword(passwd string) bool { - return db.DefaultHasher.Validate(passwd, u.Passwd, u.Salt, u.PasswdHashAlgo) + return hash.DefaultHasher.Validate(passwd, u.Passwd, u.Salt, u.PasswdHashAlgo) } // IsPasswordSet checks if the password is set or left empty diff --git a/models/user_test.go b/models/user_test.go index 7f0c8e34d8f63..af9282acdc466 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -266,7 +266,7 @@ func TestOldPasswordMatchAndUpdate(t *testing.T) { assert.Equal(t, oldPass, newPass) // With update function - argonHasher := hash.DefaultHasher.Hashers["argon2"].(hash.Argon2Hasher) + argonHasher := hash.DefaultHasher.Hashers["argon2"].(*hash.Argon2Hasher) argonHasher.Iterations = 2 argonHasher.Memory = 65536 argonHasher.Parallelism = 8 diff --git a/modules/auth/hash/hash.go b/modules/auth/hash/hash.go index 433c3fb4c0d8a..da2bc011e6aec 100644 --- a/modules/auth/hash/hash.go +++ b/modules/auth/hash/hash.go @@ -28,7 +28,7 @@ type Hasher interface { } // HashPassword returns a PasswordHash, PassWordAlgo (and optionally an error) -func (d DefaultHasherStruct) HashPassword(password, salt, config string) (string, string, error) { +func (d *DefaultHasherStruct) HashPassword(password, salt, config string) (string, string, error) { hasher, ok := d.Hashers[d.DefaultAlgorithm] if !ok { hasher = d.Hashers[defaultAlgorithm] @@ -38,7 +38,7 @@ func (d DefaultHasherStruct) HashPassword(password, salt, config string) (string } // Validate validates a plain-text password -func (d DefaultHasherStruct) Validate(password, hash, salt, algo string) bool { +func (d *DefaultHasherStruct) Validate(password, hash, salt, algo string) bool { var typ, config string var hasher Hasher var ok bool @@ -61,7 +61,7 @@ func (d DefaultHasherStruct) Validate(password, hash, salt, algo string) bool { } // PasswordNeedUpdate determines if a password needs an update -func (d DefaultHasherStruct) PasswordNeedUpdate(algo string) bool { +func (d *DefaultHasherStruct) PasswordNeedUpdate(algo string) bool { var typ, tail string var hasher Hasher var ok bool @@ -82,10 +82,10 @@ func (d DefaultHasherStruct) PasswordNeedUpdate(algo string) bool { } // DefaultHasher is the instance of the HashSet -var DefaultHasher DefaultHasherStruct +var DefaultHasher *DefaultHasherStruct func init() { - DefaultHasher = DefaultHasherStruct{ + DefaultHasher = &DefaultHasherStruct{ DefaultAlgorithm: defaultAlgorithm, Hashers: make(map[string]Hasher), } diff --git a/modules/auth/hash/hash_argon2.go b/modules/auth/hash/hash_argon2.go index 5af36b0cd06c1..c1f21bebcf8bb 100644 --- a/modules/auth/hash/hash_argon2.go +++ b/modules/auth/hash/hash_argon2.go @@ -22,7 +22,7 @@ type Argon2Hasher struct { } // HashPassword returns a PasswordHash, PassWordAlgo (and optionally an error) -func (h Argon2Hasher) HashPassword(password, salt, config string) (string, string, error) { +func (h *Argon2Hasher) HashPassword(password, salt, config string) (string, string, error) { var tempPasswd []byte if config == "fallback" { // Fixed default config to match with original configuration @@ -66,12 +66,12 @@ func (h Argon2Hasher) HashPassword(password, salt, config string) (string, strin } // Validate validates a plain-text password -func (h Argon2Hasher) Validate(password, hash, salt, config string) bool { +func (h *Argon2Hasher) Validate(password, hash, salt, config string) bool { tempHash, _, _ := h.HashPassword(password, salt, config) return subtle.ConstantTimeCompare([]byte(hash), []byte(tempHash)) == 1 } -func (h Argon2Hasher) getConfigFromAlgo(algo string) string { +func (h *Argon2Hasher) getConfigFromAlgo(algo string) string { split := strings.SplitN(algo, "$", 2) if len(split) == 1 { split[1] = "fallback" @@ -79,10 +79,10 @@ func (h Argon2Hasher) getConfigFromAlgo(algo string) string { return split[1] } -func (h Argon2Hasher) getConfigFromSetting() string { +func (h *Argon2Hasher) getConfigFromSetting() string { return fmt.Sprintf("%d$%d$%d$%d", h.Iterations, h.Memory, h.Parallelism, h.KeyLength) } func init() { - DefaultHasher.Hashers["argon2"] = Argon2Hasher{2, 65536, 8, 50} + DefaultHasher.Hashers["argon2"] = &Argon2Hasher{2, 65536, 8, 50} } diff --git a/modules/auth/hash/hash_bcrypt.go b/modules/auth/hash/hash_bcrypt.go index 9da3e3b4c97c0..e6a2827fae7ef 100644 --- a/modules/auth/hash/hash_bcrypt.go +++ b/modules/auth/hash/hash_bcrypt.go @@ -18,7 +18,7 @@ type BCryptHasher struct { } // HashPassword returns a PasswordHash, PassWordAlgo (and optionally an error) -func (h BCryptHasher) HashPassword(password, salt, config string) (string, string, error) { +func (h *BCryptHasher) HashPassword(password, salt, config string) (string, string, error) { if config == "fallback" { // Fixed default config to match with original configuration config = "10" @@ -36,19 +36,19 @@ func (h BCryptHasher) HashPassword(password, salt, config string) (string, strin } // Validate validates a plain-text password -func (h BCryptHasher) Validate(password, hash, salt, config string) bool { +func (h *BCryptHasher) Validate(password, hash, salt, config string) bool { return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) == nil } -func (h BCryptHasher) getConfigFromAlgo(algo string) string { +func (h *BCryptHasher) getConfigFromAlgo(algo string) string { split := strings.SplitN(algo, "$", 2) return split[1] } -func (h BCryptHasher) getConfigFromSetting() string { +func (h *BCryptHasher) getConfigFromSetting() string { return strconv.Itoa(h.Cost) } func init() { - DefaultHasher.Hashers["bcrypt"] = BCryptHasher{10} + DefaultHasher.Hashers["bcrypt"] = &BCryptHasher{10} } diff --git a/modules/auth/hash/hash_pbkdf2.go b/modules/auth/hash/hash_pbkdf2.go index d50bf97c6d278..db5fc2aca5c46 100644 --- a/modules/auth/hash/hash_pbkdf2.go +++ b/modules/auth/hash/hash_pbkdf2.go @@ -21,7 +21,7 @@ type Pbkdf2Hasher struct { } // HashPassword returns a PasswordHash, PassWordAlgo (and optionally an error) -func (h Pbkdf2Hasher) HashPassword(password, salt, config string) (string, string, error) { +func (h *Pbkdf2Hasher) HashPassword(password, salt, config string) (string, string, error) { var tempPasswd []byte if config == "fallback" { // Fixed default config to match with original configuration @@ -50,12 +50,12 @@ func (h Pbkdf2Hasher) HashPassword(password, salt, config string) (string, strin } // Validate validates a plain-text password -func (h Pbkdf2Hasher) Validate(password, hash, salt, config string) bool { +func (h *Pbkdf2Hasher) Validate(password, hash, salt, config string) bool { tempHash, _, _ := h.HashPassword(password, salt, config) return subtle.ConstantTimeCompare([]byte(hash), []byte(tempHash)) == 1 } -func (h Pbkdf2Hasher) getConfigFromAlgo(algo string) string { +func (h *Pbkdf2Hasher) getConfigFromAlgo(algo string) string { split := strings.SplitN(algo, "$", 2) if len(split) == 1 { split[1] = "fallback" @@ -63,10 +63,10 @@ func (h Pbkdf2Hasher) getConfigFromAlgo(algo string) string { return split[1] } -func (h Pbkdf2Hasher) getConfigFromSetting() string { +func (h *Pbkdf2Hasher) getConfigFromSetting() string { return fmt.Sprintf("%d$%d", h.Iterations, h.KeyLength) } func init() { - DefaultHasher.Hashers["pbkdf2"] = Pbkdf2Hasher{10000, 50} + DefaultHasher.Hashers["pbkdf2"] = &Pbkdf2Hasher{10000, 50} } diff --git a/modules/auth/hash/hash_scrypt.go b/modules/auth/hash/hash_scrypt.go index ec36e6f03d422..619365559b9e8 100644 --- a/modules/auth/hash/hash_scrypt.go +++ b/modules/auth/hash/hash_scrypt.go @@ -22,7 +22,7 @@ type SCryptHasher struct { } // HashPassword returns a PasswordHash, PassWordAlgo (and optionally an error) -func (h SCryptHasher) HashPassword(password, salt, config string) (string, string, error) { +func (h *SCryptHasher) HashPassword(password, salt, config string) (string, string, error) { var tempPasswd []byte if config == "fallback" { // Fixed default config to match with original configuration @@ -57,12 +57,12 @@ func (h SCryptHasher) HashPassword(password, salt, config string) (string, strin } // Validate validates a plain-text password -func (h SCryptHasher) Validate(password, hash, salt, config string) bool { +func (h *SCryptHasher) Validate(password, hash, salt, config string) bool { tempHash, _, _ := h.HashPassword(password, salt, config) return subtle.ConstantTimeCompare([]byte(hash), []byte(tempHash)) == 1 } -func (h SCryptHasher) getConfigFromAlgo(algo string) string { +func (h *SCryptHasher) getConfigFromAlgo(algo string) string { split := strings.SplitN(algo, "$", 2) if len(split) == 1 { split[1] = "fallback" @@ -70,10 +70,10 @@ func (h SCryptHasher) getConfigFromAlgo(algo string) string { return split[1] } -func (h SCryptHasher) getConfigFromSetting() string { +func (h *SCryptHasher) getConfigFromSetting() string { return fmt.Sprintf("%d$%d$%d$%d", h.N, h.R, h.P, h.KeyLength) } func init() { - DefaultHasher.Hashers["scrypt"] = SCryptHasher{65536, 16, 2, 50} + DefaultHasher.Hashers["scrypt"] = &SCryptHasher{65536, 16, 2, 50} } diff --git a/modules/setting/password_hash.go b/modules/setting/password_hash.go index b9212b0d7823f..f866dff1d7e2b 100644 --- a/modules/setting/password_hash.go +++ b/modules/setting/password_hash.go @@ -20,6 +20,6 @@ func newPasswordHashService() { sec := Cfg.Section("security.password_hash") for _, hasher := range hash.DefaultHasher.Hashers { - sec.MapTo(hasher) + _ = sec.MapTo(hasher) } } diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 08b202d192f84..a120011185645 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -2349,6 +2349,16 @@ config.log_file_root_path = Log Path config.script_type = Script Type config.reverse_auth_user = Reverse Authentication User +config.hasher_selected = Password Hash Algorithm +config.hasher_param_cost = Cost +config.hasher_param_n = CostFactor (N) +config.hasher_param_r = BlockSizeFactor (r) +config.hasher_param_p = ParallelizationFactor (p) +config.hasher_param_keylength = Key Length +config.hasher_param_iterations = Iterations +config.hasher_param_memory = Memory +config.hasher_param_parallelism = Parallelism + config.ssh_config = SSH Configuration config.ssh_enabled = Enabled config.ssh_start_builtin_server = Use Built-In Server diff --git a/routers/admin/admin.go b/routers/admin/admin.go index 49f6a394a6ef2..d3d0135103958 100644 --- a/routers/admin/admin.go +++ b/routers/admin/admin.go @@ -15,6 +15,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/auth/hash" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/cron" @@ -314,6 +315,8 @@ func Config(ctx *context.Context) { ctx.Data["DisableRouterLog"] = setting.DisableRouterLog ctx.Data["EnableXORMLog"] = setting.EnableXORMLog ctx.Data["LogSQL"] = setting.Database.LogSQL + ctx.Data["SelectedHasherParams"] = hash.DefaultHasher.Hashers[hash.DefaultHasher.DefaultAlgorithm] + ctx.Data["SelectedHasher"] = hash.DefaultHasher.DefaultAlgorithm ctx.HTML(200, tplConfig) } diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl index a3c55b2b18c28..87fbe8b1fe336 100644 --- a/templates/admin/config.tmpl +++ b/templates/admin/config.tmpl @@ -50,6 +50,38 @@
{{.i18n.Tr "admin.config.reverse_auth_user"}}
{{.ReverseProxyAuthUser}}
+
+ +
{{.i18n.Tr "admin.config.hasher_selected"}}
+
{{.SelectedHasher}}
+ {{ if eq .SelectedHasher "bcrypt" }} +
{{.i18n.Tr "admin.config.hasher_param_cost"}}
+
{{.SelectedHasherParams.Cost}}
+ {{else if eq .SelectedHasher "scrypt"}} +
{{.i18n.Tr "admin.config.hasher_param_n"}}
+
{{.SelectedHasherParams.N}}
+
{{.i18n.Tr "admin.config.hasher_param_r"}}
+
{{.SelectedHasherParams.R}}
+
{{.i18n.Tr "admin.config.hasher_param_p"}}
+
{{.SelectedHasherParams.P}}
+
{{.i18n.Tr "admin.config.hasher_param_keylength"}}
+
{{.SelectedHasherParams.KeyKength}}
+ {{else if eq .SelectedHasher "argon2"}} +
{{.i18n.Tr "admin.config.hasher_param_iterations"}}
+
{{.SelectedHasherParams.Iterations}}
+
{{.i18n.Tr "admin.config.hasher_param_memory"}}
+
{{.SelectedHasherParams.Memory}}
+
{{.i18n.Tr "admin.config.hasher_param_parallelism"}}
+
{{.SelectedHasherParams.Parallelism}}
+
{{.i18n.Tr "admin.config.hasher_param_keylength"}}
+
{{.SelectedHasherParams.KeyLength}}
+ {{else if eq .SelectedHasher "pbkdf2"}} +
{{.i18n.Tr "admin.config.hasher_param_iterations"}}
+
{{.SelectedHasherParams.Iterations}}
+
{{.i18n.Tr "admin.config.hasher_param_keylength"}}
+
{{.SelectedHasherParams.KeyLength}}
+ {{end}} + {{if .EnvVars }}
{{range .EnvVars}} From dc1654754ef927a72a07c3c090072055e761b53e Mon Sep 17 00:00:00 2001 From: Henning Bopp Date: Sat, 20 Mar 2021 15:30:04 +0100 Subject: [PATCH 7/9] Fix naming and initialize issues --- modules/auth/hash/{hash_argon2.go => argon2.go} | 0 modules/auth/hash/{hash_bcrypt.go => bcrypt.go} | 0 modules/auth/hash/hash.go | 10 +++------- modules/auth/hash/{hash_pbkdf2.go => pbkdf2.go} | 0 modules/auth/hash/{hash_scrypt.go => scrypt.go} | 0 5 files changed, 3 insertions(+), 7 deletions(-) rename modules/auth/hash/{hash_argon2.go => argon2.go} (100%) rename modules/auth/hash/{hash_bcrypt.go => bcrypt.go} (100%) rename modules/auth/hash/{hash_pbkdf2.go => pbkdf2.go} (100%) rename modules/auth/hash/{hash_scrypt.go => scrypt.go} (100%) diff --git a/modules/auth/hash/hash_argon2.go b/modules/auth/hash/argon2.go similarity index 100% rename from modules/auth/hash/hash_argon2.go rename to modules/auth/hash/argon2.go diff --git a/modules/auth/hash/hash_bcrypt.go b/modules/auth/hash/bcrypt.go similarity index 100% rename from modules/auth/hash/hash_bcrypt.go rename to modules/auth/hash/bcrypt.go diff --git a/modules/auth/hash/hash.go b/modules/auth/hash/hash.go index da2bc011e6aec..8e13c67e3a997 100644 --- a/modules/auth/hash/hash.go +++ b/modules/auth/hash/hash.go @@ -82,11 +82,7 @@ func (d *DefaultHasherStruct) PasswordNeedUpdate(algo string) bool { } // DefaultHasher is the instance of the HashSet -var DefaultHasher *DefaultHasherStruct - -func init() { - DefaultHasher = &DefaultHasherStruct{ - DefaultAlgorithm: defaultAlgorithm, - Hashers: make(map[string]Hasher), - } +var DefaultHasher = &DefaultHasherStruct{ + DefaultAlgorithm: defaultAlgorithm, + Hashers: make(map[string]Hasher), } diff --git a/modules/auth/hash/hash_pbkdf2.go b/modules/auth/hash/pbkdf2.go similarity index 100% rename from modules/auth/hash/hash_pbkdf2.go rename to modules/auth/hash/pbkdf2.go diff --git a/modules/auth/hash/hash_scrypt.go b/modules/auth/hash/scrypt.go similarity index 100% rename from modules/auth/hash/hash_scrypt.go rename to modules/auth/hash/scrypt.go From e091e7beac6db27171414c09643e034f75380097 Mon Sep 17 00:00:00 2001 From: Henning Bopp Date: Sun, 4 Jul 2021 18:02:56 +0200 Subject: [PATCH 8/9] Fix Code to allow Merge with main branch --- custom/conf/app.example.ini | 3341 +++++++++++++++++---------- models/fixtures/user.yml | 18 + models/login_source.go | 65 +- models/user_test.go | 8 +- routers/install/install.go | 474 ++++ routers/web/admin/admin.go | 488 ++++ routers/web/user/setting/account.go | 314 +++ 7 files changed, 3449 insertions(+), 1259 deletions(-) create mode 100644 routers/install/install.go create mode 100644 routers/web/admin/admin.go create mode 100644 routers/web/user/setting/account.go diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index fb34145da614a..6c23d59b96f61 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1,1281 +1,2138 @@ ; This file lists the default values used by Gitea -; Copy required sections to your own app.ini (default is custom/conf/app.ini) -; and modify as needed. -; Do not copy the whole file as-is, as it contains some invalid sections for illustrative purposes. -; If you don't know what a setting is you should not set it. - -; see https://docs.gitea.io/en-us/config-cheat-sheet/ for additional documentation. - -; App name that shows in every page title -APP_NAME = Gitea: Git with a cup of tea -; Change it if you run locally -RUN_USER = git -; Application run mode, affects performance and debugging. Either "dev", "prod" or "test", default is "prod" -RUN_MODE = prod - -[project] -; Default templates for project boards -PROJECT_BOARD_BASIC_KANBAN_TYPE = To Do, In Progress, Done -PROJECT_BOARD_BUG_TRIAGE_TYPE = Needs Triage, High Priority, Low Priority, Closed - -[repository] -; Root path for storing all repository data. It must be an absolute path. By default it is stored in a sub-directory of `APP_DATA_PATH`. -ROOT = -; The script type this server supports. Usually this is `bash`, but some users report that only `sh` is available. -SCRIPT_TYPE = bash -; DETECTED_CHARSETS_ORDER tie-break order for detected charsets. -; If the charsets have equal confidence, tie-breaking will be done by order in this list -; with charsets earlier in the list chosen in preference to those later. -; Adding "defaults" will place the unused charsets at that position. -DETECTED_CHARSETS_ORDER = UTF-8, UTF-16BE, UTF-16LE, UTF-32BE, UTF-32LE, ISO-8859, windows-1252, ISO-8859, windows-1250, ISO-8859, ISO-8859, ISO-8859, windows-1253, ISO-8859, windows-1255, ISO-8859, windows-1251, windows-1256, KOI8-R, ISO-8859, windows-1254, Shift_JIS, GB18030, EUC-JP, EUC-KR, Big5, ISO-2022, ISO-2022, ISO-2022, IBM424_rtl, IBM424_ltr, IBM420_rtl, IBM420_ltr -; Default ANSI charset to override non-UTF-8 charsets to -ANSI_CHARSET = -; Force every new repository to be private -FORCE_PRIVATE = false -; Default privacy setting when creating a new repository, allowed values: last, private, public. Default is last which means the last setting used. -DEFAULT_PRIVATE = last -; Default private when using push-to-create -DEFAULT_PUSH_CREATE_PRIVATE = true -; Global limit of repositories per user, applied at creation time. -1 means no limit -MAX_CREATION_LIMIT = -1 -; Mirror sync queue length, increase if mirror syncing starts hanging -MIRROR_QUEUE_LENGTH = 1000 -; Patch test queue length, increase if pull request patch testing starts hanging -PULL_REQUEST_QUEUE_LENGTH = 1000 -; Preferred Licenses to place at the top of the List -; The name here must match the filename in conf/license or custom/conf/license -PREFERRED_LICENSES = Apache License 2.0,MIT License -; Disable the ability to interact with repositories using the HTTP protocol -DISABLE_HTTP_GIT = false -; Value for Access-Control-Allow-Origin header, default is not to present -; WARNING: This maybe harmful to you website if you do not give it a right value. -ACCESS_CONTROL_ALLOW_ORIGIN = -; Force ssh:// clone url instead of scp-style uri when default SSH port is used -USE_COMPAT_SSH_URI = false -; Close issues as long as a commit on any branch marks it as fixed -DEFAULT_CLOSE_ISSUES_VIA_COMMITS_IN_ANY_BRANCH = false -; Allow users to push local repositories to Gitea and have them automatically created for a user or an org -ENABLE_PUSH_CREATE_USER = false -ENABLE_PUSH_CREATE_ORG = false -; Comma separated list of globally disabled repo units. Allowed values: repo.issues, repo.ext_issues, repo.pulls, repo.wiki, repo.ext_wiki -DISABLED_REPO_UNITS = -; Comma separated list of default repo units. Allowed values: repo.code, repo.releases, repo.issues, repo.pulls, repo.wiki, repo.projects. -; Note: Code and Releases can currently not be deactivated. If you specify default repo units you should still list them for future compatibility. -; External wiki and issue tracker can't be enabled by default as it requires additional settings. -; Disabled repo units will not be added to new repositories regardless if it is in the default list. -DEFAULT_REPO_UNITS = repo.code,repo.releases,repo.issues,repo.pulls,repo.wiki,repo.projects -; Prefix archive files by placing them in a directory named after the repository -PREFIX_ARCHIVE_FILES = true -; Disable the creation of new mirrors. Pre-existing mirrors remain valid. -DISABLE_MIRRORS = false -; Disable migrating feature. -DISABLE_MIGRATIONS = false -; The default branch name of new repositories -DEFAULT_BRANCH = master -; Allow adoption of unadopted repositories -ALLOW_ADOPTION_OF_UNADOPTED_REPOSITORIES = false -; Allow deletion of unadopted repositories -ALLOW_DELETION_OF_UNADOPTED_REPOSITORIES = false - -[repository.editor] -; List of file extensions for which lines should be wrapped in the Monaco editor -; Separate extensions with a comma. To line wrap files without an extension, just put a comma -LINE_WRAP_EXTENSIONS = .txt,.md,.markdown,.mdown,.mkd, -; Valid file modes that have a preview API associated with them, such as api/v1/markdown -; Separate the values by commas. The preview tab in edit mode won't be displayed if the file extension doesn't match -PREVIEWABLE_FILE_MODES = markdown - -[repository.local] -; Path for local repository copy. Defaults to `tmp/local-repo` -LOCAL_COPY_PATH = tmp/local-repo - -[repository.upload] -; Whether repository file uploads are enabled. Defaults to `true` -ENABLED = true -; Path for uploads. Defaults to `data/tmp/uploads` (tmp gets deleted on gitea restart) -TEMP_PATH = data/tmp/uploads -; Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types. -ALLOWED_TYPES = -; Max size of each file in megabytes. Defaults to 3MB -FILE_MAX_SIZE = 3 -; Max number of files per upload. Defaults to 5 -MAX_FILES = 5 - -[repository.pull-request] -; List of prefixes used in Pull Request title to mark them as Work In Progress -WORK_IN_PROGRESS_PREFIXES = WIP:,[WIP] -; List of keywords used in Pull Request comments to automatically close a related issue -CLOSE_KEYWORDS = close,closes,closed,fix,fixes,fixed,resolve,resolves,resolved -; List of keywords used in Pull Request comments to automatically reopen a related issue -REOPEN_KEYWORDS = reopen,reopens,reopened -; In the default merge message for squash commits include at most this many commits -DEFAULT_MERGE_MESSAGE_COMMITS_LIMIT = 50 -; In the default merge message for squash commits limit the size of the commit messages to this -DEFAULT_MERGE_MESSAGE_SIZE = 5120 -; In the default merge message for squash commits walk all commits to include all authors in the Co-authored-by otherwise just use those in the limited list -DEFAULT_MERGE_MESSAGE_ALL_AUTHORS = false -; In default merge messages limit the number of approvers listed as Reviewed-by: to this many -DEFAULT_MERGE_MESSAGE_MAX_APPROVERS = 10 -; In default merge messages only include approvers who are official -DEFAULT_MERGE_MESSAGE_OFFICIAL_APPROVERS_ONLY = true - -[repository.issue] -; List of reasons why a Pull Request or Issue can be locked -LOCK_REASONS = Too heated,Off-topic,Resolved,Spam - -[repository.release] -; Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types. -ALLOWED_TYPES = - -[repository.signing] -; GPG key to use to sign commits, Defaults to the default - that is the value of git config --get user.signingkey -; run in the context of the RUN_USER -; Switch to none to stop signing completely -SIGNING_KEY = default -; If a SIGNING_KEY ID is provided and is not set to default, use the provided Name and Email address as the signer. -; These should match a publicized name and email address for the key. (When SIGNING_KEY is default these are set to -; the results of git config --get user.name and git config --get user.email respectively and can only be overrided -; by setting the SIGNING_KEY ID to the correct ID.) -SIGNING_NAME = -SIGNING_EMAIL = -; Sets the default trust model for repositories. Options are: collaborator, committer, collaboratorcommitter -DEFAULT_TRUST_MODEL = collaborator -; Determines when gitea should sign the initial commit when creating a repository -; Either: -; - never -; - pubkey: only sign if the user has a pubkey -; - twofa: only sign if the user has logged in with twofa -; - always -; options other than none and always can be combined as comma separated list -INITIAL_COMMIT = always -; Determines when to sign for CRUD actions -; - as above -; - parentsigned: requires that the parent commit is signed. -CRUD_ACTIONS = pubkey, twofa, parentsigned -; Determines when to sign Wiki commits -; - as above -WIKI = never -; Determines when to sign on merges -; - basesigned: require that the parent of commit on the base repo is signed. -; - commitssigned: require that all the commits in the head branch are signed. -; - approved: only sign when merging an approved pr to a protected branch -MERGES = pubkey, twofa, basesigned, commitssigned - -[cors] -; More information about CORS can be found here: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#The_HTTP_response_headers -; enable cors headers (disabled by default) -ENABLED = false -; scheme of allowed requests -SCHEME = http -; list of requesting domains that are allowed -ALLOW_DOMAIN = * -; allow subdomains of headers listed above to request -ALLOW_SUBDOMAIN = false -; list of methods allowed to request -METHODS = GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS -; max time to cache response -MAX_AGE = 10m -; allow request with credentials -ALLOW_CREDENTIALS = false - -[ui] -; Number of repositories that are displayed on one explore page -EXPLORE_PAGING_NUM = 20 -; Number of issues that are displayed on one page -ISSUE_PAGING_NUM = 10 -; Number of maximum commits displayed in one activity feed -FEED_MAX_COMMIT_NUM = 5 -; Number of items that are displayed in home feed -FEED_PAGING_NUM = 20 -; Number of maximum commits displayed in commit graph. -GRAPH_MAX_COMMIT_NUM = 100 -; Number of line of codes shown for a code comment -CODE_COMMENT_LINES = 4 -; Value of `theme-color` meta tag, used by Android >= 5.0 -; An invalid color like "none" or "disable" will have the default style -; More info: https://developers.google.com/web/updates/2014/11/Support-for-theme-color-in-Chrome-39-for-Android -THEME_COLOR_META_TAG = `#6cc644` -; Max size of files to be displayed (default is 8MiB) -MAX_DISPLAY_FILE_SIZE = 8388608 -; Whether the email of the user should be shown in the Explore Users page -SHOW_USER_EMAIL = true -; Set the default theme for the Gitea install -DEFAULT_THEME = gitea -; All available themes. Allow users select personalized themes regardless of the value of `DEFAULT_THEME`. -THEMES = gitea,arc-green -;All available reactions users can choose on issues/prs and comments. -;Values can be emoji alias (:smile:) or a unicode emoji. -;For custom reactions, add a tightly cropped square image to public/emoji/img/reaction_name.png -REACTIONS = +1, -1, laugh, hooray, confused, heart, rocket, eyes -; Whether the full name of the users should be shown where possible. If the full name isn't set, the username will be used. -DEFAULT_SHOW_FULL_NAME = false -; Whether to search within description at repository search on explore page. -SEARCH_REPO_DESCRIPTION = true -; Whether to enable a Service Worker to cache frontend assets -USE_SERVICE_WORKER = true - -[ui.admin] -; Number of users that are displayed on one page -USER_PAGING_NUM = 50 -; Number of repos that are displayed on one page -REPO_PAGING_NUM = 50 -; Number of notices that are displayed on one page -NOTICE_PAGING_NUM = 25 -; Number of organizations that are displayed on one page -ORG_PAGING_NUM = 50 - -[ui.user] -; Number of repos that are displayed on one page -REPO_PAGING_NUM = 15 - -[ui.meta] -AUTHOR = Gitea - Git with a cup of tea -DESCRIPTION = Gitea (Git with a cup of tea) is a painless self-hosted Git service written in Go -KEYWORDS = go,git,self-hosted,gitea - -[ui.notification] -; Control how often the notification endpoint is polled to update the notification -; The timeout will increase to MAX_TIMEOUT in TIMEOUT_STEPs if the notification count is unchanged -; Set MIN_TIMEOUT to 0 to turn off -MIN_TIMEOUT = 10s -MAX_TIMEOUT = 60s -TIMEOUT_STEP = 10s -; This setting determines how often the db is queried to get the latest notification counts. -; If the browser client supports EventSource and SharedWorker, a SharedWorker will be used in preference to polling notification. Set to -1 to disable the EventSource -EVENT_SOURCE_UPDATE_TIME = 10s - -[ui.svg] -; Whether to render SVG files as images. If SVG rendering is disabled, SVG files are displayed as text and cannot be embedded in markdown files as images. -ENABLE_RENDER = true - -[markdown] -; Render soft line breaks as hard line breaks, which means a single newline character between -; paragraphs will cause a line break and adding trailing whitespace to paragraphs is not -; necessary to force a line break. -; Render soft line breaks as hard line breaks for comments -ENABLE_HARD_LINE_BREAK_IN_COMMENTS = true -; Render soft line breaks as hard line breaks for markdown documents -ENABLE_HARD_LINE_BREAK_IN_DOCUMENTS = false -; Comma separated list of custom URL-Schemes that are allowed as links when rendering Markdown -; for example git,magnet,ftp (more at https://en.wikipedia.org/wiki/List_of_URI_schemes) -; URLs starting with http and https are always displayed, whatever is put in this entry. -CUSTOM_URL_SCHEMES = -; List of file extensions that should be rendered/edited as Markdown -; Separate the extensions with a comma. To render files without any extension as markdown, just put a comma -FILE_EXTENSIONS = .md,.markdown,.mdown,.mkd - +;; Copy required sections to your own app.ini (default is custom/conf/app.ini) +;; and modify as needed. +;; Do not copy the whole file as-is, as it contains some invalid sections for illustrative purposes. +;; If you don't know what a setting is you should not set it. +;; +;; see https://docs.gitea.io/en-us/config-cheat-sheet/ for additional documentation. + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; General Settings +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; App name that shows in every page title +APP_NAME = ; Gitea: Git with a cup of tea +;; +;; RUN_USER will automatically detect the current user - but you can set it here change it if you run locally +RUN_USER = ; git +;; +;; Application run mode, affects performance and debugging. Either "dev", "prod" or "test", default is "prod" +RUN_MODE = ; prod + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; [server] -; The protocol the server listens on. One of 'http', 'https', 'unix' or 'fcgi'. -PROTOCOL = http -DOMAIN = localhost -ROOT_URL = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s/ -; when STATIC_URL_PREFIX is empty it will follow ROOT_URL -STATIC_URL_PREFIX = -; The address to listen on. Either a IPv4/IPv6 address or the path to a unix socket. -HTTP_ADDR = 0.0.0.0 -; The port to listen on. Leave empty when using a unix socket. -HTTP_PORT = 3000 -; If REDIRECT_OTHER_PORT is true, and PROTOCOL is set to https an http server -; will be started on PORT_TO_REDIRECT and it will redirect plain, non-secure http requests to the main -; ROOT_URL. Defaults are false for REDIRECT_OTHER_PORT and 80 for -; PORT_TO_REDIRECT. -REDIRECT_OTHER_PORT = false -PORT_TO_REDIRECT = 80 -; Permission for unix socket -UNIX_SOCKET_PERMISSION = 666 -; Local (DMZ) URL for Gitea workers (such as SSH update) accessing web service. -; In most cases you do not need to change the default value. -; Alter it only if your SSH server node is not the same as HTTP node. -; Do not set this variable if PROTOCOL is set to 'unix'. -LOCAL_ROOT_URL = %(PROTOCOL)s://%(HTTP_ADDR)s:%(HTTP_PORT)s/ -; Disable SSH feature when not available -DISABLE_SSH = false -; Whether to use the builtin SSH server or not. -START_SSH_SERVER = false -; Username to use for the builtin SSH server. If blank, then it is the value of RUN_USER. -BUILTIN_SSH_SERVER_USER = -; Domain name to be exposed in clone URL -SSH_DOMAIN = %(DOMAIN)s -; The network interface the builtin SSH server should listen on -SSH_LISTEN_HOST = -; Port number to be exposed in clone URL -SSH_PORT = 22 -; The port number the builtin SSH server should listen on -SSH_LISTEN_PORT = %(SSH_PORT)s -; Root path of SSH directory, default is '~/.ssh', but you have to use '/home/git/.ssh'. -SSH_ROOT_PATH = -; Gitea will create a authorized_keys file by default when it is not using the internal ssh server -; If you intend to use the AuthorizedKeysCommand functionality then you should turn this off. -SSH_CREATE_AUTHORIZED_KEYS_FILE = true -; Gitea will create a authorized_principals file by default when it is not using the internal ssh server -; If you intend to use the AuthorizedPrincipalsCommand functionality then you should turn this off. -SSH_CREATE_AUTHORIZED_PRINCIPALS_FILE = true -; For the built-in SSH server, choose the ciphers to support for SSH connections, -; for system SSH this setting has no effect -SSH_SERVER_CIPHERS = aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, arcfour256, arcfour128 -; For the built-in SSH server, choose the key exchange algorithms to support for SSH connections, -; for system SSH this setting has no effect -SSH_SERVER_KEY_EXCHANGES = diffie-hellman-group1-sha1, diffie-hellman-group14-sha1, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, curve25519-sha256@libssh.org -; For the built-in SSH server, choose the MACs to support for SSH connections, -; for system SSH this setting has no effect -SSH_SERVER_MACS = hmac-sha2-256-etm@openssh.com, hmac-sha2-256, hmac-sha1, hmac-sha1-96 -; Directory to create temporary files in when testing public keys using ssh-keygen, -; default is the system temporary directory. -SSH_KEY_TEST_PATH = -; Path to ssh-keygen, default is 'ssh-keygen' which means the shell is responsible for finding out which one to call. -SSH_KEYGEN_PATH = ssh-keygen -; Enable SSH Authorized Key Backup when rewriting all keys, default is true -SSH_AUTHORIZED_KEYS_BACKUP = true -; Determines which principals to allow -; - empty: if SSH_TRUSTED_USER_CA_KEYS is empty this will default to off, otherwise will default to email, username. -; - off: Do not allow authorized principals -; - email: the principal must match the user's email -; - username: the principal must match the user's username -; - anything: there will be no checking on the content of the principal -SSH_AUTHORIZED_PRINCIPALS_ALLOW = email, username -; Enable SSH Authorized Principals Backup when rewriting all keys, default is true -SSH_AUTHORIZED_PRINCIPALS_BACKUP = true -; Specifies the public keys of certificate authorities that are trusted to sign user certificates for authentication. -; Multiple keys should be comma separated. -; E.g."ssh- ". or "ssh- , ssh- ". -; For more information see "TrustedUserCAKeys" in the sshd config manpages. -SSH_TRUSTED_USER_CA_KEYS = -; Absolute path of the `TrustedUserCaKeys` file gitea will manage. -; Default this `RUN_USER`/.ssh/gitea-trusted-user-ca-keys.pem -; If you're running your own ssh server and you want to use the gitea managed file you'll also need to modify your -; sshd_config to point to this file. The official docker image will automatically work without further configuration. -SSH_TRUSTED_USER_CA_KEYS_FILENAME = -; Enable exposure of SSH clone URL to anonymous visitors, default is false -SSH_EXPOSE_ANONYMOUS = false -; Indicate whether to check minimum key size with corresponding type -MINIMUM_KEY_SIZE_CHECK = false -; Disable CDN even in "prod" mode -OFFLINE_MODE = false -DISABLE_ROUTER_LOG = false -; Generate steps: -; $ ./gitea cert -ca=true -duration=8760h0m0s -host=myhost.example.com -; -; Or from a .pfx file exported from the Windows certificate store (do -; not forget to export the private key): -; $ openssl pkcs12 -in cert.pfx -out cert.pem -nokeys -; $ openssl pkcs12 -in cert.pfx -out key.pem -nocerts -nodes -; Paths are relative to CUSTOM_PATH -CERT_FILE = https/cert.pem -KEY_FILE = https/key.pem -; Root directory containing templates and static files. -; default is the path where Gitea is executed -STATIC_ROOT_PATH = -; Default path for App data -APP_DATA_PATH = data -; Enable gzip compression for runtime-generated content, static resources excluded -ENABLE_GZIP = false -; Application profiling (memory and cpu) -; For "web" command it listens on localhost:6060 -; For "serve" command it dumps to disk at PPROF_DATA_PATH as (cpuprofile|memprofile)__ -ENABLE_PPROF = false -; PPROF_DATA_PATH, use an absolute path when you start gitea as service -PPROF_DATA_PATH = data/tmp/pprof -; Landing page, can be "home", "explore", "organizations" or "login" -; The "login" choice is not a security measure but just a UI flow change, use REQUIRE_SIGNIN_VIEW to force users to log in. -LANDING_PAGE = home -; Enables git-lfs support. true or false, default is false. -LFS_START_SERVER = false -; Where your lfs files reside, default is data/lfs. -LFS_CONTENT_PATH = data/lfs -; LFS authentication secret, change this yourself +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; The protocol the server listens on. One of 'http', 'https', 'unix' or 'fcgi'. Defaults to 'http' +;PROTOCOL = http +;; +;; Set the domain for the server +;DOMAIN = localhost +;; +;; Overwrite the automatically generated public URL. Necessary for proxies and docker. +;ROOT_URL = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s/ +;; +;; when STATIC_URL_PREFIX is empty it will follow ROOT_URL +;STATIC_URL_PREFIX = +;; +;; The address to listen on. Either a IPv4/IPv6 address or the path to a unix socket. +;HTTP_ADDR = 0.0.0.0 +;; +;; The port to listen on. Leave empty when using a unix socket. +;HTTP_PORT = 3000 +;; +;; If REDIRECT_OTHER_PORT is true, and PROTOCOL is set to https an http server +;; will be started on PORT_TO_REDIRECT and it will redirect plain, non-secure http requests to the main +;; ROOT_URL. Defaults are false for REDIRECT_OTHER_PORT and 80 for +;; PORT_TO_REDIRECT. +;REDIRECT_OTHER_PORT = false +;PORT_TO_REDIRECT = 80 +;; +;; Timeout for any write to the connection. (Set to 0 to disable all timeouts.) +;PER_WRITE_TIMEOUT = 30s +;; +;; Timeout per Kb written to connections. +;PER_WRITE_PER_KB_TIMEOUT = 30s +;; +;; Permission for unix socket +;UNIX_SOCKET_PERMISSION = 666 +;; +;; Local (DMZ) URL for Gitea workers (such as SSH update) accessing web service. +;; In most cases you do not need to change the default value. +;; Alter it only if your SSH server node is not the same as HTTP node. +;; Do not set this variable if PROTOCOL is set to 'unix'. +;LOCAL_ROOT_URL = %(PROTOCOL)s://%(HTTP_ADDR)s:%(HTTP_PORT)s/ +;; +;; Disable SSH feature when not available +;DISABLE_SSH = false +;; +;; Whether to use the builtin SSH server or not. +;START_SSH_SERVER = false +;; +;; Username to use for the builtin SSH server. If blank, then it is the value of RUN_USER. +;BUILTIN_SSH_SERVER_USER = +;; +;; Domain name to be exposed in clone URL +;SSH_DOMAIN = %(DOMAIN)s +;; +;; The network interface the builtin SSH server should listen on +;SSH_LISTEN_HOST = +;; +;; Port number to be exposed in clone URL +;SSH_PORT = 22 +;; +;; The port number the builtin SSH server should listen on +;SSH_LISTEN_PORT = %(SSH_PORT)s +;; +;; Root path of SSH directory, default is '~/.ssh', but you have to use '/home/git/.ssh'. +;SSH_ROOT_PATH = +;; +;; Gitea will create a authorized_keys file by default when it is not using the internal ssh server +;; If you intend to use the AuthorizedKeysCommand functionality then you should turn this off. +;SSH_CREATE_AUTHORIZED_KEYS_FILE = true +;; +;; Gitea will create a authorized_principals file by default when it is not using the internal ssh server +;; If you intend to use the AuthorizedPrincipalsCommand functionality then you should turn this off. +;SSH_CREATE_AUTHORIZED_PRINCIPALS_FILE = true +;; +;; For the built-in SSH server, choose the ciphers to support for SSH connections, +;; for system SSH this setting has no effect +;SSH_SERVER_CIPHERS = aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, arcfour256, arcfour128 +;; +;; For the built-in SSH server, choose the key exchange algorithms to support for SSH connections, +;; for system SSH this setting has no effect +;SSH_SERVER_KEY_EXCHANGES = diffie-hellman-group1-sha1, diffie-hellman-group14-sha1, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, curve25519-sha256@libssh.org +;; +;; For the built-in SSH server, choose the MACs to support for SSH connections, +;; for system SSH this setting has no effect +;SSH_SERVER_MACS = hmac-sha2-256-etm@openssh.com, hmac-sha2-256, hmac-sha1, hmac-sha1-96 +;; +;; For the built-in SSH server, choose the keypair to offer as the host key +;; The private key should be at SSH_SERVER_HOST_KEY and the public SSH_SERVER_HOST_KEY.pub +;; relative paths are made absolute relative to the APP_DATA_PATH +;SSH_SERVER_HOST_KEYS=ssh/gitea.rsa, ssh/gogs.rsa +;; +;; Directory to create temporary files in when testing public keys using ssh-keygen, +;; default is the system temporary directory. +;SSH_KEY_TEST_PATH = +;; +;; Path to ssh-keygen, default is 'ssh-keygen' which means the shell is responsible for finding out which one to call. +;SSH_KEYGEN_PATH = ssh-keygen +;; +;; Enable SSH Authorized Key Backup when rewriting all keys, default is true +;SSH_AUTHORIZED_KEYS_BACKUP = true +;; +;; Determines which principals to allow +;; - empty: if SSH_TRUSTED_USER_CA_KEYS is empty this will default to off, otherwise will default to email, username. +;; - off: Do not allow authorized principals +;; - email: the principal must match the user's email +;; - username: the principal must match the user's username +;; - anything: there will be no checking on the content of the principal +;SSH_AUTHORIZED_PRINCIPALS_ALLOW = email, username +;; +;; Enable SSH Authorized Principals Backup when rewriting all keys, default is true +;SSH_AUTHORIZED_PRINCIPALS_BACKUP = true +;; +;; Specifies the public keys of certificate authorities that are trusted to sign user certificates for authentication. +;; Multiple keys should be comma separated. +;; E.g."ssh- ". or "ssh- , ssh- ". +;; For more information see "TrustedUserCAKeys" in the sshd config manpages. +;SSH_TRUSTED_USER_CA_KEYS = +;; Absolute path of the `TrustedUserCaKeys` file gitea will manage. +;; Default this `RUN_USER`/.ssh/gitea-trusted-user-ca-keys.pem +;; If you're running your own ssh server and you want to use the gitea managed file you'll also need to modify your +;; sshd_config to point to this file. The official docker image will automatically work without further configuration. +;SSH_TRUSTED_USER_CA_KEYS_FILENAME = +;; +;; Enable exposure of SSH clone URL to anonymous visitors, default is false +;SSH_EXPOSE_ANONYMOUS = false +;; +;; Timeout for any write to ssh connections. (Set to 0 to disable all timeouts.) +;; Will default to the PER_WRITE_TIMEOUT. +;SSH_PER_WRITE_TIMEOUT = 30s +;; +;; Timeout per Kb written to ssh connections. +;; Will default to the PER_WRITE_PER_KB_TIMEOUT. +;SSH_PER_WRITE_PER_KB_TIMEOUT = 30s +;; +;; Indicate whether to check minimum key size with corresponding type +;MINIMUM_KEY_SIZE_CHECK = false +;; +;; Disable CDN even in "prod" mode +;OFFLINE_MODE = false +;DISABLE_ROUTER_LOG = false +;; +;; Generate steps: +;; $ ./gitea cert -ca=true -duration=8760h0m0s -host=myhost.example.com +;; +;; Or from a .pfx file exported from the Windows certificate store (do +;; not forget to export the private key): +;; $ openssl pkcs12 -in cert.pfx -out cert.pem -nokeys +;; $ openssl pkcs12 -in cert.pfx -out key.pem -nocerts -nodes +;; Paths are relative to CUSTOM_PATH +;CERT_FILE = https/cert.pem +;KEY_FILE = https/key.pem +;; +;; Root directory containing templates and static files. +;; default is the path where Gitea is executed +;STATIC_ROOT_PATH = +;; +;; Default path for App data +;APP_DATA_PATH = data +;; +;; Enable gzip compression for runtime-generated content, static resources excluded +;ENABLE_GZIP = false +;; +;; Application profiling (memory and cpu) +;; For "web" command it listens on localhost:6060 +;; For "serve" command it dumps to disk at PPROF_DATA_PATH as (cpuprofile|memprofile)__ +;ENABLE_PPROF = false +;; +;; PPROF_DATA_PATH, use an absolute path when you start gitea as service +;PPROF_DATA_PATH = data/tmp/pprof +;; +;; Landing page, can be "home", "explore", "organizations" or "login" +;; The "login" choice is not a security measure but just a UI flow change, use REQUIRE_SIGNIN_VIEW to force users to log in. +;LANDING_PAGE = home +;; +;; Enables git-lfs support. true or false, default is false. +;LFS_START_SERVER = false +;; +;; Where your lfs files reside, default is data/lfs. +;LFS_CONTENT_PATH = data/lfs +;; +;; LFS authentication secret, change this yourself LFS_JWT_SECRET = -; LFS authentication validity period (in time.Duration), pushes taking longer than this may fail. -LFS_HTTP_AUTH_EXPIRY = 20m -; Maximum allowed LFS file size in bytes (Set to 0 for no limit). -LFS_MAX_FILE_SIZE = 0 -; Maximum number of locks returned per page -LFS_LOCKS_PAGING_NUM = 50 -; Allow graceful restarts using SIGHUP to fork -ALLOW_GRACEFUL_RESTARTS = true -; After a restart the parent will finish ongoing requests before -; shutting down. Force shutdown if this process takes longer than this delay. -; set to a negative value to disable -GRACEFUL_HAMMER_TIME = 60s -; Allows the setting of a startup timeout and waithint for Windows as SVC service -; 0 disables this. -STARTUP_TIMEOUT = 0 -; Static resources, includes resources on custom/, public/ and all uploaded avatars web browser cache time. Note that this cache is disabled when RUN_MODE is "dev". Default is 6h -STATIC_CACHE_TIME = 6h - -; Define allowed algorithms and their minimum key length (use -1 to disable a type) -[ssh.minimum_key_sizes] -ED25519 = 256 -ECDSA = 256 -RSA = 2048 -DSA = -1 ; set to 1024 to switch on - +;; +;; LFS authentication validity period (in time.Duration), pushes taking longer than this may fail. +;LFS_HTTP_AUTH_EXPIRY = 20m +;; +;; Maximum allowed LFS file size in bytes (Set to 0 for no limit). +;LFS_MAX_FILE_SIZE = 0 +;; +;; Maximum number of locks returned per page +;LFS_LOCKS_PAGING_NUM = 50 +;; +;; Allow graceful restarts using SIGHUP to fork +;ALLOW_GRACEFUL_RESTARTS = true +;; +;; After a restart the parent will finish ongoing requests before +;; shutting down. Force shutdown if this process takes longer than this delay. +;; set to a negative value to disable +;GRACEFUL_HAMMER_TIME = 60s +;; +;; Allows the setting of a startup timeout and waithint for Windows as SVC service +;; 0 disables this. +;STARTUP_TIMEOUT = 0 +;; +;; Static resources, includes resources on custom/, public/ and all uploaded avatars web browser cache time. Note that this cache is disabled when RUN_MODE is "dev". Default is 6h +;STATIC_CACHE_TIME = 6h + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; [database] -; Database to use. Either "mysql", "postgres", "mssql" or "sqlite3". +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Database to use. Either "mysql", "postgres", "mssql" or "sqlite3". +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; MySQL Configuration +;; DB_TYPE = mysql -HOST = 127.0.0.1:3306 +HOST = 127.0.0.1:3306 ; can use socket e.g. /var/run/mysqld/mysqld.sock NAME = gitea USER = root -; Use PASSWD = `your password` for quoting if you use special characters in the password. -PASSWD = -; For Postgres, schema to use if different from "public". The schema must exist beforehand, -; the user must have creation privileges on it, and the user search path must be set -; to the look into the schema first. e.g.:ALTER USER user SET SEARCH_PATH = schema_name,"$user",public; -SCHEMA = -; For Postgres, either "disable" (default), "require", or "verify-full" -; For MySQL, either "false" (default), "true", or "skip-verify" -SSL_MODE = disable -; For MySQL only, either "utf8" or "utf8mb4", default is "utf8mb4". -; NOTICE: for "utf8mb4" you must use MySQL InnoDB > 5.6. Gitea is unable to check this. -CHARSET = utf8mb4 -; For "sqlite3" and "tidb", use an absolute path when you start gitea as service -PATH = data/gitea.db -; For "sqlite3" only. Query timeout -SQLITE_TIMEOUT = 500 -; For iterate buffer, default is 50 -ITERATE_BUFFER_SIZE = 50 -; Show the database generated SQL -LOG_SQL = true -; Maximum number of DB Connect retries -DB_RETRIES = 10 -; Backoff time per DB retry (time.Duration) -DB_RETRY_BACKOFF = 3s -; Max idle database connections on connnection pool, default is 2 -MAX_IDLE_CONNS = 2 -; Database connection max life time, default is 0 or 3s mysql (See #6804 & #7071 for reasoning) -CONN_MAX_LIFETIME = 3s -; Database maximum number of open connections, default is 0 meaning no maximum -MAX_OPEN_CONNS = 0 - -[indexer] -; Issue indexer type, currently support: bleve, db or elasticsearch, default is bleve -ISSUE_INDEXER_TYPE = bleve -; Issue indexer connection string, available when ISSUE_INDEXER_TYPE is elasticsearch -ISSUE_INDEXER_CONN_STR = http://elastic:changeme@localhost:9200 -; Issue indexer name, available when ISSUE_INDEXER_TYPE is elasticsearch -ISSUE_INDEXER_NAME = gitea_issues -; Issue indexer storage path, available when ISSUE_INDEXER_TYPE is bleve -ISSUE_INDEXER_PATH = indexers/issues.bleve -; Issue indexer queue, currently support: channel, levelqueue or redis, default is levelqueue -ISSUE_INDEXER_QUEUE_TYPE = levelqueue -; When ISSUE_INDEXER_QUEUE_TYPE is levelqueue, this will be the path where the queue will be saved. -; This can be overriden by `ISSUE_INDEXER_QUEUE_CONN_STR`. -; default is indexers/issues.queue -ISSUE_INDEXER_QUEUE_DIR = indexers/issues.queue -; When `ISSUE_INDEXER_QUEUE_TYPE` is `redis`, this will store the redis connection string. -; When `ISSUE_INDEXER_QUEUE_TYPE` is `levelqueue`, this is a directory or additional options of -; the form `leveldb://path/to/db?option=value&....`, and overrides `ISSUE_INDEXER_QUEUE_DIR`. -ISSUE_INDEXER_QUEUE_CONN_STR = "addrs=127.0.0.1:6379 db=0" -; Batch queue number, default is 20 -ISSUE_INDEXER_QUEUE_BATCH_NUMBER = 20 -; Timeout the indexer if it takes longer than this to start. -; Set to zero to disable timeout. -STARTUP_TIMEOUT = 30s - -; repo indexer by default disabled, since it uses a lot of disk space -REPO_INDEXER_ENABLED = false -; Code search engine type, could be `bleve` or `elasticsearch`. -REPO_INDEXER_TYPE = bleve -; Index file used for code search. -REPO_INDEXER_PATH = indexers/repos.bleve -; Code indexer connection string, available when `REPO_INDEXER_TYPE` is elasticsearch. i.e. http://elastic:changeme@localhost:9200 -REPO_INDEXER_CONN_STR = -; Code indexer name, available when `REPO_INDEXER_TYPE` is elasticsearch -REPO_INDEXER_NAME = gitea_codes - -UPDATE_BUFFER_LEN = 20 -MAX_FILE_SIZE = 1048576 -; A comma separated list of glob patterns (see https://github.com/gobwas/glob) to include -; in the index; default is empty -REPO_INDEXER_INCLUDE = -; A comma separated list of glob patterns to exclude from the index; ; default is empty -REPO_INDEXER_EXCLUDE = - -[queue] -; Specific queues can be individually configured with [queue.name]. [queue] provides defaults -; -; General queue queue type, currently support: persistable-channel, channel, level, redis, dummy -; default to persistable-channel -TYPE = persistable-channel -; data-dir for storing persistable queues and level queues, individual queues will be named by their type -DATADIR = queues/ -; Default queue length before a channel queue will block -LENGTH = 20 -; Batch size to send for batched queues -BATCH_LENGTH = 20 -; Connection string for redis queues this will store the redis connection string. -; When `TYPE` is `persistable-channel`, this provides a directory for the underlying leveldb -; or additional options of the form `leveldb://path/to/db?option=value&....`, and will override `DATADIR`. -CONN_STR = "addrs=127.0.0.1:6379 db=0" -; Provides the suffix of the default redis/disk queue name - specific queues can be overriden within in their [queue.name] sections. -QUEUE_NAME = "_queue" -; Provides the suffix of the default redis/disk unique queue set name - specific queues can be overriden within in their [queue.name] sections. -SET_NAME = "_unique" -; If the queue cannot be created at startup - level queues may need a timeout at startup - wrap the queue: -WRAP_IF_NECESSARY = true -; Attempt to create the wrapped queue at max -MAX_ATTEMPTS = 10 -; Timeout queue creation -TIMEOUT = 15m30s -; Create a pool with this many workers -WORKERS = 1 -; Dynamically scale the worker pool to at this many workers -MAX_WORKERS = 10 -; Add boost workers when the queue blocks for BLOCK_TIMEOUT -BLOCK_TIMEOUT = 1s -; Remove the boost workers after BOOST_TIMEOUT -BOOST_TIMEOUT = 5m -; During a boost add BOOST_WORKERS -BOOST_WORKERS = 5 - -[admin] -; Disallow regular (non-admin) users from creating organizations. -DISABLE_REGULAR_ORG_CREATION = false -; Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled -DEFAULT_EMAIL_NOTIFICATIONS = enabled - +;PASSWD = ;Use PASSWD = `your password` for quoting if you use special characters in the password. +;SSL_MODE = false ; either "false" (default), "true", or "skip-verify" +;CHARSET = utf8mb4 ;either "utf8" or "utf8mb4", default is "utf8mb4". +;; +;; NOTICE: for "utf8mb4" you must use MySQL InnoDB > 5.6. Gitea is unable to check this. +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Postgres Configuration +;; +;DB_TYPE = postgres +;HOST = 127.0.0.1:5432 ; can use socket e.g. /var/run/postgresql/ +;NAME = gitea +;USER = root +;PASSWD = +;SCHEMA = +;SSL_MODE=disable ;either "disable" (default), "require", or "verify-full" +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; SQLite Configuration +;; +;DB_TYPE = sqlite3 +;PATH= ; defaults to data/gitea.db +;SQLITE_TIMEOUT = ; Query timeout defaults to: 500 +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; MSSQL Configuration +;; +;DB_TYPE = mssql +;HOST = 172.17.0.2:1433 +;NAME = gitea +;USER = SA +;PASSWD = MwantsaSecurePassword1 +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Other settings +;; +;; For iterate buffer, default is 50 +;ITERATE_BUFFER_SIZE = 50 +;; +;; Show the database generated SQL +LOG_SQL = false ; if unset defaults to true +;; +;; Maximum number of DB Connect retries +;DB_RETRIES = 10 +;; +;; Backoff time per DB retry (time.Duration) +;DB_RETRY_BACKOFF = 3s +;; +;; Max idle database connections on connection pool, default is 2 +;MAX_IDLE_CONNS = 2 +;; +;; Database connection max life time, default is 0 or 3s mysql (See #6804 & #7071 for reasoning) +;CONN_MAX_LIFETIME = 3s +;; +;; Database maximum number of open connections, default is 0 meaning no maximum +;MAX_OPEN_CONNS = 0 + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; [security] -; Whether the installer is disabled +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Whether the installer is disabled (set to true to disable the installer) INSTALL_LOCK = false -; !!CHANGE THIS TO KEEP YOUR USER DATA SAFE!! -SECRET_KEY = !#@FDEWREWR&*( -; How long to remember that a user is logged in before requiring relogin (in days) -LOGIN_REMEMBER_DAYS = 7 -COOKIE_USERNAME = gitea_awesome -COOKIE_REMEMBER_NAME = gitea_incredible -; Reverse proxy authentication header name of user name -REVERSE_PROXY_AUTHENTICATION_USER = X-WEBAUTH-USER -REVERSE_PROXY_AUTHENTICATION_EMAIL = X-WEBAUTH-EMAIL -; The minimum password length for new Users -MIN_PASSWORD_LENGTH = 6 -; Set to true to allow users to import local server paths -IMPORT_LOCAL_PATHS = false -; Set to false to allow users with git hook privileges to create custom git hooks. -; Custom git hooks can be used to perform arbitrary code execution on the host operating system. -; This enables the users to access and modify this config file and the Gitea database and interrupt the Gitea service. -; By modifying the Gitea database, users can gain Gitea administrator privileges. -; It also enables them to access other resources available to the user on the operating system that is running the Gitea instance and perform arbitrary actions in the name of the Gitea OS user. -; WARNING: This maybe harmful to you website or your operating system. -DISABLE_GIT_HOOKS = true -; Set to true to disable webhooks feature. -DISABLE_WEBHOOKS = false -; Set to false to allow pushes to gitea repositories despite having an incomplete environment - NOT RECOMMENDED -ONLY_ALLOW_PUSH_IF_GITEA_ENVIRONMENT_SET = true -;Comma separated list of character classes required to pass minimum complexity. -;If left empty or no valid values are specified, the default is off (no checking) -;Classes include "lower,upper,digit,spec" -PASSWORD_COMPLEXITY = off -; Password Hash algorithm, either "argon2", "pbkdf2", "scrypt" or "bcrypt" -PASSWORD_HASH_ALGO = pbkdf2 -; Set false to allow JavaScript to read CSRF cookie -CSRF_COOKIE_HTTP_ONLY = true -; Validate against https://haveibeenpwned.com/Passwords to see if a password has been exposed -PASSWORD_CHECK_PWN = false - +;; +;; Global secret key that will be used - if blank will be regenerated. +SECRET_KEY = +;; +;; Secret used to validate communication within Gitea binary. +INTERNAL_TOKEN= +;; +;; Instead of defining internal token in the configuration, this configuration option can be used to give Gitea a path to a file that contains the internal token (example value: file:/etc/gitea/internal_token) +;INTERNAL_TOKEN_URI = ;e.g. /etc/gitea/internal_token +;; +;; How long to remember that a user is logged in before requiring relogin (in days) +;LOGIN_REMEMBER_DAYS = 7 +;; +;; Name of the cookie used to store the current username. +;COOKIE_USERNAME = gitea_awesome +;; +;; Name of cookie used to store authentication information. +;COOKIE_REMEMBER_NAME = gitea_incredible +;; +;; Reverse proxy authentication header name of user name and email +;REVERSE_PROXY_AUTHENTICATION_USER = X-WEBAUTH-USER +;REVERSE_PROXY_AUTHENTICATION_EMAIL = X-WEBAUTH-EMAIL +;; +;; Interpret X-Forwarded-For header or the X-Real-IP header and set this as the remote IP for the request +;REVERSE_PROXY_LIMIT = 1 +;; +;; List of IP addresses and networks separated by comma of trusted proxy servers. Use `*` to trust all. +;REVERSE_PROXY_TRUSTED_PROXIES = 127.0.0.0/8,::1/128 +;; +;; The minimum password length for new Users +;MIN_PASSWORD_LENGTH = 6 +;; +;; Set to true to allow users to import local server paths +;IMPORT_LOCAL_PATHS = false +;; +;; Set to false to allow users with git hook privileges to create custom git hooks. +;; Custom git hooks can be used to perform arbitrary code execution on the host operating system. +;; This enables the users to access and modify this config file and the Gitea database and interrupt the Gitea service. +;; By modifying the Gitea database, users can gain Gitea administrator privileges. +;; It also enables them to access other resources available to the user on the operating system that is running the Gitea instance and perform arbitrary actions in the name of the Gitea OS user. +;; WARNING: This maybe harmful to you website or your operating system. +;DISABLE_GIT_HOOKS = true +;; +;; Set to true to disable webhooks feature. +;DISABLE_WEBHOOKS = false +;; +;; Set to false to allow pushes to gitea repositories despite having an incomplete environment - NOT RECOMMENDED +;ONLY_ALLOW_PUSH_IF_GITEA_ENVIRONMENT_SET = true +;; +;;Comma separated list of character classes required to pass minimum complexity. +;;If left empty or no valid values are specified, the default is off (no checking) +;;Classes include "lower,upper,digit,spec" +;PASSWORD_COMPLEXITY = off +;; +;; Password Hash algorithm, either "argon2", "pbkdf2", "scrypt" or "bcrypt" +;PASSWORD_HASH_ALGO = pbkdf2 +;; +;; Set false to allow JavaScript to read CSRF cookie +;CSRF_COOKIE_HTTP_ONLY = true +;; +;; Validate against https://haveibeenpwned.com/Passwords to see if a password has been exposed +;PASSWORD_CHECK_PWN = false + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; [security.password_hash] -; Every parameter for the built in hash algorithms can be changed in this section. -; Handle with care! Changing the values can massively change the hash calculation time and/or memory needed for the hashing process. -; After changing a value, the user password hash will be recalculated on next successful login. +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Every parameter for the built in hash algorithms can be changed in this section. +;; Handle with care! Changing the values can massively change the hash calculation time and/or memory needed for the hashing process. +;; After changing a value, the user password hash will be recalculated on next successful login. +;; +;; Parameters for BCrypt BCRYPT_COST = 10 -; Parameters for scrypt +;; +;; Parameters for scrypt SCRYPT_N = 65536 SCRYPT_R = 16 SCRYPT_P = 2 SCRYPT_KEY_LENGTH = 50 -; Parameters for Argon2id +;; +;; Parameters for Argon2id ARGON2_ITERATIONS = 2 ARGON2_MEMORY = 65536 ARGON2_PARALLELISM = 8 ARGON2_KEY_LENGTH = 50 -; Parameters for Pbkdf2 +;; +;; Parameters for Pbkdf2 PBKDF2_ITERATIONS = 10000 PBKDF2_KEY_LENGTH = 50 -[openid] -; -; OpenID is an open, standard and decentralized authentication protocol. -; Your identity is the address of a webpage you provide, which describes -; how to prove you are in control of that page. -; -; For more info: https://en.wikipedia.org/wiki/OpenID -; -; Current implementation supports OpenID-2.0 -; -; Tested to work providers at the time of writing: -; - Any GNUSocial node (your.hostname.tld/username) -; - Any SimpleID provider (http://simpleid.koinic.net) -; - http://openid.org.cn/ -; - openid.stackexchange.com -; - login.launchpad.net -; - .livejournal.com -; -; Whether to allow signin in via OpenID -ENABLE_OPENID_SIGNIN = true -; Whether to allow registering via OpenID -; Do not include to rely on rhw DISABLE_REGISTRATION setting -;ENABLE_OPENID_SIGNUP = true -; Allowed URI patterns (POSIX regexp). -; Space separated. -; Only these would be allowed if non-blank. -; Example value: trusted.domain.org trusted.domain.net -WHITELISTED_URIS = -; Forbidden URI patterns (POSIX regexp). -; Space separated. -; Only used if WHITELISTED_URIS is blank. -; Example value: loadaverage.org/badguy stackexchange.com/.*spammer -BLACKLISTED_URIS = - -[service] -; Time limit to confirm account/email registration -ACTIVE_CODE_LIVE_MINUTES = 180 -; Time limit to perform the reset of a forgotten password -RESET_PASSWD_CODE_LIVE_MINUTES = 180 -; Whether a new user needs to confirm their email when registering. -REGISTER_EMAIL_CONFIRM = false -; Whether a new user needs to be confirmed manually after registration. (Requires `REGISTER_EMAIL_CONFIRM` to be disabled.) -REGISTER_MANUAL_CONFIRM = false -; List of domain names that are allowed to be used to register on a Gitea instance -; gitea.io,example.com -EMAIL_DOMAIN_WHITELIST = -; Comma-separated list of domain names that are not allowed to be used to register on a Gitea instance -EMAIL_DOMAIN_BLOCKLIST = -; Disallow registration, only allow admins to create accounts. -DISABLE_REGISTRATION = false -; Allow registration only using third-party services, it works only when DISABLE_REGISTRATION is false -ALLOW_ONLY_EXTERNAL_REGISTRATION = false -; User must sign in to view anything. -REQUIRE_SIGNIN_VIEW = false -; Mail notification -ENABLE_NOTIFY_MAIL = false -; This setting enables gitea to be signed in with HTTP BASIC Authentication using the user's password -; If you set this to false you will not be able to access the tokens endpoints on the API with your password -; Please note that setting this to false will not disable OAuth Basic or Basic authentication using a token -ENABLE_BASIC_AUTHENTICATION = true -; More detail: https://github.com/gogits/gogs/issues/165 -ENABLE_REVERSE_PROXY_AUTHENTICATION = false -ENABLE_REVERSE_PROXY_AUTO_REGISTRATION = false -ENABLE_REVERSE_PROXY_EMAIL = false -; Enable captcha validation for registration -ENABLE_CAPTCHA = false -; Type of captcha you want to use. Options: image, recaptcha, hcaptcha -CAPTCHA_TYPE = image -; Enable recaptcha to use Google's recaptcha service -; Go to https://www.google.com/recaptcha/admin to sign up for a key -RECAPTCHA_SECRET = -RECAPTCHA_SITEKEY = -; For hCaptcha, create an account at https://accounts.hcaptcha.com/login to get your keys -HCAPTCHA_SECRET = -HCAPTCHA_SITEKEY = -; Change this to use recaptcha.net or other recaptcha service -RECAPTCHA_URL = https://www.google.com/recaptcha/ -; Default value for KeepEmailPrivate -; Each new user will get the value of this setting copied into their profile -DEFAULT_KEEP_EMAIL_PRIVATE = false -; Default value for AllowCreateOrganization -; Every new user will have rights set to create organizations depending on this setting -DEFAULT_ALLOW_CREATE_ORGANIZATION = true -; Either "public", "limited" or "private", default is "public" -; Limited is for signed user only -; Private is only for member of the organization -; Public is for everyone -DEFAULT_ORG_VISIBILITY = public -; Default value for DefaultOrgMemberVisible -; True will make the membership of the users visible when added to the organisation -DEFAULT_ORG_MEMBER_VISIBLE = false -; Default value for EnableDependencies -; Repositories will use dependencies by default depending on this setting -DEFAULT_ENABLE_DEPENDENCIES = true -; Dependencies can be added from any repository where the user is granted access or only from the current repository depending on this setting. -ALLOW_CROSS_REPOSITORY_DEPENDENCIES = true -; Enable heatmap on users profiles. -ENABLE_USER_HEATMAP = true -; Enable Timetracking -ENABLE_TIMETRACKING = true -; Default value for EnableTimetracking -; Repositories will use timetracking by default depending on this setting -DEFAULT_ENABLE_TIMETRACKING = true -; Default value for AllowOnlyContributorsToTrackTime -; Only users with write permissions can track time if this is true -DEFAULT_ALLOW_ONLY_CONTRIBUTORS_TO_TRACK_TIME = true -; Default value for the domain part of the user's email address in the git log -; if he has set KeepEmailPrivate to true. The user's email will be replaced with a -; concatenation of the user name in lower case, "@" and NO_REPLY_ADDRESS. -NO_REPLY_ADDRESS = noreply.%(DOMAIN)s -; Show Registration button -SHOW_REGISTRATION_BUTTON = true -; Show milestones dashboard page - a view of all the user's milestones -SHOW_MILESTONES_DASHBOARD_PAGE = true -; Default value for AutoWatchNewRepos -; When adding a repo to a team or creating a new repo all team members will watch the -; repo automatically if enabled -AUTO_WATCH_NEW_REPOS = true -; Default value for AutoWatchOnChanges -; Make the user watch a repository When they commit for the first time -AUTO_WATCH_ON_CHANGES = false -; Minimum amount of time a user must exist before comments are kept when the user is deleted. -USER_DELETE_WITH_COMMENTS_MAX_TIME = 0 - -[webhook] -; Hook task queue length, increase if webhook shooting starts hanging -QUEUE_LENGTH = 1000 -; Deliver timeout in seconds -DELIVER_TIMEOUT = 5 -; Allow insecure certification -SKIP_TLS_VERIFY = false -; Number of history information in each page -PAGING_NUM = 10 -; Proxy server URL, support http://, https//, socks://, blank will follow environment http_proxy/https_proxy -PROXY_URL = -; Comma separated list of host names requiring proxy. Glob patterns (*) are accepted; use ** to match all hosts. -PROXY_HOSTS = - -[mailer] -ENABLED = false -; Buffer length of channel, keep it as it is if you don't know what it is. -SEND_BUFFER_LEN = 100 -; Prefix displayed before subject in mail -SUBJECT_PREFIX = -; Mail server -; Gmail: smtp.gmail.com:587 -; QQ: smtp.qq.com:465 -; Using STARTTLS on port 587 is recommended per RFC 6409. -; Note, if the port ends with "465", SMTPS will be used. -HOST = -; Disable HELO operation when hostnames are different. -DISABLE_HELO = -; Custom hostname for HELO operation, if no value is provided, one is retrieved from system. -HELO_HOSTNAME = -; Whether or not to skip verification of certificates; `true` to disable verification. This option is unsafe. Consider adding the certificate to the system trust store instead. -SKIP_VERIFY = false -; Use client certificate -USE_CERTIFICATE = false -CERT_FILE = custom/mailer/cert.pem -KEY_FILE = custom/mailer/key.pem -; Should SMTP connect with TLS, (if port ends with 465 TLS will always be used.) -; If this is false but STARTTLS is supported the connection will be upgraded to TLS opportunistically. -IS_TLS_ENABLED = false -; Mail from address, RFC 5322. This can be just an email address, or the `"Name" ` format -FROM = -; Mailer user name and password -; Please Note: Authentication is only supported when the SMTP server communication is encrypted with TLS (this can be via STARTTLS) or `HOST=localhost`. -USER = -; Use PASSWD = `your password` for quoting if you use special characters in the password. -PASSWD = -; Send mails as plain text -SEND_AS_PLAIN_TEXT = false -; Set Mailer Type (either SMTP, sendmail or dummy to just send to the log) -MAILER_TYPE = smtp -; Specify an alternative sendmail binary -SENDMAIL_PATH = sendmail -; Specify any extra sendmail arguments -SENDMAIL_ARGS = -; Timeout for Sendmail -SENDMAIL_TIMEOUT = 5m - -[cache] -; if the cache enabled -ENABLED = true -; Either "memory", "redis", or "memcache", default is "memory" -ADAPTER = memory -; For "memory" only, GC interval in seconds, default is 60 -INTERVAL = 60 -; For "redis" and "memcache", connection host address -; redis: network=tcp,addr=:6379,password=macaron,db=0,pool_size=100,idle_timeout=180 -; memcache: `127.0.0.1:11211` -HOST = -; Time to keep items in cache if not used, default is 16 hours. -; Setting it to 0 disables caching -ITEM_TTL = 16h - -; Last commit cache -[cache.last_commit] -; if the cache enabled -ENABLED = true -; Time to keep items in cache if not used, default is 8760 hours. -; Setting it to 0 disables caching -ITEM_TTL = 8760h -; Only enable the cache when repository's commits count great than -COMMITS_COUNT = 1000 - -[session] -; Either "memory", "file", or "redis", default is "memory" -PROVIDER = memory -; Provider config options -; memory: doesn't have any config yet -; file: session file path, e.g. `data/sessions` -; redis: network=tcp,addr=:6379,password=macaron,db=0,pool_size=100,idle_timeout=180 -; mysql: go-sql-driver/mysql dsn config string, e.g. `root:password@/session_table` -PROVIDER_CONFIG = data/sessions -; Session cookie name -COOKIE_NAME = i_like_gitea -; If you use session in https only, default is false -COOKIE_SECURE = false -; Session GC time interval in seconds, default is 86400 (1 day) -GC_INTERVAL_TIME = 86400 -; Session life time in seconds, default is 86400 (1 day) -SESSION_LIFE_TIME = 86400 - -[picture] -AVATAR_UPLOAD_PATH = data/avatars -REPOSITORY_AVATAR_UPLOAD_PATH = data/repo-avatars -; How Gitea deals with missing repository avatars -; none = no avatar will be displayed; random = random avatar will be displayed; image = default image will be used -REPOSITORY_AVATAR_FALLBACK = none -REPOSITORY_AVATAR_FALLBACK_IMAGE = /img/repo_default.png -; Max Width and Height of uploaded avatars. -; This is to limit the amount of RAM used when resizing the image. -AVATAR_MAX_WIDTH = 4096 -AVATAR_MAX_HEIGHT = 3072 -; Maximum alloved file size for uploaded avatars. -; This is to limit the amount of RAM used when resizing the image. -AVATAR_MAX_FILE_SIZE = 1048576 -; Chinese users can choose "duoshuo" -; or a custom avatar source, like: http://cn.gravatar.com/avatar/ -GRAVATAR_SOURCE = gravatar -; This value will always be true in offline mode. -DISABLE_GRAVATAR = false -; Federated avatar lookup uses DNS to discover avatar associated -; with emails, see https://www.libravatar.org -; This value will always be false in offline mode or when Gravatar is disabled. -ENABLE_FEDERATED_AVATAR = false - -[attachment] -; Whether issue and pull request attachments are enabled. Defaults to `true` -ENABLED = true -; Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types. -ALLOWED_TYPES = .docx,.gif,.gz,.jpeg,.jpg,.log,.pdf,.png,.pptx,.txt,.xlsx,.zip -; Max size of each file. Defaults to 4MB -MAX_SIZE = 4 -; Max number of files per upload. Defaults to 5 -MAX_FILES = 5 -; Storage type for attachments, `local` for local disk or `minio` for s3 compatible -; object storage service, default is `local`. -STORAGE_TYPE = local -; Allows the storage driver to redirect to authenticated URLs to serve files directly -; Currently, only `minio` is supported. -SERVE_DIRECT = false -; Path for attachments. Defaults to `data/attachments` only available when STORAGE_TYPE is `local` -PATH = data/attachments -; Minio endpoint to connect only available when STORAGE_TYPE is `minio` -MINIO_ENDPOINT = localhost:9000 -; Minio accessKeyID to connect only available when STORAGE_TYPE is `minio` -MINIO_ACCESS_KEY_ID = -; Minio secretAccessKey to connect only available when STORAGE_TYPE is `minio` -MINIO_SECRET_ACCESS_KEY = -; Minio bucket to store the attachments only available when STORAGE_TYPE is `minio` -MINIO_BUCKET = gitea -; Minio location to create bucket only available when STORAGE_TYPE is `minio` -MINIO_LOCATION = us-east-1 -; Minio base path on the bucket only available when STORAGE_TYPE is `minio` -MINIO_BASE_PATH = attachments/ -; Minio enabled ssl only available when STORAGE_TYPE is `minio` -MINIO_USE_SSL = false - -[time] -; Specifies the format for fully outputted dates. Defaults to RFC1123 -; Special supported values are ANSIC, UnixDate, RubyDate, RFC822, RFC822Z, RFC850, RFC1123, RFC1123Z, RFC3339, RFC3339Nano, Kitchen, Stamp, StampMilli, StampMicro and StampNano -; For more information about the format see http://golang.org/pkg/time/#pkg-constants -FORMAT = -; Location the UI time display i.e. Asia/Shanghai -; Empty means server's location setting -DEFAULT_UI_LOCATION = - +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[oauth2] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Enables OAuth2 provider +ENABLE = true +;; +;; Algorithm used to sign OAuth2 tokens. Valid values: HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512 +;JWT_SIGNING_ALGORITHM = RS256 +;; +;; 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. +;JWT_SIGNING_PRIVATE_KEY_FILE = jwt/private.pem +;; +;; OAuth2 authentication secret for access and refresh tokens, change this yourself to a unique string. CLI generate option is helpful in this case. https://docs.gitea.io/en-us/command-line/#generate +;; This setting is only needed if JWT_SIGNING_ALGORITHM is set to HS256, HS384 or HS512. +;JWT_SECRET = +;; +;; Lifetime of an OAuth2 access token in seconds +;ACCESS_TOKEN_EXPIRATION_TIME = 3600 +;; +;; Lifetime of an OAuth2 refresh token in hours +;REFRESH_TOKEN_EXPIRATION_TIME = 730 +;; +;; Check if refresh token got already used +;INVALIDATE_REFRESH_TOKENS = false +;; +;; Maximum length of oauth2 token/cookie stored on server +;MAX_TOKEN_LENGTH = 32767 + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[U2F] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; NOTE: THE DEFAULT VALUES HERE WILL NEED TO BE CHANGED +;; Two Factor authentication with security keys +;; https://developers.yubico.com/U2F/App_ID.html +APP_ID = ; e.g. http://localhost:3000/ +;; Comma separated list of trusted facets +TRUSTED_FACETS = ; e.g. http://localhost:3000/ + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; [log] -ROOT_PATH = -; Either "console", "file", "conn", "smtp" or "database", default is "console" -; Use comma to separate multiple modes, e.g. "console, file" +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Root path for the log files - defaults to %(GITEA_WORK_DIR)/log +;ROOT_PATH = +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Main Logger +;; +;; Either "console", "file", "conn", "smtp" or "database", default is "console" +;; Use comma to separate multiple modes, e.g. "console, file" MODE = console -; Buffer length of the channel, keep it as it is if you don't know what it is. -BUFFER_LEN = 10000 -; Either "Trace", "Debug", "Info", "Warn", "Error", "Critical", default is "Info" -ROUTER_LOG_LEVEL = Info -ROUTER = console -ENABLE_ACCESS_LOG = false -ACCESS_LOG_TEMPLATE = {{.Ctx.RemoteAddr}} - {{.Identity}} {{.Start.Format "[02/Jan/2006:15:04:05 -0700]" }} "{{.Ctx.Req.Method}} {{.Ctx.Req.URL.RequestURI}} {{.Ctx.Req.Proto}}" {{.ResponseWriter.Status}} {{.ResponseWriter.Size}} "{{.Ctx.Req.Referer}}\" \"{{.Ctx.Req.UserAgent}}" -ACCESS = file -; Either "Trace", "Debug", "Info", "Warn", "Error", "Critical", default is "Trace" +;; +;; Either "Trace", "Debug", "Info", "Warn", "Error", "Critical", default is "Trace" LEVEL = Info -; Either "Trace", "Debug", "Info", "Warn", "Error", "Critical", default is "None" -STACKTRACE_LEVEL = None - -; Generic log modes -[log.x] -FLAGS = stdflags -EXPRESSION = -PREFIX = -COLORIZE = false - -; For "console" mode only -[log.console] -LEVEL = -STDERR = false - -; For "file" mode only -[log.file] -LEVEL = -; Set the file_name for the logger. If this is a relative path this -; will be relative to ROOT_PATH -FILE_NAME = -; This enables automated log rotate(switch of following options), default is true -LOG_ROTATE = true -; Max size shift of a single file, default is 28 means 1 << 28, 256MB -MAX_SIZE_SHIFT = 28 -; Segment log daily, default is true -DAILY_ROTATE = true -; delete the log file after n days, default is 7 -MAX_DAYS = 7 -; compress logs with gzip -COMPRESS = true -; compression level see godoc for compress/gzip -COMPRESSION_LEVEL = -1 - -; For "conn" mode only -[log.conn] -LEVEL = -; Reconnect host for every single message, default is false -RECONNECT_ON_MSG = false -; Try to reconnect when connection is lost, default is false -RECONNECT = false -; Either "tcp", "unix" or "udp", default is "tcp" -PROTOCOL = tcp -; Host address -ADDR = - -; For "smtp" mode only -[log.smtp] -LEVEL = -; Name displayed in mail title, default is "Diagnostic message from server" -SUBJECT = Diagnostic message from server -; Mail server -HOST = -; Mailer user name and password -USER = -; Use PASSWD = `your password` for quoting if you use special characters in the password. -PASSWD = -; Receivers, can be one or more, e.g. 1@example.com,2@example.com -RECEIVERS = - -[cron] -; Enable running all cron tasks periodically with default settings. -ENABLED = false -; Run cron tasks when Gitea starts. -RUN_AT_START = false - -; Basic cron tasks - enabled by default - -; Clean up old repository archives -[cron.archive_cleanup] -; Whether to enable the job -ENABLED = true -; Whether to always run at least once at start up time (if ENABLED) -RUN_AT_START = true -; Notice if not success -NO_SUCCESS_NOTICE = false -; Time interval for job to run -SCHEDULE = @every 24h -; Archives created more than OLDER_THAN ago are subject to deletion -OLDER_THAN = 24h - -; Update mirrors -[cron.update_mirrors] -SCHEDULE = @every 10m -; Enable running Update mirrors task periodically. -ENABLED = true -; Run Update mirrors task when Gitea starts. -RUN_AT_START = false -; Notice if not success -NO_SUCCESS_NOTICE = true - -; Repository health check -[cron.repo_health_check] -SCHEDULE = @every 24h -; Enable running Repository health check task periodically. -ENABLED = true -; Run Repository health check task when Gitea starts. -RUN_AT_START = false -; Notice if not success -NO_SUCCESS_NOTICE = false -TIMEOUT = 60s -; Arguments for command 'git fsck', e.g. "--unreachable --tags" -; see more on http://git-scm.com/docs/git-fsck -ARGS = - -; Check repository statistics -[cron.check_repo_stats] -; Enable running check repository statistics task periodically. -ENABLED = true -; Run check repository statistics task when Gitea starts. -RUN_AT_START = true -; Notice if not success -NO_SUCCESS_NOTICE = false -SCHEDULE = @every 24h - -[cron.update_migration_poster_id] -; Update migrated repositories' issues and comments' posterid, it will always attempt synchronization when the instance starts. -ENABLED = true -; Update migrated repositories' issues and comments' posterid when starting server (default true) -RUN_AT_START = true -; Notice if not success -NO_SUCCESS_NOTICE = false -; Interval as a duration between each synchronization. (default every 24h) -SCHEDULE = @every 24h - -; Synchronize external user data (only LDAP user synchronization is supported) -[cron.sync_external_users] -ENABLED = true -; Synchronize external user data when starting server (default false) -RUN_AT_START = false -; Notice if not success -NO_SUCCESS_NOTICE = false -; Interval as a duration between each synchronization (default every 24h) -SCHEDULE = @every 24h -; Create new users, update existing user data and disable users that are not in external source anymore (default) -; or only create new users if UPDATE_EXISTING is set to false -UPDATE_EXISTING = true - -; Clean-up deleted branches -[cron.deleted_branches_cleanup] -ENABLED = true -; Clean-up deleted branches when starting server (default true) -RUN_AT_START = true -; Notice if not success -NO_SUCCESS_NOTICE = false -; Interval as a duration between each synchronization (default every 24h) -SCHEDULE = @every 24h -; deleted branches than OLDER_THAN ago are subject to deletion -OLDER_THAN = 24h - -; Cleanup hook_task table -[cron.cleanup_hook_task_table] -; Whether to enable the job -ENABLED = true -; Whether to always run at start up time (if ENABLED) -RUN_AT_START = false -; Time interval for job to run -SCHEDULE = @every 24h -; OlderThan or PerWebhook. How the records are removed, either by age (i.e. how long ago hook_task record was delivered) or by the number to keep per webhook (i.e. keep most recent x deliveries per webhook). -CLEANUP_TYPE = OlderThan -; If CLEANUP_TYPE is set to OlderThan, then any delivered hook_task records older than this expression will be deleted. -OLDER_THAN = 168h -; If CLEANUP_TYPE is set to PerWebhook, this is number of hook_task records to keep for a webhook (i.e. keep the most recent x deliveries). -NUMBER_TO_KEEP = 10 - -; Extended cron task - not enabled by default - -; Delete all unactivated accounts -[cron.delete_inactive_accounts] -ENABLED = false -RUN_AT_START = false -NO_SUCCESS_NOTICE = false -SCHEDULE = @annually -OLDER_THAN = 168h - -; Delete all repository archives -[cron.delete_repo_archives] -ENABLED = false -RUN_AT_START = false -NO_SUCCESS_NOTICE = false -SCHEDULE = @annually - -; Garbage collect all repositories -[cron.git_gc_repos] -ENABLED = false -RUN_AT_START = false -NO_SUCCESS_NOTICE = false -SCHEDULE = @every 72h -TIMEOUT = 60s -; Arguments for command 'git gc' -; The default value is same with [git] -> GC_ARGS -ARGS = - -; Update the '.ssh/authorized_keys' file with Gitea SSH keys -[cron.resync_all_sshkeys] -ENABLED = false -RUN_AT_START = false -NO_SUCCESS_NOTICE = false -SCHEDULE = @every 72h - -; Resynchronize pre-receive, update and post-receive hooks of all repositories. -[cron.resync_all_hooks] -ENABLED = false -RUN_AT_START = false -NO_SUCCESS_NOTICE = false -SCHEDULE = @every 72h - -; Reinitialize all missing Git repositories for which records exist -[cron.reinit_missing_repos] -ENABLED = false -RUN_AT_START = false -NO_SUCCESS_NOTICE = false -SCHEDULE = @every 72h - -; Delete all repositories missing their Git files -[cron.delete_missing_repos] -ENABLED = false -RUN_AT_START = false -NO_SUCCESS_NOTICE = false -SCHEDULE = @every 72h - -; Delete generated repository avatars -[cron.delete_generated_repository_avatars] -ENABLED = false -RUN_AT_START = false -NO_SUCCESS_NOTICE = false -SCHEDULE = @every 72h - +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Router Logger +;; +;; Switch off the router log +;DISABLE_ROUTER_LOG= ; false +;; +;; Set the log "modes" for the router log (if file is set the log file will default to router.log) +ROUTER = console +;; +;; The level at which the router logs +;ROUTER_LOG_LEVEL = Info +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Access Logger (Creates log in NCSA common log format) +;; +;ENABLE_ACCESS_LOG = false +;; Set the log "modes" for the access log (if file is set the log file will default to access.log) +;ACCESS = file +;; +;; Sets the template used to create the access log. +;ACCESS_LOG_TEMPLATE = {{.Ctx.RemoteAddr}} - {{.Identity}} {{.Start.Format "[02/Jan/2006:15:04:05 -0700]" }} "{{.Ctx.Req.Method}} {{.Ctx.Req.URL.RequestURI}} {{.Ctx.Req.Proto}}" {{.ResponseWriter.Status}} {{.ResponseWriter.Size}} "{{.Ctx.Req.Referer}}\" \"{{.Ctx.Req.UserAgent}}" +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; SSH log (Creates log from ssh git request) +;; +;ENABLE_SSH_LOG = false +;; +;; Other Settings +;; +;; Print Stacktraces with logs. (Rarely helpful.) Either "Trace", "Debug", "Info", "Warn", "Error", "Critical", default is "None" +;STACKTRACE_LEVEL = None +;; +;; Buffer length of the channel, keep it as it is if you don't know what it is. +;BUFFER_LEN = 10000 + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Creating specific log configuration +;; +;; You can set specific configuration for individual modes and subloggers +;; +;; Configuration available to all log modes/subloggers +;LEVEL= +;FLAGS = stdflags +;EXPRESSION = +;PREFIX = +;COLORIZE = false +;; +;; For "console" mode only +;STDERR = false +;; +;; For "file" mode only +;LEVEL = +;; Set the file_name for the logger. If this is a relative path this +;; will be relative to ROOT_PATH +;FILE_NAME = +;; This enables automated log rotate(switch of following options), default is true +;LOG_ROTATE = true +;; Max size shift of a single file, default is 28 means 1 << 28, 256MB +;MAX_SIZE_SHIFT = 28 +;; Segment log daily, default is true +;DAILY_ROTATE = true +;; delete the log file after n days, default is 7 +;MAX_DAYS = 7 +;; compress logs with gzip +;COMPRESS = true +;; compression level see godoc for compress/gzip +;COMPRESSION_LEVEL = -1 +; +;; For "conn" mode only +;LEVEL = +;; Reconnect host for every single message, default is false +;RECONNECT_ON_MSG = false +;; Try to reconnect when connection is lost, default is false +;RECONNECT = false +;; Either "tcp", "unix" or "udp", default is "tcp" +;PROTOCOL = tcp +;; Host address +;ADDR = +; +;; For "smtp" mode only +;LEVEL = +;; Name displayed in mail title, default is "Diagnostic message from server" +;SUBJECT = Diagnostic message from server +;; Mail server +;HOST = +;; Mailer user name and password +;USER = +;; Use PASSWD = `your password` for quoting if you use special characters in the password. +;PASSWD = +;; Receivers, can be one or more, e.g. 1@example.com,2@example.com +;RECEIVERS = + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; [git] -; The path of git executable. If empty, Gitea searches through the PATH environment. +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; The path of git executable. If empty, Gitea searches through the PATH environment. PATH = -; Disables highlight of added and removed changes -DISABLE_DIFF_HIGHLIGHT = false -; Max number of lines allowed in a single file in diff view -MAX_GIT_DIFF_LINES = 1000 -; Max number of allowed characters in a line in diff view -MAX_GIT_DIFF_LINE_CHARACTERS = 5000 -; Max number of files shown in diff view -MAX_GIT_DIFF_FILES = 100 -; Set the default commits range size -COMMITS_RANGE_SIZE = 50 -; Set the default branches range size -BRANCHES_RANGE_SIZE = 20 -; Arguments for command 'git gc', e.g. "--aggressive --auto" -; see more on http://git-scm.com/docs/git-gc/ -GC_ARGS = -; If use git wire protocol version 2 when git version >= 2.18, default is true, set to false when you always want git wire protocol version 1 -ENABLE_AUTO_GIT_WIRE_PROTOCOL = true -; Respond to pushes to a non-default branch with a URL for creating a Pull Request (if the repository has them enabled) -PULL_REQUEST_PUSH_MESSAGE = true - -; Operation timeout in seconds -[git.timeout] -DEFAULT = 360 -MIGRATE = 600 -MIRROR = 300 -CLONE = 300 -PULL = 300 -GC = 60 - -[mirror] -; Default interval as a duration between each check -DEFAULT_INTERVAL = 8h -; Min interval as a duration must be > 1m -MIN_INTERVAL = 10m - -[api] -; Enables Swagger. True or false; default is true. -ENABLE_SWAGGER = true -; Max number of items in a page -MAX_RESPONSE_ITEMS = 50 -; Default paging number of api -DEFAULT_PAGING_NUM = 30 -; Default and maximum number of items per page for git trees api -DEFAULT_GIT_TREES_PER_PAGE = 1000 -; Default size of a blob returned by the blobs API (default is 10MiB) -DEFAULT_MAX_BLOB_SIZE = 10485760 - -[oauth2] -; Enables OAuth2 provider -ENABLE = true -; Lifetime of an OAuth2 access token in seconds -ACCESS_TOKEN_EXPIRATION_TIME = 3600 -; Lifetime of an OAuth2 refresh token in hours -REFRESH_TOKEN_EXPIRATION_TIME = 730 -; Check if refresh token got already used -INVALIDATE_REFRESH_TOKENS = false -; OAuth2 authentication secret for access and refresh tokens, change this yourself to a unique string. CLI generate option is helpful in this case. https://docs.gitea.io/en-us/command-line/#generate -JWT_SECRET = -; Maximum length of oauth2 token/cookie stored on server -MAX_TOKEN_LENGTH = 32767 - -[i18n] -LANGS = en-US,zh-CN,zh-HK,zh-TW,de-DE,fr-FR,nl-NL,lv-LV,ru-RU,uk-UA,ja-JP,es-ES,pt-BR,pt-PT,pl-PL,bg-BG,it-IT,fi-FI,tr-TR,cs-CZ,sr-SP,sv-SE,ko-KR -NAMES = English,简体中文,繁體中文(香港),繁體中文(台灣),Deutsch,français,Nederlands,latviešu,русский,Українська,日本語,español,português do Brasil,Português de Portugal,polski,български,italiano,suomi,Türkçe,čeština,српски,svenska,한국어 +;; +;; Disables highlight of added and removed changes +;DISABLE_DIFF_HIGHLIGHT = false +;; +;; Max number of lines allowed in a single file in diff view +;MAX_GIT_DIFF_LINES = 1000 +;; +;; Max number of allowed characters in a line in diff view +;MAX_GIT_DIFF_LINE_CHARACTERS = 5000 +;; +;; Max number of files shown in diff view +;MAX_GIT_DIFF_FILES = 100 +;; +;; Set the default commits range size +;COMMITS_RANGE_SIZE = 50 +;; +;; Set the default branches range size +;BRANCHES_RANGE_SIZE = 20 +;; +;; Arguments for command 'git gc', e.g. "--aggressive --auto" +;; see more on http://git-scm.com/docs/git-gc/ +;GC_ARGS = +;; +;; If use git wire protocol version 2 when git version >= 2.18, default is true, set to false when you always want git wire protocol version 1 +;ENABLE_AUTO_GIT_WIRE_PROTOCOL = true +;; +;; Respond to pushes to a non-default branch with a URL for creating a Pull Request (if the repository has them enabled) +;PULL_REQUEST_PUSH_MESSAGE = true +;; +;; (Go-Git only) Don't cache objects greater than this in memory. (Set to 0 to disable.) +;LARGE_OBJECT_THRESHOLD = 1048576 + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[service] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Time limit to confirm account/email registration +;ACTIVE_CODE_LIVE_MINUTES = 180 +;; +;; Time limit to perform the reset of a forgotten password +;RESET_PASSWD_CODE_LIVE_MINUTES = 180 +;; +;; Whether a new user needs to confirm their email when registering. +;REGISTER_EMAIL_CONFIRM = false +;; +;; Whether a new user needs to be confirmed manually after registration. (Requires `REGISTER_EMAIL_CONFIRM` to be disabled.) +;REGISTER_MANUAL_CONFIRM = false +;; +;; List of domain names that are allowed to be used to register on a Gitea instance +;; gitea.io,example.com +;EMAIL_DOMAIN_WHITELIST = +;; +;; Comma-separated list of domain names that are not allowed to be used to register on a Gitea instance +;EMAIL_DOMAIN_BLOCKLIST = +;; +;; Disallow registration, only allow admins to create accounts. +;DISABLE_REGISTRATION = false +;; +;; Allow registration only using gitea itself, it works only when DISABLE_REGISTRATION is false +;ALLOW_ONLY_INTERNAL_REGISTRATION = false +;; +;; Allow registration only using third-party services, it works only when DISABLE_REGISTRATION is false +;ALLOW_ONLY_EXTERNAL_REGISTRATION = false +;; +;; User must sign in to view anything. +;REQUIRE_SIGNIN_VIEW = false +;; +;; Mail notification +;ENABLE_NOTIFY_MAIL = false +;; +;; This setting enables gitea to be signed in with HTTP BASIC Authentication using the user's password +;; If you set this to false you will not be able to access the tokens endpoints on the API with your password +;; Please note that setting this to false will not disable OAuth Basic or Basic authentication using a token +;ENABLE_BASIC_AUTHENTICATION = true +;; +;; More detail: https://github.com/gogits/gogs/issues/165 +;ENABLE_REVERSE_PROXY_AUTHENTICATION = false +;ENABLE_REVERSE_PROXY_AUTO_REGISTRATION = false +;ENABLE_REVERSE_PROXY_EMAIL = false +;; +;; Enable captcha validation for registration +;ENABLE_CAPTCHA = false +;; +;; Type of captcha you want to use. Options: image, recaptcha, hcaptcha +;CAPTCHA_TYPE = image +;; +;; Enable recaptcha to use Google's recaptcha service +;; Go to https://www.google.com/recaptcha/admin to sign up for a key +;RECAPTCHA_SECRET = +;RECAPTCHA_SITEKEY = +;; +;; For hCaptcha, create an account at https://accounts.hcaptcha.com/login to get your keys +;HCAPTCHA_SECRET = +;HCAPTCHA_SITEKEY = +;; +;; Change this to use recaptcha.net or other recaptcha service +;RECAPTCHA_URL = https://www.google.com/recaptcha/ +;; +;; Default value for KeepEmailPrivate +;; Each new user will get the value of this setting copied into their profile +;DEFAULT_KEEP_EMAIL_PRIVATE = false +;; +;; Default value for AllowCreateOrganization +;; Every new user will have rights set to create organizations depending on this setting +;DEFAULT_ALLOW_CREATE_ORGANIZATION = true +;; +;; Either "public", "limited" or "private", default is "public" +;; Limited is for users visible only to signed users +;; Private is for users visible only to members of their organizations +;; Public is for users visible for everyone +;DEFAULT_USER_VISIBILITY = public +;; +;; Set whitch visibibilty modes a user can have +;ALLOWED_USER_VISIBILITY_MODES = public,limited,private +;; +;; Either "public", "limited" or "private", default is "public" +;; Limited is for organizations visible only to signed users +;; Private is for organizations visible only to members of the organization +;; Public is for organizations visible to everyone +;DEFAULT_ORG_VISIBILITY = public +;; +;; Default value for DefaultOrgMemberVisible +;; True will make the membership of the users visible when added to the organisation +;DEFAULT_ORG_MEMBER_VISIBLE = false +;; +;; Default value for EnableDependencies +;; Repositories will use dependencies by default depending on this setting +;DEFAULT_ENABLE_DEPENDENCIES = true +;; +;; Dependencies can be added from any repository where the user is granted access or only from the current repository depending on this setting. +;ALLOW_CROSS_REPOSITORY_DEPENDENCIES = true +;; +;; Enable heatmap on users profiles. +;ENABLE_USER_HEATMAP = true +;; +;; Enable Timetracking +;ENABLE_TIMETRACKING = true +;; +;; Default value for EnableTimetracking +;; Repositories will use timetracking by default depending on this setting +;DEFAULT_ENABLE_TIMETRACKING = true +;; +;; Default value for AllowOnlyContributorsToTrackTime +;; Only users with write permissions can track time if this is true +;DEFAULT_ALLOW_ONLY_CONTRIBUTORS_TO_TRACK_TIME = true +;; +;; Value for the domain part of the user's email address in the git log if user +;; has set KeepEmailPrivate to true. The user's email will be replaced with a +;; concatenation of the user name in lower case, "@" and NO_REPLY_ADDRESS. Default +;; value is "noreply." + DOMAIN, where DOMAIN resolves to the value from server.DOMAIN +;; Note: do not use the notation below +;NO_REPLY_ADDRESS = ; noreply. +;; +;; Show Registration button +;SHOW_REGISTRATION_BUTTON = true +;; +;; Show milestones dashboard page - a view of all the user's milestones +;SHOW_MILESTONES_DASHBOARD_PAGE = true +;; +;; Default value for AutoWatchNewRepos +;; When adding a repo to a team or creating a new repo all team members will watch the +;; repo automatically if enabled +;AUTO_WATCH_NEW_REPOS = true +;; +;; Default value for AutoWatchOnChanges +;; Make the user watch a repository When they commit for the first time +;AUTO_WATCH_ON_CHANGES = false +;; +;; Minimum amount of time a user must exist before comments are kept when the user is deleted. +;USER_DELETE_WITH_COMMENTS_MAX_TIME = 0 +;; Valid site url schemes for user profiles +;VALID_SITE_URL_SCHEMES=http,https + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Other Settings +;; +;; Uncomment the [section.header] if you wish to +;; set the below settings. +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[repository] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Root path for storing all repository data. It must be an absolute path. By default, it is stored in a sub-directory of `APP_DATA_PATH`. +;ROOT = +;; +;; The script type this server supports. Usually this is `bash`, but some users report that only `sh` is available. +;SCRIPT_TYPE = bash +;; +;; DETECTED_CHARSETS_ORDER tie-break order for detected charsets. +;; If the charsets have equal confidence, tie-breaking will be done by order in this list +;; with charsets earlier in the list chosen in preference to those later. +;; Adding "defaults" will place the unused charsets at that position. +;DETECTED_CHARSETS_ORDER = UTF-8, UTF-16BE, UTF-16LE, UTF-32BE, UTF-32LE, ISO-8859, windows-1252, ISO-8859, windows-1250, ISO-8859, ISO-8859, ISO-8859, windows-1253, ISO-8859, windows-1255, ISO-8859, windows-1251, windows-1256, KOI8-R, ISO-8859, windows-1254, Shift_JIS, GB18030, EUC-JP, EUC-KR, Big5, ISO-2022, ISO-2022, ISO-2022, IBM424_rtl, IBM424_ltr, IBM420_rtl, IBM420_ltr +;; +;; Default ANSI charset to override non-UTF-8 charsets to +;ANSI_CHARSET = +;; +;; Force every new repository to be private +;FORCE_PRIVATE = false +;; +;; Default privacy setting when creating a new repository, allowed values: last, private, public. Default is last which means the last setting used. +;DEFAULT_PRIVATE = last +;; +;; Default private when using push-to-create +;DEFAULT_PUSH_CREATE_PRIVATE = true +;; +;; Global limit of repositories per user, applied at creation time. -1 means no limit +;MAX_CREATION_LIMIT = -1 +;; +;; Mirror sync queue length, increase if mirror syncing starts hanging +;MIRROR_QUEUE_LENGTH = 1000 +;; +;; Patch test queue length, increase if pull request patch testing starts hanging +;PULL_REQUEST_QUEUE_LENGTH = 1000 +;; +;; Preferred Licenses to place at the top of the List +;; The name here must match the filename in conf/license or custom/conf/license +;PREFERRED_LICENSES = Apache License 2.0,MIT License +;; +;; Disable the ability to interact with repositories using the HTTP protocol +;;DISABLE_HTTP_GIT = false +;; +;; Value for Access-Control-Allow-Origin header, default is not to present +;; WARNING: This may be harmful to your website if you do not give it a right value. +;ACCESS_CONTROL_ALLOW_ORIGIN = +;; +;; Force ssh:// clone url instead of scp-style uri when default SSH port is used +;USE_COMPAT_SSH_URI = false +;; +;; Close issues as long as a commit on any branch marks it as fixed +;; Comma separated list of globally disabled repo units. Allowed values: repo.issues, repo.ext_issues, repo.pulls, repo.wiki, repo.ext_wiki +;DISABLED_REPO_UNITS = +;; +;; Comma separated list of default repo units. Allowed values: repo.code, repo.releases, repo.issues, repo.pulls, repo.wiki, repo.projects. +;; Note: Code and Releases can currently not be deactivated. If you specify default repo units you should still list them for future compatibility. +;; External wiki and issue tracker can't be enabled by default as it requires additional settings. +;; Disabled repo units will not be added to new repositories regardless if it is in the default list. +;DEFAULT_REPO_UNITS = repo.code,repo.releases,repo.issues,repo.pulls,repo.wiki,repo.projects +;; +;; Prefix archive files by placing them in a directory named after the repository +;PREFIX_ARCHIVE_FILES = true +;; +;; Disable the creation of new mirrors. Pre-existing mirrors remain valid. +;DISABLE_MIRRORS = false +;; +;; Disable migrating feature. +;DISABLE_MIGRATIONS = false +;; +;; Disable stars feature. +;DISABLE_STARS = false +;; +;; The default branch name of new repositories +;DEFAULT_BRANCH = master +;; +;; Allow adoption of unadopted repositories +;ALLOW_ADOPTION_OF_UNADOPTED_REPOSITORIES = false +;; +;; Allow deletion of unadopted repositories +;ALLOW_DELETION_OF_UNADOPTED_REPOSITORIES = false + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[repository.editor] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; List of file extensions for which lines should be wrapped in the Monaco editor +;; Separate extensions with a comma. To line wrap files without an extension, just put a comma +;LINE_WRAP_EXTENSIONS = .txt,.md,.markdown,.mdown,.mkd, +;; +;; Valid file modes that have a preview API associated with them, such as api/v1/markdown +;; Separate the values by commas. The preview tab in edit mode won't be displayed if the file extension doesn't match +;PREVIEWABLE_FILE_MODES = markdown + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[repository.local] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Path for local repository copy. Defaults to `tmp/local-repo` +;LOCAL_COPY_PATH = tmp/local-repo + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[repository.upload] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Whether repository file uploads are enabled. Defaults to `true` +;ENABLED = true +;; +;; Path for uploads. Defaults to `data/tmp/uploads` (tmp gets deleted on gitea restart) +;TEMP_PATH = data/tmp/uploads +;; +;; Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types. +;ALLOWED_TYPES = +;; +;; Max size of each file in megabytes. Defaults to 3MB +;FILE_MAX_SIZE = 3 +;; +;; Max number of files per upload. Defaults to 5 +;MAX_FILES = 5 + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[repository.pull-request] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; List of prefixes used in Pull Request title to mark them as Work In Progress +;WORK_IN_PROGRESS_PREFIXES = WIP:,[WIP] +;; +;; List of keywords used in Pull Request comments to automatically close a related issue +;CLOSE_KEYWORDS = close,closes,closed,fix,fixes,fixed,resolve,resolves,resolved +;; +;; List of keywords used in Pull Request comments to automatically reopen a related issue +;REOPEN_KEYWORDS = reopen,reopens,reopened +;; +;; In the default merge message for squash commits include at most this many commits +;DEFAULT_MERGE_MESSAGE_COMMITS_LIMIT = 50 +;; +;; In the default merge message for squash commits limit the size of the commit messages to this +;DEFAULT_MERGE_MESSAGE_SIZE = 5120 +;; +;; In the default merge message for squash commits walk all commits to include all authors in the Co-authored-by otherwise just use those in the limited list +;DEFAULT_MERGE_MESSAGE_ALL_AUTHORS = false +;; +;; In default merge messages limit the number of approvers listed as Reviewed-by: to this many +;DEFAULT_MERGE_MESSAGE_MAX_APPROVERS = 10 +;; +;; In default merge messages only include approvers who are official +;DEFAULT_MERGE_MESSAGE_OFFICIAL_APPROVERS_ONLY = true + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[repository.issue] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; List of reasons why a Pull Request or Issue can be locked +;LOCK_REASONS = Too heated,Off-topic,Resolved,Spam + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[repository.release] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types. +;ALLOWED_TYPES = + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[repository.signing] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; GPG key to use to sign commits, Defaults to the default - that is the value of git config --get user.signingkey +;; run in the context of the RUN_USER +;; Switch to none to stop signing completely +;SIGNING_KEY = default +;; +;; If a SIGNING_KEY ID is provided and is not set to default, use the provided Name and Email address as the signer. +;; These should match a publicized name and email address for the key. (When SIGNING_KEY is default these are set to +;; the results of git config --get user.name and git config --get user.email respectively and can only be overridden +;; by setting the SIGNING_KEY ID to the correct ID.) +;SIGNING_NAME = +;SIGNING_EMAIL = +;; +;; Sets the default trust model for repositories. Options are: collaborator, committer, collaboratorcommitter +;DEFAULT_TRUST_MODEL = collaborator +;; +;; Determines when gitea should sign the initial commit when creating a repository +;; Either: +;; - never +;; - pubkey: only sign if the user has a pubkey +;; - twofa: only sign if the user has logged in with twofa +;; - always +;; options other than none and always can be combined as comma separated list +;INITIAL_COMMIT = always +;; +;; Determines when to sign for CRUD actions +;; - as above +;; - parentsigned: requires that the parent commit is signed. +;CRUD_ACTIONS = pubkey, twofa, parentsigned +;; Determines when to sign Wiki commits +;; - as above +;WIKI = never +;; +;; Determines when to sign on merges +;; - basesigned: require that the parent of commit on the base repo is signed. +;; - commitssigned: require that all the commits in the head branch are signed. +;; - approved: only sign when merging an approved pr to a protected branch +;MERGES = pubkey, twofa, basesigned, commitssigned + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[repository.mimetype_mapping] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Custom MIME type mapping for downloadable files +;.apk=application/vnd.android.package-archive + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[project] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Default templates for project boards +;PROJECT_BOARD_BASIC_KANBAN_TYPE = To Do, In Progress, Done +;PROJECT_BOARD_BUG_TRIAGE_TYPE = Needs Triage, High Priority, Low Priority, Closed + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[cors] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; More information about CORS can be found here: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#The_HTTP_response_headers +;; enable cors headers (disabled by default) +;ENABLED = false +;; +;; scheme of allowed requests +;SCHEME = http +;; +;; list of requesting domains that are allowed +;ALLOW_DOMAIN = * +;; +;; allow subdomains of headers listed above to request +;ALLOW_SUBDOMAIN = false +;; +;; list of methods allowed to request +;METHODS = GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS +;; +;; max time to cache response +;MAX_AGE = 10m +;; +;; allow request with credentials +;ALLOW_CREDENTIALS = false + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[ui] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Number of repositories that are displayed on one explore page +;EXPLORE_PAGING_NUM = 20 +;; +;; Number of issues that are displayed on one page +;ISSUE_PAGING_NUM = 10 +;; +;; Number of maximum commits displayed in one activity feed +;FEED_MAX_COMMIT_NUM = 5 +;; +;; Number of items that are displayed in home feed +;FEED_PAGING_NUM = 20 +;; +;; Number of maximum commits displayed in commit graph. +;GRAPH_MAX_COMMIT_NUM = 100 +;; +;; Number of line of codes shown for a code comment +;CODE_COMMENT_LINES = 4 +;; +;; Value of `theme-color` meta tag, used by Android >= 5.0 +;; An invalid color like "none" or "disable" will have the default style +;; More info: https://developers.google.com/web/updates/2014/11/Support-for-theme-color-in-Chrome-39-for-Android +;THEME_COLOR_META_TAG = `#6cc644` +;; +;; Max size of files to be displayed (default is 8MiB) +;MAX_DISPLAY_FILE_SIZE = 8388608 +;; +;; Whether the email of the user should be shown in the Explore Users page +;SHOW_USER_EMAIL = true +;; +;; Set the default theme for the Gitea install +;DEFAULT_THEME = gitea +;; +;; All available themes. Allow users select personalized themes regardless of the value of `DEFAULT_THEME`. +;THEMES = gitea,arc-green +;; +;; All available reactions users can choose on issues/prs and comments. +;; Values can be emoji alias (:smile:) or a unicode emoji. +;; For custom reactions, add a tightly cropped square image to public/img/emoji/reaction_name.png +;REACTIONS = +1, -1, laugh, hooray, confused, heart, rocket, eyes +;; +;; Additional Emojis not defined in the utf8 standard +;; By default we support gitea (:gitea:), to add more copy them to public/img/emoji/emoji_name.png and add it to this config. +;; Dont mistake it for Reactions. +;CUSTOM_EMOJIS = gitea, codeberg, gitlab, git, github, gogs +;; +;; Whether the full name of the users should be shown where possible. If the full name isn't set, the username will be used. +;DEFAULT_SHOW_FULL_NAME = false +;; +;; Whether to search within description at repository search on explore page. +;SEARCH_REPO_DESCRIPTION = true +;; +;; Whether to enable a Service Worker to cache frontend assets +;USE_SERVICE_WORKER = true + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[ui.admin] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Number of users that are displayed on one page +;USER_PAGING_NUM = 50 +;; +;; Number of repos that are displayed on one page +;REPO_PAGING_NUM = 50 +;; +;; Number of notices that are displayed on one page +;NOTICE_PAGING_NUM = 25 +;; +;; Number of organizations that are displayed on one page +;ORG_PAGING_NUM = 50 + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[ui.user] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Number of repos that are displayed on one page +;REPO_PAGING_NUM = 15 + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[ui.meta] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;AUTHOR = Gitea - Git with a cup of tea +;DESCRIPTION = Gitea (Git with a cup of tea) is a painless self-hosted Git service written in Go +;KEYWORDS = go,git,self-hosted,gitea + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[ui.notification] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Control how often the notification endpoint is polled to update the notification +;; The timeout will increase to MAX_TIMEOUT in TIMEOUT_STEPs if the notification count is unchanged +;; Set MIN_TIMEOUT to 0 to turn off +;MIN_TIMEOUT = 10s +;MAX_TIMEOUT = 60s +;TIMEOUT_STEP = 10s +;; +;; This setting determines how often the db is queried to get the latest notification counts. +;; If the browser client supports EventSource and SharedWorker, a SharedWorker will be used in preference to polling notification. Set to -1 to disable the EventSource +;EVENT_SOURCE_UPDATE_TIME = 10s + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[ui.svg] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Whether to render SVG files as images. If SVG rendering is disabled, SVG files are displayed as text and cannot be embedded in markdown files as images. +;ENABLE_RENDER = true + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[ui.csv] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Maximum allowed file size in bytes to render CSV files as table. (Set to 0 for no limit). +;MAX_FILE_SIZE = 524288 + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[markdown] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Render soft line breaks as hard line breaks, which means a single newline character between +;; paragraphs will cause a line break and adding trailing whitespace to paragraphs is not +;; necessary to force a line break. +;; Render soft line breaks as hard line breaks for comments +;ENABLE_HARD_LINE_BREAK_IN_COMMENTS = true +;; +;; Render soft line breaks as hard line breaks for markdown documents +;ENABLE_HARD_LINE_BREAK_IN_DOCUMENTS = false +;; +;; Comma separated list of custom URL-Schemes that are allowed as links when rendering Markdown +;; for example git,magnet,ftp (more at https://en.wikipedia.org/wiki/List_of_URI_schemes) +;; URLs starting with http and https are always displayed, whatever is put in this entry. +;CUSTOM_URL_SCHEMES = +;; +;; List of file extensions that should be rendered/edited as Markdown +;; Separate the extensions with a comma. To render files without any extension as markdown, just put a comma +;FILE_EXTENSIONS = .md,.markdown,.mdown,.mkd + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[ssh.minimum_key_sizes] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Define allowed algorithms and their minimum key length (use -1 to disable a type) +;ED25519 = 256 +;ECDSA = 256 +;RSA = 2048 +;DSA = -1 ; set to 1024 to switch on + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[indexer] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Issue Indexer settings +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Issue indexer type, currently support: bleve, db or elasticsearch, default is bleve +;ISSUE_INDEXER_TYPE = bleve +;; +;; Issue indexer storage path, available when ISSUE_INDEXER_TYPE is bleve +;ISSUE_INDEXER_PATH = indexers/issues.bleve +;; +;; Issue indexer connection string, available when ISSUE_INDEXER_TYPE is elasticsearch +;ISSUE_INDEXER_CONN_STR = http://elastic:changeme@localhost:9200 +;; +;; Issue indexer name, available when ISSUE_INDEXER_TYPE is elasticsearch +;ISSUE_INDEXER_NAME = gitea_issues +;; +;; Timeout the indexer if it takes longer than this to start. +;; Set to zero to disable timeout. +;STARTUP_TIMEOUT = 30s +;; +;; Issue indexer queue, currently support: channel, levelqueue or redis, default is levelqueue (deprecated - use [queue.issue_indexer]) +;ISSUE_INDEXER_QUEUE_TYPE = levelqueue; **DEPRECATED** use settings in `[queue.issue_indexer]`. +;; +;; When ISSUE_INDEXER_QUEUE_TYPE is levelqueue, this will be the path where the queue will be saved. +;; This can be overridden by `ISSUE_INDEXER_QUEUE_CONN_STR`. +;; default is queues/common +;ISSUE_INDEXER_QUEUE_DIR = queues/common; **DEPRECATED** use settings in `[queue.issue_indexer]`. +;; +;; When `ISSUE_INDEXER_QUEUE_TYPE` is `redis`, this will store the redis connection string. +;; When `ISSUE_INDEXER_QUEUE_TYPE` is `levelqueue`, this is a directory or additional options of +;; the form `leveldb://path/to/db?option=value&....`, and overrides `ISSUE_INDEXER_QUEUE_DIR`. +;ISSUE_INDEXER_QUEUE_CONN_STR = "addrs=127.0.0.1:6379 db=0"; **DEPRECATED** use settings in `[queue.issue_indexer]`. +;; +;; Batch queue number, default is 20 +;ISSUE_INDEXER_QUEUE_BATCH_NUMBER = 20; **DEPRECATED** use settings in `[queue.issue_indexer]`. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Repository Indexer settings +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; repo indexer by default disabled, since it uses a lot of disk space +;REPO_INDEXER_ENABLED = false +;; +;; Code search engine type, could be `bleve` or `elasticsearch`. +;REPO_INDEXER_TYPE = bleve +;; +;; Index file used for code search. available when `REPO_INDEXER_TYPE` is bleve +;REPO_INDEXER_PATH = indexers/repos.bleve +;; +;; Code indexer connection string, available when `REPO_INDEXER_TYPE` is elasticsearch. i.e. http://elastic:changeme@localhost:9200 +;REPO_INDEXER_CONN_STR = +;; +;; Code indexer name, available when `REPO_INDEXER_TYPE` is elasticsearch +;REPO_INDEXER_NAME = gitea_codes +;; +;; A comma separated list of glob patterns (see https://github.com/gobwas/glob) to include +;; in the index; default is empty +;REPO_INDEXER_INCLUDE = +;; +;; A comma separated list of glob patterns to exclude from the index; ; default is empty +;REPO_INDEXER_EXCLUDE = +;; +;; +;UPDATE_BUFFER_LEN = 20; **DEPRECATED** use settings in `[queue.issue_indexer]`. +;MAX_FILE_SIZE = 1048576 + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[queue] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Specific queues can be individually configured with [queue.name]. [queue] provides defaults +;; ([queue.issue_indexer] is special due to the old configuration described above) +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; General queue queue type, currently support: persistable-channel, channel, level, redis, dummy +;; default to persistable-channel +;TYPE = persistable-channel +;; +;; data-dir for storing persistable queues and level queues, individual queues will default to `queues/common` meaning the queue is shared. +;DATADIR = queues/ +;; +;; Default queue length before a channel queue will block +;LENGTH = 20 +;; +;; Batch size to send for batched queues +;BATCH_LENGTH = 20 +;; +;; Connection string for redis queues this will store the redis connection string. +;; When `TYPE` is `persistable-channel`, this provides a directory for the underlying leveldb +;; or additional options of the form `leveldb://path/to/db?option=value&....`, and will override `DATADIR`. +;CONN_STR = "addrs=127.0.0.1:6379 db=0" +;; +;; Provides the suffix of the default redis/disk queue name - specific queues can be overridden within in their [queue.name] sections. +;QUEUE_NAME = "_queue" +;; +;; Provides the suffix of the default redis/disk unique queue set name - specific queues can be overridden within in their [queue.name] sections. +;SET_NAME = "_unique" +;; +;; If the queue cannot be created at startup - level queues may need a timeout at startup - wrap the queue: +;WRAP_IF_NECESSARY = true +;; +;; Attempt to create the wrapped queue at max +;MAX_ATTEMPTS = 10 +;; +;; Timeout queue creation +;TIMEOUT = 15m30s +;; +;; Create a pool with this many workers +;WORKERS = 0 +;; +;; Dynamically scale the worker pool to at this many workers +;MAX_WORKERS = 10 +;; +;; Add boost workers when the queue blocks for BLOCK_TIMEOUT +;BLOCK_TIMEOUT = 1s +;; +;; Remove the boost workers after BOOST_TIMEOUT +;BOOST_TIMEOUT = 5m +;; +;; During a boost add BOOST_WORKERS +;BOOST_WORKERS = 1 + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[admin] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Disallow regular (non-admin) users from creating organizations. +;DISABLE_REGULAR_ORG_CREATION = false +;; +;; Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled +;DEFAULT_EMAIL_NOTIFICATIONS = enabled + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[openid] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; OpenID is an open, standard and decentralized authentication protocol. +;; Your identity is the address of a webpage you provide, which describes +;; how to prove you are in control of that page. +;; +;; For more info: https://en.wikipedia.org/wiki/OpenID +;; +;; Current implementation supports OpenID-2.0 +;; +;; Tested to work providers at the time of writing: +;; - Any GNUSocial node (your.hostname.tld/username) +;; - Any SimpleID provider (http://simpleid.koinic.net) +;; - http://openid.org.cn/ +;; - openid.stackexchange.com +;; - login.launchpad.net +;; - .livejournal.com +;; +;; Whether to allow signin in via OpenID +;ENABLE_OPENID_SIGNIN = true +;; +;; Whether to allow registering via OpenID +;; Do not include to rely on rhw DISABLE_REGISTRATION setting +;;ENABLE_OPENID_SIGNUP = true +;; +;; Allowed URI patterns (POSIX regexp). +;; Space separated. +;; Only these would be allowed if non-blank. +;; Example value: trusted.domain.org trusted.domain.net +;WHITELISTED_URIS = +;; +;; Forbidden URI patterns (POSIX regexp). +;; Space separated. +;; Only used if WHITELISTED_URIS is blank. +;; Example value: loadaverage.org/badguy stackexchange.com/.*spammer +;BLACKLISTED_URIS = + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[oauth2_client] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Whether a new auto registered oauth2 user needs to confirm their email. +;; Do not include to use the REGISTER_EMAIL_CONFIRM setting from the `[service]` section. +;REGISTER_EMAIL_CONFIRM = +;; +;; Scopes for the openid connect oauth2 provider (separated by space, the openid scope is implicitly added). +;; Typical values are profile and email. +;; For more information about the possible values see https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims +;OPENID_CONNECT_SCOPES = +;; +;; Automatically create user accounts for new oauth2 users. +;ENABLE_AUTO_REGISTRATION = false +;; +;; The source of the username for new oauth2 accounts: +;; userid = use the userid / sub attribute +;; nickname = use the nickname attribute +;; email = use the username part of the email attribute +;USERNAME = nickname +;; +;; Update avatar if available from oauth2 provider. +;; Update will be performed on each login. +;UPDATE_AVATAR = false +;; +;; How to handle if an account / email already exists: +;; disabled = show an error +;; login = show an account linking login +;; auto = link directly with the account +;ACCOUNT_LINKING = login + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[webhook] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Hook task queue length, increase if webhook shooting starts hanging +;QUEUE_LENGTH = 1000 +;; +;; Deliver timeout in seconds +;DELIVER_TIMEOUT = 5 +;; +;; Allow insecure certification +;SKIP_TLS_VERIFY = false +;; +;; Number of history information in each page +;PAGING_NUM = 10 +;; +;; Proxy server URL, support http://, https//, socks://, blank will follow environment http_proxy/https_proxy +;PROXY_URL = +;; +;; Comma separated list of host names requiring proxy. Glob patterns (*) are accepted; use ** to match all hosts. +;PROXY_HOSTS = + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[mailer] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;ENABLED = false +;; +;; Buffer length of channel, keep it as it is if you don't know what it is. +;SEND_BUFFER_LEN = 100 +;; +;; Prefix displayed before subject in mail +;SUBJECT_PREFIX = +;; +;; Mail server +;; Gmail: smtp.gmail.com:587 +;; QQ: smtp.qq.com:465 +;; As per RFC 8314 using Implicit TLS/SMTPS on port 465 (if supported) is recommended, +;; otherwise STARTTLS on port 587 should be used. +;HOST = +;; +;; Disable HELO operation when hostnames are different. +;DISABLE_HELO = +;; +;; Custom hostname for HELO operation, if no value is provided, one is retrieved from system. +;HELO_HOSTNAME = +;; +;; Whether or not to skip verification of certificates; `true` to disable verification. This option is unsafe. Consider adding the certificate to the system trust store instead. +;SKIP_VERIFY = false +;; +;; Use client certificate +;USE_CERTIFICATE = false +;CERT_FILE = custom/mailer/cert.pem +;KEY_FILE = custom/mailer/key.pem +;; +;; Should SMTP connect with TLS, (if port ends with 465 TLS will always be used.) +;; If this is false but STARTTLS is supported the connection will be upgraded to TLS opportunistically. +;IS_TLS_ENABLED = false +;; +;; Mail from address, RFC 5322. This can be just an email address, or the `"Name" ` format +;FROM = +;; +;; Mailer user name and password +;; Please Note: Authentication is only supported when the SMTP server communication is encrypted with TLS (this can be via STARTTLS) or `HOST=localhost`. +;USER = +;; +;; Use PASSWD = `your password` for quoting if you use special characters in the password. +;PASSWD = +;; +;; Send mails as plain text +;SEND_AS_PLAIN_TEXT = false +;; +;; Set Mailer Type (either SMTP, sendmail or dummy to just send to the log) +;MAILER_TYPE = smtp +;; +;; Specify an alternative sendmail binary +;SENDMAIL_PATH = sendmail +;; +;; Specify any extra sendmail arguments +;SENDMAIL_ARGS = +;; +;; Timeout for Sendmail +;SENDMAIL_TIMEOUT = 5m + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[cache] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; if the cache enabled +;ENABLED = true +;; +;; Either "memory", "redis", or "memcache", default is "memory" +;ADAPTER = memory +;; +;; For "memory" only, GC interval in seconds, default is 60 +;INTERVAL = 60 +;; +;; For "redis" and "memcache", connection host address +;; redis: network=tcp,addr=:6379,password=macaron,db=0,pool_size=100,idle_timeout=180 +;; memcache: `127.0.0.1:11211` +;HOST = +;; +;; Time to keep items in cache if not used, default is 16 hours. +;; Setting it to 0 disables caching +;ITEM_TTL = 16h + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Last commit cache +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[cache.last_commit] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; if the cache enabled +;ENABLED = true +;; +;; Time to keep items in cache if not used, default is 8760 hours. +;; Setting it to 0 disables caching +;ITEM_TTL = 8760h +;; +;; Only enable the cache when repository's commits count great than +;COMMITS_COUNT = 1000 + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[session] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Either "memory", "file", or "redis", default is "memory" +;PROVIDER = memory +;; +;; Provider config options +;; memory: doesn't have any config yet +;; file: session file path, e.g. `data/sessions` +;; redis: network=tcp,addr=:6379,password=macaron,db=0,pool_size=100,idle_timeout=180 +;; mysql: go-sql-driver/mysql dsn config string, e.g. `root:password@/session_table` +;PROVIDER_CONFIG = data/sessions +;; +;; Session cookie name +;COOKIE_NAME = i_like_gitea +;; +;; If you use session in https only, default is false +;COOKIE_SECURE = false +;; +;; Session GC time interval in seconds, default is 86400 (1 day) +;GC_INTERVAL_TIME = 86400 +;; +;; Session life time in seconds, default is 86400 (1 day) +;SESSION_LIFE_TIME = 86400 +;; +;; SameSite settings. Either "none", "lax", or "strict" +;SAME_SITE=lax + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[picture] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;AVATAR_UPLOAD_PATH = data/avatars +;REPOSITORY_AVATAR_UPLOAD_PATH = data/repo-avatars +;; +;; How Gitea deals with missing repository avatars +;; none = no avatar will be displayed; random = random avatar will be displayed; image = default image will be used +;REPOSITORY_AVATAR_FALLBACK = none +;REPOSITORY_AVATAR_FALLBACK_IMAGE = /img/repo_default.png +;; +;; Max Width and Height of uploaded avatars. +;; This is to limit the amount of RAM used when resizing the image. +;AVATAR_MAX_WIDTH = 4096 +;AVATAR_MAX_HEIGHT = 3072 +;; +;; Maximum allowed file size for uploaded avatars. +;; This is to limit the amount of RAM used when resizing the image. +;AVATAR_MAX_FILE_SIZE = 1048576 +;; +;; Chinese users can choose "duoshuo" +;; or a custom avatar source, like: http://cn.gravatar.com/avatar/ +;GRAVATAR_SOURCE = gravatar +;; +;; This value will always be true in offline mode. +;DISABLE_GRAVATAR = false +;; +;; Federated avatar lookup uses DNS to discover avatar associated +;; with emails, see https://www.libravatar.org +;; This value will always be false in offline mode or when Gravatar is disabled. +;ENABLE_FEDERATED_AVATAR = false + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[attachment] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Whether issue and pull request attachments are enabled. Defaults to `true` +;ENABLED = true +;; +;; Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types. +;ALLOWED_TYPES = .docx,.gif,.gz,.jpeg,.jpg,.log,.pdf,.png,.pptx,.txt,.xlsx,.zip +;; +;; Max size of each file. Defaults to 4MB +;MAX_SIZE = 4 +;; +;; Max number of files per upload. Defaults to 5 +;MAX_FILES = 5 +;; +;; Storage type for attachments, `local` for local disk or `minio` for s3 compatible +;; object storage service, default is `local`. +;STORAGE_TYPE = local +;; +;; Allows the storage driver to redirect to authenticated URLs to serve files directly +;; Currently, only `minio` is supported. +;SERVE_DIRECT = false +;; +;; Path for attachments. Defaults to `data/attachments` only available when STORAGE_TYPE is `local` +;PATH = data/attachments +;; +;; Minio endpoint to connect only available when STORAGE_TYPE is `minio` +;MINIO_ENDPOINT = localhost:9000 +;; +;; Minio accessKeyID to connect only available when STORAGE_TYPE is `minio` +;MINIO_ACCESS_KEY_ID = +;; +;; Minio secretAccessKey to connect only available when STORAGE_TYPE is `minio` +;MINIO_SECRET_ACCESS_KEY = +;; +;; Minio bucket to store the attachments only available when STORAGE_TYPE is `minio` +;MINIO_BUCKET = gitea +;; +;; Minio location to create bucket only available when STORAGE_TYPE is `minio` +;MINIO_LOCATION = us-east-1 +;; +;; Minio base path on the bucket only available when STORAGE_TYPE is `minio` +;MINIO_BASE_PATH = attachments/ +;; +;; Minio enabled ssl only available when STORAGE_TYPE is `minio` +;MINIO_USE_SSL = false -[U2F] -; NOTE: THE DEFAULT VALUES HERE WILL NEED TO BE CHANGED -; Two Factor authentication with security keys -; https://developers.yubico.com/U2F/App_ID.html -;APP_ID = http://localhost:3000/ -; Comma separated list of trusted facets -;TRUSTED_FACETS = http://localhost:3000/ - -; Extension mapping to highlight class -; e.g. .toml=ini -[highlight.mapping] - -[other] -SHOW_FOOTER_BRANDING = false -; Show version information about Gitea and Go in the footer -SHOW_FOOTER_VERSION = true -; Show template execution time in the footer -SHOW_FOOTER_TEMPLATE_LOAD_TIME = true - -[markup.sanitizer.1] -; The following keys can appear once to define a sanitation policy rule. -; This section can appear multiple times by adding a unique alphanumeric suffix to define multiple rules. -; e.g., [markup.sanitizer.1] -> [markup.sanitizer.2] -> [markup.sanitizer.TeX] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[time] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Specifies the format for fully outputted dates. Defaults to RFC1123 +;; Special supported values are ANSIC, UnixDate, RubyDate, RFC822, RFC822Z, RFC850, RFC1123, RFC1123Z, RFC3339, RFC3339Nano, Kitchen, Stamp, StampMilli, StampMicro and StampNano +;; For more information about the format see http://golang.org/pkg/time/#pkg-constants +;FORMAT = +;; +;; Location the UI time display i.e. Asia/Shanghai +;; Empty means server's location setting +;DEFAULT_UI_LOCATION = + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[cron] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Common settings +;; +;; Setting this to true will enable all cron tasks periodically with default settings. +;ENABLED = false +;; Setting this to true will run all enabled cron tasks when Gitea starts. +;RUN_AT_START = false +;; +;; Note: ``SCHEDULE`` accept formats +;; - Full crontab specs, e.g. "* * * * * ?" +;; - Descriptors, e.g. "@midnight", "@every 1h30m" +;; See more: https://pkg.go.dev/github.com/gogs/cron@v0.0.0-20171120032916-9f6c956d3e14 + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Basic cron tasks - enabled by default +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Clean up old repository archives +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[cron.archive_cleanup] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Whether to enable the job +;ENABLED = true +;; Whether to always run at least once at start up time (if ENABLED) +;RUN_AT_START = true +;; Notice if not success +;NO_SUCCESS_NOTICE = false +;; Time interval for job to run +;SCHEDULE = @every 24h +;; Archives created more than OLDER_THAN ago are subject to deletion +;OLDER_THAN = 24h + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Update mirrors +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[cron.update_mirrors] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;SCHEDULE = @every 10m +;; Enable running Update mirrors task periodically. +;ENABLED = true +;; Run Update mirrors task when Gitea starts. +;RUN_AT_START = false +;; Notice if not success +;NO_SUCCESS_NOTICE = true + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Repository health check +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[cron.repo_health_check] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;SCHEDULE = @every 24h +;; Enable running Repository health check task periodically. +;ENABLED = true +;; Run Repository health check task when Gitea starts. +;RUN_AT_START = false +;; Notice if not success +;NO_SUCCESS_NOTICE = false +;TIMEOUT = 60s +;; Arguments for command 'git fsck', e.g. "--unreachable --tags" +;; see more on http://git-scm.com/docs/git-fsck +;ARGS = + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Check repository statistics +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[cron.check_repo_stats] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Enable running check repository statistics task periodically. +;ENABLED = true +;; Run check repository statistics task when Gitea starts. +;RUN_AT_START = true +;; Notice if not success +;NO_SUCCESS_NOTICE = false +;SCHEDULE = @every 24h + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[cron.update_migration_poster_id] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Update migrated repositories' issues and comments' posterid, it will always attempt synchronization when the instance starts. +;ENABLED = true +;; Update migrated repositories' issues and comments' posterid when starting server (default true) +;RUN_AT_START = true +;; Notice if not success +;NO_SUCCESS_NOTICE = false +;; Interval as a duration between each synchronization. (default every 24h) +;SCHEDULE = @every 24h + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Synchronize external user data (only LDAP user synchronization is supported) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[cron.sync_external_users] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;ENABLED = true +;; Synchronize external user data when starting server (default false) +;RUN_AT_START = false +;; Notice if not success +;NO_SUCCESS_NOTICE = false +;; Interval as a duration between each synchronization (default every 24h) +;SCHEDULE = @every 24h +;; Create new users, update existing user data and disable users that are not in external source anymore (default) +;; or only create new users if UPDATE_EXISTING is set to false +;UPDATE_EXISTING = true + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Clean-up deleted branches +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[cron.deleted_branches_cleanup] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;ENABLED = true +;; Clean-up deleted branches when starting server (default true) +;RUN_AT_START = true +;; Notice if not success +;NO_SUCCESS_NOTICE = false +;; Interval as a duration between each synchronization (default every 24h) +;SCHEDULE = @every 24h +;; deleted branches than OLDER_THAN ago are subject to deletion +;OLDER_THAN = 24h + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Cleanup hook_task table +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[cron.cleanup_hook_task_table] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Whether to enable the job +;ENABLED = true +;; Whether to always run at start up time (if ENABLED) +;RUN_AT_START = false +;; Time interval for job to run +;SCHEDULE = @every 24h +;; OlderThan or PerWebhook. How the records are removed, either by age (i.e. how long ago hook_task record was delivered) or by the number to keep per webhook (i.e. keep most recent x deliveries per webhook). +;CLEANUP_TYPE = OlderThan +;; If CLEANUP_TYPE is set to OlderThan, then any delivered hook_task records older than this expression will be deleted. +;OLDER_THAN = 168h +;; If CLEANUP_TYPE is set to PerWebhook, this is number of hook_task records to keep for a webhook (i.e. keep the most recent x deliveries). +;NUMBER_TO_KEEP = 10 + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Extended cron task - not enabled by default +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Delete all unactivated accounts +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[cron.delete_inactive_accounts] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;ENABLED = false +;RUN_AT_START = false +;NO_SUCCESS_NOTICE = false +;SCHEDULE = @annually +;OLDER_THAN = 168h + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Delete all repository archives +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[cron.delete_repo_archives] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;ENABLED = false +;RUN_AT_START = false +;NO_SUCCESS_NOTICE = false +;SCHEDULE = @annually; + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Garbage collect all repositories +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[cron.git_gc_repos] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;ENABLED = false +;RUN_AT_START = false +;NO_SUCCESS_NOTICE = false +;SCHEDULE = @every 72h +;TIMEOUT = 60s +;; Arguments for command 'git gc' +;; The default value is same with [git] -> GC_ARGS +;ARGS = + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Update the '.ssh/authorized_keys' file with Gitea SSH keys +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[cron.resync_all_sshkeys] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;ENABLED = false +;RUN_AT_START = false +;NO_SUCCESS_NOTICE = false +;SCHEDULE = @every 72h + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Resynchronize pre-receive, update and post-receive hooks of all repositories. +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[cron.resync_all_hooks] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;ENABLED = false +;RUN_AT_START = false +;NO_SUCCESS_NOTICE = false +;SCHEDULE = @every 72h + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Reinitialize all missing Git repositories for which records exist +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[cron.reinit_missing_repos] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;ENABLED = false +;RUN_AT_START = false +;NO_SUCCESS_NOTICE = f;alse +;SCHEDULE = @every 72h + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Delete all repositories missing their Git files +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[cron.delete_missing_repos] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;ENABLED = false +;RUN_AT_START = false +;NO_SUCCESS_NOTICE = false +;SCHEDULE = @every 72h + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Delete generated repository avatars +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[cron.delete_generated_repository_avatars] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;ENABLED = false +;RUN_AT_START = false +;NO_SUCCESS_NOTICE = f;alse +;SCHEDULE = @every 72h + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Delete all old actions from database +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[cron.delete_old_actions] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;ENABLED = false +;RUN_AT_START = false +;NO_SUCCESS_NOTICE = false +;SCHEDULE = @every 168h +;OLDER_THAN = 8760h + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Git Operation timeout in seconds +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[git.timeout] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;DEFAULT = 360 +;MIGRATE = 600 +;MIRROR = 300 +;CLONE = 300 +;PULL = 300 +;GC = 60 + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[mirror] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Default interval as a duration between each check +;DEFAULT_INTERVAL = 8h +;; Min interval as a duration must be > 1m +;MIN_INTERVAL = 10m + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[api] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Enables Swagger. True or false; default is true. +;ENABLE_SWAGGER = true +;; Max number of items in a page +;MAX_RESPONSE_ITEMS = 50 +;; Default paging number of api +;DEFAULT_PAGING_NUM = 30 +;; Default and maximum number of items per page for git trees api +;DEFAULT_GIT_TREES_PER_PAGE = 1000 +;; Default size of a blob returned by the blobs API (default is 10MiB) +;DEFAULT_MAX_BLOB_SIZE = 10485760 + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[i18n] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;LANGS = en-US,zh-CN,zh-HK,zh-TW,de-DE,fr-FR,nl-NL,lv-LV,ru-RU,uk-UA,ja-JP,es-ES,pt-BR,pt-PT,pl-PL,bg-BG,it-IT,fi-FI,tr-TR,cs-CZ,sr-SP,sv-SE,ko-KR +;NAMES = English,简体中文,繁體中文(香港),繁體中文(台灣),Deutsch,français,Nederlands,latviešu,русский,Українська,日本語,español,português do Brasil,Português de Portugal,polski,български,italiano,suomi,Türkçe,čeština,српски,svenska,한국어 + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[highlight.mapping] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Extension mapping to highlight class +;; e.g. .toml=ini + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[other] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;SHOW_FOOTER_BRANDING = false +;; Show version information about Gitea and Go in the footer +;SHOW_FOOTER_VERSION = true +;; Show template execution time in the footer +;SHOW_FOOTER_TEMPLATE_LOAD_TIME = true + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[markup.sanitizer.1] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; The following keys can appear once to define a sanitation policy rule. +;; This section can appear multiple times by adding a unique alphanumeric suffix to define multiple rules. +;; e.g., [markup.sanitizer.1] -> [markup.sanitizer.2] -> [markup.sanitizer.TeX] ;ELEMENT = span ;ALLOW_ATTR = class ;REGEXP = ^(info|warning|error)$ - -[markup.asciidoc] -ENABLED = false -; List of file extensions that should be rendered by an external command -FILE_EXTENSIONS = .adoc,.asciidoc -; External command to render all matching extensions -RENDER_COMMAND = "asciidoc --out-file=- -" -; Don't pass the file on STDIN, pass the filename as argument instead. -IS_INPUT_FILE = false - -[metrics] -; Enables metrics endpoint. True or false; default is false. -ENABLED = false -; If you want to add authorization, specify a token here -TOKEN = - -[task] -; Task queue type, could be `channel` or `redis`. -QUEUE_TYPE = channel -; Task queue length, available only when `QUEUE_TYPE` is `channel`. -QUEUE_LENGTH = 1000 -; Task queue connection string, available only when `QUEUE_TYPE` is `redis`. -; If there is a password of redis, use `addrs=127.0.0.1:6379 password=123 db=0`. -QUEUE_CONN_STR = "addrs=127.0.0.1:6379 db=0" - -[migrations] -; Max attempts per http/https request on migrations. -MAX_ATTEMPTS = 3 -; Backoff time per http/https request retry (seconds) -RETRY_BACKOFF = 3 -; Allowed domains for migrating, default is blank. Blank means everything will be allowed. -; Multiple domains could be separated by commas. -ALLOWED_DOMAINS = -; Blocklist for migrating, default is blank. Multiple domains could be separated by commas. -; When ALLOWED_DOMAINS is not blank, this option will be ignored. -BLOCKED_DOMAINS = -; Allow private addresses defined by RFC 1918, RFC 1122, RFC 4632 and RFC 4291 (false by default) -ALLOW_LOCALNETWORKS = false - -; default storage for attachments, lfs and avatars -[storage] -; storage type -STORAGE_TYPE = local - -; lfs storage will override storage -[lfs] -STORAGE_TYPE = local - -; customize storage +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Other markup formats e.g. asciidoc +;; +;; uncomment and enable the below section. +;; (You can add other markup formats by copying the section and adjusting +;; the section name suffix "asciidoc" to something else.) +;[markup.asciidoc] +;ENABLED = false +;; List of file extensions that should be rendered by an external command +;FILE_EXTENSIONS = .adoc,.asciidoc +;; External command to render all matching extensions +;RENDER_COMMAND = "asciidoc --out-file=- -" +;; Don't pass the file on STDIN, pass the filename as argument instead. +;IS_INPUT_FILE = false + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[metrics] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Enables metrics endpoint. True or false; default is false. +;ENABLED = false +;; If you want to add authorization, specify a token here +;TOKEN = + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[task] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Task queue type, could be `channel` or `redis`. +;QUEUE_TYPE = channel +;; +;; Task queue length, available only when `QUEUE_TYPE` is `channel`. +;QUEUE_LENGTH = 1000 +;; +;; Task queue connection string, available only when `QUEUE_TYPE` is `redis`. +;; If there is a password of redis, use `addrs=127.0.0.1:6379 password=123 db=0`. +;QUEUE_CONN_STR = "addrs=127.0.0.1:6379 db=0" + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[migrations] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Max attempts per http/https request on migrations. +;MAX_ATTEMPTS = 3 +;; +;; Backoff time per http/https request retry (seconds) +;RETRY_BACKOFF = 3 +;; +;; Allowed domains for migrating, default is blank. Blank means everything will be allowed. +;; Multiple domains could be separated by commas. +;ALLOWED_DOMAINS = +;; +;; Blocklist for migrating, default is blank. Multiple domains could be separated by commas. +;; When ALLOWED_DOMAINS is not blank, this option will be ignored. +;BLOCKED_DOMAINS = +;; +;; Allow private addresses defined by RFC 1918, RFC 1122, RFC 4632 and RFC 4291 (false by default) +;ALLOW_LOCALNETWORKS = false + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; default storage for attachments, lfs and avatars +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[storage] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; storage type +;STORAGE_TYPE = local + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; settings for repository archives, will override storage setting +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[storage.repo-archive] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; storage type +;STORAGE_TYPE = local + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; lfs storage will override storage +;; +;[lfs] +;STORAGE_TYPE = local + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; customize storage ;[storage.my_minio] ;STORAGE_TYPE = minio -; Minio endpoint to connect only available when STORAGE_TYPE is `minio` +;; +;; Minio endpoint to connect only available when STORAGE_TYPE is `minio` ;MINIO_ENDPOINT = localhost:9000 -; Minio accessKeyID to connect only available when STORAGE_TYPE is `minio` +;; +;; Minio accessKeyID to connect only available when STORAGE_TYPE is `minio` ;MINIO_ACCESS_KEY_ID = -; Minio secretAccessKey to connect only available when STORAGE_TYPE is `minio` +;; +;; Minio secretAccessKey to connect only available when STORAGE_TYPE is `minio` ;MINIO_SECRET_ACCESS_KEY = -; Minio bucket to store the attachments only available when STORAGE_TYPE is `minio` +;; +;; Minio bucket to store the attachments only available when STORAGE_TYPE is `minio` ;MINIO_BUCKET = gitea -; Minio location to create bucket only available when STORAGE_TYPE is `minio` +;; +;; Minio location to create bucket only available when STORAGE_TYPE is `minio` ;MINIO_LOCATION = us-east-1 -; Minio enabled ssl only available when STORAGE_TYPE is `minio` +;; +;; Minio enabled ssl only available when STORAGE_TYPE is `minio` ;MINIO_USE_SSL = false diff --git a/models/fixtures/user.yml b/models/fixtures/user.yml index 31cf58d412937..da1d616249501 100644 --- a/models/fixtures/user.yml +++ b/models/fixtures/user.yml @@ -542,3 +542,21 @@ avatar_email: user31@example.com num_repos: 0 is_active: true + +- + id: 32 + lower_name: user32 + name: user32 + full_name: User ThirtyTwo + email: user32@example.com + passwd_hash_algo: argon2$2$65536$12$50 + passwd: 1d2304e48d92db2fed37bdb4b3e7fca97fc41f2454b32de343007f0acb4623f04b07a477759fa83df487d12310a2c4c1ab25 # password + type: 0 # individual + salt: ZogKvWdyEx + is_admin: false + login_type: 1 + is_restricted: true + avatar: avatar29 + avatar_email: user32@example.com + num_repos: 0 + is_active: true diff --git a/models/login_source.go b/models/login_source.go index b96f72e95c3fb..f0c8af5cb7557 100644 --- a/models/login_source.go +++ b/models/login_source.go @@ -19,8 +19,11 @@ import ( "code.gitea.io/gitea/modules/auth/oauth2" "code.gitea.io/gitea/modules/auth/pam" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/secret" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" + gouuid "github.com/google/uuid" jsoniter "github.com/json-iterator/go" "xorm.io/xorm" @@ -68,6 +71,17 @@ var ( _ convert.Conversion = &SSPIConfig{} ) +// jsonUnmarshalIgnoreErroneousBOM - due to a bug in xorm (see https://gitea.com/xorm/xorm/pulls/1957) - it's +// possible that a Blob may gain an unwanted prefix of 0xff 0xfe. +func jsonUnmarshalIgnoreErroneousBOM(bs []byte, v interface{}) error { + json := jsoniter.ConfigCompatibleWithStandardLibrary + err := json.Unmarshal(bs, &v) + if err != nil && len(bs) > 2 && bs[0] == 0xff && bs[1] == 0xfe { + err = json.Unmarshal(bs[2:], &v) + } + return err +} + // LDAPConfig holds configuration for LDAP login source. type LDAPConfig struct { *ldap.Source @@ -75,12 +89,25 @@ type LDAPConfig struct { // FromDB fills up a LDAPConfig from serialized format. func (cfg *LDAPConfig) FromDB(bs []byte) error { - json := jsoniter.ConfigCompatibleWithStandardLibrary - return json.Unmarshal(bs, &cfg) + err := jsonUnmarshalIgnoreErroneousBOM(bs, &cfg) + if err != nil { + return err + } + if cfg.BindPasswordEncrypt != "" { + cfg.BindPassword, err = secret.DecryptSecret(setting.SecretKey, cfg.BindPasswordEncrypt) + cfg.BindPasswordEncrypt = "" + } + return err } // ToDB exports a LDAPConfig to a serialized format. func (cfg *LDAPConfig) ToDB() ([]byte, error) { + var err error + cfg.BindPasswordEncrypt, err = secret.EncryptSecret(setting.SecretKey, cfg.BindPassword) + if err != nil { + return nil, err + } + cfg.BindPassword = "" json := jsoniter.ConfigCompatibleWithStandardLibrary return json.Marshal(cfg) } @@ -103,8 +130,7 @@ type SMTPConfig struct { // FromDB fills up an SMTPConfig from serialized format. func (cfg *SMTPConfig) FromDB(bs []byte) error { - json := jsoniter.ConfigCompatibleWithStandardLibrary - return json.Unmarshal(bs, cfg) + return jsonUnmarshalIgnoreErroneousBOM(bs, cfg) } // ToDB exports an SMTPConfig to a serialized format. @@ -116,12 +142,12 @@ func (cfg *SMTPConfig) ToDB() ([]byte, error) { // PAMConfig holds configuration for the PAM login source. type PAMConfig struct { ServiceName string // pam service (e.g. system-auth) + EmailDomain string } // FromDB fills up a PAMConfig from serialized format. func (cfg *PAMConfig) FromDB(bs []byte) error { - json := jsoniter.ConfigCompatibleWithStandardLibrary - return json.Unmarshal(bs, &cfg) + return jsonUnmarshalIgnoreErroneousBOM(bs, cfg) } // ToDB exports a PAMConfig to a serialized format. @@ -142,8 +168,7 @@ type OAuth2Config struct { // FromDB fills up an OAuth2Config from serialized format. func (cfg *OAuth2Config) FromDB(bs []byte) error { - json := jsoniter.ConfigCompatibleWithStandardLibrary - return json.Unmarshal(bs, cfg) + return jsonUnmarshalIgnoreErroneousBOM(bs, cfg) } // ToDB exports an SMTPConfig to a serialized format. @@ -163,8 +188,7 @@ type SSPIConfig struct { // FromDB fills up an SSPIConfig from serialized format. func (cfg *SSPIConfig) FromDB(bs []byte) error { - json := jsoniter.ConfigCompatibleWithStandardLibrary - return json.Unmarshal(bs, cfg) + return jsonUnmarshalIgnoreErroneousBOM(bs, cfg) } // ToDB exports an SSPIConfig to a serialized format. @@ -477,7 +501,7 @@ func LoginViaLDAP(user *User, login, password string, source *LoginSource) (*Use return nil, ErrUserNotExist{0, login, 0} } - var isAttributeSSHPublicKeySet = len(strings.TrimSpace(source.LDAP().AttributeSSHPublicKey)) > 0 + isAttributeSSHPublicKeySet := len(strings.TrimSpace(source.LDAP().AttributeSSHPublicKey)) > 0 // Update User admin flag if exist if isExist, err := IsUserExist(0, sr.Username); err != nil { @@ -696,15 +720,26 @@ func LoginViaPAM(user *User, login, password string, sourceID int64, cfg *PAMCon // Allow PAM sources with `@` in their name, like from Active Directory username := pamLogin + email := pamLogin idx := strings.Index(pamLogin, "@") if idx > -1 { username = pamLogin[:idx] } + if ValidateEmail(email) != nil { + if cfg.EmailDomain != "" { + email = fmt.Sprintf("%s@%s", username, cfg.EmailDomain) + } else { + email = fmt.Sprintf("%s@%s", username, setting.Service.NoReplyAddress) + } + if ValidateEmail(email) != nil { + email = gouuid.New().String() + "@localhost" + } + } user = &User{ LowerName: strings.ToLower(username), Name: username, - Email: pamLogin, + Email: email, Passwd: password, LoginType: LoginPAM, LoginSource: sourceID, @@ -828,7 +863,11 @@ func UserSignIn(username, password string) (*User, error) { return authUser, nil } - log.Warn("Failed to login '%s' via '%s': %v", username, source.Name, err) + if IsErrUserNotExist(err) { + log.Debug("Failed to login '%s' via '%s': %v", username, source.Name, err) + } else { + log.Warn("Failed to login '%s' via '%s': %v", username, source.Name, err) + } } return nil, ErrUserNotExist{user.ID, user.Name, 0} diff --git a/models/user_test.go b/models/user_test.go index af9282acdc466..5810c4d170596 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -137,13 +137,13 @@ func TestSearchUsers(t *testing.T) { } testUserSuccess(&SearchUserOptions{OrderBy: "id ASC", ListOptions: ListOptions{Page: 1}}, - []int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30, 31}) + []int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30, 32}) testUserSuccess(&SearchUserOptions{ListOptions: ListOptions{Page: 1}, IsActive: util.OptionalBoolFalse}, []int64{9}) testUserSuccess(&SearchUserOptions{OrderBy: "id ASC", ListOptions: ListOptions{Page: 1}, IsActive: util.OptionalBoolTrue}, - []int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 28, 29, 30, 31}) + []int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 28, 29, 30, 32}) testUserSuccess(&SearchUserOptions{Keyword: "user1", OrderBy: "id ASC", ListOptions: ListOptions{Page: 1}, IsActive: util.OptionalBoolTrue}, []int64{1, 10, 11, 12, 13, 14, 15, 16, 18}) @@ -250,7 +250,7 @@ func TestHashPasswordDeterministic(t *testing.T) { func TestOldPasswordMatchAndUpdate(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) - u := AssertExistsAndLoadBean(t, &User{ID: 31}).(*User) + u := AssertExistsAndLoadBean(t, &User{ID: 32}).(*User) hash.DefaultHasher.DefaultAlgorithm = "argon2" @@ -272,7 +272,7 @@ func TestOldPasswordMatchAndUpdate(t *testing.T) { argonHasher.Parallelism = 8 argonHasher.KeyLength = 50 - user, _ := UserSignIn("user31", matchingPass) + user, _ := UserSignIn("user32", matchingPass) validates = user.ValidatePassword(matchingPass) newPass = user.Passwd diff --git a/routers/install/install.go b/routers/install/install.go new file mode 100644 index 0000000000000..9f8bb59e8dbce --- /dev/null +++ b/routers/install/install.go @@ -0,0 +1,474 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package install + +import ( + "code.gitea.io/gitea/modules/auth/hash" + "fmt" + "net/http" + "os" + "os/exec" + "path/filepath" + "strings" + "time" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/generate" + "code.gitea.io/gitea/modules/graceful" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/templates" + "code.gitea.io/gitea/modules/translation" + "code.gitea.io/gitea/modules/user" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/modules/web/middleware" + "code.gitea.io/gitea/services/forms" + + "gitea.com/go-chi/session" + "gopkg.in/ini.v1" +) + +const ( + // tplInstall template for installation page + tplInstall base.TplName = "install" + tplPostInstall base.TplName = "post-install" +) + +// Init prepare for rendering installation page +func Init(next http.Handler) http.Handler { + var rnd = templates.HTMLRenderer() + + return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { + if setting.InstallLock { + resp.Header().Add("Refresh", "1; url="+setting.AppURL+"user/login") + _ = rnd.HTML(resp, 200, string(tplPostInstall), nil) + return + } + var locale = middleware.Locale(resp, req) + var startTime = time.Now() + var ctx = context.Context{ + Resp: context.NewResponse(resp), + Flash: &middleware.Flash{}, + Locale: locale, + Render: rnd, + Session: session.GetSession(req), + Data: map[string]interface{}{ + "Title": locale.Tr("install.install"), + "PageIsInstall": true, + "DbOptions": setting.SupportedDatabases, + "i18n": locale, + "Language": locale.Language(), + "Lang": locale.Language(), + "AllLangs": translation.AllLangs(), + "CurrentURL": setting.AppSubURL + req.URL.RequestURI(), + "PageStartTime": startTime, + "TmplLoadTimes": func() string { + return time.Since(startTime).String() + }, + "PasswordHashAlgorithms": models.AvailableHashAlgorithms, + }, + } + for _, lang := range translation.AllLangs() { + if lang.Lang == locale.Language() { + ctx.Data["LangName"] = lang.Name + break + } + } + ctx.Req = context.WithContext(req, &ctx) + next.ServeHTTP(resp, ctx.Req) + }) +} + +// Install render installation page +func Install(ctx *context.Context) { + form := forms.InstallForm{} + + // Database settings + form.DbHost = setting.Database.Host + form.DbUser = setting.Database.User + form.DbPasswd = setting.Database.Passwd + form.DbName = setting.Database.Name + form.DbPath = setting.Database.Path + form.DbSchema = setting.Database.Schema + form.Charset = setting.Database.Charset + + var curDBOption = "MySQL" + switch setting.Database.Type { + case "postgres": + curDBOption = "PostgreSQL" + case "mssql": + curDBOption = "MSSQL" + case "sqlite3": + if setting.EnableSQLite3 { + curDBOption = "SQLite3" + } + } + + ctx.Data["CurDbOption"] = curDBOption + + // Application general settings + form.AppName = setting.AppName + form.RepoRootPath = setting.RepoRootPath + form.LFSRootPath = setting.LFS.Path + + // Note(unknown): it's hard for Windows users change a running user, + // so just use current one if config says default. + if setting.IsWindows && setting.RunUser == "git" { + form.RunUser = user.CurrentUsername() + } else { + form.RunUser = setting.RunUser + } + + form.Domain = setting.Domain + form.SSHPort = setting.SSH.Port + form.HTTPPort = setting.HTTPPort + form.AppURL = setting.AppURL + form.LogRootPath = setting.LogRootPath + + // E-mail service settings + if setting.MailService != nil { + form.SMTPHost = setting.MailService.Host + form.SMTPFrom = setting.MailService.From + form.SMTPUser = setting.MailService.User + } + form.RegisterConfirm = setting.Service.RegisterEmailConfirm + form.MailNotify = setting.Service.EnableNotifyMail + + // Server and other services settings + form.OfflineMode = setting.OfflineMode + form.DisableGravatar = setting.DisableGravatar + form.EnableFederatedAvatar = setting.EnableFederatedAvatar + form.EnableOpenIDSignIn = setting.Service.EnableOpenIDSignIn + form.EnableOpenIDSignUp = setting.Service.EnableOpenIDSignUp + form.DisableRegistration = setting.Service.DisableRegistration + form.AllowOnlyExternalRegistration = setting.Service.AllowOnlyExternalRegistration + form.EnableCaptcha = setting.Service.EnableCaptcha + form.RequireSignInView = setting.Service.RequireSignInView + form.DefaultKeepEmailPrivate = setting.Service.DefaultKeepEmailPrivate + form.DefaultAllowCreateOrganization = setting.Service.DefaultAllowCreateOrganization + form.DefaultEnableTimetracking = setting.Service.DefaultEnableTimetracking + form.NoReplyAddress = setting.Service.NoReplyAddress + form.PasswordAlgorithm = hash.DefaultHasher.DefaultAlgorithm + + middleware.AssignForm(form, ctx.Data) + ctx.HTML(http.StatusOK, tplInstall) +} + +// SubmitInstall response for submit install items +func SubmitInstall(ctx *context.Context) { + form := *web.GetForm(ctx).(*forms.InstallForm) + var err error + ctx.Data["CurDbOption"] = form.DbType + + if ctx.HasError() { + if ctx.HasValue("Err_SMTPUser") { + ctx.Data["Err_SMTP"] = true + } + if ctx.HasValue("Err_AdminName") || + ctx.HasValue("Err_AdminPasswd") || + ctx.HasValue("Err_AdminEmail") { + ctx.Data["Err_Admin"] = true + } + + ctx.HTML(http.StatusOK, tplInstall) + return + } + + if _, err = exec.LookPath("git"); err != nil { + ctx.RenderWithErr(ctx.Tr("install.test_git_failed", err), tplInstall, &form) + return + } + + // Pass basic check, now test configuration. + // Test database setting. + + setting.Database.Type = setting.GetDBTypeByName(form.DbType) + setting.Database.Host = form.DbHost + setting.Database.User = form.DbUser + setting.Database.Passwd = form.DbPasswd + setting.Database.Name = form.DbName + setting.Database.Schema = form.DbSchema + setting.Database.SSLMode = form.SSLMode + setting.Database.Charset = form.Charset + setting.Database.Path = form.DbPath + + hash.DefaultHasher.DefaultAlgorithm = form.PasswordAlgorithm + + if (setting.Database.Type == "sqlite3") && + len(setting.Database.Path) == 0 { + ctx.Data["Err_DbPath"] = true + ctx.RenderWithErr(ctx.Tr("install.err_empty_db_path"), tplInstall, &form) + return + } + + // Set test engine. + if err = models.NewTestEngine(); err != nil { + if strings.Contains(err.Error(), `Unknown database type: sqlite3`) { + ctx.Data["Err_DbType"] = true + ctx.RenderWithErr(ctx.Tr("install.sqlite3_not_available", "https://docs.gitea.io/en-us/install-from-binary/"), tplInstall, &form) + } else { + ctx.Data["Err_DbSetting"] = true + ctx.RenderWithErr(ctx.Tr("install.invalid_db_setting", err), tplInstall, &form) + } + return + } + + // Test repository root path. + form.RepoRootPath = strings.ReplaceAll(form.RepoRootPath, "\\", "/") + if err = os.MkdirAll(form.RepoRootPath, os.ModePerm); err != nil { + ctx.Data["Err_RepoRootPath"] = true + ctx.RenderWithErr(ctx.Tr("install.invalid_repo_path", err), tplInstall, &form) + return + } + + // Test LFS root path if not empty, empty meaning disable LFS + if form.LFSRootPath != "" { + form.LFSRootPath = strings.ReplaceAll(form.LFSRootPath, "\\", "/") + if err := os.MkdirAll(form.LFSRootPath, os.ModePerm); err != nil { + ctx.Data["Err_LFSRootPath"] = true + ctx.RenderWithErr(ctx.Tr("install.invalid_lfs_path", err), tplInstall, &form) + return + } + } + + // Test log root path. + form.LogRootPath = strings.ReplaceAll(form.LogRootPath, "\\", "/") + if err = os.MkdirAll(form.LogRootPath, os.ModePerm); err != nil { + ctx.Data["Err_LogRootPath"] = true + ctx.RenderWithErr(ctx.Tr("install.invalid_log_root_path", err), tplInstall, &form) + return + } + + currentUser, match := setting.IsRunUserMatchCurrentUser(form.RunUser) + if !match { + ctx.Data["Err_RunUser"] = true + ctx.RenderWithErr(ctx.Tr("install.run_user_not_match", form.RunUser, currentUser), tplInstall, &form) + return + } + + // Check logic loophole between disable self-registration and no admin account. + if form.DisableRegistration && len(form.AdminName) == 0 { + ctx.Data["Err_Services"] = true + ctx.Data["Err_Admin"] = true + ctx.RenderWithErr(ctx.Tr("install.no_admin_and_disable_registration"), tplInstall, form) + return + } + + // Check admin user creation + if len(form.AdminName) > 0 { + // Ensure AdminName is valid + if err := models.IsUsableUsername(form.AdminName); err != nil { + ctx.Data["Err_Admin"] = true + ctx.Data["Err_AdminName"] = true + if models.IsErrNameReserved(err) { + ctx.RenderWithErr(ctx.Tr("install.err_admin_name_is_reserved"), tplInstall, form) + return + } else if models.IsErrNamePatternNotAllowed(err) { + ctx.RenderWithErr(ctx.Tr("install.err_admin_name_pattern_not_allowed"), tplInstall, form) + return + } + ctx.RenderWithErr(ctx.Tr("install.err_admin_name_is_invalid"), tplInstall, form) + return + } + // Check Admin email + if len(form.AdminEmail) == 0 { + ctx.Data["Err_Admin"] = true + ctx.Data["Err_AdminEmail"] = true + ctx.RenderWithErr(ctx.Tr("install.err_empty_admin_email"), tplInstall, form) + return + } + // Check admin password. + if len(form.AdminPasswd) == 0 { + ctx.Data["Err_Admin"] = true + ctx.Data["Err_AdminPasswd"] = true + ctx.RenderWithErr(ctx.Tr("install.err_empty_admin_password"), tplInstall, form) + return + } + if form.AdminPasswd != form.AdminConfirmPasswd { + ctx.Data["Err_Admin"] = true + ctx.Data["Err_AdminPasswd"] = true + ctx.RenderWithErr(ctx.Tr("form.password_not_match"), tplInstall, form) + return + } + } + + if form.AppURL[len(form.AppURL)-1] != '/' { + form.AppURL += "/" + } + + // Save settings. + cfg := ini.Empty() + isFile, err := util.IsFile(setting.CustomConf) + if err != nil { + log.Error("Unable to check if %s is a file. Error: %v", setting.CustomConf, err) + } + if isFile { + // Keeps custom settings if there is already something. + if err = cfg.Append(setting.CustomConf); err != nil { + log.Error("Failed to load custom conf '%s': %v", setting.CustomConf, err) + } + } + cfg.Section("database").Key("DB_TYPE").SetValue(setting.Database.Type) + cfg.Section("database").Key("HOST").SetValue(setting.Database.Host) + cfg.Section("database").Key("NAME").SetValue(setting.Database.Name) + cfg.Section("database").Key("USER").SetValue(setting.Database.User) + cfg.Section("database").Key("PASSWD").SetValue(setting.Database.Passwd) + cfg.Section("database").Key("SCHEMA").SetValue(setting.Database.Schema) + cfg.Section("database").Key("SSL_MODE").SetValue(setting.Database.SSLMode) + cfg.Section("database").Key("CHARSET").SetValue(setting.Database.Charset) + cfg.Section("database").Key("PATH").SetValue(setting.Database.Path) + cfg.Section("database").Key("LOG_SQL").SetValue("false") // LOG_SQL is rarely helpful + + cfg.Section("").Key("APP_NAME").SetValue(form.AppName) + cfg.Section("repository").Key("ROOT").SetValue(form.RepoRootPath) + cfg.Section("").Key("RUN_USER").SetValue(form.RunUser) + cfg.Section("server").Key("SSH_DOMAIN").SetValue(form.Domain) + cfg.Section("server").Key("DOMAIN").SetValue(form.Domain) + cfg.Section("server").Key("HTTP_PORT").SetValue(form.HTTPPort) + cfg.Section("server").Key("ROOT_URL").SetValue(form.AppURL) + + if form.SSHPort == 0 { + cfg.Section("server").Key("DISABLE_SSH").SetValue("true") + } else { + cfg.Section("server").Key("DISABLE_SSH").SetValue("false") + cfg.Section("server").Key("SSH_PORT").SetValue(fmt.Sprint(form.SSHPort)) + } + + if form.LFSRootPath != "" { + cfg.Section("server").Key("LFS_START_SERVER").SetValue("true") + cfg.Section("server").Key("LFS_CONTENT_PATH").SetValue(form.LFSRootPath) + var secretKey string + if secretKey, err = generate.NewJwtSecretBase64(); err != nil { + ctx.RenderWithErr(ctx.Tr("install.lfs_jwt_secret_failed", err), tplInstall, &form) + return + } + cfg.Section("server").Key("LFS_JWT_SECRET").SetValue(secretKey) + } else { + cfg.Section("server").Key("LFS_START_SERVER").SetValue("false") + } + + if len(strings.TrimSpace(form.SMTPHost)) > 0 { + cfg.Section("mailer").Key("ENABLED").SetValue("true") + cfg.Section("mailer").Key("HOST").SetValue(form.SMTPHost) + cfg.Section("mailer").Key("FROM").SetValue(form.SMTPFrom) + cfg.Section("mailer").Key("USER").SetValue(form.SMTPUser) + cfg.Section("mailer").Key("PASSWD").SetValue(form.SMTPPasswd) + } else { + cfg.Section("mailer").Key("ENABLED").SetValue("false") + } + cfg.Section("service").Key("REGISTER_EMAIL_CONFIRM").SetValue(fmt.Sprint(form.RegisterConfirm)) + cfg.Section("service").Key("ENABLE_NOTIFY_MAIL").SetValue(fmt.Sprint(form.MailNotify)) + + cfg.Section("server").Key("OFFLINE_MODE").SetValue(fmt.Sprint(form.OfflineMode)) + cfg.Section("picture").Key("DISABLE_GRAVATAR").SetValue(fmt.Sprint(form.DisableGravatar)) + cfg.Section("picture").Key("ENABLE_FEDERATED_AVATAR").SetValue(fmt.Sprint(form.EnableFederatedAvatar)) + cfg.Section("openid").Key("ENABLE_OPENID_SIGNIN").SetValue(fmt.Sprint(form.EnableOpenIDSignIn)) + cfg.Section("openid").Key("ENABLE_OPENID_SIGNUP").SetValue(fmt.Sprint(form.EnableOpenIDSignUp)) + cfg.Section("service").Key("DISABLE_REGISTRATION").SetValue(fmt.Sprint(form.DisableRegistration)) + cfg.Section("service").Key("ALLOW_ONLY_EXTERNAL_REGISTRATION").SetValue(fmt.Sprint(form.AllowOnlyExternalRegistration)) + cfg.Section("service").Key("ENABLE_CAPTCHA").SetValue(fmt.Sprint(form.EnableCaptcha)) + cfg.Section("service").Key("REQUIRE_SIGNIN_VIEW").SetValue(fmt.Sprint(form.RequireSignInView)) + cfg.Section("service").Key("DEFAULT_KEEP_EMAIL_PRIVATE").SetValue(fmt.Sprint(form.DefaultKeepEmailPrivate)) + cfg.Section("service").Key("DEFAULT_ALLOW_CREATE_ORGANIZATION").SetValue(fmt.Sprint(form.DefaultAllowCreateOrganization)) + cfg.Section("service").Key("DEFAULT_ENABLE_TIMETRACKING").SetValue(fmt.Sprint(form.DefaultEnableTimetracking)) + cfg.Section("service").Key("NO_REPLY_ADDRESS").SetValue(fmt.Sprint(form.NoReplyAddress)) + + cfg.Section("").Key("RUN_MODE").SetValue("prod") + + cfg.Section("session").Key("PROVIDER").SetValue("file") + + cfg.Section("log").Key("MODE").SetValue("console") + cfg.Section("log").Key("LEVEL").SetValue(setting.LogLevel.String()) + cfg.Section("log").Key("ROOT_PATH").SetValue(form.LogRootPath) + cfg.Section("log").Key("ROUTER").SetValue("console") + + cfg.Section("security").Key("INSTALL_LOCK").SetValue("true") + var secretKey string + if secretKey, err = generate.NewSecretKey(); err != nil { + ctx.RenderWithErr(ctx.Tr("install.secret_key_failed", err), tplInstall, &form) + return + } + cfg.Section("security").Key("SECRET_KEY").SetValue(secretKey) + if len(form.PasswordAlgorithm) > 0 { + cfg.Section("security").Key("PASSWORD_HASH_ALGO").SetValue(form.PasswordAlgorithm) + } + + err = os.MkdirAll(filepath.Dir(setting.CustomConf), os.ModePerm) + if err != nil { + ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form) + return + } + + if err = cfg.SaveTo(setting.CustomConf); err != nil { + ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form) + return + } + + // Re-read settings + ReloadSettings(ctx) + + // Create admin account + if len(form.AdminName) > 0 { + u := &models.User{ + Name: form.AdminName, + Email: form.AdminEmail, + Passwd: form.AdminPasswd, + IsAdmin: true, + IsActive: true, + } + if err = models.CreateUser(u); err != nil { + if !models.IsErrUserAlreadyExist(err) { + setting.InstallLock = false + ctx.Data["Err_AdminName"] = true + ctx.Data["Err_AdminEmail"] = true + ctx.RenderWithErr(ctx.Tr("install.invalid_admin_setting", err), tplInstall, &form) + return + } + log.Info("Admin account already exist") + u, _ = models.GetUserByName(u.Name) + } + + days := 86400 * setting.LogInRememberDays + ctx.SetCookie(setting.CookieUserName, u.Name, days) + + ctx.SetSuperSecureCookie(base.EncodeMD5(u.Rands+u.Passwd), + setting.CookieRememberName, u.Name, days) + + // Auto-login for admin + if err = ctx.Session.Set("uid", u.ID); err != nil { + ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form) + return + } + if err = ctx.Session.Set("uname", u.Name); err != nil { + ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form) + return + } + + if err = ctx.Session.Release(); err != nil { + ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form) + return + } + } + + log.Info("First-time run install finished!") + + ctx.Flash.Success(ctx.Tr("install.install_success")) + + ctx.Header().Add("Refresh", "1; url="+setting.AppURL+"user/login") + ctx.HTML(http.StatusOK, tplPostInstall) + + // Now get the http.Server from this request and shut it down + // NB: This is not our hammerable graceful shutdown this is http.Server.Shutdown + srv := ctx.Value(http.ServerContextKey).(*http.Server) + go func() { + if err := srv.Shutdown(graceful.GetManager().HammerContext()); err != nil { + log.Error("Unable to shutdown the install server! Error: %v", err) + } + }() +} diff --git a/routers/web/admin/admin.go b/routers/web/admin/admin.go new file mode 100644 index 0000000000000..92f658432246c --- /dev/null +++ b/routers/web/admin/admin.go @@ -0,0 +1,488 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package admin + +import ( + "fmt" + "net/http" + "net/url" + "os" + "runtime" + "strconv" + "strings" + "time" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/auth/hash" + "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/cron" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/process" + "code.gitea.io/gitea/modules/queue" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/timeutil" + "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/services/forms" + "code.gitea.io/gitea/services/mailer" + jsoniter "github.com/json-iterator/go" + + "gitea.com/go-chi/session" +) + +const ( + tplDashboard base.TplName = "admin/dashboard" + tplConfig base.TplName = "admin/config" + tplMonitor base.TplName = "admin/monitor" + tplQueue base.TplName = "admin/queue" +) + +var sysStatus struct { + Uptime string + NumGoroutine int + + // General statistics. + MemAllocated string // bytes allocated and still in use + MemTotal string // bytes allocated (even if freed) + MemSys string // bytes obtained from system (sum of XxxSys below) + Lookups uint64 // number of pointer lookups + MemMallocs uint64 // number of mallocs + MemFrees uint64 // number of frees + + // Main allocation heap statistics. + HeapAlloc string // bytes allocated and still in use + HeapSys string // bytes obtained from system + HeapIdle string // bytes in idle spans + HeapInuse string // bytes in non-idle span + HeapReleased string // bytes released to the OS + HeapObjects uint64 // total number of allocated objects + + // Low-level fixed-size structure allocator statistics. + // Inuse is bytes used now. + // Sys is bytes obtained from system. + StackInuse string // bootstrap stacks + StackSys string + MSpanInuse string // mspan structures + MSpanSys string + MCacheInuse string // mcache structures + MCacheSys string + BuckHashSys string // profiling bucket hash table + GCSys string // GC metadata + OtherSys string // other system allocations + + // Garbage collector statistics. + NextGC string // next run in HeapAlloc time (bytes) + LastGC string // last run in absolute time (ns) + PauseTotalNs string + PauseNs string // circular buffer of recent GC pause times, most recent at [(NumGC+255)%256] + NumGC uint32 +} + +func updateSystemStatus() { + sysStatus.Uptime = timeutil.TimeSincePro(setting.AppStartTime, "en") + + m := new(runtime.MemStats) + runtime.ReadMemStats(m) + sysStatus.NumGoroutine = runtime.NumGoroutine() + + sysStatus.MemAllocated = base.FileSize(int64(m.Alloc)) + sysStatus.MemTotal = base.FileSize(int64(m.TotalAlloc)) + sysStatus.MemSys = base.FileSize(int64(m.Sys)) + sysStatus.Lookups = m.Lookups + sysStatus.MemMallocs = m.Mallocs + sysStatus.MemFrees = m.Frees + + sysStatus.HeapAlloc = base.FileSize(int64(m.HeapAlloc)) + sysStatus.HeapSys = base.FileSize(int64(m.HeapSys)) + sysStatus.HeapIdle = base.FileSize(int64(m.HeapIdle)) + sysStatus.HeapInuse = base.FileSize(int64(m.HeapInuse)) + sysStatus.HeapReleased = base.FileSize(int64(m.HeapReleased)) + sysStatus.HeapObjects = m.HeapObjects + + sysStatus.StackInuse = base.FileSize(int64(m.StackInuse)) + sysStatus.StackSys = base.FileSize(int64(m.StackSys)) + sysStatus.MSpanInuse = base.FileSize(int64(m.MSpanInuse)) + sysStatus.MSpanSys = base.FileSize(int64(m.MSpanSys)) + sysStatus.MCacheInuse = base.FileSize(int64(m.MCacheInuse)) + sysStatus.MCacheSys = base.FileSize(int64(m.MCacheSys)) + sysStatus.BuckHashSys = base.FileSize(int64(m.BuckHashSys)) + sysStatus.GCSys = base.FileSize(int64(m.GCSys)) + sysStatus.OtherSys = base.FileSize(int64(m.OtherSys)) + + sysStatus.NextGC = base.FileSize(int64(m.NextGC)) + sysStatus.LastGC = fmt.Sprintf("%.1fs", float64(time.Now().UnixNano()-int64(m.LastGC))/1000/1000/1000) + sysStatus.PauseTotalNs = fmt.Sprintf("%.1fs", float64(m.PauseTotalNs)/1000/1000/1000) + sysStatus.PauseNs = fmt.Sprintf("%.3fs", float64(m.PauseNs[(m.NumGC+255)%256])/1000/1000/1000) + sysStatus.NumGC = m.NumGC +} + +// Dashboard show admin panel dashboard +func Dashboard(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("admin.dashboard") + ctx.Data["PageIsAdmin"] = true + ctx.Data["PageIsAdminDashboard"] = true + ctx.Data["Stats"] = models.GetStatistic() + // FIXME: update periodically + updateSystemStatus() + ctx.Data["SysStatus"] = sysStatus + ctx.Data["SSH"] = setting.SSH + ctx.HTML(http.StatusOK, tplDashboard) +} + +// DashboardPost run an admin operation +func DashboardPost(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.AdminDashboardForm) + ctx.Data["Title"] = ctx.Tr("admin.dashboard") + ctx.Data["PageIsAdmin"] = true + ctx.Data["PageIsAdminDashboard"] = true + ctx.Data["Stats"] = models.GetStatistic() + updateSystemStatus() + ctx.Data["SysStatus"] = sysStatus + + // Run operation. + if form.Op != "" { + task := cron.GetTask(form.Op) + if task != nil { + go task.RunWithUser(ctx.User, nil) + ctx.Flash.Success(ctx.Tr("admin.dashboard.task.started", ctx.Tr("admin.dashboard."+form.Op))) + } else { + ctx.Flash.Error(ctx.Tr("admin.dashboard.task.unknown", form.Op)) + } + } + if form.From == "monitor" { + ctx.Redirect(setting.AppSubURL + "/admin/monitor") + } else { + ctx.Redirect(setting.AppSubURL + "/admin") + } +} + +// SendTestMail send test mail to confirm mail service is OK +func SendTestMail(ctx *context.Context) { + email := ctx.Query("email") + // Send a test email to the user's email address and redirect back to Config + if err := mailer.SendTestMail(email); err != nil { + ctx.Flash.Error(ctx.Tr("admin.config.test_mail_failed", email, err)) + } else { + ctx.Flash.Info(ctx.Tr("admin.config.test_mail_sent", email)) + } + + ctx.Redirect(setting.AppSubURL + "/admin/config") +} + +func shadowPasswordKV(cfgItem, splitter string) string { + fields := strings.Split(cfgItem, splitter) + for i := 0; i < len(fields); i++ { + if strings.HasPrefix(fields[i], "password=") { + fields[i] = "password=******" + break + } + } + return strings.Join(fields, splitter) +} + +func shadowURL(provider, cfgItem string) string { + u, err := url.Parse(cfgItem) + if err != nil { + log.Error("Shadowing Password for %v failed: %v", provider, err) + return cfgItem + } + if u.User != nil { + atIdx := strings.Index(cfgItem, "@") + if atIdx > 0 { + colonIdx := strings.LastIndex(cfgItem[:atIdx], ":") + if colonIdx > 0 { + return cfgItem[:colonIdx+1] + "******" + cfgItem[atIdx:] + } + } + } + return cfgItem +} + +func shadowPassword(provider, cfgItem string) string { + switch provider { + case "redis": + return shadowPasswordKV(cfgItem, ",") + case "mysql": + //root:@tcp(localhost:3306)/macaron?charset=utf8 + atIdx := strings.Index(cfgItem, "@") + if atIdx > 0 { + colonIdx := strings.Index(cfgItem[:atIdx], ":") + if colonIdx > 0 { + return cfgItem[:colonIdx+1] + "******" + cfgItem[atIdx:] + } + } + return cfgItem + case "postgres": + // user=jiahuachen dbname=macaron port=5432 sslmode=disable + if !strings.HasPrefix(cfgItem, "postgres://") { + return shadowPasswordKV(cfgItem, " ") + } + fallthrough + case "couchbase": + return shadowURL(provider, cfgItem) + // postgres://pqgotest:password@localhost/pqgotest?sslmode=verify-full + // Notice: use shadowURL + } + return cfgItem +} + +// Config show admin config page +func Config(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("admin.config") + ctx.Data["PageIsAdmin"] = true + ctx.Data["PageIsAdminConfig"] = true + + ctx.Data["CustomConf"] = setting.CustomConf + ctx.Data["AppUrl"] = setting.AppURL + ctx.Data["Domain"] = setting.Domain + ctx.Data["OfflineMode"] = setting.OfflineMode + ctx.Data["DisableRouterLog"] = setting.DisableRouterLog + ctx.Data["RunUser"] = setting.RunUser + ctx.Data["RunMode"] = strings.Title(setting.RunMode) + if version, err := git.LocalVersion(); err == nil { + ctx.Data["GitVersion"] = version.Original() + } + ctx.Data["RepoRootPath"] = setting.RepoRootPath + ctx.Data["CustomRootPath"] = setting.CustomPath + ctx.Data["StaticRootPath"] = setting.StaticRootPath + ctx.Data["LogRootPath"] = setting.LogRootPath + ctx.Data["ScriptType"] = setting.ScriptType + ctx.Data["ReverseProxyAuthUser"] = setting.ReverseProxyAuthUser + ctx.Data["ReverseProxyAuthEmail"] = setting.ReverseProxyAuthEmail + + ctx.Data["SSH"] = setting.SSH + ctx.Data["LFS"] = setting.LFS + + ctx.Data["Service"] = setting.Service + ctx.Data["DbCfg"] = setting.Database + ctx.Data["Webhook"] = setting.Webhook + + ctx.Data["MailerEnabled"] = false + if setting.MailService != nil { + ctx.Data["MailerEnabled"] = true + ctx.Data["Mailer"] = setting.MailService + } + + ctx.Data["CacheAdapter"] = setting.CacheService.Adapter + ctx.Data["CacheInterval"] = setting.CacheService.Interval + + ctx.Data["CacheConn"] = shadowPassword(setting.CacheService.Adapter, setting.CacheService.Conn) + ctx.Data["CacheItemTTL"] = setting.CacheService.TTL + + sessionCfg := setting.SessionConfig + if sessionCfg.Provider == "VirtualSession" { + var realSession session.Options + json := jsoniter.ConfigCompatibleWithStandardLibrary + if err := json.Unmarshal([]byte(sessionCfg.ProviderConfig), &realSession); err != nil { + log.Error("Unable to unmarshall session config for virtualed provider config: %s\nError: %v", sessionCfg.ProviderConfig, err) + } + sessionCfg.Provider = realSession.Provider + sessionCfg.ProviderConfig = realSession.ProviderConfig + sessionCfg.CookieName = realSession.CookieName + sessionCfg.CookiePath = realSession.CookiePath + sessionCfg.Gclifetime = realSession.Gclifetime + sessionCfg.Maxlifetime = realSession.Maxlifetime + sessionCfg.Secure = realSession.Secure + sessionCfg.Domain = realSession.Domain + } + sessionCfg.ProviderConfig = shadowPassword(sessionCfg.Provider, sessionCfg.ProviderConfig) + ctx.Data["SessionConfig"] = sessionCfg + + ctx.Data["DisableGravatar"] = setting.DisableGravatar + ctx.Data["EnableFederatedAvatar"] = setting.EnableFederatedAvatar + + ctx.Data["Git"] = setting.Git + + type envVar struct { + Name, Value string + } + + envVars := map[string]*envVar{} + if len(os.Getenv("GITEA_WORK_DIR")) > 0 { + envVars["GITEA_WORK_DIR"] = &envVar{"GITEA_WORK_DIR", os.Getenv("GITEA_WORK_DIR")} + } + if len(os.Getenv("GITEA_CUSTOM")) > 0 { + envVars["GITEA_CUSTOM"] = &envVar{"GITEA_CUSTOM", os.Getenv("GITEA_CUSTOM")} + } + + ctx.Data["EnvVars"] = envVars + ctx.Data["Loggers"] = setting.GetLogDescriptions() + ctx.Data["EnableAccessLog"] = setting.EnableAccessLog + ctx.Data["AccessLogTemplate"] = setting.AccessLogTemplate + ctx.Data["DisableRouterLog"] = setting.DisableRouterLog + ctx.Data["EnableXORMLog"] = setting.EnableXORMLog + ctx.Data["LogSQL"] = setting.Database.LogSQL + ctx.Data["SelectedHasherParams"] = hash.DefaultHasher.Hashers[hash.DefaultHasher.DefaultAlgorithm] + ctx.Data["SelectedHasher"] = hash.DefaultHasher.DefaultAlgorithm + + ctx.HTML(http.StatusOK, tplConfig) +} + +// Monitor show admin monitor page +func Monitor(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("admin.monitor") + ctx.Data["PageIsAdmin"] = true + ctx.Data["PageIsAdminMonitor"] = true + ctx.Data["Processes"] = process.GetManager().Processes() + ctx.Data["Entries"] = cron.ListTasks() + ctx.Data["Queues"] = queue.GetManager().ManagedQueues() + ctx.HTML(http.StatusOK, tplMonitor) +} + +// MonitorCancel cancels a process +func MonitorCancel(ctx *context.Context) { + pid := ctx.ParamsInt64("pid") + process.GetManager().Cancel(pid) + ctx.JSON(http.StatusOK, map[string]interface{}{ + "redirect": setting.AppSubURL + "/admin/monitor", + }) +} + +// Queue shows details for a specific queue +func Queue(ctx *context.Context) { + qid := ctx.ParamsInt64("qid") + mq := queue.GetManager().GetManagedQueue(qid) + if mq == nil { + ctx.Status(404) + return + } + ctx.Data["Title"] = ctx.Tr("admin.monitor.queue", mq.Name) + ctx.Data["PageIsAdmin"] = true + ctx.Data["PageIsAdminMonitor"] = true + ctx.Data["Queue"] = mq + ctx.HTML(http.StatusOK, tplQueue) +} + +// WorkerCancel cancels a worker group +func WorkerCancel(ctx *context.Context) { + qid := ctx.ParamsInt64("qid") + mq := queue.GetManager().GetManagedQueue(qid) + if mq == nil { + ctx.Status(404) + return + } + pid := ctx.ParamsInt64("pid") + mq.CancelWorkers(pid) + ctx.Flash.Info(ctx.Tr("admin.monitor.queue.pool.cancelling")) + ctx.JSON(http.StatusOK, map[string]interface{}{ + "redirect": setting.AppSubURL + "/admin/monitor/queue/" + strconv.FormatInt(qid, 10), + }) +} + +// Flush flushes a queue +func Flush(ctx *context.Context) { + qid := ctx.ParamsInt64("qid") + mq := queue.GetManager().GetManagedQueue(qid) + if mq == nil { + ctx.Status(404) + return + } + timeout, err := time.ParseDuration(ctx.Query("timeout")) + if err != nil { + timeout = -1 + } + ctx.Flash.Info(ctx.Tr("admin.monitor.queue.pool.flush.added", mq.Name)) + go func() { + err := mq.Flush(timeout) + if err != nil { + log.Error("Flushing failure for %s: Error %v", mq.Name, err) + } + }() + ctx.Redirect(setting.AppSubURL + "/admin/monitor/queue/" + strconv.FormatInt(qid, 10)) +} + +// AddWorkers adds workers to a worker group +func AddWorkers(ctx *context.Context) { + qid := ctx.ParamsInt64("qid") + mq := queue.GetManager().GetManagedQueue(qid) + if mq == nil { + ctx.Status(404) + return + } + number := ctx.QueryInt("number") + if number < 1 { + ctx.Flash.Error(ctx.Tr("admin.monitor.queue.pool.addworkers.mustnumbergreaterzero")) + ctx.Redirect(setting.AppSubURL + "/admin/monitor/queue/" + strconv.FormatInt(qid, 10)) + return + } + timeout, err := time.ParseDuration(ctx.Query("timeout")) + if err != nil { + ctx.Flash.Error(ctx.Tr("admin.monitor.queue.pool.addworkers.musttimeoutduration")) + ctx.Redirect(setting.AppSubURL + "/admin/monitor/queue/" + strconv.FormatInt(qid, 10)) + return + } + if _, ok := mq.Managed.(queue.ManagedPool); !ok { + ctx.Flash.Error(ctx.Tr("admin.monitor.queue.pool.none")) + ctx.Redirect(setting.AppSubURL + "/admin/monitor/queue/" + strconv.FormatInt(qid, 10)) + return + } + mq.AddWorkers(number, timeout) + ctx.Flash.Success(ctx.Tr("admin.monitor.queue.pool.added")) + ctx.Redirect(setting.AppSubURL + "/admin/monitor/queue/" + strconv.FormatInt(qid, 10)) +} + +// SetQueueSettings sets the maximum number of workers and other settings for this queue +func SetQueueSettings(ctx *context.Context) { + qid := ctx.ParamsInt64("qid") + mq := queue.GetManager().GetManagedQueue(qid) + if mq == nil { + ctx.Status(404) + return + } + if _, ok := mq.Managed.(queue.ManagedPool); !ok { + ctx.Flash.Error(ctx.Tr("admin.monitor.queue.pool.none")) + ctx.Redirect(setting.AppSubURL + "/admin/monitor/queue/" + strconv.FormatInt(qid, 10)) + return + } + + maxNumberStr := ctx.Query("max-number") + numberStr := ctx.Query("number") + timeoutStr := ctx.Query("timeout") + + var err error + var maxNumber, number int + var timeout time.Duration + if len(maxNumberStr) > 0 { + maxNumber, err = strconv.Atoi(maxNumberStr) + if err != nil { + ctx.Flash.Error(ctx.Tr("admin.monitor.queue.settings.maxnumberworkers.error")) + ctx.Redirect(setting.AppSubURL + "/admin/monitor/queue/" + strconv.FormatInt(qid, 10)) + return + } + if maxNumber < -1 { + maxNumber = -1 + } + } else { + maxNumber = mq.MaxNumberOfWorkers() + } + + if len(numberStr) > 0 { + number, err = strconv.Atoi(numberStr) + if err != nil || number < 0 { + ctx.Flash.Error(ctx.Tr("admin.monitor.queue.settings.numberworkers.error")) + ctx.Redirect(setting.AppSubURL + "/admin/monitor/queue/" + strconv.FormatInt(qid, 10)) + return + } + } else { + number = mq.BoostWorkers() + } + + if len(timeoutStr) > 0 { + timeout, err = time.ParseDuration(timeoutStr) + if err != nil { + ctx.Flash.Error(ctx.Tr("admin.monitor.queue.settings.timeout.error")) + ctx.Redirect(setting.AppSubURL + "/admin/monitor/queue/" + strconv.FormatInt(qid, 10)) + return + } + } else { + timeout = mq.BoostTimeout() + } + + mq.SetPoolSettings(maxNumber, number, timeout) + ctx.Flash.Success(ctx.Tr("admin.monitor.queue.settings.changed")) + ctx.Redirect(setting.AppSubURL + "/admin/monitor/queue/" + strconv.FormatInt(qid, 10)) +} diff --git a/routers/web/user/setting/account.go b/routers/web/user/setting/account.go new file mode 100644 index 0000000000000..281ecc9e5e3cc --- /dev/null +++ b/routers/web/user/setting/account.go @@ -0,0 +1,314 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Copyright 2018 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package setting + +import ( + "errors" + "net/http" + "time" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/password" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/timeutil" + "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/services/forms" + "code.gitea.io/gitea/services/mailer" +) + +const ( + tplSettingsAccount base.TplName = "user/settings/account" +) + +// Account renders change user's password, user's email and user suicide page +func Account(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("settings") + ctx.Data["PageIsSettingsAccount"] = true + ctx.Data["Email"] = ctx.User.Email + + loadAccountData(ctx) + + ctx.HTML(http.StatusOK, tplSettingsAccount) +} + +// AccountPost response for change user's password +func AccountPost(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.ChangePasswordForm) + ctx.Data["Title"] = ctx.Tr("settings") + ctx.Data["PageIsSettingsAccount"] = true + + if ctx.HasError() { + loadAccountData(ctx) + + ctx.HTML(http.StatusOK, tplSettingsAccount) + return + } + + if len(form.Password) < setting.MinPasswordLength { + ctx.Flash.Error(ctx.Tr("auth.password_too_short", setting.MinPasswordLength)) + } else if ctx.User.IsPasswordSet() && !ctx.User.ValidatePassword(form.OldPassword) { + ctx.Flash.Error(ctx.Tr("settings.password_incorrect")) + } else if form.Password != form.Retype { + ctx.Flash.Error(ctx.Tr("form.password_not_match")) + } else if !password.IsComplexEnough(form.Password) { + ctx.Flash.Error(password.BuildComplexityError(ctx)) + } else if pwned, err := password.IsPwned(ctx, form.Password); pwned || err != nil { + errMsg := ctx.Tr("auth.password_pwned") + if err != nil { + log.Error(err.Error()) + errMsg = ctx.Tr("auth.password_pwned_err") + } + ctx.Flash.Error(errMsg) + } else { + var err error + if err = ctx.User.SetPassword(form.Password); err != nil { + ctx.ServerError("UpdateUser", err) + return + } + if err := models.UpdateUserCols(ctx.User, "salt", "passwd", "passwd_hash_algo"); err != nil { + ctx.ServerError("UpdateUser", err) + return + } + + log.Trace("User password updated: %s", ctx.User.Name) + ctx.Flash.Success(ctx.Tr("settings.change_password_success")) + } + + ctx.Redirect(setting.AppSubURL + "/user/settings/account") +} + +// EmailPost response for change user's email +func EmailPost(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.AddEmailForm) + ctx.Data["Title"] = ctx.Tr("settings") + ctx.Data["PageIsSettingsAccount"] = true + + // Make emailaddress primary. + if ctx.Query("_method") == "PRIMARY" { + if err := models.MakeEmailPrimary(&models.EmailAddress{ID: ctx.QueryInt64("id")}); err != nil { + ctx.ServerError("MakeEmailPrimary", err) + return + } + + log.Trace("Email made primary: %s", ctx.User.Name) + ctx.Redirect(setting.AppSubURL + "/user/settings/account") + return + } + // Send activation Email + if ctx.Query("_method") == "SENDACTIVATION" { + var address string + if ctx.Cache.IsExist("MailResendLimit_" + ctx.User.LowerName) { + log.Error("Send activation: activation still pending") + ctx.Redirect(setting.AppSubURL + "/user/settings/account") + return + } + if ctx.Query("id") == "PRIMARY" { + if ctx.User.IsActive { + log.Error("Send activation: email not set for activation") + ctx.Redirect(setting.AppSubURL + "/user/settings/account") + return + } + mailer.SendActivateAccountMail(ctx.Locale, ctx.User) + address = ctx.User.Email + } else { + id := ctx.QueryInt64("id") + email, err := models.GetEmailAddressByID(ctx.User.ID, id) + if err != nil { + log.Error("GetEmailAddressByID(%d,%d) error: %v", ctx.User.ID, id, err) + ctx.Redirect(setting.AppSubURL + "/user/settings/account") + return + } + if email == nil { + log.Error("Send activation: EmailAddress not found; user:%d, id: %d", ctx.User.ID, id) + ctx.Redirect(setting.AppSubURL + "/user/settings/account") + return + } + if email.IsActivated { + log.Error("Send activation: email not set for activation") + ctx.Redirect(setting.AppSubURL + "/user/settings/account") + return + } + mailer.SendActivateEmailMail(ctx.User, email) + address = email.Email + } + + if err := ctx.Cache.Put("MailResendLimit_"+ctx.User.LowerName, ctx.User.LowerName, 180); err != nil { + log.Error("Set cache(MailResendLimit) fail: %v", err) + } + ctx.Flash.Info(ctx.Tr("settings.add_email_confirmation_sent", address, timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale.Language()))) + ctx.Redirect(setting.AppSubURL + "/user/settings/account") + return + } + // Set Email Notification Preference + if ctx.Query("_method") == "NOTIFICATION" { + preference := ctx.Query("preference") + if !(preference == models.EmailNotificationsEnabled || + preference == models.EmailNotificationsOnMention || + preference == models.EmailNotificationsDisabled) { + log.Error("Email notifications preference change returned unrecognized option %s: %s", preference, ctx.User.Name) + ctx.ServerError("SetEmailPreference", errors.New("option unrecognized")) + return + } + if err := ctx.User.SetEmailNotifications(preference); err != nil { + log.Error("Set Email Notifications failed: %v", err) + ctx.ServerError("SetEmailNotifications", err) + return + } + log.Trace("Email notifications preference made %s: %s", preference, ctx.User.Name) + ctx.Flash.Success(ctx.Tr("settings.email_preference_set_success")) + ctx.Redirect(setting.AppSubURL + "/user/settings/account") + return + } + + if ctx.HasError() { + loadAccountData(ctx) + + ctx.HTML(http.StatusOK, tplSettingsAccount) + return + } + + email := &models.EmailAddress{ + UID: ctx.User.ID, + Email: form.Email, + IsActivated: !setting.Service.RegisterEmailConfirm, + } + if err := models.AddEmailAddress(email); err != nil { + if models.IsErrEmailAlreadyUsed(err) { + loadAccountData(ctx) + + ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplSettingsAccount, &form) + return + } else if models.IsErrEmailInvalid(err) { + loadAccountData(ctx) + + ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tplSettingsAccount, &form) + return + } + ctx.ServerError("AddEmailAddress", err) + return + } + + // Send confirmation email + if setting.Service.RegisterEmailConfirm { + mailer.SendActivateEmailMail(ctx.User, email) + if err := ctx.Cache.Put("MailResendLimit_"+ctx.User.LowerName, ctx.User.LowerName, 180); err != nil { + log.Error("Set cache(MailResendLimit) fail: %v", err) + } + ctx.Flash.Info(ctx.Tr("settings.add_email_confirmation_sent", email.Email, timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale.Language()))) + } else { + ctx.Flash.Success(ctx.Tr("settings.add_email_success")) + } + + log.Trace("Email address added: %s", email.Email) + ctx.Redirect(setting.AppSubURL + "/user/settings/account") +} + +// DeleteEmail response for delete user's email +func DeleteEmail(ctx *context.Context) { + if err := models.DeleteEmailAddress(&models.EmailAddress{ID: ctx.QueryInt64("id"), UID: ctx.User.ID}); err != nil { + ctx.ServerError("DeleteEmail", err) + return + } + log.Trace("Email address deleted: %s", ctx.User.Name) + + ctx.Flash.Success(ctx.Tr("settings.email_deletion_success")) + ctx.JSON(http.StatusOK, map[string]interface{}{ + "redirect": setting.AppSubURL + "/user/settings/account", + }) +} + +// DeleteAccount render user suicide page and response for delete user himself +func DeleteAccount(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("settings") + ctx.Data["PageIsSettingsAccount"] = true + + if _, err := models.UserSignIn(ctx.User.Name, ctx.Query("password")); err != nil { + if models.IsErrUserNotExist(err) { + loadAccountData(ctx) + + ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_password"), tplSettingsAccount, nil) + } else { + ctx.ServerError("UserSignIn", err) + } + return + } + + if err := models.DeleteUser(ctx.User); err != nil { + switch { + case models.IsErrUserOwnRepos(err): + ctx.Flash.Error(ctx.Tr("form.still_own_repo")) + ctx.Redirect(setting.AppSubURL + "/user/settings/account") + case models.IsErrUserHasOrgs(err): + ctx.Flash.Error(ctx.Tr("form.still_has_org")) + ctx.Redirect(setting.AppSubURL + "/user/settings/account") + default: + ctx.ServerError("DeleteUser", err) + } + } else { + log.Trace("Account deleted: %s", ctx.User.Name) + ctx.Redirect(setting.AppSubURL + "/") + } +} + +// UpdateUIThemePost is used to update users' specific theme +func UpdateUIThemePost(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.UpdateThemeForm) + ctx.Data["Title"] = ctx.Tr("settings") + ctx.Data["PageIsSettingsAccount"] = true + + if ctx.HasError() { + ctx.Redirect(setting.AppSubURL + "/user/settings/account") + return + } + + if !form.IsThemeExists() { + ctx.Flash.Error(ctx.Tr("settings.theme_update_error")) + ctx.Redirect(setting.AppSubURL + "/user/settings/account") + return + } + + if err := ctx.User.UpdateTheme(form.Theme); err != nil { + ctx.Flash.Error(ctx.Tr("settings.theme_update_error")) + ctx.Redirect(setting.AppSubURL + "/user/settings/account") + return + } + + log.Trace("Update user theme: %s", ctx.User.Name) + ctx.Flash.Success(ctx.Tr("settings.theme_update_success")) + ctx.Redirect(setting.AppSubURL + "/user/settings/account") +} + +func loadAccountData(ctx *context.Context) { + emlist, err := models.GetEmailAddresses(ctx.User.ID) + if err != nil { + ctx.ServerError("GetEmailAddresses", err) + return + } + type UserEmail struct { + models.EmailAddress + CanBePrimary bool + } + pendingActivation := ctx.Cache.IsExist("MailResendLimit_" + ctx.User.LowerName) + emails := make([]*UserEmail, len(emlist)) + for i, em := range emlist { + var email UserEmail + email.EmailAddress = *em + email.CanBePrimary = em.IsActivated + emails[i] = &email + } + ctx.Data["Emails"] = emails + ctx.Data["EmailNotificationsPreference"] = ctx.User.EmailNotifications() + ctx.Data["ActivationsPending"] = pendingActivation + ctx.Data["CanAddEmails"] = !pendingActivation || !setting.Service.RegisterEmailConfirm + + if setting.Service.UserDeleteWithCommentsMaxTime != 0 { + ctx.Data["UserDeleteWithCommentsMaxTime"] = setting.Service.UserDeleteWithCommentsMaxTime.String() + ctx.Data["UserDeleteWithComments"] = ctx.User.CreatedUnix.AsTime().Add(setting.Service.UserDeleteWithCommentsMaxTime).After(time.Now()) + } +} From 1d49b68ffe030533d934bc2daddf1abc398f0706 Mon Sep 17 00:00:00 2001 From: Henning Bopp Date: Mon, 5 Jul 2021 21:19:55 +0200 Subject: [PATCH 9/9] Make ini config optional with fallbacks if not set * Make all hash related parameters optional * Set fallback parameters for each algo * Note in ini that all params (per algo) have to be defined in order to be used * Comment out ini entries * Reorder imports in install.go --- custom/conf/app.example.ini | 36 +++++++++++++++++++----------------- modules/auth/hash/argon2.go | 8 ++++++-- modules/auth/hash/bcrypt.go | 10 +++++++--- modules/auth/hash/pbkdf2.go | 8 ++++++-- modules/auth/hash/scrypt.go | 8 ++++++-- routers/install/install.go | 2 +- 6 files changed, 45 insertions(+), 27 deletions(-) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 6c23d59b96f61..647f792b7699f 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -389,24 +389,26 @@ INTERNAL_TOKEN= ;; Handle with care! Changing the values can massively change the hash calculation time and/or memory needed for the hashing process. ;; After changing a value, the user password hash will be recalculated on next successful login. ;; +;; In order to use values diverging from the defaults, all parameters (for the hash of choice) need to be set! +;; ;; Parameters for BCrypt -BCRYPT_COST = 10 -;; -;; Parameters for scrypt -SCRYPT_N = 65536 -SCRYPT_R = 16 -SCRYPT_P = 2 -SCRYPT_KEY_LENGTH = 50 -;; -;; Parameters for Argon2id -ARGON2_ITERATIONS = 2 -ARGON2_MEMORY = 65536 -ARGON2_PARALLELISM = 8 -ARGON2_KEY_LENGTH = 50 -;; -;; Parameters for Pbkdf2 -PBKDF2_ITERATIONS = 10000 -PBKDF2_KEY_LENGTH = 50 +;BCRYPT_COST = 10 +;; +;; Parameters for scrypt (all 4 parameters need to be set) +;SCRYPT_N = 65536 +;SCRYPT_R = 16 +;SCRYPT_P = 2 +;SCRYPT_KEY_LENGTH = 50 +;; +;; Parameters for Argon2id (all 4 parameters need to be set) +;ARGON2_ITERATIONS = 2 +;ARGON2_MEMORY = 65536 +;ARGON2_PARALLELISM = 8 +;ARGON2_KEY_LENGTH = 50 +;; +;; Parameters for Pbkdf2 (both parameters need to be set) +;PBKDF2_ITERATIONS = 10000 +;PBKDF2_KEY_LENGTH = 50 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/modules/auth/hash/argon2.go b/modules/auth/hash/argon2.go index c1f21bebcf8bb..5b18ceaf1df4a 100644 --- a/modules/auth/hash/argon2.go +++ b/modules/auth/hash/argon2.go @@ -19,6 +19,7 @@ type Argon2Hasher struct { Memory uint32 `ini:"ARGON2_MEMORY"` Parallelism uint8 `ini:"ARGON2_PARALLELISM"` KeyLength uint32 `ini:"ARGON2_KEY_LENGTH"` + fallback string } // HashPassword returns a PasswordHash, PassWordAlgo (and optionally an error) @@ -26,7 +27,7 @@ func (h *Argon2Hasher) HashPassword(password, salt, config string) (string, stri var tempPasswd []byte if config == "fallback" { // Fixed default config to match with original configuration - config = "2$65536$8$50" + config = h.fallback } split := strings.Split(config, "$") @@ -80,9 +81,12 @@ func (h *Argon2Hasher) getConfigFromAlgo(algo string) string { } func (h *Argon2Hasher) getConfigFromSetting() string { + if h.Iterations == 0 || h.Memory == 0 || h.Parallelism == 0 || h.KeyLength == 0 { + return h.fallback + } return fmt.Sprintf("%d$%d$%d$%d", h.Iterations, h.Memory, h.Parallelism, h.KeyLength) } func init() { - DefaultHasher.Hashers["argon2"] = &Argon2Hasher{2, 65536, 8, 50} + DefaultHasher.Hashers["argon2"] = &Argon2Hasher{2, 65536, 8, 50, "2$65536$8$50"} } diff --git a/modules/auth/hash/bcrypt.go b/modules/auth/hash/bcrypt.go index e6a2827fae7ef..b1b2e7450dd35 100644 --- a/modules/auth/hash/bcrypt.go +++ b/modules/auth/hash/bcrypt.go @@ -14,14 +14,15 @@ import ( // BCryptHasher is a Hash implementation for BCrypt type BCryptHasher struct { - Cost int `ini:"BCRYPT_COST"` + Cost int `ini:"BCRYPT_COST"` + fallback string } // HashPassword returns a PasswordHash, PassWordAlgo (and optionally an error) func (h *BCryptHasher) HashPassword(password, salt, config string) (string, string, error) { if config == "fallback" { // Fixed default config to match with original configuration - config = "10" + config = h.fallback } else if config == "" { config = h.getConfigFromSetting() } @@ -46,9 +47,12 @@ func (h *BCryptHasher) getConfigFromAlgo(algo string) string { } func (h *BCryptHasher) getConfigFromSetting() string { + if h.Cost == 0 { + return h.fallback + } return strconv.Itoa(h.Cost) } func init() { - DefaultHasher.Hashers["bcrypt"] = &BCryptHasher{10} + DefaultHasher.Hashers["bcrypt"] = &BCryptHasher{10, "10"} } diff --git a/modules/auth/hash/pbkdf2.go b/modules/auth/hash/pbkdf2.go index db5fc2aca5c46..5e49488451b9e 100644 --- a/modules/auth/hash/pbkdf2.go +++ b/modules/auth/hash/pbkdf2.go @@ -18,6 +18,7 @@ import ( type Pbkdf2Hasher struct { Iterations int `ini:"PBKDF2_ITERATIONS"` KeyLength int `ini:"PBKDF2_KEY_LENGTH"` + fallback string } // HashPassword returns a PasswordHash, PassWordAlgo (and optionally an error) @@ -25,7 +26,7 @@ func (h *Pbkdf2Hasher) HashPassword(password, salt, config string) (string, stri var tempPasswd []byte if config == "fallback" { // Fixed default config to match with original configuration - config = "10000$50" + config = h.fallback } split := strings.Split(config, "$") @@ -64,9 +65,12 @@ func (h *Pbkdf2Hasher) getConfigFromAlgo(algo string) string { } func (h *Pbkdf2Hasher) getConfigFromSetting() string { + if h.Iterations == 0 || h.KeyLength == 0 { + return h.fallback + } return fmt.Sprintf("%d$%d", h.Iterations, h.KeyLength) } func init() { - DefaultHasher.Hashers["pbkdf2"] = &Pbkdf2Hasher{10000, 50} + DefaultHasher.Hashers["pbkdf2"] = &Pbkdf2Hasher{10000, 50, "10000$50"} } diff --git a/modules/auth/hash/scrypt.go b/modules/auth/hash/scrypt.go index 619365559b9e8..7986ec3282c12 100644 --- a/modules/auth/hash/scrypt.go +++ b/modules/auth/hash/scrypt.go @@ -19,6 +19,7 @@ type SCryptHasher struct { R int `ini:"SCRYPT_R"` P int `ini:"SCRYPT_P"` KeyLength int `ini:"SCRYPT_KEY_LENGTH"` + fallback string } // HashPassword returns a PasswordHash, PassWordAlgo (and optionally an error) @@ -26,7 +27,7 @@ func (h *SCryptHasher) HashPassword(password, salt, config string) (string, stri var tempPasswd []byte if config == "fallback" { // Fixed default config to match with original configuration - config = "65536$16$2$50" + config = h.fallback } split := strings.Split(config, "$") @@ -71,9 +72,12 @@ func (h *SCryptHasher) getConfigFromAlgo(algo string) string { } func (h *SCryptHasher) getConfigFromSetting() string { + if h.N == 0 || h.R == 0 || h.P == 0 || h.KeyLength == 0 { + return h.fallback + } return fmt.Sprintf("%d$%d$%d$%d", h.N, h.R, h.P, h.KeyLength) } func init() { - DefaultHasher.Hashers["scrypt"] = &SCryptHasher{65536, 16, 2, 50} + DefaultHasher.Hashers["scrypt"] = &SCryptHasher{65536, 16, 2, 50, "65536$16$2$50"} } diff --git a/routers/install/install.go b/routers/install/install.go index 9f8bb59e8dbce..8bd4e9ac3a681 100644 --- a/routers/install/install.go +++ b/routers/install/install.go @@ -6,7 +6,6 @@ package install import ( - "code.gitea.io/gitea/modules/auth/hash" "fmt" "net/http" "os" @@ -16,6 +15,7 @@ import ( "time" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/auth/hash" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/generate"