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-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
2015-09-10 22:03:14 +03:00
// Note: new type must be added at the end of list to maintain compatibility.
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
2015-09-10 22:03:14 +03:00
DLDAP
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 {
2015-09-05 06:39:23 +03:00
LDAP : "LDAP (via BindDN)" ,
DLDAP : "LDAP (simple auth)" ,
SMTP : "SMTP" ,
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 {
2015-08-29 10:45:58 +03:00
Auth string
Host string
Port int
TLS bool
SkipVerify bool
2014-05-11 11:49:36 +04:00
}
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 {
2015-08-29 10:45:58 +03:00
ID int64 ` xorm:"pk autoincr" `
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
}
2015-08-29 10:45:58 +03:00
func ( source * LoginSource ) BeforeSet ( colName string , val xorm . Cell ) {
switch colName {
case "type" :
switch LoginType ( ( * val ) . ( int64 ) ) {
2015-09-10 22:03:14 +03:00
case LDAP , DLDAP :
2015-08-29 10:45:58 +03:00
source . Cfg = new ( LDAPConfig )
case SMTP :
source . Cfg = new ( SMTPConfig )
case PAM :
source . Cfg = new ( PAMConfig )
}
}
}
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-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 ) {
2015-08-29 10:45:58 +03:00
auths := make ( [ ] * LoginSource , 0 , 5 )
return auths , x . Find ( & auths )
2014-05-03 06:48:14 +04:00
}
2015-08-29 10:45:58 +03:00
func GetLoginSourceByID ( id int64 ) ( * LoginSource , error ) {
2014-05-05 12:40:25 +04:00
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 {
2015-08-29 10:45:58 +03: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 {
2015-08-29 10:45:58 +03: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
}
2015-08-29 10:45:58 +03: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 ) {
2015-09-05 06:39:23 +03:00
var u * User
2014-05-11 10:12:45 +04:00
if strings . Contains ( uname , "@" ) {
u = & User { Email : uname }
} else {
u = & User { LowerName : strings . ToLower ( uname ) }
}
2015-09-05 06:39:23 +03:00
userExists , err := x . Get ( u )
2014-05-11 10:12:45 +04:00
if err != nil {
return nil , err
}
2015-09-05 06:39:23 +03:00
if userExists {
switch u . LoginType {
case NOTYPE :
fallthrough
case PLAIN :
if u . ValidatePassword ( passwd ) {
return u , nil
}
2014-05-11 10:12:45 +04:00
2015-08-05 06:14:17 +03:00
return nil , ErrUserNotExist { u . Id , u . Name }
2015-09-05 06:39:23 +03:00
default :
var source LoginSource
hasSource , err := x . Id ( u . LoginSource ) . Get ( & source )
if err != nil {
return nil , err
} else if ! hasSource {
return nil , ErrLoginSourceNotExist
}
return ExternalUserLogin ( u , u . LoginName , passwd , & source , false )
2014-05-11 10:12:45 +04:00
}
2015-03-06 03:20:27 +03:00
}
2015-09-05 06:39:23 +03:00
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-09-05 06:39:23 +03:00
for _ , source := range sources {
u , err := ExternalUserLogin ( nil , uname , passwd , & source , true )
if err == nil {
return u , nil
2014-05-11 10:12:45 +04:00
}
2015-09-05 06:39:23 +03:00
log . Warn ( "Failed to login '%s' via '%s': %v" , uname , source . Name , err )
2015-03-06 03:20:27 +03:00
}
2014-05-11 10:12:45 +04:00
2015-09-05 06:39:23 +03:00
return nil , ErrUserNotExist { u . Id , u . Name }
}
func ExternalUserLogin ( u * User , name , passwd string , source * LoginSource , autoRegister bool ) ( * User , error ) {
if ! source . IsActived {
2015-03-06 03:20:27 +03:00
return nil , ErrLoginSourceNotActived
}
2015-09-05 06:39:23 +03:00
switch source . Type {
2015-09-10 22:03:14 +03:00
case LDAP , DLDAP :
2015-09-05 06:39:23 +03:00
return LoginUserLdapSource ( u , name , passwd , source , autoRegister )
2015-03-06 03:20:27 +03:00
case SMTP :
2015-09-05 06:39:23 +03:00
return LoginUserSMTPSource ( u , name , passwd , source . ID , source . Cfg . ( * SMTPConfig ) , autoRegister )
2015-04-23 14:58:57 +03:00
case PAM :
2015-09-05 06:39:23 +03:00
return LoginUserPAMSource ( u , name , passwd , source . ID , source . Cfg . ( * PAMConfig ) , autoRegister )
2014-05-11 10:12:45 +04:00
}
2015-09-05 06:39:23 +03: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
2015-09-05 06:39:23 +03:00
func LoginUserLdapSource ( u * User , name , passwd string , source * LoginSource , autoRegister bool ) ( * User , error ) {
cfg := source . Cfg . ( * LDAPConfig )
directBind := ( source . Type == DLDAP )
fn , sn , mail , admin , logged := cfg . Ldapsource . SearchEntry ( name , passwd , directBind )
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-13 02:58:27 +03:00
return nil , ErrUserNotExist { 0 , name }
2014-05-11 10:12:45 +04:00
}
2015-08-13 02:58:27 +03:00
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 {
2015-08-13 02:58:27 +03:00
mail = fmt . Sprintf ( "%s@localhost" , name )
2014-12-06 02:08:09 +03:00
}
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 ,
2015-09-05 06:39:23 +03:00
LoginType : source . Type ,
LoginSource : source . ID ,
2014-05-11 10:12:45 +04:00
LoginName : name ,
Passwd : passwd ,
Email : mail ,
2015-08-19 07:34:03 +03:00
IsAdmin : admin ,
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
}
2015-08-29 10:45:58 +03:00
const (
2014-05-11 14:10:37 +04:00
SMTP_PLAIN = "PLAIN"
SMTP_LOGIN = "LOGIN"
2014-05-11 11:49:36 +04:00
)
2015-08-29 10:45:58 +03:00
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 ) )
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
}
2015-08-29 10:45:58 +03:00
if cfg . TLS {
2014-05-11 16:04:28 +04:00
if ok , _ := c . Extension ( "STARTTLS" ) ; ok {
2015-08-29 10:45:58 +03:00
if err = c . StartTLS ( & tls . Config {
InsecureSkipVerify : cfg . SkipVerify ,
ServerName : cfg . Host ,
} ) ; 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
}
2015-08-29 10:45:58 +03:00
if err := SMTPAuth ( auth , cfg ) ; err != nil {
2014-05-15 22:46:04 +04:00
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
}