models/login_source: code improvement

This commit is contained in:
Unknwon 2016-08-31 01:22:41 -07:00
parent 99c2ae7b35
commit 152e715999
6 changed files with 131 additions and 134 deletions

View file

@ -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) ![](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 | | Web | UI | Preview |
|:-------------:|:-------:|:-------:| |:-------------:|:-------:|:-------:|

View file

@ -17,7 +17,7 @@ import (
"github.com/gogits/gogs/modules/setting" "github.com/gogits/gogs/modules/setting"
) )
const APP_VER = "0.9.96.0830" const APP_VER = "0.9.97.0830"
func init() { func init() {
runtime.GOMAXPROCS(runtime.NumCPU()) runtime.GOMAXPROCS(runtime.NumCPU())

View file

@ -635,6 +635,19 @@ func (err ErrLoginSourceAlreadyExist) Error() string {
return fmt.Sprintf("login source already exists [name: %s]", err.Name) 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)
}
// ___________ // ___________
// \__ ___/___ _____ _____ // \__ ___/___ _____ _____
// | |_/ __ \\__ \ / \ // | |_/ __ \\__ \ / \

View file

@ -24,13 +24,9 @@ import (
"github.com/gogits/gogs/modules/log" "github.com/gogits/gogs/modules/log"
) )
var (
ErrAuthenticationUserUsed = errors.New("Authentication has been used by some users")
)
type LoginType int type LoginType int
// Note: new type must be added at the end of list to maintain compatibility. // Note: new type must append to the end of list to maintain compatibility.
const ( const (
LOGIN_NOTYPE LoginType = iota LOGIN_NOTYPE LoginType = iota
LOGIN_PLAIN // 1 LOGIN_PLAIN // 1
@ -105,6 +101,7 @@ func (cfg *PAMConfig) ToDB() ([]byte, error) {
return json.Marshal(cfg) return json.Marshal(cfg)
} }
// LoginSource represents an external way for authorizing users.
type LoginSource struct { type LoginSource struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`
Type LoginType Type LoginType
@ -222,13 +219,6 @@ func (source *LoginSource) SMTP() *SMTPConfig {
func (source *LoginSource) PAM() *PAMConfig { func (source *LoginSource) PAM() *PAMConfig {
return source.Cfg.(*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 { func CreateLoginSource(source *LoginSource) error {
has, err := x.Get(&LoginSource{Name: source.Name}) has, err := x.Get(&LoginSource{Name: source.Name})
if err != nil { if err != nil {
@ -268,12 +258,18 @@ func DeleteSource(source *LoginSource) error {
if err != nil { if err != nil {
return err return err
} else if count > 0 { } else if count > 0 {
return ErrAuthenticationUserUsed return ErrLoginSourceInUse{source.ID}
} }
_, err = x.Id(source.ID).Delete(new(LoginSource)) _, err = x.Id(source.ID).Delete(new(LoginSource))
return err return err
} }
// CountLoginSources returns number of login sources.
func CountLoginSources() int64 {
count, _ := x.Count(new(LoginSource))
return count
}
// .____ ________ _____ __________ // .____ ________ _____ __________
// | | \______ \ / _ \\______ \ // | | \______ \ / _ \\______ \
// | | | | \ / /_\ \| ___/ // | | | | \ / /_\ \| ___/
@ -281,49 +277,6 @@ func DeleteSource(source *LoginSource) error {
// |_______ \/_______ /\____|__ /____| // |_______ \/_______ /\____|__ /____|
// \/ \/ \/ // \/ \/ \/
// 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 { func composeFullName(firstname, surname, username string) string {
switch { switch {
case len(firstname) == 0 && len(surname) == 0: case len(firstname) == 0 && len(surname) == 0:
@ -337,6 +290,46 @@ 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, 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)
}
// _________ __________________________ // _________ __________________________
// / _____/ / \__ ___/\______ \ // / _____/ / \__ ___/\______ \
// \_____ \ / \ / \| | | ___/ // \_____ \ / \ / \| | | ___/
@ -344,25 +337,21 @@ func composeFullName(firstname, surname, username string) string {
// /_______ /\____|__ /____| |____| // /_______ /\____|__ /____| |____|
// \/ \/ // \/ \/
type loginAuth struct { type smtpLoginAuth struct {
username, password string username, password string
} }
func LoginAuth(username, password string) smtp.Auth { func (auth *smtpLoginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
return &loginAuth{username, password} return "LOGIN", []byte(auth.username), nil
} }
func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) { func (auth *smtpLoginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
return "LOGIN", []byte(a.username), nil
}
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
if more { if more {
switch string(fromServer) { switch string(fromServer) {
case "Username:": case "Username:":
return []byte(a.username), nil return []byte(auth.username), nil
case "Password:": case "Password:":
return []byte(a.password), nil return []byte(auth.password), nil
} }
} }
return nil, nil return nil, nil
@ -408,25 +397,24 @@ func SMTPAuth(a smtp.Auth, cfg *SMTPConfig) error {
return ErrUnsupportedLoginType return ErrUnsupportedLoginType
} }
// Query if name/passwd can login against the LDAP directory pool // LoginViaSMTP queries if login/password is valid against the SMTP,
// Create a local user if success // and create a local user if success when enabled.
// Return the same LoginUserPlain semantic func LoginViaSMTP(user *User, login, password string, sourceID int64, cfg *SMTPConfig, autoRegister bool) (*User, error) {
func LoginUserSMTPSource(u *User, name, passwd string, sourceID int64, cfg *SMTPConfig, autoRegister bool) (*User, error) {
// Verify allowed domains. // Verify allowed domains.
if len(cfg.AllowedDomains) > 0 { if len(cfg.AllowedDomains) > 0 {
idx := strings.Index(name, "@") idx := strings.Index(login, "@")
if idx == -1 { if idx == -1 {
return nil, ErrUserNotExist{0, name} return nil, ErrUserNotExist{0, login}
} else if !com.IsSliceContainsStr(strings.Split(cfg.AllowedDomains, ","), name[idx+1:]) { } else if !com.IsSliceContainsStr(strings.Split(cfg.AllowedDomains, ","), login[idx+1:]) {
return nil, ErrUserNotExist{0, name} return nil, ErrUserNotExist{0, login}
} }
} }
var auth smtp.Auth var auth smtp.Auth
if cfg.Auth == SMTP_PLAIN { if cfg.Auth == SMTP_PLAIN {
auth = smtp.PlainAuth("", name, passwd, cfg.Host) auth = smtp.PlainAuth("", login, password, cfg.Host)
} else if cfg.Auth == SMTP_LOGIN { } else if cfg.Auth == SMTP_LOGIN {
auth = LoginAuth(name, passwd) auth = &smtpLoginAuth{login, password}
} else { } else {
return nil, errors.New("Unsupported SMTP auth type") return nil, errors.New("Unsupported SMTP auth type")
} }
@ -437,33 +425,32 @@ func LoginUserSMTPSource(u *User, name, passwd string, sourceID int64, cfg *SMTP
tperr, ok := err.(*textproto.Error) tperr, ok := err.(*textproto.Error)
if (ok && tperr.Code == 535) || if (ok && tperr.Code == 535) ||
strings.Contains(err.Error(), "Username and Password not accepted") { strings.Contains(err.Error(), "Username and Password not accepted") {
return nil, ErrUserNotExist{0, name} return nil, ErrUserNotExist{0, login}
} }
return nil, err return nil, err
} }
if !autoRegister { if !autoRegister {
return u, nil return user, nil
} }
var loginName = name username := login
idx := strings.Index(name, "@") idx := strings.Index(login, "@")
if idx > -1 { if idx > -1 {
loginName = name[:idx] username = login[:idx]
} }
// fake a local user creation
u = &User{ user = &User{
LowerName: strings.ToLower(loginName), LowerName: strings.ToLower(username),
Name: strings.ToLower(loginName), Name: strings.ToLower(username),
Email: login,
Passwd: password,
LoginType: LOGIN_SMTP, LoginType: LOGIN_SMTP,
LoginSource: sourceID, LoginSource: sourceID,
LoginName: name, LoginName: login,
IsActive: true, IsActive: true,
Passwd: passwd,
Email: name,
} }
err := CreateUser(u) return user, CreateUser(user)
return u, err
} }
// __________ _____ _____ // __________ _____ _____
@ -473,101 +460,99 @@ func LoginUserSMTPSource(u *User, name, passwd string, sourceID int64, cfg *SMTP
// |____| \____|__ /\____|__ / // |____| \____|__ /\____|__ /
// \/ \/ // \/ \/
// Query if name/passwd can login against PAM // LoginViaPAM queries if login/password is valid against the PAM,
// Create a local user if success // and create a local user if success when enabled.
// Return the same LoginUserPlain semantic func LoginViaPAM(user *User, login, password string, sourceID int64, cfg *PAMConfig, autoRegister bool) (*User, error) {
func LoginUserPAMSource(u *User, name, passwd string, sourceID int64, cfg *PAMConfig, autoRegister bool) (*User, error) { if err := pam.PAMAuth(cfg.ServiceName, login, password); err != nil {
if err := pam.PAMAuth(cfg.ServiceName, name, passwd); err != nil {
if strings.Contains(err.Error(), "Authentication failure") { if strings.Contains(err.Error(), "Authentication failure") {
return nil, ErrUserNotExist{0, name} return nil, ErrUserNotExist{0, login}
} }
return nil, err return nil, err
} }
if !autoRegister { if !autoRegister {
return u, nil return user, nil
} }
// fake a local user creation user = &User{
u = &User{ LowerName: strings.ToLower(login),
LowerName: strings.ToLower(name), Name: login,
Name: name, Email: login,
Passwd: password,
LoginType: LOGIN_PAM, LoginType: LOGIN_PAM,
LoginSource: sourceID, LoginSource: sourceID,
LoginName: name, LoginName: login,
IsActive: true, IsActive: true,
Passwd: passwd,
Email: name,
} }
return u, CreateUser(u) return user, CreateUser(user)
} }
func ExternalUserLogin(u *User, name, passwd string, source *LoginSource, autoRegister bool) (*User, error) { func ExternalUserLogin(user *User, login, password string, source *LoginSource, autoRegister bool) (*User, error) {
if !source.IsActived { if !source.IsActived {
return nil, ErrLoginSourceNotActived return nil, ErrLoginSourceNotActived
} }
switch source.Type { switch source.Type {
case LOGIN_LDAP, LOGIN_DLDAP: case LOGIN_LDAP, LOGIN_DLDAP:
return LoginUserLDAPSource(u, name, passwd, source, autoRegister) return LoginViaLDAP(user, login, password, source, autoRegister)
case LOGIN_SMTP: case LOGIN_SMTP:
return LoginUserSMTPSource(u, name, passwd, source.ID, source.Cfg.(*SMTPConfig), autoRegister) return LoginViaSMTP(user, login, password, source.ID, source.Cfg.(*SMTPConfig), autoRegister)
case LOGIN_PAM: case LOGIN_PAM:
return LoginUserPAMSource(u, name, passwd, source.ID, source.Cfg.(*PAMConfig), autoRegister) return LoginViaPAM(user, login, password, source.ID, source.Cfg.(*PAMConfig), autoRegister)
} }
return nil, ErrUnsupportedLoginType return nil, ErrUnsupportedLoginType
} }
// UserSignIn validates user name and password. // UserSignIn validates user name and password.
func UserSignIn(uname, passwd string) (*User, error) { func UserSignIn(username, passowrd string) (*User, error) {
var u *User var user *User
if strings.Contains(uname, "@") { if strings.Contains(username, "@") {
u = &User{Email: strings.ToLower(uname)} user = &User{Email: strings.ToLower(username)}
} else { } else {
u = &User{LowerName: strings.ToLower(uname)} user = &User{LowerName: strings.ToLower(username)}
} }
userExists, err := x.Get(u) hasUser, err := x.Get(user)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if userExists { if hasUser {
switch u.LoginType { switch user.LoginType {
case LOGIN_NOTYPE, LOGIN_PLAIN: case LOGIN_NOTYPE, LOGIN_PLAIN:
if u.ValidatePassword(passwd) { if user.ValidatePassword(passowrd) {
return u, nil return user, nil
} }
return nil, ErrUserNotExist{u.ID, u.Name} return nil, ErrUserNotExist{user.ID, user.Name}
default: default:
var source LoginSource var source LoginSource
hasSource, err := x.Id(u.LoginSource).Get(&source) hasSource, err := x.Id(user.LoginSource).Get(&source)
if err != nil { if err != nil {
return nil, err return nil, err
} else if !hasSource { } else if !hasSource {
return nil, ErrLoginSourceNotExist{u.LoginSource} return nil, ErrLoginSourceNotExist{user.LoginSource}
} }
return ExternalUserLogin(u, u.LoginName, passwd, &source, false) return ExternalUserLogin(user, user.LoginName, passowrd, &source, false)
} }
} }
var sources []LoginSource sources := make([]*LoginSource, 3)
if err = x.UseBool().Find(&sources, &LoginSource{IsActived: true}); err != nil { if err = x.UseBool().Find(&sources, &LoginSource{IsActived: true}); err != nil {
return nil, err return nil, err
} }
for _, source := range sources { for _, source := range sources {
u, err := ExternalUserLogin(nil, uname, passwd, &source, true) authUser, err := ExternalUserLogin(nil, username, passowrd, source, true)
if err == nil { if err == nil {
return u, nil return authUser, nil
} }
log.Warn("Failed to login '%s' via '%s': %v", uname, source.Name, err) log.Warn("Failed to login '%s' via '%s': %v", username, source.Name, err)
} }
return nil, ErrUserNotExist{u.ID, u.Name} return nil, ErrUserNotExist{user.ID, user.Name}
} }

View file

@ -242,10 +242,9 @@ func DeleteAuthSource(ctx *context.Context) {
} }
if err = models.DeleteSource(source); err != nil { if err = models.DeleteSource(source); err != nil {
switch err { if models.IsErrLoginSourceInUse(err) {
case models.ErrAuthenticationUserUsed:
ctx.Flash.Error(ctx.Tr("admin.auths.still_in_used")) ctx.Flash.Error(ctx.Tr("admin.auths.still_in_used"))
default: } else {
ctx.Flash.Error(fmt.Sprintf("DeleteSource: %v", err)) ctx.Flash.Error(fmt.Sprintf("DeleteSource: %v", err))
} }
ctx.JSON(200, map[string]interface{}{ ctx.JSON(200, map[string]interface{}{

View file

@ -1 +1 @@
0.9.96.0830 0.9.97.0830