Prevent double-login for Git HTTP and LFS and simplify login (#15303)
* Prevent double-login for Git HTTP and LFS and simplify login There are a number of inconsistencies with our current methods for logging in for git and lfs. The first is that there is a double login process. This is particularly evident in 1.13 where there are no less than 4 hash checks for basic authentication due to the previous IsPasswordSet behaviour. This duplicated code had individual inconsistencies that were not helpful and caused confusion. This PR does the following: * Remove the specific login code from the git and lfs handlers except for the lfs special bearer token * Simplify the meaning of DisableBasicAuthentication to allow Token and Oauth2 sign-in. * The removal of the specific code from git and lfs means that these both now have the same login semantics and can - if not DisableBasicAuthentication - login from external services. Further it allows Oauth2 token authentication as per our standard mechanisms. * The change in the recovery handler prevents the service from re-attempting to login - primarily because this could easily cause a further panic and it is wasteful. * add test Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: Andrew Thornton <art27@cantab.net>
This commit is contained in:
parent
ba526ceffe
commit
17c5c654a5
10 changed files with 292 additions and 221 deletions
|
@ -44,7 +44,7 @@ func TestAPILFSLocksNotLogin(t *testing.T) {
|
||||||
resp := MakeRequest(t, req, http.StatusUnauthorized)
|
resp := MakeRequest(t, req, http.StatusUnauthorized)
|
||||||
var lfsLockError api.LFSLockError
|
var lfsLockError api.LFSLockError
|
||||||
DecodeJSON(t, resp, &lfsLockError)
|
DecodeJSON(t, resp, &lfsLockError)
|
||||||
assert.Equal(t, "Unauthorized", lfsLockError.Message)
|
assert.Equal(t, "You must have pull access to list locks", lfsLockError.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAPILFSLocksLogged(t *testing.T) {
|
func TestAPILFSLocksLogged(t *testing.T) {
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
"code.gitea.io/gitea/modules/web/middleware"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Ensure the struct implements the interface.
|
// Ensure the struct implements the interface.
|
||||||
|
@ -40,7 +41,7 @@ func (b *Basic) Free() error {
|
||||||
// IsEnabled returns true as this plugin is enabled by default and its not possible to disable
|
// IsEnabled returns true as this plugin is enabled by default and its not possible to disable
|
||||||
// it from settings.
|
// it from settings.
|
||||||
func (b *Basic) IsEnabled() bool {
|
func (b *Basic) IsEnabled() bool {
|
||||||
return setting.Service.EnableBasicAuth
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifyAuthData extracts and validates Basic data (username and password/token) from the
|
// VerifyAuthData extracts and validates Basic data (username and password/token) from the
|
||||||
|
@ -48,17 +49,22 @@ func (b *Basic) IsEnabled() bool {
|
||||||
// name/token on successful validation.
|
// name/token on successful validation.
|
||||||
// Returns nil if header is empty or validation fails.
|
// Returns nil if header is empty or validation fails.
|
||||||
func (b *Basic) VerifyAuthData(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) *models.User {
|
func (b *Basic) VerifyAuthData(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) *models.User {
|
||||||
|
|
||||||
|
// Basic authentication should only fire on API, Download or on Git or LFSPaths
|
||||||
|
if middleware.IsInternalPath(req) || !middleware.IsAPIPath(req) && !isAttachmentDownload(req) && !isGitOrLFSPath(req) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
baHead := req.Header.Get("Authorization")
|
baHead := req.Header.Get("Authorization")
|
||||||
if len(baHead) == 0 {
|
if len(baHead) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
auths := strings.Fields(baHead)
|
auths := strings.SplitN(baHead, " ", 2)
|
||||||
if len(auths) != 2 || (auths[0] != "Basic" && auths[0] != "basic") {
|
if len(auths) != 2 || (auths[0] != "Basic" && auths[0] != "basic") {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var u *models.User
|
|
||||||
uname, passwd, _ := base.BasicAuthDecode(auths[1])
|
uname, passwd, _ := base.BasicAuthDecode(auths[1])
|
||||||
|
|
||||||
// Check if username or password is a token
|
// Check if username or password is a token
|
||||||
|
@ -76,20 +82,21 @@ func (b *Basic) VerifyAuthData(req *http.Request, w http.ResponseWriter, store D
|
||||||
uid := CheckOAuthAccessToken(authToken)
|
uid := CheckOAuthAccessToken(authToken)
|
||||||
if uid != 0 {
|
if uid != 0 {
|
||||||
log.Trace("Basic Authorization: Valid OAuthAccessToken for user[%d]", uid)
|
log.Trace("Basic Authorization: Valid OAuthAccessToken for user[%d]", uid)
|
||||||
var err error
|
|
||||||
store.GetData()["IsApiToken"] = true
|
|
||||||
|
|
||||||
u, err = models.GetUserByID(uid)
|
u, err := models.GetUserByID(uid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetUserByID: %v", err)
|
log.Error("GetUserByID: %v", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
store.GetData()["IsApiToken"] = true
|
||||||
|
return u
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err := models.GetAccessTokenBySHA(authToken)
|
token, err := models.GetAccessTokenBySHA(authToken)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
log.Trace("Basic Authorization: Valid AccessToken for user[%d]", uid)
|
log.Trace("Basic Authorization: Valid AccessToken for user[%d]", uid)
|
||||||
|
u, err := models.GetUserByID(token.UID)
|
||||||
u, err = models.GetUserByID(token.UID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("GetUserByID: %v", err)
|
log.Error("GetUserByID: %v", err)
|
||||||
return nil
|
return nil
|
||||||
|
@ -99,23 +106,25 @@ func (b *Basic) VerifyAuthData(req *http.Request, w http.ResponseWriter, store D
|
||||||
if err = models.UpdateAccessToken(token); err != nil {
|
if err = models.UpdateAccessToken(token); err != nil {
|
||||||
log.Error("UpdateAccessToken: %v", err)
|
log.Error("UpdateAccessToken: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
store.GetData()["IsApiToken"] = true
|
||||||
|
return u
|
||||||
} else if !models.IsErrAccessTokenNotExist(err) && !models.IsErrAccessTokenEmpty(err) {
|
} else if !models.IsErrAccessTokenNotExist(err) && !models.IsErrAccessTokenEmpty(err) {
|
||||||
log.Error("GetAccessTokenBySha: %v", err)
|
log.Error("GetAccessTokenBySha: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if u == nil {
|
if !setting.Service.EnableBasicAuth {
|
||||||
log.Trace("Basic Authorization: Attempting SignIn for %s", uname)
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
u, err = models.UserSignIn(uname, passwd)
|
log.Trace("Basic Authorization: Attempting SignIn for %s", uname)
|
||||||
|
u, err := models.UserSignIn(uname, passwd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !models.IsErrUserNotExist(err) {
|
if !models.IsErrUserNotExist(err) {
|
||||||
log.Error("UserSignIn: %v", err)
|
log.Error("UserSignIn: %v", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
store.GetData()["IsApiToken"] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Trace("Basic Authorization: Logged in user %-v", u)
|
log.Trace("Basic Authorization: Logged in user %-v", u)
|
||||||
|
|
||||||
|
|
|
@ -9,10 +9,12 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
"code.gitea.io/gitea/models"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/web/middleware"
|
"code.gitea.io/gitea/modules/web/middleware"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -27,9 +29,9 @@ import (
|
||||||
// for users that have already signed in.
|
// for users that have already signed in.
|
||||||
var ssoMethods = []SingleSignOn{
|
var ssoMethods = []SingleSignOn{
|
||||||
&OAuth2{},
|
&OAuth2{},
|
||||||
|
&Basic{},
|
||||||
&Session{},
|
&Session{},
|
||||||
&ReverseProxy{},
|
&ReverseProxy{},
|
||||||
&Basic{},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The purpose of the following three function variables is to let the linter know that
|
// The purpose of the following three function variables is to let the linter know that
|
||||||
|
@ -102,6 +104,19 @@ func isAttachmentDownload(req *http.Request) bool {
|
||||||
return strings.HasPrefix(req.URL.Path, "/attachments/") && req.Method == "GET"
|
return strings.HasPrefix(req.URL.Path, "/attachments/") && req.Method == "GET"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var gitPathRe = regexp.MustCompile(`^/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+/(?:(?:git-(?:(?:upload)|(?:receive))-pack$)|(?:info/refs$)|(?:HEAD$)|(?:objects/))`)
|
||||||
|
var lfsPathRe = regexp.MustCompile(`^/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+/info/lfs/`)
|
||||||
|
|
||||||
|
func isGitOrLFSPath(req *http.Request) bool {
|
||||||
|
if gitPathRe.MatchString(req.URL.Path) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if setting.LFS.StartServer {
|
||||||
|
return lfsPathRe.MatchString(req.URL.Path)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// handleSignIn clears existing session variables and stores new ones for the specified user object
|
// handleSignIn clears existing session variables and stores new ones for the specified user object
|
||||||
func handleSignIn(resp http.ResponseWriter, req *http.Request, sess SessionStore, user *models.User) {
|
func handleSignIn(resp http.ResponseWriter, req *http.Request, sess SessionStore, user *models.User) {
|
||||||
_ = sess.Delete("openid_verified_uri")
|
_ = sess.Delete("openid_verified_uri")
|
||||||
|
|
124
modules/auth/sso/sso_test.go
Normal file
124
modules/auth/sso/sso_test.go
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||||
|
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package sso
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_isGitOrLFSPath(t *testing.T) {
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
path string
|
||||||
|
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"/owner/repo/git-upload-pack",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"/owner/repo/git-receive-pack",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"/owner/repo/info/refs",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"/owner/repo/HEAD",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"/owner/repo/objects/info/alternates",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"/owner/repo/objects/info/http-alternates",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"/owner/repo/objects/info/packs",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"/owner/repo/objects/info/blahahsdhsdkla",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"/owner/repo/objects/01/23456789abcdef0123456789abcdef01234567",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"/owner/repo/objects/pack/pack-123456789012345678921234567893124567894.pack",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"/owner/repo/objects/pack/pack-0123456789abcdef0123456789abcdef0123456.idx",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"/owner/repo/stars",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"/notowner",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"/owner/repo",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"/owner/repo/commit/123456789012345678921234567893124567894",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
lfsTests := []string{
|
||||||
|
"/owner/repo/info/lfs/",
|
||||||
|
"/owner/repo/info/lfs/objects/batch",
|
||||||
|
"/owner/repo/info/lfs/objects/oid/filename",
|
||||||
|
"/owner/repo/info/lfs/objects/oid",
|
||||||
|
"/owner/repo/info/lfs/objects",
|
||||||
|
"/owner/repo/info/lfs/verify",
|
||||||
|
"/owner/repo/info/lfs/locks",
|
||||||
|
"/owner/repo/info/lfs/locks/verify",
|
||||||
|
"/owner/repo/info/lfs/locks/123/unlock",
|
||||||
|
}
|
||||||
|
|
||||||
|
origLFSStartServer := setting.LFS.StartServer
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.path, func(t *testing.T) {
|
||||||
|
req, _ := http.NewRequest("POST", "http://localhost"+tt.path, nil)
|
||||||
|
setting.LFS.StartServer = false
|
||||||
|
if got := isGitOrLFSPath(req); got != tt.want {
|
||||||
|
t.Errorf("isGitOrLFSPath() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
setting.LFS.StartServer = true
|
||||||
|
if got := isGitOrLFSPath(req); got != tt.want {
|
||||||
|
t.Errorf("isGitOrLFSPath() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, tt := range lfsTests {
|
||||||
|
t.Run(tt, func(t *testing.T) {
|
||||||
|
req, _ := http.NewRequest("POST", tt, nil)
|
||||||
|
setting.LFS.StartServer = false
|
||||||
|
if got := isGitOrLFSPath(req); got != setting.LFS.StartServer {
|
||||||
|
t.Errorf("isGitOrLFSPath(%q) = %v, want %v, %v", tt, got, setting.LFS.StartServer, gitPathRe.MatchString(tt))
|
||||||
|
}
|
||||||
|
setting.LFS.StartServer = true
|
||||||
|
if got := isGitOrLFSPath(req); got != setting.LFS.StartServer {
|
||||||
|
t.Errorf("isGitOrLFSPath(%q) = %v, want %v", tt, got, setting.LFS.StartServer)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
setting.LFS.StartServer = origLFSStartServer
|
||||||
|
}
|
|
@ -683,6 +683,9 @@ func Contexter() func(next http.Handler) http.Handler {
|
||||||
} else {
|
} else {
|
||||||
ctx.Data["SignedUserID"] = int64(0)
|
ctx.Data["SignedUserID"] = int64(0)
|
||||||
ctx.Data["SignedUserName"] = ""
|
ctx.Data["SignedUserName"] = ""
|
||||||
|
|
||||||
|
// ensure the session uid is deleted
|
||||||
|
_ = ctx.Session.Delete("uid")
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Resp.Header().Set(`X-Frame-Options`, `SAMEORIGIN`)
|
ctx.Resp.Header().Set(`X-Frame-Options`, `SAMEORIGIN`)
|
||||||
|
|
|
@ -23,7 +23,7 @@ import (
|
||||||
func CheckInternalToken(next http.Handler) http.Handler {
|
func CheckInternalToken(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
tokens := req.Header.Get("Authorization")
|
tokens := req.Header.Get("Authorization")
|
||||||
fields := strings.Fields(tokens)
|
fields := strings.SplitN(tokens, " ", 2)
|
||||||
if len(fields) != 2 || fields[0] != "Bearer" || fields[1] != setting.InternalToken {
|
if len(fields) != 2 || fields[0] != "Bearer" || fields[1] != setting.InternalToken {
|
||||||
log.Debug("Forbidden attempt to access internal url: Authorization header: %s", tokens)
|
log.Debug("Forbidden attempt to access internal url: Authorization header: %s", tokens)
|
||||||
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
|
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
|
||||||
|
|
|
@ -22,15 +22,12 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
"code.gitea.io/gitea/models"
|
||||||
"code.gitea.io/gitea/modules/auth/sso"
|
|
||||||
"code.gitea.io/gitea/modules/base"
|
|
||||||
"code.gitea.io/gitea/modules/context"
|
"code.gitea.io/gitea/modules/context"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/process"
|
"code.gitea.io/gitea/modules/process"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/structs"
|
"code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
repo_service "code.gitea.io/gitea/services/repository"
|
repo_service "code.gitea.io/gitea/services/repository"
|
||||||
)
|
)
|
||||||
|
@ -154,9 +151,6 @@ func httpBase(ctx *context.Context) (h *serviceHandler) {
|
||||||
isPublicPull := repoExist && !repo.IsPrivate && isPull
|
isPublicPull := repoExist && !repo.IsPrivate && isPull
|
||||||
var (
|
var (
|
||||||
askAuth = !isPublicPull || setting.Service.RequireSignInView
|
askAuth = !isPublicPull || setting.Service.RequireSignInView
|
||||||
authUser *models.User
|
|
||||||
authUsername string
|
|
||||||
authPasswd string
|
|
||||||
environ []string
|
environ []string
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -172,90 +166,16 @@ func httpBase(ctx *context.Context) (h *serviceHandler) {
|
||||||
|
|
||||||
// check access
|
// check access
|
||||||
if askAuth {
|
if askAuth {
|
||||||
authUsername = ctx.Req.Header.Get(setting.ReverseProxyAuthUser)
|
// rely on the results of Contexter
|
||||||
if setting.Service.EnableReverseProxyAuth && len(authUsername) > 0 {
|
if !ctx.IsSigned {
|
||||||
authUser, err = models.GetUserByName(authUsername)
|
// TODO: support digit auth - which would be Authorization header with digit
|
||||||
if err != nil {
|
|
||||||
ctx.HandleText(401, "reverse proxy login error, got error while running GetUserByName")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
authHead := ctx.Req.Header.Get("Authorization")
|
|
||||||
if len(authHead) == 0 {
|
|
||||||
ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=\".\"")
|
ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=\".\"")
|
||||||
ctx.Error(http.StatusUnauthorized)
|
ctx.Error(http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
auths := strings.Fields(authHead)
|
if ctx.IsBasicAuth {
|
||||||
// currently check basic auth
|
_, err = models.GetTwoFactorByUID(ctx.User.ID)
|
||||||
// TODO: support digit auth
|
|
||||||
// FIXME: middlewares/context.go did basic auth check already,
|
|
||||||
// maybe could use that one.
|
|
||||||
if len(auths) != 2 || auths[0] != "Basic" {
|
|
||||||
ctx.HandleText(http.StatusUnauthorized, "no basic auth and digit auth")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
authUsername, authPasswd, err = base.BasicAuthDecode(auths[1])
|
|
||||||
if err != nil {
|
|
||||||
ctx.HandleText(http.StatusUnauthorized, "no basic auth and digit auth")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if username or password is a token
|
|
||||||
isUsernameToken := len(authPasswd) == 0 || authPasswd == "x-oauth-basic"
|
|
||||||
// Assume username is token
|
|
||||||
authToken := authUsername
|
|
||||||
if !isUsernameToken {
|
|
||||||
// Assume password is token
|
|
||||||
authToken = authPasswd
|
|
||||||
}
|
|
||||||
uid := sso.CheckOAuthAccessToken(authToken)
|
|
||||||
if uid != 0 {
|
|
||||||
ctx.Data["IsApiToken"] = true
|
|
||||||
|
|
||||||
authUser, err = models.GetUserByID(uid)
|
|
||||||
if err != nil {
|
|
||||||
ctx.ServerError("GetUserByID", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Assume password is a token.
|
|
||||||
token, err := models.GetAccessTokenBySHA(authToken)
|
|
||||||
if err == nil {
|
|
||||||
authUser, err = models.GetUserByID(token.UID)
|
|
||||||
if err != nil {
|
|
||||||
ctx.ServerError("GetUserByID", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
token.UpdatedUnix = timeutil.TimeStampNow()
|
|
||||||
if err = models.UpdateAccessToken(token); err != nil {
|
|
||||||
ctx.ServerError("UpdateAccessToken", err)
|
|
||||||
}
|
|
||||||
} else if !models.IsErrAccessTokenNotExist(err) && !models.IsErrAccessTokenEmpty(err) {
|
|
||||||
log.Error("GetAccessTokenBySha: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if authUser == nil {
|
|
||||||
// Check username and password
|
|
||||||
authUser, err = models.UserSignIn(authUsername, authPasswd)
|
|
||||||
if err != nil {
|
|
||||||
if models.IsErrUserProhibitLogin(err) {
|
|
||||||
ctx.HandleText(http.StatusForbidden, "User is not permitted to login")
|
|
||||||
return
|
|
||||||
} else if !models.IsErrUserNotExist(err) {
|
|
||||||
ctx.ServerError("UserSignIn error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if authUser == nil {
|
|
||||||
ctx.HandleText(http.StatusUnauthorized, fmt.Sprintf("invalid credentials from %s", ctx.RemoteAddr()))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = models.GetTwoFactorByUID(authUser.ID)
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// TODO: This response should be changed to "invalid credentials" for security reasons once the expectation behind it (creating an app token to authenticate) is properly documented
|
// TODO: This response should be changed to "invalid credentials" for security reasons once the expectation behind it (creating an app token to authenticate) is properly documented
|
||||||
ctx.HandleText(http.StatusUnauthorized, "Users with two-factor authentication enabled cannot perform HTTP/HTTPS operations via plain username and password. Please create and use a personal access token on the user settings page")
|
ctx.HandleText(http.StatusUnauthorized, "Users with two-factor authentication enabled cannot perform HTTP/HTTPS operations via plain username and password. Please create and use a personal access token on the user settings page")
|
||||||
|
@ -265,15 +185,14 @@ func httpBase(ctx *context.Context) (h *serviceHandler) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if !authUser.IsActive || authUser.ProhibitLogin {
|
if !ctx.User.IsActive || ctx.User.ProhibitLogin {
|
||||||
ctx.HandleText(http.StatusForbidden, "Your account is disabled.")
|
ctx.HandleText(http.StatusForbidden, "Your account is disabled.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if repoExist {
|
if repoExist {
|
||||||
perm, err := models.GetUserRepoPermission(repo, authUser)
|
perm, err := models.GetUserRepoPermission(repo, ctx.User)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GetUserRepoPermission", err)
|
ctx.ServerError("GetUserRepoPermission", err)
|
||||||
return
|
return
|
||||||
|
@ -293,14 +212,14 @@ func httpBase(ctx *context.Context) (h *serviceHandler) {
|
||||||
environ = []string{
|
environ = []string{
|
||||||
models.EnvRepoUsername + "=" + username,
|
models.EnvRepoUsername + "=" + username,
|
||||||
models.EnvRepoName + "=" + reponame,
|
models.EnvRepoName + "=" + reponame,
|
||||||
models.EnvPusherName + "=" + authUser.Name,
|
models.EnvPusherName + "=" + ctx.User.Name,
|
||||||
models.EnvPusherID + fmt.Sprintf("=%d", authUser.ID),
|
models.EnvPusherID + fmt.Sprintf("=%d", ctx.User.ID),
|
||||||
models.EnvIsDeployKey + "=false",
|
models.EnvIsDeployKey + "=false",
|
||||||
models.EnvAppURL + "=" + setting.AppURL,
|
models.EnvAppURL + "=" + setting.AppURL,
|
||||||
}
|
}
|
||||||
|
|
||||||
if !authUser.KeepEmailPrivate {
|
if !ctx.User.KeepEmailPrivate {
|
||||||
environ = append(environ, models.EnvPusherEmail+"="+authUser.Email)
|
environ = append(environ, models.EnvPusherEmail+"="+ctx.User.Email)
|
||||||
}
|
}
|
||||||
|
|
||||||
if isWiki {
|
if isWiki {
|
||||||
|
@ -336,7 +255,7 @@ func httpBase(ctx *context.Context) (h *serviceHandler) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
repo, err = repo_service.PushCreateRepo(authUser, owner, reponame)
|
repo, err = repo_service.PushCreateRepo(ctx.User, owner, reponame)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("pushCreateRepo: %v", err)
|
log.Error("pushCreateRepo: %v", err)
|
||||||
ctx.Status(http.StatusNotFound)
|
ctx.Status(http.StatusNotFound)
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models"
|
||||||
"code.gitea.io/gitea/modules/auth/sso"
|
"code.gitea.io/gitea/modules/auth/sso"
|
||||||
"code.gitea.io/gitea/modules/context"
|
"code.gitea.io/gitea/modules/context"
|
||||||
"code.gitea.io/gitea/modules/httpcache"
|
"code.gitea.io/gitea/modules/httpcache"
|
||||||
|
@ -171,8 +172,19 @@ func Recovery() func(next http.Handler) http.Handler {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get user from session if logged in.
|
var user *models.User
|
||||||
user, _ := sso.SignedInUser(req, w, &store, sessionStore)
|
if apiContext := context.GetAPIContext(req); apiContext != nil {
|
||||||
|
user = apiContext.User
|
||||||
|
}
|
||||||
|
if user == nil {
|
||||||
|
if ctx := context.GetContext(req); ctx != nil {
|
||||||
|
user = ctx.User
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if user == nil {
|
||||||
|
// Get user from session if logged in - do not attempt to sign-in
|
||||||
|
user = sso.SessionUser(sessionStore)
|
||||||
|
}
|
||||||
if user != nil {
|
if user != nil {
|
||||||
store.Data["IsSigned"] = true
|
store.Data["IsSigned"] = true
|
||||||
store.Data["SignedUser"] = user
|
store.Data["SignedUser"] = user
|
||||||
|
|
|
@ -31,15 +31,6 @@ func checkIsValidRequest(ctx *context.Context) bool {
|
||||||
writeStatus(ctx, http.StatusBadRequest)
|
writeStatus(ctx, http.StatusBadRequest)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if !ctx.IsSigned {
|
|
||||||
user, _, _, err := parseToken(ctx.Req.Header.Get("Authorization"))
|
|
||||||
if err != nil {
|
|
||||||
ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
|
|
||||||
writeStatus(ctx, http.StatusUnauthorized)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
ctx.User = user
|
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,19 +64,21 @@ func GetListLockHandler(ctx *context.Context) {
|
||||||
// Status is written in checkIsValidRequest
|
// Status is written in checkIsValidRequest
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.Resp.Header().Set("Content-Type", lfs_module.MediaType)
|
|
||||||
|
|
||||||
rv, _ := unpack(ctx)
|
rv, _ := unpack(ctx)
|
||||||
|
|
||||||
repository, err := models.GetRepositoryByOwnerAndName(rv.User, rv.Repo)
|
repository, err := models.GetRepositoryByOwnerAndName(rv.User, rv.Repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("Could not find repository: %s/%s - %s", rv.User, rv.Repo, err)
|
log.Debug("Could not find repository: %s/%s - %s", rv.User, rv.Repo, err)
|
||||||
writeStatus(ctx, 404)
|
ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
|
||||||
|
ctx.JSON(401, api.LFSLockError{
|
||||||
|
Message: "You must have pull access to list locks",
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
repository.MustOwner()
|
repository.MustOwner()
|
||||||
|
|
||||||
authenticated := authenticate(ctx, repository, rv.Authorization, false)
|
authenticated := authenticate(ctx, repository, rv.Authorization, true, false)
|
||||||
if !authenticated {
|
if !authenticated {
|
||||||
ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
|
ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
|
||||||
ctx.JSON(http.StatusUnauthorized, api.LFSLockError{
|
ctx.JSON(http.StatusUnauthorized, api.LFSLockError{
|
||||||
|
@ -93,6 +86,7 @@ func GetListLockHandler(ctx *context.Context) {
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
ctx.Resp.Header().Set("Content-Type", lfs_module.MediaType)
|
||||||
|
|
||||||
cursor := ctx.QueryInt("cursor")
|
cursor := ctx.QueryInt("cursor")
|
||||||
if cursor < 0 {
|
if cursor < 0 {
|
||||||
|
@ -160,7 +154,6 @@ func PostLockHandler(ctx *context.Context) {
|
||||||
// Status is written in checkIsValidRequest
|
// Status is written in checkIsValidRequest
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.Resp.Header().Set("Content-Type", lfs_module.MediaType)
|
|
||||||
|
|
||||||
userName := ctx.Params("username")
|
userName := ctx.Params("username")
|
||||||
repoName := strings.TrimSuffix(ctx.Params("reponame"), ".git")
|
repoName := strings.TrimSuffix(ctx.Params("reponame"), ".git")
|
||||||
|
@ -169,12 +162,15 @@ func PostLockHandler(ctx *context.Context) {
|
||||||
repository, err := models.GetRepositoryByOwnerAndName(userName, repoName)
|
repository, err := models.GetRepositoryByOwnerAndName(userName, repoName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Unable to get repository: %s/%s Error: %v", userName, repoName, err)
|
log.Error("Unable to get repository: %s/%s Error: %v", userName, repoName, err)
|
||||||
writeStatus(ctx, 404)
|
ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
|
||||||
|
ctx.JSON(401, api.LFSLockError{
|
||||||
|
Message: "You must have push access to create locks",
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
repository.MustOwner()
|
repository.MustOwner()
|
||||||
|
|
||||||
authenticated := authenticate(ctx, repository, authorization, true)
|
authenticated := authenticate(ctx, repository, authorization, true, true)
|
||||||
if !authenticated {
|
if !authenticated {
|
||||||
ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
|
ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
|
||||||
ctx.JSON(http.StatusUnauthorized, api.LFSLockError{
|
ctx.JSON(http.StatusUnauthorized, api.LFSLockError{
|
||||||
|
@ -183,6 +179,8 @@ func PostLockHandler(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx.Resp.Header().Set("Content-Type", lfs_module.MediaType)
|
||||||
|
|
||||||
var req api.LFSLockRequest
|
var req api.LFSLockRequest
|
||||||
bodyReader := ctx.Req.Body
|
bodyReader := ctx.Req.Body
|
||||||
defer bodyReader.Close()
|
defer bodyReader.Close()
|
||||||
|
@ -229,7 +227,6 @@ func VerifyLockHandler(ctx *context.Context) {
|
||||||
// Status is written in checkIsValidRequest
|
// Status is written in checkIsValidRequest
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.Resp.Header().Set("Content-Type", lfs_module.MediaType)
|
|
||||||
|
|
||||||
userName := ctx.Params("username")
|
userName := ctx.Params("username")
|
||||||
repoName := strings.TrimSuffix(ctx.Params("reponame"), ".git")
|
repoName := strings.TrimSuffix(ctx.Params("reponame"), ".git")
|
||||||
|
@ -238,12 +235,15 @@ func VerifyLockHandler(ctx *context.Context) {
|
||||||
repository, err := models.GetRepositoryByOwnerAndName(userName, repoName)
|
repository, err := models.GetRepositoryByOwnerAndName(userName, repoName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Unable to get repository: %s/%s Error: %v", userName, repoName, err)
|
log.Error("Unable to get repository: %s/%s Error: %v", userName, repoName, err)
|
||||||
writeStatus(ctx, 404)
|
ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
|
||||||
|
ctx.JSON(401, api.LFSLockError{
|
||||||
|
Message: "You must have push access to verify locks",
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
repository.MustOwner()
|
repository.MustOwner()
|
||||||
|
|
||||||
authenticated := authenticate(ctx, repository, authorization, true)
|
authenticated := authenticate(ctx, repository, authorization, true, true)
|
||||||
if !authenticated {
|
if !authenticated {
|
||||||
ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
|
ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
|
||||||
ctx.JSON(http.StatusUnauthorized, api.LFSLockError{
|
ctx.JSON(http.StatusUnauthorized, api.LFSLockError{
|
||||||
|
@ -252,6 +252,8 @@ func VerifyLockHandler(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx.Resp.Header().Set("Content-Type", lfs_module.MediaType)
|
||||||
|
|
||||||
cursor := ctx.QueryInt("cursor")
|
cursor := ctx.QueryInt("cursor")
|
||||||
if cursor < 0 {
|
if cursor < 0 {
|
||||||
cursor = 0
|
cursor = 0
|
||||||
|
@ -296,7 +298,6 @@ func UnLockHandler(ctx *context.Context) {
|
||||||
// Status is written in checkIsValidRequest
|
// Status is written in checkIsValidRequest
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.Resp.Header().Set("Content-Type", lfs_module.MediaType)
|
|
||||||
|
|
||||||
userName := ctx.Params("username")
|
userName := ctx.Params("username")
|
||||||
repoName := strings.TrimSuffix(ctx.Params("reponame"), ".git")
|
repoName := strings.TrimSuffix(ctx.Params("reponame"), ".git")
|
||||||
|
@ -305,12 +306,15 @@ func UnLockHandler(ctx *context.Context) {
|
||||||
repository, err := models.GetRepositoryByOwnerAndName(userName, repoName)
|
repository, err := models.GetRepositoryByOwnerAndName(userName, repoName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Unable to get repository: %s/%s Error: %v", userName, repoName, err)
|
log.Error("Unable to get repository: %s/%s Error: %v", userName, repoName, err)
|
||||||
writeStatus(ctx, 404)
|
ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
|
||||||
|
ctx.JSON(401, api.LFSLockError{
|
||||||
|
Message: "You must have push access to delete locks",
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
repository.MustOwner()
|
repository.MustOwner()
|
||||||
|
|
||||||
authenticated := authenticate(ctx, repository, authorization, true)
|
authenticated := authenticate(ctx, repository, authorization, true, true)
|
||||||
if !authenticated {
|
if !authenticated {
|
||||||
ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
|
ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
|
||||||
ctx.JSON(http.StatusUnauthorized, api.LFSLockError{
|
ctx.JSON(http.StatusUnauthorized, api.LFSLockError{
|
||||||
|
@ -319,6 +323,8 @@ func UnLockHandler(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx.Resp.Header().Set("Content-Type", lfs_module.MediaType)
|
||||||
|
|
||||||
var req api.LFSLockDeleteRequest
|
var req api.LFSLockDeleteRequest
|
||||||
bodyReader := ctx.Req.Body
|
bodyReader := ctx.Req.Body
|
||||||
defer bodyReader.Close()
|
defer bodyReader.Close()
|
||||||
|
|
|
@ -94,7 +94,7 @@ func getAuthenticatedRepoAndMeta(ctx *context.Context, rc *requestContext, p lfs
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !authenticate(ctx, repository, rc.Authorization, requireWrite) {
|
if !authenticate(ctx, repository, rc.Authorization, false, requireWrite) {
|
||||||
requireAuth(ctx)
|
requireAuth(ctx)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
@ -232,7 +232,7 @@ func PostHandler(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !authenticate(ctx, repository, rc.Authorization, true) {
|
if !authenticate(ctx, repository, rc.Authorization, false, true) {
|
||||||
requireAuth(ctx)
|
requireAuth(ctx)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -322,7 +322,7 @@ func BatchHandler(ctx *context.Context) {
|
||||||
requireWrite = true
|
requireWrite = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if !authenticate(ctx, repository, reqCtx.Authorization, requireWrite) {
|
if !authenticate(ctx, repository, reqCtx.Authorization, false, requireWrite) {
|
||||||
requireAuth(ctx)
|
requireAuth(ctx)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -561,7 +561,7 @@ func logRequest(r *http.Request, status int) {
|
||||||
|
|
||||||
// authenticate uses the authorization string to determine whether
|
// authenticate uses the authorization string to determine whether
|
||||||
// or not to proceed. This server assumes an HTTP Basic auth format.
|
// or not to proceed. This server assumes an HTTP Basic auth format.
|
||||||
func authenticate(ctx *context.Context, repository *models.Repository, authorization string, requireWrite bool) bool {
|
func authenticate(ctx *context.Context, repository *models.Repository, authorization string, requireSigned, requireWrite bool) bool {
|
||||||
accessMode := models.AccessModeRead
|
accessMode := models.AccessModeRead
|
||||||
if requireWrite {
|
if requireWrite {
|
||||||
accessMode = models.AccessModeWrite
|
accessMode = models.AccessModeWrite
|
||||||
|
@ -575,89 +575,72 @@ func authenticate(ctx *context.Context, repository *models.Repository, authoriza
|
||||||
}
|
}
|
||||||
|
|
||||||
canRead := perm.CanAccess(accessMode, models.UnitTypeCode)
|
canRead := perm.CanAccess(accessMode, models.UnitTypeCode)
|
||||||
if canRead {
|
if canRead && (!requireSigned || ctx.IsSigned) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
user, repo, opStr, err := parseToken(authorization)
|
user, err := parseToken(authorization, repository, accessMode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Most of these are Warn level - the true internal server errors are logged in parseToken already
|
// Most of these are Warn level - the true internal server errors are logged in parseToken already
|
||||||
log.Warn("Authentication failure for provided token with Error: %v", err)
|
log.Warn("Authentication failure for provided token with Error: %v", err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
ctx.User = user
|
ctx.User = user
|
||||||
if opStr == "basic" {
|
|
||||||
perm, err = models.GetUserRepoPermission(repository, ctx.User)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Unable to GetUserRepoPermission for user %-v in repo %-v Error: %v", ctx.User, repository)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return perm.CanAccess(accessMode, models.UnitTypeCode)
|
|
||||||
}
|
|
||||||
if repository.ID == repo.ID {
|
|
||||||
if requireWrite && opStr != "upload" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseToken(authorization string) (*models.User, *models.Repository, string, error) {
|
func handleLFSToken(tokenSHA string, target *models.Repository, mode models.AccessMode) (*models.User, error) {
|
||||||
if authorization == "" {
|
if !strings.Contains(tokenSHA, ".") {
|
||||||
return nil, nil, "unknown", fmt.Errorf("No token")
|
return nil, nil
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(authorization, "Bearer ") {
|
token, err := jwt.ParseWithClaims(tokenSHA, &Claims{}, func(t *jwt.Token) (interface{}, error) {
|
||||||
token, err := jwt.ParseWithClaims(authorization[7:], &Claims{}, func(t *jwt.Token) (interface{}, error) {
|
|
||||||
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
|
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||||
return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
|
return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
|
||||||
}
|
}
|
||||||
return setting.LFS.JWTSecretBytes, nil
|
return setting.LFS.JWTSecretBytes, nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// The error here is WARN level because it is caused by bad authorization rather than an internal server error
|
return nil, nil
|
||||||
return nil, nil, "unknown", err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
claims, claimsOk := token.Claims.(*Claims)
|
claims, claimsOk := token.Claims.(*Claims)
|
||||||
if !token.Valid || !claimsOk {
|
if !token.Valid || !claimsOk {
|
||||||
return nil, nil, "unknown", fmt.Errorf("Token claim invalid")
|
return nil, fmt.Errorf("invalid token claim")
|
||||||
}
|
}
|
||||||
r, err := models.GetRepositoryByID(claims.RepoID)
|
|
||||||
if err != nil {
|
if claims.RepoID != target.ID {
|
||||||
log.Error("Unable to GetRepositoryById[%d]: Error: %v", claims.RepoID, err)
|
return nil, fmt.Errorf("invalid token claim")
|
||||||
return nil, nil, claims.Op, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if mode == models.AccessModeWrite && claims.Op != "upload" {
|
||||||
|
return nil, fmt.Errorf("invalid token claim")
|
||||||
|
}
|
||||||
|
|
||||||
u, err := models.GetUserByID(claims.UserID)
|
u, err := models.GetUserByID(claims.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Unable to GetUserById[%d]: Error: %v", claims.UserID, err)
|
log.Error("Unable to GetUserById[%d]: Error: %v", claims.UserID, err)
|
||||||
return nil, r, claims.Op, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return u, r, claims.Op, nil
|
return u, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(authorization, "Basic ") {
|
func parseToken(authorization string, target *models.Repository, mode models.AccessMode) (*models.User, error) {
|
||||||
c, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(authorization, "Basic "))
|
if authorization == "" {
|
||||||
if err != nil {
|
return nil, fmt.Errorf("no token")
|
||||||
return nil, nil, "basic", err
|
|
||||||
}
|
|
||||||
cs := string(c)
|
|
||||||
i := strings.IndexByte(cs, ':')
|
|
||||||
if i < 0 {
|
|
||||||
return nil, nil, "basic", fmt.Errorf("Basic auth invalid")
|
|
||||||
}
|
|
||||||
user, password := cs[:i], cs[i+1:]
|
|
||||||
u, err := models.GetUserByName(user)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Unable to GetUserByName[%d]: Error: %v", user, err)
|
|
||||||
return nil, nil, "basic", err
|
|
||||||
}
|
|
||||||
if !u.IsPasswordSet() || !u.ValidatePassword(password) {
|
|
||||||
return nil, nil, "basic", fmt.Errorf("Basic auth failed")
|
|
||||||
}
|
|
||||||
return u, nil, "basic", nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil, "unknown", fmt.Errorf("Token not found")
|
parts := strings.SplitN(authorization, " ", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return nil, fmt.Errorf("no token")
|
||||||
|
}
|
||||||
|
tokenSHA := parts[1]
|
||||||
|
switch strings.ToLower(parts[0]) {
|
||||||
|
case "bearer":
|
||||||
|
fallthrough
|
||||||
|
case "token":
|
||||||
|
return handleLFSToken(tokenSHA, target, mode)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("token not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
func requireAuth(ctx *context.Context) {
|
func requireAuth(ctx *context.Context) {
|
||||||
|
|
Loading…
Reference in a new issue