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
|
||||
)
|
||||
|
||||
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) {
|
||||
var err error
|
||||
var conLdap *MyLdap
|
||||
@ -43,7 +65,7 @@ func connectLdap(myldap *MyLdap) (*MyLdap, error) {
|
||||
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
|
||||
if conLdap.Conn == nil {
|
||||
conLdap, err = connectLdap(conLdap)
|
||||
@ -65,30 +87,40 @@ func ldapSearch(conLdap *MyLdap, searchReq *ldap.SearchRequest, attempt int) (*l
|
||||
return result, err
|
||||
} else {
|
||||
attempt = attempt + 1
|
||||
return ldapSearch(conLdap, searchReq, attempt)
|
||||
return internalLdapSearch(conLdap, searchReq, attempt)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
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 realAttributes []string
|
||||
|
||||
// Build search filter
|
||||
// Build final search filter
|
||||
if strings.EqualFold(class, "ALL") {
|
||||
filter = fmt.Sprintf("(objectClass=*)")
|
||||
fFilter = fmt.Sprintf("(objectClass=*)")
|
||||
} else {
|
||||
filter = fmt.Sprintf("(objectClass=%s)", class)
|
||||
fFilter = fmt.Sprintf("(objectClass=%s)", class)
|
||||
}
|
||||
|
||||
if false == strings.EqualFold(cn, "ALL") {
|
||||
filter = fmt.Sprintf("(&%s(cn=%s))", filter, ldap.EscapeFilter(cn))
|
||||
if false == strings.EqualFold(filter, "ALL") {
|
||||
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)
|
||||
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)
|
||||
|
||||
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 {
|
||||
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) {
|
||||
sr, err := doLdapSearch(myldap, "", username, "ALL", "")
|
||||
filter := fmt.Sprintf("cn=%s", username)
|
||||
sr, err := doLdapSearch(myldap, "", filter, "ALL", "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
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 {
|
||||
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
|
||||
|
||||
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 (
|
||||
gVersion = "0.5.2"
|
||||
gVersion = "0.5.3"
|
||||
|
||||
gRoLdap *MyLdap
|
||||
)
|
||||
@ -111,7 +111,7 @@ func checkIfModifiedSince(c *gin.Context, myldap *MyLdap, baseDn, cn, class, att
|
||||
|
||||
log.Debugf("ifModifiedSince: %s", ifModifiedSince)
|
||||
|
||||
res, err := doLdapSearch(myldap, baseDn, cn, class, "modifyTimestamp")
|
||||
res, err := searchByCn(myldap, baseDn, cn, class, "modifyTimestamp")
|
||||
if err != nil {
|
||||
log.Errorf("Error searching modifyTimestamp for %s in %s : %v", cn, baseDn, err)
|
||||
return true, err
|
||||
@ -145,7 +145,6 @@ func checkIfModifiedSince(c *gin.Context, myldap *MyLdap, baseDn, cn, class, att
|
||||
return true, nil
|
||||
}
|
||||
|
||||
|
||||
// Basic Authentication handler with local hardcoded account - do not use
|
||||
func basicAuth(c *gin.Context) {
|
||||
user, password, hasAuth := c.Request.BasicAuth()
|
||||
@ -203,7 +202,6 @@ func ldapBasicAuth(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func getLdapConFromContext(c *gin.Context) (*MyLdap, error) {
|
||||
ldapCon, exist := c.Get("ldapCon")
|
||||
if exist != true {
|
||||
@ -212,7 +210,6 @@ func getLdapConFromContext(c *gin.Context) (*MyLdap, error) {
|
||||
return ldapCon.(*MyLdap), nil
|
||||
}
|
||||
|
||||
|
||||
func initRouter(r *gin.Engine) {
|
||||
r.GET("/ping", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
@ -238,7 +235,7 @@ func initRouter(r *gin.Engine) {
|
||||
// json format is the default
|
||||
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 err != nil {
|
||||
@ -272,7 +269,7 @@ func initRouter(r *gin.Engine) {
|
||||
}
|
||||
|
||||
if modified {
|
||||
res, err := doLdapSearch(ldapCon, ou, cn, class, "ALL")
|
||||
res, err := searchByCn(ldapCon, ou, cn, class, "ALL")
|
||||
if err != nil {
|
||||
log.Errorf("Error searching %s in %s : %v", cn, ou, err)
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
@ -301,7 +298,7 @@ func initRouter(r *gin.Engine) {
|
||||
return
|
||||
}
|
||||
|
||||
res, err := doLdapSearch(ldapCon, ou, cn, class, attr)
|
||||
res, err := searchByCn(ldapCon, ou, cn, class, attr)
|
||||
if err != nil {
|
||||
log.Errorf("Error searching %s in %s : %v", cn, ou, err)
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
@ -334,7 +331,7 @@ func initRouter(r *gin.Engine) {
|
||||
}
|
||||
|
||||
if modified {
|
||||
res, err := doLdapSearch(ldapCon, ou, cn, class, attr)
|
||||
res, err := searchByCn(ldapCon, ou, cn, class, attr)
|
||||
if err != nil {
|
||||
log.Errorf("Error searching %s in %s : %v", cn, ou, err)
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
@ -346,6 +343,113 @@ func initRouter(r *gin.Engine) {
|
||||
}
|
||||
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() {
|
||||
|
Loading…
Reference in New Issue
Block a user