code.gitea.io/gitea@v1.21.7/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 if oldCommitID == git.EmptySHA { 33 // When creating a new branch, the oldCommitID is empty, by using "newCommitID --not --all": 34 // List commits that are reachable by following the newCommitID, exclude "all" existing heads/tags commits 35 // So, it only lists the new commits received, doesn't list the commits already present in the receiving repository 36 command = git.NewCommand(repo.Ctx, "rev-list").AddDynamicArguments(newCommitID).AddArguments("--not", "--all") 37 } else { 38 command = git.NewCommand(repo.Ctx, "rev-list").AddDynamicArguments(oldCommitID + "..." + newCommitID) 39 } 40 // This is safe as force pushes are already forbidden 41 err = command.Run(&git.RunOpts{ 42 Env: env, 43 Dir: repo.Path, 44 Stdout: stdoutWriter, 45 PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error { 46 _ = stdoutWriter.Close() 47 err := readAndVerifyCommitsFromShaReader(stdoutReader, repo, env) 48 if err != nil { 49 log.Error("%v", err) 50 cancel() 51 } 52 _ = stdoutReader.Close() 53 return err 54 }, 55 }) 56 if err != nil && !isErrUnverifiedCommit(err) { 57 log.Error("Unable to check commits from %s to %s in %s: %v", oldCommitID, newCommitID, repo.Path, err) 58 } 59 return err 60 } 61 62 func readAndVerifyCommitsFromShaReader(input io.ReadCloser, repo *git.Repository, env []string) error { 63 scanner := bufio.NewScanner(input) 64 for scanner.Scan() { 65 line := scanner.Text() 66 err := readAndVerifyCommit(line, repo, env) 67 if err != nil { 68 log.Error("%v", err) 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 hash := git.MustIDFromString(sha) 86 87 return git.NewCommand(repo.Ctx, "cat-file", "commit").AddDynamicArguments(sha). 88 Run(&git.RunOpts{ 89 Env: env, 90 Dir: repo.Path, 91 Stdout: stdoutWriter, 92 PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error { 93 _ = stdoutWriter.Close() 94 commit, err := git.CommitFromReader(repo, hash, stdoutReader) 95 if err != nil { 96 return err 97 } 98 verification := asymkey_model.ParseCommitWithSignature(ctx, commit) 99 if !verification.Verified { 100 cancel() 101 return &errUnverifiedCommit{ 102 commit.ID.String(), 103 } 104 } 105 return nil 106 }, 107 }) 108 } 109 110 type errUnverifiedCommit struct { 111 sha string 112 } 113 114 func (e *errUnverifiedCommit) Error() string { 115 return fmt.Sprintf("Unverified commit: %s", e.sha) 116 } 117 118 func isErrUnverifiedCommit(err error) bool { 119 _, ok := err.(*errUnverifiedCommit) 120 return ok 121 }