Compare commits

..

2 Commits

Author SHA1 Message Date
GiteaBot
89d11314f9 [skip ci] Updated translations via Crowdin 2026-06-16 01:27:50 +00:00
Giteabot
8ff8422307 chore(deps): update module github.com/go-swagger/go-swagger to v0.34.1 (#38122)
This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
|
[github.com/go-swagger/go-swagger](https://redirect.github.com/go-swagger/go-swagger)
| `v0.34.0` → `v0.34.1` |
![age](https://developer.mend.io/api/mc/badges/age/go/github.com%2fgo-swagger%2fgo-swagger/v0.34.1?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/go/github.com%2fgo-swagger%2fgo-swagger/v0.34.0/v0.34.1?slim=true)
|

---

### Release Notes

<details>
<summary>go-swagger/go-swagger
(github.com/go-swagger/go-swagger)</summary>

###
[`v0.34.1`](https://redirect.github.com/go-swagger/go-swagger/releases/tag/v0.34.1)

[Compare
Source](https://redirect.github.com/go-swagger/go-swagger/compare/v0.34.0...v0.34.1)

go-swagger release 0.34.1

***

Released on 2026 Jun 05

#####
[0.34.1](https://redirect.github.com/go-swagger/go-swagger/tree/v0.34.1)
- 2026-06-05

Fixed regression on initialisms (codegen)

**Full Changelog**:
<https://github.com/go-swagger/go-swagger/compare/v0.34.0...v0.34.1>

7 commits in this release.

***

##### <!-- 01 -->Fixed bugs

- fix(codegen): fixed regression from v0.34.0 : initialisms skipped by
[@&#8203;fredbi](https://redirect.github.com/fredbi) in
[#&#8203;3360](https://redirect.github.com/go-swagger/go-swagger/pull/3360)
[...](e5c5fec88a)

##### <!-- 07 -->Miscellaneous tasks

- fix(ci): fixed release announcement to discord by
[@&#8203;fredbi](https://redirect.github.com/fredbi) in
[#&#8203;3354](https://redirect.github.com/go-swagger/go-swagger/pull/3354)
[...](d9244f53d4)
- ci: fixed event by
[@&#8203;fredbi](https://redirect.github.com/fredbi)
[...](ba881e3580)
- ci: repair release by
[@&#8203;fredbi](https://redirect.github.com/fredbi) in
[#&#8203;3353](https://redirect.github.com/go-swagger/go-swagger/pull/3353)
[...](70b7c214ce)

##### <!-- 0A -->Updates

- chore(deps): bump the development-dependencies group with 3 updates by
[@&#8203;dependabot\[bot\]](https://redirect.github.com/dependabot\[bot])
in
[#&#8203;3359](https://redirect.github.com/go-swagger/go-swagger/pull/3359)
[...](65d6af084b)
- chore(deps): bump golang from `91eda97` to `f23e8b2` in the
development-dependencies group across 1 directory by
[@&#8203;dependabot\[bot\]](https://redirect.github.com/dependabot\[bot])
in
[#&#8203;3356](https://redirect.github.com/go-swagger/go-swagger/pull/3356)
[...](b1dfdf0c14)
- chore(deps): bump the development-dependencies group with 2 updates by
[@&#8203;dependabot\[bot\]](https://redirect.github.com/dependabot\[bot])
in
[#&#8203;3355](https://redirect.github.com/go-swagger/go-swagger/pull/3355)
[...](340826c27f)

***

##### People who contributed to this release

- [@&#8203;fredbi](https://redirect.github.com/fredbi)

***

**[go-swagger](https://redirect.github.com/go-swagger/go-swagger)
license terms**

[![License][license-badge]][license-url]

[license-badge]:
http://img.shields.io/badge/license-Apache%20v2-orange.svg

[license-url]:
https://redirect.github.com/go-swagger/go-swagger/?tab=Apache-2.0-1-ov-file#readme

***

Released by
[GoReleaser](https://redirect.github.com/goreleaser/goreleaser).

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - Only on Monday (`* * * * 1`)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR has been generated by [Mend
Renovate](https://redirect.github.com/renovatebot/renovate).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xNDEuNSIsInVwZGF0ZWRJblZlciI6IjQzLjE0MS41IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

Co-authored-by: bircni <bircni@icloud.com>
2026-06-15 20:23:13 +00:00
18 changed files with 68 additions and 284 deletions

View File

@@ -16,7 +16,7 @@ EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-che
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2 # renovate: datasource=go
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.15 # renovate: datasource=go
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.8.0 # renovate: datasource=go
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.34.0 # renovate: datasource=go
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.34.1 # renovate: datasource=go
XGO_PACKAGE ?= src.techknowlogick.com/xgo@v1.9.0 # renovate: datasource=go
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1.3.0 # renovate: datasource=go
ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1.7.12 # renovate: datasource=go

View File

@@ -51,6 +51,8 @@ ROOT_PATH = /data/gitea/log
[security]
INSTALL_LOCK = $INSTALL_LOCK
SECRET_KEY = $SECRET_KEY
REVERSE_PROXY_LIMIT = 1
REVERSE_PROXY_TRUSTED_PROXIES = *
[service]
DISABLE_REGISTRATION = $DISABLE_REGISTRATION

View File

@@ -48,6 +48,8 @@ ROOT_PATH = $GITEA_WORK_DIR/data/log
[security]
INSTALL_LOCK = $INSTALL_LOCK
SECRET_KEY = $SECRET_KEY
REVERSE_PROXY_LIMIT = 1
REVERSE_PROXY_TRUSTED_PROXIES = *
[service]
DISABLE_REGISTRATION = $DISABLE_REGISTRATION

View File

@@ -21,7 +21,6 @@ import (
"github.com/pquerna/otp/totp"
"golang.org/x/crypto/pbkdf2"
"xorm.io/builder"
)
//
@@ -105,43 +104,20 @@ func (t *TwoFactor) SetSecret(secretString string) error {
return nil
}
// validateTOTP validates the provided passcode. It does not consume the passcode; all login
// surfaces must go through ValidateAndConsumeTOTP so that a passcode cannot be redeemed twice.
func (t *TwoFactor) validateTOTP(passcode string) (bool, error) {
// ValidateTOTP validates the provided passcode.
func (t *TwoFactor) ValidateTOTP(passcode string) (bool, error) {
decodedStoredSecret, err := base64.StdEncoding.DecodeString(t.Secret)
if err != nil {
return false, fmt.Errorf("validateTOTP invalid base64: %w", err)
return false, fmt.Errorf("ValidateTOTP invalid base64: %w", err)
}
secretBytes, err := secret.AesDecrypt(t.getEncryptionKey(), decodedStoredSecret)
if err != nil {
return false, fmt.Errorf("validateTOTP unable to decrypt (maybe SECRET_KEY is wrong): %w", err)
return false, fmt.Errorf("ValidateTOTP unable to decrypt (maybe SECRET_KEY is wrong): %w", err)
}
secretStr := string(secretBytes)
return totp.Validate(passcode, secretStr), nil
}
// ValidateAndConsumeTOTP validates the passcode and atomically records it as used so that the
// same passcode cannot be redeemed more than once (RFC 6238 §5.2). It returns false for an
// invalid passcode as well as for a replay, including the case where a concurrent request with
// the same passcode won the race first. All TOTP login surfaces must go through this helper.
func (t *TwoFactor) ValidateAndConsumeTOTP(ctx context.Context, passcode string) (bool, error) {
ok, err := t.validateTOTP(passcode)
if err != nil || !ok {
return false, err
}
// Conditional update: only a row whose stored passcode differs from this one is updated, so a
// replay (or a concurrent duplicate) matches zero rows and is rejected. The row lock taken by
// the UPDATE serializes racing requests, closing the read-validate-write TOCTOU window.
t.LastUsedPasscode = passcode
n, err := db.GetEngine(ctx).ID(t.ID).
Where(builder.Or(builder.IsNull{"last_used_passcode"}, builder.Neq{"last_used_passcode": passcode})).
Cols("last_used_passcode").Update(t)
if err != nil {
return false, err
}
return n == 1, nil
}
// NewTwoFactor creates a new two-factor authentication token.
func NewTwoFactor(ctx context.Context, t *TwoFactor) error {
_, err := db.GetEngine(ctx).Insert(t)

View File

@@ -1,47 +0,0 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package auth_test
import (
"testing"
"time"
auth_model "gitea.dev/models/auth"
"gitea.dev/models/unittest"
"github.com/pquerna/otp/totp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestTwoFactorValidateAndConsumeTOTP(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
key, err := totp.Generate(totp.GenerateOpts{SecretSize: 40, Issuer: "gitea-test", AccountName: "consume"})
require.NoError(t, err)
tfa := &auth_model.TwoFactor{UID: 1}
require.NoError(t, tfa.SetSecret(key.Secret()))
require.NoError(t, auth_model.NewTwoFactor(t.Context(), tfa))
passcode, err := totp.GenerateCode(key.Secret(), time.Now())
require.NoError(t, err)
// first use of a valid passcode succeeds
ok, err := tfa.ValidateAndConsumeTOTP(t.Context(), passcode)
require.NoError(t, err)
assert.True(t, ok)
// replaying the same passcode is refused, even when still inside the TOTP validity window
reloaded, err := auth_model.GetTwoFactorByUID(t.Context(), tfa.UID)
require.NoError(t, err)
ok, err = reloaded.ValidateAndConsumeTOTP(t.Context(), passcode)
require.NoError(t, err)
assert.False(t, ok)
// an invalid passcode is rejected without consuming anything
ok, err = reloaded.ValidateAndConsumeTOTP(t.Context(), "000000")
require.NoError(t, err)
assert.False(t, ok)
}

View File

@@ -2205,10 +2205,10 @@
"repo.settings.trust_model.collaborator.desc": "Déanfar sínithe bailí ó chomhoibritheoirí an stórais seo a mharcáil mar \"iontaofa\", cibé acu a mheaitseálann siad an tiomnóir nó nach meaitseálann. Seachas sin, déanfar sínithe bailí a mharcáil mar \"neamhiontaofa\" má mheaitseálann an síniú an tiomnóir agus \"gan mheaitseáil\" mura bhfuil.",
"repo.settings.trust_model.committer": "Coimisitheoir",
"repo.settings.trust_model.committer.long": "Tiomnaithe: Sínithe muiníne a mheaitseálann tiomnóirí. Meaitseálann sé seo iompar GitHub agus cuirfidh sé iallach ar thiomnóirí atá sínithe ag Gitea Gitea a bheith mar an tiomnóir.",
"repo.settings.trust_model.committer.desc": "Ní mharcálfar sínithe bailí mar \"iontaofa\" ach amháin má mheaitseálann siad an tiomn, nó marcálfar iad mar \"gan mheaitseáil\". Cuireann sé seo iallach ar Gitea a bheith ina tiomn ar thiomnuithe sínithe, agus an tiomn iarbhír marcáilte mar Chomhúdaraithe ag: agus Co-thiomnaithe ag: leantóir sa tiomnú. Caithfidh eochair réamhshocraithe Gitea a bheith ag teacht le húsáideoir sa bhunachar sonraí.",
"repo.settings.trust_model.committer.desc": "Ní mharcálfar sínithe bailí mar \"iontaofa\" ach amháin má mheaitseálann siad an tiomnóir, nó marcálfar iad mar \"gan mheaitseáil\". Cuireann sé seo iallach ar Gitea a bheith ina thiomnóir ar thiomnuithe sínithe, agus an tiomnóir iarbhír marcáilte mar leantóir Co-authored-by: sa thiomnú. Caithfidh eochair réamhshocraithe Gitea a bheith ag teacht le húsáideoir sa bhunachar sonraí.",
"repo.settings.trust_model.collaboratorcommitter": "Comhoibritheo+Coimiteoir",
"repo.settings.trust_model.collaboratorcommitter.long": "Comhoibrí+Coiste: sínithe muiníne ó chomhoibrithe a mheaitseálann an tiomnóir",
"repo.settings.trust_model.collaboratorcommitter.desc": "Marcálfar sínithe bailí ó chomhoibritheoirí an stórais seo mar \"iontaofa\" má mheaitseálann siad an tiomn. Seachas sin, marcálfar sínithe bailí mar \"neamhiontaofa\" má mheaitseálann an síniú an tiomn agus \"gan mheaitseáil\" murach sin. Cuirfidh sé seo iallach ar Gitea a bheith marcáilte mar an tiomn ar thiomnuithe sínithe, agus an tiomn iarbhír marcáilte mar Chomhúdaraithe ag: agus Co-Tiomnaithe ag: leantóir sa tiomnú. Ní mór don eochair réamhshocraithe Gitea a bheith ag teacht le húsáideoir sa bhunachar sonraí.",
"repo.settings.trust_model.collaboratorcommitter.desc": "Marcálfar sínithe bailí ó chomhoibritheoirí an stórais seo mar \"iontaofa\" má mheaitseálann siad an tiomnóir. Seachas sin, marcálfar sínithe bailí mar \"neamhiontaofa\" má mheaitseálann an síniú an tiomnóir agus \"gan mheaitseáil\" murach sin. Cuirfidh sé seo iallach ar Gitea a bheith marcáilte mar an tiomnóir ar thiomnuithe sínithe, agus an tiomnóir iarbhír marcáilte mar leantóir Co-Authored-By: sa tiomnú. Ní mór don eochair réamhshocraithe Gitea a bheith ag teacht le húsáideoir sa bhunachar sonraí.",
"repo.settings.wiki_delete": "Scrios Sonraí Vicí",
"repo.settings.wiki_delete_desc": "Tá sonraí wiki stóras a scriosadh buan agus ní féidir iad a chur ar ais.",
"repo.settings.wiki_delete_notices_1": "- Scriosfaidh agus díchumasóidh sé seo an stóras vicí do %s go buan.",
@@ -2599,6 +2599,9 @@
"repo.diff.review.reject": "Iarr athruithe",
"repo.diff.review.self_approve": "Ní féidir le húdair iarratais tarraing a n-iarratas tarraingthe féin a chead",
"repo.diff.committed_by": "tiomanta ag",
"repo.diff.coauthored_by": "comhúdaraithe ag",
"repo.commits.avatar_stack_and": "agus",
"repo.commits.avatar_stack_people": "%d duine",
"repo.diff.protected": "Cosanta",
"repo.diff.image.side_by_side": "Taobh le Taobh",
"repo.diff.image.swipe": "Scaoil",
@@ -2862,6 +2865,14 @@
"org.teams.all_repositories_read_permission_desc": "Tugann an fhoireann seo rochtain do <strong>Léamh</strong> ar <strong>gach stórais</strong>: is féidir le baill amharc ar stórais agus iad a chlónáil.",
"org.teams.all_repositories_write_permission_desc": "Tugann an fhoireann seo rochtain do <strong>Scríobh</strong> ar <strong>gach stórais</strong>: is féidir le baill léamh ó stórais agus iad a bhrú chucu.",
"org.teams.all_repositories_admin_permission_desc": "Tugann an fhoireann seo rochtain <strong>Riarthóra</strong> ar <strong>gach stóras</strong>: is féidir le comhaltaí léamh, brú a dhéanamh agus comhoibritheoirí a chur le stórtha.",
"org.teams.visibility": "Infheictheacht",
"org.teams.visibility_private": "Príobháideach",
"org.teams.visibility_private_helper": "Le feiceáil ag baill foirne agus úinéirí eagraíochta amháin.",
"org.teams.visibility_limited": "Teoranta",
"org.teams.visibility_limited_helper": "Infheicthe ag gach ball den eagraíocht seo.",
"org.teams.visibility_public": "Poiblí",
"org.teams.visibility_public_helper": "Infheicthe ag aon úsáideoir atá sínithe isteach.",
"org.teams.owners_visibility_fixed": "Ní féidir infheictheacht fhoireann na nÚinéirí a athrú.",
"org.teams.invite.title": "Tugadh cuireadh duit dul isteach i bhfoireann <strong>%s</strong> san eagraíocht <strong>%s</strong>.",
"org.teams.invite.by": "Ar cuireadh ó %s",
"org.teams.invite.description": "Cliceáil ar an gcnaipe thíos le do thoil chun dul isteach san fhoireann.",
@@ -3774,6 +3785,7 @@
"actions.runs.no_matching_online_runner_helper": "Gan aon reathaí ar líne a mheaitseáil le lipéad: %s",
"actions.runs.no_job_without_needs": "Caithfidh post amháin ar a laghad a bheith sa sreabhadh oibre gan spleáchas.",
"actions.runs.no_job": "Caithfidh post amháin ar a laghad a bheith sa sreabhadh oibre",
"actions.runs.invalid_reusable_workflow_uses": "Sreabhadh oibre in-athúsáidte neamhbhailí \"úsáidí\": %s",
"actions.runs.actor": "Aisteoir",
"actions.runs.status": "Stádas",
"actions.runs.actors_no_select": "Gach aisteoir",
@@ -3794,13 +3806,17 @@
"actions.runs.view_workflow_file": "Féach ar chomhad sreabha oibre",
"actions.runs.summary": "Achoimre",
"actions.runs.all_jobs": "Gach post",
"actions.runs.job_summaries": "Achoimrí poist",
"actions.runs.expand_caller_jobs": "Taispeáin poist an ghlaoiteora sreabha oibre in-athúsáidte seo",
"actions.runs.collapse_caller_jobs": "Folaigh poist an ghlaoiteora sreabha oibre in-athúsáidte seo",
"actions.runs.attempt": "Iarracht",
"actions.runs.latest": "Is déanaí",
"actions.runs.latest_attempt": "An iarracht is déanaí",
"actions.runs.triggered_via": "Spreagtha trí %s",
"actions.runs.total_duration": "Fad iomlán:",
"actions.runs.rerun_triggered": "Athrith spreagtha",
"actions.runs.back_to_pull_request": "Ar ais chuig an iarratas tarraingthe",
"actions.runs.back_to_workflow": "Ar ais chuig an sreabhadh oibre",
"actions.runs.total_duration": "Fad iomlán",
"actions.runs.workflow_dependencies": "Spleáchais ar Shreabhadh Oibre",
"actions.runs.graph_jobs_count_1": "%d post",
"actions.runs.graph_jobs_count_n": "%d poist",

View File

@@ -505,21 +505,6 @@ func reqOrgOwnership() func(ctx *context.APIContext) {
}
}
// reqOrgVisible requires the organization to be visible to the doer, or a site admin
func reqOrgVisible() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if ctx.Org.Organization == nil {
setting.PanicInDevOrTesting("reqOrgVisible: unprepared context")
ctx.APIErrorInternal(errors.New("reqOrgVisible: unprepared context"))
return
}
if !organization.HasOrgOrUserVisible(ctx, ctx.Org.Organization.AsUser(), ctx.Doer) {
ctx.APIErrorNotFound()
return
}
}
}
func teamAccessPrivileged(ctx *context.APIContext) (orgID int64, privileged, ok bool) {
if ctx.IsUserSiteAdmin() {
return 0, true, true
@@ -1743,7 +1728,7 @@ func Routes() *web.Router {
m.Combo("/{id}").Get(reqToken(), org.GetLabel).
Patch(reqToken(), reqOrgOwnership(), bind(api.EditLabelOption{}), org.EditLabel).
Delete(reqToken(), reqOrgOwnership(), org.DeleteLabel)
}, reqOrgVisible())
})
m.Group("/hooks", func() {
m.Combo("").Get(org.ListHooks).
Post(bind(api.CreateHookOption{}), org.CreateHook)

View File

@@ -1336,9 +1336,6 @@ func MergeUpstream(ctx *context.APIContext) {
} else if errors.Is(err, util.ErrNotExist) {
ctx.APIError(http.StatusNotFound, err.Error())
return
} else if errors.Is(err, util.ErrPermissionDenied) {
ctx.APIError(http.StatusForbidden, err.Error())
return
}
ctx.APIErrorInternal(err)
return

View File

@@ -40,6 +40,9 @@ type preReceiveContext struct {
canCreatePullRequest bool
checkedCanCreatePullRequest bool
canWriteCode bool
checkedCanWriteCode bool
protectedTags []*git_model.ProtectedTag
gotProtectedTags bool
@@ -47,36 +50,24 @@ type preReceiveContext struct {
opts *private.HookOptions
// this context should only contain shared variables, mutable variables like "current branch name" shouldn't be put here
canWriteCodeUnitCached *bool
branchName string
}
func (ctx *preReceiveContext) canWriteCodeUnit() bool {
if ctx.canWriteCodeUnitCached == nil {
var canWrite bool
if ctx.loadPusherAndPermission() {
canWrite = ctx.userPerm.CanWrite(unit.TypeCode) || ctx.deployKeyAccessMode >= perm_model.AccessModeWrite
// CanWriteCode returns true if pusher can write code
func (ctx *preReceiveContext) CanWriteCode() bool {
if !ctx.checkedCanWriteCode {
if !ctx.loadPusherAndPermission() {
return false
}
ctx.canWriteCodeUnitCached = &canWrite
ctx.canWriteCode = issues_model.CanMaintainerWriteToBranch(ctx, ctx.userPerm, ctx.branchName, ctx.user) || ctx.deployKeyAccessMode >= perm_model.AccessModeWrite
ctx.checkedCanWriteCode = true
}
return *ctx.canWriteCodeUnitCached
return ctx.canWriteCode
}
// canWriteCodeRef returns true if pusher can write to the code ref (branch/tag/commit)
func (ctx *preReceiveContext) canWriteCodeRef(refFullName git.RefName) bool {
if ctx.canWriteCodeUnit() {
return true
}
// then check whether if the pusher is a maintainer who can write the PR author's head repo branch
if !refFullName.IsBranch() {
return false
}
return issues_model.CanMaintainerWriteToBranch(ctx, ctx.userPerm, refFullName.BranchName(), ctx.user)
}
// assertCanWriteRef returns true if pusher can write to the code ref, otherwise it responds with 403 Forbidden and returns false
func (ctx *preReceiveContext) assertCanWriteRef(refFullName git.RefName) bool {
if !ctx.canWriteCodeRef(refFullName) {
// AssertCanWriteCode returns true if pusher can write code
func (ctx *preReceiveContext) AssertCanWriteCode() bool {
if !ctx.CanWriteCode() {
if ctx.Written() {
return false
}
@@ -138,7 +129,7 @@ func HookPreReceive(ctx *gitea_context.PrivateContext) {
case git.DefaultFeatures().SupportProcReceive && refFullName.IsFor():
preReceiveFor(ourCtx, refFullName)
default:
ourCtx.assertCanWriteRef(refFullName)
ourCtx.AssertCanWriteCode()
}
if ctx.Written() {
return
@@ -150,8 +141,9 @@ func HookPreReceive(ctx *gitea_context.PrivateContext) {
func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, refFullName git.RefName) {
branchName := refFullName.BranchName()
ctx.branchName = branchName
if !ctx.assertCanWriteRef(refFullName) {
if !ctx.AssertCanWriteCode() {
return
}
@@ -412,7 +404,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
}
func preReceiveTag(ctx *preReceiveContext, refFullName git.RefName) {
if !ctx.assertCanWriteRef(refFullName) {
if !ctx.AssertCanWriteCode() {
return
}

View File

@@ -1,70 +0,0 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package private
import (
"testing"
issues_model "gitea.dev/models/issues"
"gitea.dev/models/perm/access"
repo_model "gitea.dev/models/repo"
"gitea.dev/models/unittest"
"gitea.dev/modules/git"
"gitea.dev/services/contexttest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestPreReceiveCanWriteCodePerBranch ensures the maintainer-edit write grant is evaluated against
// the exact ref being pushed on every call, derived from that ref rather than shared mutable state.
// Otherwise a per-branch grant (an open PR with "allow edits from maintainers") could be batched
// together with a protected branch or a tag to escalate into full repository write.
func TestPreReceiveCanWriteCodePerBranch(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10})
headRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 11})
require.NoError(t, baseRepo.LoadOwner(t.Context()))
require.NoError(t, headRepo.LoadOwner(t.Context()))
// An open PR from the head repo owner, with maintainer edits allowed: this grants the base
// repo owner write access to exactly this head branch and nothing else.
pr := &issues_model.PullRequest{
Issue: &issues_model.Issue{
RepoID: baseRepo.ID,
PosterID: headRepo.OwnerID,
},
HeadRepoID: headRepo.ID,
BaseRepoID: baseRepo.ID,
HeadBranch: "granted-branch",
BaseBranch: "master",
AllowMaintainerEdit: true,
}
require.NoError(t, issues_model.NewPullRequest(t.Context(), baseRepo, pr.Issue, nil, nil, pr))
// The pusher is the base repo owner (the maintainer) with only read access on the head repo.
maintainer := baseRepo.Owner
headPerm, err := access.GetIndividualUserRepoPermission(t.Context(), headRepo, maintainer)
require.NoError(t, err)
mockCtx, _ := contexttest.MockPrivateContext(t, "/")
ctx := &preReceiveContext{
PrivateContext: mockCtx,
loadedPusher: true,
user: maintainer,
userPerm: headPerm,
}
// The granted branch must be writable...
assert.True(t, ctx.canWriteCodeRef(git.RefNameFromBranch("granted-branch")))
// ...but another branch in the same push must NOT inherit that grant.
assert.False(t, ctx.canWriteCodeRef(git.RefNameFromBranch("master")))
// ...and a tag sharing the granted branch's name must NOT inherit it either: the grant is
// scoped to PR head branches, so a non-branch ref can never match it. (A tag ref already
// yields an empty branch name, so this guards the per-ref evaluation, not the IsBranch check.)
assert.False(t, ctx.canWriteCodeRef(git.RefNameFromTag("granted-branch")))
}

View File

@@ -58,14 +58,14 @@ func TwoFactorPost(ctx *context.Context) {
return
}
// Validate the passcode and atomically consume it to prevent reuse/replay.
ok, err := twofa.ValidateAndConsumeTOTP(ctx, form.Passcode)
// Validate the passcode with the stored TOTP secret.
ok, err := twofa.ValidateTOTP(form.Passcode)
if err != nil {
ctx.ServerError("UserSignIn", err)
return
}
if ok {
if ok && twofa.LastUsedPasscode != form.Passcode {
remember := ctx.Session.Get("twofaRemember").(bool)
u, err := user_model.GetUserByID(ctx, id)
if err != nil {
@@ -81,6 +81,12 @@ func TwoFactorPost(ctx *context.Context) {
}
}
twofa.LastUsedPasscode = form.Passcode
if err = auth.UpdateTwoFactor(ctx, twofa); err != nil {
ctx.ServerError("UserSignIn", err)
return
}
_ = ctx.Session.Set(session.KeyUserHasTwoFactorAuth, true)
handleSignIn(ctx, u, remember)
return

View File

@@ -177,17 +177,23 @@ func ResetPasswdPost(ctx *context.Context) {
regenerateScratchToken = true
} else {
passcode := ctx.FormString("passcode")
ok, err := twofa.ValidateAndConsumeTOTP(ctx, passcode)
ok, err := twofa.ValidateTOTP(passcode)
if err != nil {
ctx.HTTPError(http.StatusInternalServerError, "ValidateAndConsumeTOTP", err.Error())
ctx.HTTPError(http.StatusInternalServerError, "ValidateTOTP", err.Error())
return
}
if !ok {
if !ok || twofa.LastUsedPasscode == passcode {
ctx.Data["IsResetForm"] = true
ctx.Data["Err_Passcode"] = true
ctx.RenderWithErrDeprecated(ctx.Tr("auth.twofa_passcode_incorrect"), tplResetPassword, nil)
return
}
twofa.LastUsedPasscode = passcode
if err = auth.UpdateTwoFactor(ctx, twofa); err != nil {
ctx.ServerError("ResetPasswdPost: UpdateTwoFactor", err)
return
}
}
}

View File

@@ -264,7 +264,7 @@ func MergeUpstream(ctx *context.Context) {
branchName := ctx.FormString("branch")
_, err := repo_service.MergeUpstream(ctx, ctx.Doer, ctx.Repo.Repository, branchName, false)
if err != nil {
if errors.Is(err, util.ErrNotExist) || errors.Is(err, util.ErrPermissionDenied) {
if errors.Is(err, util.ErrNotExist) {
ctx.JSONErrorNotFound()
return
} else if pull_service.IsErrMergeConflicts(err) {

View File

@@ -177,8 +177,7 @@ func validateTOTP(req *http.Request, u *user_model.User) error {
}
return err
}
// Consume the passcode atomically so a captured OTP cannot be replayed within its validity window.
if ok, err := twofa.ValidateAndConsumeTOTP(req.Context(), req.Header.Get("X-Gitea-OTP")); err != nil {
if ok, err := twofa.ValidateTOTP(req.Header.Get("X-Gitea-OTP")); err != nil {
return err
} else if !ok {
return util.NewInvalidArgumentErrorf("invalid provided OTP")

View File

@@ -8,9 +8,7 @@ import (
"fmt"
issue_model "gitea.dev/models/issues"
access_model "gitea.dev/models/perm/access"
repo_model "gitea.dev/models/repo"
"gitea.dev/models/unit"
user_model "gitea.dev/models/user"
"gitea.dev/modules/git"
"gitea.dev/modules/gitrepo"
@@ -28,17 +26,6 @@ func MergeUpstream(ctx reqctx.RequestContext, doer *user_model.User, repo *repo_
if err = repo.GetBaseRepo(ctx); err != nil {
return "", err
}
// The doer must still be able to read the base repository's code. Otherwise a fork created
// while the base repo was public could keep pulling commits after it turned private.
basePerm, err := access_model.GetDoerRepoPermission(ctx, repo.BaseRepo, doer)
if err != nil {
return "", err
}
if !basePerm.CanRead(unit.TypeCode) {
return "", util.NewPermissionDeniedErrorf("permission denied to read base repo %d", repo.BaseRepo.ID)
}
divergingInfo, err := GetUpstreamDivergingInfo(ctx, repo, branch)
if err != nil {
return "", err

View File

@@ -11,7 +11,6 @@ import (
"time"
auth_model "gitea.dev/models/auth"
issues_model "gitea.dev/models/issues"
org_model "gitea.dev/models/organization"
"gitea.dev/models/perm"
repo_model "gitea.dev/models/repo"
@@ -293,50 +292,3 @@ func testAPIDeleteOrgRepos(t *testing.T) {
MakeRequest(t, req, http.StatusNoContent) // The org contains no repositories, so the API should return StatusNoContent
})
}
// TestAPIOrgLabelsVisibility ensures the organization label read endpoints honor
// the organization visibility: labels of a private org must not be disclosed to
// users who cannot see the org (GHSA: unauthorized access to private org labels).
func TestAPIOrgLabelsVisibility(t *testing.T) {
defer tests.PrepareTestEnv(t)()
// privated_org (id 23) is a private organization; user5 is its only member.
privateOrg := unittest.AssertExistsAndLoadBean(t, &org_model.Organization{ID: 23})
label := &issues_model.Label{OrgID: privateOrg.ID, Name: "internal-label", Color: "#aabbcc", Description: "private organization label"}
require.NoError(t, issues_model.NewLabel(t.Context(), label))
listURL := fmt.Sprintf("/api/v1/orgs/%s/labels", privateOrg.Name)
getURL := fmt.Sprintf("/api/v1/orgs/%s/labels/%d", privateOrg.Name, label.ID)
t.Run("NonMemberDenied", func(t *testing.T) {
// user2 is not a member of the private org and must not see its labels.
token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadOrganization)
MakeRequest(t, NewRequest(t, "GET", listURL).AddTokenAuth(token), http.StatusNotFound)
MakeRequest(t, NewRequest(t, "GET", getURL).AddTokenAuth(token), http.StatusNotFound)
})
t.Run("AnonymousDenied", func(t *testing.T) {
MakeRequest(t, NewRequest(t, "GET", listURL), http.StatusNotFound)
MakeRequest(t, NewRequest(t, "GET", getURL), http.StatusNotFound)
})
t.Run("MemberAllowed", func(t *testing.T) {
token := getUserToken(t, "user5", auth_model.AccessTokenScopeReadOrganization)
resp := MakeRequest(t, NewRequest(t, "GET", listURL).AddTokenAuth(token), http.StatusOK)
labels := DecodeJSON(t, resp, &[]*api.Label{})
assert.Len(t, *labels, 1)
MakeRequest(t, NewRequest(t, "GET", getURL).AddTokenAuth(token), http.StatusOK)
})
t.Run("SiteAdminAllowed", func(t *testing.T) {
token := getUserToken(t, "user1", auth_model.AccessTokenScopeReadOrganization)
MakeRequest(t, NewRequest(t, "GET", listURL).AddTokenAuth(token), http.StatusOK)
MakeRequest(t, NewRequest(t, "GET", getURL).AddTokenAuth(token), http.StatusOK)
})
t.Run("PublicOrgStillReadable", func(t *testing.T) {
// org3 (id 3) is a public org with labels; non-members may read them.
token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadOrganization)
MakeRequest(t, NewRequest(t, "GET", "/api/v1/orgs/org3/labels").AddTokenAuth(token), http.StatusOK)
})
}

View File

@@ -51,12 +51,6 @@ func TestAPITwoFactor(t *testing.T) {
AddBasicAuth(user.Name)
req.Header.Set("X-Gitea-OTP", passcode)
MakeRequest(t, req, http.StatusOK)
// the same passcode must not be replayable on the basic-auth surface (RFC 6238 single-use)
req = NewRequest(t, "GET", "/api/v1/user").
AddBasicAuth(user.Name)
req.Header.Set("X-Gitea-OTP", passcode)
MakeRequest(t, req, http.StatusUnauthorized)
}
func TestBasicAuthWithWebAuthn(t *testing.T) {

View File

@@ -171,18 +171,5 @@ func TestRepoMergeUpstream(t *testing.T) {
}).AddTokenAuth(token)
MakeRequest(t, req, http.StatusBadRequest)
})
t.Run("BasePrivateBlocksSync", func(t *testing.T) {
// add a new commit to the base repo, then make the base repo private
require.NoError(t, createOrReplaceFileInBranch(baseUser, baseRepo, "secret.txt", "master", "private-content"))
baseRepo.IsPrivate = true
_, err := db.GetEngine(t.Context()).ID(baseRepo.ID).Cols("is_private").Update(baseRepo)
require.NoError(t, err)
// the fork owner can no longer read the base repo, so syncing must be refused
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/test-repo-fork/merge-upstream", forkUser.Name), &api.MergeUpstreamRequest{
Branch: "fork-branch",
}).AddTokenAuth(token)
MakeRequest(t, req, http.StatusForbidden)
})
})
}