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
2015-09-11 19:03:08 +03:00
"github.com/Unknwon/com"
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" )
)
2015-09-11 00:11:41 +03:00
var LoginNames = 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 {
2015-09-14 22:48:51 +03:00
* ldap . Source
2014-04-26 10:21:04 +04:00
}
func ( cfg * LDAPConfig ) FromDB ( bs [ ] byte ) error {
2015-09-14 22:48:51 +03:00
return json . Unmarshal ( bs , & cfg )
2014-04-26 10:21:04 +04:00
}
func ( cfg * LDAPConfig ) ToDB ( ) ( [ ] byte , error ) {
2015-09-14 22:48:51 +03:00
return json . Marshal ( cfg )
2014-04-26 10:21:04 +04:00
}
2014-05-11 11:49:36 +04:00
type SMTPConfig struct {
2015-09-11 20:32:33 +03:00
Auth string
Host string
Port int
AllowedDomains string ` xorm:"TEXT" `
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-09-13 03:58:51 +03:00
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:"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 )
2015-09-11 19:03:08 +03:00
default :
panic ( "unrecognized login source type: " + com . ToStr ( * val ) )
2015-08-29 10:45:58 +03:00
}
}
}
2015-09-11 00:11:41 +03:00
func ( source * LoginSource ) TypeName ( ) string {
return LoginNames [ source . Type ]
2014-05-05 12:40:25 +04:00
}
2015-09-11 19:03:08 +03:00
func ( source * LoginSource ) IsLDAP ( ) bool {
return source . Type == LDAP
}
func ( source * LoginSource ) IsDLDAP ( ) bool {
return source . Type == DLDAP
}
func ( source * LoginSource ) IsSMTP ( ) bool {
return source . Type == SMTP
}
func ( source * LoginSource ) IsPAM ( ) bool {
return source . Type == PAM
}
func ( source * LoginSource ) UseTLS ( ) bool {
switch source . Type {
case LDAP , DLDAP :
return source . LDAP ( ) . UseSSL
case SMTP :
return source . SMTP ( ) . TLS
}
return false
}
2015-09-14 22:48:51 +03:00
func ( source * LoginSource ) SkipVerify ( ) bool {
switch source . Type {
case LDAP , DLDAP :
return source . LDAP ( ) . SkipVerify
case SMTP :
return source . SMTP ( ) . SkipVerify
}
return false
}
2014-05-05 12:40:25 +04:00
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 )
}
2015-09-10 22:45:03 +03:00
// CountLoginSources returns number of login sources.
func CountLoginSources ( ) int64 {
count , _ := x . Count ( new ( LoginSource ) )
return count
}
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
}
2015-09-13 16:51:51 +03:00
func LoginSources ( ) ( [ ] * 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
}
2015-09-11 19:03:08 +03:00
func DeleteSource ( source * LoginSource ) error {
count , err := x . Count ( & User { LoginSource : source . ID } )
2014-05-05 12:40:25 +04:00
if err != nil {
return err
2015-09-11 19:03:08 +03:00
} else if count > 0 {
2014-05-05 12:40:25 +04:00
return ErrAuthenticationUserUsed
}
2015-09-11 19:03:08 +03:00
_ , err = x . Id ( source . ID ) . Delete ( new ( 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
2015-09-13 03:58:51 +03:00
// .____ ________ _____ __________
// | | \______ \ / _ \\______ \
// | | | | \ / /_\ \| ___/
// | |___ | ` \/ | \ |
// |_______ \/_______ /\____|__ /____|
// \/ \/ \/
2014-05-11 10:12:45 +04:00
2015-11-21 20:58:31 +03:00
// LoginUserLDAPSource queries if name/passwd can login against the LDAP directory pool,
// and create a local user if success when enabled.
// It returns the same LoginUserPlain semantic.
2015-09-13 03:58:51 +03:00
func LoginUserLDAPSource ( u * User , name , passwd string , source * LoginSource , autoRegister bool ) ( * User , error ) {
2015-09-05 06:39:23 +03:00
cfg := source . Cfg . ( * LDAPConfig )
directBind := ( source . Type == DLDAP )
2015-09-14 22:48:51 +03:00
fn , sn , mail , admin , logged := cfg . 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-09-13 03:58:51 +03:00
FullName : strings . TrimSpace ( 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 ,
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
2015-09-13 03:58:51 +03:00
// _________ __________________________
// / _____/ / \__ ___/\______ \
// \_____ \ / \ / \| | | ___/
// / \/ Y \ | | |
// /_______ /\____|__ /____| |____|
// \/ \/
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-09-11 00:11:41 +03:00
var SMTPAuths = [ ] string { SMTP_PLAIN , SMTP_LOGIN }
2015-08-29 10:45:58 +03:00
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 ) {
2015-09-11 20:32:33 +03:00
// 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 }
}
}
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-09-11 20:32:33 +03:00
return nil , ErrUserNotExist { 0 , 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
2015-09-13 03:58:51 +03:00
// __________ _____ _____
// \______ \/ _ \ / \
// | ___/ /_\ \ / \ / \
// | | / | \/ Y \
// |____| \____|__ /\____|__ /
// \/ \/
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-09-14 18:03:42 +03:00
return nil , ErrUserNotExist { 0 , 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
}
2015-09-13 03:58:51 +03:00
func ExternalUserLogin ( u * User , name , passwd string , source * LoginSource , autoRegister bool ) ( * User , error ) {
if ! source . IsActived {
return nil , ErrLoginSourceNotActived
}
switch source . Type {
case LDAP , DLDAP :
return LoginUserLDAPSource ( u , name , passwd , source , autoRegister )
case SMTP :
return LoginUserSMTPSource ( u , name , passwd , source . ID , source . Cfg . ( * SMTPConfig ) , autoRegister )
case 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 : 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 NOTYPE , 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
}
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 }
}