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