mirror of
https://gitcode.com/gitea/gitea.git
synced 2025-10-20 22:53:22 +08:00
@ -476,7 +476,7 @@ func applySubscribedCondition(sess *xorm.Session, subscriberID int64) {
|
||||
),
|
||||
builder.Eq{"issue.poster_id": subscriberID},
|
||||
builder.In("issue.repo_id", builder.
|
||||
Select("id").
|
||||
Select("repo_id").
|
||||
From("watch").
|
||||
Where(builder.And(builder.Eq{"user_id": subscriberID},
|
||||
builder.In("mode", repo_model.WatchModeNormal, repo_model.WatchModeAuto))),
|
||||
|
@ -197,6 +197,12 @@ func TestIssues(t *testing.T) {
|
||||
},
|
||||
[]int64{2},
|
||||
},
|
||||
{
|
||||
issues_model.IssuesOptions{
|
||||
SubscriberID: 11,
|
||||
},
|
||||
[]int64{11, 5, 9, 8, 3, 2, 1},
|
||||
},
|
||||
} {
|
||||
issues, err := issues_model.Issues(t.Context(), &test.Opts)
|
||||
assert.NoError(t, err)
|
||||
|
@ -46,7 +46,7 @@ var (
|
||||
// https://www.debian.org/doc/debian-policy/ch-controlfields.html#source
|
||||
namePattern = regexp.MustCompile(`\A[a-z0-9][a-z0-9+-.]+\z`)
|
||||
// https://www.debian.org/doc/debian-policy/ch-controlfields.html#version
|
||||
versionPattern = regexp.MustCompile(`\A(?:[0-9]:)?[a-zA-Z0-9.+~]+(?:-[a-zA-Z0-9.+-~]+)?\z`)
|
||||
versionPattern = regexp.MustCompile(`\A(?:(0|[1-9][0-9]*):)?[a-zA-Z0-9.+~]+(?:-[a-zA-Z0-9.+-~]+)?\z`)
|
||||
)
|
||||
|
||||
type Package struct {
|
||||
|
@ -176,4 +176,12 @@ func TestParseControlFile(t *testing.T) {
|
||||
assert.Equal(t, []string{"a", "b"}, p.Metadata.Dependencies)
|
||||
assert.Equal(t, full, p.Control)
|
||||
})
|
||||
|
||||
t.Run("ValidVersions", func(t *testing.T) {
|
||||
for _, version := range []string{"1.0", "0:1.2", "9:1.0", "10:1.0", "900:1a.2b-x-y_z~1+2"} {
|
||||
p, err := ParseControlFile(buildContent("testpkg", version, "amd64"))
|
||||
assert.NoError(t, err, "ParseControlFile with version %q", version)
|
||||
assert.NotNil(t, p)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -104,7 +104,8 @@ func logPrinter(logger log.Logger) func(trigger Event, record *requestRecord) {
|
||||
}
|
||||
logf := logInfo
|
||||
// lower the log level for some specific requests, in most cases these logs are not useful
|
||||
if strings.HasPrefix(req.RequestURI, "/assets/") /* static assets */ ||
|
||||
if status > 0 && status < 400 &&
|
||||
strings.HasPrefix(req.RequestURI, "/assets/") /* static assets */ ||
|
||||
req.RequestURI == "/user/events" /* Server-Sent Events (SSE) handler */ ||
|
||||
req.RequestURI == "/api/actions/runner.v1.RunnerService/FetchTask" /* Actions Runner polling */ {
|
||||
logf = logTrace
|
||||
|
@ -4,7 +4,7 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
gocontext "context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@ -173,7 +173,7 @@ func Migrate(ctx *context.APIContext) {
|
||||
opts.AWSSecretAccessKey = form.AWSSecretAccessKey
|
||||
}
|
||||
|
||||
repo, err := repo_service.CreateRepositoryDirectly(ctx, ctx.Doer, repoOwner, repo_service.CreateRepoOptions{
|
||||
createdRepo, err := repo_service.CreateRepositoryDirectly(ctx, ctx.Doer, repoOwner, repo_service.CreateRepoOptions{
|
||||
Name: opts.RepoName,
|
||||
Description: opts.Description,
|
||||
OriginalURL: form.CloneAddr,
|
||||
@ -187,35 +187,37 @@ func Migrate(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
opts.MigrateToRepoID = repo.ID
|
||||
opts.MigrateToRepoID = createdRepo.ID
|
||||
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(&buf, "Handler crashed with error: %v", log.Stack(2))
|
||||
|
||||
err = errors.New(buf.String())
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
notify_service.MigrateRepository(ctx, ctx.Doer, repoOwner, repo)
|
||||
return
|
||||
}
|
||||
|
||||
if repo != nil {
|
||||
if errDelete := repo_service.DeleteRepositoryDirectly(ctx, repo.ID); errDelete != nil {
|
||||
log.Error("DeleteRepository: %v", errDelete)
|
||||
doLongTimeMigrate := func(ctx gocontext.Context, doer *user_model.User) (migratedRepo *repo_model.Repository, retErr error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
log.Error("MigrateRepository panic: %v\n%s", e, log.Stack(2))
|
||||
if errDelete := repo_service.DeleteRepositoryDirectly(ctx, createdRepo.ID); errDelete != nil {
|
||||
log.Error("Unable to delete repo after MigrateRepository panic: %v", errDelete)
|
||||
}
|
||||
retErr = errors.New("MigrateRepository panic") // no idea why it would happen, just legacy code
|
||||
}
|
||||
}
|
||||
}()
|
||||
}()
|
||||
|
||||
if repo, err = migrations.MigrateRepository(graceful.GetManager().HammerContext(), ctx.Doer, repoOwner.Name, opts, nil); err != nil {
|
||||
migratedRepo, err := migrations.MigrateRepository(ctx, doer, repoOwner.Name, opts, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
notify_service.MigrateRepository(ctx, doer, repoOwner, migratedRepo)
|
||||
return migratedRepo, nil
|
||||
}
|
||||
|
||||
// use a background context, don't cancel the migration even if the client goes away
|
||||
// HammerContext doesn't seem right (from https://github.com/go-gitea/gitea/pull/9335/files)
|
||||
// There are other abuses, maybe most HammerContext abuses should be fixed together in the future.
|
||||
migratedRepo, err := doLongTimeMigrate(graceful.GetManager().HammerContext(), ctx.Doer)
|
||||
if err != nil {
|
||||
handleMigrateError(ctx, repoOwner, err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("Repository migrated: %s/%s", repoOwner.Name, form.RepoName)
|
||||
ctx.JSON(http.StatusCreated, convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeAdmin}))
|
||||
ctx.JSON(http.StatusCreated, convert.ToRepo(ctx, migratedRepo, access_model.Permission{AccessMode: perm.AccessModeAdmin}))
|
||||
}
|
||||
|
||||
func handleMigrateError(ctx *context.APIContext, repoOwner *user_model.User, err error) {
|
||||
|
@ -131,7 +131,7 @@ func getPullInfo(ctx *context.Context) (issue *issues_model.Issue, ok bool) {
|
||||
ctx.Data["Issue"] = issue
|
||||
|
||||
if !issue.IsPull {
|
||||
ctx.NotFound(nil)
|
||||
ctx.Redirect(issue.Link())
|
||||
return nil, false
|
||||
}
|
||||
|
||||
|
@ -5,10 +5,8 @@ package actions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"regexp"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
secret_service "code.gitea.io/gitea/services/secrets"
|
||||
)
|
||||
@ -18,10 +16,6 @@ func CreateVariable(ctx context.Context, ownerID, repoID int64, name, data, desc
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := envNameCIRegexMatch(name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v, err := actions_model.InsertVariable(ctx, ownerID, repoID, name, util.ReserveLineBreakForTextarea(data), description)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -35,10 +29,6 @@ func UpdateVariableNameData(ctx context.Context, variable *actions_model.ActionV
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err := envNameCIRegexMatch(variable.Name); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
variable.Data = util.ReserveLineBreakForTextarea(variable.Data)
|
||||
|
||||
return actions_model.UpdateVariableCols(ctx, variable, "name", "data", "description")
|
||||
@ -49,14 +39,6 @@ func DeleteVariableByID(ctx context.Context, variableID int64) error {
|
||||
}
|
||||
|
||||
func DeleteVariableByName(ctx context.Context, ownerID, repoID int64, name string) error {
|
||||
if err := secret_service.ValidateName(name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := envNameCIRegexMatch(name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v, err := GetVariable(ctx, actions_model.FindVariablesOpts{
|
||||
OwnerID: ownerID,
|
||||
RepoID: repoID,
|
||||
@ -79,19 +61,3 @@ func GetVariable(ctx context.Context, opts actions_model.FindVariablesOpts) (*ac
|
||||
}
|
||||
return vars[0], nil
|
||||
}
|
||||
|
||||
// some regular expression of `variables` and `secrets`
|
||||
// reference to:
|
||||
// https://docs.github.com/en/actions/learn-github-actions/variables#naming-conventions-for-configuration-variables
|
||||
// https://docs.github.com/en/actions/security-guides/encrypted-secrets#naming-your-secrets
|
||||
var (
|
||||
forbiddenEnvNameCIRx = regexp.MustCompile("(?i)^CI")
|
||||
)
|
||||
|
||||
func envNameCIRegexMatch(name string) error {
|
||||
if forbiddenEnvNameCIRx.MatchString(name) {
|
||||
log.Error("Env Name cannot be ci")
|
||||
return util.NewInvalidArgumentErrorf("env name cannot be ci")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -56,10 +56,6 @@ func DeleteSecretByID(ctx context.Context, ownerID, repoID, secretID int64) erro
|
||||
}
|
||||
|
||||
func DeleteSecretByName(ctx context.Context, ownerID, repoID int64, name string) error {
|
||||
if err := ValidateName(name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s, err := db.Find[secret_model.Secret](ctx, secret_model.FindSecretsOptions{
|
||||
OwnerID: ownerID,
|
||||
RepoID: repoID,
|
||||
|
@ -5,21 +5,29 @@ package secrets
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
// https://docs.github.com/en/actions/learn-github-actions/variables#naming-conventions-for-configuration-variables
|
||||
// https://docs.github.com/en/actions/security-guides/encrypted-secrets#naming-your-secrets
|
||||
var (
|
||||
namePattern = regexp.MustCompile("(?i)^[A-Z_][A-Z0-9_]*$")
|
||||
forbiddenPrefixPattern = regexp.MustCompile("(?i)^GIT(EA|HUB)_")
|
||||
|
||||
ErrInvalidName = util.NewInvalidArgumentErrorf("invalid secret name")
|
||||
)
|
||||
var globalVars = sync.OnceValue(func() (ret struct {
|
||||
namePattern, forbiddenPrefixPattern *regexp.Regexp
|
||||
},
|
||||
) {
|
||||
ret.namePattern = regexp.MustCompile("(?i)^[A-Z_][A-Z0-9_]*$")
|
||||
ret.forbiddenPrefixPattern = regexp.MustCompile("(?i)^GIT(EA|HUB)_")
|
||||
return ret
|
||||
})
|
||||
|
||||
func ValidateName(name string) error {
|
||||
if !namePattern.MatchString(name) || forbiddenPrefixPattern.MatchString(name) {
|
||||
return ErrInvalidName
|
||||
vars := globalVars()
|
||||
if !vars.namePattern.MatchString(name) ||
|
||||
vars.forbiddenPrefixPattern.MatchString(name) ||
|
||||
strings.EqualFold(name, "CI") /* CI is always set to true in GitHub Actions*/ {
|
||||
return util.NewInvalidArgumentErrorf("invalid variable or secret name")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
29
services/secrets/validation_test.go
Normal file
29
services/secrets/validation_test.go
Normal file
@ -0,0 +1,29 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package secrets
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestValidateName(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
valid bool
|
||||
}{
|
||||
{"FOO", true},
|
||||
{"FOO1_BAR2", true},
|
||||
{"_FOO", true}, // really? why support this
|
||||
{"1FOO", false},
|
||||
{"giteA_xx", false},
|
||||
{"githuB_xx", false},
|
||||
{"cI", false},
|
||||
}
|
||||
for _, c := range cases {
|
||||
err := ValidateName(c.name)
|
||||
assert.Equal(t, c.valid, err == nil, "ValidateName(%q)", c.name)
|
||||
}
|
||||
}
|
@ -32,10 +32,10 @@
|
||||
<ul>
|
||||
{{if not .DisableDownloadSourceArchives}}
|
||||
<li>
|
||||
<a href="{{.Release.Repo.Link}}/archive/{{.Release.TagName | PathEscapeSegments}}.zip" rel="nofollow"><strong>{{.locale.Tr "mail.release.download.zip"}}</strong></a>
|
||||
<a href="{{.Release.Repo.HTMLURL}}/archive/{{.Release.TagName | PathEscapeSegments}}.zip" rel="nofollow"><strong>{{.locale.Tr "mail.release.download.zip"}}</strong></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{.Release.Repo.Link}}/archive/{{.Release.TagName | PathEscapeSegments}}.tar.gz" rel="nofollow"><strong>{{.locale.Tr "mail.release.download.targz"}}</strong></a>
|
||||
<a href="{{.Release.Repo.HTMLURL}}/archive/{{.Release.TagName | PathEscapeSegments}}.tar.gz" rel="nofollow"><strong>{{.locale.Tr "mail.release.download.targz"}}</strong></a>
|
||||
</li>
|
||||
{{end}}
|
||||
{{if .Release.Attachments}}
|
||||
|
@ -131,9 +131,5 @@ func TestAPIRepoSecrets(t *testing.T) {
|
||||
req = NewRequest(t, "DELETE", url).
|
||||
AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
|
||||
req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/actions/secrets/000", repo.FullName())).
|
||||
AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusBadRequest)
|
||||
})
|
||||
}
|
||||
|
@ -92,9 +92,5 @@ func TestAPIUserSecrets(t *testing.T) {
|
||||
req = NewRequest(t, "DELETE", url).
|
||||
AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
|
||||
req = NewRequest(t, "DELETE", "/api/v1/user/actions/secrets/000").
|
||||
AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusBadRequest)
|
||||
})
|
||||
}
|
||||
|
@ -497,9 +497,13 @@ func TestIssueRedirect(t *testing.T) {
|
||||
resp = session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
assert.Equal(t, "/user2/repo1/pulls/2", test.RedirectURL(resp))
|
||||
|
||||
repoUnit := unittest.AssertExistsAndLoadBean(t, &repo_model.RepoUnit{RepoID: 1, Type: unit.TypeIssues})
|
||||
// by the way, test PR redirection
|
||||
req = NewRequest(t, "GET", "/user2/repo1/pulls/1")
|
||||
resp = session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
assert.Equal(t, "/user2/repo1/issues/1", test.RedirectURL(resp))
|
||||
|
||||
// disable issue unit, it will be reset
|
||||
// disable issue unit
|
||||
repoUnit := unittest.AssertExistsAndLoadBean(t, &repo_model.RepoUnit{RepoID: 1, Type: unit.TypeIssues})
|
||||
_, err := db.DeleteByID[repo_model.RepoUnit](t.Context(), repoUnit.ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
|
Reference in New Issue
Block a user