github.com/tommi2day/gomodules@v1.13.2-0.20240423190010-b7d55d252a27/ldaplib/ldap.go (about)

     1  // Package ldaplib collects ldap related functions
     2  package ldaplib
     3  
     4  import (
     5  	"crypto/tls"
     6  	"fmt"
     7  	"strings"
     8  	"time"
     9  
    10  	ldap "github.com/go-ldap/ldap/v3"
    11  )
    12  
    13  // LdapConfigType helds config properties
    14  type LdapConfigType struct {
    15  	Server   string
    16  	Port     int
    17  	URL      string
    18  	TLS      bool
    19  	Insecure bool
    20  	BaseDN   string
    21  	Timeout  int // in second
    22  	Conn     *ldap.Conn
    23  }
    24  
    25  // NewConfig defines common connection parameter
    26  func NewConfig(server string, port int, tls bool, insecure bool, basedn string, timeout int) *LdapConfigType {
    27  	ldapConfig := LdapConfigType{}
    28  	if port == 0 {
    29  		if tls {
    30  			port = 636
    31  		} else {
    32  			port = 389
    33  		}
    34  	}
    35  	ldapConfig.Server = server
    36  	ldapConfig.Port = port
    37  	ldapConfig.TLS = tls
    38  	ldapConfig.Insecure = insecure
    39  	ldapConfig.URL = fmt.Sprintf("ldap://%s:%d", ldapConfig.Server, ldapConfig.Port)
    40  	if tls {
    41  		ldapConfig.URL = fmt.Sprintf("ldaps://%s:%d", ldapConfig.Server, ldapConfig.Port)
    42  	}
    43  	ldapConfig.BaseDN = basedn
    44  	ldapConfig.Timeout = timeout
    45  	return &ldapConfig
    46  }
    47  
    48  // Connect will authorize to the ldap server
    49  func (lc *LdapConfigType) Connect(bindDN string, bindPassword string) (err error) {
    50  	l := lc.Conn
    51  	if l != nil {
    52  		_ = l.Close()
    53  		l = nil
    54  	}
    55  
    56  	// set timeout
    57  	ldap.DefaultTimeout = time.Duration(lc.Timeout) * time.Second
    58  
    59  	// You can also use IP instead of FQDN
    60  	if lc.Insecure {
    61  		//nolint gosec
    62  		l, err = ldap.DialURL(lc.URL, ldap.DialWithTLSConfig(&tls.Config{InsecureSkipVerify: true}))
    63  	} else {
    64  		l, err = ldap.DialURL(lc.URL)
    65  	}
    66  
    67  	if err != nil {
    68  		return
    69  	}
    70  	if len(bindDN) == 0 {
    71  		err = l.UnauthenticatedBind("")
    72  	} else {
    73  		err = l.Bind(bindDN, bindPassword)
    74  	}
    75  	if err != nil {
    76  		return
    77  	}
    78  	lc.Conn = l
    79  	return
    80  }
    81  
    82  // Search do a search on ldap
    83  func (lc *LdapConfigType) Search(baseDN string, filter string, attributes []string, scope int, deref int) (entries []*ldap.Entry, err error) {
    84  	var result *ldap.SearchResult
    85  	l := lc.Conn
    86  	if l == nil {
    87  		err = fmt.Errorf("ldap search: not connected")
    88  		return
    89  	}
    90  	searchReq := ldap.NewSearchRequest(
    91  		baseDN,
    92  		scope, // https://pkg.go.dev/github.com/go-ldap/ldap/v3@v3.4.4#ScopeWholeSubtree
    93  		deref, //https://pkg.go.dev/github.com/go-ldap/ldap/v3@v3.4.4#DerefInSearching
    94  		0,
    95  		0,
    96  		false,
    97  		filter,
    98  		attributes,
    99  		nil,
   100  	)
   101  
   102  	result, err = l.Search(searchReq)
   103  	if err != nil {
   104  		if ldap.IsErrorWithCode(err, 32) {
   105  			return nil, nil
   106  		}
   107  		return nil, err
   108  	}
   109  	entries = result.Entries
   110  	return
   111  }
   112  
   113  // DeleteEntry deletes given DN from Ldap
   114  func (lc *LdapConfigType) DeleteEntry(dn string) (err error) {
   115  	l := lc.Conn
   116  	if l == nil {
   117  		err = fmt.Errorf("ldap delete: not connected")
   118  		return
   119  	}
   120  	if len(dn) == 0 {
   121  		err = fmt.Errorf("ldap delete dn empty")
   122  		return
   123  	}
   124  	req := ldap.NewDelRequest(dn, nil)
   125  	err = l.Del(req)
   126  	return
   127  }
   128  
   129  // AddEntry creates a new Entry
   130  func (lc *LdapConfigType) AddEntry(dn string, attr []ldap.Attribute) (err error) {
   131  	l := lc.Conn
   132  	if l == nil {
   133  		err = fmt.Errorf("ldap add: not connected")
   134  		return
   135  	}
   136  	if len(dn) == 0 {
   137  		err = fmt.Errorf("ldap add: dn empty")
   138  		return
   139  	}
   140  	if len(attr) == 0 {
   141  		err = fmt.Errorf("ldap add: attributes empty")
   142  		return
   143  	}
   144  	req := ldap.NewAddRequest(dn, nil)
   145  	for _, a := range attr {
   146  		req.Attribute(a.Type, a.Vals)
   147  	}
   148  	err = l.Add(req)
   149  	return
   150  }
   151  
   152  // ModifyAttribute add, replaces or deletes one Attribute of an Entry
   153  func (lc *LdapConfigType) ModifyAttribute(dn string, modtype string, name string, values []string) (err error) {
   154  	l := lc.Conn
   155  	if l == nil {
   156  		err = fmt.Errorf("ldap modify: not connected")
   157  		return
   158  	}
   159  	if len(dn) == 0 {
   160  		err = fmt.Errorf("ldap modify: dn empty")
   161  		return
   162  	}
   163  	if len(name) == 0 {
   164  		err = fmt.Errorf("ldap modify: attribute name empty")
   165  		return
   166  	}
   167  	if len(values) == 0 {
   168  		err = fmt.Errorf("ldap modify: values empty")
   169  		return
   170  	}
   171  	req := ldap.NewModifyRequest(dn, nil)
   172  	switch modtype {
   173  	case "add":
   174  		req.Add(name, values)
   175  	case "modify":
   176  		req.Replace(name, values)
   177  	case "replace":
   178  		req.Replace(name, values)
   179  	case "delete":
   180  		req.Delete(name, values)
   181  	case "increment":
   182  		req.Increment(name, values[0])
   183  	default:
   184  		err = fmt.Errorf("ldap modify unknow type %s", modtype)
   185  		return
   186  	}
   187  	err = l.Modify(req)
   188  	return
   189  }
   190  
   191  // SetPassword changes an existing password to the given or generated value
   192  func (lc *LdapConfigType) SetPassword(dn string, oldPass string, newPass string) (generatedPass string, err error) {
   193  	// all parameter can be empty
   194  	l := lc.Conn
   195  	if l == nil {
   196  		err = fmt.Errorf("ldap delete: not connected")
   197  		return
   198  	}
   199  	passwdModReq := ldap.NewPasswordModifyRequest(dn, oldPass, newPass)
   200  	passwdModResp, err := l.PasswordModify(passwdModReq)
   201  	if err != nil {
   202  		return
   203  	}
   204  	if newPass == "" {
   205  		generatedPass = passwdModResp.GeneratedPassword
   206  	}
   207  	return
   208  }
   209  
   210  // RetrieveEntry returns the first entry found for the given DN
   211  func (lc *LdapConfigType) RetrieveEntry(dn string, filter string, fields string) (entry *ldap.Entry, err error) {
   212  	if len(dn) == 0 {
   213  		err = fmt.Errorf("ldap lookup: dn empty")
   214  		return
   215  	}
   216  	if len(filter) == 0 {
   217  		filter = "(objectclass=*)"
   218  	}
   219  	f := strings.Split(fields, ",")
   220  	if f[0] == "" {
   221  		f = []string{"*"}
   222  	}
   223  	entries, err := lc.Search(dn, filter, f, ldap.ScopeBaseObject, ldap.DerefInSearching)
   224  	if err != nil {
   225  		err = fmt.Errorf("ldap search for %s returned error %v", dn, err)
   226  		return
   227  	}
   228  	if len(entries) == 0 {
   229  		err = fmt.Errorf("ldap search for %s returned no entry", dn)
   230  		return
   231  	}
   232  	entry = entries[0]
   233  	return
   234  }
   235  
   236  // HasAttribute checks if the given entry has the given attribute
   237  func HasAttribute(entry *ldap.Entry, attribute string) bool {
   238  	if entry == nil {
   239  		return false
   240  	}
   241  	if len(entry.Attributes) == 0 {
   242  		return false
   243  	}
   244  	attribute = strings.ToLower(attribute)
   245  	for _, a := range entry.Attributes {
   246  		name := strings.ToLower(a.Name)
   247  		if name == attribute {
   248  			return true
   249  		}
   250  	}
   251  	return false
   252  }
   253  
   254  // HasObjectClass checks if the given entry has the given objectClass
   255  func HasObjectClass(entry *ldap.Entry, objectClass string) bool {
   256  	if entry == nil {
   257  		return false
   258  	}
   259  	if len(entry.Attributes) == 0 {
   260  		return false
   261  	}
   262  	objectClass = strings.ToLower(objectClass)
   263  	oc := entry.GetAttributeValues("objectClass")
   264  	for _, c := range oc {
   265  		c = strings.ToLower(c)
   266  		if c == objectClass {
   267  			return true
   268  		}
   269  	}
   270  	return false
   271  }