auth.go 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. package auth
  2. import (
  3. "net/http"
  4. "regexp"
  5. "strings"
  6. )
  7. type AuthInfo struct {
  8. // Usernane or email
  9. Username string
  10. // Plaintext password or token
  11. Password string
  12. // repo component of URL
  13. // Usually: "username/repo_name"
  14. // But could also be: "some_repo.git"
  15. Repo string
  16. // Are we pushing or fetching ?
  17. Push bool
  18. Fetch bool
  19. }
  20. var (
  21. repoNameRegex = regexp.MustCompile("^/?(.*?)/(HEAD|git-upload-pack|git-receive-pack|info/refs|objects/.*)$")
  22. )
  23. func Authenticator(authf func(AuthInfo) (bool, error)) func(http.Handler) http.Handler {
  24. return func(handler http.Handler) http.Handler {
  25. return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
  26. auth, err := parseAuthHeader(req.Header.Get("Authorization"))
  27. if err != nil {
  28. w.Header().Set("WWW-Authenticate", `Basic realm="git server"`)
  29. http.Error(w, err.Error(), 401)
  30. return
  31. }
  32. // Build up info from request headers and URL
  33. info := AuthInfo{
  34. Username: auth.Name,
  35. Password: auth.Pass,
  36. Repo: repoName(req.URL.Path),
  37. Push: isPush(req),
  38. Fetch: isFetch(req),
  39. }
  40. // Call authentication function
  41. authenticated, err := authf(info)
  42. if err != nil {
  43. code := 500
  44. msg := err.Error()
  45. if se, ok := err.(StatusError); ok {
  46. code = se.StatusCode()
  47. }
  48. http.Error(w, msg, code)
  49. return
  50. }
  51. // Deny access to repo
  52. if !authenticated {
  53. http.Error(w, "Forbidden", 403)
  54. return
  55. }
  56. // Access granted
  57. handler.ServeHTTP(w, req)
  58. })
  59. }
  60. }
  61. func isFetch(req *http.Request) bool {
  62. return isService("upload-pack", req)
  63. }
  64. func isPush(req *http.Request) bool {
  65. return isService("receive-pack", req)
  66. }
  67. func isService(service string, req *http.Request) bool {
  68. return getServiceType(req) == service || strings.HasSuffix(req.URL.Path, service)
  69. }
  70. func repoName(urlPath string) string {
  71. matches := repoNameRegex.FindStringSubmatch(urlPath)
  72. if matches == nil {
  73. return ""
  74. }
  75. return matches[1]
  76. }
  77. func getServiceType(r *http.Request) string {
  78. service_type := r.FormValue("service")
  79. if s := strings.HasPrefix(service_type, "git-"); !s {
  80. return ""
  81. }
  82. return strings.Replace(service_type, "git-", "", 1)
  83. }
  84. // StatusCode is an interface allowing authenticators
  85. // to pass down error's with an http error code
  86. type StatusError interface {
  87. StatusCode() int
  88. }