code.gitea.io/gitea@v1.22.3/routers/private/hook_verification.go (about) 1 // Copyright 2021 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package private 5 6 import ( 7 "bufio" 8 "context" 9 "fmt" 10 "io" 11 "os" 12 13 asymkey_model "code.gitea.io/gitea/models/asymkey" 14 "code.gitea.io/gitea/modules/git" 15 "code.gitea.io/gitea/modules/log" 16 ) 17 18 // This file contains commit verification functions for refs passed across in hooks 19 20 func verifyCommits(oldCommitID, newCommitID string, repo *git.Repository, env []string) error { 21 stdoutReader, stdoutWriter, err := os.Pipe() 22 if err != nil { 23 log.Error("Unable to create os.Pipe for %s", repo.Path) 24 return err 25 } 26 defer func() { 27 _ = stdoutReader.Close() 28 _ = stdoutWriter.Close() 29 }() 30 31 var command *git.Command 32 objectFormat, _ := repo.GetObjectFormat() 33 if oldCommitID == objectFormat.EmptyObjectID().String() { 34 // When creating a new branch, the oldCommitID is empty, by using "newCommitID --not --all": 35 // List commits that are reachable by following the newCommitID, exclude "all" existing heads/tags commits 36 // So, it only lists the new commits received, doesn't list the commits already present in the receiving repository 37 command = git.NewCommand(repo.Ctx, "rev-list").AddDynamicArguments(newCommitID).AddArguments("--not", "--all") 38 } else { 39 command = git.NewCommand(repo.Ctx, "rev-list").AddDynamicArguments(oldCommitID + "..." + newCommitID) 40 } 41 // This is safe as force pushes are already forbidden 42 err = command.Run(&git.RunOpts{ 43 Env: env, 44 Dir: repo.Path, 45 Stdout: stdoutWriter, 46 PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error { 47 _ = stdoutWriter.Close() 48 err := readAndVerifyCommitsFromShaReader(stdoutReader, repo, env) 49 if err != nil { 50 log.Error("readAndVerifyCommitsFromShaReader failed: %v", err) 51 cancel() 52 } 53 _ = stdoutReader.Close() 54 return err 55 }, 56 }) 57 if err != nil && !isErrUnverifiedCommit(err) { 58 log.Error("Unable to check commits from %s to %s in %s: %v", oldCommitID, newCommitID, repo.Path, err) 59 } 60 return err 61 } 62 63 func readAndVerifyCommitsFromShaReader(input io.ReadCloser, repo *git.Repository, env []string) error { 64 scanner := bufio.NewScanner(input) 65 for scanner.Scan() { 66 line := scanner.Text() 67 err := readAndVerifyCommit(line, repo, env) 68 if err != nil { 69 return err 70 } 71 } 72 return scanner.Err() 73 } 74 75 func readAndVerifyCommit(sha string, repo *git.Repository, env []string) error { 76 stdoutReader, stdoutWriter, err := os.Pipe() 77 if err != nil { 78 log.Error("Unable to create pipe for %s: %v", repo.Path, err) 79 return err 80 } 81 defer func() { 82 _ = stdoutReader.Close() 83 _ = stdoutWriter.Close() 84 }() 85 86 commitID := git.MustIDFromString(sha) 87 88 return git.NewCommand(repo.Ctx, "cat-file", "commit").AddDynamicArguments(sha). 89 Run(&git.RunOpts{ 90 Env: env, 91 Dir: repo.Path, 92 Stdout: stdoutWriter, 93 PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error { 94 _ = stdoutWriter.Close() 95 commit, err := git.CommitFromReader(repo, commitID, stdoutReader) 96 if err != nil { 97 return err 98 } 99 verification := asymkey_model.ParseCommitWithSignature(ctx, commit) 100 if !verification.Verified { 101 cancel() 102 return &errUnverifiedCommit{ 103 commit.ID.String(), 104 } 105 } 106 return nil 107 }, 108 }) 109 } 110 111 type errUnverifiedCommit struct { 112 sha string 113 } 114 115 func (e *errUnverifiedCommit) Error() string { 116 return fmt.Sprintf("Unverified commit: %s", e.sha) 117 } 118 119 func isErrUnverifiedCommit(err error) bool { 120 _, ok := err.(*errUnverifiedCommit) 121 return ok 122 }