]> source.dussan.org Git - gitea.git/commitdiff
models/login_source: code improvement
authorUnknwon <u@gogs.io>
Wed, 31 Aug 2016 08:22:41 +0000 (01:22 -0700)
committerUnknwon <u@gogs.io>
Wed, 31 Aug 2016 08:22:41 +0000 (01:22 -0700)
README.md
gogs.go
models/error.go
models/login.go [deleted file]
models/login_source.go [new file with mode: 0644]
routers/admin/auths.go
templates/.VERSION

index cab3c59d4537be0a13090c814f3d8d69f49a1e5b..60aa87527b0c6a521e0fa0e2e3b9a58e592826f8 100644 (file)
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@ Gogs - Go Git Service [![Build Status](https://travis-ci.org/gogits/gogs.svg?bra
 
 ![](https://github.com/gogits/gogs/blob/master/public/img/gogs-large-resize.png?raw=true)
 
-##### Current tip version: 0.9.96 (see [Releases](https://github.com/gogits/gogs/releases) for binary versions)
+##### Current tip version: 0.9.97 (see [Releases](https://github.com/gogits/gogs/releases) for binary versions)
 
 | Web | UI  | Preview  |
 |:-------------:|:-------:|:-------:|
diff --git a/gogs.go b/gogs.go
index be61885dc33a3310eb0be5ec818cefde060ddde6..04689d454449f0b43ba0bc822f24330e940b6dee 100644 (file)
--- a/gogs.go
+++ b/gogs.go
@@ -17,7 +17,7 @@ import (
        "github.com/gogits/gogs/modules/setting"
 )
 
-const APP_VER = "0.9.96.0830"
+const APP_VER = "0.9.97.0830"
 
 func init() {
        runtime.GOMAXPROCS(runtime.NumCPU())
index 065857e001f00c524cc403dd264e5434bf16dbf3..182a944a6f5edb5fd7450b6d47d3fcbc2cfbeca6 100644 (file)
@@ -635,6 +635,19 @@ func (err ErrLoginSourceAlreadyExist) Error() string {
        return fmt.Sprintf("login source already exists [name: %s]", err.Name)
 }
 
+type ErrLoginSourceInUse struct {
+       ID int64
+}
+
+func IsErrLoginSourceInUse(err error) bool {
+       _, ok := err.(ErrLoginSourceInUse)
+       return ok
+}
+
+func (err ErrLoginSourceInUse) Error() string {
+       return fmt.Sprintf("login source is still used by some users [id: %d]", err.ID)
+}
+
 // ___________
 // \__    ___/___ _____    _____
 //   |    |_/ __ \\__  \  /     \
diff --git a/models/login.go b/models/login.go
deleted file mode 100644 (file)
index bef1617..0000000
+++ /dev/null
@@ -1,573 +0,0 @@
-// 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 models
-
-import (
-       "crypto/tls"
-       "encoding/json"
-       "errors"
-       "fmt"
-       "net/smtp"
-       "net/textproto"
-       "strings"
-       "time"
-
-       "github.com/Unknwon/com"
-       "github.com/go-macaron/binding"
-       "github.com/go-xorm/core"
-       "github.com/go-xorm/xorm"
-
-       "github.com/gogits/gogs/modules/auth/ldap"
-       "github.com/gogits/gogs/modules/auth/pam"
-       "github.com/gogits/gogs/modules/log"
-)
-
-var (
-       ErrAuthenticationUserUsed = errors.New("Authentication has been used by some users")
-)
-
-type LoginType int
-
-// Note: new type must be added at 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
-)
-
-var LoginNames = map[LoginType]string{
-       LOGIN_LDAP:  "LDAP (via BindDN)",
-       LOGIN_DLDAP: "LDAP (simple auth)", // Via direct bind
-       LOGIN_SMTP:  "SMTP",
-       LOGIN_PAM:   "PAM",
-}
-
-var SecurityProtocolNames = map[ldap.SecurityProtocol]string{
-       ldap.SECURITY_PROTOCOL_UNENCRYPTED: "Unencrypted",
-       ldap.SECURITY_PROTOCOL_LDAPS:       "LDAPS",
-       ldap.SECURITY_PROTOCOL_START_TLS:   "StartTLS",
-}
-
-// Ensure structs implemented interface.
-var (
-       _ core.Conversion = &LDAPConfig{}
-       _ core.Conversion = &SMTPConfig{}
-       _ core.Conversion = &PAMConfig{}
-)
-
-type LDAPConfig struct {
-       *ldap.Source
-}
-
-func (cfg *LDAPConfig) FromDB(bs []byte) error {
-       return json.Unmarshal(bs, &cfg)
-}
-
-func (cfg *LDAPConfig) ToDB() ([]byte, error) {
-       return json.Marshal(cfg)
-}
-
-func (cfg *LDAPConfig) SecurityProtocolName() string {
-       return SecurityProtocolNames[cfg.SecurityProtocol]
-}
-
-type SMTPConfig struct {
-       Auth           string
-       Host           string
-       Port           int
-       AllowedDomains string `xorm:"TEXT"`
-       TLS            bool
-       SkipVerify     bool
-}
-
-func (cfg *SMTPConfig) FromDB(bs []byte) error {
-       return json.Unmarshal(bs, cfg)
-}
-
-func (cfg *SMTPConfig) ToDB() ([]byte, error) {
-       return json.Marshal(cfg)
-}
-
-type PAMConfig struct {
-       ServiceName string // pam service (e.g. system-auth)
-}
-
-func (cfg *PAMConfig) FromDB(bs []byte) error {
-       return json.Unmarshal(bs, &cfg)
-}
-
-func (cfg *PAMConfig) ToDB() ([]byte, error) {
-       return json.Marshal(cfg)
-}
-
-type LoginSource struct {
-       ID        int64 `xorm:"pk autoincr"`
-       Type      LoginType
-       Name      string          `xorm:"UNIQUE"`
-       IsActived bool            `xorm:"NOT NULL DEFAULT false"`
-       Cfg       core.Conversion `xorm:"TEXT"`
-
-       Created     time.Time `xorm:"-"`
-       CreatedUnix int64
-       Updated     time.Time `xorm:"-"`
-       UpdatedUnix int64
-}
-
-func (s *LoginSource) BeforeInsert() {
-       s.CreatedUnix = time.Now().Unix()
-       s.UpdatedUnix = s.CreatedUnix
-}
-
-func (s *LoginSource) BeforeUpdate() {
-       s.UpdatedUnix = time.Now().Unix()
-}
-
-// Cell2Int64 converts a xorm.Cell type to int64,
-// and handles possible irregular cases.
-func Cell2Int64(val xorm.Cell) int64 {
-       switch (*val).(type) {
-       case []uint8:
-               log.Trace("Cell2Int64 ([]uint8): %v", *val)
-               return com.StrTo(string((*val).([]uint8))).MustInt64()
-       }
-       return (*val).(int64)
-}
-
-func (source *LoginSource) BeforeSet(colName string, val xorm.Cell) {
-       switch colName {
-       case "type":
-               switch LoginType(Cell2Int64(val)) {
-               case LOGIN_LDAP, LOGIN_DLDAP:
-                       source.Cfg = new(LDAPConfig)
-               case LOGIN_SMTP:
-                       source.Cfg = new(SMTPConfig)
-               case LOGIN_PAM:
-                       source.Cfg = new(PAMConfig)
-               default:
-                       panic("unrecognized login source type: " + com.ToStr(*val))
-               }
-       }
-}
-
-func (s *LoginSource) AfterSet(colName string, _ xorm.Cell) {
-       switch colName {
-       case "created_unix":
-               s.Created = time.Unix(s.CreatedUnix, 0).Local()
-       case "updated_unix":
-               s.Updated = time.Unix(s.UpdatedUnix, 0).Local()
-       }
-}
-
-func (source *LoginSource) TypeName() string {
-       return LoginNames[source.Type]
-}
-
-func (source *LoginSource) IsLDAP() bool {
-       return source.Type == LOGIN_LDAP
-}
-
-func (source *LoginSource) IsDLDAP() bool {
-       return source.Type == LOGIN_DLDAP
-}
-
-func (source *LoginSource) IsSMTP() bool {
-       return source.Type == LOGIN_SMTP
-}
-
-func (source *LoginSource) IsPAM() bool {
-       return source.Type == LOGIN_PAM
-}
-
-func (source *LoginSource) HasTLS() bool {
-       return ((source.IsLDAP() || source.IsDLDAP()) &&
-               source.LDAP().SecurityProtocol > ldap.SECURITY_PROTOCOL_UNENCRYPTED) ||
-               source.IsSMTP()
-}
-
-func (source *LoginSource) UseTLS() bool {
-       switch source.Type {
-       case LOGIN_LDAP, LOGIN_DLDAP:
-               return source.LDAP().SecurityProtocol != ldap.SECURITY_PROTOCOL_UNENCRYPTED
-       case LOGIN_SMTP:
-               return source.SMTP().TLS
-       }
-
-       return false
-}
-
-func (source *LoginSource) SkipVerify() bool {
-       switch source.Type {
-       case LOGIN_LDAP, LOGIN_DLDAP:
-               return source.LDAP().SkipVerify
-       case LOGIN_SMTP:
-               return source.SMTP().SkipVerify
-       }
-
-       return false
-}
-
-func (source *LoginSource) LDAP() *LDAPConfig {
-       return source.Cfg.(*LDAPConfig)
-}
-
-func (source *LoginSource) SMTP() *SMTPConfig {
-       return source.Cfg.(*SMTPConfig)
-}
-
-func (source *LoginSource) PAM() *PAMConfig {
-       return source.Cfg.(*PAMConfig)
-}
-
-// CountLoginSources returns number of login sources.
-func CountLoginSources() int64 {
-       count, _ := x.Count(new(LoginSource))
-       return count
-}
-
-func CreateLoginSource(source *LoginSource) error {
-       has, err := x.Get(&LoginSource{Name: source.Name})
-       if err != nil {
-               return err
-       } else if has {
-               return ErrLoginSourceAlreadyExist{source.Name}
-       }
-
-       _, err = x.Insert(source)
-       return err
-}
-
-func LoginSources() ([]*LoginSource, error) {
-       auths := make([]*LoginSource, 0, 5)
-       return auths, x.Find(&auths)
-}
-
-// 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 nil, ErrLoginSourceNotExist{id}
-       }
-       return source, nil
-}
-
-func UpdateSource(source *LoginSource) error {
-       _, err := x.Id(source.ID).AllCols().Update(source)
-       return err
-}
-
-func DeleteSource(source *LoginSource) error {
-       count, err := x.Count(&User{LoginSource: source.ID})
-       if err != nil {
-               return err
-       } else if count > 0 {
-               return ErrAuthenticationUserUsed
-       }
-       _, err = x.Id(source.ID).Delete(new(LoginSource))
-       return err
-}
-
-// .____     ________      _____ __________
-// |    |    \______ \    /  _  \\______   \
-// |    |     |    |  \  /  /_\  \|     ___/
-// |    |___  |    `   \/    |    \    |
-// |_______ \/_______  /\____|__  /____|
-//         \/        \/         \/
-
-// LoginUserLDAPSource queries if loginName/passwd can login against the LDAP directory pool,
-// and create a local user if success when enabled.
-// It returns the same LoginUserPlain semantic.
-func LoginUserLDAPSource(u *User, loginName, passwd string, source *LoginSource, autoRegister bool) (*User, error) {
-       cfg := source.Cfg.(*LDAPConfig)
-       directBind := (source.Type == LOGIN_DLDAP)
-       username, fn, sn, mail, isAdmin, logged := cfg.SearchEntry(loginName, passwd, directBind)
-       if !logged {
-               // User not in LDAP, do nothing
-               return nil, ErrUserNotExist{0, loginName}
-       }
-
-       if !autoRegister {
-               return u, nil
-       }
-
-       // Fallback.
-       if len(username) == 0 {
-               username = loginName
-       }
-       // Validate username make sure it satisfies requirement.
-       if binding.AlphaDashDotPattern.MatchString(username) {
-               return nil, fmt.Errorf("Invalid pattern for attribute 'username' [%s]: must be valid alpha or numeric or dash(-_) or dot characters", username)
-       }
-
-       if len(mail) == 0 {
-               mail = fmt.Sprintf("%s@localhost", username)
-       }
-
-       u = &User{
-               LowerName:   strings.ToLower(username),
-               Name:        username,
-               FullName:    composeFullName(fn, sn, username),
-               LoginType:   source.Type,
-               LoginSource: source.ID,
-               LoginName:   loginName,
-               Email:       mail,
-               IsAdmin:     isAdmin,
-               IsActive:    true,
-       }
-       return u, CreateUser(u)
-}
-
-func composeFullName(firstname, surname, username string) string {
-       switch {
-       case len(firstname) == 0 && len(surname) == 0:
-               return username
-       case len(firstname) == 0:
-               return surname
-       case len(surname) == 0:
-               return firstname
-       default:
-               return firstname + " " + surname
-       }
-}
-
-//   _________   __________________________
-//  /   _____/  /     \__    ___/\______   \
-//  \_____  \  /  \ /  \|    |    |     ___/
-//  /        \/    Y    \    |    |    |
-// /_______  /\____|__  /____|    |____|
-//         \/         \/
-
-type loginAuth struct {
-       username, password string
-}
-
-func LoginAuth(username, password string) smtp.Auth {
-       return &loginAuth{username, password}
-}
-
-func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
-       return "LOGIN", []byte(a.username), nil
-}
-
-func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
-       if more {
-               switch string(fromServer) {
-               case "Username:":
-                       return []byte(a.username), nil
-               case "Password:":
-                       return []byte(a.password), nil
-               }
-       }
-       return nil, nil
-}
-
-const (
-       SMTP_PLAIN = "PLAIN"
-       SMTP_LOGIN = "LOGIN"
-)
-
-var SMTPAuths = []string{SMTP_PLAIN, SMTP_LOGIN}
-
-func SMTPAuth(a smtp.Auth, cfg *SMTPConfig) error {
-       c, err := smtp.Dial(fmt.Sprintf("%s:%d", cfg.Host, cfg.Port))
-       if err != nil {
-               return err
-       }
-       defer c.Close()
-
-       if err = c.Hello("gogs"); err != nil {
-               return err
-       }
-
-       if cfg.TLS {
-               if ok, _ := c.Extension("STARTTLS"); ok {
-                       if err = c.StartTLS(&tls.Config{
-                               InsecureSkipVerify: cfg.SkipVerify,
-                               ServerName:         cfg.Host,
-                       }); err != nil {
-                               return err
-                       }
-               } else {
-                       return errors.New("SMTP server unsupports TLS")
-               }
-       }
-
-       if ok, _ := c.Extension("AUTH"); ok {
-               if err = c.Auth(a); err != nil {
-                       return err
-               }
-               return nil
-       }
-       return ErrUnsupportedLoginType
-}
-
-// Query if name/passwd can login against the LDAP directory pool
-// Create a local user if success
-// Return the same LoginUserPlain semantic
-func LoginUserSMTPSource(u *User, name, passwd string, sourceID int64, cfg *SMTPConfig, autoRegister bool) (*User, error) {
-       // Verify allowed domains.
-       if len(cfg.AllowedDomains) > 0 {
-               idx := strings.Index(name, "@")
-               if idx == -1 {
-                       return nil, ErrUserNotExist{0, name}
-               } else if !com.IsSliceContainsStr(strings.Split(cfg.AllowedDomains, ","), name[idx+1:]) {
-                       return nil, ErrUserNotExist{0, name}
-               }
-       }
-
-       var auth smtp.Auth
-       if cfg.Auth == SMTP_PLAIN {
-               auth = smtp.PlainAuth("", name, passwd, cfg.Host)
-       } else if cfg.Auth == SMTP_LOGIN {
-               auth = LoginAuth(name, passwd)
-       } else {
-               return nil, errors.New("Unsupported SMTP auth type")
-       }
-
-       if err := SMTPAuth(auth, cfg); err != nil {
-               // Check standard error format first,
-               // then fallback to worse case.
-               tperr, ok := err.(*textproto.Error)
-               if (ok && tperr.Code == 535) ||
-                       strings.Contains(err.Error(), "Username and Password not accepted") {
-                       return nil, ErrUserNotExist{0, name}
-               }
-               return nil, err
-       }
-
-       if !autoRegister {
-               return u, nil
-       }
-
-       var loginName = name
-       idx := strings.Index(name, "@")
-       if idx > -1 {
-               loginName = name[:idx]
-       }
-       // fake a local user creation
-       u = &User{
-               LowerName:   strings.ToLower(loginName),
-               Name:        strings.ToLower(loginName),
-               LoginType:   LOGIN_SMTP,
-               LoginSource: sourceID,
-               LoginName:   name,
-               IsActive:    true,
-               Passwd:      passwd,
-               Email:       name,
-       }
-       err := CreateUser(u)
-       return u, err
-}
-
-// __________  _____      _____
-// \______   \/  _  \    /     \
-//  |     ___/  /_\  \  /  \ /  \
-//  |    |  /    |    \/    Y    \
-//  |____|  \____|__  /\____|__  /
-//                  \/         \/
-
-// Query if name/passwd can login against PAM
-// Create a local user if success
-// Return the same LoginUserPlain semantic
-func LoginUserPAMSource(u *User, name, passwd string, sourceID int64, cfg *PAMConfig, autoRegister bool) (*User, error) {
-       if err := pam.PAMAuth(cfg.ServiceName, name, passwd); err != nil {
-               if strings.Contains(err.Error(), "Authentication failure") {
-                       return nil, ErrUserNotExist{0, name}
-               }
-               return nil, err
-       }
-
-       if !autoRegister {
-               return u, nil
-       }
-
-       // fake a local user creation
-       u = &User{
-               LowerName:   strings.ToLower(name),
-               Name:        name,
-               LoginType:   LOGIN_PAM,
-               LoginSource: sourceID,
-               LoginName:   name,
-               IsActive:    true,
-               Passwd:      passwd,
-               Email:       name,
-       }
-       return u, CreateUser(u)
-}
-
-func ExternalUserLogin(u *User, name, passwd string, source *LoginSource, autoRegister bool) (*User, error) {
-       if !source.IsActived {
-               return nil, ErrLoginSourceNotActived
-       }
-
-       switch source.Type {
-       case LOGIN_LDAP, LOGIN_DLDAP:
-               return LoginUserLDAPSource(u, name, passwd, source, autoRegister)
-       case LOGIN_SMTP:
-               return LoginUserSMTPSource(u, name, passwd, source.ID, source.Cfg.(*SMTPConfig), autoRegister)
-       case LOGIN_PAM:
-               return LoginUserPAMSource(u, name, passwd, source.ID, source.Cfg.(*PAMConfig), autoRegister)
-       }
-
-       return nil, ErrUnsupportedLoginType
-}
-
-// UserSignIn validates user name and password.
-func UserSignIn(uname, passwd string) (*User, error) {
-       var u *User
-       if strings.Contains(uname, "@") {
-               u = &User{Email: strings.ToLower(uname)}
-       } else {
-               u = &User{LowerName: strings.ToLower(uname)}
-       }
-
-       userExists, err := x.Get(u)
-       if err != nil {
-               return nil, err
-       }
-
-       if userExists {
-               switch u.LoginType {
-               case LOGIN_NOTYPE, LOGIN_PLAIN:
-                       if u.ValidatePassword(passwd) {
-                               return u, nil
-                       }
-
-                       return nil, ErrUserNotExist{u.ID, u.Name}
-
-               default:
-                       var source LoginSource
-                       hasSource, err := x.Id(u.LoginSource).Get(&source)
-                       if err != nil {
-                               return nil, err
-                       } else if !hasSource {
-                               return nil, ErrLoginSourceNotExist{u.LoginSource}
-                       }
-
-                       return ExternalUserLogin(u, u.LoginName, passwd, &source, false)
-               }
-       }
-
-       var sources []LoginSource
-       if err = x.UseBool().Find(&sources, &LoginSource{IsActived: true}); err != nil {
-               return nil, err
-       }
-
-       for _, source := range sources {
-               u, err := ExternalUserLogin(nil, uname, passwd, &source, true)
-               if err == nil {
-                       return u, nil
-               }
-
-               log.Warn("Failed to login '%s' via '%s': %v", uname, source.Name, err)
-       }
-
-       return nil, ErrUserNotExist{u.ID, u.Name}
-}
diff --git a/models/login_source.go b/models/login_source.go
new file mode 100644 (file)
index 0000000..82186fb
--- /dev/null
@@ -0,0 +1,558 @@
+// 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 models
+
+import (
+       "crypto/tls"
+       "encoding/json"
+       "errors"
+       "fmt"
+       "net/smtp"
+       "net/textproto"
+       "strings"
+       "time"
+
+       "github.com/Unknwon/com"
+       "github.com/go-macaron/binding"
+       "github.com/go-xorm/core"
+       "github.com/go-xorm/xorm"
+
+       "github.com/gogits/gogs/modules/auth/ldap"
+       "github.com/gogits/gogs/modules/auth/pam"
+       "github.com/gogits/gogs/modules/log"
+)
+
+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
+)
+
+var LoginNames = map[LoginType]string{
+       LOGIN_LDAP:  "LDAP (via BindDN)",
+       LOGIN_DLDAP: "LDAP (simple auth)", // Via direct bind
+       LOGIN_SMTP:  "SMTP",
+       LOGIN_PAM:   "PAM",
+}
+
+var SecurityProtocolNames = map[ldap.SecurityProtocol]string{
+       ldap.SECURITY_PROTOCOL_UNENCRYPTED: "Unencrypted",
+       ldap.SECURITY_PROTOCOL_LDAPS:       "LDAPS",
+       ldap.SECURITY_PROTOCOL_START_TLS:   "StartTLS",
+}
+
+// Ensure structs implemented interface.
+var (
+       _ core.Conversion = &LDAPConfig{}
+       _ core.Conversion = &SMTPConfig{}
+       _ core.Conversion = &PAMConfig{}
+)
+
+type LDAPConfig struct {
+       *ldap.Source
+}
+
+func (cfg *LDAPConfig) FromDB(bs []byte) error {
+       return json.Unmarshal(bs, &cfg)
+}
+
+func (cfg *LDAPConfig) ToDB() ([]byte, error) {
+       return json.Marshal(cfg)
+}
+
+func (cfg *LDAPConfig) SecurityProtocolName() string {
+       return SecurityProtocolNames[cfg.SecurityProtocol]
+}
+
+type SMTPConfig struct {
+       Auth           string
+       Host           string
+       Port           int
+       AllowedDomains string `xorm:"TEXT"`
+       TLS            bool
+       SkipVerify     bool
+}
+
+func (cfg *SMTPConfig) FromDB(bs []byte) error {
+       return json.Unmarshal(bs, cfg)
+}
+
+func (cfg *SMTPConfig) ToDB() ([]byte, error) {
+       return json.Marshal(cfg)
+}
+
+type PAMConfig struct {
+       ServiceName string // pam service (e.g. system-auth)
+}
+
+func (cfg *PAMConfig) FromDB(bs []byte) error {
+       return json.Unmarshal(bs, &cfg)
+}
+
+func (cfg *PAMConfig) ToDB() ([]byte, error) {
+       return json.Marshal(cfg)
+}
+
+// LoginSource represents an external way for authorizing users.
+type LoginSource struct {
+       ID        int64 `xorm:"pk autoincr"`
+       Type      LoginType
+       Name      string          `xorm:"UNIQUE"`
+       IsActived bool            `xorm:"NOT NULL DEFAULT false"`
+       Cfg       core.Conversion `xorm:"TEXT"`
+
+       Created     time.Time `xorm:"-"`
+       CreatedUnix int64
+       Updated     time.Time `xorm:"-"`
+       UpdatedUnix int64
+}
+
+func (s *LoginSource) BeforeInsert() {
+       s.CreatedUnix = time.Now().Unix()
+       s.UpdatedUnix = s.CreatedUnix
+}
+
+func (s *LoginSource) BeforeUpdate() {
+       s.UpdatedUnix = time.Now().Unix()
+}
+
+// Cell2Int64 converts a xorm.Cell type to int64,
+// and handles possible irregular cases.
+func Cell2Int64(val xorm.Cell) int64 {
+       switch (*val).(type) {
+       case []uint8:
+               log.Trace("Cell2Int64 ([]uint8): %v", *val)
+               return com.StrTo(string((*val).([]uint8))).MustInt64()
+       }
+       return (*val).(int64)
+}
+
+func (source *LoginSource) BeforeSet(colName string, val xorm.Cell) {
+       switch colName {
+       case "type":
+               switch LoginType(Cell2Int64(val)) {
+               case LOGIN_LDAP, LOGIN_DLDAP:
+                       source.Cfg = new(LDAPConfig)
+               case LOGIN_SMTP:
+                       source.Cfg = new(SMTPConfig)
+               case LOGIN_PAM:
+                       source.Cfg = new(PAMConfig)
+               default:
+                       panic("unrecognized login source type: " + com.ToStr(*val))
+               }
+       }
+}
+
+func (s *LoginSource) AfterSet(colName string, _ xorm.Cell) {
+       switch colName {
+       case "created_unix":
+               s.Created = time.Unix(s.CreatedUnix, 0).Local()
+       case "updated_unix":
+               s.Updated = time.Unix(s.UpdatedUnix, 0).Local()
+       }
+}
+
+func (source *LoginSource) TypeName() string {
+       return LoginNames[source.Type]
+}
+
+func (source *LoginSource) IsLDAP() bool {
+       return source.Type == LOGIN_LDAP
+}
+
+func (source *LoginSource) IsDLDAP() bool {
+       return source.Type == LOGIN_DLDAP
+}
+
+func (source *LoginSource) IsSMTP() bool {
+       return source.Type == LOGIN_SMTP
+}
+
+func (source *LoginSource) IsPAM() bool {
+       return source.Type == LOGIN_PAM
+}
+
+func (source *LoginSource) HasTLS() bool {
+       return ((source.IsLDAP() || source.IsDLDAP()) &&
+               source.LDAP().SecurityProtocol > ldap.SECURITY_PROTOCOL_UNENCRYPTED) ||
+               source.IsSMTP()
+}
+
+func (source *LoginSource) UseTLS() bool {
+       switch source.Type {
+       case LOGIN_LDAP, LOGIN_DLDAP:
+               return source.LDAP().SecurityProtocol != ldap.SECURITY_PROTOCOL_UNENCRYPTED
+       case LOGIN_SMTP:
+               return source.SMTP().TLS
+       }
+
+       return false
+}
+
+func (source *LoginSource) SkipVerify() bool {
+       switch source.Type {
+       case LOGIN_LDAP, LOGIN_DLDAP:
+               return source.LDAP().SkipVerify
+       case LOGIN_SMTP:
+               return source.SMTP().SkipVerify
+       }
+
+       return false
+}
+
+func (source *LoginSource) LDAP() *LDAPConfig {
+       return source.Cfg.(*LDAPConfig)
+}
+
+func (source *LoginSource) SMTP() *SMTPConfig {
+       return source.Cfg.(*SMTPConfig)
+}
+
+func (source *LoginSource) PAM() *PAMConfig {
+       return source.Cfg.(*PAMConfig)
+}
+func CreateLoginSource(source *LoginSource) error {
+       has, err := x.Get(&LoginSource{Name: source.Name})
+       if err != nil {
+               return err
+       } else if has {
+               return ErrLoginSourceAlreadyExist{source.Name}
+       }
+
+       _, err = x.Insert(source)
+       return err
+}
+
+func LoginSources() ([]*LoginSource, error) {
+       auths := make([]*LoginSource, 0, 5)
+       return auths, x.Find(&auths)
+}
+
+// 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 nil, ErrLoginSourceNotExist{id}
+       }
+       return source, nil
+}
+
+func UpdateSource(source *LoginSource) error {
+       _, err := x.Id(source.ID).AllCols().Update(source)
+       return err
+}
+
+func DeleteSource(source *LoginSource) error {
+       count, err := x.Count(&User{LoginSource: source.ID})
+       if err != nil {
+               return err
+       } else if count > 0 {
+               return ErrLoginSourceInUse{source.ID}
+       }
+       _, err = x.Id(source.ID).Delete(new(LoginSource))
+       return err
+}
+
+// CountLoginSources returns number of login sources.
+func CountLoginSources() int64 {
+       count, _ := x.Count(new(LoginSource))
+       return count
+}
+
+// .____     ________      _____ __________
+// |    |    \______ \    /  _  \\______   \
+// |    |     |    |  \  /  /_\  \|     ___/
+// |    |___  |    `   \/    |    \    |
+// |_______ \/_______  /\____|__  /____|
+//         \/        \/         \/
+
+func composeFullName(firstname, surname, username string) string {
+       switch {
+       case len(firstname) == 0 && len(surname) == 0:
+               return username
+       case len(firstname) == 0:
+               return surname
+       case len(surname) == 0:
+               return firstname
+       default:
+               return firstname + " " + surname
+       }
+}
+
+// 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, passowrd string, source *LoginSource, autoRegister bool) (*User, error) {
+       username, fn, sn, mail, isAdmin, succeed := source.Cfg.(*LDAPConfig).SearchEntry(login, passowrd, source.Type == LOGIN_DLDAP)
+       if !succeed {
+               // User not in LDAP, do nothing
+               return nil, ErrUserNotExist{0, login}
+       }
+
+       if !autoRegister {
+               return user, nil
+       }
+
+       // Fallback.
+       if len(username) == 0 {
+               username = login
+       }
+       // Validate username make sure it satisfies requirement.
+       if binding.AlphaDashDotPattern.MatchString(username) {
+               return nil, fmt.Errorf("Invalid pattern for attribute 'username' [%s]: must be valid alpha or numeric or dash(-_) or dot characters", username)
+       }
+
+       if len(mail) == 0 {
+               mail = fmt.Sprintf("%s@localhost", username)
+       }
+
+       user = &User{
+               LowerName:   strings.ToLower(username),
+               Name:        username,
+               FullName:    composeFullName(fn, sn, username),
+               Email:       mail,
+               LoginType:   source.Type,
+               LoginSource: source.ID,
+               LoginName:   login,
+               IsActive:    true,
+               IsAdmin:     isAdmin,
+       }
+       return user, CreateUser(user)
+}
+
+//   _________   __________________________
+//  /   _____/  /     \__    ___/\______   \
+//  \_____  \  /  \ /  \|    |    |     ___/
+//  /        \/    Y    \    |    |    |
+// /_______  /\____|__  /____|    |____|
+//         \/         \/
+
+type smtpLoginAuth struct {
+       username, password string
+}
+
+func (auth *smtpLoginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
+       return "LOGIN", []byte(auth.username), nil
+}
+
+func (auth *smtpLoginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
+       if more {
+               switch string(fromServer) {
+               case "Username:":
+                       return []byte(auth.username), nil
+               case "Password:":
+                       return []byte(auth.password), nil
+               }
+       }
+       return nil, nil
+}
+
+const (
+       SMTP_PLAIN = "PLAIN"
+       SMTP_LOGIN = "LOGIN"
+)
+
+var SMTPAuths = []string{SMTP_PLAIN, SMTP_LOGIN}
+
+func SMTPAuth(a smtp.Auth, cfg *SMTPConfig) error {
+       c, err := smtp.Dial(fmt.Sprintf("%s:%d", cfg.Host, cfg.Port))
+       if err != nil {
+               return err
+       }
+       defer c.Close()
+
+       if err = c.Hello("gogs"); err != nil {
+               return err
+       }
+
+       if cfg.TLS {
+               if ok, _ := c.Extension("STARTTLS"); ok {
+                       if err = c.StartTLS(&tls.Config{
+                               InsecureSkipVerify: cfg.SkipVerify,
+                               ServerName:         cfg.Host,
+                       }); err != nil {
+                               return err
+                       }
+               } else {
+                       return errors.New("SMTP server unsupports TLS")
+               }
+       }
+
+       if ok, _ := c.Extension("AUTH"); ok {
+               if err = c.Auth(a); err != nil {
+                       return err
+               }
+               return nil
+       }
+       return ErrUnsupportedLoginType
+}
+
+// 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) {
+       // Verify allowed domains.
+       if len(cfg.AllowedDomains) > 0 {
+               idx := strings.Index(login, "@")
+               if idx == -1 {
+                       return nil, ErrUserNotExist{0, login}
+               } else if !com.IsSliceContainsStr(strings.Split(cfg.AllowedDomains, ","), login[idx+1:]) {
+                       return nil, ErrUserNotExist{0, login}
+               }
+       }
+
+       var auth smtp.Auth
+       if cfg.Auth == SMTP_PLAIN {
+               auth = smtp.PlainAuth("", login, password, cfg.Host)
+       } else if cfg.Auth == SMTP_LOGIN {
+               auth = &smtpLoginAuth{login, password}
+       } else {
+               return nil, errors.New("Unsupported SMTP auth type")
+       }
+
+       if err := SMTPAuth(auth, cfg); err != nil {
+               // Check standard error format first,
+               // then fallback to worse case.
+               tperr, ok := err.(*textproto.Error)
+               if (ok && tperr.Code == 535) ||
+                       strings.Contains(err.Error(), "Username and Password not accepted") {
+                       return nil, ErrUserNotExist{0, login}
+               }
+               return nil, err
+       }
+
+       if !autoRegister {
+               return user, nil
+       }
+
+       username := login
+       idx := strings.Index(login, "@")
+       if idx > -1 {
+               username = login[:idx]
+       }
+
+       user = &User{
+               LowerName:   strings.ToLower(username),
+               Name:        strings.ToLower(username),
+               Email:       login,
+               Passwd:      password,
+               LoginType:   LOGIN_SMTP,
+               LoginSource: sourceID,
+               LoginName:   login,
+               IsActive:    true,
+       }
+       return user, CreateUser(user)
+}
+
+// __________  _____      _____
+// \______   \/  _  \    /     \
+//  |     ___/  /_\  \  /  \ /  \
+//  |    |  /    |    \/    Y    \
+//  |____|  \____|__  /\____|__  /
+//                  \/         \/
+
+// 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) {
+       if err := pam.PAMAuth(cfg.ServiceName, login, password); err != nil {
+               if strings.Contains(err.Error(), "Authentication failure") {
+                       return nil, ErrUserNotExist{0, login}
+               }
+               return nil, err
+       }
+
+       if !autoRegister {
+               return user, nil
+       }
+
+       user = &User{
+               LowerName:   strings.ToLower(login),
+               Name:        login,
+               Email:       login,
+               Passwd:      password,
+               LoginType:   LOGIN_PAM,
+               LoginSource: sourceID,
+               LoginName:   login,
+               IsActive:    true,
+       }
+       return user, CreateUser(user)
+}
+
+func ExternalUserLogin(user *User, login, password string, source *LoginSource, autoRegister bool) (*User, error) {
+       if !source.IsActived {
+               return nil, ErrLoginSourceNotActived
+       }
+
+       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)
+       }
+
+       return nil, ErrUnsupportedLoginType
+}
+
+// UserSignIn validates user name and password.
+func UserSignIn(username, passowrd string) (*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, err
+       }
+
+       if hasUser {
+               switch user.LoginType {
+               case LOGIN_NOTYPE, LOGIN_PLAIN:
+                       if user.ValidatePassword(passowrd) {
+                               return user, nil
+                       }
+
+                       return nil, ErrUserNotExist{user.ID, user.Name}
+
+               default:
+                       var source LoginSource
+                       hasSource, err := x.Id(user.LoginSource).Get(&source)
+                       if err != nil {
+                               return nil, err
+                       } else if !hasSource {
+                               return nil, ErrLoginSourceNotExist{user.LoginSource}
+                       }
+
+                       return ExternalUserLogin(user, user.LoginName, passowrd, &source, false)
+               }
+       }
+
+       sources := make([]*LoginSource, 3)
+       if err = x.UseBool().Find(&sources, &LoginSource{IsActived: true}); err != nil {
+               return nil, err
+       }
+
+       for _, source := range sources {
+               authUser, err := ExternalUserLogin(nil, username, passowrd, source, true)
+               if err == nil {
+                       return authUser, nil
+               }
+
+               log.Warn("Failed to login '%s' via '%s': %v", username, source.Name, err)
+       }
+
+       return nil, ErrUserNotExist{user.ID, user.Name}
+}
index 8d3304eacfda9c998df54edec7d8ab5b12334b83..df2512aa069d767c7c56a904a0f3d91d521cf254 100644 (file)
@@ -242,10 +242,9 @@ func DeleteAuthSource(ctx *context.Context) {
        }
 
        if err = models.DeleteSource(source); err != nil {
-               switch err {
-               case models.ErrAuthenticationUserUsed:
+               if models.IsErrLoginSourceInUse(err) {
                        ctx.Flash.Error(ctx.Tr("admin.auths.still_in_used"))
-               default:
+               } else {
                        ctx.Flash.Error(fmt.Sprintf("DeleteSource: %v", err))
                }
                ctx.JSON(200, map[string]interface{}{
index 93b1bf2a43a21f8c1f49eceba1cc732064afe012..60a098b413513353b82062f48208586a2a5eb676 100644 (file)
@@ -1 +1 @@
-0.9.96.0830
\ No newline at end of file
+0.9.97.0830
\ No newline at end of file