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 }