mirror of https://github.com/harness/drone.git
219 lines
6.1 KiB
Go
219 lines
6.1 KiB
Go
// Copyright 2022 Harness Inc. All rights reserved.
|
|
// Use of this source code is governed by the Polyform Free Trial License
|
|
// that can be found in the LICENSE.md file for this repository.
|
|
|
|
package repo
|
|
|
|
import (
|
|
"compress/gzip"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/harness/gitness/gitrpc"
|
|
apiauth "github.com/harness/gitness/internal/api/auth"
|
|
repoctrl "github.com/harness/gitness/internal/api/controller/repo"
|
|
"github.com/harness/gitness/internal/api/request"
|
|
"github.com/harness/gitness/internal/api/usererror"
|
|
"github.com/harness/gitness/internal/auth/authz"
|
|
"github.com/harness/gitness/internal/paths"
|
|
"github.com/harness/gitness/internal/store"
|
|
"github.com/harness/gitness/internal/url"
|
|
"github.com/harness/gitness/types/enum"
|
|
|
|
"github.com/rs/zerolog/hlog"
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
type CtxRepoType string
|
|
|
|
type GitAuthError struct {
|
|
AccountID string
|
|
}
|
|
|
|
func (e GitAuthError) Error() string {
|
|
return fmt.Sprintf("Authentication failed for account %s", e.AccountID)
|
|
}
|
|
|
|
func GetInfoRefs(client gitrpc.Interface, repoStore store.RepoStore, authorizer authz.Authorizer) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
session, _ := request.AuthSessionFrom(ctx)
|
|
repoRef, err := request.GetRepoRefFromPath(r)
|
|
if err != nil {
|
|
http.Error(w, usererror.Translate(err).Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
repo, err := repoStore.FindRepoFromRef(ctx, repoRef)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
accountID, _, err := paths.DisectRoot(repo.Path)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
if err = apiauth.CheckRepo(ctx, authorizer, session, repo, enum.PermissionRepoView, true); err != nil {
|
|
if errors.Is(err, apiauth.ErrNotAuthenticated) {
|
|
basicAuth(w, accountID)
|
|
return
|
|
}
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Clients MUST NOT reuse or revalidate a cached response.
|
|
// Servers MUST include sufficient Cache-Control headers to prevent caching of the response.
|
|
// https://git-scm.com/docs/http-protocol
|
|
setHeaderNoCache(w)
|
|
|
|
service := getServiceType(r)
|
|
log.Debug().Msgf("in GetInfoRefs: git service: %v", service)
|
|
w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-advertisement", service))
|
|
|
|
if err = client.GetInfoRefs(ctx, w, &gitrpc.InfoRefsParams{
|
|
ReadParams: repoctrl.CreateRPCReadParams(repo),
|
|
Service: service,
|
|
Options: nil,
|
|
GitProtocol: r.Header.Get("Git-Protocol"),
|
|
}); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
log.Err(err).Msgf("in GetInfoRefs: error occurred in service %v", service)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
}
|
|
}
|
|
|
|
func GetUploadPack(client gitrpc.Interface, urlProvider *url.Provider,
|
|
repoStore store.RepoStore, authorizer authz.Authorizer) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
const service = "upload-pack"
|
|
|
|
if err := serviceRPC(w, r, client, urlProvider, repoStore, authorizer, service, false,
|
|
enum.PermissionRepoView, true); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func PostReceivePack(client gitrpc.Interface, urlProvider *url.Provider,
|
|
repoStore store.RepoStore, authorizer authz.Authorizer) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
const service = "receive-pack"
|
|
if err := serviceRPC(w, r, client, urlProvider, repoStore, authorizer, service, true,
|
|
enum.PermissionRepoEdit, false); err != nil {
|
|
var authError *GitAuthError
|
|
if errors.As(err, &authError) {
|
|
basicAuth(w, authError.AccountID)
|
|
return
|
|
}
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func serviceRPC(
|
|
w http.ResponseWriter,
|
|
r *http.Request,
|
|
client gitrpc.Interface,
|
|
urlProvider *url.Provider,
|
|
repoStore store.RepoStore,
|
|
authorizer authz.Authorizer,
|
|
service string,
|
|
isWriteOperation bool,
|
|
permission enum.Permission,
|
|
orPublic bool,
|
|
) error {
|
|
ctx := r.Context()
|
|
log := hlog.FromRequest(r)
|
|
defer func() {
|
|
if err := r.Body.Close(); err != nil {
|
|
log.Err(err).Msgf("serviceRPC: Close: %v", err)
|
|
}
|
|
}()
|
|
|
|
session, _ := request.AuthSessionFrom(ctx)
|
|
repoRef, err := request.GetRepoRefFromPath(r)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
repo, err := repoStore.FindRepoFromRef(ctx, repoRef)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
accountID, _, err := paths.DisectRoot(repo.Path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = apiauth.CheckRepo(ctx, authorizer, session, repo, permission, orPublic); err != nil {
|
|
if errors.Is(err, apiauth.ErrNotAuthenticated) {
|
|
return &GitAuthError{
|
|
AccountID: accountID,
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-result", service))
|
|
|
|
reqBody := r.Body
|
|
|
|
// Handle GZIP.
|
|
if r.Header.Get("Content-Encoding") == "gzip" {
|
|
reqBody, err = gzip.NewReader(reqBody)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
params := &gitrpc.ServicePackParams{
|
|
Service: service,
|
|
Data: reqBody,
|
|
Options: nil,
|
|
GitProtocol: r.Header.Get("Git-Protocol"),
|
|
}
|
|
|
|
// setup read/writeparams depending on whether it's a write operation
|
|
if isWriteOperation {
|
|
var writeParams gitrpc.WriteParams
|
|
writeParams, err = repoctrl.CreateRPCWriteParams(ctx, urlProvider, session, repo)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create RPC write params: %w", err)
|
|
}
|
|
params.WriteParams = &writeParams
|
|
} else {
|
|
readParams := repoctrl.CreateRPCReadParams(repo)
|
|
params.ReadParams = &readParams
|
|
}
|
|
|
|
return client.ServicePack(ctx, w, params)
|
|
}
|
|
|
|
func setHeaderNoCache(w http.ResponseWriter) {
|
|
w.Header().Set("Expires", "Fri, 01 Jan 1980 00:00:00 GMT")
|
|
w.Header().Set("Pragma", "no-cache")
|
|
w.Header().Set("Cache-Control", "no-cache, max-age=0, must-revalidate")
|
|
}
|
|
|
|
func getServiceType(r *http.Request) string {
|
|
serviceType := r.FormValue("service")
|
|
if !strings.HasPrefix(serviceType, "git-") {
|
|
return ""
|
|
}
|
|
return strings.Replace(serviceType, "git-", "", 1)
|
|
}
|
|
|
|
func basicAuth(w http.ResponseWriter, accountID string) {
|
|
w.Header().Add("WWW-Authenticate", fmt.Sprintf(`Basic realm="%s"`, accountID))
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
}
|