diff options
Diffstat (limited to 'internal/route')
-rw-r--r-- | internal/route/admin/auths.go | 38 | ||||
-rw-r--r-- | internal/route/admin/users.go | 12 | ||||
-rw-r--r-- | internal/route/api/v1/admin/user.go | 4 | ||||
-rw-r--r-- | internal/route/api/v1/api.go | 2 | ||||
-rw-r--r-- | internal/route/api/v1/repo/repo.go | 4 | ||||
-rw-r--r-- | internal/route/api/v1/user/app.go | 4 | ||||
-rw-r--r-- | internal/route/home.go | 2 | ||||
-rw-r--r-- | internal/route/install.go | 13 | ||||
-rw-r--r-- | internal/route/lfs/basic.go | 122 | ||||
-rw-r--r-- | internal/route/lfs/batch.go | 182 | ||||
-rw-r--r-- | internal/route/lfs/route.go | 159 | ||||
-rw-r--r-- | internal/route/org/setting.go | 2 | ||||
-rw-r--r-- | internal/route/org/teams.go | 6 | ||||
-rw-r--r-- | internal/route/repo/http.go | 19 | ||||
-rw-r--r-- | internal/route/repo/setting.go | 2 | ||||
-rw-r--r-- | internal/route/user/auth.go | 10 | ||||
-rw-r--r-- | internal/route/user/setting.go | 6 |
17 files changed, 522 insertions, 65 deletions
diff --git a/internal/route/admin/auths.go b/internal/route/admin/auths.go index bcf52e5e..c5c1e480 100644 --- a/internal/route/admin/auths.go +++ b/internal/route/admin/auths.go @@ -32,7 +32,7 @@ func Authentications(c *context.Context) { c.PageIs("AdminAuthentications") var err error - c.Data["Sources"], err = db.LoginSources() + c.Data["Sources"], err = db.ListLoginSources() if err != nil { c.Error(err, "list login sources") return @@ -49,11 +49,11 @@ type dropdownItem struct { var ( authSources = []dropdownItem{ - {db.LoginNames[db.LOGIN_LDAP], db.LOGIN_LDAP}, - {db.LoginNames[db.LOGIN_DLDAP], db.LOGIN_DLDAP}, - {db.LoginNames[db.LOGIN_SMTP], db.LOGIN_SMTP}, - {db.LoginNames[db.LOGIN_PAM], db.LOGIN_PAM}, - {db.LoginNames[db.LOGIN_GITHUB], db.LOGIN_GITHUB}, + {db.LoginNames[db.LoginLDAP], db.LoginLDAP}, + {db.LoginNames[db.LoginDLDAP], db.LoginDLDAP}, + {db.LoginNames[db.LoginSMTP], db.LoginSMTP}, + {db.LoginNames[db.LoginPAM], db.LoginPAM}, + {db.LoginNames[db.LoginGitHub], db.LoginGitHub}, } securityProtocols = []dropdownItem{ {db.SecurityProtocolNames[ldap.SECURITY_PROTOCOL_UNENCRYPTED], ldap.SECURITY_PROTOCOL_UNENCRYPTED}, @@ -67,8 +67,8 @@ func NewAuthSource(c *context.Context) { c.PageIs("Admin") c.PageIs("AdminAuthentications") - c.Data["type"] = db.LOGIN_LDAP - c.Data["CurrentTypeName"] = db.LoginNames[db.LOGIN_LDAP] + c.Data["type"] = db.LoginLDAP + c.Data["CurrentTypeName"] = db.LoginNames[db.LoginLDAP] c.Data["CurrentSecurityProtocol"] = db.SecurityProtocolNames[ldap.SECURITY_PROTOCOL_UNENCRYPTED] c.Data["smtp_auth"] = "PLAIN" c.Data["is_active"] = true @@ -131,17 +131,17 @@ func NewAuthSourcePost(c *context.Context, f form.Authentication) { hasTLS := false var config core.Conversion switch db.LoginType(f.Type) { - case db.LOGIN_LDAP, db.LOGIN_DLDAP: + case db.LoginLDAP, db.LoginDLDAP: config = parseLDAPConfig(f) hasTLS = ldap.SecurityProtocol(f.SecurityProtocol) > ldap.SECURITY_PROTOCOL_UNENCRYPTED - case db.LOGIN_SMTP: + case db.LoginSMTP: config = parseSMTPConfig(f) hasTLS = true - case db.LOGIN_PAM: + case db.LoginPAM: config = &db.PAMConfig{ ServiceName: f.PAMServiceName, } - case db.LOGIN_GITHUB: + case db.LoginGitHub: config = &db.GitHubConfig{ APIEndpoint: strings.TrimSuffix(f.GitHubAPIEndpoint, "/") + "/", } @@ -186,7 +186,7 @@ func EditAuthSource(c *context.Context) { c.Data["SecurityProtocols"] = securityProtocols c.Data["SMTPAuths"] = db.SMTPAuths - source, err := db.GetLoginSourceByID(c.ParamsInt64(":authid")) + source, err := db.LoginSources.GetByID(c.ParamsInt64(":authid")) if err != nil { c.Error(err, "get login source by ID") return @@ -204,7 +204,7 @@ func EditAuthSourcePost(c *context.Context, f form.Authentication) { c.Data["SMTPAuths"] = db.SMTPAuths - source, err := db.GetLoginSourceByID(c.ParamsInt64(":authid")) + source, err := db.LoginSources.GetByID(c.ParamsInt64(":authid")) if err != nil { c.Error(err, "get login source by ID") return @@ -219,15 +219,15 @@ func EditAuthSourcePost(c *context.Context, f form.Authentication) { var config core.Conversion switch db.LoginType(f.Type) { - case db.LOGIN_LDAP, db.LOGIN_DLDAP: + case db.LoginLDAP, db.LoginDLDAP: config = parseLDAPConfig(f) - case db.LOGIN_SMTP: + case db.LoginSMTP: config = parseSMTPConfig(f) - case db.LOGIN_PAM: + case db.LoginPAM: config = &db.PAMConfig{ ServiceName: f.PAMServiceName, } - case db.LOGIN_GITHUB: + case db.LoginGitHub: config = &db.GitHubConfig{ APIEndpoint: strings.TrimSuffix(f.GitHubAPIEndpoint, "/") + "/", } @@ -252,7 +252,7 @@ func EditAuthSourcePost(c *context.Context, f form.Authentication) { } func DeleteAuthSource(c *context.Context) { - source, err := db.GetLoginSourceByID(c.ParamsInt64(":authid")) + source, err := db.LoginSources.GetByID(c.ParamsInt64(":authid")) if err != nil { c.Error(err, "get login source by ID") return diff --git a/internal/route/admin/users.go b/internal/route/admin/users.go index 630fa4ca..96257c59 100644 --- a/internal/route/admin/users.go +++ b/internal/route/admin/users.go @@ -32,7 +32,7 @@ func Users(c *context.Context) { route.RenderUserSearch(c, &route.UserSearchOptions{ Type: db.USER_TYPE_INDIVIDUAL, Counter: db.CountUsers, - Ranger: db.Users, + Ranger: db.ListUsers, PageSize: conf.UI.Admin.UserPagingNum, OrderBy: "id ASC", TplName: USERS, @@ -46,7 +46,7 @@ func NewUser(c *context.Context) { c.Data["login_type"] = "0-0" - sources, err := db.LoginSources() + sources, err := db.ListLoginSources() if err != nil { c.Error(err, "list login sources") return @@ -62,7 +62,7 @@ func NewUserPost(c *context.Context, f form.AdminCrateUser) { c.Data["PageIsAdmin"] = true c.Data["PageIsAdminUsers"] = true - sources, err := db.LoginSources() + sources, err := db.ListLoginSources() if err != nil { c.Error(err, "list login sources") return @@ -81,7 +81,7 @@ func NewUserPost(c *context.Context, f form.AdminCrateUser) { Email: f.Email, Passwd: f.Password, IsActive: true, - LoginType: db.LOGIN_PLAIN, + LoginType: db.LoginPlain, } if len(f.LoginType) > 0 { @@ -132,7 +132,7 @@ func prepareUserInfo(c *context.Context) *db.User { c.Data["User"] = u if u.LoginSource > 0 { - c.Data["LoginSource"], err = db.GetLoginSourceByID(u.LoginSource) + c.Data["LoginSource"], err = db.LoginSources.GetByID(u.LoginSource) if err != nil { c.Error(err, "get login source by ID") return nil @@ -141,7 +141,7 @@ func prepareUserInfo(c *context.Context) *db.User { c.Data["LoginSource"] = &db.LoginSource{} } - sources, err := db.LoginSources() + sources, err := db.ListLoginSources() if err != nil { c.Error(err, "list login sources") return nil diff --git a/internal/route/api/v1/admin/user.go b/internal/route/api/v1/admin/user.go index c339edd2..06c6569f 100644 --- a/internal/route/api/v1/admin/user.go +++ b/internal/route/api/v1/admin/user.go @@ -23,7 +23,7 @@ func parseLoginSource(c *context.APIContext, u *db.User, sourceID int64, loginNa return } - source, err := db.GetLoginSourceByID(sourceID) + source, err := db.LoginSources.GetByID(sourceID) if err != nil { if errors.IsLoginSourceNotExist(err) { c.ErrorStatus(http.StatusUnprocessableEntity, err) @@ -45,7 +45,7 @@ func CreateUser(c *context.APIContext, form api.CreateUserOption) { Email: form.Email, Passwd: form.Password, IsActive: true, - LoginType: db.LOGIN_PLAIN, + LoginType: db.LoginPlain, } parseLoginSource(c, u, form.SourceID, form.LoginName) diff --git a/internal/route/api/v1/api.go b/internal/route/api/v1/api.go index 01f23d42..1ef21505 100644 --- a/internal/route/api/v1/api.go +++ b/internal/route/api/v1/api.go @@ -55,7 +55,7 @@ func repoAssignment() macaron.Handler { } if c.IsTokenAuth && c.User.IsAdmin { - c.Repo.AccessMode = db.ACCESS_MODE_OWNER + c.Repo.AccessMode = db.AccessModeOwner } else { mode, err := db.UserAccessMode(c.UserID(), r) if err != nil { diff --git a/internal/route/api/v1/repo/repo.go b/internal/route/api/v1/repo/repo.go index 71b94d75..8ac7fd87 100644 --- a/internal/route/api/v1/repo/repo.go +++ b/internal/route/api/v1/repo/repo.go @@ -131,8 +131,8 @@ func listUserRepositories(c *context.APIContext, username string) { i := numOwnRepos for repo, access := range accessibleRepos { repos[i] = repo.APIFormat(&api.Permission{ - Admin: access >= db.ACCESS_MODE_ADMIN, - Push: access >= db.ACCESS_MODE_WRITE, + Admin: access >= db.AccessModeAdmin, + Push: access >= db.AccessModeWrite, Pull: true, }) i++ diff --git a/internal/route/api/v1/user/app.go b/internal/route/api/v1/user/app.go index 99a422cc..98532ae2 100644 --- a/internal/route/api/v1/user/app.go +++ b/internal/route/api/v1/user/app.go @@ -30,8 +30,8 @@ func ListAccessTokens(c *context.APIContext) { func CreateAccessToken(c *context.APIContext, form api.CreateAccessTokenOption) { t := &db.AccessToken{ - UID: c.User.ID, - Name: form.Name, + UserID: c.User.ID, + Name: form.Name, } if err := db.NewAccessToken(t); err != nil { if errors.IsAccessTokenNameAlreadyExist(err) { diff --git a/internal/route/home.go b/internal/route/home.go index 3da7f0cf..86a659d0 100644 --- a/internal/route/home.go +++ b/internal/route/home.go @@ -135,7 +135,7 @@ func ExploreUsers(c *context.Context) { RenderUserSearch(c, &UserSearchOptions{ Type: db.USER_TYPE_INDIVIDUAL, Counter: db.CountUsers, - Ranger: db.Users, + Ranger: db.ListUsers, PageSize: conf.UI.ExplorePagingNum, OrderBy: "updated_unix DESC", TplName: EXPLORE_USERS, diff --git a/internal/route/install.go b/internal/route/install.go index 899393b4..fbb605f0 100644 --- a/internal/route/install.go +++ b/internal/route/install.go @@ -86,9 +86,6 @@ func GlobalInit(customConf string) error { db.InitDeliverHooks() db.InitTestPullRequests() } - if db.EnableSQLite3 { - log.Info("SQLite3 is supported") - } if conf.HasMinWinSvc { log.Info("Builtin Windows Service is supported") } @@ -125,11 +122,7 @@ func InstallInit(c *context.Context) { c.Title("install.install") c.PageIs("Install") - dbOpts := []string{"MySQL", "PostgreSQL", "MSSQL"} - if db.EnableSQLite3 { - dbOpts = append(dbOpts, "SQLite3") - } - c.Data["DbOptions"] = dbOpts + c.Data["DbOptions"] = []string{"MySQL", "PostgreSQL", "MSSQL", "SQLite3"} } func Install(c *context.Context) { @@ -148,9 +141,7 @@ func Install(c *context.Context) { case "mssql": c.Data["CurDbOption"] = "MSSQL" case "sqlite3": - if db.EnableSQLite3 { - c.Data["CurDbOption"] = "SQLite3" - } + c.Data["CurDbOption"] = "SQLite3" } // Application general settings diff --git a/internal/route/lfs/basic.go b/internal/route/lfs/basic.go new file mode 100644 index 00000000..98597345 --- /dev/null +++ b/internal/route/lfs/basic.go @@ -0,0 +1,122 @@ +// Copyright 2020 The Gogs 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 lfs + +import ( + "encoding/json" + "io" + "net/http" + "os" + "strconv" + + log "unknwon.dev/clog/v2" + + "gogs.io/gogs/internal/conf" + "gogs.io/gogs/internal/context" + "gogs.io/gogs/internal/db" + "gogs.io/gogs/internal/lfsutil" + "gogs.io/gogs/internal/strutil" +) + +const transferBasic = "basic" +const ( + basicOperationUpload = "upload" + basicOperationDownload = "download" +) + +// GET /{owner}/{repo}.git/info/lfs/object/basic/{oid} +func serveBasicDownload(c *context.Context, repo *db.Repository, oid lfsutil.OID) { + object, err := db.LFS.GetObjectByOID(repo.ID, oid) + if err != nil { + if db.IsErrLFSObjectNotExist(err) { + responseJSON(c.Resp, http.StatusNotFound, responseError{ + Message: "Object does not exist", + }) + } else { + internalServerError(c.Resp) + log.Error("Failed to get object [repo_id: %d, oid: %s]: %v", repo.ID, oid, err) + } + return + } + + fpath := lfsutil.StorageLocalPath(conf.LFS.ObjectsPath, object.OID) + r, err := os.Open(fpath) + if err != nil { + internalServerError(c.Resp) + log.Error("Failed to open object file [path: %s]: %v", fpath, err) + return + } + defer r.Close() + + c.Header().Set("Content-Type", "application/octet-stream") + c.Header().Set("Content-Length", strconv.FormatInt(object.Size, 10)) + + _, err = io.Copy(c.Resp, r) + if err != nil { + c.Status(http.StatusInternalServerError) + log.Error("Failed to copy object file: %v", err) + return + } + c.Status(http.StatusOK) +} + +// PUT /{owner}/{repo}.git/info/lfs/object/basic/{oid} +func serveBasicUpload(c *context.Context, repo *db.Repository, oid lfsutil.OID) { + err := db.LFS.CreateObject(repo.ID, oid, c.Req.Request.Body, lfsutil.StorageLocal) + if err != nil { + internalServerError(c.Resp) + log.Error("Failed to create object [repo_id: %d, oid: %s]: %v", repo.ID, oid, err) + return + } + c.Status(http.StatusOK) + + log.Trace("[LFS] Object created %q", oid) +} + +// POST /{owner}/{repo}.git/info/lfs/object/basic/verify +func serveBasicVerify(c *context.Context, repo *db.Repository) { + var request basicVerifyRequest + defer c.Req.Request.Body.Close() + err := json.NewDecoder(c.Req.Request.Body).Decode(&request) + if err != nil { + responseJSON(c.Resp, http.StatusBadRequest, responseError{ + Message: strutil.ToUpperFirst(err.Error()), + }) + return + } + + if !lfsutil.ValidOID(request.Oid) { + responseJSON(c.Resp, http.StatusBadRequest, responseError{ + Message: "Invalid oid", + }) + return + } + + object, err := db.LFS.GetObjectByOID(repo.ID, lfsutil.OID(request.Oid)) + if err != nil { + if db.IsErrLFSObjectNotExist(err) { + responseJSON(c.Resp, http.StatusNotFound, responseError{ + Message: "Object does not exist", + }) + } else { + internalServerError(c.Resp) + log.Error("Failed to get object [repo_id: %d, oid: %s]: %v", repo.ID, request.Oid, err) + } + return + } + + if object.Size != request.Size { + responseJSON(c.Resp, http.StatusNotFound, responseError{ + Message: "Object size mismatch", + }) + return + } + c.Status(http.StatusOK) +} + +type basicVerifyRequest struct { + Oid lfsutil.OID `json:"oid"` + Size int64 `json:"size"` +} diff --git a/internal/route/lfs/batch.go b/internal/route/lfs/batch.go new file mode 100644 index 00000000..ae53f5d3 --- /dev/null +++ b/internal/route/lfs/batch.go @@ -0,0 +1,182 @@ +// Copyright 2020 The Gogs 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 lfs + +import ( + "fmt" + "net/http" + + jsoniter "github.com/json-iterator/go" + log "unknwon.dev/clog/v2" + + "gogs.io/gogs/internal/conf" + "gogs.io/gogs/internal/context" + "gogs.io/gogs/internal/db" + "gogs.io/gogs/internal/lfsutil" + "gogs.io/gogs/internal/strutil" +) + +// POST /{owner}/{repo}.git/info/lfs/object/batch +func serveBatch(c *context.Context, owner *db.User, repo *db.Repository) { + var request batchRequest + defer c.Req.Request.Body.Close() + err := jsoniter.NewDecoder(c.Req.Request.Body).Decode(&request) + if err != nil { + responseJSON(c.Resp, http.StatusBadRequest, responseError{ + Message: strutil.ToUpperFirst(err.Error()), + }) + return + } + + // NOTE: We only support basic transfer as of now. + transfer := transferBasic + // Example: https://try.gogs.io/gogs/gogs.git/info/lfs/object/basic + baseHref := fmt.Sprintf("%s%s/%s.git/info/lfs/objects/basic", conf.Server.ExternalURL, owner.Name, repo.Name) + + objects := make([]batchObject, 0, len(request.Objects)) + switch request.Operation { + case basicOperationUpload: + for _, obj := range request.Objects { + var actions batchActions + if lfsutil.ValidOID(obj.Oid) { + actions = batchActions{ + Upload: &batchAction{ + Href: fmt.Sprintf("%s/%s", baseHref, obj.Oid), + }, + Verify: &batchAction{ + Href: fmt.Sprintf("%s/verify", baseHref), + }, + } + } else { + actions = batchActions{ + Error: &batchError{ + Code: http.StatusUnprocessableEntity, + Message: "Object has invalid oid", + }, + } + } + + objects = append(objects, batchObject{ + Oid: obj.Oid, + Size: obj.Size, + Actions: actions, + }) + } + + case basicOperationDownload: + oids := make([]lfsutil.OID, 0, len(request.Objects)) + for _, obj := range request.Objects { + oids = append(oids, obj.Oid) + } + stored, err := db.LFS.GetObjectsByOIDs(repo.ID, oids...) + if err != nil { + internalServerError(c.Resp) + log.Error("Failed to get objects [repo_id: %d, oids: %v]: %v", repo.ID, oids, err) + return + } + storedSet := make(map[lfsutil.OID]*db.LFSObject, len(stored)) + for _, obj := range stored { + storedSet[obj.OID] = obj + } + + for _, obj := range request.Objects { + var actions batchActions + if stored := storedSet[obj.Oid]; stored != nil { + if stored.Size != obj.Size { + actions.Error = &batchError{ + Code: http.StatusUnprocessableEntity, + Message: "Object size mismatch", + } + } else { + actions.Download = &batchAction{ + Href: fmt.Sprintf("%s/%s", baseHref, obj.Oid), + } + } + } else { + actions.Error = &batchError{ + Code: http.StatusNotFound, + Message: "Object does not exist", + } + } + + objects = append(objects, batchObject{ + Oid: obj.Oid, + Size: obj.Size, + Actions: actions, + }) + } + + default: + responseJSON(c.Resp, http.StatusBadRequest, responseError{ + Message: "Operation not recognized", + }) + return + } + + responseJSON(c.Resp, http.StatusOK, batchResponse{ + Transfer: transfer, + Objects: objects, + }) +} + +// batchRequest defines the request payload for the batch endpoint. +type batchRequest struct { + Operation string `json:"operation"` + Objects []struct { + Oid lfsutil.OID `json:"oid"` + Size int64 `json:"size"` + } `json:"objects"` +} + +type batchError struct { + Code int `json:"code"` + Message string `json:"message"` +} + +type batchAction struct { + Href string `json:"href"` +} + +type batchActions struct { + Download *batchAction `json:"download,omitempty"` + Upload *batchAction `json:"upload,omitempty"` + Verify *batchAction `json:"verify,omitempty"` + Error *batchError `json:"error,omitempty"` +} + +type batchObject struct { + Oid lfsutil.OID `json:"oid"` + Size int64 `json:"size"` + Actions batchActions `json:"actions"` +} + +// batchResponse defines the response payload for the batch endpoint. +type batchResponse struct { + Transfer string `json:"transfer"` + Objects []batchObject `json:"objects"` +} + +type responseError struct { + Message string `json:"message"` +} + +const contentType = "application/vnd.git-lfs+json" + +func responseJSON(w http.ResponseWriter, status int, v interface{}) { + w.Header().Set("Content-Type", contentType) + + err := jsoniter.NewEncoder(w).Encode(v) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + w.WriteHeader(status) +} + +func internalServerError(w http.ResponseWriter) { + responseJSON(w, http.StatusInternalServerError, responseError{ + Message: "Internal server error", + }) +} diff --git a/internal/route/lfs/route.go b/internal/route/lfs/route.go new file mode 100644 index 00000000..27224265 --- /dev/null +++ b/internal/route/lfs/route.go @@ -0,0 +1,159 @@ +// Copyright 2020 The Gogs 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 lfs + +import ( + "net/http" + "strings" + "time" + + "gopkg.in/macaron.v1" + log "unknwon.dev/clog/v2" + + "gogs.io/gogs/internal/authutil" + "gogs.io/gogs/internal/context" + "gogs.io/gogs/internal/db" + "gogs.io/gogs/internal/lfsutil" +) + +// RegisterRoutes registers LFS routes using given router, and inherits all groups and middleware. +func RegisterRoutes(r *macaron.Router) { + verifyAccept := verifyHeader("Accept", contentType, http.StatusNotAcceptable) + verifyContentTypeJSON := verifyHeader("Content-Type", contentType, http.StatusBadRequest) + verifyContentTypeStream := verifyHeader("Content-Type", "application/octet-stream", http.StatusBadRequest) + + r.Group("", func() { + r.Post("/objects/batch", authorize(db.AccessModeRead), verifyAccept, verifyContentTypeJSON, serveBatch) + r.Group("/objects/basic", func() { + r.Combo("/:oid", verifyOID()). + Get(authorize(db.AccessModeRead), serveBasicDownload). + Put(authorize(db.AccessModeWrite), verifyContentTypeStream, serveBasicUpload) + r.Post("/verify", authorize(db.AccessModeWrite), verifyAccept, verifyContentTypeJSON, serveBasicVerify) + }) + }, authenticate()) +} + +// authenticate tries to authenticate user via HTTP Basic Auth. +func authenticate() macaron.Handler { + askCredentials := func(w http.ResponseWriter) { + w.Header().Set("LFS-Authenticate", `Basic realm="Git LFS"`) + responseJSON(w, http.StatusUnauthorized, responseError{ + Message: "Credentials needed", + }) + } + + return func(c *context.Context) { + username, password := authutil.DecodeBasic(c.Req.Header) + if username == "" { + askCredentials(c.Resp) + return + } + + user, err := db.Users.Authenticate(username, password, -1) + if err != nil && !db.IsErrUserNotExist(err) { + c.Status(http.StatusInternalServerError) + log.Error("Failed to authenticate user [name: %s]: %v", username, err) + return + } + + if err == nil && user.IsEnabledTwoFactor() { + c.PlainText(http.StatusBadRequest, `Users with 2FA enabled are not allowed to authenticate via username and password.`) + return + } + + // If username and password authentication failed, try again using username as an access token. + if db.IsErrUserNotExist(err) { + token, err := db.AccessTokens.GetBySHA(username) + if err != nil { + if db.IsErrAccessTokenNotExist(err) { + askCredentials(c.Resp) + } else { + c.Status(http.StatusInternalServerError) + log.Error("Failed to get access token [sha: %s]: %v", username, err) + } + return + } + token.Updated = time.Now() + if err = db.AccessTokens.Save(token); err != nil { + log.Error("Failed to update access token: %v", err) + } + + user, err = db.Users.GetByID(token.UserID) + if err != nil { + // Once we found the token, we're supposed to find its related user, + // thus any error is unexpected. + c.Status(http.StatusInternalServerError) + log.Error("Failed to get user: %v", err) + return + } + } + + log.Trace("[LFS] Authenticated user: %s", user.Name) + + c.Map(user) + } +} + +// authorize tries to authorize the user to the context repository with given access mode. +func authorize(mode db.AccessMode) macaron.Handler { + return func(c *context.Context, user *db.User) { + username := c.Params(":username") + reponame := strings.TrimSuffix(c.Params(":reponame"), ".git") + + owner, err := db.Users.GetByUsername(username) + if err != nil { + if db.IsErrUserNotExist(err) { + c.Status(http.StatusNotFound) + } else { + c.Status(http.StatusInternalServerError) + log.Error("Failed to get user [name: %s]: %v", username, err) + } + return + } + + repo, err := db.Repos.GetByName(owner.ID, reponame) + if err != nil { + if db.IsErrRepoNotExist(err) { + c.Status(http.StatusNotFound) + } else { + c.Status(http.StatusInternalServerError) + log.Error("Failed to get repository [owner_id: %d, name: %s]: %v", owner.ID, reponame, err) + } + return + } + + if !db.Perms.Authorize(user.ID, repo, mode) { + c.Status(http.StatusNotFound) + return + } + + c.Map(owner) + c.Map(repo) + } +} + +// verifyHeader checks if the HTTP header value is matching. +// When not, response given "failCode" as status code. +func verifyHeader(key, value string, failCode int) macaron.Handler { + return func(c *context.Context) { + if c.Req.Header.Get(key) != value { + c.Status(failCode) + return + } + } +} + +// verifyOID checks if the ":oid" URL parameter is valid. +func verifyOID() macaron.Handler { + return func(c *context.Context) { + oid := lfsutil.OID(c.Params(":oid")) + if !lfsutil.ValidOID(oid) { + c.PlainText(http.StatusBadRequest, "Invalid oid") + return + } + + c.Map(oid) + } +} diff --git a/internal/route/org/setting.go b/internal/route/org/setting.go index 15b57623..64c30827 100644 --- a/internal/route/org/setting.go +++ b/internal/route/org/setting.go @@ -110,7 +110,7 @@ func SettingsDelete(c *context.Context) { org := c.Org.Organization if c.Req.Method == "POST" { - if _, err := db.UserLogin(c.User.Name, c.Query("password"), c.User.LoginSource); err != nil { + if _, err := db.Users.Authenticate(c.User.Name, c.Query("password"), c.User.LoginSource); err != nil { if db.IsErrUserNotExist(err) { c.RenderWithErr(c.Tr("form.enterred_invalid_password"), SETTINGS_DELETE, nil) } else { diff --git a/internal/route/org/teams.go b/internal/route/org/teams.go index a703a82d..6b0061fc 100644 --- a/internal/route/org/teams.go +++ b/internal/route/org/teams.go @@ -228,11 +228,11 @@ func EditTeamPost(c *context.Context, f form.CreateTeam) { var auth db.AccessMode switch f.Permission { case "read": - auth = db.ACCESS_MODE_READ + auth = db.AccessModeRead case "write": - auth = db.ACCESS_MODE_WRITE + auth = db.AccessModeWrite case "admin": - auth = db.ACCESS_MODE_ADMIN + auth = db.AccessModeAdmin default: c.Status(http.StatusUnauthorized) return diff --git a/internal/route/repo/http.go b/internal/route/repo/http.go index 413b660f..c48cbdb8 100644 --- a/internal/route/repo/http.go +++ b/internal/route/repo/http.go @@ -12,6 +12,7 @@ import ( "os" "os/exec" "path" + "path/filepath" "strconv" "strings" "time" @@ -111,7 +112,7 @@ func HTTPContexter() macaron.Handler { return } - authUser, err := db.UserLogin(authUsername, authPassword, -1) + authUser, err := db.Users.Authenticate(authUsername, authPassword, -1) if err != nil && !db.IsErrUserNotExist(err) { c.Error(err, "authenticate user") return @@ -119,9 +120,9 @@ func HTTPContexter() macaron.Handler { // If username and password combination failed, try again using username as a token. if authUser == nil { - token, err := db.GetAccessTokenBySHA(authUsername) + token, err := db.AccessTokens.GetBySHA(authUsername) if err != nil { - if db.IsErrAccessTokenEmpty(err) || db.IsErrAccessTokenNotExist(err) { + if db.IsErrAccessTokenNotExist(err) { askCredentials(c, http.StatusUnauthorized, "") } else { c.Error(err, "get access token by SHA") @@ -129,9 +130,11 @@ func HTTPContexter() macaron.Handler { return } token.Updated = time.Now() - // TODO: verify or update token.Updated in database + if err = db.AccessTokens.Save(token); err != nil { + log.Error("Failed to update access token: %v", err) + } - authUser, err = db.GetUserByID(token.UID) + authUser, err = db.GetUserByID(token.UserID) if err != nil { // Once we found token, we're supposed to find its related user, // thus any error is unexpected. @@ -146,9 +149,9 @@ Please create and use personal access token on user settings page`) log.Trace("HTTPGit - Authenticated user: %s", authUser.Name) - mode := db.ACCESS_MODE_WRITE + mode := db.AccessModeWrite if isPull { - mode = db.ACCESS_MODE_READ + mode = db.AccessModeRead } has, err := db.HasAccess(authUser.ID, repo, mode) if err != nil { @@ -367,7 +370,7 @@ func getGitRepoPath(dir string) (string, error) { dir += ".git" } - filename := path.Join(conf.Repository.Root, dir) + filename := filepath.Join(conf.Repository.Root, dir) if _, err := os.Stat(filename); os.IsNotExist(err) { return "", err } diff --git a/internal/route/repo/setting.go b/internal/route/repo/setting.go index 1c4446d0..fd281dcc 100644 --- a/internal/route/repo/setting.go +++ b/internal/route/repo/setting.go @@ -513,7 +513,7 @@ func SettingsProtectedBranch(c *context.Context) { c.Data["Users"] = users c.Data["whitelist_users"] = protectBranch.WhitelistUserIDs - teams, err := c.Repo.Owner.TeamsHaveAccessToRepo(c.Repo.Repository.ID, db.ACCESS_MODE_WRITE) + teams, err := c.Repo.Owner.TeamsHaveAccessToRepo(c.Repo.Repository.ID, db.AccessModeWrite) if err != nil { c.Error(err, "get teams have access to the repository") return diff --git a/internal/route/user/auth.go b/internal/route/user/auth.go index a29b6311..22e8176c 100644 --- a/internal/route/user/auth.go +++ b/internal/route/user/auth.go @@ -9,12 +9,12 @@ import ( "net/url" "github.com/go-macaron/captcha" + "github.com/pkg/errors" log "unknwon.dev/clog/v2" "gogs.io/gogs/internal/conf" "gogs.io/gogs/internal/context" "gogs.io/gogs/internal/db" - "gogs.io/gogs/internal/db/errors" "gogs.io/gogs/internal/email" "gogs.io/gogs/internal/form" "gogs.io/gogs/internal/tool" @@ -160,13 +160,13 @@ func LoginPost(c *context.Context, f form.SignIn) { return } - u, err := db.UserLogin(f.UserName, f.Password, f.LoginSource) + u, err := db.Users.Authenticate(f.UserName, f.Password, f.LoginSource) if err != nil { - switch err.(type) { + switch errors.Cause(err).(type) { case db.ErrUserNotExist: c.FormErr("UserName", "Password") c.RenderWithErr(c.Tr("form.username_password_incorrect"), LOGIN, &f) - case errors.LoginSourceMismatch: + case db.ErrLoginSourceMismatch: c.FormErr("LoginSource") c.RenderWithErr(c.Tr("form.auth_source_mismatch"), LOGIN, &f) @@ -263,7 +263,7 @@ func LoginTwoFactorRecoveryCodePost(c *context.Context) { } if err := db.UseRecoveryCode(userID, c.Query("recovery_code")); err != nil { - if errors.IsTwoFactorRecoveryCodeNotFound(err) { + if db.IsTwoFactorRecoveryCodeNotFound(err) { c.Flash.Error(c.Tr("auth.login_two_factor_invalid_recovery_code")) c.RedirectSubpath("/user/login/two_factor_recovery_code") } else { diff --git a/internal/route/user/setting.go b/internal/route/user/setting.go index c9ccdc8f..63575c9e 100644 --- a/internal/route/user/setting.go +++ b/internal/route/user/setting.go @@ -608,8 +608,8 @@ func SettingsApplicationsPost(c *context.Context, f form.NewAccessToken) { } t := &db.AccessToken{ - UID: c.User.ID, - Name: f.Name, + UserID: c.User.ID, + Name: f.Name, } if err := db.NewAccessToken(t); err != nil { if errors.IsAccessTokenNameAlreadyExist(err) { @@ -643,7 +643,7 @@ func SettingsDelete(c *context.Context) { c.PageIs("SettingsDelete") if c.Req.Method == "POST" { - if _, err := db.UserLogin(c.User.Name, c.Query("password"), c.User.LoginSource); err != nil { + if _, err := db.Users.Authenticate(c.User.Name, c.Query("password"), c.User.LoginSource); err != nil { if db.IsErrUserNotExist(err) { c.RenderWithErr(c.Tr("form.enterred_invalid_password"), SETTINGS_DELETE, nil) } else { |