aboutsummaryrefslogtreecommitdiff
path: root/internal/db
diff options
context:
space:
mode:
authorᴜɴᴋɴᴡᴏɴ <u@gogs.io>2020-04-04 21:14:15 +0800
committerGitHub <noreply@github.com>2020-04-04 21:14:15 +0800
commit34145c990d4fd9f278f29cdf9c61378a75e9b934 (patch)
tree7b151bbd5aef9e487759953e3a775a82244d268d /internal/db
parent2bd9d0b9c8238ded727cd98a3ace20b53c10a44f (diff)
lfs: implement HTTP routes (#6035)
* Bootstrap with GORM * Fix lint error * Set conn max lifetime to one minute * Fallback to use gorm v1 * Define HTTP routes * Finish authentication * Save token updated * Add docstring * Finish authorization * serveBatch rundown * Define types in lfsutil * Finish Batch * authutil * Finish basic * Formalize response error * Fix lint errors * authutil: add tests * dbutil: add tests * lfsutil: add tests * strutil: add tests * Formalize 401 response
Diffstat (limited to 'internal/db')
-rw-r--r--internal/db/access.go37
-rw-r--r--internal/db/access_tokens.go65
-rw-r--r--internal/db/db.go171
-rw-r--r--internal/db/error.go32
-rw-r--r--internal/db/errors/login_source.go13
-rw-r--r--internal/db/errors/two_factor.go13
-rw-r--r--internal/db/issue.go2
-rw-r--r--internal/db/lfs.go129
-rw-r--r--internal/db/login_source.go192
-rw-r--r--internal/db/login_sources.go36
-rw-r--r--internal/db/models.go55
-rw-r--r--internal/db/models_sqlite.go15
-rw-r--r--internal/db/org.go2
-rw-r--r--internal/db/org_team.go4
-rw-r--r--internal/db/perms.go56
-rw-r--r--internal/db/repo.go7
-rw-r--r--internal/db/repo_branch.go4
-rw-r--r--internal/db/repo_collaboration.go16
-rw-r--r--internal/db/repos.go38
-rw-r--r--internal/db/ssh_key.go6
-rw-r--r--internal/db/token.go45
-rw-r--r--internal/db/two_factor.go25
-rw-r--r--internal/db/two_factors.go33
-rw-r--r--internal/db/user.go18
-rw-r--r--internal/db/users.go138
25 files changed, 813 insertions, 339 deletions
diff --git a/internal/db/access.go b/internal/db/access.go
index 63911f8e..551c29d7 100644
--- a/internal/db/access.go
+++ b/internal/db/access.go
@@ -13,22 +13,22 @@ import (
type AccessMode int
const (
- ACCESS_MODE_NONE AccessMode = iota // 0
- ACCESS_MODE_READ // 1
- ACCESS_MODE_WRITE // 2
- ACCESS_MODE_ADMIN // 3
- ACCESS_MODE_OWNER // 4
+ AccessModeNone AccessMode = iota // 0
+ AccessModeRead // 1
+ AccessModeWrite // 2
+ AccessModeAdmin // 3
+ AccessModeOwner // 4
)
func (mode AccessMode) String() string {
switch mode {
- case ACCESS_MODE_READ:
+ case AccessModeRead:
return "read"
- case ACCESS_MODE_WRITE:
+ case AccessModeWrite:
return "write"
- case ACCESS_MODE_ADMIN:
+ case AccessModeAdmin:
return "admin"
- case ACCESS_MODE_OWNER:
+ case AccessModeOwner:
return "owner"
default:
return "none"
@@ -39,15 +39,15 @@ func (mode AccessMode) String() string {
func ParseAccessMode(permission string) AccessMode {
switch permission {
case "write":
- return ACCESS_MODE_WRITE
+ return AccessModeWrite
case "admin":
- return ACCESS_MODE_ADMIN
+ return AccessModeAdmin
default:
- return ACCESS_MODE_READ
+ return AccessModeRead
}
}
-// Access represents the highest access level of a user to the repository. The only access type
+// Access represents the highest access level of a user to a repository. The only access type
// that is not in this table is the real owner of a repository. In case of an organization
// repository, the members of the owners team are in this table.
type Access struct {
@@ -58,10 +58,10 @@ type Access struct {
}
func userAccessMode(e Engine, userID int64, repo *Repository) (AccessMode, error) {
- mode := ACCESS_MODE_NONE
+ mode := AccessModeNone
// Everyone has read access to public repository
if !repo.IsPrivate {
- mode = ACCESS_MODE_READ
+ mode = AccessModeRead
}
if userID <= 0 {
@@ -69,7 +69,7 @@ func userAccessMode(e Engine, userID int64, repo *Repository) (AccessMode, error
}
if userID == repo.OwnerID {
- return ACCESS_MODE_OWNER, nil
+ return AccessModeOwner, nil
}
access := &Access{
@@ -93,6 +93,7 @@ func hasAccess(e Engine, userID int64, repo *Repository, testMode AccessMode) (b
}
// HasAccess returns true if someone has the request access level. User can be nil!
+// Deprecated: Use Perms.HasAccess instead.
func HasAccess(userID int64, repo *Repository, testMode AccessMode) (bool, error) {
return hasAccess(x, userID, repo, testMode)
}
@@ -136,7 +137,7 @@ func (user *User) GetAccessibleRepositories(limit int) (repos []*Repository, _ e
}
func maxAccessMode(modes ...AccessMode) AccessMode {
- max := ACCESS_MODE_NONE
+ max := AccessModeNone
for _, mode := range modes {
if mode > max {
max = mode
@@ -205,7 +206,7 @@ func (repo *Repository) recalculateTeamAccesses(e Engine, ignTeamID int64) (err
// Owner team gets owner access, and skip for teams that do not
// have relations with repository.
if t.IsOwnerTeam() {
- t.Authorize = ACCESS_MODE_OWNER
+ t.Authorize = AccessModeOwner
} else if !t.hasRepository(e, repo.ID) {
continue
}
diff --git a/internal/db/access_tokens.go b/internal/db/access_tokens.go
new file mode 100644
index 00000000..ad5e6c7a
--- /dev/null
+++ b/internal/db/access_tokens.go
@@ -0,0 +1,65 @@
+// 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 db
+
+import (
+ "fmt"
+
+ "github.com/jinzhu/gorm"
+
+ "gogs.io/gogs/internal/errutil"
+)
+
+// AccessTokensStore is the persistent interface for access tokens.
+//
+// NOTE: All methods are sorted in alphabetical order.
+type AccessTokensStore interface {
+ // GetBySHA returns the access token with given SHA1.
+ // It returns ErrAccessTokenNotExist when not found.
+ GetBySHA(sha string) (*AccessToken, error)
+ // Save persists all values of given access token.
+ Save(t *AccessToken) error
+}
+
+var AccessTokens AccessTokensStore
+
+type accessTokens struct {
+ *gorm.DB
+}
+
+var _ errutil.NotFound = (*ErrAccessTokenNotExist)(nil)
+
+type ErrAccessTokenNotExist struct {
+ args errutil.Args
+}
+
+func IsErrAccessTokenNotExist(err error) bool {
+ _, ok := err.(ErrAccessTokenNotExist)
+ return ok
+}
+
+func (err ErrAccessTokenNotExist) Error() string {
+ return fmt.Sprintf("access token does not exist: %v", err.args)
+}
+
+func (ErrAccessTokenNotExist) NotFound() bool {
+ return true
+}
+
+func (db *accessTokens) GetBySHA(sha string) (*AccessToken, error) {
+ token := new(AccessToken)
+ err := db.Where("sha1 = ?", sha).First(token).Error
+ if err != nil {
+ if err == gorm.ErrRecordNotFound {
+ return nil, ErrAccessTokenNotExist{args: errutil.Args{"sha": sha}}
+ }
+ return nil, err
+ }
+ return token, nil
+}
+
+func (db *accessTokens) Save(t *AccessToken) error {
+ return db.DB.Save(t).Error
+}
diff --git a/internal/db/db.go b/internal/db/db.go
new file mode 100644
index 00000000..85503533
--- /dev/null
+++ b/internal/db/db.go
@@ -0,0 +1,171 @@
+// 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 db
+
+import (
+ "fmt"
+ "io"
+ "net/url"
+ "path/filepath"
+ "strings"
+ "time"
+
+ "github.com/jinzhu/gorm"
+ _ "github.com/jinzhu/gorm/dialects/mssql"
+ _ "github.com/jinzhu/gorm/dialects/mysql"
+ _ "github.com/jinzhu/gorm/dialects/postgres"
+ _ "github.com/jinzhu/gorm/dialects/sqlite"
+ "github.com/pkg/errors"
+ log "unknwon.dev/clog/v2"
+
+ "gogs.io/gogs/internal/conf"
+ "gogs.io/gogs/internal/dbutil"
+)
+
+// parsePostgreSQLHostPort parses given input in various forms defined in
+// https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING
+// and returns proper host and port number.
+func parsePostgreSQLHostPort(info string) (string, string) {
+ host, port := "127.0.0.1", "5432"
+ if strings.Contains(info, ":") && !strings.HasSuffix(info, "]") {
+ idx := strings.LastIndex(info, ":")
+ host = info[:idx]
+ port = info[idx+1:]
+ } else if len(info) > 0 {
+ host = info
+ }
+ return host, port
+}
+
+func parseMSSQLHostPort(info string) (string, string) {
+ host, port := "127.0.0.1", "1433"
+ if strings.Contains(info, ":") {
+ host = strings.Split(info, ":")[0]
+ port = strings.Split(info, ":")[1]
+ } else if strings.Contains(info, ",") {
+ host = strings.Split(info, ",")[0]
+ port = strings.TrimSpace(strings.Split(info, ",")[1])
+ } else if len(info) > 0 {
+ host = info
+ }
+ return host, port
+}
+
+// parseDSN takes given database options and returns parsed DSN.
+func parseDSN(opts conf.DatabaseOpts) (dsn string, err error) {
+ // In case the database name contains "?" with some parameters
+ concate := "?"
+ if strings.Contains(opts.Name, concate) {
+ concate = "&"
+ }
+
+ switch opts.Type {
+ case "mysql":
+ if opts.Host[0] == '/' { // Looks like a unix socket
+ dsn = fmt.Sprintf("%s:%s@unix(%s)/%s%scharset=utf8mb4&parseTime=true",
+ opts.User, opts.Password, opts.Host, opts.Name, concate)
+ } else {
+ dsn = fmt.Sprintf("%s:%s@tcp(%s)/%s%scharset=utf8mb4&parseTime=true",
+ opts.User, opts.Password, opts.Host, opts.Name, concate)
+ }
+
+ case "postgres":
+ host, port := parsePostgreSQLHostPort(opts.Host)
+ if host[0] == '/' { // looks like a unix socket
+ dsn = fmt.Sprintf("postgres://%s:%s@:%s/%s%ssslmode=%s&host=%s",
+ url.QueryEscape(opts.User), url.QueryEscape(opts.Password), port, opts.Name, concate, opts.SSLMode, host)
+ } else {
+ dsn = fmt.Sprintf("postgres://%s:%s@%s:%s/%s%ssslmode=%s",
+ url.QueryEscape(opts.User), url.QueryEscape(opts.Password), host, port, opts.Name, concate, opts.SSLMode)
+ }
+
+ case "mssql":
+ host, port := parseMSSQLHostPort(opts.Host)
+ dsn = fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;",
+ host, port, opts.Name, opts.User, opts.Password)
+
+ case "sqlite3":
+ dsn = "file:" + opts.Path + "?cache=shared&mode=rwc"
+
+ default:
+ return "", errors.Errorf("unrecognized dialect: %s", opts.Type)
+ }
+
+ return dsn, nil
+}
+
+func openDB(opts conf.DatabaseOpts) (*gorm.DB, error) {
+ dsn, err := parseDSN(opts)
+ if err != nil {
+ return nil, errors.Wrap(err, "parse DSN")
+ }
+
+ return gorm.Open(opts.Type, dsn)
+}
+
+func getLogWriter() (io.Writer, error) {
+ sec := conf.File.Section("log.gorm")
+ w, err := log.NewFileWriter(
+ filepath.Join(conf.Log.RootPath, "gorm.log"),
+ log.FileRotationConfig{
+ Rotate: sec.Key("ROTATE").MustBool(true),
+ Daily: sec.Key("ROTATE_DAILY").MustBool(true),
+ MaxSize: sec.Key("MAX_SIZE").MustInt64(100) * 1024 * 1024,
+ MaxDays: sec.Key("MAX_DAYS").MustInt64(3),
+ },
+ )
+ if err != nil {
+ return nil, errors.Wrap(err, `create "gorm.log"`)
+ }
+ return w, nil
+}
+
+func Init() error {
+ db, err := openDB(conf.Database)
+ if err != nil {
+ return errors.Wrap(err, "open database")
+ }
+ db.SingularTable(true)
+ db.DB().SetMaxOpenConns(conf.Database.MaxOpenConns)
+ db.DB().SetMaxIdleConns(conf.Database.MaxIdleConns)
+ db.DB().SetConnMaxLifetime(time.Minute)
+
+ w, err := getLogWriter()
+ if err != nil {
+ return errors.Wrap(err, "get log writer")
+ }
+ db.SetLogger(&dbutil.Writer{Writer: w})
+ if !conf.IsProdMode() {
+ db = db.LogMode(true)
+ }
+
+ switch conf.Database.Type {
+ case "mysql":
+ conf.UseMySQL = true
+ db = db.Set("gorm:table_options", "ENGINE=InnoDB")
+ case "postgres":
+ conf.UsePostgreSQL = true
+ case "mssql":
+ conf.UseMSSQL = true
+ case "sqlite3":
+ conf.UseMySQL = true
+ }
+
+ err = db.AutoMigrate(new(LFSObject)).Error
+ if err != nil {
+ return errors.Wrap(err, "migrate schemes")
+ }
+
+ // Initialize stores, sorted in alphabetical order.
+ AccessTokens = &accessTokens{DB: db}
+ LoginSources = &loginSources{DB: db}
+ LFS = &lfs{DB: db}
+ Perms = &perms{DB: db}
+ Repos = &repos{DB: db}
+ TwoFactors = &twoFactors{DB: db}
+ Users = &users{DB: db}
+
+ return db.DB().Ping()
+}
diff --git a/internal/db/error.go b/internal/db/error.go
index dff234d9..ce87debd 100644
--- a/internal/db/error.go
+++ b/internal/db/error.go
@@ -218,38 +218,6 @@ func (err ErrDeployKeyNameAlreadyUsed) Error() string {
return fmt.Sprintf("public key already exists [repo_id: %d, name: %s]", err.RepoID, err.Name)
}
-// _____ ___________ __
-// / _ \ ____ ____ ____ ______ _____\__ ___/___ | | __ ____ ____
-// / /_\ \_/ ___\/ ___\/ __ \ / ___// ___/ | | / _ \| |/ // __ \ / \
-// / | \ \__\ \__\ ___/ \___ \ \___ \ | |( <_> ) <\ ___/| | \
-// \____|__ /\___ >___ >___ >____ >____ > |____| \____/|__|_ \\___ >___| /
-// \/ \/ \/ \/ \/ \/ \/ \/ \/
-
-type ErrAccessTokenNotExist struct {
- SHA string
-}
-
-func IsErrAccessTokenNotExist(err error) bool {
- _, ok := err.(ErrAccessTokenNotExist)
- return ok
-}
-
-func (err ErrAccessTokenNotExist) Error() string {
- return fmt.Sprintf("access token does not exist [sha: %s]", err.SHA)
-}
-
-type ErrAccessTokenEmpty struct {
-}
-
-func IsErrAccessTokenEmpty(err error) bool {
- _, ok := err.(ErrAccessTokenEmpty)
- return ok
-}
-
-func (err ErrAccessTokenEmpty) Error() string {
- return fmt.Sprintf("access token is empty")
-}
-
// ________ .__ __ .__
// \_____ \_______ _________ ____ |__|____________ _/ |_|__| ____ ____
// / | \_ __ \/ ___\__ \ / \| \___ /\__ \\ __\ |/ _ \ / \
diff --git a/internal/db/errors/login_source.go b/internal/db/errors/login_source.go
index dd18664e..876a0820 100644
--- a/internal/db/errors/login_source.go
+++ b/internal/db/errors/login_source.go
@@ -45,16 +45,3 @@ func (err InvalidLoginSourceType) Error() string {
return fmt.Sprintf("invalid login source type [type: %v]", err.Type)
}
-type LoginSourceMismatch struct {
- Expect int64
- Actual int64
-}
-
-func IsLoginSourceMismatch(err error) bool {
- _, ok := err.(LoginSourceMismatch)
- return ok
-}
-
-func (err LoginSourceMismatch) Error() string {
- return fmt.Sprintf("login source mismatch [expect: %d, actual: %d]", err.Expect, err.Actual)
-}
diff --git a/internal/db/errors/two_factor.go b/internal/db/errors/two_factor.go
index 02cdcf5c..c474152d 100644
--- a/internal/db/errors/two_factor.go
+++ b/internal/db/errors/two_factor.go
@@ -18,16 +18,3 @@ func IsTwoFactorNotFound(err error) bool {
func (err TwoFactorNotFound) Error() string {
return fmt.Sprintf("two-factor authentication does not found [user_id: %d]", err.UserID)
}
-
-type TwoFactorRecoveryCodeNotFound struct {
- Code string
-}
-
-func IsTwoFactorRecoveryCodeNotFound(err error) bool {
- _, ok := err.(TwoFactorRecoveryCodeNotFound)
- return ok
-}
-
-func (err TwoFactorRecoveryCodeNotFound) Error() string {
- return fmt.Sprintf("two-factor recovery code does not found [code: %s]", err.Code)
-}
diff --git a/internal/db/issue.go b/internal/db/issue.go
index b153e6a1..6347c99d 100644
--- a/internal/db/issue.go
+++ b/internal/db/issue.go
@@ -681,7 +681,7 @@ func newIssue(e *xorm.Session, opts NewIssueOptions) (err error) {
// Assume assignee is invalid and drop silently.
opts.Issue.AssigneeID = 0
if assignee != nil {
- valid, err := hasAccess(e, assignee.ID, opts.Repo, ACCESS_MODE_READ)
+ valid, err := hasAccess(e, assignee.ID, opts.Repo, AccessModeRead)
if err != nil {
return fmt.Errorf("hasAccess [user_id: %d, repo_id: %d]: %v", assignee.ID, opts.Repo.ID, err)
}
diff --git a/internal/db/lfs.go b/internal/db/lfs.go
new file mode 100644
index 00000000..bf1af0bb
--- /dev/null
+++ b/internal/db/lfs.go
@@ -0,0 +1,129 @@
+// 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 db
+
+import (
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
+ "time"
+
+ "github.com/jinzhu/gorm"
+ "github.com/pkg/errors"
+
+ "gogs.io/gogs/internal/conf"
+ "gogs.io/gogs/internal/errutil"
+ "gogs.io/gogs/internal/lfsutil"
+)
+
+// LFSStore is the persistent interface for LFS objects.
+//
+// NOTE: All methods are sorted in alphabetical order.
+type LFSStore interface {
+ // CreateObject streams io.ReadCloser to target storage and creates a record in database.
+ CreateObject(repoID int64, oid lfsutil.OID, rc io.ReadCloser, storage lfsutil.Storage) error
+ // GetObjectByOID returns the LFS object with given OID. It returns ErrLFSObjectNotExist
+ // when not found.
+ GetObjectByOID(repoID int64, oid lfsutil.OID) (*LFSObject, error)
+ // GetObjectsByOIDs returns LFS objects found within "oids". The returned list could have
+ // less elements if some oids were not found.
+ GetObjectsByOIDs(repoID int64, oids ...lfsutil.OID) ([]*LFSObject, error)
+}
+
+var LFS LFSStore
+
+type lfs struct {
+ *gorm.DB
+}
+
+// LFSObject is the relation between an LFS object and a repository.
+type LFSObject struct {
+ RepoID int64 `gorm:"PRIMARY_KEY;AUTO_INCREMENT:false"`
+ OID lfsutil.OID `gorm:"PRIMARY_KEY;column:oid"`
+ Size int64 `gorm:"NOT NULL"`
+ Storage lfsutil.Storage `gorm:"NOT NULL"`
+ CreatedAt time.Time `gorm:"NOT NULL"`
+}
+
+func (db *lfs) CreateObject(repoID int64, oid lfsutil.OID, rc io.ReadCloser, storage lfsutil.Storage) (err error) {
+ if storage != lfsutil.StorageLocal {
+ return errors.New("only local storage is supported")
+ }
+
+ fpath := lfsutil.StorageLocalPath(conf.LFS.ObjectsPath, oid)
+ defer func() {
+ rc.Close()
+
+ if err != nil {
+ _ = os.Remove(fpath)
+ }
+ }()
+
+ err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm)
+ if err != nil {
+ return errors.Wrap(err, "create directories")
+ }
+ w, err := os.Create(fpath)
+ if err != nil {
+ return errors.Wrap(err, "create file")
+ }
+ defer w.Close()
+
+ written, err := io.Copy(w, rc)
+ if err != nil {
+ return errors.Wrap(err, "copy file")
+ }
+
+ object := &LFSObject{
+ RepoID: repoID,
+ OID: oid,
+ Size: written,
+ Storage: storage,
+ }
+ return db.DB.Create(object).Error
+}
+
+type ErrLFSObjectNotExist struct {
+ args errutil.Args
+}
+
+func IsErrLFSObjectNotExist(err error) bool {
+ _, ok := err.(ErrLFSObjectNotExist)
+ return ok
+}
+
+func (err ErrLFSObjectNotExist) Error() string {
+ return fmt.Sprintf("LFS object does not exist: %v", err.args)
+}
+
+func (ErrLFSObjectNotExist) NotFound() bool {
+ return true
+}
+
+func (db *lfs) GetObjectByOID(repoID int64, oid lfsutil.OID) (*LFSObject, error) {
+ object := new(LFSObject)
+ err := db.Where("repo_id = ? AND oid = ?", repoID, oid).First(object).Error
+ if err != nil {
+ if err == gorm.ErrRecordNotFound {
+ return nil, ErrLFSObjectNotExist{args: errutil.Args{"repoID": repoID, "oid": oid}}
+ }
+ return nil, err
+ }
+ return object, err
+}
+
+func (db *lfs) GetObjectsByOIDs(repoID int64, oids ...lfsutil.OID) ([]*LFSObject, error) {
+ if len(oids) == 0 {
+ return []*LFSObject{}, nil
+ }
+
+ objects := make([]*LFSObject, 0, len(oids))
+ err := db.Where("repo_id = ? AND oid IN (?)", repoID, oids).Find(&objects).Error
+ if err != nil && err != gorm.ErrRecordNotFound {
+ return nil, err
+ }
+ return objects, nil
+}
diff --git a/internal/db/login_source.go b/internal/db/login_source.go
index 80d65f6e..aabca145 100644
--- a/internal/db/login_source.go
+++ b/internal/db/login_source.go
@@ -35,21 +35,21 @@ type LoginType int
// Note: new type must append to the end of list to maintain compatibility.
const (
- LOGIN_NOTYPE LoginType = iota
- LOGIN_PLAIN // 1
- LOGIN_LDAP // 2
- LOGIN_SMTP // 3
- LOGIN_PAM // 4
- LOGIN_DLDAP // 5
- LOGIN_GITHUB // 6
+ LoginNotype LoginType = iota
+ LoginPlain // 1
+ LoginLDAP // 2
+ LoginSMTP // 3
+ LoginPAM // 4
+ LoginDLDAP // 5
+ LoginGitHub // 6
)
var LoginNames = map[LoginType]string{
- LOGIN_LDAP: "LDAP (via BindDN)",
- LOGIN_DLDAP: "LDAP (simple auth)", // Via direct bind
- LOGIN_SMTP: "SMTP",
- LOGIN_PAM: "PAM",
- LOGIN_GITHUB: "GitHub",
+ LoginLDAP: "LDAP (via BindDN)",
+ LoginDLDAP: "LDAP (simple auth)", // Via direct bind
+ LoginSMTP: "SMTP",
+ LoginPAM: "PAM",
+ LoginGitHub: "GitHub",
}
var SecurityProtocolNames = map[ldap.SecurityProtocol]string{
@@ -185,13 +185,13 @@ func (s *LoginSource) BeforeSet(colName string, val xorm.Cell) {
switch colName {
case "type":
switch LoginType(Cell2Int64(val)) {
- case LOGIN_LDAP, LOGIN_DLDAP:
+ case LoginLDAP, LoginDLDAP:
s.Cfg = new(LDAPConfig)
- case LOGIN_SMTP:
+ case LoginSMTP:
s.Cfg = new(SMTPConfig)
- case LOGIN_PAM:
+ case LoginPAM:
s.Cfg = new(PAMConfig)
- case LOGIN_GITHUB:
+ case LoginGitHub:
s.Cfg = new(GitHubConfig)
default:
panic("unrecognized login source type: " + com.ToStr(*val))
@@ -213,23 +213,23 @@ func (s *LoginSource) TypeName() string {
}
func (s *LoginSource) IsLDAP() bool {
- return s.Type == LOGIN_LDAP
+ return s.Type == LoginLDAP
}
func (s *LoginSource) IsDLDAP() bool {
- return s.Type == LOGIN_DLDAP
+ return s.Type == LoginDLDAP
}
func (s *LoginSource) IsSMTP() bool {
- return s.Type == LOGIN_SMTP
+ return s.Type == LoginSMTP
}
func (s *LoginSource) IsPAM() bool {
- return s.Type == LOGIN_PAM
+ return s.Type == LoginPAM
}
func (s *LoginSource) IsGitHub() bool {
- return s.Type == LOGIN_GITHUB
+ return s.Type == LoginGitHub
}
func (s *LoginSource) HasTLS() bool {
@@ -240,9 +240,9 @@ func (s *LoginSource) HasTLS() bool {
func (s *LoginSource) UseTLS() bool {
switch s.Type {
- case LOGIN_LDAP, LOGIN_DLDAP:
+ case LoginLDAP, LoginDLDAP:
return s.LDAP().SecurityProtocol != ldap.SECURITY_PROTOCOL_UNENCRYPTED
- case LOGIN_SMTP:
+ case LoginSMTP:
return s.SMTP().TLS
}
@@ -251,9 +251,9 @@ func (s *LoginSource) UseTLS() bool {
func (s *LoginSource) SkipVerify() bool {
switch s.Type {
- case LOGIN_LDAP, LOGIN_DLDAP:
+ case LoginLDAP, LoginDLDAP:
return s.LDAP().SkipVerify
- case LOGIN_SMTP:
+ case LoginSMTP:
return s.SMTP().SkipVerify
}
@@ -293,8 +293,8 @@ func CreateLoginSource(source *LoginSource) error {
return nil
}
-// LoginSources returns all login sources defined.
-func LoginSources() ([]*LoginSource, error) {
+// ListLoginSources returns all login sources defined.
+func ListLoginSources() ([]*LoginSource, error) {
sources := make([]*LoginSource, 0, 2)
if err := x.Find(&sources); err != nil {
return nil, err
@@ -312,18 +312,6 @@ func ActivatedLoginSources() ([]*LoginSource, error) {
return append(sources, localLoginSources.ActivatedList()...), nil
}
-// GetLoginSourceByID returns login source by given ID.
-func GetLoginSourceByID(id int64) (*LoginSource, error) {
- source := new(LoginSource)
- has, err := x.Id(id).Get(source)
- if err != nil {
- return nil, err
- } else if !has {
- return localLoginSources.GetLoginSourceByID(id)
- }
- return source, nil
-}
-
// ResetNonDefaultLoginSources clean other default source flag
func ResetNonDefaultLoginSources(source *LoginSource) error {
// update changes to DB
@@ -504,19 +492,19 @@ func LoadAuthSources() {
authType := s.Key("type").String()
switch authType {
case "ldap_bind_dn":
- loginSource.Type = LOGIN_LDAP
+ loginSource.Type = LoginLDAP
loginSource.Cfg = &LDAPConfig{}
case "ldap_simple_auth":
- loginSource.Type = LOGIN_DLDAP
+ loginSource.Type = LoginDLDAP
loginSource.Cfg = &LDAPConfig{}
case "smtp":
- loginSource.Type = LOGIN_SMTP
+ loginSource.Type = LoginSMTP
loginSource.Cfg = &SMTPConfig{}
case "pam":
- loginSource.Type = LOGIN_PAM
+ loginSource.Type = LoginPAM
loginSource.Cfg = &PAMConfig{}
case "github":
- loginSource.Type = LOGIN_GITHUB
+ loginSource.Type = LoginGitHub
loginSource.Cfg = &GitHubConfig{}
default:
log.Fatal("Failed to load authentication source: unknown type '%s'", authType)
@@ -552,15 +540,15 @@ func composeFullName(firstname, surname, username string) string {
// LoginViaLDAP queries if login/password is valid against the LDAP directory pool,
// and create a local user if success when enabled.
-func LoginViaLDAP(user *User, login, password string, source *LoginSource, autoRegister bool) (*User, error) {
- username, fn, sn, mail, isAdmin, succeed := source.Cfg.(*LDAPConfig).SearchEntry(login, password, source.Type == LOGIN_DLDAP)
+func LoginViaLDAP(login, password string, source *LoginSource, autoRegister bool) (*User, error) {
+ username, fn, sn, mail, isAdmin, succeed := source.Cfg.(*LDAPConfig).SearchEntry(login, password, source.Type == LoginDLDAP)
if !succeed {
// User not in LDAP, do nothing
return nil, ErrUserNotExist{args: map[string]interface{}{"login": login}}
}
if !autoRegister {
- return user, nil
+ return nil, nil
}
// Fallback.
@@ -576,7 +564,7 @@ func LoginViaLDAP(user *User, login, password string, source *LoginSource, autoR
mail = fmt.Sprintf("%s@localhost", username)
}
- user = &User{
+ user := &User{
LowerName: strings.ToLower(username),
Name: username,
FullName: composeFullName(fn, sn, username),
@@ -669,7 +657,7 @@ func SMTPAuth(a smtp.Auth, cfg *SMTPConfig) error {
// LoginViaSMTP queries if login/password is valid against the SMTP,
// and create a local user if success when enabled.
-func LoginViaSMTP(user *User, login, password string, sourceID int64, cfg *SMTPConfig, autoRegister bool) (*User, error) {
+func LoginViaSMTP(login, password string, sourceID int64, cfg *SMTPConfig, autoRegister bool) (*User, error) {
// Verify allowed domains.
if len(cfg.AllowedDomains) > 0 {
idx := strings.Index(login, "@")
@@ -701,7 +689,7 @@ func LoginViaSMTP(user *User, login, password string, sourceID int64, cfg *SMTPC
}
if !autoRegister {
- return user, nil
+ return nil, nil
}
username := login
@@ -710,12 +698,12 @@ func LoginViaSMTP(user *User, login, password string, sourceID int64, cfg *SMTPC
username = login[:idx]
}
- user = &User{
+ user := &User{
LowerName: strings.ToLower(username),
Name: strings.ToLower(username),
Email: login,
Passwd: password,
- LoginType: LOGIN_SMTP,
+ LoginType: LoginSMTP,
LoginSource: sourceID,
LoginName: login,
IsActive: true,
@@ -732,7 +720,7 @@ func LoginViaSMTP(user *User, login, password string, sourceID int64, cfg *SMTPC
// LoginViaPAM queries if login/password is valid against the PAM,
// and create a local user if success when enabled.
-func LoginViaPAM(user *User, login, password string, sourceID int64, cfg *PAMConfig, autoRegister bool) (*User, error) {
+func LoginViaPAM(login, password string, sourceID int64, cfg *PAMConfig, autoRegister bool) (*User, error) {
if err := pam.PAMAuth(cfg.ServiceName, login, password); err != nil {
if strings.Contains(err.Error(), "Authentication failure") {
return nil, ErrUserNotExist{args: map[string]interface{}{"login": login}}
@@ -741,15 +729,15 @@ func LoginViaPAM(user *User, login, password string, sourceID int64, cfg *PAMCon
}
if !autoRegister {
- return user, nil
+ return nil, nil
}
- user = &User{
+ user := &User{
LowerName: strings.ToLower(login),
Name: login,
Email: login,
Passwd: password,
- LoginType: LOGIN_PAM,
+ LoginType: LoginPAM,
LoginSource: sourceID,
LoginName: login,
IsActive: true,
@@ -757,14 +745,14 @@ func LoginViaPAM(user *User, login, password string, sourceID int64, cfg *PAMCon
return user, CreateUser(user)
}
-//________.__ __ ___ ___ ___.
-/// _____/|__|/ |_ / | \ __ _\_ |__
-/// \ ___| \ __\/ ~ \ | \ __ \
-//\ \_\ \ || | \ Y / | / \_\ \
-//\______ /__||__| \___|_ /|____/|___ /
-//\/ \/ \/
+// ________.__ __ ___ ___ ___.
+// / _____/|__|/ |_ / | \ __ _\_ |__
+// / \ ___| \ __\/ ~ \ | \ __ \
+// \ \_\ \ || | \ Y / | / \_\ \
+// \______ /__||__| \___|_ /|____/|___ /
+// \/ \/ \/
-func LoginViaGitHub(user *User, login, password string, sourceID int64, cfg *GitHubConfig, autoRegister bool) (*User, error) {
+func LoginViaGitHub(login, password string, sourceID int64, cfg *GitHubConfig, autoRegister bool) (*User, error) {
fullname, email, url, location, err := github.Authenticate(cfg.APIEndpoint, login, password)
if err != nil {
if strings.Contains(err.Error(), "401") {
@@ -774,16 +762,16 @@ func LoginViaGitHub(user *User, login, password string, sourceID int64, cfg *Git
}
if !autoRegister {
- return user, nil
+ return nil, nil
}
- user = &User{
+ user := &User{
LowerName: strings.ToLower(login),
Name: login,
FullName: fullname,
Email: email,
Website: url,
Passwd: password,
- LoginType: LOGIN_GITHUB,
+ LoginType: LoginGitHub,
LoginSource: sourceID,
LoginName: login,
IsActive: true,
@@ -792,75 +780,21 @@ func LoginViaGitHub(user *User, login, password string, sourceID int64, cfg *Git
return user, CreateUser(user)
}
-func remoteUserLogin(user *User, login, password string, source *LoginSource, autoRegister bool) (*User, error) {
+func authenticateViaLoginSource(source *LoginSource, login, password string, autoRegister bool) (*User, error) {
if !source.IsActived {
return nil, errors.LoginSourceNotActivated{SourceID: source.ID}
}
switch source.Type {
- case LOGIN_LDAP, LOGIN_DLDAP:
- return LoginViaLDAP(user, login, password, source, autoRegister)
- case LOGIN_SMTP:
- return LoginViaSMTP(user, login, password, source.ID, source.Cfg.(*SMTPConfig), autoRegister)
- case LOGIN_PAM:
- return LoginViaPAM(user, login, password, source.ID, source.Cfg.(*PAMConfig), autoRegister)
- case LOGIN_GITHUB:
- return LoginViaGitHub(user, login, password, source.ID, source.Cfg.(*GitHubConfig), autoRegister)
+ case LoginLDAP, LoginDLDAP:
+ return LoginViaLDAP(login, password, source, autoRegister)
+ case LoginSMTP:
+ return LoginViaSMTP(login, password, source.ID, source.Cfg.(*SMTPConfig), autoRegister)
+ case LoginPAM:
+ return LoginViaPAM(login, password, source.ID, source.Cfg.(*PAMConfig), autoRegister)
+ case LoginGitHub:
+ return LoginViaGitHub(login, password, source.ID, source.Cfg.(*GitHubConfig), autoRegister)
}
return nil, errors.InvalidLoginSourceType{Type: source.Type}
}
-
-// UserLogin validates user name and password via given login source ID.
-// If the loginSourceID is negative, it will abort login process if user is not found.
-func UserLogin(username, password string, loginSourceID int64) (*User, error) {
- var user *User
- if strings.Contains(username, "@") {
- user = &User{Email: strings.ToLower(username)}
- } else {
- user = &User{LowerName: strings.ToLower(username)}
- }
-
- hasUser, err := x.Get(user)
- if err != nil {
- return nil, fmt.Errorf("get user record: %v", err)
- }
-
- if hasUser {
- // Note: This check is unnecessary but to reduce user confusion at login page
- // and make it more consistent at user's perspective.
- if loginSourceID >= 0 && user.LoginSource != loginSourceID {
- return nil, errors.LoginSourceMismatch{Expect: loginSourceID, Actual: user.LoginSource}
- }
-
- // Validate password hash fetched from database for local accounts
- if user.LoginType == LOGIN_NOTYPE ||
- user.LoginType == LOGIN_PLAIN {
- if user.ValidatePassword(password) {
- return user, nil
- }
-
- return nil, ErrUserNotExist{args: map[string]interface{}{"userID": user.ID, "name": user.Name}}
- }
-
- // Remote login to the login source the user is associated with
- source, err := GetLoginSourceByID(user.LoginSource)
- if err != nil {
- return nil, err
- }
-
- return remoteUserLogin(user, user.LoginName, password, source, false)
- }
-
- // Non-local login source is always greater than 0
- if loginSourceID <= 0 {
- return nil, ErrUserNotExist{args: map[string]interface{}{"name": username}}
- }
-
- source, err := GetLoginSourceByID(loginSourceID)
- if err != nil {
- return nil, err
- }
-
- return remoteUserLogin(nil, username, password, source, true)
-}
diff --git a/internal/db/login_sources.go b/internal/db/login_sources.go
new file mode 100644
index 00000000..91432ff4
--- /dev/null
+++ b/internal/db/login_sources.go
@@ -0,0 +1,36 @@
+// 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 db
+
+import (
+ "github.com/jinzhu/gorm"
+)
+
+// LoginSourcesStore is the persistent interface for login sources.
+//
+// NOTE: All methods are sorted in alphabetical order.
+type LoginSourcesStore interface {
+ // GetByID returns the login source with given ID.
+ // It returns ErrLoginSourceNotExist when not found.
+ GetByID(id int64) (*LoginSource, error)
+}
+
+var LoginSources LoginSourcesStore
+
+type loginSources struct {
+ *gorm.DB
+}
+
+func (db *loginSources) GetByID(id int64) (*LoginSource, error) {
+ source := new(LoginSource)
+ err := db.Where("id = ?", id).First(source).Error
+ if err != nil {
+ if err == gorm.ErrRecordNotFound {
+ return localLoginSources.GetLoginSourceByID(id)
+ }
+ return nil, err
+ }
+ return source, nil
+}
diff --git a/internal/db/models.go b/internal/db/models.go
index cf00727e..3bb35e7f 100644
--- a/internal/db/models.go
+++ b/internal/db/models.go
@@ -7,7 +7,6 @@ package db
import (
"bufio"
"database/sql"
- "errors"
"fmt"
"net/url"
"os"
@@ -15,10 +14,7 @@ import (
"strings"
"time"
- _ "github.com/denisenkom/go-mssqldb"
- _ "github.com/go-sql-driver/mysql"
"github.com/json-iterator/go"
- _ "github.com/lib/pq"
"github.com/unknwon/com"
log "unknwon.dev/clog/v2"
"xorm.io/core"
@@ -48,8 +44,6 @@ var (
x *xorm.Engine
tables []interface{}
HasEngine bool
-
- EnableSQLite3 bool
)
func init() {
@@ -70,35 +64,6 @@ func init() {
}
}
-// parsePostgreSQLHostPort parses given input in various forms defined in
-// https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING
-// and returns proper host and port number.
-func parsePostgreSQLHostPort(info string) (string, string) {
- host, port := "127.0.0.1", "5432"
- if strings.Contains(info, ":") && !strings.HasSuffix(info, "]") {
- idx := strings.LastIndex(info, ":")
- host = info[:idx]
- port = info[idx+1:]
- } else if len(info) > 0 {
- host = info
- }
- return host, port
-}
-
-func parseMSSQLHostPort(info string) (string, string) {
- host, port := "127.0.0.1", "1433"
- if strings.Contains(info, ":") {
- host = strings.Split(info, ":")[0]
- port = strings.Split(info, ":")[1]
- } else if strings.Contains(info, ",") {
- host = strings.Split(info, ",")[0]
- port = strings.TrimSpace(strings.Split(info, ",")[1])
- } else if len(info) > 0 {
- host = info
- }
- return host, port
-}
-
func getEngine() (*xorm.Engine, error) {
Param := "?"
if strings.Contains(conf.Database.Name, Param) {
@@ -133,12 +98,9 @@ func getEngine() (*xorm.Engine, error) {
case "mssql":
conf.UseMSSQL = true
host, port := parseMSSQLHostPort(conf.Database.Host)
- connStr = fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", host, port, conf.Database.Name, conf.Database.User, conf.Database.Passwd)
+ connStr = fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", host, port, conf.Database.Name, conf.Database.User, conf.Database.Password)
case "sqlite3":
- if !EnableSQLite3 {
- return nil, errors.New("this binary version does not build support for SQLite3")
- }
if err := os.MkdirAll(path.Dir(conf.Database.Path), os.ModePerm); err != nil {
return nil, fmt.Errorf("create directories: %v", err)
}
@@ -183,9 +145,8 @@ func SetEngine() (err error) {
return fmt.Errorf("create 'xorm.log': %v", err)
}
- // To prevent mystery "MySQL: invalid connection" error,
- // see https://gogs.io/gogs/issues/5532.
- x.SetMaxIdleConns(0)
+ x.SetMaxOpenConns(conf.Database.MaxOpenConns)
+ x.SetMaxIdleConns(conf.Database.MaxIdleConns)
x.SetConnMaxLifetime(time.Second)
if conf.IsProdMode() {
@@ -194,7 +155,7 @@ func SetEngine() (err error) {
x.SetLogger(xorm.NewSimpleLogger(logger))
}
x.ShowSQL(true)
- return nil
+ return Init()
}
func NewEngine() (err error) {
@@ -331,13 +292,13 @@ func ImportDatabase(dirPath string, verbose bool) (err error) {
tp := LoginType(com.StrTo(com.ToStr(meta["Type"])).MustInt64())
switch tp {
- case LOGIN_LDAP, LOGIN_DLDAP:
+ case LoginLDAP, LoginDLDAP:
bean.Cfg = new(LDAPConfig)
- case LOGIN_SMTP:
+ case LoginSMTP:
bean.Cfg = new(SMTPConfig)
- case LOGIN_PAM:
+ case LoginPAM:
bean.Cfg = new(PAMConfig)
- case LOGIN_GITHUB:
+ case LoginGitHub:
bean.Cfg = new(GitHubConfig)
default:
return fmt.Errorf("unrecognized login source type:: %v", tp)
diff --git a/internal/db/models_sqlite.go b/internal/db/models_sqlite.go
deleted file mode 100644
index c462cc5d..00000000
--- a/internal/db/models_sqlite.go
+++ /dev/null
@@ -1,15 +0,0 @@
-// +build sqlite
-
-// Copyright 2014 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 db
-
-import (
- _ "github.com/mattn/go-sqlite3"
-)
-
-func init() {
- EnableSQLite3 = true
-}
diff --git a/internal/db/org.go b/internal/db/org.go
index bcf307a7..b28d4333 100644
--- a/internal/db/org.go
+++ b/internal/db/org.go
@@ -148,7 +148,7 @@ func CreateOrganization(org, owner *User) (err error) {
OrgID: org.ID,
LowerName: strings.ToLower(OWNER_TEAM),
Name: OWNER_TEAM,
- Authorize: ACCESS_MODE_OWNER,
+ Authorize: AccessModeOwner,
NumMembers: 1,
}
if _, err = sess.Insert(t); err != nil {
diff --git a/internal/db/org_team.go b/internal/db/org_team.go
index 8ed587db..f5309888 100644
--- a/internal/db/org_team.go
+++ b/internal/db/org_team.go
@@ -47,7 +47,7 @@ func (t *Team) IsOwnerTeam() bool {
// HasWriteAccess returns true if team has at least write level access mode.
func (t *Team) HasWriteAccess() bool {
- return t.Authorize >= ACCESS_MODE_WRITE
+ return t.Authorize >= AccessModeWrite
}
// IsTeamMember returns true if given user is a member of team.
@@ -174,7 +174,7 @@ func (t *Team) removeRepository(e Engine, repo *Repository, recalculate bool) (e
return fmt.Errorf("get team members: %v", err)
}
for _, member := range t.Members {
- has, err := hasAccess(e, member.ID, repo, ACCESS_MODE_READ)
+ has, err := hasAccess(e, member.ID, repo, AccessModeRead)
if err != nil {
return err
} else if has {
diff --git a/internal/db/perms.go b/internal/db/perms.go
new file mode 100644
index 00000000..6dc5d423
--- /dev/null
+++ b/internal/db/perms.go
@@ -0,0 +1,56 @@
+// 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 db
+
+import (
+ "github.com/jinzhu/gorm"
+ log "unknwon.dev/clog/v2"
+)
+
+// PermsStore is the persistent interface for permissions.
+//
+// NOTE: All methods are sorted in alphabetical order.
+type PermsStore interface {
+ // AccessMode returns the access mode of given user has to the repository.
+ AccessMode(userID int64, repo *Repository) AccessMode
+ // Authorize returns true if the user has as good as desired access mode to
+ // the repository.
+ Authorize(userID int64, repo *Repository, desired AccessMode) bool
+}
+
+var Perms PermsStore
+
+type perms struct {
+ *gorm.DB
+}
+
+func (db *perms) AccessMode(userID int64, repo *Repository) AccessMode {
+ var mode AccessMode
+ // Everyone has read access to public repository.
+ if !repo.IsPrivate {
+ mode = AccessModeRead
+ }
+
+ // Quick check to avoid a DB query.
+ if userID <= 0 {
+ return mode
+ }
+
+ if userID == repo.OwnerID {
+ return AccessModeOwner
+ }
+
+ access := new(Access)
+ err := db.Where("user_id = ? AND repo_id = ?", userID, repo.ID).First(access).Error
+ if err != nil {
+ log.Error("Failed to get access [user_id: %d, repo_id: %d]: %v", userID, repo.ID, err)
+ return mode
+ }
+ return access.Mode
+}
+
+func (db *perms) Authorize(userID int64, repo *Repository, desired AccessMode) bool {
+ return desired <= db.AccessMode(userID, repo)
+}
diff --git a/internal/db/repo.go b/internal/db/repo.go
index 7c27b26a..a7d54bb9 100644
--- a/internal/db/repo.go
+++ b/internal/db/repo.go
@@ -492,7 +492,7 @@ func (repo *Repository) getUsersWithAccesMode(e Engine, mode AccessMode) (_ []*U
// getAssignees returns a list of users who can be assigned to issues in this repository.
func (repo *Repository) getAssignees(e Engine) (_ []*User, err error) {
- return repo.getUsersWithAccesMode(e, ACCESS_MODE_READ)
+ return repo.getUsersWithAccesMode(e, AccessModeRead)
}
// GetAssignees returns all users that have read access and can be assigned to issues
@@ -508,7 +508,7 @@ func (repo *Repository) GetAssigneeByID(userID int64) (*User, error) {
// GetWriters returns all users that have write access to the repository.
func (repo *Repository) GetWriters() (_ []*User, err error) {
- return repo.getUsersWithAccesMode(x, ACCESS_MODE_WRITE)
+ return repo.getUsersWithAccesMode(x, AccessModeWrite)
}
// GetMilestoneByID returns the milestone belongs to repository by given ID.
@@ -551,7 +551,7 @@ func (repo *Repository) ComposeCompareURL(oldCommitID, newCommitID string) strin
}
func (repo *Repository) HasAccess(userID int64) bool {
- has, _ := HasAccess(userID, repo, ACCESS_MODE_READ)
+ has, _ := HasAccess(userID, repo, AccessModeRead)
return has
}
@@ -1666,6 +1666,7 @@ func (ErrRepoNotExist) NotFound() bool {
}
// GetRepositoryByName returns the repository by given name under user if exists.
+// Deprecated: Use Repos.GetByName instead.
func GetRepositoryByName(ownerID int64, name string) (*Repository, error) {
repo := &Repository{
OwnerID: ownerID,
diff --git a/internal/db/repo_branch.go b/internal/db/repo_branch.go
index 8da99945..ceb99231 100644
--- a/internal/db/repo_branch.go
+++ b/internal/db/repo_branch.go
@@ -175,7 +175,7 @@ func UpdateOrgProtectBranch(repo *Repository, protectBranch *ProtectBranch, whit
userIDs := tool.StringsToInt64s(strings.Split(whitelistUserIDs, ","))
validUserIDs = make([]int64, 0, len(userIDs))
for _, userID := range userIDs {
- has, err := HasAccess(userID, repo, ACCESS_MODE_WRITE)
+ has, err := HasAccess(userID, repo, AccessModeWrite)
if err != nil {
return fmt.Errorf("HasAccess [user_id: %d, repo_id: %d]: %v", userID, protectBranch.RepoID, err)
} else if !has {
@@ -193,7 +193,7 @@ func UpdateOrgProtectBranch(repo *Repository, protectBranch *ProtectBranch, whit
if protectBranch.WhitelistTeamIDs != whitelistTeamIDs {
hasTeamsChanged = true
teamIDs := tool.StringsToInt64s(strings.Split(whitelistTeamIDs, ","))
- teams, err := GetTeamsHaveAccessToRepo(repo.OwnerID, repo.ID, ACCESS_MODE_WRITE)
+ teams, err := GetTeamsHaveAccessToRepo(repo.OwnerID, repo.ID, AccessModeWrite)
if err != nil {
return fmt.Errorf("GetTeamsHaveAccessToRepo [org_id: %d, repo_id: %d]: %v", repo.OwnerID, repo.ID, err)
}
diff --git a/internal/db/repo_collaboration.go b/internal/db/repo_collaboration.go
index b3d046ec..8aec18dc 100644
--- a/internal/db/repo_collaboration.go
+++ b/internal/db/repo_collaboration.go
@@ -22,11 +22,11 @@ type Collaboration struct {
func (c *Collaboration) ModeI18nKey() string {
switch c.Mode {
- case ACCESS_MODE_READ:
+ case AccessModeRead:
return "repo.settings.collaboration.read"
- case ACCESS_MODE_WRITE:
+ case AccessModeWrite:
return "repo.settings.collaboration.write"
- case ACCESS_MODE_ADMIN:
+ case AccessModeAdmin:
return "repo.settings.collaboration.admin"
default:
return "repo.settings.collaboration.undefined"
@@ -64,7 +64,7 @@ func (repo *Repository) AddCollaborator(u *User) error {
} else if has {
return nil
}
- collaboration.Mode = ACCESS_MODE_WRITE
+ collaboration.Mode = AccessModeWrite
sess := x.NewSession()
defer sess.Close()
@@ -96,9 +96,9 @@ func (c *Collaborator) APIFormat() *api.Collaborator {
return &api.Collaborator{
User: c.User.APIFormat(),
Permissions: api.Permission{
- Admin: c.Collaboration.Mode >= ACCESS_MODE_ADMIN,
- Push: c.Collaboration.Mode >= ACCESS_MODE_WRITE,
- Pull: c.Collaboration.Mode >= ACCESS_MODE_READ,
+ Admin: c.Collaboration.Mode >= AccessModeAdmin,
+ Push: c.Collaboration.Mode >= AccessModeWrite,
+ Pull: c.Collaboration.Mode >= AccessModeRead,
},
}
}
@@ -131,7 +131,7 @@ func (repo *Repository) GetCollaborators() ([]*Collaborator, error) {
// ChangeCollaborationAccessMode sets new access mode for the collaboration.
func (repo *Repository) ChangeCollaborationAccessMode(userID int64, mode AccessMode) error {
// Discard invalid input
- if mode <= ACCESS_MODE_NONE || mode > ACCESS_MODE_OWNER {
+ if mode <= AccessModeNone || mode > AccessModeOwner {
return nil
}
diff --git a/internal/db/repos.go b/internal/db/repos.go
new file mode 100644
index 00000000..bcab7dbd
--- /dev/null
+++ b/internal/db/repos.go
@@ -0,0 +1,38 @@
+// 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 db
+
+import (
+ "strings"
+
+ "github.com/jinzhu/gorm"
+)
+
+// ReposStore is the persistent interface for repositories.
+//
+// NOTE: All methods are sorted in alphabetical order.
+type ReposStore interface {
+ // GetByName returns the repository with given owner and name.
+ // It returns ErrRepoNotExist when not found.
+ GetByName(ownerID int64, name string) (*Repository, error)
+}
+
+var Repos ReposStore
+
+type repos struct {
+ *gorm.DB
+}
+
+func (db *repos) GetByName(ownerID int64, name string) (*Repository, error) {
+ repo := new(Repository)
+ err := db.Where("owner_id = ? AND lower_name = ?", ownerID, strings.ToLower(name)).First(repo).Error
+ if err != nil {
+ if err == gorm.ErrRecordNotFound {
+ return nil, ErrRepoNotExist{args: map[string]interface{}{"ownerID": ownerID, "name": name}}
+ }
+ return nil, err
+ }
+ return repo, nil
+}
diff --git a/internal/db/ssh_key.go b/internal/db/ssh_key.go
index b82f81a6..49cee17a 100644
--- a/internal/db/ssh_key.go
+++ b/internal/db/ssh_key.go
@@ -426,7 +426,7 @@ func AddPublicKey(ownerID int64, name, content string) (*PublicKey, error) {
OwnerID: ownerID,
Name: name,
Content: content,
- Mode: ACCESS_MODE_WRITE,
+ Mode: AccessModeWrite,
Type: KEY_TYPE_USER,
}
if err = addKey(sess, key); err != nil {
@@ -656,7 +656,7 @@ func AddDeployKey(repoID int64, name, content string) (*DeployKey, error) {
pkey := &PublicKey{
Content: content,
- Mode: ACCESS_MODE_READ,
+ Mode: AccessModeRead,
Type: KEY_TYPE_DEPLOY,
}
has, err := x.Get(pkey)
@@ -753,7 +753,7 @@ func DeleteDeployKey(doer *User, id int64) error {
if err != nil {
return fmt.Errorf("GetRepositoryByID: %v", err)
}
- yes, err := HasAccess(doer.ID, repo, ACCESS_MODE_ADMIN)
+ yes, err := HasAccess(doer.ID, repo, AccessModeAdmin)
if err != nil {
return fmt.Errorf("HasAccess: %v", err)
} else if !yes {
diff --git a/internal/db/token.go b/internal/db/token.go
index e7cb7e99..afe747e0 100644
--- a/internal/db/token.go
+++ b/internal/db/token.go
@@ -16,17 +16,17 @@ import (
// AccessToken represents a personal access token.
type AccessToken struct {
- ID int64
- UID int64 `xorm:"INDEX"`
- Name string
- Sha1 string `xorm:"UNIQUE VARCHAR(40)"`
+ ID int64
+ UserID int64 `xorm:"uid INDEX" gorm:"COLUMN:uid"`
+ Name string
+ Sha1 string `xorm:"UNIQUE VARCHAR(40)"`
- Created time.Time `xorm:"-" json:"-"`
+ Created time.Time `xorm:"-" gorm:"-" json:"-"`
CreatedUnix int64
- Updated time.Time `xorm:"-" json:"-"` // Note: Updated must below Created for AfterSet.
+ Updated time.Time `xorm:"-" gorm:"-" json:"-"` // Note: Updated must below Created for AfterSet.
UpdatedUnix int64
- HasRecentActivity bool `xorm:"-" json:"-"`
- HasUsed bool `xorm:"-" json:"-"`
+ HasRecentActivity bool `xorm:"-" gorm:"-" json:"-"`
+ HasUsed bool `xorm:"-" gorm:"-" json:"-"`
}
func (t *AccessToken) BeforeInsert() {
@@ -52,8 +52,8 @@ func (t *AccessToken) AfterSet(colName string, _ xorm.Cell) {
func NewAccessToken(t *AccessToken) error {
t.Sha1 = tool.SHA1(gouuid.NewV4().String())
has, err := x.Get(&AccessToken{
- UID: t.UID,
- Name: t.Name,
+ UserID: t.UserID,
+ Name: t.Name,
})
if err != nil {
return err
@@ -65,38 +65,17 @@ func NewAccessToken(t *AccessToken) error {
return err
}
-// GetAccessTokenBySHA returns access token by given sha1.
-func GetAccessTokenBySHA(sha string) (*AccessToken, error) {
- if sha == "" {
- return nil, ErrAccessTokenEmpty{}
- }
- t := &AccessToken{Sha1: sha}
- has, err := x.Get(t)
- if err != nil {
- return nil, err
- } else if !has {
- return nil, ErrAccessTokenNotExist{sha}
- }
- return t, nil
-}
-
// ListAccessTokens returns a list of access tokens belongs to given user.
func ListAccessTokens(uid int64) ([]*AccessToken, error) {
tokens := make([]*AccessToken, 0, 5)
return tokens, x.Where("uid=?", uid).Desc("id").Find(&tokens)
}
-// UpdateAccessToken updates information of access token.
-func UpdateAccessToken(t *AccessToken) error {
- _, err := x.Id(t.ID).AllCols().Update(t)
- return err
-}
-
// DeleteAccessTokenOfUserByID deletes access token by given ID.
func DeleteAccessTokenOfUserByID(userID, id int64) error {
_, err := x.Delete(&AccessToken{
- ID: id,
- UID: userID,
+ ID: id,
+ UserID: userID,
})
return err
}
diff --git a/internal/db/two_factor.go b/internal/db/two_factor.go
index a46fb992..e827d59b 100644
--- a/internal/db/two_factor.go
+++ b/internal/db/two_factor.go
@@ -12,7 +12,6 @@ import (
"github.com/pquerna/otp/totp"
"github.com/unknwon/com"
- log "unknwon.dev/clog/v2"
"xorm.io/xorm"
"gogs.io/gogs/internal/conf"
@@ -54,15 +53,6 @@ func (t *TwoFactor) ValidateTOTP(passcode string) (bool, error) {
return totp.Validate(passcode, string(decryptSecret)), nil
}
-// IsUserEnabledTwoFactor returns true if user has enabled two-factor authentication.
-func IsUserEnabledTwoFactor(userID int64) bool {
- has, err := x.Where("user_id = ?", userID).Get(new(TwoFactor))
- if err != nil {
- log.Error("IsUserEnabledTwoFactor [user_id: %d]: %v", userID, err)
- }
- return has
-}
-
func generateRecoveryCodes(userID int64) ([]*TwoFactorRecoveryCode, error) {
recoveryCodes := make([]*TwoFactorRecoveryCode, 10)
for i := 0; i < 10; i++ {
@@ -182,6 +172,19 @@ func RegenerateRecoveryCodes(userID int64) error {
return sess.Commit()
}
+type ErrTwoFactorRecoveryCodeNotFound struct {
+ Code string
+}
+
+func IsTwoFactorRecoveryCodeNotFound(err error) bool {
+ _, ok := err.(ErrTwoFactorRecoveryCodeNotFound)
+ return ok
+}
+
+func (err ErrTwoFactorRecoveryCodeNotFound) Error() string {
+ return fmt.Sprintf("two-factor recovery code does not found [code: %s]", err.Code)
+}
+
// UseRecoveryCode validates recovery code of given user and marks it is used if valid.
func UseRecoveryCode(userID int64, code string) error {
recoveryCode := new(TwoFactorRecoveryCode)
@@ -189,7 +192,7 @@ func UseRecoveryCode(userID int64, code string) error {
if err != nil {
return fmt.Errorf("get unused code: %v", err)
} else if !has {
- return errors.TwoFactorRecoveryCodeNotFound{Code: code}
+ return ErrTwoFactorRecoveryCodeNotFound{Code: code}
}
recoveryCode.IsUsed = true
diff --git a/internal/db/two_factors.go b/internal/db/two_factors.go
new file mode 100644
index 00000000..376cc40c
--- /dev/null
+++ b/internal/db/two_factors.go
@@ -0,0 +1,33 @@
+// 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 db
+
+import (
+ "github.com/jinzhu/gorm"
+ log "unknwon.dev/clog/v2"
+)
+
+// TwoFactorsStore is the persistent interface for 2FA.
+//
+// NOTE: All methods are sorted in alphabetical order.
+type TwoFactorsStore interface {
+ // IsUserEnabled returns true if the user has enabled 2FA.
+ IsUserEnabled(userID int64) bool
+}
+
+var TwoFactors TwoFactorsStore
+
+type twoFactors struct {
+ *gorm.DB
+}
+
+func (db *twoFactors) IsUserEnabled(userID int64) bool {
+ var count int64
+ err := db.Model(new(TwoFactor)).Where("user_id = ?", userID).Count(&count).Error
+ if err != nil {
+ log.Error("Failed to count two factors [user_id: %d]: %v", userID, err)
+ }
+ return count > 0
+}
diff --git a/internal/db/user.go b/internal/db/user.go
index 4764db4a..a61bb687 100644
--- a/internal/db/user.go
+++ b/internal/db/user.go
@@ -139,9 +139,9 @@ func (u *User) APIFormat() *api.User {
}
}
-// returns true if user login type is LOGIN_PLAIN.
+// returns true if user login type is LoginPlain.
func (u *User) IsLocal() bool {
- return u.LoginType <= LOGIN_PLAIN
+ return u.LoginType <= LoginPlain
}
// HasForkedRepo checks if user has already forked a repository with given ID.
@@ -369,7 +369,7 @@ func (u *User) DeleteAvatar() error {
// IsAdminOfRepo returns true if user has admin or higher access of repository.
func (u *User) IsAdminOfRepo(repo *Repository) bool {
- has, err := HasAccess(u.ID, repo, ACCESS_MODE_ADMIN)
+ has, err := HasAccess(u.ID, repo, AccessModeAdmin)
if err != nil {
log.Error("HasAccess: %v", err)
}
@@ -378,7 +378,7 @@ func (u *User) IsAdminOfRepo(repo *Repository) bool {
// IsWriterOfRepo returns true if user has write access to given repository.
func (u *User) IsWriterOfRepo(repo *Repository) bool {
- has, err := HasAccess(u.ID, repo, ACCESS_MODE_WRITE)
+ has, err := HasAccess(u.ID, repo, AccessModeWrite)
if err != nil {
log.Error("HasAccess: %v", err)
}
@@ -402,7 +402,7 @@ func (u *User) IsPublicMember(orgId int64) bool {
// IsEnabledTwoFactor returns true if user has enabled two-factor authentication.
func (u *User) IsEnabledTwoFactor() bool {
- return IsUserEnabledTwoFactor(u.ID)
+ return TwoFactors.IsUserEnabled(u.ID)
}
func (u *User) getOrganizationCount(e Engine) (int64, error) {
@@ -590,7 +590,7 @@ func CountUsers() int64 {
}
// Users returns number of users in given page.
-func Users(page, pageSize int) ([]*User, error) {
+func ListUsers(page, pageSize int) ([]*User, error) {
users := make([]*User, 0, pageSize)
return users, x.Limit(pageSize, (page-1)*pageSize).Where("type=0").Asc("id").Find(&users)
}
@@ -786,7 +786,7 @@ func deleteUser(e *xorm.Session, u *User) error {
// ***** END: Follow *****
if err = deleteBeans(e,
- &AccessToken{UID: u.ID},
+ &AccessToken{UserID: u.ID},
&Collaboration{UserID: u.ID},
&Access{UserID: u.ID},
&Watch{UserID: u.ID},
@@ -922,13 +922,14 @@ func getUserByID(e Engine, id int64) (*User, error) {
}
// GetUserByID returns the user object by given ID if exists.
+// Deprecated: Use Users.GetByID instead.
func GetUserByID(id int64) (*User, error) {
return getUserByID(x, id)
}
// GetAssigneeByID returns the user with write access of repository by given ID.
func GetAssigneeByID(repo *Repository, userID int64) (*User, error) {
- has, err := HasAccess(userID, repo, ACCESS_MODE_READ)
+ has, err := HasAccess(userID, repo, AccessModeRead)
if err != nil {
return nil, err
} else if !has {
@@ -938,6 +939,7 @@ func GetAssigneeByID(repo *Repository, userID int64) (*User, error) {
}
// GetUserByName returns a user by given name.
+// Deprecated: Use Users.GetByUsername instead.
func GetUserByName(name string) (*User, error) {
if len(name) == 0 {
return nil, ErrUserNotExist{args: map[string]interface{}{"name": name}}
diff --git a/internal/db/users.go b/internal/db/users.go
new file mode 100644
index 00000000..cadd106c
--- /dev/null
+++ b/internal/db/users.go
@@ -0,0 +1,138 @@
+// 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 db
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/jinzhu/gorm"
+ "github.com/pkg/errors"
+
+ "gogs.io/gogs/internal/errutil"
+)
+
+// UsersStore is the persistent interface for users.
+//
+// NOTE: All methods are sorted in alphabetical order.
+type UsersStore interface {
+ // Authenticate validates username and password via given login source ID.
+ // It returns ErrUserNotExist when the user was not found.
+ //
+ // When the "loginSourceID" is negative, it aborts the process and returns
+ // ErrUserNotExist if the user was not found in the database.
+ //
+ // When the "loginSourceID" is non-negative, it returns ErrLoginSourceMismatch
+ // if the user has different login source ID than the "loginSourceID".
+ //
+ // When the "loginSourceID" is positive, it tries to authenticate via given
+ // login source and creates a new user when not yet exists in the database.
+ Authenticate(username, password string, loginSourceID int64) (*User, error)
+ // GetByID returns the user with given ID. It returns ErrUserNotExist when not found.
+ GetByID(id int64) (*User, error)
+ // GetByUsername returns the user with given username. It returns ErrUserNotExist
+ // when not found.
+ GetByUsername(username string) (*User, error)
+}
+
+var Users UsersStore
+
+type users struct {
+ *gorm.DB
+}
+
+type ErrLoginSourceMismatch struct {
+ args errutil.Args
+}
+
+func (err ErrLoginSourceMismatch) Error() string {
+ return fmt.Sprintf("login source mismatch: %v", err.args)
+}
+
+func (db *users) Authenticate(username, password string, loginSourceID int64) (*User, error) {
+ username = strings.ToLower(username)
+
+ var query *gorm.DB
+ if strings.Contains(username, "@") {
+ query = db.Where("email = ?", username)
+ } else {
+ query = db.Where("lower_name = ?", username)
+ }
+
+ user := new(User)
+ err := query.First(user).Error
+ if err != nil && err != gorm.ErrRecordNotFound {
+ return nil, errors.Wrap(err, "get user")
+ }
+
+ // User found in the database
+ if err == nil {
+ // Note: This check is unnecessary but to reduce user confusion at login page
+ // and make it more consistent from user's perspective.
+ if loginSourceID >= 0 && user.LoginSource != loginSourceID {
+ return nil, ErrLoginSourceMismatch{args: errutil.Args{"expect": loginSourceID, "actual": user.LoginSource}}
+ }
+
+ // Validate password hash fetched from database for local accounts.
+ if user.LoginType == LoginNotype || user.LoginType == LoginPlain {
+ if user.ValidatePassword(password) {
+ return user, nil
+ }
+
+ return nil, ErrUserNotExist{args: map[string]interface{}{"userID": user.ID, "name": user.Name}}
+ }
+
+ source, err := LoginSources.GetByID(user.LoginSource)
+ if err != nil {
+ return nil, errors.Wrap(err, "get login source")
+ }
+
+ _, err = authenticateViaLoginSource(source, username, password, false)
+ if err != nil {
+ return nil, errors.Wrap(err, "authenticate via login source")
+ }
+ return user, nil
+ }
+
+ // Non-local login source is always greater than 0.
+ if loginSourceID <= 0 {
+ return nil, ErrUserNotExist{args: map[string]interface{}{"name": username}}
+ }
+
+ source, err := LoginSources.GetByID(loginSourceID)
+ if err != nil {
+ return nil, errors.Wrap(err, "get login source")
+ }
+
+ user, err = authenticateViaLoginSource(source, username, password, true)
+ if err != nil {
+ return nil, errors.Wrap(err, "authenticate via login source")
+ }
+ return user, nil
+}
+
+func (db *users) GetByID(id int64) (*User, error) {
+ user := new(User)
+ err := db.Where("id = ?", id).First(user).Error
+ if err != nil {
+ if err == gorm.ErrRecordNotFound {
+ return nil, ErrUserNotExist{args: map[string]interface{}{"userID": id}}
+ }
+ return nil, err
+ }
+ return user, nil
+}
+
+func (db *users) GetByUsername(username string) (*User, error) {
+ user := new(User)
+ err := db.Where("lower_name = ?", strings.ToLower(username)).First(user).Error
+ if err != nil {
+ if err == gorm.ErrRecordNotFound {
+ return nil, ErrUserNotExist{args: map[string]interface{}{"name": username}}
+ }
+ return nil, err
+ }
+ return user, nil
+}