Browse Source

实现ldap功能

David Jiang 8 năm trước cách đây
mục cha
commit
b7a9af8beb
4 tập tin đã thay đổi với 165 bổ sung90 xóa
  1. 1 1
      commands/install.go
  2. 17 5
      conf/app.conf.example
  3. 14 3
      models/errors.go
  4. 133 81
      models/member.go

+ 1 - 1
commands/install.go

@@ -32,7 +32,6 @@ func initialization() {
 
 	o := orm.NewOrm()
 
-
 	_, err := o.Raw(`INSERT INTO md_options (option_title, option_name, option_value) SELECT '是否启用注册','ENABLED_REGISTER','false' WHERE NOT exists(SELECT * FROM md_options WHERE option_name = 'ENABLED_REGISTER');`).Exec()
 
 	if err != nil {
@@ -64,6 +63,7 @@ func initialization() {
 		member.Account = "admin"
 		member.Avatar = "/static/images/headimgurl.jpg"
 		member.Password = "123456"
+		member.AuthMethod = "local"
 		member.Role = 0
 		member.Email = "[email protected]"
 

+ 17 - 5
conf/app.conf.example

@@ -77,11 +77,23 @@ cdnimg=
 ################百度地图密钥#################
 baidumapkey=
 
-
-
-
-
-
+################Active Directory/LDAP################
+#是否启用ldap
+ldap_enable=false
+#ldap主机名
+ldap_host=ad.example.com
+#ldap端口
+ldap_port=3268
+#ldap内哪个属性作为用户名
+ldap_attribute=sAMAccountName
+#搜索范围
+ldap_base=DC=example,DC=com
+#第一次绑定ldap用户dn
+ldap_user=CN=ldap helper,OU=example.com,DC=example,DC=com
+#第一次绑定ldap用户密码
+ldap_password=superSecret
+#自动注册用户角色:0 超级管理员 /1 管理员/ 2 普通用户 
+ldap_user_role=2
 
 
 

+ 14 - 3
models/errors.go

@@ -3,12 +3,23 @@ package models
 
 import "errors"
 
-var(
+var (
 	// ErrMemberNoExist 用户不存在.
-	ErrMemberNoExist = errors.New("用户不存在")
+	ErrMemberNoExist  = errors.New("用户不存在")
 	ErrMemberDisabled = errors.New("用户被禁用")
 	// ErrorMemberPasswordError 密码错误.
 	ErrorMemberPasswordError = errors.New("用户密码错误")
+	//ErrorMemberAuthMethodInvalid 不支持此认证方式
+	ErrMemberAuthMethodInvalid = errors.New("不支持此认证方式")
+	//ErrLDAPConnect 无法连接到LDAP服务器
+	ErrLDAPConnect = errors.New("无法连接到LDAP服务器")
+	//ErrLDAPFirstBind 第一次LDAP绑定失败
+	ErrLDAPFirstBind = errors.New("第一次LDAP绑定失败")
+	//ErrLDAPSearch LDAP搜索失败
+	ErrLDAPSearch = errors.New("LDAP搜索失败")
+	//ErrLDAPUserNotFoundOrTooMany
+	ErrLDAPUserNotFoundOrTooMany = errors.New("LDAP用户不存在或者多于一个")
+
 	// ErrDataNotExist 指定的服务已存在.
 	ErrDataNotExist = errors.New("数据不存在")
 
@@ -17,6 +28,6 @@ var(
 
 	ErrPermissionDenied = errors.New("Permission denied")
 
-	ErrCommentClosed = errors.New("评论已关闭")
+	ErrCommentClosed          = errors.New("评论已关闭")
 	ErrCommentContentNotEmpty = errors.New("评论内容不能为空")
 )

+ 133 - 81
models/member.go

@@ -2,45 +2,52 @@
 package models
 
 import (
-	"time"
-	"github.com/astaxie/beego/orm"
-	"github.com/lifei6671/godoc/utils"
-	"github.com/lifei6671/godoc/conf"
-	"github.com/astaxie/beego/logs"
 	"errors"
+	"fmt"
 	"regexp"
 	"strings"
+	"time"
+
+	ldap "gopkg.in/ldap.v2"
+
+	"github.com/astaxie/beego"
+	"github.com/astaxie/beego/logs"
+	"github.com/astaxie/beego/orm"
+	"github.com/lifei6671/godoc/conf"
+	"github.com/lifei6671/godoc/utils"
 )
 
 type Member struct {
-	MemberId int		`orm:"pk;auto;unique;column(member_id)" json:"member_id"`
-	Account string 		`orm:"size(100);unique;column(account)" json:"account"`
-	Password string 	`orm:"size(1000);column(password)" json:"-"`
-	Description string	`orm:"column(description);size(2000)" json:"description"`
-	Email string 		`orm:"size(100);column(email);unique" json:"email"`
-	Phone string 		`orm:"size(255);column(phone);null;default(null)" json:"phone"`
-	Avatar string 		`orm:"size(1000);column(avatar)" json:"avatar"`
+	MemberId int    `orm:"pk;auto;unique;column(member_id)" json:"member_id"`
+	Account  string `orm:"size(100);unique;column(account)" json:"account"`
+	Password string `orm:"size(1000);column(password)" json:"-"`
+	//认证方式: local 本地数据库 /ldap LDAP
+	AuthMethod  string `orm:"size(10);column(auth_method);default(local)" json:"auth_method)"`
+	Description string `orm:"column(description);size(2000)" json:"description"`
+	Email       string `orm:"size(100);column(email);unique" json:"email"`
+	Phone       string `orm:"size(255);column(phone);null;default(null)" json:"phone"`
+	Avatar      string `orm:"size(1000);column(avatar)" json:"avatar"`
 	//用户角色:0 超级管理员 /1 管理员/ 2 普通用户 .
-	Role int		`orm:"column(role);type(int);default(1);index" json:"role"`
-	RoleName string 	`orm:"-" json:"role_name"`
-	Status int 		`orm:"column(status);type(int);default(0)" json:"status"`	//用户状态:0 正常/1 禁用
-	CreateTime time.Time	`orm:"type(datetime);column(create_time);auto_now_add" json:"create_time"`
-	CreateAt int		`orm:"type(int);column(create_at)" json:"create_at"`
-	LastLoginTime time.Time	`orm:"type(datetime);column(last_login_time);null" json:"last_login_time"`
-
+	Role          int       `orm:"column(role);type(int);default(1);index" json:"role"`
+	RoleName      string    `orm:"-" json:"role_name"`
+	Status        int       `orm:"column(status);type(int);default(0)" json:"status"` //用户状态:0 正常/1 禁用
+	CreateTime    time.Time `orm:"type(datetime);column(create_time);auto_now_add" json:"create_time"`
+	CreateAt      int       `orm:"type(int);column(create_at)" json:"create_at"`
+	LastLoginTime time.Time `orm:"type(datetime);column(last_login_time);null" json:"last_login_time"`
 }
 
 // TableName 获取对应数据库表名.
 func (m *Member) TableName() string {
 	return "members"
 }
+
 // TableEngine 获取数据使用的引擎.
 func (m *Member) TableEngine() string {
 	return "INNODB"
 }
 
-func (m *Member)TableNameWithPrefix() string {
-	return conf.GetDatabasePrefix() +  m.TableName()
+func (m *Member) TableNameWithPrefix() string {
+	return conf.GetDatabasePrefix() + m.TableName()
 }
 
 func NewMember() *Member {
@@ -48,164 +55,209 @@ func NewMember() *Member {
 }
 
 // Login 用户登录.
-func (m *Member) Login(account string,password string) (*Member,error) {
+func (m *Member) Login(account string, password string) (*Member, error) {
 	o := orm.NewOrm()
 
 	member := &Member{}
 
-	err := o.QueryTable(m.TableNameWithPrefix()).Filter("account",account).Filter("status",0).One(member);
+	err := o.QueryTable(m.TableNameWithPrefix()).Filter("account", account).Filter("status", 0).One(member)
 
 	if err != nil {
-		logs.Error("用户登录 => ",err)
-		return  member,ErrMemberNoExist
+		if beego.AppConfig.DefaultBool("ldap_enable", false) == true {
+			logs.Info("转入LDAP登陆")
+			return member.ldapLogin(account, password)
+		} else {
+			logs.Error("用户登录 => ", err)
+			return member, ErrMemberNoExist
+		}
+	}
+
+	switch member.AuthMethod {
+	case "local":
+		ok, err := utils.PasswordVerify(member.Password, password)
+		if ok && err == nil {
+			m.ResolveRoleName()
+			return member, nil
+		}
+	case "ldap":
+		return member.ldapLogin(account, password)
+	default:
+		return member, ErrMemberAuthMethodInvalid
 	}
 
-	ok,err := utils.PasswordVerify(member.Password,password) ;
+	return member, ErrorMemberPasswordError
+}
 
-	if ok && err == nil {
+//ldapLogin 通过LDAP登陆
+func (m *Member) ldapLogin(account string, password string) (*Member, error) {
+	if beego.AppConfig.DefaultBool("ldap_enable", false) == false {
+		return m, ErrMemberAuthMethodInvalid
+	}
+	var err error
+	lc, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", beego.AppConfig.String("ldap_host"), beego.AppConfig.DefaultInt("ldap_port", 3268)))
+	if err != nil {
+		return m, ErrLDAPConnect
+	}
+	defer lc.Close()
+	err = lc.Bind(beego.AppConfig.String("ldap_user"), beego.AppConfig.String("ldap_password"))
+	if err != nil {
+		return m, ErrLDAPFirstBind
+	}
+	searchRequest := ldap.NewSearchRequest(
+		beego.AppConfig.String("ldap_base"),
+		ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
+		fmt.Sprintf("(&(objectClass=User)(%s=%s))", beego.AppConfig.String("ldap_attribute"), account),
+		[]string{"dn", "mail"},
+		nil,
+	)
+	searchResult, err := lc.Search(searchRequest)
+	if err != nil {
+		return m, ErrLDAPSearch
+	}
+	if len(searchResult.Entries) != 1 {
+		return m, ErrLDAPUserNotFoundOrTooMany
+	}
+	userdn := searchResult.Entries[0].DN
+	err = lc.Bind(userdn, password)
+	if err != nil {
+		return m, ErrorMemberPasswordError
+	}
+	if m.Account == "" {
+		m.Account = account
+		m.Email = searchResult.Entries[0].GetAttributeValue("mail")
+		m.AuthMethod = "ldap"
+		m.Avatar = "/static/images/headimgurl.jpg"
+		m.Role = beego.AppConfig.DefaultInt("ldap_user_role", 2)
+		err = m.Add()
+		if err != nil {
+			logs.Error("自动注册LDAP用户错误", err)
+			return m, ErrorMemberPasswordError
+		}
 		m.ResolveRoleName()
-		return member,nil
 	}
-
-	return member,ErrorMemberPasswordError
+	return m, nil
 }
 
 // Add 添加一个用户.
-func (m *Member) Add () (error) {
+func (m *Member) Add() error {
 	o := orm.NewOrm()
 
-	if ok,err := regexp.MatchString(conf.RegexpAccount,m.Account); m.Account == "" || !ok || err != nil {
+	if ok, err := regexp.MatchString(conf.RegexpAccount, m.Account); m.Account == "" || !ok || err != nil {
 		return errors.New("账号只能由英文字母数字组成,且在3-50个字符")
 	}
 	if m.Email == "" {
 		return errors.New("邮箱不能为空")
 	}
-	if  ok,err := regexp.MatchString(conf.RegexpEmail,m.Email); !ok || err != nil || m.Email == "" {
+	if ok, err := regexp.MatchString(conf.RegexpEmail, m.Email); !ok || err != nil || m.Email == "" {
 		return errors.New("邮箱格式不正确")
 	}
-	if l :=  strings.Count(m.Password,""); l < 6 || l >= 50{
-		return errors.New("密码不能为空且必须在6-50个字符之间")
+	if m.AuthMethod == "local" {
+		if l := strings.Count(m.Password, ""); l < 6 || l >= 50 {
+			return errors.New("密码不能为空且必须在6-50个字符之间")
+		}
 	}
-	if c,err :=  o.QueryTable(m.TableNameWithPrefix()).Filter("email",m.Email).Count(); err == nil && c > 0 {
-		return  errors.New("邮箱已被使用")
+	if c, err := o.QueryTable(m.TableNameWithPrefix()).Filter("email", m.Email).Count(); err == nil && c > 0 {
+		return errors.New("邮箱已被使用")
 	}
 
-	hash ,err := utils.PasswordHash(m.Password);
+	hash, err := utils.PasswordHash(m.Password)
 
-	if  err != nil {
+	if err != nil {
 		return err
 	}
 
 	m.Password = hash
 
-	_,err = o.Insert(m)
+	_, err = o.Insert(m)
 
 	if err != nil {
 		return err
 	}
 	m.ResolveRoleName()
-	return  nil
+	return nil
 }
 
 // Update 更新用户信息.
-func (m *Member) Update(cols... string) (error) {
+func (m *Member) Update(cols ...string) error {
 	o := orm.NewOrm()
 
 	if m.Email == "" {
 		return errors.New("邮箱不能为空")
 	}
-	if _,err := o.Update(m,cols...);err != nil {
+	if _, err := o.Update(m, cols...); err != nil {
 		return err
 	}
 	return nil
 }
 
-func (m *Member) Find(id int) (*Member,error){
+func (m *Member) Find(id int) (*Member, error) {
 	o := orm.NewOrm()
 
 	m.MemberId = id
 	if err := o.Read(m); err != nil {
-		return  m,err
+		return m, err
 	}
 	m.ResolveRoleName()
-	return m,nil
+	return m, nil
 }
 
-func (m *Member) ResolveRoleName (){
+func (m *Member) ResolveRoleName() {
 	if m.Role == conf.MemberSuperRole {
 		m.RoleName = "超级管理员"
-	}else if m.Role == conf.MemberAdminRole {
+	} else if m.Role == conf.MemberAdminRole {
 		m.RoleName = "管理员"
-	}else if m.Role == conf.MemberGeneralRole {
+	} else if m.Role == conf.MemberGeneralRole {
 		m.RoleName = "普通用户"
 	}
 }
 
-func (m *Member) FindByAccount (account string) (*Member,error)  {
+func (m *Member) FindByAccount(account string) (*Member, error) {
 	o := orm.NewOrm()
 
-	err := o.QueryTable(m.TableNameWithPrefix()).Filter("account",account).One(m)
+	err := o.QueryTable(m.TableNameWithPrefix()).Filter("account", account).One(m)
 
 	if err == nil {
 		m.ResolveRoleName()
 	}
-	return m,err
+	return m, err
 }
 
-func (m *Member) FindToPager(pageIndex, pageSize int) ([]*Member,int64,error)  {
+func (m *Member) FindToPager(pageIndex, pageSize int) ([]*Member, int64, error) {
 	o := orm.NewOrm()
 
 	var members []*Member
 
 	offset := (pageIndex - 1) * pageSize
 
-	totalCount,err := o.QueryTable(m.TableNameWithPrefix()).Count()
+	totalCount, err := o.QueryTable(m.TableNameWithPrefix()).Count()
 
 	if err != nil {
-		return members,0,err
+		return members, 0, err
 	}
 
-	_,err = o.QueryTable(m.TableNameWithPrefix()).OrderBy("-member_id").Offset(offset).Limit(pageSize).All(&members)
+	_, err = o.QueryTable(m.TableNameWithPrefix()).OrderBy("-member_id").Offset(offset).Limit(pageSize).All(&members)
 
 	if err != nil {
-		return members,0,err
+		return members, 0, err
 	}
 
-	for _,m := range members {
+	for _, m := range members {
 		m.ResolveRoleName()
 	}
-	return members,totalCount,nil
+	return members, totalCount, nil
 }
 
-
 func (c *Member) IsAdministrator() bool {
-	if c == nil || c.MemberId <= 0{
+	if c == nil || c.MemberId <= 0 {
 		return false
 	}
 	return c.Role == 0 || c.Role == 1
 }
 
-func (m *Member) FindByFieldFirst(field string,value interface{}) (*Member,error)  {
+func (m *Member) FindByFieldFirst(field string, value interface{}) (*Member, error) {
 	o := orm.NewOrm()
 
-	err := o.QueryTable(m.TableNameWithPrefix()).Filter(field,value).OrderBy("-member_id").One(m)
+	err := o.QueryTable(m.TableNameWithPrefix()).Filter(field, value).OrderBy("-member_id").One(m)
 
-	return m,err
+	return m, err
 }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-