Skip to content

Commit 913bcba

Browse files
authored
Merge branch 'main' into lunny/use_withtx_if_possible
2 parents 5e7df07 + f201dde commit 913bcba

File tree

13 files changed

+150
-72
lines changed

13 files changed

+150
-72
lines changed

modules/session/mem.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package session
5+
6+
import (
7+
"bytes"
8+
"encoding/gob"
9+
"net/http"
10+
11+
"gitea.com/go-chi/session"
12+
)
13+
14+
type mockMemRawStore struct {
15+
s *session.MemStore
16+
}
17+
18+
var _ session.RawStore = (*mockMemRawStore)(nil)
19+
20+
func (m *mockMemRawStore) Set(k, v any) error {
21+
// We need to use gob to encode the value, to make it have the same behavior as other stores and catch abuses.
22+
// Because gob needs to "Register" the type before it can encode it, and it's unable to decode a struct to "any" so use a map to help to decode the value.
23+
var buf bytes.Buffer
24+
if err := gob.NewEncoder(&buf).Encode(map[string]any{"v": v}); err != nil {
25+
return err
26+
}
27+
return m.s.Set(k, buf.Bytes())
28+
}
29+
30+
func (m *mockMemRawStore) Get(k any) (ret any) {
31+
v, ok := m.s.Get(k).([]byte)
32+
if !ok {
33+
return nil
34+
}
35+
var w map[string]any
36+
_ = gob.NewDecoder(bytes.NewBuffer(v)).Decode(&w)
37+
return w["v"]
38+
}
39+
40+
func (m *mockMemRawStore) Delete(k any) error {
41+
return m.s.Delete(k)
42+
}
43+
44+
func (m *mockMemRawStore) ID() string {
45+
return m.s.ID()
46+
}
47+
48+
func (m *mockMemRawStore) Release() error {
49+
return m.s.Release()
50+
}
51+
52+
func (m *mockMemRawStore) Flush() error {
53+
return m.s.Flush()
54+
}
55+
56+
type mockMemStore struct {
57+
*mockMemRawStore
58+
}
59+
60+
var _ Store = (*mockMemStore)(nil)
61+
62+
func (m mockMemStore) Destroy(writer http.ResponseWriter, request *http.Request) error {
63+
return nil
64+
}
65+
66+
func NewMockMemStore(sid string) Store {
67+
return &mockMemStore{&mockMemRawStore{session.NewMemStore(sid)}}
68+
}

modules/session/mock.go

Lines changed: 0 additions & 26 deletions
This file was deleted.

modules/session/store.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,34 +11,34 @@ import (
1111
"gitea.com/go-chi/session"
1212
)
1313

14-
// Store represents a session store
14+
type RawStore = session.RawStore
15+
1516
type Store interface {
16-
Get(any) any
17-
Set(any, any) error
18-
Delete(any) error
19-
ID() string
20-
Release() error
21-
Flush() error
17+
RawStore
2218
Destroy(http.ResponseWriter, *http.Request) error
2319
}
2420

