Skip to content

Commit 611a91a

Browse files
authored
Merge branch 'main' into password-recovery
2 parents 5afa4e1 + 62a4879 commit 611a91a

File tree

95 files changed

+1975
-322
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

95 files changed

+1975
-322
lines changed

cmd/hook.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -221,16 +221,16 @@ Gitea or set your environment appropriately.`, "")
221221
total++
222222
lastline++
223223

224-
// If the ref is a branch, check if it's protected
225-
if strings.HasPrefix(refFullName, git.BranchPrefix) {
224+
// If the ref is a branch or tag, check if it's protected
225+
if strings.HasPrefix(refFullName, git.BranchPrefix) || strings.HasPrefix(refFullName, git.TagPrefix) {
226226
oldCommitIDs[count] = oldCommitID
227227
newCommitIDs[count] = newCommitID
228228
refFullNames[count] = refFullName
229229
count++
230230
fmt.Fprintf(out, "*")
231231

232232
if count >= hookBatchSize {
233-
fmt.Fprintf(out, " Checking %d branches\n", count)
233+
fmt.Fprintf(out, " Checking %d references\n", count)
234234

235235
hookOptions.OldCommitIDs = oldCommitIDs
236236
hookOptions.NewCommitIDs = newCommitIDs
@@ -261,7 +261,7 @@ Gitea or set your environment appropriately.`, "")
261261
hookOptions.NewCommitIDs = newCommitIDs[:count]
262262
hookOptions.RefFullNames = refFullNames[:count]
263263

264-
fmt.Fprintf(out, " Checking %d branches\n", count)
264+
fmt.Fprintf(out, " Checking %d references\n", count)
265265

