Add POST, PUT, DELETE
This commit is contained in:
parent
447bdd1128
commit
6c73cc8b76
180
ldap.go
180
ldap.go
@ -25,6 +25,28 @@ var (
|
|||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func marshalResultToStrMap(sr *ldap.SearchResult) map[string][]string {
|
||||||
|
var res = make(map[string][]string)
|
||||||
|
for _, e := range sr.Entries {
|
||||||
|
for _, a := range e.Attributes {
|
||||||
|
for _, v := range a.Values {
|
||||||
|
res[a.Name] = append(res[a.Name], v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func isStringInArray(strarr []string, searched string) bool {
|
||||||
|
for _, s := range strarr {
|
||||||
|
if strings.EqualFold(s, searched) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func connectLdap(myldap *MyLdap) (*MyLdap, error) {
|
func connectLdap(myldap *MyLdap) (*MyLdap, error) {
|
||||||
var err error
|
var err error
|
||||||
var conLdap *MyLdap
|
var conLdap *MyLdap
|
||||||
@ -43,7 +65,7 @@ func connectLdap(myldap *MyLdap) (*MyLdap, error) {
|
|||||||
return myldap, err
|
return myldap, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func ldapSearch(conLdap *MyLdap, searchReq *ldap.SearchRequest, attempt int) (*ldap.SearchResult, error) {
|
func internalLdapSearch(conLdap *MyLdap, searchReq *ldap.SearchRequest, attempt int) (*ldap.SearchResult, error) {
|
||||||
var err error
|
var err error
|
||||||
if conLdap.Conn == nil {
|
if conLdap.Conn == nil {
|
||||||
conLdap, err = connectLdap(conLdap)
|
conLdap, err = connectLdap(conLdap)
|
||||||
@ -65,30 +87,40 @@ func ldapSearch(conLdap *MyLdap, searchReq *ldap.SearchRequest, attempt int) (*l
|
|||||||
return result, err
|
return result, err
|
||||||
} else {
|
} else {
|
||||||
attempt = attempt + 1
|
attempt = attempt + 1
|
||||||
return ldapSearch(conLdap, searchReq, attempt)
|
return internalLdapSearch(conLdap, searchReq, attempt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func doLdapSearch(myldap *MyLdap, baseDn, cn, class, attributes string) (*ldap.SearchResult, error) {
|
func searchByCn(myldap *MyLdap, baseDn, cn, class, attributes string) (*ldap.SearchResult, error) {
|
||||||
var filter string
|
var filter string
|
||||||
|
if false == strings.EqualFold(cn, "ALL") {
|
||||||
|
filter = fmt.Sprintf("(cn=%s)", cn)
|
||||||
|
} else {
|
||||||
|
filter = cn
|
||||||
|
}
|
||||||
|
return doLdapSearch(myldap, baseDn, filter, class, attributes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doLdapSearch(myldap *MyLdap, baseDn, filter, class, attributes string) (*ldap.SearchResult, error) {
|
||||||
|
var fFilter string
|
||||||
var realBaseDn string
|
var realBaseDn string
|
||||||
var realAttributes []string
|
var realAttributes []string
|
||||||
|
|
||||||
// Build search filter
|
// Build final search filter
|
||||||
if strings.EqualFold(class, "ALL") {
|
if strings.EqualFold(class, "ALL") {
|
||||||
filter = fmt.Sprintf("(objectClass=*)")
|
fFilter = fmt.Sprintf("(objectClass=*)")
|
||||||
} else {
|
} else {
|
||||||
filter = fmt.Sprintf("(objectClass=%s)", class)
|
fFilter = fmt.Sprintf("(objectClass=%s)", class)
|
||||||
}
|
}
|
||||||
|
|
||||||
if false == strings.EqualFold(cn, "ALL") {
|
if false == strings.EqualFold(filter, "ALL") {
|
||||||
filter = fmt.Sprintf("(&%s(cn=%s))", filter, ldap.EscapeFilter(cn))
|
fFilter = fmt.Sprintf("(&%s(%s))", fFilter, ldap.EscapeFilter(filter))
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("LDAP search filter: %s", filter)
|
log.Debugf("LDAP search filter: %s", fFilter)
|
||||||
|
|
||||||
// Build absolute search base DN from configuration & provided DN (which is relative)
|
// Build absolute search base DN from configuration & provided DN (which is relative)
|
||||||
if strings.EqualFold(baseDn, "ALL") || len(baseDn) == 0 {
|
if strings.EqualFold(baseDn, "ALL") || len(baseDn) == 0 {
|
||||||
@ -109,9 +141,9 @@ func doLdapSearch(myldap *MyLdap, baseDn, cn, class, attributes string) (*ldap.S
|
|||||||
log.Debugf("LDAP search attributes to return (all if empty): %v", realAttributes)
|
log.Debugf("LDAP search attributes to return (all if empty): %v", realAttributes)
|
||||||
|
|
||||||
searchReq := ldap.NewSearchRequest(realBaseDn, ldap.ScopeWholeSubtree, 0, 0, 0,
|
searchReq := ldap.NewSearchRequest(realBaseDn, ldap.ScopeWholeSubtree, 0, 0, 0,
|
||||||
false, filter, realAttributes, []ldap.Control{})
|
false, fFilter, realAttributes, []ldap.Control{})
|
||||||
|
|
||||||
result, err := ldapSearch(myldap, searchReq, 0)
|
result, err := internalLdapSearch(myldap, searchReq, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -120,17 +152,137 @@ func doLdapSearch(myldap *MyLdap, baseDn, cn, class, attributes string) (*ldap.S
|
|||||||
}
|
}
|
||||||
|
|
||||||
func findUserFullDN(myldap *MyLdap, username string) (string, error) {
|
func findUserFullDN(myldap *MyLdap, username string) (string, error) {
|
||||||
sr, err := doLdapSearch(myldap, "", username, "ALL", "")
|
filter := fmt.Sprintf("cn=%s", username)
|
||||||
|
sr, err := doLdapSearch(myldap, "", filter, "ALL", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if len(sr.Entries) == 0 {
|
if len(sr.Entries) == 0 {
|
||||||
return "", fmt.Errorf("User not found with cn=%s", username)
|
return "", fmt.Errorf("User not found with %s", filter)
|
||||||
} else if len(sr.Entries) > 1 {
|
} else if len(sr.Entries) > 1 {
|
||||||
return "", fmt.Errorf("More than one object (%d) found with cn=%s", len(sr.Entries), username)
|
return "", fmt.Errorf("More than one object (%d) found with %s", len(sr.Entries), filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
result := sr.Entries[0].DN
|
result := sr.Entries[0].DN
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createEntry(myldap *MyLdap, dn string, attributes map[string]interface{}) error {
|
||||||
|
log.Debugf("Creating DN %s with attributes %v", dn, attributes)
|
||||||
|
|
||||||
|
addReq := ldap.NewAddRequest(dn, nil)
|
||||||
|
|
||||||
|
// Remove dn from map if exists
|
||||||
|
delete(attributes, "dn")
|
||||||
|
|
||||||
|
//build attributes list
|
||||||
|
for key, val := range attributes {
|
||||||
|
var v []string
|
||||||
|
|
||||||
|
strval := fmt.Sprintf("%s",val)
|
||||||
|
if strings.HasPrefix(strval, "[") && strings.HasSuffix(strval, "]") {
|
||||||
|
for _, va := range val.([]interface{}) {
|
||||||
|
v = append(v, va.(string))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
v = append(v, strval)
|
||||||
|
}
|
||||||
|
|
||||||
|
addReq.Attribute(key, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := myldap.Conn.Add(addReq)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateEntry(myldap *MyLdap, dn string, attributes map[string]interface{}) error {
|
||||||
|
log.Debugf("Updating DN %s with attributes %v", dn, attributes)
|
||||||
|
|
||||||
|
// Remove dn from map if exists
|
||||||
|
delete(attributes, "dn")
|
||||||
|
|
||||||
|
// First get the current object so we can build a list of add, modify, delete attributes
|
||||||
|
// We cant search for a full DN, so separate cn and base dn, then remove basedn as doLdapSearch already append it
|
||||||
|
filter := strings.Split(dn, ",")[0]
|
||||||
|
rem := strings.Split(dn, ",")[1:]
|
||||||
|
bdn := strings.Join(rem, ",")
|
||||||
|
bdn = strings.Replace(bdn, fmt.Sprintf(",%s", myldap.BaseDN), "", 1)
|
||||||
|
sr, err := doLdapSearch(myldap, bdn, filter, "ALL", "ALL")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(sr.Entries) == 0 {
|
||||||
|
return fmt.Errorf("Object %s not found", filter)
|
||||||
|
} else if len(sr.Entries) > 1 {
|
||||||
|
return fmt.Errorf("More than one object (%d) found with %s", len(sr.Entries), filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
actualAttrs := marshalResultToStrMap(sr)
|
||||||
|
|
||||||
|
// convert attributes to map[string][]string
|
||||||
|
var attrsMap = make(map[string][]string)
|
||||||
|
for key, val := range attributes {
|
||||||
|
var v []string
|
||||||
|
|
||||||
|
strval := fmt.Sprintf("%s",val)
|
||||||
|
if strings.HasPrefix(strval, "[") && strings.HasSuffix(strval, "]") {
|
||||||
|
for _, va := range val.([]interface{}) {
|
||||||
|
v = append(v, va.(string))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
v = append(v, strval)
|
||||||
|
}
|
||||||
|
|
||||||
|
attrsMap[key] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
modReq := ldap.NewModifyRequest(dn, nil)
|
||||||
|
|
||||||
|
// Now compare. We browse required attributes to get modify and add operations
|
||||||
|
opnr := 0
|
||||||
|
for k, v := range attrsMap {
|
||||||
|
fv := 0
|
||||||
|
va, found := actualAttrs[k]
|
||||||
|
if found {
|
||||||
|
for _, cv := range va {
|
||||||
|
if isStringInArray(v, cv) {
|
||||||
|
fv += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if fv != len(va) {
|
||||||
|
modReq.Replace(k, v)
|
||||||
|
opnr++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
modReq.Add(k, v)
|
||||||
|
opnr++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// now browse actual attributes to get delete operations
|
||||||
|
for k, v := range actualAttrs {
|
||||||
|
_, found := attrsMap[k]
|
||||||
|
if false == found {
|
||||||
|
modReq.Delete(k, v)
|
||||||
|
opnr++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if opnr == 0 {
|
||||||
|
return fmt.Errorf("No modification required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally execute operations
|
||||||
|
err = myldap.Conn.Modify(modReq)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteEntry(myldap *MyLdap, dn string) error {
|
||||||
|
log.Debugf("Deleting DN %s", dn)
|
||||||
|
|
||||||
|
delReq := ldap.NewDelRequest(dn, nil)
|
||||||
|
|
||||||
|
err := myldap.Conn.Del(delReq)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
122
main.go
122
main.go
@ -21,7 +21,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
gVersion = "0.5.2"
|
gVersion = "0.5.3"
|
||||||
|
|
||||||
gRoLdap *MyLdap
|
gRoLdap *MyLdap
|
||||||
)
|
)
|
||||||
@ -111,7 +111,7 @@ func checkIfModifiedSince(c *gin.Context, myldap *MyLdap, baseDn, cn, class, att
|
|||||||
|
|
||||||
log.Debugf("ifModifiedSince: %s", ifModifiedSince)
|
log.Debugf("ifModifiedSince: %s", ifModifiedSince)
|
||||||
|
|
||||||
res, err := doLdapSearch(myldap, baseDn, cn, class, "modifyTimestamp")
|
res, err := searchByCn(myldap, baseDn, cn, class, "modifyTimestamp")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error searching modifyTimestamp for %s in %s : %v", cn, baseDn, err)
|
log.Errorf("Error searching modifyTimestamp for %s in %s : %v", cn, baseDn, err)
|
||||||
return true, err
|
return true, err
|
||||||
@ -145,7 +145,6 @@ func checkIfModifiedSince(c *gin.Context, myldap *MyLdap, baseDn, cn, class, att
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Basic Authentication handler with local hardcoded account - do not use
|
// Basic Authentication handler with local hardcoded account - do not use
|
||||||
func basicAuth(c *gin.Context) {
|
func basicAuth(c *gin.Context) {
|
||||||
user, password, hasAuth := c.Request.BasicAuth()
|
user, password, hasAuth := c.Request.BasicAuth()
|
||||||
@ -203,7 +202,6 @@ func ldapBasicAuth(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func getLdapConFromContext(c *gin.Context) (*MyLdap, error) {
|
func getLdapConFromContext(c *gin.Context) (*MyLdap, error) {
|
||||||
ldapCon, exist := c.Get("ldapCon")
|
ldapCon, exist := c.Get("ldapCon")
|
||||||
if exist != true {
|
if exist != true {
|
||||||
@ -212,7 +210,6 @@ func getLdapConFromContext(c *gin.Context) (*MyLdap, error) {
|
|||||||
return ldapCon.(*MyLdap), nil
|
return ldapCon.(*MyLdap), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func initRouter(r *gin.Engine) {
|
func initRouter(r *gin.Engine) {
|
||||||
r.GET("/ping", func(c *gin.Context) {
|
r.GET("/ping", func(c *gin.Context) {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
@ -238,7 +235,7 @@ func initRouter(r *gin.Engine) {
|
|||||||
// json format is the default
|
// json format is the default
|
||||||
format := c.DefaultQuery("format", "json")
|
format := c.DefaultQuery("format", "json")
|
||||||
|
|
||||||
res, err := doLdapSearch(ldapCon, ou, cn, class, "ALL")
|
res, err := searchByCn(ldapCon, ou, cn, class, "ALL")
|
||||||
|
|
||||||
// If OU does not exist, we'll get err='LDAP Result Code 32 "No Such Object"'
|
// If OU does not exist, we'll get err='LDAP Result Code 32 "No Such Object"'
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -272,7 +269,7 @@ func initRouter(r *gin.Engine) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if modified {
|
if modified {
|
||||||
res, err := doLdapSearch(ldapCon, ou, cn, class, "ALL")
|
res, err := searchByCn(ldapCon, ou, cn, class, "ALL")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error searching %s in %s : %v", cn, ou, err)
|
log.Errorf("Error searching %s in %s : %v", cn, ou, err)
|
||||||
c.AbortWithError(http.StatusInternalServerError, err)
|
c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
@ -301,7 +298,7 @@ func initRouter(r *gin.Engine) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := doLdapSearch(ldapCon, ou, cn, class, attr)
|
res, err := searchByCn(ldapCon, ou, cn, class, attr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error searching %s in %s : %v", cn, ou, err)
|
log.Errorf("Error searching %s in %s : %v", cn, ou, err)
|
||||||
c.AbortWithError(http.StatusInternalServerError, err)
|
c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
@ -334,7 +331,7 @@ func initRouter(r *gin.Engine) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if modified {
|
if modified {
|
||||||
res, err := doLdapSearch(ldapCon, ou, cn, class, attr)
|
res, err := searchByCn(ldapCon, ou, cn, class, attr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error searching %s in %s : %v", cn, ou, err)
|
log.Errorf("Error searching %s in %s : %v", cn, ou, err)
|
||||||
c.AbortWithError(http.StatusInternalServerError, err)
|
c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
@ -346,6 +343,113 @@ func initRouter(r *gin.Engine) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* curl -u "admin:admin" --header "Content-Type: application/json" -X POST
|
||||||
|
* --data '{"objectClass":["person","top"],"cn":"newuser","sn":"New"}' \
|
||||||
|
* https://localhost:8443/cn=newuser,ou=users,dc=example,dc=org
|
||||||
|
*/
|
||||||
|
r.POST("/:dn", ldapBasicAuth, func(c *gin.Context) {
|
||||||
|
dn := c.Param("dn")
|
||||||
|
|
||||||
|
// Get user authenticated LDAP connection from context
|
||||||
|
ldapCon, err := getLdapConFromContext(c)
|
||||||
|
if err != nil {
|
||||||
|
//log.Error(err)
|
||||||
|
c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshall json body to a map
|
||||||
|
if c.Request.Header.Get("Content-Type") == "application/json" {
|
||||||
|
var attributes map[string]interface{}
|
||||||
|
err := c.ShouldBindJSON(&attributes)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = createEntry(ldapCon, dn, attributes)
|
||||||
|
if err != nil {
|
||||||
|
if strings.Contains(err.Error(), "LDAP Result Code 50") {
|
||||||
|
c.AbortWithStatus(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
c.AbortWithError(http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusCreated, gin.H{"message": "Successfully created"})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* curl -u "admin:admin" --header "Content-Type: application/json" -X PUT
|
||||||
|
* --data '{"objectClass":["person","top"],"cn":"newuser","sn":"New","description":"Test account"}' \
|
||||||
|
* https://localhost:8443/cn=newuser,ou=users,dc=example,dc=org
|
||||||
|
*/
|
||||||
|
r.PUT("/:dn", ldapBasicAuth, func(c *gin.Context) {
|
||||||
|
dn := c.Param("dn")
|
||||||
|
|
||||||
|
// Get user authenticated LDAP connection from context
|
||||||
|
ldapCon, err := getLdapConFromContext(c)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshall json body to a map
|
||||||
|
if c.Request.Header.Get("Content-Type") == "application/json" {
|
||||||
|
var attributes map[string]interface{}
|
||||||
|
err := c.ShouldBindJSON(&attributes)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = updateEntry(ldapCon, dn, attributes)
|
||||||
|
if err != nil {
|
||||||
|
if strings.Contains(err.Error(), "LDAP Result Code 50") {
|
||||||
|
c.AbortWithStatus(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
} else if strings.Contains(err.Error(), "No modification required") {
|
||||||
|
c.JSON(http.StatusNoContent, gin.H{"message": "No modification required"})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
c.AbortWithError(http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusCreated, gin.H{"message": "Successfully updated"})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* curl -i -u "admin:admin" -X DELETE https://localhost:8443/cn=newuser,ou=users,dc=example,dc=org
|
||||||
|
*
|
||||||
|
* Each leaf have to be deleted (cannot delete if subordinates)
|
||||||
|
*/
|
||||||
|
r.DELETE("/:dn", ldapBasicAuth, func(c *gin.Context) {
|
||||||
|
dn := c.Param("dn")
|
||||||
|
|
||||||
|
// Get user authenticated LDAP connection from context
|
||||||
|
ldapCon, err := getLdapConFromContext(c)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = deleteEntry(ldapCon, dn)
|
||||||
|
if err != nil {
|
||||||
|
//log.Errorf("Error creating %s: %v", dn, err)
|
||||||
|
c.AbortWithError(http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusCreated, gin.H{"message": "Successfully deleted"})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
Loading…
Reference in New Issue
Block a user