3、Go语言操作LDAP

注:因为工作,需要使用LDAP,所以特意查看了go语言的官方包,因为使用的beego框架,所以LDAP初始化的数据都保存在app.conf文件里

app.conf 文件里面的初始化数据如下:

[auth.ldap]
enabled = false
ldap_url = ldap://127.0.0.1
ldap_search_dn = "cn=Manager,dc=my-domain,dc=com"
ldap_search_password = 123456
ldap_base_dn = "dc=my-domain,dc=com"
ldap_filter =
ldap_uid = uid
ldap_scope = 3
ldap_connection_timeout = 30
package ldap

//此结构体的数据一般来自前端传入的数据 用户名和密码
type AuthModel struct { 
	Username string
	Password string
}

type LDAPAuth struct{}

const metaChars = "&|!=~*<>()"

func (*LDAPAuth) Authenticate(m models.AuthModel) (*models.User, error) {
	u := m.Username
	if len(strings.TrimSpace(u)) == 0 {
		logs.Info("LDAP authentication failed for empty user id.")
		return nil, nil
	}
	// 用户名不允许包含特殊字段
	for _, c := range metaChars {
		if strings.ContainsRune(u, c) {
			return nil, fmt.Errorf("the principal contains meta char: %q", c)
		}
	}

	ldapConfs := models.LdapConf{}
    // 获取LDAP初始化数据 
 	section, err := beego.AppConfig.GetSection("auth.ldap")
	if err != nil {
		return nil, fmt.Errorf("Can't find ldap config. ")
	}

	ldapConfs.LdapURL = section["ldap_url"]
	ldapConfs.LdapSearchDn = section["ldap_search_dn"]
	ldapConfs.LdapSearchPassword = section["ldap_search_password"]
	ldapConfs.LdapBaseDn = section["ldap_base_dn"]
	ldapConfs.LdapFilter = section["ldap_filter"]
	ldapConfs.LdapUID = section["ldap_uid"]
	ldapScope, err := strconv.ParseInt(section["ldap_scope"], 10, 64)
	if err != nil {
		return nil, fmt.Errorf("ldap_scope parse error, must be int. ")
	}
	ldapConnectionTimeout, err := strconv.ParseInt(section["ldap_connection_timeout"], 10, 64)
	if err != nil {
		return nil, fmt.Errorf("ldap_connection_timeout parse error, must be int. ")
	}
	ldapConfs.LdapScope = int(ldapScope)
	ldapConfs.LdapConnectionTimeout = int(ldapConnectionTimeout)
	ldapConfs, err = ldapUtils.ValidateLdapConf(ldapConfs)

	if err != nil {
		return nil, fmt.Errorf("invalid ldap request: %v", err)
	}
	// 设置LDAP 里面的Filter
	ldapConfs.LdapFilter = ldapUtils.MakeFilter(u, ldapConfs.LdapFilter, ldapConfs.LdapUID)
	ldapUsers, err := ldapUtils.SearchUser(ldapConfs)

	if err != nil {
		logs.Warning("ldap search fail: %v", err)
		return nil, err
	}

	if len(ldapUsers) == 0 {
		logs.Warning("Not found an entry.")
		return nil, fmt.Errorf("Not found an entry. ")
	} else if len(ldapUsers) != 1 {
		logs.Warning("Found more than one entry.")
		return nil, fmt.Errorf("Found more than one entry. ")
	}

	
	fmt.Println(ldapUsers[0].Username) 
	fmt.Println(ldapUsers[0].Email) 
	fmt.Println(ldapUsers[0].Realname) 
	dn := ldapUsers[0].DN
	// 这一步验证用户的输入的密码和LDAP里面保存的密码是否一致
	if err := ldapUtils.Bind(ldapConfs, dn, m.Password); err != nil {
		logs.Warning("Failed to bind user, username: %s, dn: %s, error: %v", user.Name, dn, err)
		return nil, fmt.Errorf("Failed to bind user, username: %s, dn: %s, error: %v ", user.Name, dn, err)
	}

	return user, nil
}
// 这个是我写的接口,可以直接使用对应的函数,代码逻辑非常简单
package ldap

