resp := MakeRequest(t, req, http.StatusUnauthorized)
var lfsLockError api.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) {
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
+ "code.gitea.io/gitea/modules/web/middleware"
)
// Ensure the struct implements the interface.
// IsEnabled returns true as this plugin is enabled by default and its not possible to disable
// it from settings.
func (b *Basic) IsEnabled() bool {
- return setting.Service.EnableBasicAuth
+ return true
}
// VerifyAuthData extracts and validates Basic data (username and password/token) from the
// name/token on successful validation.
// 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 {
+
+ // 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")
if len(baHead) == 0 {
return nil
}
- auths := strings.Fields(baHead)
+ auths := strings.SplitN(baHead, " ", 2)
if len(auths) != 2 || (auths[0] != "Basic" && auths[0] != "basic") {
return nil
}
- var u *models.User
uname, passwd, _ := base.BasicAuthDecode(auths[1])
// Check if username or password is a token
uid := CheckOAuthAccessToken(authToken)
if uid != 0 {
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 {
log.Error("GetUserByID: %v", err)
return nil
}
+
+ store.GetData()["IsApiToken"] = true
+ return u
}
+
token, err := models.GetAccessTokenBySHA(authToken)
if err == nil {
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 {
log.Error("GetUserByID: %v", err)
return nil
if err = models.UpdateAccessToken(token); err != nil {
log.Error("UpdateAccessToken: %v", err)
}
+
+ store.GetData()["IsApiToken"] = true
+ return u
} else if !models.IsErrAccessTokenNotExist(err) && !models.IsErrAccessTokenEmpty(err) {
log.Error("GetAccessTokenBySha: %v", err)
}
- if u == nil {
- log.Trace("Basic Authorization: Attempting SignIn for %s", uname)
+ if !setting.Service.EnableBasicAuth {
+ return nil
+ }
- u, err = models.UserSignIn(uname, passwd)
- if err != nil {
- if !models.IsErrUserNotExist(err) {
- log.Error("UserSignIn: %v", err)
- }
- return nil
+ log.Trace("Basic Authorization: Attempting SignIn for %s", uname)
+ u, err := models.UserSignIn(uname, passwd)
+ if err != nil {
+ if !models.IsErrUserNotExist(err) {
+ log.Error("UserSignIn: %v", err)
}
- } else {
- store.GetData()["IsApiToken"] = true
+ return nil
}
log.Trace("Basic Authorization: Logged in user %-v", u)
"fmt"
"net/http"
"reflect"
+ "regexp"
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web/middleware"
)
// for users that have already signed in.
var ssoMethods = []SingleSignOn{
&OAuth2{},
+ &Basic{},
&Session{},
&ReverseProxy{},
- &Basic{},
}
// The purpose of the following three function variables is to let the linter know that
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
func handleSignIn(resp http.ResponseWriter, req *http.Request, sess SessionStore, user *models.User) {
_ = sess.Delete("openid_verified_uri")
--- /dev/null
+// 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
+}
} else {
ctx.Data["SignedUserID"] = int64(0)
ctx.Data["SignedUserName"] = ""
+
+ // ensure the session uid is deleted
+ _ = ctx.Session.Delete("uid")
}
ctx.Resp.Header().Set(`X-Frame-Options`, `SAMEORIGIN`)
func CheckInternalToken(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
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 {
log.Debug("Forbidden attempt to access internal url: Authorization header: %s", tokens)
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
"time"
"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/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
- "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
repo_service "code.gitea.io/gitea/services/repository"
)
// Only public pull don't need auth.
isPublicPull := repoExist && !repo.IsPrivate && isPull
var (
- askAuth = !isPublicPull || setting.Service.RequireSignInView
- authUser *models.User
- authUsername string
- authPasswd string
- environ []string
+ askAuth = !isPublicPull || setting.Service.RequireSignInView
+ environ []string
)
// don't allow anonymous pulls if organization is not public
// check access
if askAuth {
- authUsername = ctx.Req.Header.Get(setting.ReverseProxyAuthUser)
- if setting.Service.EnableReverseProxyAuth && len(authUsername) > 0 {
- authUser, err = models.GetUserByName(authUsername)
- 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.Error(http.StatusUnauthorized)
- return
- }
+ // rely on the results of Contexter
+ if !ctx.IsSigned {
+ // TODO: support digit auth - which would be Authorization header with digit
+ ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=\".\"")
+ ctx.Error(http.StatusUnauthorized)
+ return
+ }
- auths := strings.Fields(authHead)
- // currently check basic auth
- // 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")
+ if ctx.IsBasicAuth {
+ _, err = models.GetTwoFactorByUID(ctx.User.ID)
+ 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
+ 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")
return
- }
- authUsername, authPasswd, err = base.BasicAuthDecode(auths[1])
- if err != nil {
- ctx.HandleText(http.StatusUnauthorized, "no basic auth and digit auth")
+ } else if !models.IsErrTwoFactorNotEnrolled(err) {
+ ctx.ServerError("IsErrTwoFactorNotEnrolled", err)
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 {
- // 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")
- return
- } else if !models.IsErrTwoFactorNotEnrolled(err) {
- ctx.ServerError("IsErrTwoFactorNotEnrolled", err)
- return
- }
- }
}
- if !authUser.IsActive || authUser.ProhibitLogin {
+ if !ctx.User.IsActive || ctx.User.ProhibitLogin {
ctx.HandleText(http.StatusForbidden, "Your account is disabled.")
return
}
if repoExist {
- perm, err := models.GetUserRepoPermission(repo, authUser)
+ perm, err := models.GetUserRepoPermission(repo, ctx.User)
if err != nil {
ctx.ServerError("GetUserRepoPermission", err)
return
environ = []string{
models.EnvRepoUsername + "=" + username,
models.EnvRepoName + "=" + reponame,
- models.EnvPusherName + "=" + authUser.Name,
- models.EnvPusherID + fmt.Sprintf("=%d", authUser.ID),
+ models.EnvPusherName + "=" + ctx.User.Name,
+ models.EnvPusherID + fmt.Sprintf("=%d", ctx.User.ID),
models.EnvIsDeployKey + "=false",
models.EnvAppURL + "=" + setting.AppURL,
}
- if !authUser.KeepEmailPrivate {
- environ = append(environ, models.EnvPusherEmail+"="+authUser.Email)
+ if !ctx.User.KeepEmailPrivate {
+ environ = append(environ, models.EnvPusherEmail+"="+ctx.User.Email)
}
if isWiki {
return
}
- repo, err = repo_service.PushCreateRepo(authUser, owner, reponame)
+ repo, err = repo_service.PushCreateRepo(ctx.User, owner, reponame)
if err != nil {
log.Error("pushCreateRepo: %v", err)
ctx.Status(http.StatusNotFound)
"strings"
"time"
+ "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth/sso"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/httpcache"
},
}
- // Get user from session if logged in.
- user, _ := sso.SignedInUser(req, w, &store, sessionStore)
+ var user *models.User
+ 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 {
store.Data["IsSigned"] = true
store.Data["SignedUser"] = user
writeStatus(ctx, http.StatusBadRequest)
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
}
// Status is written in checkIsValidRequest
return
}
- ctx.Resp.Header().Set("Content-Type", lfs_module.MediaType)
rv, _ := unpack(ctx)
repository, err := models.GetRepositoryByOwnerAndName(rv.User, rv.Repo)
if err != nil {
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
}
repository.MustOwner()
- authenticated := authenticate(ctx, repository, rv.Authorization, false)
+ authenticated := authenticate(ctx, repository, rv.Authorization, true, false)
if !authenticated {
ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
ctx.JSON(http.StatusUnauthorized, api.LFSLockError{
})
return
}
+ ctx.Resp.Header().Set("Content-Type", lfs_module.MediaType)
cursor := ctx.QueryInt("cursor")
if cursor < 0 {
// Status is written in checkIsValidRequest
return
}
- ctx.Resp.Header().Set("Content-Type", lfs_module.MediaType)
userName := ctx.Params("username")
repoName := strings.TrimSuffix(ctx.Params("reponame"), ".git")
repository, err := models.GetRepositoryByOwnerAndName(userName, repoName)
if err != nil {
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
}
repository.MustOwner()
- authenticated := authenticate(ctx, repository, authorization, true)
+ authenticated := authenticate(ctx, repository, authorization, true, true)
if !authenticated {
ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
ctx.JSON(http.StatusUnauthorized, api.LFSLockError{
return
}
+ ctx.Resp.Header().Set("Content-Type", lfs_module.MediaType)
+
var req api.LFSLockRequest
bodyReader := ctx.Req.Body
defer bodyReader.Close()
// Status is written in checkIsValidRequest
return
}
- ctx.Resp.Header().Set("Content-Type", lfs_module.MediaType)
userName := ctx.Params("username")
repoName := strings.TrimSuffix(ctx.Params("reponame"), ".git")
repository, err := models.GetRepositoryByOwnerAndName(userName, repoName)
if err != nil {
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
}
repository.MustOwner()
- authenticated := authenticate(ctx, repository, authorization, true)
+ authenticated := authenticate(ctx, repository, authorization, true, true)
if !authenticated {
ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
ctx.JSON(http.StatusUnauthorized, api.LFSLockError{
return
}
+ ctx.Resp.Header().Set("Content-Type", lfs_module.MediaType)
+
cursor := ctx.QueryInt("cursor")
if cursor < 0 {
cursor = 0
// Status is written in checkIsValidRequest
return
}
- ctx.Resp.Header().Set("Content-Type", lfs_module.MediaType)
userName := ctx.Params("username")
repoName := strings.TrimSuffix(ctx.Params("reponame"), ".git")
repository, err := models.GetRepositoryByOwnerAndName(userName, repoName)
if err != nil {
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
}
repository.MustOwner()
- authenticated := authenticate(ctx, repository, authorization, true)
+ authenticated := authenticate(ctx, repository, authorization, true, true)
if !authenticated {
ctx.Resp.Header().Set("WWW-Authenticate", "Basic realm=gitea-lfs")
ctx.JSON(http.StatusUnauthorized, api.LFSLockError{
return
}
+ ctx.Resp.Header().Set("Content-Type", lfs_module.MediaType)
+
var req api.LFSLockDeleteRequest
bodyReader := ctx.Req.Body
defer bodyReader.Close()
return nil, nil
}
- if !authenticate(ctx, repository, rc.Authorization, requireWrite) {
+ if !authenticate(ctx, repository, rc.Authorization, false, requireWrite) {
requireAuth(ctx)
return nil, nil
}
return
}
- if !authenticate(ctx, repository, rc.Authorization, true) {
+ if !authenticate(ctx, repository, rc.Authorization, false, true) {
requireAuth(ctx)
return
}
requireWrite = true
}
- if !authenticate(ctx, repository, reqCtx.Authorization, requireWrite) {
+ if !authenticate(ctx, repository, reqCtx.Authorization, false, requireWrite) {
requireAuth(ctx)
return
}
// authenticate uses the authorization string to determine whether
// 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
if requireWrite {
accessMode = models.AccessModeWrite
}
canRead := perm.CanAccess(accessMode, models.UnitTypeCode)
- if canRead {
+ if canRead && (!requireSigned || ctx.IsSigned) {
return true
}
- user, repo, opStr, err := parseToken(authorization)
+ user, err := parseToken(authorization, repository, accessMode)
if err != nil {
// 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)
return false
}
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)
+ return true
+}
+
+func handleLFSToken(tokenSHA string, target *models.Repository, mode models.AccessMode) (*models.User, error) {
+ if !strings.Contains(tokenSHA, ".") {
+ return nil, nil
}
- if repository.ID == repo.ID {
- if requireWrite && opStr != "upload" {
- return false
+ token, err := jwt.ParseWithClaims(tokenSHA, &Claims{}, func(t *jwt.Token) (interface{}, error) {
+ if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
+ return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
}
- return true
+ return setting.LFS.JWTSecretBytes, nil
+ })
+ if err != nil {
+ return nil, nil
}
- return false
-}
-func parseToken(authorization string) (*models.User, *models.Repository, string, error) {
- if authorization == "" {
- return nil, nil, "unknown", fmt.Errorf("No token")
+ claims, claimsOk := token.Claims.(*Claims)
+ if !token.Valid || !claimsOk {
+ return nil, fmt.Errorf("invalid token claim")
}
- if strings.HasPrefix(authorization, "Bearer ") {
- token, err := jwt.ParseWithClaims(authorization[7:], &Claims{}, func(t *jwt.Token) (interface{}, error) {
- if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
- return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
- }
- return setting.LFS.JWTSecretBytes, 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, "unknown", err
- }
- claims, claimsOk := token.Claims.(*Claims)
- if !token.Valid || !claimsOk {
- return nil, nil, "unknown", fmt.Errorf("Token claim invalid")
- }
- r, err := models.GetRepositoryByID(claims.RepoID)
- if err != nil {
- log.Error("Unable to GetRepositoryById[%d]: Error: %v", claims.RepoID, err)
- return nil, nil, claims.Op, err
- }
- u, err := models.GetUserByID(claims.UserID)
- if err != nil {
- log.Error("Unable to GetUserById[%d]: Error: %v", claims.UserID, err)
- return nil, r, claims.Op, err
- }
- return u, r, claims.Op, nil
+
+ if claims.RepoID != target.ID {
+ return nil, fmt.Errorf("invalid token claim")
}
- if strings.HasPrefix(authorization, "Basic ") {
- c, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(authorization, "Basic "))
- if err != nil {
- 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
+ if mode == models.AccessModeWrite && claims.Op != "upload" {
+ return nil, fmt.Errorf("invalid token claim")
}
- return nil, nil, "unknown", fmt.Errorf("Token not found")
+ u, err := models.GetUserByID(claims.UserID)
+ if err != nil {
+ log.Error("Unable to GetUserById[%d]: Error: %v", claims.UserID, err)
+ return nil, err
+ }
+ return u, nil
+}
+
+func parseToken(authorization string, target *models.Repository, mode models.AccessMode) (*models.User, error) {
+ if authorization == "" {
+ return nil, fmt.Errorf("no token")
+ }
+
+ 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) {