repo/http: fix client is not informed to provide credentials

When Git client has cached credentials for a site, missing response
header 'WWW-Authenticate: Basic realm="."' will result in Git client
does not prompt user to input credentials again but plain error
message and halts push/pull process.
master
Unknwon 2017-04-04 19:36:30 -04:00
parent d05395fe90
commit fe25effe7c
No known key found for this signature in database
GPG Key ID: 25B575AE3213B2B3
1 changed files with 35 additions and 26 deletions

View File

@ -48,68 +48,75 @@ type HTTPContext struct {
AuthUser *models.User
}
// askCredentials responses HTTP header and status which informs client to provide credentials.
func askCredentials(c *context.Context, status int, text string) {
c.Resp.Header().Set("WWW-Authenticate", "Basic realm=\".\"")
c.HandleText(status, text)
}
func HTTPContexter() macaron.Handler {
return func(ctx *context.Context) {
ownerName := ctx.Params(":username")
repoName := strings.TrimSuffix(ctx.Params(":reponame"), ".git")
return func(c *context.Context) {
ownerName := c.Params(":username")
repoName := strings.TrimSuffix(c.Params(":reponame"), ".git")
repoName = strings.TrimSuffix(repoName, ".wiki")
isPull := ctx.Query("service") == "git-upload-pack" ||
strings.HasSuffix(ctx.Req.URL.Path, "git-upload-pack") ||
ctx.Req.Method == "GET"
isPull := c.Query("service") == "git-upload-pack" ||
strings.HasSuffix(c.Req.URL.Path, "git-upload-pack") ||
c.Req.Method == "GET"
owner, err := models.GetUserByName(ownerName)
if err != nil {
ctx.NotFoundOrServerError("GetUserByName", errors.IsUserNotExist, err)
c.NotFoundOrServerError("GetUserByName", errors.IsUserNotExist, err)
return
}
repo, err := models.GetRepositoryByName(owner.ID, repoName)
if err != nil {
ctx.NotFoundOrServerError("GetRepositoryByName", errors.IsRepoNotExist, err)
c.NotFoundOrServerError("GetRepositoryByName", errors.IsRepoNotExist, err)
return
}
// Authentication is not required for pulling from public repositories.
if isPull && !repo.IsPrivate && !setting.Service.RequireSignInView {
ctx.Map(&HTTPContext{
Context: ctx,
c.Map(&HTTPContext{
Context: c,
})
return
}
// In case user requested a wrong URL and not intended to access Git objects.
action := ctx.Params("*")
action := c.Params("*")
if !strings.Contains(action, "git-") &&
!strings.Contains(action, "info/") &&
!strings.Contains(action, "HEAD") &&
!strings.Contains(action, "objects/") {
ctx.NotFound()
c.NotFound()
return
}
// Handle HTTP Basic Authentication
authHead := ctx.Req.Header.Get("Authorization")
authHead := c.Req.Header.Get("Authorization")
if len(authHead) == 0 {
ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=\".\"")
ctx.Error(http.StatusUnauthorized)
askCredentials(c, http.StatusUnauthorized, "")
return
}
auths := strings.Fields(authHead)
if len(auths) != 2 || auths[0] != "Basic" {
ctx.Error(http.StatusUnauthorized)
askCredentials(c, http.StatusUnauthorized, "")
return
}
authUsername, authPassword, err := base.BasicAuthDecode(auths[1])
if err != nil {
ctx.Error(http.StatusUnauthorized)
askCredentials(c, http.StatusUnauthorized, "")
return
}
fmt.Println(authUsername, authPassword)
authUser, err := models.UserSignIn(authUsername, authPassword)
if err != nil && !errors.IsUserNotExist(err) {
ctx.Handle(http.StatusInternalServerError, "UserSignIn", err)
c.Handle(http.StatusInternalServerError, "UserSignIn", err)
return
}
@ -118,9 +125,9 @@ func HTTPContexter() macaron.Handler {
token, err := models.GetAccessTokenBySHA(authUsername)
if err != nil {
if models.IsErrAccessTokenEmpty(err) || models.IsErrAccessTokenNotExist(err) {
ctx.Error(http.StatusUnauthorized)
askCredentials(c, http.StatusUnauthorized, "")
} else {
ctx.Handle(http.StatusInternalServerError, "GetAccessTokenBySHA", err)
c.Handle(http.StatusInternalServerError, "GetAccessTokenBySHA", err)
}
return
}
@ -130,31 +137,33 @@ func HTTPContexter() macaron.Handler {
if err != nil {
// Once we found token, we're supposed to find its related user,
// thus any error is unexpected.
ctx.Handle(http.StatusInternalServerError, "GetUserByID", err)
c.Handle(http.StatusInternalServerError, "GetUserByID", err)
return
}
}
log.Trace("HTTPGit - Authenticated user: %s", authUser.Name)
mode := models.ACCESS_MODE_WRITE
if isPull {
mode = models.ACCESS_MODE_READ
}
has, err := models.HasAccess(authUser.ID, repo, mode)
if err != nil {
ctx.Handle(http.StatusInternalServerError, "HasAccess", err)
c.Handle(http.StatusInternalServerError, "HasAccess", err)
return
} else if !has {
ctx.HandleText(http.StatusForbidden, "User permission denied")
askCredentials(c, http.StatusUnauthorized, "User permission denied")
return
}
if !isPull && repo.IsMirror {
ctx.HandleText(http.StatusForbidden, "Mirror repository is read-only")
c.HandleText(http.StatusForbidden, "Mirror repository is read-only")
return
}
ctx.Map(&HTTPContext{
Context: ctx,
c.Map(&HTTPContext{
Context: c,
OwnerName: ownerName,
OwnerSalt: owner.Salt,
RepoID: repo.ID,