266266
statusCode, msg := private.HookPreReceive(username, reponame, hookOptions)
267267
switch statusCode {

custom/conf/app.example.ini

+2
Original file line numberDiff line numberDiff line change
@@ -705,6 +705,8 @@ PATH =
705705
;;
706706
;; Minimum amount of time a user must exist before comments are kept when the user is deleted.
707707
;USER_DELETE_WITH_COMMENTS_MAX_TIME = 0
708+
;; Valid site url schemes for user profiles
709+
;VALID_SITE_URL_SCHEMES=http,https
708710

709711

710712
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

docs/content/doc/advanced/config-cheat-sheet.en-us.md

+1
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,7 @@ relation to port exhaustion.
519519
- `NO_REPLY_ADDRESS`: **noreply.DOMAIN** Value for the domain part of the user's email address in the git log if user has set KeepEmailPrivate to true. DOMAIN resolves to the value in server.DOMAIN.
520520
The user's email will be replaced with a concatenation of the user name in lower case, "@" and NO_REPLY_ADDRESS.
521521
- `USER_DELETE_WITH_COMMENTS_MAX_TIME`: **0** Minimum amount of time a user must exist before comments are kept when the user is deleted.
522+
- `VALID_SITE_URL_SCHEMES`: **http, https**: Valid site url schemes for user profiles
522523

523524
### Service - Expore (`service.explore`)
524525

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
---
2+
date: "2021-05-14T00:00:00-00:00"
3+
title: "Protected tags"
4+
slug: "protected-tags"
5+
weight: 45
6+
toc: false
7+
draft: false
8+
menu:
9+
sidebar:
10+
parent: "advanced"
11+
name: "Protected tags"
12+
weight: 45
13+
identifier: "protected-tags"
14+
---
15+
16+
# Protected tags
17+
18+
Protected tags allow control over who has permission to create or update git tags. Each rule allows you to match either an individual tag name, or use an appropriate pattern to control multiple tags at once.
19+
20+
**Table of Contents**
21+
22+
{{< toc >}}
23+
24+
## Setting up protected tags
25+
26+
To protect a tag, you need to follow these steps:
27+
28+
1. Go to the repository’s **Settings** > **Tags** page.
29+
1. Type a pattern to match a name. You can use a single name, a [glob pattern](https://pkg.go.dev/github.com/gobwas/glob#Compile) or a regular expression.
30+
1. Choose the allowed users and/or teams. If you leave these fields empty noone is allowed to create or modify this tag.
31+
1. Select **Save** to save the configuration.
32+
33+
## Pattern protected tags
34+
35+
The pattern uses [glob](https://pkg.go.dev/github.com/gobwas/glob#Compile) or regular expressions to match a tag name. For regular expressions you need to enclose the pattern in slashes.
36+
37+
Examples:
38+
39+
| Type | Pattern Protected Tag | Possible Matching Tags |
40+
| ----- | ------------------------ | --------------------------------------- |
41+
| Glob | `v*` | `v`, `v-1`, `version2` |
42+
| Glob | `v[0-9]` | `v0`, `v1` up to `v9` |
43+
| Glob | `*-release` | `2.1-release`, `final-release` |
44+
| Glob | `gitea` | only `gitea` |
45+
| Glob | `*gitea*` | `gitea`, `2.1-gitea`, `1_gitea-release` |
46+
| Glob | `{v,rel}-*` | `v-`, `v-1`, `v-final`, `rel-`, `rel-x` |
47+
| Glob | `*` | matches all possible tag names |
48+
| Regex | `/\Av/` | `v`, `v-1`, `version2` |
49+
| Regex | `/\Av[0-9]\z/` | `v0`, `v1` up to `v9` |
50+
| Regex | `/\Av\d+\.\d+\.\d+\z/` | `v1.0.17`, `v2.1.0` |
51+
| Regex | `/\Av\d+(\.\d+){0,2}\z/` | `v1`, `v2.1`, `v1.2.34` |
52+
| Regex | `/-release\z/` | `2.1-release`, `final-release` |
53+
| Regex | `/gitea/` | `gitea`, `2.1-gitea`, `1_gitea-release` |
54+
| Regex | `/\Agitea\z/` | only `gitea` |
55+
| Regex | `/^gitea$/` | only `gitea` |
56+
| Regex | `/\A(v\|rel)-/` | `v-`, `v-1`, `v-final`, `rel-`, `rel-x` |
57+
| Regex | `/.+/` | matches all possible tag names |

docs/content/doc/usage/reverse-proxies.en-us.md

+3
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,9 @@ If you wish to run Gitea with IIS. You will need to setup IIS with URL Rewrite a
221221
```xml
222222
<?xml version="1.0" encoding="UTF-8"?>
223223
<configuration>
224+
<system.web>
225+
<httpRuntime requestPathInvalidCharacters="" />
226+
</system.web>
224227
<system.webServer>
225228
<security>
226229
<requestFiltering>

integrations/api_pull_review_test.go

+54
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111

1212
"code.gitea.io/gitea/models"
1313
api "code.gitea.io/gitea/modules/structs"
14+
jsoniter "github.com/json-iterator/go"
1415
"github.com/stretchr/testify/assert"
1516
)
1617

@@ -139,6 +140,59 @@ func TestAPIPullReview(t *testing.T) {
139140
req = NewRequestf(t, http.MethodDelete, "/api/v1/repos/%s/%s/pulls/%d/reviews/%d?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, review.ID, token)
140141
resp = session.MakeRequest(t, req, http.StatusNoContent)
141142

143+
// test CreatePullReview Comment without body but with comments
144+
req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, token), &api.CreatePullReviewOptions{
145+
// Body: "",
146+
Event: "COMMENT",
147+
Comments: []api.CreatePullReviewComment{{
148+
Path: "README.md",
149+
Body: "first new line",
150+
OldLineNum: 0,
151+
NewLineNum: 1,
152+
}, {
153+
Path: "README.md",
154+
Body: "first old line",
155+
OldLineNum: 1,
156+
NewLineNum: 0,
157+
},
158+
},
159+
})
160+
var commentReview api.PullReview
161+
162+
resp = session.MakeRequest(t, req, http.StatusOK)
163+
DecodeJSON(t, resp, &commentReview)
164+
assert.EqualValues(t, "COMMENT", commentReview.State)
165+
assert.EqualValues(t, 2, commentReview.CodeCommentsCount)
166+
assert.EqualValues(t, "", commentReview.Body)
167+
assert.EqualValues(t, false, commentReview.Dismissed)
168+
169+
// test CreatePullReview Comment with body but without comments
170+
commentBody := "This is a body of the comment."
171+
req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, token), &api.CreatePullReviewOptions{
172+
Body: commentBody,
173+
Event: "COMMENT",
174+
Comments: []api.CreatePullReviewComment{},
175+
})
176+
177+
resp = session.MakeRequest(t, req, http.StatusOK)
178+
DecodeJSON(t, resp, &commentReview)
179+
assert.EqualValues(t, "COMMENT", commentReview.State)
180+
assert.EqualValues(t, 0, commentReview.CodeCommentsCount)
181+
assert.EqualValues(t, commentBody, commentReview.Body)
182+
assert.EqualValues(t, false, commentReview.Dismissed)
183+
184+
// test CreatePullReview Comment without body and no comments
185+
req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, token), &api.CreatePullReviewOptions{
186+
Body: "",
187+
Event: "COMMENT",
188+
Comments: []api.CreatePullReviewComment{},
189+
})
190+
resp = session.MakeRequest(t, req, http.StatusUnprocessableEntity)
191+
errMap := make(map[string]interface{})
192+
json := jsoniter.ConfigCompatibleWithStandardLibrary
193+
json.Unmarshal(resp.Body.Bytes(), &errMap)
194+
assert.EqualValues(t, "review event COMMENT requires a body or a comment", errMap["message"].(string))
195+
142196
// test get review requests
143197
// to make it simple, use same api with get review
144198
pullIssue12 := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 12}).(*models.Issue)

integrations/api_user_heatmap_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ func TestUserHeatmap(t *testing.T) {
2626
var heatmap []*models.UserHeatmapData
2727
DecodeJSON(t, resp, &heatmap)
2828
var dummyheatmap []*models.UserHeatmapData
29-
dummyheatmap = append(dummyheatmap, &models.UserHeatmapData{Timestamp: 1603152000, Contributions: 1})
29+
dummyheatmap = append(dummyheatmap, &models.UserHeatmapData{Timestamp: 1603227600, Contributions: 1})
3030

3131
assert.Equal(t, dummyheatmap, heatmap)
3232
}

integrations/mirror_pull_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ func TestMirrorPull(t *testing.T) {
5959

6060
assert.NoError(t, release_service.CreateRelease(gitRepo, &models.Release{
6161
RepoID: repo.ID,
62+
Repo: repo,
6263
PublisherID: user.ID,
64+
Publisher: user,
6365
TagName: "v0.2",
6466
Target: "master",
6567
Title: "v0.2 is released",

integrations/repo_tag_test.go

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright 2021 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package integrations
6+
7+
import (
8+
"io/ioutil"
9+
"net/url"
10+
"testing"
11+
12+
"code.gitea.io/gitea/models"
13+
"code.gitea.io/gitea/modules/git"
14+
"code.gitea.io/gitea/modules/util"
15+
"code.gitea.io/gitea/services/release"
16+
17+
"github.com/stretchr/testify/assert"
18+
)
19+
20+
func TestCreateNewTagProtected(t *testing.T) {
21+
defer prepareTestEnv(t)()
22+
23+
repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
24+
owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User)
25+
26+
t.Run("API", func(t *testing.T) {
27+
defer PrintCurrentTest(t)()
28+
29+
err := release.CreateNewTag(owner, repo, "master", "v-1", "first tag")
30+
assert.NoError(t, err)
31+
32+
err = models.InsertProtectedTag(&models.ProtectedTag{
33+
RepoID: repo.ID,
34+
NamePattern: "v-*",
35+
})
36+
assert.NoError(t, err)
37+
err = models.InsertProtectedTag(&models.ProtectedTag{
38+
RepoID: repo.ID,
39+
NamePattern: "v-1.1",
40+
AllowlistUserIDs: []int64{repo.OwnerID},
41+
})
42+
assert.NoError(t, err)
43+
44+
err = release.CreateNewTag(owner, repo, "master", "v-2", "second tag")
45+
assert.Error(t, err)
46+
assert.True(t, models.IsErrProtectedTagName(err))
47+
48+
err = release.CreateNewTag(owner, repo, "master", "v-1.1", "third tag")
49+
assert.NoError(t, err)
50+
})
51+
52+
t.Run("Git", func(t *testing.T) {
53+
onGiteaRun(t, func(t *testing.T, u *url.URL) {
54+
username := "user2"
55+
httpContext := NewAPITestContext(t, username, "repo1")
56+
57+
dstPath, err := ioutil.TempDir("", httpContext.Reponame)
58+
assert.NoError(t, err)
59+
defer util.RemoveAll(dstPath)
60+
61+
u.Path = httpContext.GitPath()
62+
u.User = url.UserPassword(username, userPassword)
63+
64+
doGitClone(dstPath, u)(t)
65+
66+
_, err = git.NewCommand("tag", "v-2").RunInDir(dstPath)
67+
assert.NoError(t, err)
68+
69+
_, err = git.NewCommand("push", "--tags").RunInDir(dstPath)
70+
assert.Error(t, err)
71+
assert.Contains(t, err.Error(), "Tag v-2 is protected")
72+
})
73+
})
74+
}

models/error.go

+15
Original file line numberDiff line numberDiff line change
@@ -985,6 +985,21 @@ func (err ErrInvalidTagName) Error() string {
985985
return fmt.Sprintf("release tag name is not valid [tag_name: %s]", err.TagName)
986986
}
987987

988+
// ErrProtectedTagName represents a "ProtectedTagName" kind of error.
989+
type ErrProtectedTagName struct {
990+
TagName string
991+
}
992+
993+
// IsErrProtectedTagName checks if an error is a ErrProtectedTagName.
994+
func IsErrProtectedTagName(err error) bool {
995+
_, ok := err.(ErrProtectedTagName)
996+
return ok
997+
}
998+
999+
func (err ErrProtectedTagName) Error() string {
1000+
return fmt.Sprintf("release tag name is protected [tag_name: %s]", err.TagName)
1001+
}
1002+
9881003
// ErrRepoFileAlreadyExists represents a "RepoFileAlreadyExist" kind of error.
9891004
type ErrRepoFileAlreadyExists struct {
9901005
Path string

models/fixtures/action.yml

+24
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,27 @@
3232
repo_id: 22
3333
is_private: true
3434
created_unix: 1603267920
35+
36+
- id: 5
37+
user_id: 10
38+
op_type: 1 # create repo
39+
act_user_id: 10
40+
repo_id: 6
41+
is_private: true
42+
created_unix: 1603010100
43+
44+
- id: 6
45+
user_id: 10
46+
op_type: 1 # create repo
47+
act_user_id: 10
48+
repo_id: 7
49+
is_private: true
50+
created_unix: 1603011300
51+
52+
- id: 7
53+
user_id: 10
54+
op_type: 1 # create repo
55+
act_user_id: 10
56+
repo_id: 8
57+
is_private: false
58+
created_unix: 1603011540 # grouped with id:7

models/login_source.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -856,7 +856,11 @@ func UserSignIn(username, password string) (*User, error) {
856856
return authUser, nil
857857
}
858858

859-
log.Warn("Failed to login '%s' via '%s': %v", username, source.Name, err)
859+
if IsErrUserNotExist(err) {
860+
log.Debug("Failed to login '%s' via '%s': %v", username, source.Name, err)
861+
} else {
862+
log.Warn("Failed to login '%s' via '%s': %v", username, source.Name, err)
863+
}
860864
}
861865

862866
return nil, ErrUserNotExist{user.ID, user.Name, 0}

models/migrations/migrations.go

+2
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,8 @@ var migrations = []Migration{
321321
NewMigration("Rename Task errors to message", renameTaskErrorsToMessage),
322322
// v185 -> v186
323323
NewMigration("Add new table repo_archiver", addRepoArchiver),
324+
// v186 -> v187
325+
NewMigration("Create protected tag table", createProtectedTagTable),
324326
}
325327

326328
// GetCurrentDBVersion returns the current db version

models/migrations/v186.go

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright 2021 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package migrations
6+
7+
import (
8+
"code.gitea.io/gitea/modules/timeutil"
9+
10+
"xorm.io/xorm"
11+
)
12+
13+
func createProtectedTagTable(x *xorm.Engine) error {
14+
type ProtectedTag struct {
15+
ID int64 `xorm:"pk autoincr"`
16+
RepoID int64
17+
NamePattern string
18+
AllowlistUserIDs []int64 `xorm:"JSON TEXT"`
19+
AllowlistTeamIDs []int64 `xorm:"JSON TEXT"`
20+
21+
CreatedUnix timeutil.TimeStamp `xorm:"created"`
22+
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
23+
}
24+
25+
return x.Sync2(new(ProtectedTag))
26+
}

models/models.go

+1
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ func init() {
137137
new(IssueIndex),
138138
new(PushMirror),
139139
new(RepoArchiver),
140+
new(ProtectedTag),
140141
)
141142

142143
gonicNames := []string{"SSL", "UID"}

0 commit comments

Comments
 (0)