import (
	goldap "gopkg.in/ldap.v2"
)

// ValidateLdapConf ...
func ValidateLdapConf(ldapConfs models.LdapConf) (models.LdapConf, error) {
	var err error

	if ldapConfs.LdapURL == "" {
		return ldapConfs, fmt.Errorf("can not get any available LDAP_URL")
	}

	ldapConfs.LdapURL, err = formatLdapURL(ldapConfs.LdapURL)

	if err != nil {
		logs.Error("invalid LdapURL format, error: %v", err)
		return ldapConfs, err
	}

	// Compatible with legacy codes
	// in previous harbor.cfg:
	// the scope to search for users, 1-LDAP_SCOPE_BASE, 2-LDAP_SCOPE_ONELEVEL, 3-LDAP_SCOPE_SUBTREE
	switch ldapConfs.LdapScope {
	case 1:
		ldapConfs.LdapScope = goldap.ScopeBaseObject
	case 2:
		ldapConfs.LdapScope = goldap.ScopeSingleLevel
	case 3:
		ldapConfs.LdapScope = goldap.ScopeWholeSubtree
	default:
		return ldapConfs, fmt.Errorf("invalid ldap search scope")
	}

	//	value := reflect.ValueOf(ldapConfs)
	//	lType := reflect.TypeOf(ldapConfs)
	//	for i := 0; i < value.NumField(); i++ {
	//		fmt.Printf("Field %d: %v %v\n", i, value.Field(i), lType.Field(i).Name)
	//	}

	return ldapConfs, nil

}

func formatLdapURL(ldapURL string) (string, error) {

	var protocol, hostport string
	var err error

	_, err = url.Parse(ldapURL)
	if err != nil {
		return "", fmt.Errorf("parse Ldap Host ERR: %s", err)
	}

	if strings.Contains(ldapURL, "://") {
		splitLdapURL := strings.Split(ldapURL, "://")
		protocol, hostport = splitLdapURL[0], splitLdapURL[1]
		if !((protocol == "ldap") || (protocol == "ldaps")) {
			return "", fmt.Errorf("unknown ldap protocol")
		}
	} else {
		hostport = ldapURL
		protocol = "ldap"
	}

	if strings.Contains(hostport, ":") {
		splitHostPort := strings.Split(hostport, ":")
		port, error := strconv.Atoi(splitHostPort[1])
		if error != nil {
			return "", fmt.Errorf("illegal url port")
		}
		if port == 636 {
			protocol = "ldaps"
		}

	} else {
		switch protocol {
		case "ldap":
			hostport = hostport + ":389"
		case "ldaps":
			hostport = hostport + ":636"
		}
	}

	fLdapURL := protocol + "://" + hostport

	return fLdapURL, nil

}

// Bind establish a connection to ldap based on ldapConfs and bind the user with given parameters.
func Bind(ldapConfs models.LdapConf, dn string, password string) error {
	conn, err := dialLDAP(ldapConfs)
	if err != nil {
		return err
	}
	defer conn.Close()
	if ldapConfs.LdapSearchDn != "" {
		if err := bindLDAPSearchDN(ldapConfs, conn); err != nil {
			return err
		}
	}
	return conn.Bind(dn, password)
}

// MakeFilter ...
func MakeFilter(username string, ldapFilter string, ldapUID string) string {

	var filterTag string

	if username == "" {
		filterTag = "*"
	} else {
		filterTag = username
	}

	if ldapFilter == "" {
		ldapFilter = "(" + ldapUID + "=" + filterTag + ")"
	} else {
		if !strings.Contains(ldapFilter, ldapUID+"=") {
			ldapFilter = "(&" + ldapFilter + "(" + ldapUID + "=" + filterTag + "))"
		} else {
			ldapFilter = strings.Replace(ldapFilter, ldapUID+"=*", ldapUID+"="+filterTag, -1)
		}
	}

	return ldapFilter
}

