completed phase 2b
This commit is contained in:
@@ -11,22 +11,32 @@ import (
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"xorm.io/xorm"
|
||||
|
||||
"github.com/forgeo/forgebucket/internal/config"
|
||||
"github.com/forgeo/forgebucket/internal/events"
|
||||
"github.com/forgeo/forgebucket/internal/models"
|
||||
)
|
||||
|
||||
type GitHTTPHandler struct {
|
||||
db *xorm.Engine
|
||||
cfg *config.Config
|
||||
bus events.EventBus
|
||||
}
|
||||
|
||||
func NewGitHTTPHandler(db *xorm.Engine, cfg *config.Config) *GitHTTPHandler {
|
||||
return &GitHTTPHandler{db: db, cfg: cfg}
|
||||
func NewGitHTTPHandler(db *xorm.Engine, cfg *config.Config, bus events.EventBus) *GitHTTPHandler {
|
||||
return &GitHTTPHandler{db: db, cfg: cfg, bus: bus}
|
||||
}
|
||||
|
||||
// refUpdate captures one ref-update line from a git-receive-pack request.
|
||||
type refUpdate struct {
|
||||
OldRev string
|
||||
NewRev string
|
||||
Ref string
|
||||
}
|
||||
|
||||
// ServeGit is the entry point for all git smart-HTTP requests.
|
||||
@@ -107,13 +117,15 @@ func (h *GitHTTPHandler) ServeGit(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Branch protection check: parse pkt-lines from the receive-pack body,
|
||||
// check each ref against stored protection rules, then restore the body.
|
||||
var pushedRefs []refUpdate
|
||||
if service == "git-receive-pack" {
|
||||
if reason, newBody := checkProtectionsFromBody(h.db, repo.ID, authedUser, r.Body); reason != "" {
|
||||
reason, refs, newBody := parseAndCheckBody(h.db, repo.ID, authedUser, r.Body)
|
||||
if reason != "" {
|
||||
http.Error(w, reason, http.StatusForbidden)
|
||||
return
|
||||
} else {
|
||||
r.Body = io.NopCloser(newBody)
|
||||
}
|
||||
pushedRefs = refs
|
||||
r.Body = io.NopCloser(newBody)
|
||||
}
|
||||
|
||||
// Build PATH_INFO: /{reponame}.git/{suffix}
|
||||
@@ -157,6 +169,27 @@ func (h *GitHTTPHandler) ServeGit(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if err := runGitBackend(r.Context(), w, r.Body, gitExec, env); err != nil {
|
||||
http.Error(w, fmt.Sprintf("git http-backend: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Publish push.received for each ref pushed so the CI orchestrator can react.
|
||||
if service == "git-receive-pack" {
|
||||
zeroOID := strings.Repeat("0", 40)
|
||||
for _, ref := range pushedRefs {
|
||||
if ref.NewRev == zeroOID {
|
||||
continue // branch deletion — skip CI trigger
|
||||
}
|
||||
go h.bus.Publish(events.SubjectPushReceived, events.PushEvent{ //nolint:errcheck
|
||||
RepoID: repo.ID,
|
||||
RepoName: repoName,
|
||||
OwnerName: owner,
|
||||
Ref: ref.Ref,
|
||||
Before: ref.OldRev,
|
||||
After: ref.NewRev,
|
||||
Pusher: authedUser,
|
||||
At: time.Now().UTC(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,15 +272,14 @@ func runGitBackend(ctx context.Context, w http.ResponseWriter, body io.Reader, g
|
||||
return waitErr
|
||||
}
|
||||
|
||||
// checkProtectionsFromBody parses git pkt-line ref updates from a receive-pack body,
|
||||
// checks each ref against stored branch protection rules, and returns a denial reason
|
||||
// (or "") plus a restored reader so the body can still be passed to http-backend.
|
||||
func checkProtectionsFromBody(db *xorm.Engine, repoID int64, pusher string, body io.Reader) (reason string, restored io.Reader) {
|
||||
// parseAndCheckBody parses git pkt-line ref updates from a receive-pack body,
|
||||
// checks each ref against stored branch protection rules, and returns a denial
|
||||
// reason (or ""), the list of parsed ref updates, and a restored reader.
|
||||
func parseAndCheckBody(db *xorm.Engine, repoID int64, pusher string, body io.Reader) (reason string, refs []refUpdate, restored io.Reader) {
|
||||
var buf bytes.Buffer
|
||||
zeroOID := strings.Repeat("0", 40)
|
||||
|
||||
for {
|
||||
// Every pkt-line starts with a 4-hex-digit length that includes itself.
|
||||
lenBuf := make([]byte, 4)
|
||||
if _, err := io.ReadFull(body, lenBuf); err != nil {
|
||||
break
|
||||
@@ -259,8 +291,7 @@ func checkProtectionsFromBody(db *xorm.Engine, repoID int64, pusher string, body
|
||||
break
|
||||
}
|
||||
if pktLen64 == 0 {
|
||||
// Flush packet — end of ref-update list.
|
||||
break
|
||||
break // flush packet — end of ref-update list
|
||||
}
|
||||
dataLen := int(pktLen64) - 4
|
||||
if dataLen <= 0 {
|
||||
@@ -280,16 +311,15 @@ func checkProtectionsFromBody(db *xorm.Engine, repoID int64, pusher string, body
|
||||
}
|
||||
oldRev, newRev, refname := parts[0], parts[1], parts[2]
|
||||
|
||||
// New branches (oldRev all zeros) are not subject to protection.
|
||||
if oldRev == zeroOID {
|
||||
continue
|
||||
}
|
||||
// Detect force push: if newRev is all zeros it's a branch deletion.
|
||||
isForcePush := newRev == zeroOID
|
||||
refs = append(refs, refUpdate{OldRev: oldRev, NewRev: newRev, Ref: refname})
|
||||
|
||||
if oldRev == zeroOID {
|
||||
continue // new branch — not subject to protection
|
||||
}
|
||||
isForcePush := newRev == zeroOID
|
||||
if msg := CheckBranchProtection(db, repoID, pusher, refname, isForcePush); msg != "" {
|
||||
return msg, io.MultiReader(&buf, body)
|
||||
return msg, refs, io.MultiReader(&buf, body)
|
||||
}
|
||||
}
|
||||
return "", io.MultiReader(&buf, body)
|
||||
return "", refs, io.MultiReader(&buf, body)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user