2022-02-09 23:28:55 +03:00
// Copyright 2021 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2022-02-09 23:28:55 +03:00
package files
import (
"context"
"fmt"
"strings"
"code.gitea.io/gitea/models"
2022-06-12 18:51:54 +03:00
git_model "code.gitea.io/gitea/models/git"
2022-02-09 23:28:55 +03:00
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/structs"
asymkey_service "code.gitea.io/gitea/services/asymkey"
)
// ApplyDiffPatchOptions holds the repository diff patch update options
type ApplyDiffPatchOptions struct {
LastCommitID string
OldBranch string
NewBranch string
Message string
Content string
SHA string
Author * IdentityOptions
Committer * IdentityOptions
Dates * CommitDateOptions
Signoff bool
}
// Validate validates the provided options
func ( opts * ApplyDiffPatchOptions ) Validate ( ctx context . Context , repo * repo_model . Repository , doer * user_model . User ) error {
// If no branch name is set, assume master
if opts . OldBranch == "" {
opts . OldBranch = repo . DefaultBranch
}
if opts . NewBranch == "" {
opts . NewBranch = opts . OldBranch
}
gitRepo , closer , err := git . RepositoryFromContextOrOpen ( ctx , repo . RepoPath ( ) )
if err != nil {
return err
}
defer closer . Close ( )
// oldBranch must exist for this operation
if _ , err := gitRepo . GetBranch ( opts . OldBranch ) ; err != nil {
return err
}
// A NewBranch can be specified for the patch to be applied to.
// Check to make sure the branch does not already exist, otherwise we can't proceed.
// If we aren't branching to a new branch, make sure user can commit to the given branch
if opts . NewBranch != opts . OldBranch {
existingBranch , err := gitRepo . GetBranch ( opts . NewBranch )
if existingBranch != nil {
return models . ErrBranchAlreadyExists {
BranchName : opts . NewBranch ,
}
}
if err != nil && ! git . IsErrBranchNotExist ( err ) {
return err
}
} else {
2023-01-16 11:00:22 +03:00
protectedBranch , err := git_model . GetFirstMatchProtectedBranchRule ( ctx , repo . ID , opts . OldBranch )
2022-02-09 23:28:55 +03:00
if err != nil {
return err
}
2023-01-16 11:00:22 +03:00
if protectedBranch != nil {
protectedBranch . Repo = repo
if ! protectedBranch . CanUserPush ( ctx , doer ) {
return models . ErrUserCannotCommit {
UserName : doer . LowerName ,
}
2022-02-09 23:28:55 +03:00
}
}
if protectedBranch != nil && protectedBranch . RequireSignedCommits {
_ , _ , _ , err := asymkey_service . SignCRUDAction ( ctx , repo . RepoPath ( ) , doer , repo . RepoPath ( ) , opts . OldBranch )
if err != nil {
if ! asymkey_service . IsErrWontSign ( err ) {
return err
}
return models . ErrUserCannotCommit {
UserName : doer . LowerName ,
}
}
}
}
return nil
}
// ApplyDiffPatch applies a patch to the given repository
func ApplyDiffPatch ( ctx context . Context , repo * repo_model . Repository , doer * user_model . User , opts * ApplyDiffPatchOptions ) ( * structs . FileResponse , error ) {
if err := opts . Validate ( ctx , repo , doer ) ; err != nil {
return nil , err
}
message := strings . TrimSpace ( opts . Message )
author , committer := GetAuthorAndCommitterUsers ( opts . Author , opts . Committer , doer )
t , err := NewTemporaryUploadRepository ( ctx , repo )
if err != nil {
log . Error ( "%v" , err )
}
defer t . Close ( )
if err := t . Clone ( opts . OldBranch ) ; err != nil {
return nil , err
}
if err := t . SetDefaultIndex ( ) ; err != nil {
return nil , err
}
// Get the commit of the original branch
commit , err := t . GetBranchCommit ( opts . OldBranch )
if err != nil {
return nil , err // Couldn't get a commit for the branch
}
// Assigned LastCommitID in opts if it hasn't been set
if opts . LastCommitID == "" {
opts . LastCommitID = commit . ID . String ( )
} else {
lastCommitID , err := t . gitRepo . ConvertToSHA1 ( opts . LastCommitID )
if err != nil {
2022-10-24 22:29:17 +03:00
return nil , fmt . Errorf ( "ApplyPatch: Invalid last commit ID: %w" , err )
2022-02-09 23:28:55 +03:00
}
opts . LastCommitID = lastCommitID . String ( )
if commit . ID . String ( ) != opts . LastCommitID {
return nil , models . ErrCommitIDDoesNotMatch {
GivenCommitID : opts . LastCommitID ,
CurrentCommitID : opts . LastCommitID ,
}
}
}
stdout := & strings . Builder { }
stderr := & strings . Builder { }
Refactor git command package to improve security and maintainability (#22678)
This PR follows #21535 (and replace #22592)
## Review without space diff
https://github.com/go-gitea/gitea/pull/22678/files?diff=split&w=1
## Purpose of this PR
1. Make git module command completely safe (risky user inputs won't be
passed as argument option anymore)
2. Avoid low-level mistakes like
https://github.com/go-gitea/gitea/pull/22098#discussion_r1045234918
3. Remove deprecated and dirty `CmdArgCheck` function, hide the `CmdArg`
type
4. Simplify code when using git command
## The main idea of this PR
* Move the `git.CmdArg` to the `internal` package, then no other package
except `git` could use it. Then developers could never do
`AddArguments(git.CmdArg(userInput))` any more.
* Introduce `git.ToTrustedCmdArgs`, it's for user-provided and already
trusted arguments. It's only used in a few cases, for example: use git
arguments from config file, help unit test with some arguments.
* Introduce `AddOptionValues` and `AddOptionFormat`, they make code more
clear and simple:
* Before: `AddArguments("-m").AddDynamicArguments(message)`
* After: `AddOptionValues("-m", message)`
* -
* Before: `AddArguments(git.CmdArg(fmt.Sprintf("--author='%s <%s>'",
sig.Name, sig.Email)))`
* After: `AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email)`
## FAQ
### Why these changes were not done in #21535 ?
#21535 is mainly a search&replace, it did its best to not change too
much logic.
Making the framework better needs a lot of changes, so this separate PR
is needed as the second step.
### The naming of `AddOptionXxx`
According to git's manual, the `--xxx` part is called `option`.
### How can it guarantee that `internal.CmdArg` won't be not misused?
Go's specification guarantees that. Trying to access other package's
internal package causes compilation error.
And, `golangci-lint` also denies the git/internal package. Only the
`git/command.go` can use it carefully.
### There is still a `ToTrustedCmdArgs`, will it still allow developers
to make mistakes and pass untrusted arguments?
Generally speaking, no. Because when using `ToTrustedCmdArgs`, the code
will be very complex (see the changes for examples). Then developers and
reviewers can know that something might be unreasonable.
### Why there was a `CmdArgCheck` and why it's removed?
At the moment of #21535, to reduce unnecessary changes, `CmdArgCheck`
was introduced as a hacky patch. Now, almost all code could be written
as `cmd := NewCommand(); cmd.AddXxx(...)`, then there is no need for
`CmdArgCheck` anymore.
### Why many codes for `signArg == ""` is deleted?
Because in the old code, `signArg` could never be empty string, it's
either `-S[key-id]` or `--no-gpg-sign`. So the `signArg == ""` is just
dead code.
---------
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-02-04 05:30:43 +03:00
cmdApply := git . NewCommand ( ctx , "apply" , "--index" , "--recount" , "--cached" , "--ignore-whitespace" , "--whitespace=fix" , "--binary" )
2022-02-09 23:28:55 +03:00
if git . CheckGitVersionAtLeast ( "2.32" ) == nil {
Refactor git command package to improve security and maintainability (#22678)
This PR follows #21535 (and replace #22592)
## Review without space diff
https://github.com/go-gitea/gitea/pull/22678/files?diff=split&w=1
## Purpose of this PR
1. Make git module command completely safe (risky user inputs won't be
passed as argument option anymore)
2. Avoid low-level mistakes like
https://github.com/go-gitea/gitea/pull/22098#discussion_r1045234918
3. Remove deprecated and dirty `CmdArgCheck` function, hide the `CmdArg`
type
4. Simplify code when using git command
## The main idea of this PR
* Move the `git.CmdArg` to the `internal` package, then no other package
except `git` could use it. Then developers could never do
`AddArguments(git.CmdArg(userInput))` any more.
* Introduce `git.ToTrustedCmdArgs`, it's for user-provided and already
trusted arguments. It's only used in a few cases, for example: use git
arguments from config file, help unit test with some arguments.
* Introduce `AddOptionValues` and `AddOptionFormat`, they make code more
clear and simple:
* Before: `AddArguments("-m").AddDynamicArguments(message)`
* After: `AddOptionValues("-m", message)`
* -
* Before: `AddArguments(git.CmdArg(fmt.Sprintf("--author='%s <%s>'",
sig.Name, sig.Email)))`
* After: `AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email)`
## FAQ
### Why these changes were not done in #21535 ?
#21535 is mainly a search&replace, it did its best to not change too
much logic.
Making the framework better needs a lot of changes, so this separate PR
is needed as the second step.
### The naming of `AddOptionXxx`
According to git's manual, the `--xxx` part is called `option`.
### How can it guarantee that `internal.CmdArg` won't be not misused?
Go's specification guarantees that. Trying to access other package's
internal package causes compilation error.
And, `golangci-lint` also denies the git/internal package. Only the
`git/command.go` can use it carefully.
### There is still a `ToTrustedCmdArgs`, will it still allow developers
to make mistakes and pass untrusted arguments?
Generally speaking, no. Because when using `ToTrustedCmdArgs`, the code
will be very complex (see the changes for examples). Then developers and
reviewers can know that something might be unreasonable.
### Why there was a `CmdArgCheck` and why it's removed?
At the moment of #21535, to reduce unnecessary changes, `CmdArgCheck`
was introduced as a hacky patch. Now, almost all code could be written
as `cmd := NewCommand(); cmd.AddXxx(...)`, then there is no need for
`CmdArgCheck` anymore.
### Why many codes for `signArg == ""` is deleted?
Because in the old code, `signArg` could never be empty string, it's
either `-S[key-id]` or `--no-gpg-sign`. So the `signArg == ""` is just
dead code.
---------
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-02-04 05:30:43 +03:00
cmdApply . AddArguments ( "-3" )
2022-02-09 23:28:55 +03:00
}
Refactor git command package to improve security and maintainability (#22678)
This PR follows #21535 (and replace #22592)
## Review without space diff
https://github.com/go-gitea/gitea/pull/22678/files?diff=split&w=1
## Purpose of this PR
1. Make git module command completely safe (risky user inputs won't be
passed as argument option anymore)
2. Avoid low-level mistakes like
https://github.com/go-gitea/gitea/pull/22098#discussion_r1045234918
3. Remove deprecated and dirty `CmdArgCheck` function, hide the `CmdArg`
type
4. Simplify code when using git command
## The main idea of this PR
* Move the `git.CmdArg` to the `internal` package, then no other package
except `git` could use it. Then developers could never do
`AddArguments(git.CmdArg(userInput))` any more.
* Introduce `git.ToTrustedCmdArgs`, it's for user-provided and already
trusted arguments. It's only used in a few cases, for example: use git
arguments from config file, help unit test with some arguments.
* Introduce `AddOptionValues` and `AddOptionFormat`, they make code more
clear and simple:
* Before: `AddArguments("-m").AddDynamicArguments(message)`
* After: `AddOptionValues("-m", message)`
* -
* Before: `AddArguments(git.CmdArg(fmt.Sprintf("--author='%s <%s>'",
sig.Name, sig.Email)))`
* After: `AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email)`
## FAQ
### Why these changes were not done in #21535 ?
#21535 is mainly a search&replace, it did its best to not change too
much logic.
Making the framework better needs a lot of changes, so this separate PR
is needed as the second step.
### The naming of `AddOptionXxx`
According to git's manual, the `--xxx` part is called `option`.
### How can it guarantee that `internal.CmdArg` won't be not misused?
Go's specification guarantees that. Trying to access other package's
internal package causes compilation error.
And, `golangci-lint` also denies the git/internal package. Only the
`git/command.go` can use it carefully.
### There is still a `ToTrustedCmdArgs`, will it still allow developers
to make mistakes and pass untrusted arguments?
Generally speaking, no. Because when using `ToTrustedCmdArgs`, the code
will be very complex (see the changes for examples). Then developers and
reviewers can know that something might be unreasonable.
### Why there was a `CmdArgCheck` and why it's removed?
At the moment of #21535, to reduce unnecessary changes, `CmdArgCheck`
was introduced as a hacky patch. Now, almost all code could be written
as `cmd := NewCommand(); cmd.AddXxx(...)`, then there is no need for
`CmdArgCheck` anymore.
### Why many codes for `signArg == ""` is deleted?
Because in the old code, `signArg` could never be empty string, it's
either `-S[key-id]` or `--no-gpg-sign`. So the `signArg == ""` is just
dead code.
---------
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-02-04 05:30:43 +03:00
if err := cmdApply . Run ( & git . RunOpts {
2022-04-01 05:55:30 +03:00
Dir : t . basePath ,
Stdout : stdout ,
Stderr : stderr ,
Stdin : strings . NewReader ( opts . Content ) ,
2022-02-09 23:28:55 +03:00
} ) ; err != nil {
2022-10-24 22:29:17 +03:00
return nil , fmt . Errorf ( "Error: Stdout: %s\nStderr: %s\nErr: %w" , stdout . String ( ) , stderr . String ( ) , err )
2022-02-09 23:28:55 +03:00
}
// Now write the tree
treeHash , err := t . WriteTree ( )
if err != nil {
return nil , err
}
// Now commit the tree
var commitHash string
if opts . Dates != nil {
2022-03-28 22:48:41 +03:00
commitHash , err = t . CommitTreeWithDate ( "HEAD" , author , committer , treeHash , message , opts . Signoff , opts . Dates . Author , opts . Dates . Committer )
2022-02-09 23:28:55 +03:00
} else {
2022-03-28 22:48:41 +03:00
commitHash , err = t . CommitTree ( "HEAD" , author , committer , treeHash , message , opts . Signoff )
2022-02-09 23:28:55 +03:00
}
if err != nil {
return nil , err
}
// Then push this tree to NewBranch
if err := t . Push ( doer , commitHash , opts . NewBranch ) ; err != nil {
return nil , err
}
commit , err = t . GetCommit ( commitHash )
if err != nil {
return nil , err
}
fileCommitResponse , _ := GetFileCommitResponse ( repo , commit ) // ok if fails, then will be nil
verification := GetPayloadCommitVerification ( commit )
fileResponse := & structs . FileResponse {
Commit : fileCommitResponse ,
Verification : verification ,
}
return fileResponse , nil
}