// SearchUser ...
func SearchUser(ldapConfs models.LdapConf) ([]models.LdapUser, error) {
	var ldapUsers []models.LdapUser
	var ldapConn *goldap.Conn
	var err error

	ldapConn, err = dialLDAP(ldapConfs)

	if err != nil {
		return nil, err
	}
	defer ldapConn.Close()

	if ldapConfs.LdapSearchDn != "" {
		err = bindLDAPSearchDN(ldapConfs, ldapConn)
		if err != nil {
			return nil, err
		}
	}

	if ldapConfs.LdapBaseDn == "" {
		return nil, fmt.Errorf("can not get any available LDAP_BASE_DN")
	}

	result, err := searchLDAP(ldapConfs, ldapConn)

	if err != nil {
		return nil, err
	}

	for _, ldapEntry := range result.Entries {
		var u models.LdapUser
		for _, attr := range ldapEntry.Attributes {
			val := attr.Values[0]
			logs.Info("Current ldap entry attr name: %s\n", attr.Name)
			switch strings.ToLower(attr.Name) {
			case strings.ToLower(ldapConfs.LdapUID):
				u.Username = val
			case "uid":
				u.Realname = val
			case "cn":
				u.Realname = val
			case "mail":
				u.Email = val
			case "email":
				u.Email = val
			}
		}
		u.DN = ldapEntry.DN
		ldapUsers = append(ldapUsers, u)
	}

	return ldapUsers, nil
}

func dialLDAP(ldapConfs models.LdapConf) (*goldap.Conn, error) {

	var err error
	var ldap *goldap.Conn
	splitLdapURL := strings.Split(ldapConfs.LdapURL, "://")
	protocol, hostport := splitLdapURL[0], splitLdapURL[1]

	// Sets a Dial Timeout for LDAP
	connectionTimeout := ldapConfs.LdapConnectionTimeout
	goldap.DefaultTimeout = time.Duration(connectionTimeout) * time.Second

	switch protocol {
	case "ldap":
		ldap, err = goldap.Dial("tcp", hostport)
	case "ldaps":
		ldap, err = goldap.DialTLS("tcp", hostport, &tls.Config{InsecureSkipVerify: true})
	}

	return ldap, err
}

func bindLDAPSearchDN(ldapConfs models.LdapConf, ldap *goldap.Conn) error {

	var err error

	ldapSearchDn := ldapConfs.LdapSearchDn
	ldapSearchPassword := ldapConfs.LdapSearchPassword

	err = ldap.Bind(ldapSearchDn, ldapSearchPassword)
	if err != nil {
		logs.Info("Bind search dn error", err)
		return err
	}

	return nil
}

func searchLDAP(ldapConfs models.LdapConf, ldap *goldap.Conn) (*goldap.SearchResult, error) {

	var err error
	ldapBaseDn := ldapConfs.LdapBaseDn
	ldapScope := ldapConfs.LdapScope
	ldapFilter := ldapConfs.LdapFilter

	attributes := []string{"uid", "cn", "mail", "email"}
	lowerUID := strings.ToLower(ldapConfs.LdapUID)
	if lowerUID != "uid" && lowerUID != "cn" && lowerUID != "mail" && lowerUID != "email" {
		attributes = append(attributes, ldapConfs.LdapUID)
	}
	searchRequest := goldap.NewSearchRequest(
		ldapBaseDn,
		ldapScope,
		goldap.NeverDerefAliases,
		0,     // Unlimited results.
		0,     // Search Timeout.
		false, // Types Only
		ldapFilter,
		attributes,
		nil,
	)

	result, err := ldap.Search(searchRequest)

	if err != nil {
		logs.Info("LDAP search error", err)
		return nil, err
	}

	return result, nil
}

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值