mirror of
https://github.com/go-gitea/gitea.git
synced 2026-09-08 14:03:24 +09: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:
co-authored by
Claude Mythos
silverwind
bircni
wxiaoguang
parent
3c4d5a6a5c
commit
646ea0f253
@@ -4,18 +4,13 @@
|
||||
package private
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
git_model "gitea.dev/models/git"
|
||||
issues_model "gitea.dev/models/issues"
|
||||
access_model "gitea.dev/models/perm/access"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/cache"
|
||||
"gitea.dev/modules/cachegroup"
|
||||
"gitea.dev/modules/git"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/private"
|
||||
@@ -103,15 +98,11 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) {
|
||||
setting.PanicInDevOrTesting("wiki hook-post-receive is not supported")
|
||||
return
|
||||
}
|
||||
|
||||
ownerName := ctx.PathParam("owner")
|
||||
repoName := ctx.PathParam("repo")
|
||||
repo := loadRepository(ctx, ownerName, repoName)
|
||||
if ctx.Written() {
|
||||
if !loadContextDoerPermission(ctx, opts.UserID, opts.UserExtDoerData) {
|
||||
return
|
||||
}
|
||||
// now, repo can't be nil
|
||||
|
||||
repo := ctx.Repo.Repository
|
||||
// first, collect updates and sync branches
|
||||
updates := hookPostReceiveCollectPushUpdates(opts, repo)
|
||||
if !hookPostReceiveSyncDatabaseBranches(ctx, opts, repo, updates) {
|
||||
@@ -144,17 +135,7 @@ func hookPostReceiveUpdateRepoByOptions(ctx *gitea_context.PrivateContext, opts
|
||||
isTemplate := opts.GitPushOptions.Bool(private.GitPushOptionRepoTemplate)
|
||||
// Handle Push Options
|
||||
if isPrivate.Has() || isTemplate.Has() {
|
||||
pusher, err := loadContextCacheUser(ctx, opts.UserID)
|
||||
if err != nil {
|
||||
ctx.PrivateInternalErrorf("failed to load pusher user: %v", err)
|
||||
return false
|
||||
}
|
||||
perm, err := access_model.GetDoerRepoPermission(ctx, repo, pusher)
|
||||
if err != nil {
|
||||
ctx.PrivateInternalErrorf("failed to load doer repo permission: %v", err)
|
||||
return false
|
||||
}
|
||||
if !perm.IsOwner() && !perm.IsAdmin() {
|
||||
if !ctx.Repo.Permission.IsAdmin() {
|
||||
ctx.PrivateUserErrorf(http.StatusNotFound, "permission denied")
|
||||
return false
|
||||
}
|
||||
@@ -171,13 +152,13 @@ func hookPostReceiveUpdateRepoByOptions(ctx *gitea_context.PrivateContext, opts
|
||||
// yet; setting the flags directly is sufficient in this push-to-create case.
|
||||
if isPrivate.Has() && repo.IsPrivate != isPrivate.Value() {
|
||||
repo.IsPrivate = isPrivate.Value()
|
||||
if err = repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "is_private"); err != nil {
|
||||
if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "is_private"); err != nil {
|
||||
log.Error("failed to update repo is_private: %v", err)
|
||||
}
|
||||
}
|
||||
if isTemplate.Has() && repo.IsTemplate != isTemplate.Value() {
|
||||
repo.IsTemplate = isTemplate.Value()
|
||||
if err = repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "is_template"); err != nil {
|
||||
if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "is_template"); err != nil {
|
||||
log.Error("failed to update repo is_template: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -244,10 +225,6 @@ func hookPostReceiveRespondWithTrailer(ctx *gitea_context.PrivateContext, opts *
|
||||
ctx.JSON(http.StatusOK, private.HookPostReceiveResult{Results: results})
|
||||
}
|
||||
|
||||
func loadContextCacheUser(ctx context.Context, id int64) (*user_model.User, error) {
|
||||
return cache.GetWithContextCache(ctx, cachegroup.User, id, user_model.GetUserByID)
|
||||
}
|
||||
|
||||
// hookPostReceiveHandlePullRequestMerging handle pull request merging, a pull request action should push at least 1 commit
|
||||
func hookPostReceiveHandlePullRequestMerging(ctx *gitea_context.PrivateContext, opts *private.HookOptions, updates []*repo_module.PushUpdateOptions) bool {
|
||||
if len(updates) == 0 {
|
||||
@@ -261,15 +238,9 @@ func hookPostReceiveHandlePullRequestMerging(ctx *gitea_context.PrivateContext,
|
||||
return false
|
||||
}
|
||||
|
||||
pusher, err := loadContextCacheUser(ctx, opts.UserID)
|
||||
if err != nil {
|
||||
ctx.PrivateInternalErrorf("failed to load pusher user %d: %v", opts.UserID, err)
|
||||
return false
|
||||
}
|
||||
|
||||
// FIXME: Maybe we need a `PullRequestStatusMerged` status for PRs that are merged, currently we use the previous status
|
||||
// here to keep it as before, that maybe PullRequestStatusMergeable
|
||||
_, err = pull_service.SetMerged(ctx, pr, updates[len(updates)-1].NewCommitID, timeutil.TimeStampNow(), pusher, pr.Status)
|
||||
_, err = pull_service.SetMerged(ctx, pr, updates[len(updates)-1].NewCommitID, timeutil.TimeStampNow(), ctx.Doer, pr.Status)
|
||||
if err != nil {
|
||||
ctx.PrivateInternalErrorf("failed to set pr %d to merged: %v", pr.ID, err)
|
||||
return false
|
||||
|
||||
@@ -25,13 +25,14 @@ func TestHandlePullRequestMerging(t *testing.T) {
|
||||
assert.NoError(t, pr.LoadBaseRepo(t.Context()))
|
||||
|
||||
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
err = pull_model.ScheduleAutoMerge(t.Context(), user1, pr.ID, repo_model.MergeStyleSquash, "squash merge a pr", false)
|
||||
assert.NoError(t, err)
|
||||
|
||||
autoMerge := unittest.AssertExistsAndLoadBean(t, &pull_model.AutoMerge{PullID: pr.ID})
|
||||
|
||||
ctx, resp := contexttest.MockPrivateContext(t, "/")
|
||||
ctx.Doer = user2
|
||||
hookPostReceiveHandlePullRequestMerging(ctx, &private.HookOptions{
|
||||
PullRequestID: pr.ID,
|
||||
UserID: 2,
|
||||
|
||||
@@ -8,11 +8,8 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
asymkey_model "gitea.dev/models/asymkey"
|
||||
git_model "gitea.dev/models/git"
|
||||
issues_model "gitea.dev/models/issues"
|
||||
perm_model "gitea.dev/models/perm"
|
||||
access_model "gitea.dev/models/perm/access"
|
||||
"gitea.dev/models/unit"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/git"
|
||||
@@ -27,29 +24,18 @@ import (
|
||||
|
||||
type preReceiveContext struct {
|
||||
*gitea_context.PrivateContext
|
||||
|
||||
user *user_model.User // the "pusher", it's the org user if a DeployKey is used
|
||||
userPerm access_model.Permission
|
||||
deployKeyAccessMode perm_model.AccessMode
|
||||
|
||||
canCreatePullRequest bool
|
||||
checkedCanCreatePullRequest bool
|
||||
|
||||
protectedTags []*git_model.ProtectedTag
|
||||
gotProtectedTags bool
|
||||
|
||||
env []string
|
||||
|
||||
env []string
|
||||
opts *private.HookOptions
|
||||
|
||||
// this context should only contain shared variables, mutable variables like "current branch name" shouldn't be put here
|
||||
canWriteCodeUnitCached *bool
|
||||
canCreatePullRequest *bool
|
||||
protectedTags []*git_model.ProtectedTag
|
||||
}
|
||||
|
||||
func (ctx *preReceiveContext) canWriteCodeUnit() bool {
|
||||
if ctx.canWriteCodeUnitCached == nil {
|
||||
canWrite := ctx.userPerm.CanWrite(unit.TypeCode) || ctx.deployKeyAccessMode >= perm_model.AccessModeWrite
|
||||
ctx.canWriteCodeUnitCached = &canWrite
|
||||
ctx.canWriteCodeUnitCached = new(ctx.Repo.Permission.CanWrite(unit.TypeCode))
|
||||
}
|
||||
return *ctx.canWriteCodeUnitCached
|
||||
}
|
||||
@@ -63,7 +49,7 @@ func (ctx *preReceiveContext) canWriteCodeRef(refFullName git.RefName) bool {
|
||||
if !refFullName.IsBranch() {
|
||||
return false
|
||||
}
|
||||
return issues_model.CanMaintainerWriteToBranch(ctx, ctx.userPerm, refFullName.BranchName(), ctx.user)
|
||||
return issues_model.CanMaintainerWriteToBranch(ctx, ctx.Repo.Permission, refFullName.BranchName(), ctx.Doer)
|
||||
}
|
||||
|
||||
// assertCanWriteRef returns true if pusher can write to the code ref, otherwise it responds with 403 Forbidden and returns false
|
||||
@@ -80,11 +66,10 @@ func (ctx *preReceiveContext) assertCanWriteRef(refFullName git.RefName) bool {
|
||||
|
||||
// CanCreatePullRequest returns true if pusher can create pull requests
|
||||
func (ctx *preReceiveContext) CanCreatePullRequest() bool {
|
||||
if !ctx.checkedCanCreatePullRequest {
|
||||
ctx.canCreatePullRequest = ctx.userPerm.CanRead(unit.TypePullRequests)
|
||||
ctx.checkedCanCreatePullRequest = true
|
||||
if ctx.canCreatePullRequest == nil {
|
||||
ctx.canCreatePullRequest = new(ctx.Repo.Permission.CanRead(unit.TypePullRequests))
|
||||
}
|
||||
return ctx.canCreatePullRequest
|
||||
return *ctx.canCreatePullRequest
|
||||
}
|
||||
|
||||
// AssertCreatePullRequest returns true if can create pull requests
|
||||
@@ -102,6 +87,9 @@ func (ctx *preReceiveContext) AssertCreatePullRequest() bool {
|
||||
// HookPreReceive checks whether a individual commit is acceptable
|
||||
func HookPreReceive(ctx *gitea_context.PrivateContext) {
|
||||
opts := web.GetForm[*private.HookOptions](ctx)
|
||||
if !loadContextDoerPermission(ctx, opts.UserID, opts.UserExtDoerData) {
|
||||
return
|
||||
}
|
||||
|
||||
ourCtx := &preReceiveContext{
|
||||
PrivateContext: ctx,
|
||||
@@ -109,10 +97,6 @@ func HookPreReceive(ctx *gitea_context.PrivateContext) {
|
||||
opts: opts,
|
||||
}
|
||||
|
||||
if !ourCtx.loadPusherAndPermission() {
|
||||
return // if error occurs, loadPusherAndPermission had written the error response
|
||||
}
|
||||
|
||||
// Iterate across the provided old commit IDs
|
||||
for i := range opts.OldCommitIDs {
|
||||
oldCommitID := opts.OldCommitIDs[i]
|
||||
@@ -236,7 +220,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
|
||||
|
||||
// 5. Check if the doer is allowed to push (and force-push if the incoming push is a force-push)
|
||||
var canPush bool
|
||||
if ctx.opts.DeployKeyID != 0 {
|
||||
if ctx.opts.UserID == user_model.DeployKeyUserID {
|
||||
// This flag is only ever true if protectBranch.CanForcePush is true
|
||||
if isForcePush {
|
||||
canPush = !changedProtectedfiles && protectBranch.CanPush && (!protectBranch.EnableForcePushAllowlist || protectBranch.ForcePushAllowlistDeployKeys)
|
||||
@@ -245,9 +229,9 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
|
||||
}
|
||||
} else {
|
||||
if isForcePush {
|
||||
canPush = !changedProtectedfiles && protectBranch.CanUserForcePush(ctx, ctx.user)
|
||||
canPush = !changedProtectedfiles && protectBranch.CanUserForcePush(ctx, ctx.Doer)
|
||||
} else {
|
||||
canPush = !changedProtectedfiles && protectBranch.CanUserPush(ctx, ctx.user)
|
||||
canPush = !changedProtectedfiles && protectBranch.CanUserPush(ctx, ctx.Doer)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,7 +280,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
|
||||
|
||||
// Now check if the user is allowed to merge PRs for this repository
|
||||
// Note: we can use ctx.perm and ctx.user directly as they will have been loaded above
|
||||
allowedMerge, err := pull_service.IsUserAllowedToMerge(ctx, pr, ctx.userPerm, ctx.user)
|
||||
allowedMerge, err := pull_service.IsUserAllowedToMerge(ctx, pr, ctx.Repo.Permission, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.PrivateInternalErrorf("Error calculating if allowed to merge: %v", err)
|
||||
return
|
||||
@@ -308,7 +292,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
|
||||
}
|
||||
|
||||
// If we can bypass branch protection we can ignore status checks, reviews and protected files
|
||||
if git_model.CanBypassBranchProtection(ctx, protectBranch, ctx.user, ctx.userPerm.IsAdmin()) {
|
||||
if git_model.CanBypassBranchProtection(ctx, protectBranch, ctx.Doer, ctx.Repo.Permission.IsAdmin()) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -337,14 +321,14 @@ func preReceiveTag(ctx *preReceiveContext, refFullName git.RefName) {
|
||||
|
||||
tagName := refFullName.TagName()
|
||||
|
||||
if !ctx.gotProtectedTags {
|
||||
if ctx.protectedTags == nil {
|
||||
var err error
|
||||
ctx.protectedTags, err = git_model.GetProtectedTags(ctx, ctx.Repo.Repository.ID)
|
||||
if err != nil {
|
||||
ctx.PrivateInternalErrorf("Unable to get protected tags: %v", err)
|
||||
return
|
||||
}
|
||||
ctx.gotProtectedTags = true
|
||||
ctx.protectedTags = util.SliceNilAsEmpty(ctx.protectedTags)
|
||||
}
|
||||
|
||||
isAllowed, err := git_model.IsUserAllowedToControlTag(ctx, ctx.protectedTags, tagName, ctx.opts.UserID)
|
||||
@@ -399,45 +383,3 @@ func generateGitEnv(opts *private.HookOptions) (env []string) {
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
// loadPusherAndPermission returns false if an error occurs, and it writes the error response
|
||||
func (ctx *preReceiveContext) loadPusherAndPermission() bool {
|
||||
if ctx.opts.UserID == user_model.ActionsUserID {
|
||||
taskID := ctx.opts.ActionsTaskID
|
||||
ctx.user = user_model.NewActionsUserWithTaskID(taskID)
|
||||
if taskID == 0 {
|
||||
ctx.PrivateUserErrorf(http.StatusInternalServerError, "ActionsUser with task ID 0")
|
||||
return false
|
||||
}
|
||||
|
||||
userPerm, err := access_model.GetActionsUserRepoPermission(ctx, ctx.Repo.Repository, ctx.user, taskID)
|
||||
if err != nil {
|
||||
ctx.PrivateInternalErrorf("Unable to get Actions user repo permission for task %d Error: %v", taskID, err)
|
||||
return false
|
||||
}
|
||||
ctx.userPerm = userPerm
|
||||
} else {
|
||||
user, err := user_model.GetUserByID(ctx, ctx.opts.UserID)
|
||||
if err != nil {
|
||||
ctx.PrivateInternalErrorf("Unable to get User id %d Error: %v", ctx.opts.UserID, err)
|
||||
return false
|
||||
}
|
||||
ctx.user = user
|
||||
userPerm, err := access_model.GetDoerRepoPermission(ctx, ctx.Repo.Repository, user)
|
||||
if err != nil {
|
||||
ctx.PrivateInternalErrorf("Unable to get Repo permission of repo %s/%s of User %s: %v", ctx.Repo.Repository.OwnerName, ctx.Repo.Repository.Name, user.Name, err)
|
||||
return false
|
||||
}
|
||||
ctx.userPerm = userPerm
|
||||
}
|
||||
|
||||
if ctx.opts.DeployKeyID != 0 {
|
||||
deployKey, err := asymkey_model.GetDeployKeyByID(ctx, ctx.Repo.Repository.ID, ctx.opts.DeployKeyID)
|
||||
if err != nil {
|
||||
ctx.PrivateInternalErrorf("Unable to get DeployKey id %d Error: %v", ctx.opts.DeployKeyID, err)
|
||||
return false
|
||||
}
|
||||
ctx.deployKeyAccessMode = deployKey.Mode
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ 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"
|
||||
@@ -19,7 +18,7 @@ import (
|
||||
|
||||
// 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
|
||||
// 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())
|
||||
@@ -45,16 +44,12 @@ func TestPreReceiveCanWriteCodePerBranch(t *testing.T) {
|
||||
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,
|
||||
user: maintainer,
|
||||
userPerm: headPerm,
|
||||
}
|
||||
ctx := &preReceiveContext{PrivateContext: mockCtx}
|
||||
ctx.SetPathParam("owner", headRepo.OwnerName)
|
||||
ctx.SetPathParam("repo", headRepo.Name)
|
||||
RepoAssignment(ctx.PrivateContext)
|
||||
loadContextDoerPermission(ctx.PrivateContext, baseRepo.OwnerID, "")
|
||||
|
||||
// The granted branch must be writable...
|
||||
assert.True(t, ctx.canWriteCodeRef(git.RefNameFromBranch("granted-branch")))
|
||||
|
||||
@@ -23,8 +23,17 @@ func HookProcReceive(ctx *gitea_context.PrivateContext) {
|
||||
ctx.Status(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if !loadContextDoerPermission(ctx, opts.UserID, opts.UserExtDoerData) {
|
||||
return
|
||||
}
|
||||
|
||||
results, err := agit.ProcReceive(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, opts)
|
||||
results, err := agit.ProcReceive(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, &agit.ProcReceiveOptions{
|
||||
OldCommitIDs: opts.OldCommitIDs,
|
||||
NewCommitIDs: opts.NewCommitIDs,
|
||||
RefFullNames: opts.RefFullNames,
|
||||
GitPushOptions: opts.GitPushOptions,
|
||||
Doer: ctx.Doer,
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, issues_model.ErrMustCollaborator) {
|
||||
ctx.PrivateUserErrorf(http.StatusUnauthorized, "You must be a collaborator to create pull request.")
|
||||
|
||||
@@ -80,7 +80,7 @@ func Routes() *web.Router {
|
||||
r.Post("/ssh/{id}/update/{repoid}", UpdatePublicKeyInRepo)
|
||||
r.Post("/ssh/log", bind(private.SSHLogOption{}), SSHLog)
|
||||
r.Post("/hook/pre-receive/{owner}/{repo}", RepoAssignment, bind(private.HookOptions{}), HookPreReceive)
|
||||
r.Post("/hook/post-receive/{owner}/{repo}", context.OverrideContext(), bind(private.HookOptions{}), HookPostReceive)
|
||||
r.Post("/hook/post-receive/{owner}/{repo}", context.OverrideContext(), RepoAssignment, bind(private.HookOptions{}), HookPostReceive)
|
||||
r.Post("/hook/proc-receive/{owner}/{repo}", context.OverrideContext(), RepoAssignment, bind(private.HookOptions{}), HookProcReceive)
|
||||
r.Get("/serv/none/{keyid}", ServNoCommand)
|
||||
r.Get("/serv/command/{keyid}/{owner}/{repo}", ServCommand)
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
package private
|
||||
|
||||
import (
|
||||
"gitea.dev/models/perm/access"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/models/user"
|
||||
"gitea.dev/modules/git"
|
||||
gitea_context "gitea.dev/services/context"
|
||||
)
|
||||
@@ -27,10 +29,7 @@ func RepoAssignment(ctx *gitea_context.PrivateContext) {
|
||||
ctx.PrivateInternalErrorf("Failed to open repository: %s/%s Error: %v", ownerName, repoName, err)
|
||||
return
|
||||
}
|
||||
ctx.Repo = &gitea_context.Repository{
|
||||
Repository: repo,
|
||||
GitRepo: gitRepo,
|
||||
}
|
||||
ctx.Repo = &gitea_context.Repository{Repository: repo, GitRepo: gitRepo}
|
||||
}
|
||||
|
||||
func loadRepository(ctx *gitea_context.PrivateContext, ownerName, repoName string) *repo_model.Repository {
|
||||
@@ -44,3 +43,18 @@ func loadRepository(ctx *gitea_context.PrivateContext, ownerName, repoName strin
|
||||
}
|
||||
return repo
|
||||
}
|
||||
|
||||
func loadContextDoerPermission(ctx *gitea_context.PrivateContext, userID int64, extDoerData string) bool {
|
||||
doer, err := user.GetDoerUser(ctx, userID, extDoerData)
|
||||
if err != nil {
|
||||
ctx.PrivateInternalErrorf("Failed to get user: %d, error: %v", userID, err)
|
||||
return false
|
||||
}
|
||||
ctx.Doer = doer
|
||||
ctx.Repo.Permission, err = access.GetDoerRepoPermission(ctx, ctx.Repo.Repository, doer)
|
||||
if err != nil {
|
||||
ctx.PrivateInternalErrorf("Failed to get permission for user: %d, error: %v", userID, err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
asymkey_model "gitea.dev/models/asymkey"
|
||||
"gitea.dev/modules/timeutil"
|
||||
deploykey_model "gitea.dev/models/deploykey"
|
||||
"gitea.dev/services/context"
|
||||
)
|
||||
|
||||
@@ -20,17 +20,16 @@ func UpdatePublicKeyInRepo(ctx *context.PrivateContext) {
|
||||
return
|
||||
}
|
||||
|
||||
deployKey, err := asymkey_model.GetDeployKeyByRepoPublicKey(ctx, repoID, keyID)
|
||||
deployKey, err := deploykey_model.GetDeployKeyByRepoPublicKey(ctx, repoID, keyID)
|
||||
if err != nil {
|
||||
if asymkey_model.IsErrDeployKeyNotExist(err) {
|
||||
if deploykey_model.IsErrDeployKeyNotExist(err) {
|
||||
ctx.PlainText(http.StatusOK, "success")
|
||||
return
|
||||
}
|
||||
ctx.PrivateInternalErrorf("%v", err)
|
||||
return
|
||||
}
|
||||
deployKey.UpdatedUnix = timeutil.TimeStampNow()
|
||||
if err = asymkey_model.UpdateDeployKeyCols(ctx, deployKey, "updated_unix"); err != nil {
|
||||
if err = deploykey_model.UpdateDeployKeyLastUsed(ctx, deployKey.ID); err != nil {
|
||||
ctx.PrivateInternalErrorf("%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
+35
-54
@@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
|
||||
asymkey_model "gitea.dev/models/asymkey"
|
||||
deploykey_model "gitea.dev/models/deploykey"
|
||||
"gitea.dev/models/perm"
|
||||
access_model "gitea.dev/models/perm/access"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
@@ -73,9 +74,9 @@ func ServCommand(ctx *context.PrivateContext) {
|
||||
|
||||
// Set the basic parts of the results to return
|
||||
results := private.ServCommandResults{
|
||||
OwnerName: reqOwnerName, // it might be changed if there is "renamed user redirection"
|
||||
RepoName: reqRepoName, // it might be changed if there is "renamed repo redirection", or the repo is a wiki
|
||||
KeyID: keyID,
|
||||
OwnerName: reqOwnerName, // it might be changed if there is "renamed user redirection"
|
||||
RepoName: reqRepoName, // it might be changed if there is "renamed repo redirection", or the repo is a wiki
|
||||
PublicKeyID: keyID,
|
||||
}
|
||||
repoLogName := reqOwnerName + "/" + reqRepoName
|
||||
|
||||
@@ -184,40 +185,25 @@ func ServCommand(ctx *context.PrivateContext) {
|
||||
ctx.PrivateInternalErrorf("Unable to get key: %d, error: %v", keyID, err)
|
||||
return
|
||||
}
|
||||
results.KeyName = key.Name
|
||||
results.KeyID = key.ID
|
||||
results.UserID = key.OwnerID
|
||||
results.PublicKeyID = key.ID
|
||||
|
||||
// Deploy Keys have ownerID set to 0 therefore we can't use the owner
|
||||
// So now we need to check if the key is a deploy key
|
||||
// We'll keep hold of the deploy key here for permissions checking
|
||||
var deployKey *asymkey_model.DeployKey
|
||||
var deployKey *deploykey_model.DeployKey
|
||||
var user *user_model.User
|
||||
if key.Type == asymkey_model.KeyTypeDeploy {
|
||||
if repo == nil {
|
||||
ctx.PrivateUserErrorf(http.StatusNotFound, "Cannot find repository %s", repoLogName)
|
||||
return
|
||||
}
|
||||
deployKey, err = asymkey_model.GetDeployKeyByRepoPublicKey(ctx, repo.ID, key.ID)
|
||||
deployKey, err = deploykey_model.GetDeployKeyByRepoPublicKey(ctx, repo.ID, key.ID)
|
||||
if err != nil {
|
||||
if asymkey_model.IsErrDeployKeyNotExist(err) {
|
||||
ctx.PrivateUserErrorf(http.StatusNotFound, "Deploy key %d:%s has no %q permission for %s.", key.ID, key.Name, modeString, repoLogName)
|
||||
if deploykey_model.IsErrDeployKeyNotExist(err) {
|
||||
ctx.PrivateUserErrorf(http.StatusNotFound, "Deploy-key %d:%s has no %q permission for %s.", key.ID, key.Name, modeString, repoLogName)
|
||||
return
|
||||
}
|
||||
ctx.PrivateInternalErrorf("Unable to get deploy for public (deploy) key %d for %s, error: %v", key.ID, repoLogName, err)
|
||||
return
|
||||
}
|
||||
results.DeployKeyID = deployKey.ID
|
||||
results.KeyName = deployKey.Name
|
||||
|
||||
// FIXME: Deploy keys aren't really the owner of the repo pushing changes
|
||||
// however we don't have good way of representing deploy keys in hook.go
|
||||
// so for now use the owner of the repository
|
||||
results.UserName = results.OwnerName
|
||||
results.UserID = repo.OwnerID
|
||||
if !repo.Owner.KeepEmailPrivate {
|
||||
results.UserEmail = repo.Owner.Email
|
||||
}
|
||||
user = user_model.NewDeployKeyUserWithKeyID(deployKey.ID)
|
||||
} else {
|
||||
// Get the user represented by the Key
|
||||
user, err = user_model.GetUserByID(ctx, key.OwnerID)
|
||||
@@ -229,16 +215,19 @@ func ServCommand(ctx *context.PrivateContext) {
|
||||
ctx.PrivateInternalErrorf("Unable to get key owner %d for public key %d:%s, error: %v", key.OwnerID, key.ID, key.Name, err)
|
||||
return
|
||||
}
|
||||
|
||||
if !user.IsActive || user.ProhibitLogin {
|
||||
ctx.PrivateUserErrorf(http.StatusForbidden, "Your account is disabled.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
results.UserName = user.Name
|
||||
if !user.KeepEmailPrivate {
|
||||
results.UserEmail = user.Email
|
||||
}
|
||||
results.UserID = user.ID
|
||||
results.UserName = user.Name
|
||||
if !user.KeepEmailPrivate {
|
||||
results.UserEmail = user.Email
|
||||
}
|
||||
if user.ExtDoerData != nil {
|
||||
results.UserExtDoerData = user.ExtDoerData.EncodeToString()
|
||||
}
|
||||
|
||||
// Don't allow pushing if the repo is archived
|
||||
@@ -252,37 +241,29 @@ func ServCommand(ctx *context.PrivateContext) {
|
||||
(mode > perm.AccessModeRead ||
|
||||
repo.IsPrivate ||
|
||||
owner.Visibility.IsPrivate() ||
|
||||
(user != nil && user.IsRestricted) || // user will be nil if the key is a deploy key
|
||||
user.IsRestricted ||
|
||||
setting.Service.RequireSignInViewStrict) {
|
||||
if key.Type == asymkey_model.KeyTypeDeploy {
|
||||
if deployKey == nil || deployKey.Mode < mode {
|
||||
ctx.PrivateUserErrorf(http.StatusUnauthorized, "Deploy key %d:%s has no %q permission for %s.", key.ID, key.Name, modeString, repoLogName)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Because of the special ref "refs/for" (AGit) we will need to delay write permission check,
|
||||
// AGit flow needs to write its own ref when the doer has "reader" permission (allowing to create PR).
|
||||
// The real permission check is done in HookPreReceive (routers/private/hook_pre_receive.go).
|
||||
// Here it should relax the permission check for "git push (git-receive-pack)", but not for others like LFS operations.
|
||||
if git.DefaultFeatures().SupportProcReceive && unitType == unit.TypeCode && verb == git.CmdVerbReceivePack {
|
||||
mode = perm.AccessModeRead
|
||||
}
|
||||
// Because of the special ref "refs/for" (AGit) we will need to delay write permission check,
|
||||
// AGit flow needs to write its own ref when the doer has "reader" permission (allowing to create PR).
|
||||
// The real permission check is done in HookPreReceive (routers/private/hook_pre_receive.go).
|
||||
// Here it should relax the permission check for "git push (git-receive-pack)", but not for others like LFS operations.
|
||||
if git.DefaultFeatures().SupportProcReceive && unitType == unit.TypeCode && verb == git.CmdVerbReceivePack {
|
||||
mode = perm.AccessModeRead
|
||||
}
|
||||
|
||||
userPerm, err := access_model.GetDoerRepoPermission(ctx, repo, user)
|
||||
if err != nil {
|
||||
ctx.PrivateInternalErrorf("Unable to get permissions for %-v with key %d in %-v, error: %v", user, key.ID, repo, err)
|
||||
return
|
||||
}
|
||||
userPerm, err := access_model.GetDoerRepoPermission(ctx, repo, user)
|
||||
if err != nil {
|
||||
ctx.PrivateInternalErrorf("Unable to get permissions for %-v with key %d in %-v, error: %v", user, key.ID, repo, err)
|
||||
return
|
||||
}
|
||||
|
||||
userMode := userPerm.UnitAccessMode(unitType)
|
||||
if userMode < mode {
|
||||
ctx.PrivateUserErrorf(http.StatusUnauthorized, "User %d with key %d:%s has no %q permission for %s", key.OwnerID, key.ID, key.Name, modeString, repoLogName)
|
||||
return
|
||||
}
|
||||
userMode := userPerm.UnitAccessMode(unitType)
|
||||
if userMode < mode {
|
||||
ctx.PrivateUserErrorf(http.StatusUnauthorized, "User key %d:%s has no %q permission for %s", key.ID, key.Name, modeString, repoLogName)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// We already know we aren't using a deploy key
|
||||
if repo == nil {
|
||||
if owner.IsOrganization() && !setting.Repository.EnablePushCreateOrg {
|
||||
ctx.PrivateUserErrorf(http.StatusForbidden, "Push to create is not enabled for organizations.")
|
||||
|
||||
Reference in New Issue
Block a user