mirror of
https://github.com/go-gitea/gitea.git
synced 2026-08-29 06:44:06 +00:00
feat: add deploy tokens (#37306)
Deploy keys only work over SSH. A deploy token is their counterpart for HTTPS: a repository scoped credential, used as the password of a Git request, with read or read and write access. It covers Git operations and LFS, and can be regenerated in place. Signed-off-by: silverwind <me@silverwind.io> Co-authored-by: Claude Mythos <noreply@anthropic.com> Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: bircni <bircni@icloud.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
@@ -121,7 +121,7 @@ func (input *notifyInput) Notify(ctx context.Context) {
|
||||
|
||||
func notify(ctx context.Context, input *notifyInput) error {
|
||||
shouldDetectSchedules := input.Event == webhook_module.HookEventPush && input.Ref.BranchName() == input.Repo.DefaultBranch
|
||||
if input.Doer.IsGiteaActions() {
|
||||
if input.Doer.ID == user_model.ActionsUserID {
|
||||
// avoiding triggering cyclically, for example:
|
||||
// a comment of an issue will trigger the runner to add a new comment as reply,
|
||||
// and the new comment will trigger the runner again.
|
||||
|
||||
+13
-6
@@ -60,8 +60,18 @@ func GetAgitBranchInfo(ctx context.Context, repoID int64, baseBranchName string)
|
||||
return "", "", util.NewNotExistErrorf("base branch does not exist")
|
||||
}
|
||||
|
||||
type ProcReceiveOptions struct {
|
||||
OldCommitIDs []string
|
||||
NewCommitIDs []string
|
||||
RefFullNames []git.RefName
|
||||
|
||||
GitPushOptions private.GitPushOptions
|
||||
|
||||
Doer *user_model.User
|
||||
}
|
||||
|
||||
// ProcReceive handle proc receive work
|
||||
func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, opts *private.HookOptions) ([]private.HookProcReceiveRefResult, error) {
|
||||
func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, opts *ProcReceiveOptions) ([]private.HookProcReceiveRefResult, error) {
|
||||
results := make([]private.HookProcReceiveRefResult, 0, len(opts.OldCommitIDs))
|
||||
forcePush := opts.GitPushOptions.Bool(private.GitPushOptionForcePush)
|
||||
topicBranch := opts.GitPushOptions["topic"]
|
||||
@@ -72,12 +82,9 @@ func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git.
|
||||
description := parseAgitPushOptionValue(opts.GitPushOptions["description"])
|
||||
|
||||
objectFormat := git.ObjectFormatFromName(repo.ObjectFormatName)
|
||||
userName := strings.ToLower(opts.UserName)
|
||||
|
||||
pusher, err := user_model.GetUserByID(ctx, opts.UserID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get user. Error: %w", err)
|
||||
}
|
||||
pusher := opts.Doer
|
||||
userName := strings.ToLower(pusher.Name)
|
||||
|
||||
for i := range opts.OldCommitIDs {
|
||||
if opts.NewCommitIDs[i] == objectFormat.EmptyObjectID().String() {
|
||||
|
||||
@@ -9,12 +9,13 @@ import (
|
||||
|
||||
asymkey_model "gitea.dev/models/asymkey"
|
||||
"gitea.dev/models/db"
|
||||
deploykey_model "gitea.dev/models/deploykey"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
)
|
||||
|
||||
// DeleteRepoDeployKeys deletes all deploy keys of a repository. permissions check should be done outside
|
||||
func DeleteRepoDeployKeys(ctx context.Context, repoID int64) (int, error) {
|
||||
deployKeys, err := db.Find[asymkey_model.DeployKey](ctx, asymkey_model.ListDeployKeysOptions{RepoID: repoID})
|
||||
deployKeys, err := db.Find[deploykey_model.DeployKey](ctx, deploykey_model.ListDeployKeysOptions{RepoID: repoID})
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("listDeployKeys: %w", err)
|
||||
}
|
||||
@@ -28,13 +29,17 @@ func DeleteRepoDeployKeys(ctx context.Context, repoID int64) (int, error) {
|
||||
}
|
||||
|
||||
// deleteDeployKeyFromDB delete deploy keys from database
|
||||
func deleteDeployKeyFromDB(ctx context.Context, key *asymkey_model.DeployKey) error {
|
||||
if _, err := db.DeleteByID[asymkey_model.DeployKey](ctx, key.ID); err != nil {
|
||||
func deleteDeployKeyFromDB(ctx context.Context, key *deploykey_model.DeployKey) error {
|
||||
if _, err := db.DeleteByID[deploykey_model.DeployKey](ctx, key.ID); err != nil {
|
||||
return fmt.Errorf("delete deploy key [%d]: %w", key.ID, err)
|
||||
}
|
||||
|
||||
if key.KeyType == deploykey_model.KeyTypeToken { // a token has no public key to clean up
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if this is the last reference to same key content.
|
||||
has, err := asymkey_model.IsDeployKeyExistByPublicKeyID(ctx, key.KeyID)
|
||||
has, err := deploykey_model.IsDeployKeyExistByPublicKeyID(ctx, key.KeyID)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !has {
|
||||
@@ -46,21 +51,22 @@ func deleteDeployKeyFromDB(ctx context.Context, key *asymkey_model.DeployKey) er
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteDeployKey deletes deploy key from its repository authorized_keys file if needed.
|
||||
// Permissions check should be done outside.
|
||||
func DeleteDeployKey(ctx context.Context, repo *repo_model.Repository, id int64) error {
|
||||
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||
key, err := asymkey_model.GetDeployKeyByID(ctx, repo.ID, id)
|
||||
// DeleteDeployKey deletes deploy key from its repository authorized_keys file if needed,
|
||||
// and returns the key it deleted. Permissions check should be done outside.
|
||||
func DeleteDeployKey(ctx context.Context, repo *repo_model.Repository, id int64) (*deploykey_model.DeployKey, error) {
|
||||
deleted, err := db.WithTx2(ctx, func(ctx context.Context) (*deploykey_model.DeployKey, error) {
|
||||
key, err := deploykey_model.GetDeployKeyByID(ctx, repo.ID, id)
|
||||
if err != nil {
|
||||
if asymkey_model.IsErrDeployKeyNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("GetDeployKeyByID: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
return deleteDeployKeyFromDB(ctx, key)
|
||||
}); err != nil {
|
||||
return err
|
||||
return key, deleteDeployKeyFromDB(ctx, key)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if deleted.KeyType == deploykey_model.KeyTypeToken {
|
||||
return deleted, nil // a token never appears in the authorized_keys file
|
||||
}
|
||||
|
||||
return RewriteAllPublicKeys(ctx)
|
||||
return deleted, RewriteAllPublicKeys(ctx)
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ const (
|
||||
AccessTokenMethodName = "access_token"
|
||||
OAuth2TokenMethodName = "oauth2_token"
|
||||
ActionTokenMethodName = "action_token"
|
||||
DeployTokenMethodName = "deploy_token"
|
||||
)
|
||||
|
||||
// Basic implements the Auth interface and authenticates requests (API requests
|
||||
@@ -41,7 +42,7 @@ func (b *Basic) Name() string {
|
||||
return BasicMethodName
|
||||
}
|
||||
|
||||
func (b *Basic) parseAuthBasic(req *http.Request) (ret struct{ authToken, uname, passwd string }) {
|
||||
func parseAuthBasic(req *http.Request) (ret struct{ authToken, uname, passwd string }) {
|
||||
authHeader := req.Header.Get("Authorization")
|
||||
if authHeader == "" {
|
||||
return ret
|
||||
@@ -53,7 +54,7 @@ func (b *Basic) parseAuthBasic(req *http.Request) (ret struct{ authToken, uname,
|
||||
uname, passwd := parsed.BasicAuth.Username, parsed.BasicAuth.Password
|
||||
|
||||
// Check if username or password is a token
|
||||
isUsernameToken := len(passwd) == 0 || passwd == "x-oauth-basic"
|
||||
isUsernameToken := passwd == "" || passwd == "x-oauth-basic"
|
||||
// Assume username is token
|
||||
authToken := uname
|
||||
if !isUsernameToken {
|
||||
@@ -122,7 +123,7 @@ func (b *Basic) VerifyAuthToken(req *http.Request, w http.ResponseWriter, store
|
||||
// name/token on successful validation.
|
||||
// Returns nil if header is empty or validation fails.
|
||||
func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) (*user_model.User, error) {
|
||||
parseBasicRet := b.parseAuthBasic(req)
|
||||
parseBasicRet := parseAuthBasic(req)
|
||||
authToken, uname, passwd := parseBasicRet.authToken, parseBasicRet.uname, parseBasicRet.passwd
|
||||
if authToken == "" && uname == "" {
|
||||
return nil, nil //nolint:nilnil // the auth method is not applicable
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
deploykey_model "gitea.dev/models/deploykey"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/log"
|
||||
)
|
||||
|
||||
var _ Method = &DeployToken{}
|
||||
|
||||
// DeployToken authenticates a deploy key token given as HTTP basic auth credential.
|
||||
// Only add it to an auth group where a repo scoped credential makes sense.
|
||||
type DeployToken struct{}
|
||||
|
||||
func (d *DeployToken) Name() string {
|
||||
return DeployTokenMethodName
|
||||
}
|
||||
|
||||
// Verify returns a user that stands for the deploy key alone. Its permissions come from the key,
|
||||
// see access_model.getDeployKeyRepoPermission, so the request can never reach another repository
|
||||
// or exceed the access mode of the key.
|
||||
func (d *DeployToken) Verify(req *http.Request, _ http.ResponseWriter, store DataStore, _ SessionStore) (*user_model.User, error) {
|
||||
authToken := parseAuthBasic(req).authToken
|
||||
if authToken == "" {
|
||||
return nil, nil //nolint:nilnil // the auth method is not applicable
|
||||
}
|
||||
|
||||
key, err := deploykey_model.VerifyDeployKeyToken(req.Context(), authToken)
|
||||
if err != nil {
|
||||
if deploykey_model.IsErrDeployKeyNotExist(err) {
|
||||
return nil, nil //nolint:nilnil // not a deploy token, let the other methods try
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := deploykey_model.UpdateDeployKeyLastUsed(req.Context(), key.ID); err != nil {
|
||||
log.Error("UpdateDeployKeyUpdated: %v", err)
|
||||
}
|
||||
|
||||
store.GetData()["LoginMethod"] = DeployTokenMethodName
|
||||
return user_model.NewDeployKeyUserWithKeyID(key.ID), nil
|
||||
}
|
||||
@@ -225,11 +225,11 @@ func NewBaseContext(resp http.ResponseWriter, req *http.Request) *Base {
|
||||
return b
|
||||
}
|
||||
|
||||
func NewBaseContextForTest(resp http.ResponseWriter, req *http.Request) *Base {
|
||||
func NewBaseContextForTest(t reqctx.TestingT, resp http.ResponseWriter, req *http.Request) *Base {
|
||||
if !setting.IsInTesting {
|
||||
panic("This function is only for testing")
|
||||
}
|
||||
ctx := reqctx.NewRequestContextForTest(req.Context())
|
||||
ctx := reqctx.NewRequestContextForTest(t)
|
||||
*req = *req.WithContext(ctx)
|
||||
return NewBaseContext(resp, req)
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ func TestRedirect(t *testing.T) {
|
||||
}
|
||||
for _, c := range cases {
|
||||
resp := httptest.NewRecorder()
|
||||
b := NewBaseContextForTest(resp, req)
|
||||
b := NewBaseContextForTest(t, resp, req)
|
||||
resp.Header().Add("Set-Cookie", (&http.Cookie{Name: setting.SessionConfig.CookieName, Value: "dummy"}).String())
|
||||
b.Redirect(c.url)
|
||||
has := resp.Header().Get("Set-Cookie") == "i_like_gitea=dummy"
|
||||
@@ -39,7 +39,7 @@ func TestRedirect(t *testing.T) {
|
||||
req, _ = http.NewRequest(http.MethodGet, "/", nil)
|
||||
resp := httptest.NewRecorder()
|
||||
req.Header.Add("X-Gitea-Fetch-Action", "1")
|
||||
b := NewBaseContextForTest(resp, req)
|
||||
b := NewBaseContextForTest(t, resp, req)
|
||||
b.Redirect("/other")
|
||||
assert.Contains(t, resp.Header().Get("Content-Type"), "application/json")
|
||||
assert.JSONEq(t, `{"redirect":"/other"}`, resp.Body.String())
|
||||
|
||||
@@ -42,7 +42,7 @@ func TestRedirectToCurrentSite(t *testing.T) {
|
||||
t.Run(c.location, func(t *testing.T) {
|
||||
req := &http.Request{URL: &url.URL{Path: "/"}}
|
||||
resp := httptest.NewRecorder()
|
||||
base := NewBaseContextForTest(resp, req)
|
||||
base := NewBaseContextForTest(t, resp, req)
|
||||
ctx := NewWebContext(base, nil, nil)
|
||||
ctx.RedirectToCurrentSite(c.location)
|
||||
redirect := test.RedirectURL(resp)
|
||||
@@ -58,7 +58,7 @@ func TestAppFullLink(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.PublicURLDetection, setting.PublicURLNever)()
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "https://gitea.example.com/sub/", nil)
|
||||
tmplCtx := NewTemplateContext(reqctx.NewRequestContextForTest(req.Context()), req)
|
||||
tmplCtx := NewTemplateContext(reqctx.NewRequestContextForTest(t), req)
|
||||
|
||||
assert.Equal(t, "https://gitea.example.com/sub", string(tmplCtx.AppFullLink()))
|
||||
assert.Equal(t, "https://gitea.example.com/sub/user/repo", string(tmplCtx.AppFullLink("user/repo")))
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/graceful"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/private"
|
||||
@@ -23,6 +24,7 @@ type PrivateContext struct {
|
||||
*Base
|
||||
Override context.Context
|
||||
|
||||
Doer *user_model.User
|
||||
Repo *Repository
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ func mockRequest(t *testing.T, reqPath string) *http.Request {
|
||||
requestURL, err := url.Parse(path)
|
||||
assert.NoError(t, err)
|
||||
req := &http.Request{Method: method, Host: requestURL.Host, URL: requestURL, Form: maps.Clone(requestURL.Query()), Header: http.Header{}}
|
||||
req = req.WithContext(reqctx.NewRequestContextForTest(req.Context()))
|
||||
req = req.WithContext(reqctx.NewRequestContextForTest(t))
|
||||
return req
|
||||
}
|
||||
|
||||
|
||||
+13
-10
@@ -20,6 +20,7 @@ import (
|
||||
asymkey_model "gitea.dev/models/asymkey"
|
||||
"gitea.dev/models/auth"
|
||||
"gitea.dev/models/db"
|
||||
deploykey_model "gitea.dev/models/deploykey"
|
||||
git_model "gitea.dev/models/git"
|
||||
issues_model "gitea.dev/models/issues"
|
||||
"gitea.dev/models/organization"
|
||||
@@ -846,19 +847,21 @@ func ToGitHook(h *git.Hook) *api.GitHook {
|
||||
}
|
||||
}
|
||||
|
||||
// ToDeployKey convert asymkey_model.DeployKey to api.DeployKey
|
||||
func ToDeployKey(ctx context.Context, repo *repo_model.Repository, deployKey *asymkey_model.DeployKey) *api.DeployKey {
|
||||
// ToDeployKey convert deploykey_model.DeployKey to api.DeployKey
|
||||
func ToDeployKey(ctx context.Context, repo *repo_model.Repository, deployKey *deploykey_model.DeployKey) *api.DeployKey {
|
||||
k := &api.DeployKey{
|
||||
ID: deployKey.ID,
|
||||
KeyID: deployKey.KeyID,
|
||||
URL: repo.APIURL(ctx) + fmt.Sprintf("/keys/%d", deployKey.ID),
|
||||
Title: deployKey.Name,
|
||||
Created: deployKey.CreatedUnix.AsTime(),
|
||||
ReadOnly: deployKey.Mode == perm.AccessModeRead, // All deploy keys are read-only.
|
||||
ID: deployKey.ID,
|
||||
KeyType: util.Iif(deployKey.KeyType == deploykey_model.KeyTypeSSH, "ssh", "token"),
|
||||
KeyID: deployKey.KeyID,
|
||||
Token: deployKey.Token,
|
||||
URL: repo.APIURL(ctx) + fmt.Sprintf("/keys/%d", deployKey.ID),
|
||||
Title: deployKey.Name,
|
||||
Fingerprint: deployKey.Fingerprint,
|
||||
Created: deployKey.CreatedUnix.AsTime(),
|
||||
ReadOnly: deployKey.IsReadOnly(),
|
||||
}
|
||||
if err := deployKey.LoadPublicKey(ctx); err == nil {
|
||||
if deployKey.KeyType == deploykey_model.KeyTypeSSH && deployKey.LoadPublicKey(ctx) == nil {
|
||||
k.Key = deployKey.PublicKey.Content
|
||||
k.Fingerprint = deployKey.PublicKey.Fingerprint
|
||||
}
|
||||
return k
|
||||
}
|
||||
|
||||
@@ -576,3 +576,10 @@ type SaveTopicForm struct {
|
||||
middleware.FormDefaultValidator
|
||||
Topics []string `binding:"topics;Required;"`
|
||||
}
|
||||
|
||||
// AddDeployTokenForm form for adding a deploy token to a repository
|
||||
type AddDeployTokenForm struct {
|
||||
middleware.FormDefaultValidator
|
||||
Title string `binding:"Required;MaxSize(50)"`
|
||||
IsWritable bool
|
||||
}
|
||||
|
||||
+14
-20
@@ -49,16 +49,18 @@ type requestContext struct {
|
||||
|
||||
// Claims is a JWT Token Claims
|
||||
type Claims struct {
|
||||
RepoID int64
|
||||
Op string
|
||||
UserID int64
|
||||
RepoID int64
|
||||
Op string
|
||||
UserID int64
|
||||
UserExtDoerData string
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
type AuthTokenOptions struct {
|
||||
Op string
|
||||
UserID int64
|
||||
RepoID int64
|
||||
Op string
|
||||
UserID int64
|
||||
UserExtDoerData string
|
||||
RepoID int64
|
||||
}
|
||||
|
||||
func GetLFSAuthTokenWithBearer(opts AuthTokenOptions) (string, error) {
|
||||
@@ -68,9 +70,10 @@ func GetLFSAuthTokenWithBearer(opts AuthTokenOptions) (string, error) {
|
||||
ExpiresAt: jwt.NewNumericDate(now.Add(setting.LFS.HTTPAuthExpiry)),
|
||||
NotBefore: jwt.NewNumericDate(now),
|
||||
},
|
||||
RepoID: opts.RepoID,
|
||||
Op: opts.Op,
|
||||
UserID: opts.UserID,
|
||||
RepoID: opts.RepoID,
|
||||
Op: opts.Op,
|
||||
UserID: opts.UserID,
|
||||
UserExtDoerData: opts.UserExtDoerData,
|
||||
}
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
|
||||
@@ -544,15 +547,6 @@ func authenticate(ctx *context.Context, repository *repo_model.Repository, autho
|
||||
accessMode = perm_model.AccessModeWrite
|
||||
}
|
||||
|
||||
if taskID, ok := user_model.GetActionsUserTaskID(ctx.Doer); ok {
|
||||
perm, err := access_model.GetActionsUserRepoPermission(ctx, repository, ctx.Doer, taskID)
|
||||
if err != nil {
|
||||
log.Error("Unable to GetActionsUserRepoPermission for task[%d] Error: %v", taskID, err)
|
||||
return false
|
||||
}
|
||||
return perm.CanAccess(accessMode, unit.TypeCode)
|
||||
}
|
||||
|
||||
// it works for both anonymous request and signed-in user, then perm.CanAccess will do the permission check
|
||||
perm, err := access_model.GetDoerRepoPermission(ctx, repository, ctx.Doer)
|
||||
if err != nil {
|
||||
@@ -604,9 +598,9 @@ func handleLFSToken(ctx stdCtx.Context, tokenSHA string, target *repo_model.Repo
|
||||
return nil, errors.New("invalid token claim")
|
||||
}
|
||||
|
||||
u, err := user_model.GetUserByID(ctx, claims.UserID)
|
||||
u, err := user_model.GetDoerUser(ctx, claims.UserID, claims.UserExtDoerData)
|
||||
if err != nil {
|
||||
log.Error("Unable to GetUserById[%d]: Error: %v", claims.UserID, err)
|
||||
log.Error("Unable to GetDoerUser[%d]: Error: %v", claims.UserID, err)
|
||||
return nil, err
|
||||
}
|
||||
if !u.IsActive || u.ProhibitLogin {
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
deploykey_model "gitea.dev/models/deploykey"
|
||||
perm_model "gitea.dev/models/perm"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/models/unittest"
|
||||
@@ -101,4 +102,23 @@ func TestAuthenticate(t *testing.T) {
|
||||
err := handleLFSTokenTestPerm("upload", 2, repo1, perm_model.AccessModeWrite)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
// a deploy-key doer has no user row, so the token must carry its ext doer data to stay redeemable
|
||||
t.Run("handleLFSToken resolves deploy-key doers", func(t *testing.T) {
|
||||
key, err := deploykey_model.AddDeployKeyToken(t.Context(), repo1.ID, "lfs", perm_model.AccessModeRead)
|
||||
require.NoError(t, err)
|
||||
doer := user_model.NewDeployKeyUserWithKeyID(key.ID)
|
||||
getDoerToken := func(op string) string {
|
||||
s, _ := GetLFSAuthTokenWithBearer(AuthTokenOptions{Op: op, UserID: doer.ID, UserExtDoerData: doer.ExtDoerData.EncodeToString(), RepoID: repo1.ID})
|
||||
_, token, _ := strings.Cut(s, " ")
|
||||
return token
|
||||
}
|
||||
|
||||
u, err := handleLFSToken(ctx, getDoerToken("download"), repo1, perm_model.AccessModeRead)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, user_model.DeployKeyUserID, u.ID)
|
||||
|
||||
_, err = handleLFSToken(ctx, getDoerToken("upload"), repo1, perm_model.AccessModeWrite)
|
||||
assert.ErrorContains(t, err, "no permission to access the repository")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ func TestRenderHelperMention(t *testing.T) {
|
||||
// when using web context, use user.IsUserVisibleToViewer to check
|
||||
req, err := http.NewRequest(http.MethodGet, "/", nil)
|
||||
assert.NoError(t, err)
|
||||
base := gitea_context.NewBaseContextForTest(httptest.NewRecorder(), req)
|
||||
base := gitea_context.NewBaseContextForTest(t, httptest.NewRecorder(), req)
|
||||
giteaCtx := gitea_context.NewWebContext(base, &contexttest.MockRender{}, nil)
|
||||
|
||||
assert.True(t, FormalRenderHelperFuncs().IsUsernameMentionable(giteaCtx, userPublic))
|
||||
|
||||
@@ -96,13 +96,8 @@ func pushQueueHandleUpdates(optsList []*repo_module.PushUpdateOptions) error {
|
||||
|
||||
if opts.RefFullName.IsTag() {
|
||||
if pusher == nil || pusher.ID != opts.PusherID {
|
||||
if opts.PusherID == user_model.ActionsUserID {
|
||||
pusher = user_model.NewActionsUser()
|
||||
} else {
|
||||
var err error
|
||||
if pusher, err = user_model.GetUserByID(ctx, opts.PusherID); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, pusher, err = user_model.GetPossibleUserByID(ctx, opts.PusherID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
tagName := opts.RefFullName.TagName()
|
||||
@@ -143,13 +138,8 @@ func pushQueueHandleUpdates(optsList []*repo_module.PushUpdateOptions) error {
|
||||
}
|
||||
} else if opts.RefFullName.IsBranch() {
|
||||
if pusher == nil || pusher.ID != opts.PusherID {
|
||||
if opts.PusherID == user_model.ActionsUserID {
|
||||
pusher = user_model.NewActionsUser()
|
||||
} else {
|
||||
var err error
|
||||
if pusher, err = user_model.GetUserByID(ctx, opts.PusherID); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, pusher, err = user_model.GetPossibleUserByID(ctx, opts.PusherID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user