2014-05-05 13:32:47 +04:00
// Copyright github.com/juju2013. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
2014-04-26 10:21:04 +04:00
package models
2014-05-03 06:48:14 +04:00
import (
"encoding/json"
2014-05-05 12:40:25 +04:00
"errors"
2014-05-11 11:49:36 +04:00
"fmt"
"net/smtp"
2014-05-11 10:12:45 +04:00
"strings"
2014-05-03 06:48:14 +04:00
"time"
2014-04-26 10:21:04 +04:00
2014-05-03 06:48:14 +04:00
"github.com/go-xorm/core"
2014-05-05 12:40:25 +04:00
"github.com/go-xorm/xorm"
2014-05-05 13:32:47 +04:00
2014-05-03 06:48:14 +04:00
"github.com/gogits/gogs/modules/auth/ldap"
)
2014-04-26 10:21:04 +04:00
2014-05-05 12:40:25 +04:00
// Login types.
const (
2014-05-11 10:12:45 +04:00
LT_NOTYPE = iota
LT_PLAIN
2014-04-26 10:21:04 +04:00
LT_LDAP
LT_SMTP
2014-05-05 12:40:25 +04:00
)
var (
ErrAuthenticationAlreadyExist = errors . New ( "Authentication already exist" )
2014-05-05 13:32:47 +04:00
ErrAuthenticationNotExist = errors . New ( "Authentication does not exist" )
2014-05-05 12:40:25 +04:00
ErrAuthenticationUserUsed = errors . New ( "Authentication has been used by some users" )
)
var LoginTypes = map [ int ] string {
LT_LDAP : "LDAP" ,
LT_SMTP : "SMTP" ,
}
2014-04-26 10:21:04 +04:00
var _ core . Conversion = & LDAPConfig { }
2014-05-11 14:10:37 +04:00
var _ core . Conversion = & SMTPConfig { }
2014-04-26 10:21:04 +04:00
type LDAPConfig struct {
2014-05-03 06:48:14 +04:00
ldap . Ldapsource
2014-04-26 10:21:04 +04:00
}
// implement
func ( cfg * LDAPConfig ) FromDB ( bs [ ] byte ) error {
2014-05-03 06:48:14 +04:00
return json . Unmarshal ( bs , & cfg . Ldapsource )
2014-04-26 10:21:04 +04:00
}
func ( cfg * LDAPConfig ) ToDB ( ) ( [ ] byte , error ) {
2014-05-03 06:48:14 +04:00
return json . Marshal ( cfg . Ldapsource )
2014-04-26 10:21:04 +04:00
}
2014-05-11 11:49:36 +04:00
type SMTPConfig struct {
Auth string
Host string
2014-05-11 14:10:37 +04:00
Port int
2014-05-11 11:49:36 +04:00
TLS bool
}
// implement
func ( cfg * SMTPConfig ) FromDB ( bs [ ] byte ) error {
return json . Unmarshal ( bs , cfg )
}
func ( cfg * SMTPConfig ) ToDB ( ) ( [ ] byte , error ) {
return json . Marshal ( cfg )
}
2014-04-26 10:21:04 +04:00
type LoginSource struct {
2014-05-11 10:12:45 +04:00
Id int64
Type int
Name string ` xorm:"unique" `
IsActived bool ` xorm:"not null default false" `
Cfg core . Conversion ` xorm:"TEXT" `
Created time . Time ` xorm:"created" `
Updated time . Time ` xorm:"updated" `
2014-05-11 16:18:57 +04:00
AllowAutoRegister bool ` xorm:"not null default false" `
2014-05-03 06:48:14 +04:00
}
2014-05-05 12:40:25 +04:00
func ( source * LoginSource ) TypeString ( ) string {
return LoginTypes [ source . Type ]
}
func ( source * LoginSource ) LDAP ( ) * LDAPConfig {
return source . Cfg . ( * LDAPConfig )
}
2014-05-11 11:49:36 +04:00
func ( source * LoginSource ) SMTP ( ) * SMTPConfig {
return source . Cfg . ( * SMTPConfig )
}
2014-05-05 12:40:25 +04:00
// for xorm callback
func ( source * LoginSource ) BeforeSet ( colName string , val xorm . Cell ) {
if colName == "type" {
ty := ( * val ) . ( int64 )
switch ty {
case LT_LDAP :
source . Cfg = new ( LDAPConfig )
2014-05-11 11:49:36 +04:00
case LT_SMTP :
source . Cfg = new ( SMTPConfig )
2014-05-05 12:40:25 +04:00
}
}
}
2014-05-03 06:48:14 +04:00
func GetAuths ( ) ( [ ] * LoginSource , error ) {
var auths = make ( [ ] * LoginSource , 0 )
err := orm . Find ( & auths )
return auths , err
}
2014-05-05 12:40:25 +04:00
func GetLoginSourceById ( id int64 ) ( * LoginSource , error ) {
source := new ( LoginSource )
has , err := orm . Id ( id ) . Get ( source )
if err != nil {
return nil , err
}
if ! has {
return nil , ErrAuthenticationNotExist
}
return source , nil
}
2014-05-11 14:10:37 +04:00
func AddSource ( source * LoginSource ) error {
_ , err := orm . Insert ( source )
2014-05-03 06:48:14 +04:00
return err
}
2014-05-11 14:10:37 +04:00
func UpdateSource ( source * LoginSource ) error {
2014-05-05 12:40:25 +04:00
_ , err := orm . AllCols ( ) . Id ( source . Id ) . Update ( source )
2014-05-03 06:48:14 +04:00
return err
}
2014-05-05 12:40:25 +04:00
func DelLoginSource ( source * LoginSource ) error {
cnt , err := orm . Count ( & User { LoginSource : source . Id } )
if err != nil {
return err
}
if cnt > 0 {
return ErrAuthenticationUserUsed
}
_ , err = orm . Id ( source . Id ) . Delete ( & LoginSource { } )
2014-05-03 06:48:14 +04:00
return err
2014-04-26 10:21:04 +04:00
}
2014-05-11 10:12:45 +04:00
// login a user
func LoginUser ( uname , passwd string ) ( * User , error ) {
var u * User
if strings . Contains ( uname , "@" ) {
u = & User { Email : uname }
} else {
u = & User { LowerName : strings . ToLower ( uname ) }
}
has , err := orm . Get ( u )
if err != nil {
return nil , err
}
if u . LoginType == LT_NOTYPE {
2014-05-11 16:04:28 +04:00
if has {
u . LoginType = LT_PLAIN
}
2014-05-11 10:12:45 +04:00
}
// for plain login, user must have existed.
if u . LoginType == LT_PLAIN {
if ! has {
return nil , ErrUserNotExist
}
newUser := & User { Passwd : passwd , Salt : u . Salt }
newUser . EncodePasswd ( )
if u . Passwd != newUser . Passwd {
return nil , ErrUserNotExist
}
return u , nil
} else {
if ! has {
var sources [ ] LoginSource
2014-05-11 16:18:57 +04:00
cond := & LoginSource { IsActived : true , AllowAutoRegister : true }
2014-05-11 10:12:45 +04:00
err = orm . UseBool ( ) . Find ( & sources , cond )
if err != nil {
return nil , err
}
for _ , source := range sources {
2014-05-11 11:49:36 +04:00
if source . Type == LT_LDAP {
2014-05-11 16:04:28 +04:00
u , err := LoginUserLdapSource ( nil , uname , passwd ,
2014-05-11 11:49:36 +04:00
source . Id , source . Cfg . ( * LDAPConfig ) , true )
if err == nil {
return u , err
}
} else if source . Type == LT_SMTP {
2014-05-11 16:04:28 +04:00
u , err := LoginUserSMTPSource ( nil , uname , passwd ,
2014-05-11 11:49:36 +04:00
source . Id , source . Cfg . ( * SMTPConfig ) , true )
if err == nil {
return u , err
}
2014-05-11 10:12:45 +04:00
}
}
return nil , ErrUserNotExist
}
var source LoginSource
hasSource , err := orm . Id ( u . LoginSource ) . Get ( & source )
if err != nil {
return nil , err
}
if ! hasSource {
return nil , ErrLoginSourceNotExist
}
if ! source . IsActived {
return nil , ErrLoginSourceNotActived
}
switch u . LoginType {
case LT_LDAP :
return LoginUserLdapSource ( u , u . LoginName , passwd ,
source . Id , source . Cfg . ( * LDAPConfig ) , false )
case LT_SMTP :
2014-05-11 11:49:36 +04:00
return LoginUserSMTPSource ( u , u . LoginName , passwd ,
source . Id , source . Cfg . ( * SMTPConfig ) , false )
2014-05-11 10:12:45 +04:00
}
return nil , ErrUnsupportedLoginType
}
}
// Query if name/passwd can login against the LDAP direcotry pool
// Create a local user if success
// Return the same LoginUserPlain semantic
func LoginUserLdapSource ( user * User , name , passwd string , sourceId int64 , cfg * LDAPConfig , autoRegister bool ) ( * User , error ) {
mail , logged := cfg . Ldapsource . SearchEntry ( name , passwd )
if ! logged {
// user not in LDAP, do nothing
return nil , ErrUserNotExist
}
if ! autoRegister {
return user , nil
}
// fake a local user creation
user = & User {
LowerName : strings . ToLower ( name ) ,
Name : strings . ToLower ( name ) ,
LoginType : LT_LDAP ,
LoginSource : sourceId ,
LoginName : name ,
IsActive : true ,
Passwd : passwd ,
Email : mail ,
}
return RegisterUser ( user )
}
2014-05-11 11:49:36 +04:00
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
}
var (
2014-05-11 14:10:37 +04:00
SMTP_PLAIN = "PLAIN"
SMTP_LOGIN = "LOGIN"
SMTPAuths = [ ] string { SMTP_PLAIN , SMTP_LOGIN }
2014-05-11 11:49:36 +04:00
)
2014-05-11 16:04:28 +04:00
func SmtpAuth ( addr string , a smtp . Auth , tls bool ) error {
2014-05-11 11:49:36 +04:00
c , err := smtp . Dial ( addr )
if err != nil {
return err
}
defer c . Close ( )
2014-05-11 16:04:28 +04:00
if tls {
if ok , _ := c . Extension ( "STARTTLS" ) ; ok {
if err = c . StartTLS ( nil ) ; err != nil {
return err
}
} else {
return errors . New ( "smtp server unsupported tls" )
2014-05-11 11:49:36 +04:00
}
}
if ok , _ := c . Extension ( "AUTH" ) ; ok {
if err = c . Auth ( a ) ; err != nil {
return err
}
return nil
} else {
return ErrUnsupportedLoginType
}
}
// Query if name/passwd can login against the LDAP direcotry pool
// Create a local user if success
// Return the same LoginUserPlain semantic
func LoginUserSMTPSource ( user * User , name , passwd string , sourceId int64 , cfg * SMTPConfig , autoRegister bool ) ( * User , error ) {
var auth smtp . Auth
2014-05-11 14:10:37 +04:00
if cfg . Auth == SMTP_PLAIN {
2014-05-11 11:49:36 +04:00
auth = smtp . PlainAuth ( "" , name , passwd , cfg . Host )
2014-05-11 14:10:37 +04:00
} else if cfg . Auth == SMTP_LOGIN {
2014-05-11 11:49:36 +04:00
auth = LoginAuth ( name , passwd )
2014-05-11 16:04:28 +04:00
} else {
return nil , errors . New ( "Unsupported smtp auth type" )
2014-05-11 11:49:36 +04:00
}
2014-05-11 16:04:28 +04:00
err := SmtpAuth ( fmt . Sprintf ( "%s:%d" , cfg . Host , cfg . Port ) , auth , cfg . TLS )
2014-05-11 11:49:36 +04:00
if err != nil {
return nil , err
}
if ! autoRegister {
return user , nil
}
2014-05-11 16:04:28 +04:00
var loginName = name
idx := strings . Index ( name , "@" )
if idx > - 1 {
loginName = name [ : idx ]
}
2014-05-11 11:49:36 +04:00
// fake a local user creation
user = & User {
2014-05-11 16:04:28 +04:00
LowerName : strings . ToLower ( loginName ) ,
Name : strings . ToLower ( loginName ) ,
2014-05-11 11:49:36 +04:00
LoginType : LT_SMTP ,
LoginSource : sourceId ,
LoginName : name ,
IsActive : true ,
Passwd : passwd ,
Email : name ,
}
return RegisterUser ( user )
}