21+
type mockStoreContextKeyStruct struct{}
22+
23+
var MockStoreContextKey = mockStoreContextKeyStruct{}
24+
2525
// RegenerateSession regenerates the underlying session and returns the new store
2626
func RegenerateSession(resp http.ResponseWriter, req *http.Request) (Store, error) {
2727
for _, f := range BeforeRegenerateSession {
2828
f(resp, req)
2929
}
3030
if setting.IsInTesting {
31-
if store, ok := req.Context().Value(MockStoreContextKey).(*MockStore); ok {
32-
return store, nil
31+
if store := req.Context().Value(MockStoreContextKey); store != nil {
32+
return store.(Store), nil
3333
}
3434
}
3535
return session.RegenerateSession(resp, req)
3636
}
3737

3838
func GetContextSession(req *http.Request) Store {
3939
if setting.IsInTesting {
40-
if store, ok := req.Context().Value(MockStoreContextKey).(*MockStore); ok {
41-
return store
40+
if store := req.Context().Value(MockStoreContextKey); store != nil {
41+
return store.(Store)
4242
}
4343
}
4444
return session.GetSession(req)

modules/session/virtual.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ type VirtualSessionProvider struct {
2222
provider session.Provider
2323
}
2424

25-
// Init initializes the cookie session provider with given root path.
26-
func (o *VirtualSessionProvider) Init(gclifetime int64, config string) error {
25+
// Init initializes the cookie session provider with the given config.
26+
func (o *VirtualSessionProvider) Init(gcLifetime int64, config string) error {
2727
var opts session.Options
2828
if err := json.Unmarshal([]byte(config), &opts); err != nil {
2929
return err
@@ -52,7 +52,7 @@ func (o *VirtualSessionProvider) Init(gclifetime int64, config string) error {
5252
default:
5353
return fmt.Errorf("VirtualSessionProvider: Unknown Provider: %s", opts.Provider)
5454
}
55-
return o.provider.Init(gclifetime, opts.ProviderConfig)
55+
return o.provider.Init(gcLifetime, opts.ProviderConfig)
5656
}
5757

5858
// Read returns raw session store by session ID.

options/locale/locale_zh-CN.ini

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ unpin=取消置顶
130130

131131
artifacts=产物
132132
expired=已过期
133+
confirm_delete_artifact=您确定要删除产物「%s」吗?
133134

134135
archived=已归档
135136

@@ -459,6 +460,7 @@ oauth_signin_submit=绑定账号
459460
oauth.signin.error.general=处理授权请求时出错:%s。如果此错误仍然存在,请与站点管理员联系。
460461
oauth.signin.error.access_denied=授权请求被拒绝。
461462
oauth.signin.error.temporarily_unavailable=授权失败,因为认证服务器暂时不可用。请稍后再试。
463+
oauth_callback_unable_auto_reg=自动注册已启用,但 OAuth2 提供商 %[1]s 返回缺失的字段:%[2]s,无法自动创建帐户,请创建或链接到一个帐户,或联系站点管理员。
462464
openid_connect_submit=连接
463465
openid_connect_title=连接到现有的帐户
464466
openid_connect_desc=所选的 OpenID URI 未知。在这里关联一个新帐户。
@@ -499,6 +501,7 @@ activate_email.text=请在 <b>%s</b> 时间内,点击以下链接,以验证
499501

500502
register_notify=欢迎来到 %s
501503
register_notify.title=%[1]s,欢迎来到 %[2]s
504+
register_notify.text_1=这是您的 %s 注册确认邮件 !
502505
register_notify.text_3=如果此账户已为您创建,请先 <a href="%s">设置您的密码</a>。
503506

504507
reset_password=恢复您的账户
@@ -723,6 +726,7 @@ webauthn=两步验证(安全密钥)
723726
public_profile=公开信息
724727
biography_placeholder=告诉我们一点您自己! (您可以使用 Markdown)
725728
location_placeholder=与他人分享您的大概位置
729+
profile_desc=控制您的个人资料对其他用户的显示方式。您的主邮箱地址将用于通知、密码恢复和基于网页的 Git 操作。
726730
password_username_disabled=您不被允许更改您的用户名。更多详情请联系您的系统管理员。
727731
password_full_name_disabled=您不被允许更改您的全名。请联系您的站点管理员了解更多详情。
728732
full_name=自定义名称
@@ -801,6 +805,7 @@ activations_pending=等待激活
801805
can_not_add_email_activations_pending=有一个待处理的激活请求,请稍等几分钟后再尝试添加新的邮箱地址。
802806
delete_email=移除
803807
email_deletion=移除邮箱地址
808+
email_deletion_desc=邮箱地址和相关信息将会被删除。使用此邮箱地址发送的Git提交将会保留,继续?
804809
email_deletion_success=您的邮箱地址已移除。
805810
theme_update_success=您的主题已更新。
806811
theme_update_error=所选主题不存在。
@@ -986,6 +991,7 @@ webauthn_alternative_tip=您可能想要配置额外的身份验证方法。
986991

987992
manage_account_links=管理绑定过的账号
988993
manage_account_links_desc=这些外部帐户已经绑定到您的 Gitea 帐户。
994+
account_links_not_available=当前没有外部帐户链接到您的 Gitea 帐户。
989995
link_account=链接账户
990996
remove_account_link=删除已绑定的账号
991997
remove_account_link_desc=删除已绑定帐户将吊销其对您的 Gitea 帐户的访问权限。继续?
@@ -1008,6 +1014,8 @@ email_notifications.onmention=仅被提及时通知
10081014
email_notifications.disable=停用邮件通知
10091015
email_notifications.submit=设置邮件通知
10101016
email_notifications.andyourown=仅与您相关的通知
1017+
email_notifications.actions.desc=设置了 <a target="_blank" href="%s">Gitea 工作流</a> 的仓库中工作流运行的通知。
1018+
email_notifications.actions.failure_only=仅在工作流运行失败时通知
10111019

10121020
visibility=用户可见性
10131021
visibility.public=公开
@@ -1022,6 +1030,8 @@ new_repo_helper=代码仓库包含了所有的项目文件,包括版本历史
10221030
owner=拥有者
10231031
owner_helper=由于最大仓库数量限制,一些组织可能不会显示在下拉列表中。
10241032
repo_name=仓库名称
1033+
repo_name_profile_public_hint=.profile 是一个特殊的仓库,您可以使用它将 README.md 添加到您的公共组织资料中,任何人都可以看到。请确保它是公开的,并使用个人资料目录中的 README 对其进行初始化以开始使用。
1034+
repo_name_profile_private_hint=.profile-private 是一个特殊的仓库,您可以使用它向您的组织成员个人资料添加 README.md,仅对组织成员可见。请确保它是私有的,并使用个人资料目录中的 README 对其进行初始化以开始使用。
10251035
repo_name_helper=理想的仓库名称应由简短、有意义和独特的关键词组成。「.profile」和「.profile-private」可用于为用户/组织添加 README.md。
10261036
repo_size=仓库大小
10271037
template=模板
@@ -1380,6 +1390,8 @@ editor.failed_to_commit=提交更改失败。
13801390
editor.failed_to_commit_summary=错误信息:
13811391

13821392
editor.fork_create=派生仓库以请求变更
1393+
editor.fork_create_description=您不能直接编辑此仓库。您可以派生此仓库,进行编辑并创建一个合并请求。
1394+
editor.fork_edit_description=您不能直接编辑此仓库。 更改将写入您的派生仓库 <b>%s</b>,以便您可以创建一个合并请求。
13831395
editor.fork_not_editable=您已经派生了此仓库,但您的派生是不可编辑的。
13841396
editor.fork_failed_to_push_branch=推送分支 %s 到仓库失败。
13851397
editor.fork_branch_exists=分支「%s」已存在于您的派生仓库中,请选择一个新的分支名称。
@@ -2795,6 +2807,7 @@ team_permission_desc=权限
27952807
team_unit_desc=允许访问仓库单元
27962808
team_unit_disabled=(已禁用)
27972809

2810+
form.name_been_taken=组织名称「%s」已经被占用。
27982811
form.name_reserved=组织名称「%s」是保留的。
27992812
form.name_pattern_not_allowed=组织名中不允许使用「%s」格式。
28002813
form.create_org_not_allowed=此账号禁止创建组织
@@ -2836,6 +2849,7 @@ settings.delete_notices_2=此操作将永久删除 <strong>%s</strong> 的所有
28362849
settings.delete_notices_3=此操作将永久删除 <strong>%s</strong> 的所有 <strong>软件包</strong>。
28372850
settings.delete_notices_4=此操作将永久删除 <strong>%s</strong> 的所有 <strong>项目</strong>。
28382851
settings.confirm_delete_account=确认删除组织
2852+
settings.delete_failed=由于内部错误,删除组织失败
28392853
settings.delete_successful=组织 <b>%s</b> 已成功删除。
28402854
settings.hooks_desc=在此处添加的 Web 钩子将会应用到该组织下的 <strong>所有仓库</strong>。
28412855

@@ -2985,6 +2999,8 @@ dashboard.resync_all_sshprincipals=使用 Gitea 的 SSH 规则更新「.ssh/auth
29852999
dashboard.resync_all_hooks=重新同步所有仓库的 pre-receive、update 和 post-receive 钩子
29863000
dashboard.reinit_missing_repos=重新初始化所有丢失的 Git 仓库存在的记录
29873001
dashboard.sync_external_users=同步外部用户数据
3002+
dashboard.cleanup_hook_task_table=清理 hook_task 表
3003+
dashboard.cleanup_actions=清理过期的工作流资源
29883004
dashboard.server_uptime=服务运行时间
29893005
dashboard.current_goroutine=当前 Goroutines 数量
29903006
dashboard.current_memory_usage=当前内存使用量
@@ -3215,6 +3231,8 @@ auths.oauth2_required_claim_name_helper=设置此名称,只有具有此名称
32153231
auths.oauth2_required_claim_value=必须填写 Claim 声明的值
32163232
auths.oauth2_required_claim_value_helper=设置此值,只有拥有对应的声明(Claim)的名称和值的用户才被允许从此源登录
32173233
auths.oauth2_group_claim_name=用于提供用户组名称的 Claim 声明名称。(可选)
3234+
auths.oauth2_full_name_claim_name=全名声明名称。(可选,如果设置,用户的全名将始终与此声明同步)
3235+
auths.oauth2_ssh_public_key_claim_name=SSH 公钥声明名称
32183236
auths.oauth2_admin_group=管理员用户组的 Claim 声明值。(可选 - 需要上面的声明名称)
32193237
auths.oauth2_restricted_group=受限用户组的 Claim 声明值。(可选 - 需要上面的声明名称)
32203238
auths.oauth2_map_group_to_team=映射声明的组到组织团队。(可选 - 要求在上面填写声明的名字)
@@ -3690,6 +3708,7 @@ owner.settings.cargo.initialize.success=Cargo 索引已经成功创建。
36903708
owner.settings.cargo.rebuild=重建索引
36913709
owner.settings.cargo.rebuild.description=如果索引与存储的 Cargo 包不同步,重建可能会有用。
36923710
owner.settings.cargo.rebuild.error=无法重建 Cargo 索引: %v
3711+
owner.settings.cargo.rebuild.success=Cargo 索引已成功重建。
36933712
owner.settings.cleanuprules.title=管理清理规则
36943713
owner.settings.cleanuprules.add=添加清理规则
36953714
owner.settings.cleanuprules.edit=编辑清理规则

routers/web/auth/auth.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -565,7 +565,7 @@ func createUserInContext(ctx *context.Context, tpl templates.TplName, form any,
565565
oauth2LinkAccount(ctx, user, possibleLinkAccountData, true)
566566
return false // user is already created here, all redirects are handled
567567
case setting.OAuth2AccountLinkingLogin:
568-
showLinkingLogin(ctx, &possibleLinkAccountData.AuthSource, possibleLinkAccountData.GothUser)
568+
showLinkingLogin(ctx, possibleLinkAccountData.AuthSourceID, possibleLinkAccountData.GothUser)
569569
return false // user will be created only after linking login
570570
}
571571
}
@@ -633,7 +633,7 @@ func handleUserCreated(ctx *context.Context, u *user_model.User, possibleLinkAcc
633633

634634
// update external user information
635635
if possibleLinkAccountData != nil {
636-
if err := externalaccount.EnsureLinkExternalToUser(ctx, possibleLinkAccountData.AuthSource.ID, u, possibleLinkAccountData.GothUser); err != nil {
636+
if err := externalaccount.EnsureLinkExternalToUser(ctx, possibleLinkAccountData.AuthSourceID, u, possibleLinkAccountData.GothUser); err != nil {
637637
log.Error("EnsureLinkExternalToUser failed: %v", err)
638638
}
639639
}

routers/web/auth/auth_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,14 @@ func TestUserLogin(t *testing.T) {
6464
func TestSignUpOAuth2Login(t *testing.T) {
6565
defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)()
6666

67+
_ = oauth2.Init(t.Context())
6768
addOAuth2Source(t, "dummy-auth-source", oauth2.Source{})
6869

6970
t.Run("OAuth2MissingField", func(t *testing.T) {
7071
defer test.MockVariableValue(&gothic.CompleteUserAuth, func(res http.ResponseWriter, req *http.Request) (goth.User, error) {
7172
return goth.User{Provider: "dummy-auth-source", UserID: "dummy-user"}, nil
7273
})()
73-
mockOpt := contexttest.MockContextOption{SessionStore: session.NewMockStore("dummy-sid")}
74+
mockOpt := contexttest.MockContextOption{SessionStore: session.NewMockMemStore("dummy-sid")}
7475
ctx, resp := contexttest.MockContext(t, "/user/oauth2/dummy-auth-source/callback?code=dummy-code", mockOpt)
7576
ctx.SetPathParam("provider", "dummy-auth-source")
7677
SignInOAuthCallback(ctx)
@@ -84,7 +85,7 @@ func TestSignUpOAuth2Login(t *testing.T) {
8485
})
8586

8687
t.Run("OAuth2CallbackError", func(t *testing.T) {
87-
mockOpt := contexttest.MockContextOption{SessionStore: session.NewMockStore("dummy-sid")}
88+
mockOpt := contexttest.MockContextOption{SessionStore: session.NewMockMemStore("dummy-sid")}
8889
ctx, resp := contexttest.MockContext(t, "/user/oauth2/dummy-auth-source/callback", mockOpt)
8990
ctx.SetPathParam("provider", "dummy-auth-source")
9091
SignInOAuthCallback(ctx)

routers/web/auth/linkaccount.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ func LinkAccountPostSignIn(ctx *context.Context) {
170170
}
171171

172172
func oauth2LinkAccount(ctx *context.Context, u *user_model.User, linkAccountData *LinkAccountData, remember bool) {
173-
oauth2SignInSync(ctx, &linkAccountData.AuthSource, u, linkAccountData.GothUser)
173+
oauth2SignInSync(ctx, linkAccountData.AuthSourceID, u, linkAccountData.GothUser)
174174
if ctx.Written() {
175175
return
176176
}
@@ -185,7 +185,7 @@ func oauth2LinkAccount(ctx *context.Context, u *user_model.User, linkAccountData
185185
return
186186
}
187187

188-
err = externalaccount.LinkAccountToUser(ctx, linkAccountData.AuthSource.ID, u, linkAccountData.GothUser)
188+
err = externalaccount.LinkAccountToUser(ctx, linkAccountData.AuthSourceID, u, linkAccountData.GothUser)
189189
if err != nil {
190190
ctx.ServerError("UserLinkAccount", err)
191191
return
@@ -295,7 +295,7 @@ func LinkAccountPostRegister(ctx *context.Context) {
295295
Email: form.Email,
296296
Passwd: form.Password,
297297
LoginType: auth.OAuth2,
298-
LoginSource: linkAccountData.AuthSource.ID,
298+
LoginSource: linkAccountData.AuthSourceID,
299299
LoginName: linkAccountData.GothUser.UserID,
300300
}
301301

@@ -304,7 +304,12 @@ func LinkAccountPostRegister(ctx *context.Context) {
304304
return
305305
}
306306

307-
source := linkAccountData.AuthSource.Cfg.(*oauth2.Source)
307+
authSource, err := auth.GetSourceByID(ctx, linkAccountData.AuthSourceID)
308+
if err != nil {
309+
ctx.ServerError("GetSourceByID", err)
310+
return
311+
}
312+
source := authSource.Cfg.(*oauth2.Source)
308313
if err := syncGroupsToTeams(ctx, source, &linkAccountData.GothUser, u); err != nil {
309314
ctx.ServerError("SyncGroupsToTeams", err)
310315
return
@@ -318,5 +323,5 @@ func linkAccountFromContext(ctx *context.Context, user *user_model.User) error {
318323
if linkAccountData == nil {
319324
return errors.New("not in LinkAccount session")
320325
}
321-
return externalaccount.LinkAccountToUser(ctx, linkAccountData.AuthSource.ID, user, linkAccountData.GothUser)
326+
return externalaccount.LinkAccountToUser(ctx, linkAccountData.AuthSourceID, user, linkAccountData.GothUser)
322327
}

0 commit comments

Comments
 (0)