From cd79fbf94a7abc540cdcc1dd61cbf227633f41fc Mon Sep 17 00:00:00 2001 From: zeripath Date: Wed, 14 Apr 2021 01:04:17 +0100 Subject: [PATCH 001/195] v172 migration adds created_unix field instead of expiry (#15458) The Session table must have an Expiry field not a created_unix field - somehow this migration adds the incorrect named field leading to #15445 reports. Fix #15445 Signed-off-by: Andrew Thornton --- models/migrations/v172.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/models/migrations/v172.go b/models/migrations/v172.go index 51f83bcc912a4..125522a4b8ea1 100644 --- a/models/migrations/v172.go +++ b/models/migrations/v172.go @@ -12,9 +12,9 @@ import ( func addSessionTable(x *xorm.Engine) error { type Session struct { - Key string `xorm:"pk CHAR(16)"` - Data []byte `xorm:"BLOB"` - CreatedUnix timeutil.TimeStamp + Key string `xorm:"pk CHAR(16)"` + Data []byte `xorm:"BLOB"` + Expiry timeutil.TimeStamp } return x.Sync2(new(Session)) } From 35381a0e4f3c62ce0ba17d0bf628b3f82307a670 Mon Sep 17 00:00:00 2001 From: GiteaBot Date: Wed, 14 Apr 2021 00:12:38 +0000 Subject: [PATCH 002/195] [skip ci] Updated translations via Crowdin --- options/locale/locale_tr-TR.ini | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/options/locale/locale_tr-TR.ini b/options/locale/locale_tr-TR.ini index c694786a4aec5..3e85a2912e98d 100644 --- a/options/locale/locale_tr-TR.ini +++ b/options/locale/locale_tr-TR.ini @@ -319,8 +319,13 @@ reset_password=Hesabınızı kurtarın register_success=Kayıt başarılı register_notify=Gitea'ya Hoş Geldiniz +release.new.subject=%s içinden %s bırakıldı +repo.transfer.subject_to=%s "%s" aktarımını %s tarafına gerçekleştirmek istiyor +repo.transfer.subject_to_you=%s size "%s" aktarmak istiyor +repo.transfer.to_you=siz +repo.collaborator.added.subject=%s sizi %s ekledi [modal] yes=Evet @@ -720,6 +725,9 @@ mirror_address=URL'den Klonla mirror_address_desc=Gerekli kimlikleri Yetkilendirmeyi Klonla bölümüne girin. mirror_address_url_invalid=Sağlanan Url geçersiz. Url'nin tüm bileşenlerinden doğru olarak kaçmalısınız. mirror_address_protocol_invalid=Sağlanan url geçersiz. Yalnızca http(s):// veya git:// konumları yansıtılabilir. +mirror_lfs_desc=LFS verisinin yansılamasını etkinleştir. +mirror_lfs_endpoint=LFS Uç Noktası +mirror_lfs_endpoint_desc=Senkronizasyon, LFS sunucusunu belirlemek için klonlama url'sini kullanmaya çalışacak. Eğer LFS veri deposu başka yerdeyse özel bir uç nokta da belirtebilirsiniz. mirror_last_synced=Son Senkronize Edilen watchers=İzleyenler stargazers=Yıldızlayanlar @@ -778,6 +786,11 @@ migrate_options=Göç Seçenekleri migrate_service=Göç Hizmeti migrate_options_mirror_helper=Bu depo bir yansı olacaktır migrate_options_mirror_disabled=Site yöneticiniz yeni yansıları devre dışı bıraktı. +migrate_options_lfs=LFS dosyalarını taşı +migrate_options_lfs_endpoint.label=LFS Uç Noktası +migrate_options_lfs_endpoint.description=Taşıma, LFS sunucusunu belirlemek için Git uzak sunucusunu kullanmaya çalışacak. Eğer LFS veri deposu başka yerdeyse özel bir uç nokta da belirtebilirsiniz. +migrate_options_lfs_endpoint.description.local=Yerel bir sunucu yolu da destekleniyor. +migrate_options_lfs_endpoint.placeholder=Klonlama URL'sinden üretmek için boş bırakın migrate_items=Göç Öğeleri migrate_items_wiki=Wiki migrate_items_milestones=Kilometre Taşları @@ -794,6 +807,7 @@ migrate.permission_denied=Yerel depoları içeri aktarma izniniz yok. migrate.permission_denied_blocked=Engellenen ana bilgisayarlardan içeri aktarmanıza izin verilmiyor. migrate.permission_denied_private_ip=Özel IP'lerden içeri aktarmanıza izin verilmiyor. migrate.invalid_local_path=Yerel yol geçersiz. Mevcut değil veya bir dizin değil. +migrate.invalid_lfs_endpoint=LFS Uç noktası geçerli değil. migrate.failed=Göç başarısız: %v migrate.migrate_items_options=Ek öğeleri taşımak için Erişim Kodu gereklidir migrated_from=%[2]s konumundan göç edildi From ca2e1d8090ee9450bd47e5518eb515a9ee1e1475 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Wed, 14 Apr 2021 08:46:17 +0200 Subject: [PATCH 003/195] docs: migration start new section (#15462) --- models/migrations/migrations.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index e9d4927ae6395..12e7a74561995 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -302,6 +302,9 @@ var migrations = []Migration{ NewMigration("Remove invalid labels from comments", removeInvalidLabels), // v177 -> v178 NewMigration("Delete orphaned IssueLabels", deleteOrphanedIssueLabels), + + // Gitea 1.14.0 ends at v178 + // v178 -> v179 NewMigration("Add LFS columns to Mirror", addLFSMirrorColumns), } From 55eb1745bd5427c6f84f77703a580d580ac379b3 Mon Sep 17 00:00:00 2001 From: Martin Michaelis Date: Wed, 14 Apr 2021 14:02:12 +0200 Subject: [PATCH 004/195] OAuth2 auto-register (#5123) * Refactored handleOAuth2SignIn in routers/user/auth.go The function handleOAuth2SignIn was called twice but some code path could only be reached by one of the invocations. Moved the unnecessary code path out of handleOAuth2SignIn. * Refactored user creation There was common code to create a user and display the correct error message. And after the creation the only user should be an admin and if enabled a confirmation email should be sent. This common code is now abstracted into two functions and a helper function to call both. * Added auto-register for OAuth2 users If enabled new OAuth2 users will be registered with their OAuth2 details. The UserID, Name and Email fields from the gothUser are used. Therefore the OpenID Connect provider needs additional scopes to return the coresponding claims. * Added error for missing fields in OAuth2 response * Linking and auto linking on oauth2 registration * Set default username source to nickname * Add automatic oauth2 scopes for github and google * Add hint to change the openid connect scopes if fields are missing * Extend info about auto linking security risk Co-authored-by: Viktor Kuzmin Signed-off-by: Martin Michaelis --- custom/conf/app.example.ini | 24 ++ .../doc/advanced/config-cheat-sheet.en-us.md | 15 + models/migrations/migrations.go | 2 + models/migrations/v179.go | 26 ++ modules/auth/oauth2/oauth2.go | 14 +- modules/setting/oauth2_client.go | 90 ++++++ modules/setting/setting.go | 1 + routers/user/auth.go | 262 ++++++++++++------ routers/user/auth_openid.go | 52 +--- 9 files changed, 351 insertions(+), 135 deletions(-) create mode 100644 models/migrations/v179.go create mode 100644 modules/setting/oauth2_client.go diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 127643270bf48..c3ecf10c2628f 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -617,6 +617,30 @@ WHITELISTED_URIS = ; 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 (seperated 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 = disabled + [service] ; Time limit to confirm account/email registration ACTIVE_CODE_LIVE_MINUTES = 180 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 f1c5bf1b8e8d4..9bafee846f87f 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -429,6 +429,21 @@ relation to port exhaustion. - `BLACKLISTED_URIS`: **\**: If non-empty, list of POSIX regex patterns matching OpenID URI's to block. +## OAuth2 Client (`oauth2_client`) + +- `REGISTER_EMAIL_CONFIRM`: *[service]* **REGISTER\_EMAIL\_CONFIRM**: Set this to enable or disable email confirmation of OAuth2 auto-registration. (Overwrites the REGISTER\_EMAIL\_CONFIRM setting of the `[service]` section) +- `OPENID_CONNECT_SCOPES`: **\**: List of additional openid connect scopes. (`openid` is implicitly added) +- `ENABLE_AUTO_REGISTRATION`: **false**: Automatically create user accounts for new oauth2 users. +- `USERNAME`: **nickname**: 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 +- `UPDATE_AVATAR`: **false**: Update avatar if available from oauth2 provider. Update will be performed on each login. +- `ACCOUNT_LINKING`: **disabled**: How to handle if an account / email already exists: + - disabled - show an error + - login - show an account linking login + - auto - automatically link with the account (Please be aware that this will grant access to an existing account just because the same username or email is provided. You must make sure that this does not cause issues with your authentication providers.) + ## Service (`service`) - `ACTIVE_CODE_LIVE_MINUTES`: **180**: Time limit (min) to confirm account/email registration. diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 12e7a74561995..c54c383fb810d 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -307,6 +307,8 @@ var migrations = []Migration{ // v178 -> v179 NewMigration("Add LFS columns to Mirror", addLFSMirrorColumns), + // v179 -> v180 + NewMigration("Convert avatar url to text", convertAvatarURLToText), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v179.go b/models/migrations/v179.go new file mode 100644 index 0000000000000..735e6b62dd2c5 --- /dev/null +++ b/models/migrations/v179.go @@ -0,0 +1,26 @@ +// 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/xorm" + "xorm.io/xorm/schemas" +) + +func convertAvatarURLToText(x *xorm.Engine) error { + dbType := x.Dialect().URI().DBType + if dbType == schemas.SQLITE { // For SQLITE, varchar or char will always be represented as TEXT + return nil + } + + // Some oauth2 providers may give very long avatar urls (i.e. Google) + return modifyColumn(x, "external_login_user", &schemas.Column{ + Name: "avatar_url", + SQLType: schemas.SQLType{ + Name: schemas.Text, + }, + Nullable: true, + }) +} diff --git a/modules/auth/oauth2/oauth2.go b/modules/auth/oauth2/oauth2.go index e2c97b72f31d2..5d152e0a5588a 100644 --- a/modules/auth/oauth2/oauth2.go +++ b/modules/auth/oauth2/oauth2.go @@ -157,7 +157,11 @@ func createProvider(providerName, providerType, clientID, clientSecret, openIDCo emailURL = customURLMapping.EmailURL } } - provider = github.NewCustomisedURL(clientID, clientSecret, callbackURL, authURL, tokenURL, profileURL, emailURL) + scopes := []string{} + if setting.OAuth2Client.EnableAutoRegistration { + scopes = append(scopes, "user:email") + } + provider = github.NewCustomisedURL(clientID, clientSecret, callbackURL, authURL, tokenURL, profileURL, emailURL, scopes...) case "gitlab": authURL := gitlab.AuthURL tokenURL := gitlab.TokenURL @@ -175,9 +179,13 @@ func createProvider(providerName, providerType, clientID, clientSecret, openIDCo } provider = gitlab.NewCustomisedURL(clientID, clientSecret, callbackURL, authURL, tokenURL, profileURL, "read_user") case "gplus": // named gplus due to legacy gplus -> google migration (Google killed Google+). This ensures old connections still work - provider = google.New(clientID, clientSecret, callbackURL) + scopes := []string{"email"} + if setting.OAuth2Client.UpdateAvatar || setting.OAuth2Client.EnableAutoRegistration { + scopes = append(scopes, "profile") + } + provider = google.New(clientID, clientSecret, callbackURL, scopes...) case "openidConnect": - if provider, err = openidConnect.New(clientID, clientSecret, callbackURL, openIDConnectAutoDiscoveryURL); err != nil { + if provider, err = openidConnect.New(clientID, clientSecret, callbackURL, openIDConnectAutoDiscoveryURL, setting.OAuth2Client.OpenIDConnectScopes...); err != nil { log.Warn("Failed to create OpenID Connect Provider with name '%s' with url '%s': %v", providerName, openIDConnectAutoDiscoveryURL, err) } case "twitter": diff --git a/modules/setting/oauth2_client.go b/modules/setting/oauth2_client.go new file mode 100644 index 0000000000000..a336563c9a456 --- /dev/null +++ b/modules/setting/oauth2_client.go @@ -0,0 +1,90 @@ +// 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/log" + + "gopkg.in/ini.v1" +) + +// OAuth2UsernameType is enum describing the way gitea 'name' should be generated from oauth2 data +type OAuth2UsernameType string + +const ( + // OAuth2UsernameUserid oauth2 userid field will be used as gitea name + OAuth2UsernameUserid OAuth2UsernameType = "userid" + // OAuth2UsernameNickname oauth2 nickname field will be used as gitea name + OAuth2UsernameNickname OAuth2UsernameType = "nickname" + // OAuth2UsernameEmail username of oauth2 email filed will be used as gitea name + OAuth2UsernameEmail OAuth2UsernameType = "email" +) + +func (username OAuth2UsernameType) isValid() bool { + switch username { + case OAuth2UsernameUserid, OAuth2UsernameNickname, OAuth2UsernameEmail: + return true + } + return false +} + +// OAuth2AccountLinkingType is enum describing behaviour of linking with existing account +type OAuth2AccountLinkingType string + +const ( + // OAuth2AccountLinkingDisabled error will be displayed if account exist + OAuth2AccountLinkingDisabled OAuth2AccountLinkingType = "disabled" + // OAuth2AccountLinkingLogin account linking login will be displayed if account exist + OAuth2AccountLinkingLogin OAuth2AccountLinkingType = "login" + // OAuth2AccountLinkingAuto account will be automatically linked if account exist + OAuth2AccountLinkingAuto OAuth2AccountLinkingType = "auto" +) + +func (accountLinking OAuth2AccountLinkingType) isValid() bool { + switch accountLinking { + case OAuth2AccountLinkingDisabled, OAuth2AccountLinkingLogin, OAuth2AccountLinkingAuto: + return true + } + return false +} + +// OAuth2Client settings +var OAuth2Client struct { + RegisterEmailConfirm bool + OpenIDConnectScopes []string + EnableAutoRegistration bool + Username OAuth2UsernameType + UpdateAvatar bool + AccountLinking OAuth2AccountLinkingType +} + +func newOAuth2Client() { + sec := Cfg.Section("oauth2_client") + OAuth2Client.RegisterEmailConfirm = sec.Key("REGISTER_EMAIL_CONFIRM").MustBool(Service.RegisterEmailConfirm) + OAuth2Client.OpenIDConnectScopes = parseScopes(sec, "OPENID_CONNECT_SCOPES") + OAuth2Client.EnableAutoRegistration = sec.Key("ENABLE_AUTO_REGISTRATION").MustBool() + OAuth2Client.Username = OAuth2UsernameType(sec.Key("USERNAME").MustString(string(OAuth2UsernameNickname))) + if !OAuth2Client.Username.isValid() { + log.Warn("Username setting is not valid: '%s', will fallback to '%s'", OAuth2Client.Username, OAuth2UsernameNickname) + OAuth2Client.Username = OAuth2UsernameNickname + } + OAuth2Client.UpdateAvatar = sec.Key("UPDATE_AVATAR").MustBool() + OAuth2Client.AccountLinking = OAuth2AccountLinkingType(sec.Key("ACCOUNT_LINKING").MustString(string(OAuth2AccountLinkingDisabled))) + if !OAuth2Client.AccountLinking.isValid() { + log.Warn("Account linking setting is not valid: '%s', will fallback to '%s'", OAuth2Client.AccountLinking, OAuth2AccountLinkingDisabled) + OAuth2Client.AccountLinking = OAuth2AccountLinkingDisabled + } +} + +func parseScopes(sec *ini.Section, name string) []string { + parts := sec.Key(name).Strings(" ") + scopes := make([]string, 0, len(parts)) + for _, scope := range parts { + if scope != "" { + scopes = append(scopes, scope) + } + } + return scopes +} diff --git a/modules/setting/setting.go b/modules/setting/setting.go index f609edba179b1..7963776fd0644 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -1163,6 +1163,7 @@ func MakeManifestData(appName string, appURL string, absoluteAssetURL string) [] func NewServices() { InitDBConfig() newService() + newOAuth2Client() NewLogServices(false) newCacheService() newSessionService() diff --git a/routers/user/auth.go b/routers/user/auth.go index 1692a396cca68..2ec09cc069f88 100644 --- a/routers/user/auth.go +++ b/routers/user/auth.go @@ -8,6 +8,8 @@ package user import ( "errors" "fmt" + "io" + "io/ioutil" "net/http" "strings" @@ -571,7 +573,7 @@ func SignInOAuth(ctx *context.Context) { user, gothUser, err := oAuth2UserLoginCallback(loginSource, ctx.Req, ctx.Resp) if err == nil && user != nil { // we got the user without going through the whole OAuth2 authentication flow again - handleOAuth2SignIn(user, gothUser, ctx, err) + handleOAuth2SignIn(ctx, user, gothUser) return } @@ -609,30 +611,102 @@ func SignInOAuthCallback(ctx *context.Context) { u, gothUser, err := oAuth2UserLoginCallback(loginSource, ctx.Req, ctx.Resp) - handleOAuth2SignIn(u, gothUser, ctx, err) -} - -func handleOAuth2SignIn(u *models.User, gothUser goth.User, ctx *context.Context, err error) { if err != nil { ctx.ServerError("UserSignIn", err) return } if u == nil { - // no existing user is found, request attach or new account - if err := ctx.Session.Set("linkAccountGothUser", gothUser); err != nil { - log.Error("Error setting linkAccountGothUser in session: %v", err) + if setting.OAuth2Client.EnableAutoRegistration { + // create new user with details from oauth2 provider + var missingFields []string + if gothUser.UserID == "" { + missingFields = append(missingFields, "sub") + } + if gothUser.Email == "" { + missingFields = append(missingFields, "email") + } + if setting.OAuth2Client.Username == setting.OAuth2UsernameNickname && gothUser.NickName == "" { + missingFields = append(missingFields, "nickname") + } + if len(missingFields) > 0 { + log.Error("OAuth2 Provider %s returned empty or missing fields: %s", loginSource.Name, missingFields) + if loginSource.IsOAuth2() && loginSource.OAuth2().Provider == "openidConnect" { + log.Error("You may need to change the 'OPENID_CONNECT_SCOPES' setting to request all required fields") + } + err = fmt.Errorf("OAuth2 Provider %s returned empty or missing fields: %s", loginSource.Name, missingFields) + ctx.ServerError("CreateUser", err) + return + } + u = &models.User{ + Name: getUserName(&gothUser), + FullName: gothUser.Name, + Email: gothUser.Email, + IsActive: !setting.OAuth2Client.RegisterEmailConfirm, + LoginType: models.LoginOAuth2, + LoginSource: loginSource.ID, + LoginName: gothUser.UserID, + } + + if !createAndHandleCreatedUser(ctx, base.TplName(""), nil, u, &gothUser, setting.OAuth2Client.AccountLinking != setting.OAuth2AccountLinkingDisabled) { + // error already handled + return + } + } else { + // no existing user is found, request attach or new account + showLinkingLogin(ctx, gothUser) + return } - if err := ctx.Session.Release(); err != nil { - log.Error("Error storing session: %v", err) + } + + handleOAuth2SignIn(ctx, u, gothUser) +} + +func getUserName(gothUser *goth.User) string { + switch setting.OAuth2Client.Username { + case setting.OAuth2UsernameEmail: + return strings.Split(gothUser.Email, "@")[0] + case setting.OAuth2UsernameNickname: + return gothUser.NickName + default: // OAuth2UsernameUserid + return gothUser.UserID + } +} + +func showLinkingLogin(ctx *context.Context, gothUser goth.User) { + if err := ctx.Session.Set("linkAccountGothUser", gothUser); err != nil { + log.Error("Error setting linkAccountGothUser in session: %v", err) + } + if err := ctx.Session.Release(); err != nil { + log.Error("Error storing session: %v", err) + } + ctx.Redirect(setting.AppSubURL + "/user/link_account") +} + +func updateAvatarIfNeed(url string, u *models.User) { + if setting.OAuth2Client.UpdateAvatar && len(url) > 0 { + resp, err := http.Get(url) + if err == nil { + defer func() { + _ = resp.Body.Close() + }() + } + // ignore any error + if err == nil && resp.StatusCode == http.StatusOK { + data, err := ioutil.ReadAll(io.LimitReader(resp.Body, setting.Avatar.MaxFileSize+1)) + if err == nil && int64(len(data)) <= setting.Avatar.MaxFileSize { + _ = u.UploadAvatar(data) + } } - ctx.Redirect(setting.AppSubURL + "/user/link_account") - return } +} + +func handleOAuth2SignIn(ctx *context.Context, u *models.User, gothUser goth.User) { + updateAvatarIfNeed(gothUser.AvatarURL, u) // If this user is enrolled in 2FA, we can't sign the user in just yet. // Instead, redirect them to the 2FA authentication page. - _, err = models.GetTwoFactorByUID(u.ID) + _, err := models.GetTwoFactorByUID(u.ID) if err != nil { if !models.IsErrTwoFactorNotEnrolled(err) { ctx.ServerError("UserSignIn", err) @@ -766,8 +840,9 @@ func LinkAccount(ctx *context.Context) { return } - uname := gothUser.(goth.User).NickName - email := gothUser.(goth.User).Email + gu, _ := gothUser.(goth.User) + uname := getUserName(&gu) + email := gu.Email ctx.Data["user_name"] = uname ctx.Data["email"] = email @@ -836,22 +911,28 @@ func LinkAccountPostSignIn(ctx *context.Context) { return } + linkAccount(ctx, u, gothUser.(goth.User), signInForm.Remember) +} + +func linkAccount(ctx *context.Context, u *models.User, gothUser goth.User, remember bool) { + updateAvatarIfNeed(gothUser.AvatarURL, u) + // If this user is enrolled in 2FA, we can't sign the user in just yet. // Instead, redirect them to the 2FA authentication page. - _, err = models.GetTwoFactorByUID(u.ID) + _, err := models.GetTwoFactorByUID(u.ID) if err != nil { if !models.IsErrTwoFactorNotEnrolled(err) { ctx.ServerError("UserLinkAccount", err) return } - err = externalaccount.LinkAccountToUser(u, gothUser.(goth.User)) + err = externalaccount.LinkAccountToUser(u, gothUser) if err != nil { ctx.ServerError("UserLinkAccount", err) return } - handleSignIn(ctx, u, signInForm.Remember) + handleSignIn(ctx, u, remember) return } @@ -859,7 +940,7 @@ func LinkAccountPostSignIn(ctx *context.Context) { if err := ctx.Session.Set("twofaUid", u.ID); err != nil { log.Error("Error setting twofaUid in session: %v", err) } - if err := ctx.Session.Set("twofaRemember", signInForm.Remember); err != nil { + if err := ctx.Session.Set("twofaRemember", remember); err != nil { log.Error("Error setting twofaRemember in session: %v", err) } if err := ctx.Session.Set("linkAccount", true); err != nil { @@ -982,62 +1063,8 @@ func LinkAccountPostRegister(ctx *context.Context) { LoginName: gothUser.(goth.User).UserID, } - //nolint: dupl - if err := models.CreateUser(u); err != nil { - switch { - case models.IsErrUserAlreadyExist(err): - ctx.Data["Err_UserName"] = true - ctx.RenderWithErr(ctx.Tr("form.username_been_taken"), tplLinkAccount, &form) - case models.IsErrEmailAlreadyUsed(err): - ctx.Data["Err_Email"] = true - ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplLinkAccount, &form) - case models.IsErrEmailInvalid(err): - ctx.Data["Err_Email"] = true - ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tplSignUp, &form) - case models.IsErrNameReserved(err): - ctx.Data["Err_UserName"] = true - ctx.RenderWithErr(ctx.Tr("user.form.name_reserved", err.(models.ErrNameReserved).Name), tplLinkAccount, &form) - case models.IsErrNamePatternNotAllowed(err): - ctx.Data["Err_UserName"] = true - ctx.RenderWithErr(ctx.Tr("user.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tplLinkAccount, &form) - case models.IsErrNameCharsNotAllowed(err): - ctx.Data["Err_UserName"] = true - ctx.RenderWithErr(ctx.Tr("user.form.name_chars_not_allowed", err.(models.ErrNameCharsNotAllowed).Name), tplLinkAccount, &form) - default: - ctx.ServerError("CreateUser", err) - } - return - } - log.Trace("Account created: %s", u.Name) - - // Auto-set admin for the only user. - if models.CountUsers() == 1 { - u.IsAdmin = true - u.IsActive = true - u.SetLastLogin() - if err := models.UpdateUserCols(u, "is_admin", "is_active", "last_login_unix"); err != nil { - ctx.ServerError("UpdateUser", err) - return - } - } - - // update external user information - if err := models.UpdateExternalUser(u, gothUser.(goth.User)); err != nil { - log.Error("UpdateExternalUser failed: %v", err) - } - - // Send confirmation email - if setting.Service.RegisterEmailConfirm && u.ID > 1 { - mailer.SendActivateAccountMail(ctx.Locale, u) - - ctx.Data["IsSendRegisterMail"] = true - ctx.Data["Email"] = u.Email - ctx.Data["ActiveCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale.Language()) - ctx.HTML(http.StatusOK, TplActivate) - - if err := ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil { - log.Error("Set cache(MailResendLimit) fail: %v", err) - } + if !createAndHandleCreatedUser(ctx, tplLinkAccount, form, u, gothUser.(*goth.User), false) { + // error already handled return } @@ -1176,30 +1203,91 @@ func SignUpPost(ctx *context.Context) { Passwd: form.Password, IsActive: !(setting.Service.RegisterEmailConfirm || setting.Service.RegisterManualConfirm), } + + if !createAndHandleCreatedUser(ctx, tplSignUp, form, u, nil, false) { + // error already handled + return + } + + ctx.Flash.Success(ctx.Tr("auth.sign_up_successful")) + handleSignInFull(ctx, u, false, true) +} + +// createAndHandleCreatedUser calls createUserInContext and +// then handleUserCreated. +func createAndHandleCreatedUser(ctx *context.Context, tpl base.TplName, form interface{}, u *models.User, gothUser *goth.User, allowLink bool) bool { + if !createUserInContext(ctx, tpl, form, u, gothUser, allowLink) { + return false + } + return handleUserCreated(ctx, u, gothUser) +} + +// createUserInContext creates a user and handles errors within a given context. +// Optionally a template can be specified. +func createUserInContext(ctx *context.Context, tpl base.TplName, form interface{}, u *models.User, gothUser *goth.User, allowLink bool) (ok bool) { if err := models.CreateUser(u); err != nil { + if allowLink && (models.IsErrUserAlreadyExist(err) || models.IsErrEmailAlreadyUsed(err)) { + if setting.OAuth2Client.AccountLinking == setting.OAuth2AccountLinkingAuto { + var user *models.User + user = &models.User{Name: u.Name} + hasUser, err := models.GetUser(user) + if !hasUser || err != nil { + user = &models.User{Email: u.Email} + hasUser, err = models.GetUser(user) + if !hasUser || err != nil { + ctx.ServerError("UserLinkAccount", err) + return + } + } + + // TODO: probably we should respect 'remeber' user's choice... + linkAccount(ctx, user, *gothUser, true) + return // user is already created here, all redirects are handled + } else if setting.OAuth2Client.AccountLinking == setting.OAuth2AccountLinkingLogin { + showLinkingLogin(ctx, *gothUser) + return // user will be created only after linking login + } + } + + // handle error without template + if len(tpl) == 0 { + ctx.ServerError("CreateUser", err) + return + } + + // handle error with template switch { case models.IsErrUserAlreadyExist(err): ctx.Data["Err_UserName"] = true - ctx.RenderWithErr(ctx.Tr("form.username_been_taken"), tplSignUp, &form) + ctx.RenderWithErr(ctx.Tr("form.username_been_taken"), tpl, form) case models.IsErrEmailAlreadyUsed(err): ctx.Data["Err_Email"] = true - ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplSignUp, &form) + ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tpl, form) case models.IsErrEmailInvalid(err): ctx.Data["Err_Email"] = true - ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tplSignUp, &form) + ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tpl, form) case models.IsErrNameReserved(err): ctx.Data["Err_UserName"] = true - ctx.RenderWithErr(ctx.Tr("user.form.name_reserved", err.(models.ErrNameReserved).Name), tplSignUp, &form) + ctx.RenderWithErr(ctx.Tr("user.form.name_reserved", err.(models.ErrNameReserved).Name), tpl, form) case models.IsErrNamePatternNotAllowed(err): ctx.Data["Err_UserName"] = true - ctx.RenderWithErr(ctx.Tr("user.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tplSignUp, &form) + ctx.RenderWithErr(ctx.Tr("user.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tpl, form) + case models.IsErrNameCharsNotAllowed(err): + ctx.Data["Err_UserName"] = true + ctx.RenderWithErr(ctx.Tr("user.form.name_chars_not_allowed", err.(models.ErrNameCharsNotAllowed).Name), tpl, form) default: ctx.ServerError("CreateUser", err) } return } log.Trace("Account created: %s", u.Name) + return true +} +// handleUserCreated does additional steps after a new user is created. +// It auto-sets admin for the only user, updates the optional external user and +// sends a confirmation email if required. +func handleUserCreated(ctx *context.Context, u *models.User, gothUser *goth.User) (ok bool) { // Auto-set admin for the only user. if models.CountUsers() == 1 { u.IsAdmin = true @@ -1211,8 +1299,15 @@ func SignUpPost(ctx *context.Context) { } } - // Send confirmation email, no need for social account. - if setting.Service.RegisterEmailConfirm && u.ID > 1 { + // update external user information + if gothUser != nil { + if err := models.UpdateExternalUser(u, *gothUser); err != nil { + log.Error("UpdateExternalUser failed: %v", err) + } + } + + // Send confirmation email + if !u.IsActive && u.ID > 1 { mailer.SendActivateAccountMail(ctx.Locale, u) ctx.Data["IsSendRegisterMail"] = true @@ -1226,8 +1321,7 @@ func SignUpPost(ctx *context.Context) { return } - ctx.Flash.Success(ctx.Tr("auth.sign_up_successful")) - handleSignInFull(ctx, u, false, true) + return true } // Activate render activate user page diff --git a/routers/user/auth_openid.go b/routers/user/auth_openid.go index 93a8861da7fc7..863fa67184774 100644 --- a/routers/user/auth_openid.go +++ b/routers/user/auth_openid.go @@ -18,11 +18,9 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/recaptcha" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web/middleware" "code.gitea.io/gitea/services/forms" - "code.gitea.io/gitea/services/mailer" ) const ( @@ -412,37 +410,16 @@ func RegisterOpenIDPost(ctx *context.Context) { return } - // TODO: abstract a finalizeSignUp function ? u := &models.User{ Name: form.UserName, Email: form.Email, Passwd: password, IsActive: !(setting.Service.RegisterEmailConfirm || setting.Service.RegisterManualConfirm), } - //nolint: dupl - if err := models.CreateUser(u); err != nil { - switch { - case models.IsErrUserAlreadyExist(err): - ctx.Data["Err_UserName"] = true - ctx.RenderWithErr(ctx.Tr("form.username_been_taken"), tplSignUpOID, &form) - case models.IsErrEmailAlreadyUsed(err): - ctx.Data["Err_Email"] = true - ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplSignUpOID, &form) - case models.IsErrNameReserved(err): - ctx.Data["Err_UserName"] = true - ctx.RenderWithErr(ctx.Tr("user.form.name_reserved", err.(models.ErrNameReserved).Name), tplSignUpOID, &form) - case models.IsErrNamePatternNotAllowed(err): - ctx.Data["Err_UserName"] = true - ctx.RenderWithErr(ctx.Tr("user.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tplSignUpOID, &form) - case models.IsErrNameCharsNotAllowed(err): - ctx.Data["Err_UserName"] = true - ctx.RenderWithErr(ctx.Tr("user.form.name_chars_not_allowed", err.(models.ErrNameCharsNotAllowed).Name), tplSignUpOID, &form) - default: - ctx.ServerError("CreateUser", err) - } + if !createUserInContext(ctx, tplSignUpOID, form, u, nil, false) { + // error already handled return } - log.Trace("Account created: %s", u.Name) // add OpenID for the user userOID := &models.UserOpenID{UID: u.ID, URI: oid} @@ -455,29 +432,8 @@ func RegisterOpenIDPost(ctx *context.Context) { return } - // Auto-set admin for the only user. - if models.CountUsers() == 1 { - u.IsAdmin = true - u.IsActive = true - u.SetLastLogin() - if err := models.UpdateUserCols(u, "is_admin", "is_active", "last_login_unix"); err != nil { - ctx.ServerError("UpdateUser", err) - return - } - } - - // Send confirmation email, no need for social account. - if setting.Service.RegisterEmailConfirm && u.ID > 1 { - mailer.SendActivateAccountMail(ctx.Locale, u) - - ctx.Data["IsSendRegisterMail"] = true - ctx.Data["Email"] = u.Email - ctx.Data["ActiveCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale.Language()) - ctx.HTML(http.StatusOK, TplActivate) - - if err := ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil { - log.Error("Set cache(MailResendLimit) fail: %v", err) - } + if !handleUserCreated(ctx, u, nil) { + // error already handled return } From 8e2a8efd84bf39c4dd38f7f4acdc2d7f499f610a Mon Sep 17 00:00:00 2001 From: zeripath Date: Wed, 14 Apr 2021 13:57:18 +0100 Subject: [PATCH 005/195] Prevent superfluous response.WriteHeader (#15456) This PR simply checks the status before writing the header. Signed-off-by: Andrew Thornton --- modules/context/response.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/modules/context/response.go b/modules/context/response.go index bdbbb97af78ba..4ffbd230a20d6 100644 --- a/modules/context/response.go +++ b/modules/context/response.go @@ -4,7 +4,9 @@ package context -import "net/http" +import ( + "net/http" +) // ResponseWriter represents a response writer for HTTP type ResponseWriter interface { @@ -60,8 +62,10 @@ func (r *Response) WriteHeader(statusCode int) { } r.beforeExecuted = true } - r.status = statusCode - r.ResponseWriter.WriteHeader(statusCode) + if r.status == 0 { + r.status = statusCode + r.ResponseWriter.WriteHeader(statusCode) + } } // Flush flush cached data From 424bd86c607048a4cdf618614a24cc3aeb2d0ac8 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 14 Apr 2021 21:33:22 +0800 Subject: [PATCH 006/195] Display more repository type on admin repository management (#15440) --- templates/admin/repo/list.tmpl | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/templates/admin/repo/list.tmpl b/templates/admin/repo/list.tmpl index dbd1ad6921a04..e96d9ebb33dc2 100644 --- a/templates/admin/repo/list.tmpl +++ b/templates/admin/repo/list.tmpl @@ -52,8 +52,30 @@ {{.Name}} - {{if .IsPrivate}} - {{svg "octicon-lock"}} + {{if .IsArchived}} + {{$.i18n.Tr "repo.desc.archived"}} + {{end}} + {{if .IsTemplate}} + {{if .IsPrivate}} + {{$.i18n.Tr "repo.desc.private_template"}} + {{else}} + {{if .Owner.Visibility.IsPrivate}} + {{$.i18n.Tr "repo.desc.internal_template"}} + {{end}} + {{end}} + {{else}} + {{if .IsPrivate}} + {{$.i18n.Tr "repo.desc.private"}} + {{else}} + {{if .Owner.Visibility.IsPrivate}} + {{$.i18n.Tr "repo.desc.internal"}} + {{end}} + {{end}} + {{end}} + {{if .IsFork}} + {{svg "octicon-repo-forked"}} + {{else if .IsMirror}} + {{svg "octicon-mirror"}} {{end}} {{.NumWatches}} From 1ee776970ac102121871b184b965c30103a2da71 Mon Sep 17 00:00:00 2001 From: zeripath Date: Wed, 14 Apr 2021 15:22:37 +0100 Subject: [PATCH 007/195] Fix ambiguous argument error on tags (#15432) There is a weird gotcha with GetTagCommitID that because it uses git rev-list can cause an ambiguous argument error. This PR simply makes tags use the same code as branches. Signed-off-by: Andrew Thornton --- modules/git/repo_commit.go | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index ea0aeeb35d370..5e2db34fd18e3 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -21,14 +21,7 @@ func (repo *Repository) GetBranchCommitID(name string) (string, error) { // GetTagCommitID returns last commit ID string of given tag. func (repo *Repository) GetTagCommitID(name string) (string, error) { - stdout, err := NewCommand("rev-list", "-n", "1", TagPrefix+name).RunInDir(repo.Path) - if err != nil { - if strings.Contains(err.Error(), "unknown revision or path") { - return "", ErrNotExist{name, ""} - } - return "", err - } - return strings.TrimSpace(stdout), nil + return repo.GetRefCommitID(TagPrefix + name) } // ConvertToSHA1 returns a Hash object from a potential ID string From 08ba895c2b871c309e7787110fbde21214a3d684 Mon Sep 17 00:00:00 2001 From: a1012112796 <1012112796@qq.com> Date: Wed, 14 Apr 2021 23:59:42 +0800 Subject: [PATCH 008/195] fix wrong file link in code search page (#15466) in previous the grenrated link is ``testg/testrepo/src/commit/....`` which is not right. the right version is ``/testg/testrepo/.......`` (start wiht ``/``) or ``http://127.0.0.1:3000/xxxxx`` (full link) to make it hase same result with explore page I choose the secound style. fix #15438 Signed-off-by: a1012112796 <1012112796@qq.com> Co-authored-by: 6543 <6543@obermui.de> --- routers/repo/search.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/routers/repo/search.go b/routers/repo/search.go index af4fe9ef12198..d9604bade06b0 100644 --- a/routers/repo/search.go +++ b/routers/repo/search.go @@ -6,7 +6,6 @@ package repo import ( "net/http" - "path" "strings" "code.gitea.io/gitea/modules/base" @@ -41,7 +40,7 @@ func Search(ctx *context.Context) { ctx.Data["Keyword"] = keyword ctx.Data["Language"] = language ctx.Data["queryType"] = queryType - ctx.Data["SourcePath"] = path.Join(setting.AppSubURL, ctx.Repo.Repository.Owner.Name, ctx.Repo.Repository.Name) + ctx.Data["SourcePath"] = ctx.Repo.Repository.HTMLURL() ctx.Data["SearchResults"] = searchResults ctx.Data["SearchResultLanguages"] = searchResultLanguages ctx.Data["RequireHighlightJS"] = true From 662bbed32e48ce83f0b71d5cda08c6222020866e Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Wed, 14 Apr 2021 18:44:01 +0200 Subject: [PATCH 009/195] Fixed several typos. (#15470) --- custom/conf/app.example.ini | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index c3ecf10c2628f..4fdc4b0214d1d 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -19,7 +19,7 @@ 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 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 @@ -48,7 +48,7 @@ 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. +; 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 @@ -134,7 +134,7 @@ ALLOWED_TYPES = 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 +; 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 = @@ -447,7 +447,7 @@ LOG_SQL = true 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 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 @@ -466,7 +466,7 @@ 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`. +; This can be overridden 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. @@ -514,9 +514,9 @@ BATCH_LENGTH = 20 ; 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. +; 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 overriden within in their [queue.name] sections. +; 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 @@ -621,7 +621,7 @@ BLACKLISTED_URIS = ; 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 (seperated by space, the openid scope is implicitly added). +; 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 = @@ -846,7 +846,7 @@ REPOSITORY_AVATAR_FALLBACK_IMAGE = /img/repo_default.png ; 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. +; 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" From 078df7a39272fb4c20db2d72b39338f3d4920f1b Mon Sep 17 00:00:00 2001 From: John Olheiser Date: Wed, 14 Apr 2021 12:54:54 -0500 Subject: [PATCH 010/195] quick fix (#15464) Signed-off-by: jolheiser --- templates/admin/config.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl index a3c55b2b18c28..6979512df7915 100644 --- a/templates/admin/config.tmpl +++ b/templates/admin/config.tmpl @@ -70,7 +70,7 @@
{{if not .SSH.Disabled}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}
{{if not .SSH.Disabled}}
{{.i18n.Tr "admin.config.ssh_start_builtin_server"}}
-
{{if not .SSH.StartBuiltinServer}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}
+
{{if .SSH.StartBuiltinServer}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}
{{.i18n.Tr "admin.config.ssh_domain"}}
{{.SSH.Domain}}
{{.i18n.Tr "admin.config.ssh_port"}}
From 1426601cf7dd6e2cefca218a84d38905dc1cb295 Mon Sep 17 00:00:00 2001 From: Naohisa Murakami Date: Thu, 15 Apr 2021 03:52:01 +0900 Subject: [PATCH 011/195] Use index of the supported tags to choose user lang (#15452) Fix #14793. The previous implementation used the first return value of matcher.Match, which is the chosen language tag but may contain extensions such as de-DE-u-rg-chzzzz. As mentioned in the documentation of language package, matcher.Match also returns the index of the supported tags, so I think it is better to use it rather than manipulate the returned language tag. --- modules/translation/translation.go | 16 +++++++++------- modules/web/middleware/locale.go | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/modules/translation/translation.go b/modules/translation/translation.go index dec48c7a657b2..77cc9ac7f5847 100644 --- a/modules/translation/translation.go +++ b/modules/translation/translation.go @@ -25,8 +25,9 @@ type LangType struct { } var ( - matcher language.Matcher - allLangs []LangType + matcher language.Matcher + allLangs []LangType + supportedTags []language.Tag ) // AllLangs returns all supported langauages @@ -50,12 +51,12 @@ func InitLocales() { } } - tags := make([]language.Tag, len(setting.Langs)) + supportedTags = make([]language.Tag, len(setting.Langs)) for i, lang := range setting.Langs { - tags[i] = language.Raw.Make(lang) + supportedTags[i] = language.Raw.Make(lang) } - matcher = language.NewMatcher(tags) + matcher = language.NewMatcher(supportedTags) for i := range setting.Names { key := "locale_" + setting.Langs[i] + ".ini" if err = i18n.SetMessageWithDesc(setting.Langs[i], setting.Names[i], localFiles[key]); err != nil { @@ -73,8 +74,9 @@ func InitLocales() { } // Match matches accept languages -func Match(tags ...language.Tag) (tag language.Tag, index int, c language.Confidence) { - return matcher.Match(tags...) +func Match(tags ...language.Tag) language.Tag { + _, i, _ := matcher.Match(tags...) + return supportedTags[i] } // locale represents the information of localization. diff --git a/modules/web/middleware/locale.go b/modules/web/middleware/locale.go index a08e5aaeec7a1..ede38ef933c03 100644 --- a/modules/web/middleware/locale.go +++ b/modules/web/middleware/locale.go @@ -38,7 +38,7 @@ func Locale(resp http.ResponseWriter, req *http.Request) translation.Locale { // The first element in the list is chosen to be the default language automatically. if len(lang) == 0 { tags, _, _ := language.ParseAcceptLanguage(req.Header.Get("Accept-Language")) - tag, _, _ := translation.Match(tags...) + tag := translation.Match(tags...) lang = tag.String() } From cf8f66e5dcebc07fd1975806e94b69ad557a13ed Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Wed, 14 Apr 2021 21:43:17 +0200 Subject: [PATCH 012/195] Use subdir for URL (#15446) Fixes #15444 --- templates/repo/create.tmpl | 2 +- templates/repo/issue/labels/label_list.tmpl | 2 +- templates/user/auth/activate.tmpl | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/repo/create.tmpl b/templates/repo/create.tmpl index 4aabcb6bb377b..3b76e0ace6305 100644 --- a/templates/repo/create.tmpl +++ b/templates/repo/create.tmpl @@ -10,7 +10,7 @@
{{template "base/alert" .}} -

{{.i18n.Tr "repo.new_repo_helper" "/repo/migrate" | Safe}}

+

{{.i18n.Tr "repo.new_repo_helper" (printf "%s%s" AppSubUrl "/repo/migrate") | Safe}}

{{if not .CanCreateRepo}}
diff --git a/templates/repo/issue/labels/label_list.tmpl b/templates/repo/issue/labels/label_list.tmpl index 4faec9a5ab36b..cbca86d6caafe 100644 --- a/templates/repo/issue/labels/label_list.tmpl +++ b/templates/repo/issue/labels/label_list.tmpl @@ -40,7 +40,7 @@
{{if $.PageIsOrgSettingsLabels}} - {{svg "octicon-issue-opened"}} {{$.i18n.Tr "repo.issues.label_open_issues" .NumOpenIssues}} + {{svg "octicon-issue-opened"}} {{$.i18n.Tr "repo.issues.label_open_issues" .NumOpenIssues}} {{else}} {{svg "octicon-issue-opened"}} {{$.i18n.Tr "repo.issues.label_open_issues" .NumOpenIssues}} {{end}} diff --git a/templates/user/auth/activate.tmpl b/templates/user/auth/activate.tmpl index 7f9021ae4fa2f..cc6f52b571396 100644 --- a/templates/user/auth/activate.tmpl +++ b/templates/user/auth/activate.tmpl @@ -19,7 +19,7 @@ {{end}} {{else}} {{if .NeedsPassword}} -
+
From dc5a1d617d4a7bf1da77a473cd9b251a35ba5d74 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 15 Apr 2021 05:15:28 +0800 Subject: [PATCH 013/195] Fix potential copy lfs records failure when fork a repository (#15441) --- modules/repository/fork.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/modules/repository/fork.go b/modules/repository/fork.go index cdd08e3d3c8e4..f8cb74bcb4833 100644 --- a/modules/repository/fork.go +++ b/modules/repository/fork.go @@ -64,6 +64,12 @@ func ForkRepository(doer, owner *models.User, oldRepo *models.Repository, name, return err } + // copy lfs files failure should not be ignored + if err := models.CopyLFS(ctx, repo, oldRepo); err != nil { + rollbackRemoveFn() + return err + } + repoPath := models.RepoPath(owner.Name, repo.Name) if stdout, err := git.NewCommand( "clone", "--bare", oldRepoPath, repoPath). @@ -92,6 +98,7 @@ func ForkRepository(doer, owner *models.User, oldRepo *models.Repository, name, return nil, err } + // even if below operations failed, it could be ignored. And they will be retried ctx := models.DefaultDBContext() if err = repo.UpdateSize(ctx); err != nil { log.Error("Failed to update size for repository: %v", err) @@ -100,11 +107,5 @@ func ForkRepository(doer, owner *models.User, oldRepo *models.Repository, name, log.Error("Copy language stat from oldRepo failed") } - if err := models.CopyLFS(ctx, repo, oldRepo); err != nil { - if errDelete := models.DeleteRepository(doer, owner.ID, repo.ID); errDelete != nil { - log.Error("Rollback deleteRepository: %v", errDelete) - } - return nil, err - } return repo, nil } From 61bae620c14b311ab77462b1356557f000a28ce1 Mon Sep 17 00:00:00 2001 From: zeripath Date: Thu, 15 Apr 2021 10:02:44 +0100 Subject: [PATCH 014/195] Build go-git variants for windows (#15482) It appears that there are significant performance problems with the pure git backend on windows. Therefore until we can sort this out - provide go-git backend builds. Signed-off-by: Andrew Thornton Co-authored-by: techknowlogick --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index dcfaf89a8a3e2..7bd479ff6bf10 100644 --- a/Makefile +++ b/Makefile @@ -613,6 +613,9 @@ release-windows: | $(DIST_DIRS) $(GO) install src.techknowlogick.com/xgo@latest; \ fi CGO_CFLAGS="$(CGO_CFLAGS)" xgo -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) . +ifeq (,$(findstring gogit,$(TAGS))) + CGO_CFLAGS="$(CGO_CFLAGS)" xgo -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'netgo osusergo gogit $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION)-gogit . +endif ifeq ($(CI),drone) cp /build/* $(DIST)/binaries endif From 9d07facdebffdd686108ad3b86641b85289d024b Mon Sep 17 00:00:00 2001 From: zeripath Date: Thu, 15 Apr 2021 11:03:11 +0100 Subject: [PATCH 015/195] Ensure review dismissal only dismisses the correct review (#15477) Fix #15472 Signed-off-by: Andrew Thornton art27@cantab.net --- models/review.go | 6 ++++- models/review_test.go | 60 ++++++++++++++++++++++++++++++++++++++----- 2 files changed, 58 insertions(+), 8 deletions(-) diff --git a/models/review.go b/models/review.go index 702e9634ef799..343621c0fa5ab 100644 --- a/models/review.go +++ b/models/review.go @@ -566,7 +566,11 @@ func DismissReview(review *Review, isDismiss bool) (err error) { review.Dismissed = isDismiss - _, err = x.Cols("dismissed").Update(review) + if review.ID == 0 { + return ErrReviewNotExist{} + } + + _, err = x.ID(review.ID).Cols("dismissed").Update(review) return } diff --git a/models/review_test.go b/models/review_test.go index 4f049b45e39d7..accc1841933ec 100644 --- a/models/review_test.go +++ b/models/review_test.go @@ -143,11 +143,57 @@ func TestGetReviewersByIssueID(t *testing.T) { } func TestDismissReview(t *testing.T) { - review1 := AssertExistsAndLoadBean(t, &Review{ID: 9}).(*Review) - review2 := AssertExistsAndLoadBean(t, &Review{ID: 11}).(*Review) - assert.NoError(t, DismissReview(review1, true)) - assert.NoError(t, DismissReview(review2, true)) - assert.NoError(t, DismissReview(review2, true)) - assert.NoError(t, DismissReview(review2, false)) - assert.NoError(t, DismissReview(review2, false)) + assert.NoError(t, PrepareTestDatabase()) + + rejectReviewExample := AssertExistsAndLoadBean(t, &Review{ID: 9}).(*Review) + requestReviewExample := AssertExistsAndLoadBean(t, &Review{ID: 11}).(*Review) + approveReviewExample := AssertExistsAndLoadBean(t, &Review{ID: 8}).(*Review) + assert.False(t, rejectReviewExample.Dismissed) + assert.False(t, requestReviewExample.Dismissed) + assert.False(t, approveReviewExample.Dismissed) + + assert.NoError(t, DismissReview(rejectReviewExample, true)) + rejectReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 9}).(*Review) + requestReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 11}).(*Review) + assert.True(t, rejectReviewExample.Dismissed) + assert.False(t, requestReviewExample.Dismissed) + + assert.NoError(t, DismissReview(requestReviewExample, true)) + rejectReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 9}).(*Review) + requestReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 11}).(*Review) + assert.True(t, rejectReviewExample.Dismissed) + assert.False(t, requestReviewExample.Dismissed) + assert.False(t, approveReviewExample.Dismissed) + + assert.NoError(t, DismissReview(requestReviewExample, true)) + rejectReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 9}).(*Review) + requestReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 11}).(*Review) + assert.True(t, rejectReviewExample.Dismissed) + assert.False(t, requestReviewExample.Dismissed) + assert.False(t, approveReviewExample.Dismissed) + + assert.NoError(t, DismissReview(requestReviewExample, false)) + rejectReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 9}).(*Review) + requestReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 11}).(*Review) + assert.True(t, rejectReviewExample.Dismissed) + assert.False(t, requestReviewExample.Dismissed) + assert.False(t, approveReviewExample.Dismissed) + + assert.NoError(t, DismissReview(requestReviewExample, false)) + rejectReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 9}).(*Review) + requestReviewExample = AssertExistsAndLoadBean(t, &Review{ID: 11}).(*Review) + assert.True(t, rejectReviewExample.Dismissed) + assert.False(t, requestReviewExample.Dismissed) + assert.False(t, approveReviewExample.Dismissed) + + assert.NoError(t, DismissReview(rejectReviewExample, false)) + assert.False(t, rejectReviewExample.Dismissed) + assert.False(t, requestReviewExample.Dismissed) + assert.False(t, approveReviewExample.Dismissed) + + assert.NoError(t, DismissReview(approveReviewExample, true)) + assert.False(t, rejectReviewExample.Dismissed) + assert.False(t, requestReviewExample.Dismissed) + assert.True(t, approveReviewExample.Dismissed) + } From f7830041f4538633362de6607bbfc8c5f645f0a7 Mon Sep 17 00:00:00 2001 From: silverwind Date: Thu, 15 Apr 2021 13:02:34 +0200 Subject: [PATCH 016/195] Make build scripts compatible with node 12 (#15479) * Make build scripts compatible with node 12 "fs/promises" is not in node 12, use a more compatible way to import it. Also, lock major down versions of the image build dependencies to prevent future surprises. * add node_modules dependency --- Makefile | 4 ++-- build/generate-images.js | 3 ++- build/generate-svg.js | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 7bd479ff6bf10..2cd3d383be575 100644 --- a/Makefile +++ b/Makefile @@ -736,8 +736,8 @@ generate-gitignore: GO111MODULE=on $(GO) run build/generate-gitignores.go .PHONY: generate-images -generate-images: - npm install --no-save --no-package-lock fabric imagemin-zopfli +generate-images: | node_modules + npm install --no-save --no-package-lock fabric@4 imagemin-zopfli@7 node build/generate-images.js $(TAGS) .PHONY: generate-manpage diff --git a/build/generate-images.js b/build/generate-images.js index d82f21564f68e..b6616810a76d5 100755 --- a/build/generate-images.js +++ b/build/generate-images.js @@ -1,10 +1,11 @@ import imageminZopfli from 'imagemin-zopfli'; import {optimize, extendDefaultPlugins} from 'svgo'; import {fabric} from 'fabric'; -import {readFile, writeFile} from 'fs/promises'; +import fs from 'fs'; import {resolve, dirname} from 'path'; import {fileURLToPath} from 'url'; +const {readFile, writeFile} = fs.promises; const __dirname = dirname(fileURLToPath(import.meta.url)); const logoFile = resolve(__dirname, '../assets/logo.svg'); diff --git a/build/generate-svg.js b/build/generate-svg.js index 3e14f906e30df..72c3be3cbdb76 100755 --- a/build/generate-svg.js +++ b/build/generate-svg.js @@ -1,9 +1,10 @@ import fastGlob from 'fast-glob'; import {optimize, extendDefaultPlugins} from 'svgo'; import {resolve, parse, dirname} from 'path'; -import {readFile, writeFile, mkdir} from 'fs/promises'; +import fs from 'fs'; import {fileURLToPath} from 'url'; +const {readFile, writeFile, mkdir} = fs.promises; const __dirname = dirname(fileURLToPath(import.meta.url)); const glob = (pattern) => fastGlob.sync(pattern, {cwd: resolve(__dirname), absolute: true}); const outputDir = resolve(__dirname, '../public/img/svg'); From 217b5c150f85b5dc1786383ae99a07dab45a6c97 Mon Sep 17 00:00:00 2001 From: zeripath Date: Thu, 15 Apr 2021 13:22:04 +0100 Subject: [PATCH 017/195] Query the DB for the hash before inserting in to email_hash (#15457) Some postgres users have logging which logs even failed transactions. So just query the db before trying to insert. Fix #15451 Signed-off-by: Andrew Thornton art27@cantab.net Co-authored-by: Lunny Xiao --- models/avatar.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/models/avatar.go b/models/avatar.go index 166ca337ca262..ad1e19d0d7cd0 100644 --- a/models/avatar.go +++ b/models/avatar.go @@ -96,6 +96,11 @@ func HashedAvatarLink(email string) string { // we don't care about any DB problem just return the lowerEmail return lowerEmail, nil } + has, err := sess.Where("email = ? AND hash = ?", emailHash.Email, emailHash.Hash).Get(new(EmailHash)) + if has || err != nil { + // Seriously we don't care about any DB problems just return the lowerEmail - we expect the transaction to fail most of the time + return lowerEmail, nil + } _, _ = sess.Insert(emailHash) if err := sess.Commit(); err != nil { // Seriously we don't care about any DB problems just return the lowerEmail - we expect the transaction to fail most of the time From 2a42d80d14e97fe962658bbcb3d2341571f2afcf Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Thu, 15 Apr 2021 15:34:22 +0200 Subject: [PATCH 018/195] migration: github: if rate limit is not enabled, ignore it (#15490) --- modules/migrations/github.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/migrations/github.go b/modules/migrations/github.go index cb61086b2ad6b..282e3b4786151 100644 --- a/modules/migrations/github.go +++ b/modules/migrations/github.go @@ -132,6 +132,11 @@ func (g *GithubDownloaderV3) sleep() { func (g *GithubDownloaderV3) RefreshRate() error { rates, _, err := g.client.RateLimits(g.ctx) if err != nil { + // if rate limit is not enabled, ignore it + if strings.Contains(err.Error(), "404") { + g.rate = nil + return nil + } return err } From af2adb4e35ea71ca5c7fbb1e51b9d25fe49af2e4 Mon Sep 17 00:00:00 2001 From: firesoft-de <34716031+firesoft-de@users.noreply.github.com> Date: Thu, 15 Apr 2021 18:06:32 +0200 Subject: [PATCH 019/195] Specify relation between multiple signing options (#15496) --- docs/content/doc/advanced/signing.en-us.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/content/doc/advanced/signing.en-us.md b/docs/content/doc/advanced/signing.en-us.md index 926f6b1f4c6f6..71af33fad8766 100644 --- a/docs/content/doc/advanced/signing.en-us.md +++ b/docs/content/doc/advanced/signing.en-us.md @@ -109,7 +109,7 @@ when creating a repository. The possible values are: - `always`: Always sign Options other than `never` and `always` can be combined as a comma -separated list. +separated list. The commit will be signed if all selected options are true. ### `WIKI` @@ -123,7 +123,7 @@ The possible values are: - `always`: Always sign Options other than `never` and `always` can be combined as a comma -separated list. +separated list. The commit will be signed if all selected options are true. ### `CRUD_ACTIONS` @@ -137,7 +137,7 @@ editor or API CRUD actions. The possible values are: - `always`: Always sign Options other than `never` and `always` can be combined as a comma -separated list. +separated list. The change will be signed if all selected options are true. ### `MERGES` @@ -154,7 +154,7 @@ The possible options are: - `always`: Always sign Options other than `never` and `always` can be combined as a comma -separated list. +separated list. The merge will be signed if all selected options are true. ## Obtaining the Public Key of the Signing Key From f44543a1bb776fa8bdfd3b605d67197d1466eb20 Mon Sep 17 00:00:00 2001 From: Kyle D Date: Thu, 15 Apr 2021 10:53:57 -0600 Subject: [PATCH 020/195] Disable Stars config option (#14653) * Add config option to disable stars * Replace "stars" with watched in user profile * Add documentation --- custom/conf/app.example.ini | 2 ++ .../doc/advanced/config-cheat-sheet.en-us.md | 1 + models/repo_list.go | 6 +++++ modules/context/context.go | 1 + modules/setting/repository.go | 2 ++ modules/structs/settings.go | 1 + options/locale/locale_en-US.ini | 1 + routers/api/v1/settings/settings.go | 1 + routers/user/profile.go | 21 ++++++++++++++++ templates/base/head_navbar.tmpl | 10 ++++---- templates/explore/repo_list.tmpl | 4 +++- templates/explore/repo_search.tmpl | 6 +++-- templates/repo/header.tmpl | 24 ++++++++++--------- templates/swagger/v1_json.tmpl | 4 ++++ templates/user/dashboard/repolist.tmpl | 10 ++++---- templates/user/profile.tmpl | 16 +++++++++---- 16 files changed, 83 insertions(+), 27 deletions(-) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 4fdc4b0214d1d..35f1bfaeabf2a 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -70,6 +70,8 @@ PREFIX_ARCHIVE_FILES = true 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 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 9bafee846f87f..088b56fedd312 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -75,6 +75,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. - `PREFIX_ARCHIVE_FILES`: **true**: Prefix archive files by placing them in a directory named after the repository. - `DISABLE_MIRRORS`: **false**: Disable the creation of **new** mirrors. Pre-existing mirrors remain valid. - `DISABLE_MIGRATIONS`: **false**: Disable migrating feature. +- `DISABLE_STARS`: **false**: Disable stars feature. - `DEFAULT_BRANCH`: **master**: Default branch name of all repositories. - `ALLOW_ADOPTION_OF_UNADOPTED_REPOSITORIES`: **false**: Allow non-admin users to adopt unadopted repositories - `ALLOW_DELETION_OF_UNADOPTED_REPOSITORIES`: **false**: Allow non-admin users to delete unadopted repositories diff --git a/models/repo_list.go b/models/repo_list.go index 74bc256190df5..b4a6d9e438fc2 100644 --- a/models/repo_list.go +++ b/models/repo_list.go @@ -143,6 +143,7 @@ type SearchRepoOptions struct { OrderBy SearchOrderBy Private bool // Include private repositories in results StarredByID int64 + WatchedByID int64 AllPublic bool // Include also all public repositories of users and public organisations AllLimited bool // Include also all public repositories of limited organisations // None -> include public and private @@ -241,6 +242,11 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond { cond = cond.And(builder.In("id", builder.Select("repo_id").From("star").Where(builder.Eq{"uid": opts.StarredByID}))) } + // Restrict to watched repositories + if opts.WatchedByID > 0 { + cond = cond.And(builder.In("id", builder.Select("repo_id").From("watch").Where(builder.Eq{"user_id": opts.WatchedByID}))) + } + // Restrict repositories to those the OwnerID owns or contributes to as per opts.Collaborate if opts.OwnerID > 0 { accessCond := builder.NewCond() diff --git a/modules/context/context.go b/modules/context/context.go index b876487d5e004..523499aa610d4 100644 --- a/modules/context/context.go +++ b/modules/context/context.go @@ -704,6 +704,7 @@ func Contexter() func(next http.Handler) http.Handler { ctx.Data["EnableSwagger"] = setting.API.EnableSwagger ctx.Data["EnableOpenIDSignIn"] = setting.Service.EnableOpenIDSignIn ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations + ctx.Data["DisableStars"] = setting.Repository.DisableStars ctx.Data["ManifestData"] = setting.ManifestData diff --git a/modules/setting/repository.go b/modules/setting/repository.go index 139512bf008c1..a6fc73651a305 100644 --- a/modules/setting/repository.go +++ b/modules/setting/repository.go @@ -43,6 +43,7 @@ var ( PrefixArchiveFiles bool DisableMirrors bool DisableMigrations bool + DisableStars bool `ini:"DISABLE_STARS"` DefaultBranch string AllowAdoptionOfUnadoptedRepositories bool AllowDeleteOfUnadoptedRepositories bool @@ -154,6 +155,7 @@ var ( PrefixArchiveFiles: true, DisableMirrors: false, DisableMigrations: false, + DisableStars: false, DefaultBranch: "master", // Repository editor settings diff --git a/modules/structs/settings.go b/modules/structs/settings.go index e15c750356f68..842b12792d1d9 100644 --- a/modules/structs/settings.go +++ b/modules/structs/settings.go @@ -9,6 +9,7 @@ type GeneralRepoSettings struct { MirrorsDisabled bool `json:"mirrors_disabled"` HTTPGitDisabled bool `json:"http_git_disabled"` MigrationsDisabled bool `json:"migrations_disabled"` + StarsDisabled bool `json:"stars_disabled"` TimeTrackingDisabled bool `json:"time_tracking_disabled"` LFSDisabled bool `json:"lfs_disabled"` } diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index c2f835a98c0d2..1a8d253749cf5 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -421,6 +421,7 @@ repositories = Repositories activity = Public Activity followers = Followers starred = Starred Repositories +watched = Watched Repositories projects = Projects following = Following follow = Follow diff --git a/routers/api/v1/settings/settings.go b/routers/api/v1/settings/settings.go index cfb059a3acce8..e6417e40748c0 100644 --- a/routers/api/v1/settings/settings.go +++ b/routers/api/v1/settings/settings.go @@ -60,6 +60,7 @@ func GetGeneralRepoSettings(ctx *context.APIContext) { MirrorsDisabled: setting.Repository.DisableMirrors, HTTPGitDisabled: setting.Repository.DisableHTTPGit, MigrationsDisabled: setting.Repository.DisableMigrations, + StarsDisabled: setting.Repository.DisableStars, TimeTrackingDisabled: !setting.Service.EnableTimetracking, LFSDisabled: !setting.LFS.StartServer, }) diff --git a/routers/user/profile.go b/routers/user/profile.go index 40619aaf0f082..c24614b108fc2 100644 --- a/routers/user/profile.go +++ b/routers/user/profile.go @@ -238,6 +238,27 @@ func Profile(ctx *context.Context) { ctx.ServerError("GetProjects", err) return } + case "watching": + repos, count, err = models.SearchRepository(&models.SearchRepoOptions{ + ListOptions: models.ListOptions{ + PageSize: setting.UI.User.RepoPagingNum, + Page: page, + }, + Actor: ctx.User, + Keyword: keyword, + OrderBy: orderBy, + Private: ctx.IsSigned, + WatchedByID: ctxUser.ID, + Collaborate: util.OptionalBoolFalse, + TopicOnly: topicOnly, + IncludeDescription: setting.UI.SearchRepoDescription, + }) + if err != nil { + ctx.ServerError("SearchRepository", err) + return + } + + total = int(count) default: repos, count, err = models.SearchRepository(&models.SearchRepoOptions{ ListOptions: models.ListOptions{ diff --git a/templates/base/head_navbar.tmpl b/templates/base/head_navbar.tmpl index ec5c17b6b45d1..7e476fa4a48f4 100644 --- a/templates/base/head_navbar.tmpl +++ b/templates/base/head_navbar.tmpl @@ -157,10 +157,12 @@ {{svg "octicon-person"}} {{.i18n.Tr "your_profile"}} - - {{svg "octicon-star"}} - {{.i18n.Tr "your_starred"}} - + {{if not .DisableStars}} + + {{svg "octicon-star"}} + {{.i18n.Tr "your_starred"}} + + {{end}} {{svg "octicon-tools"}} {{.i18n.Tr "your_settings"}} diff --git a/templates/explore/repo_list.tmpl b/templates/explore/repo_list.tmpl index 91dc3d8bf40a7..4ff48b60761d8 100644 --- a/templates/explore/repo_list.tmpl +++ b/templates/explore/repo_list.tmpl @@ -42,7 +42,9 @@ {{if .PrimaryLanguage }} {{ .PrimaryLanguage.Language }} {{end}} - {{svg "octicon-star" 16 "mr-3"}}{{.NumStars}} + {{if not $.DisableStars}} + {{svg "octicon-star" 16 "mr-3"}}{{.NumStars}} + {{end}} {{svg "octicon-git-branch" 16 "mr-3"}}{{.NumForks}}
diff --git a/templates/explore/repo_search.tmpl b/templates/explore/repo_search.tmpl index 5e7bed8b31832..c1745525a9f53 100644 --- a/templates/explore/repo_search.tmpl +++ b/templates/explore/repo_search.tmpl @@ -12,8 +12,10 @@ {{.i18n.Tr "repo.issues.label.filter_sort.reverse_alphabetically"}} {{.i18n.Tr "repo.issues.filter_sort.recentupdate"}} {{.i18n.Tr "repo.issues.filter_sort.leastupdate"}} - {{.i18n.Tr "repo.issues.filter_sort.moststars"}} - {{.i18n.Tr "repo.issues.filter_sort.feweststars"}} + {{if not .DisableStars}} + {{.i18n.Tr "repo.issues.filter_sort.moststars"}} + {{.i18n.Tr "repo.issues.filter_sort.feweststars"}} + {{end}} {{.i18n.Tr "repo.issues.filter_sort.mostforks"}} {{.i18n.Tr "repo.issues.filter_sort.fewestforks"}}
diff --git a/templates/repo/header.tmpl b/templates/repo/header.tmpl index 188fc87b6a71c..ebd0333e8ca5e 100644 --- a/templates/repo/header.tmpl +++ b/templates/repo/header.tmpl @@ -71,17 +71,19 @@ -
- {{$.CsrfTokenHtml}} -
- - - {{CountFmt .NumStars}} - -
-
+ {{if not $.DisableStars}} +
+ {{$.CsrfTokenHtml}} +
+ + + {{CountFmt .NumStars}} + +
+
+ {{end}} {{if and (not .IsEmpty) ($.Permission.CanRead $.UnitTypeCode)}} -
- ${repo.stars_count} - {{svg "octicon-star" 16 "ml-2"}} -
+ {{if not .DisableStars}} +
+ ${repo.stars_count} + {{svg "octicon-star" 16 "ml-2"}} +
+ {{end}}
diff --git a/templates/user/profile.tmpl b/templates/user/profile.tmpl index ddad4c46c3511..18f3c9f6ddecd 100644 --- a/templates/user/profile.tmpl +++ b/templates/user/profile.tmpl @@ -84,16 +84,22 @@