2014-06-09 01:53:53 +04:00
// Copyright 2014 The Gogs Authors. All rights reserved.
2014-05-05 13:32:47 +04:00
// 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 (
2014-05-15 22:46:04 +04:00
"crypto/tls"
2014-05-03 06:48:14 +04:00
"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"
2015-04-23 14:58:57 +03:00
"github.com/gogits/gogs/modules/auth/pam"
2014-05-15 18:34:36 +04:00
"github.com/gogits/gogs/modules/log"
2014-12-06 02:08:09 +03:00
"github.com/gogits/gogs/modules/uuid"
2014-05-03 06:48:14 +04:00
)
2014-04-26 10:21:04 +04:00
2014-06-09 01:53:53 +04:00
type LoginType int
2014-05-05 12:40:25 +04:00
const (
2014-06-09 01:53:53 +04:00
NOTYPE LoginType = iota
PLAIN
LDAP
SMTP
2015-04-23 14:58:57 +03:00
PAM
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" )
)
2014-06-09 01:53:53 +04:00
var LoginTypes = map [ LoginType ] string {
LDAP : "LDAP" ,
SMTP : "SMTP" ,
2015-08-05 06:14:17 +03:00
PAM : "PAM" ,
2014-05-05 12:40:25 +04:00
}
2014-04-26 10:21:04 +04:00
2014-12-07 04:22:48 +03:00
// Ensure structs implemented interface.
2014-05-12 19:02:36 +04:00
var (
_ core . Conversion = & LDAPConfig { }
_ core . Conversion = & SMTPConfig { }
2015-04-23 14:58:57 +03:00
_ core . Conversion = & PAMConfig { }
2014-05-12 19:02:36 +04:00
)
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
}
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
}
func ( cfg * SMTPConfig ) FromDB ( bs [ ] byte ) error {
return json . Unmarshal ( bs , cfg )
}
func ( cfg * SMTPConfig ) ToDB ( ) ( [ ] byte , error ) {
return json . Marshal ( cfg )
}
2015-04-23 14:58:57 +03:00
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 )
}
2014-04-26 10:21:04 +04:00
type LoginSource struct {
2014-05-11 10:12:45 +04:00
Id int64
2014-06-09 01:53:53 +04:00
Type LoginType
Name string ` xorm:"UNIQUE" `
IsActived bool ` xorm:"NOT NULL DEFAULT false" `
2014-05-11 10:12:45 +04:00
Cfg core . Conversion ` xorm:"TEXT" `
2014-06-09 01:53:53 +04:00
AllowAutoRegister bool ` xorm:"NOT NULL DEFAULT false" `
Created time . Time ` xorm:"CREATED" `
Updated time . Time ` xorm:"UPDATED" `
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 )
}
2015-04-23 14:58:57 +03:00
func ( source * LoginSource ) PAM ( ) * PAMConfig {
return source . Cfg . ( * PAMConfig )
}
2014-05-05 12:40:25 +04:00
func ( source * LoginSource ) BeforeSet ( colName string , val xorm . Cell ) {
if colName == "type" {
ty := ( * val ) . ( int64 )
2014-06-09 01:53:53 +04:00
switch LoginType ( ty ) {
case LDAP :
2014-05-05 12:40:25 +04:00
source . Cfg = new ( LDAPConfig )
2014-06-09 01:53:53 +04:00
case SMTP :
2014-05-11 11:49:36 +04:00
source . Cfg = new ( SMTPConfig )
2015-04-23 14:58:57 +03:00
case PAM :
source . Cfg = new ( PAMConfig )
2014-05-05 12:40:25 +04:00
}
}
}
2014-06-09 01:53:53 +04:00
func CreateSource ( source * LoginSource ) error {
2014-06-21 08:51:41 +04:00
_ , err := x . Insert ( source )
2014-06-09 01:53:53 +04:00
return err
}
2014-05-03 06:48:14 +04:00
func GetAuths ( ) ( [ ] * LoginSource , error ) {
2014-06-09 01:53:53 +04:00
var auths = make ( [ ] * LoginSource , 0 , 5 )
2014-06-21 08:51:41 +04:00
err := x . Find ( & auths )
2014-05-03 06:48:14 +04:00
return auths , err
}
2014-05-05 12:40:25 +04:00
func GetLoginSourceById ( id int64 ) ( * LoginSource , error ) {
source := new ( LoginSource )
2014-06-21 08:51:41 +04:00
has , err := x . Id ( id ) . Get ( source )
2014-05-05 12:40:25 +04:00
if err != nil {
return nil , err
2014-06-09 01:53:53 +04:00
} else if ! has {
2014-05-05 12:40:25 +04:00
return nil , ErrAuthenticationNotExist
}
return source , nil
}
2014-05-11 14:10:37 +04:00
func UpdateSource ( source * LoginSource ) error {
2014-06-21 08:51:41 +04:00
_ , err := x . Id ( source . Id ) . AllCols ( ) . 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 {
2014-06-21 08:51:41 +04:00
cnt , err := x . Count ( & User { LoginSource : source . Id } )
2014-05-05 12:40:25 +04:00
if err != nil {
return err
}
if cnt > 0 {
return ErrAuthenticationUserUsed
}
2014-06-21 08:51:41 +04:00
_ , err = x . 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
2014-06-06 06:07:35 +04:00
// UserSignIn validates user name and password.
func UserSignIn ( uname , passwd string ) ( * User , error ) {
2014-10-25 02:43:17 +04:00
u := new ( User )
2014-05-11 10:12:45 +04:00
if strings . Contains ( uname , "@" ) {
u = & User { Email : uname }
} else {
u = & User { LowerName : strings . ToLower ( uname ) }
}
2014-06-21 08:51:41 +04:00
has , err := x . Get ( u )
2014-05-11 10:12:45 +04:00
if err != nil {
return nil , err
}
2014-10-03 21:12:54 +04:00
if u . LoginType == NOTYPE && has {
u . LoginType = PLAIN
2014-05-11 10:12:45 +04:00
}
2014-07-26 08:24:27 +04:00
// For plain login, user must exist to reach this line.
// Now verify password.
2014-06-09 01:53:53 +04:00
if u . LoginType == PLAIN {
2015-04-16 21:36:32 +03:00
if ! u . ValidatePassword ( passwd ) {
2015-08-05 06:14:17 +03:00
return nil , ErrUserNotExist { u . Id , u . Name }
2014-05-11 10:12:45 +04:00
}
return u , nil
2015-03-06 03:20:27 +03:00
}
if ! has {
var sources [ ] LoginSource
if err = x . UseBool ( ) . Find ( & sources ,
& LoginSource { IsActived : true , AllowAutoRegister : true } ) ; err != nil {
return nil , err
}
2014-05-11 10:12:45 +04:00
2015-03-06 03:20:27 +03:00
for _ , source := range sources {
if source . Type == LDAP {
u , err := LoginUserLdapSource ( nil , uname , passwd ,
source . Id , source . Cfg . ( * LDAPConfig ) , true )
if err == nil {
return u , nil
}
log . Warn ( "Fail to login(%s) by LDAP(%s): %v" , uname , source . Name , err )
} else if source . Type == SMTP {
u , err := LoginUserSMTPSource ( nil , uname , passwd ,
source . Id , source . Cfg . ( * SMTPConfig ) , true )
if err == nil {
return u , nil
2014-05-11 10:12:45 +04:00
}
2015-03-06 03:20:27 +03:00
log . Warn ( "Fail to login(%s) by SMTP(%s): %v" , uname , source . Name , err )
2015-04-23 14:58:57 +03:00
} else if source . Type == PAM {
u , err := LoginUserPAMSource ( nil , uname , passwd ,
source . Id , source . Cfg . ( * PAMConfig ) , true )
if err == nil {
return u , nil
}
log . Warn ( "Fail to login(%s) by PAM(%s): %v" , uname , source . Name , err )
2014-05-11 10:12:45 +04:00
}
}
2015-08-05 06:14:17 +03:00
return nil , ErrUserNotExist { u . Id , u . Name }
2015-03-06 03:20:27 +03:00
}
2014-05-11 10:12:45 +04:00
2015-03-06 03:20:27 +03:00
var source LoginSource
hasSource , err := x . Id ( u . LoginSource ) . Get ( & source )
if err != nil {
return nil , err
} else if ! hasSource {
return nil , ErrLoginSourceNotExist
} else if ! source . IsActived {
return nil , ErrLoginSourceNotActived
}
switch u . LoginType {
case LDAP :
2015-03-12 08:15:01 +03:00
return LoginUserLdapSource ( u , u . LoginName , passwd , source . Id , source . Cfg . ( * LDAPConfig ) , false )
2015-03-06 03:20:27 +03:00
case SMTP :
2015-03-12 08:15:01 +03:00
return LoginUserSMTPSource ( u , u . LoginName , passwd , source . Id , source . Cfg . ( * SMTPConfig ) , false )
2015-04-23 14:58:57 +03:00
case PAM :
return LoginUserPAMSource ( u , u . LoginName , passwd , source . Id , source . Cfg . ( * PAMConfig ) , false )
2014-05-11 10:12:45 +04:00
}
2015-03-06 03:20:27 +03:00
return nil , ErrUnsupportedLoginType
2014-05-11 10:12:45 +04:00
}
2014-12-07 04:22:48 +03:00
// Query if name/passwd can login against the LDAP directory pool
2014-05-11 10:12:45 +04:00
// Create a local user if success
// Return the same LoginUserPlain semantic
2014-12-06 02:08:09 +03:00
// FIXME: https://github.com/gogits/gogs/issues/672
2014-07-26 08:24:27 +04:00
func LoginUserLdapSource ( u * User , name , passwd string , sourceId int64 , cfg * LDAPConfig , autoRegister bool ) ( * User , error ) {
2015-02-08 02:49:51 +03:00
name , fn , sn , mail , logged := cfg . Ldapsource . SearchEntry ( name , passwd )
2014-05-11 10:12:45 +04:00
if ! logged {
2014-12-06 02:08:09 +03:00
// User not in LDAP, do nothing
2015-08-05 06:14:17 +03:00
return nil , ErrUserNotExist { u . Id , u . Name }
2014-05-11 10:12:45 +04:00
}
if ! autoRegister {
2014-07-26 08:24:27 +04:00
return u , nil
2014-05-11 10:12:45 +04:00
}
2014-12-06 02:08:09 +03:00
// Fallback.
if len ( mail ) == 0 {
mail = uuid . NewV4 ( ) . String ( ) + "@localhost"
}
2014-07-26 08:24:27 +04:00
u = & User {
2015-03-23 17:51:43 +03:00
LowerName : strings . ToLower ( name ) ,
2014-12-06 02:08:09 +03:00
Name : name ,
2015-02-08 02:49:51 +03:00
FullName : fn + " " + sn ,
2014-06-09 01:53:53 +04:00
LoginType : LDAP ,
2014-05-11 10:12:45 +04:00
LoginSource : sourceId ,
LoginName : name ,
Passwd : passwd ,
Email : mail ,
2014-12-06 02:08:09 +03:00
IsActive : true ,
2014-05-11 10:12:45 +04:00
}
2014-12-06 02:08:09 +03:00
return u , CreateUser ( u )
2014-05-11 10:12:45 +04:00
}
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-15 22:46:04 +04:00
func SmtpAuth ( host string , port int , a smtp . Auth , useTls bool ) error {
c , err := smtp . Dial ( fmt . Sprintf ( "%s:%d" , host , port ) )
2014-05-11 11:49:36 +04:00
if err != nil {
return err
}
defer c . Close ( )
2014-05-15 22:46:04 +04:00
if err = c . Hello ( "gogs" ) ; err != nil {
return err
}
if useTls {
2014-05-11 16:04:28 +04:00
if ok , _ := c . Extension ( "STARTTLS" ) ; ok {
2014-05-15 22:46:04 +04:00
config := & tls . Config { ServerName : host }
if err = c . StartTLS ( config ) ; err != nil {
2014-05-11 16:04:28 +04:00
return err
}
} else {
2014-05-16 07:03:26 +04:00
return errors . New ( "SMTP server unsupports 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
}
2014-06-09 01:53:53 +04:00
return ErrUnsupportedLoginType
2014-05-11 11:49:36 +04:00
}
2014-12-07 04:22:48 +03:00
// Query if name/passwd can login against the LDAP directory pool
2014-05-11 11:49:36 +04:00
// Create a local user if success
// Return the same LoginUserPlain semantic
2014-07-26 08:24:27 +04:00
func LoginUserSMTPSource ( u * User , name , passwd string , sourceId int64 , cfg * SMTPConfig , autoRegister bool ) ( * User , error ) {
2014-05-11 11:49:36 +04:00
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 {
2014-05-15 22:46:04 +04:00
return nil , errors . New ( "Unsupported SMTP auth type" )
2014-05-11 11:49:36 +04:00
}
2014-05-15 22:46:04 +04:00
if err := SmtpAuth ( cfg . Host , cfg . Port , auth , cfg . TLS ) ; err != nil {
if strings . Contains ( err . Error ( ) , "Username and Password not accepted" ) {
2015-08-05 06:14:17 +03:00
return nil , ErrUserNotExist { u . Id , u . Name }
2014-05-15 22:46:04 +04:00
}
2014-05-11 11:49:36 +04:00
return nil , err
}
if ! autoRegister {
2014-07-26 08:24:27 +04:00
return u , nil
2014-05-11 11:49:36 +04:00
}
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
2014-07-26 08:24:27 +04:00
u = & User {
2014-05-11 16:04:28 +04:00
LowerName : strings . ToLower ( loginName ) ,
Name : strings . ToLower ( loginName ) ,
2014-06-09 01:53:53 +04:00
LoginType : SMTP ,
2014-05-11 11:49:36 +04:00
LoginSource : sourceId ,
LoginName : name ,
IsActive : true ,
Passwd : passwd ,
Email : name ,
}
2014-07-26 08:24:27 +04:00
err := CreateUser ( u )
return u , err
2014-05-11 11:49:36 +04:00
}
2015-04-23 14:58:57 +03:00
// 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" ) {
2015-08-05 06:14:17 +03:00
return nil , ErrUserNotExist { u . Id , u . Name }
2015-04-23 14:58:57 +03:00
}
return nil , err
}
if ! autoRegister {
return u , nil
}
// fake a local user creation
u = & User {
LowerName : strings . ToLower ( name ) ,
Name : strings . ToLower ( name ) ,
LoginType : PAM ,
LoginSource : sourceId ,
LoginName : name ,
IsActive : true ,
Passwd : passwd ,
Email : name ,
}
err := CreateUser ( u )
return u , err
}