2020-01-19 11:49:49 +00:00
package main
import (
2020-01-19 12:00:53 +00:00
"encoding/base64"
"encoding/json"
2020-01-19 11:49:49 +00:00
"fmt"
"log"
2020-01-19 12:00:53 +00:00
"math/rand"
2020-01-19 11:49:49 +00:00
"os"
"os/signal"
"strings"
2020-01-19 12:00:53 +00:00
"syscall"
2020-01-19 11:49:49 +00:00
2020-01-19 12:00:53 +00:00
ldap "./ldapserver"
2020-01-19 11:49:49 +00:00
consul "github.com/hashicorp/consul/api"
message "github.com/vjeantet/goldap/message"
)
func dnToConsul ( dn string ) string {
rdns := strings . Split ( dn , "," )
// Reverse rdns
for i , j := 0 , len ( rdns ) - 1 ; i < j ; i , j = i + 1 , j - 1 {
rdns [ i ] , rdns [ j ] = rdns [ j ] , rdns [ i ]
}
return strings . Join ( rdns , "/" )
}
2020-01-19 16:55:25 +00:00
func consulToDN ( pair * consul . KVPair ) ( string , string , [ ] byte ) {
path := strings . Split ( pair . Key , "/" )
dn := ""
for _ , cpath := range path {
if cpath == "" {
continue
}
kv := strings . Split ( cpath , "=" )
if len ( kv ) == 2 && kv [ 0 ] == "attribute" {
return dn , kv [ 1 ] , pair . Value
}
if dn != "" {
dn = "," + dn
}
dn = cpath + dn
}
2020-01-19 21:21:05 +00:00
panic ( "Consul key " + pair . Key + " does not end with attribute=something" )
2020-01-19 16:55:25 +00:00
}
2020-01-19 18:10:38 +00:00
func parseValue ( value [ ] byte ) ( [ ] string , error ) {
val := [ ] string { }
err := json . Unmarshal ( value , & val )
if err == nil {
return val , nil
}
val2 := ""
err = json . Unmarshal ( value , & val2 )
if err == nil {
return [ ] string { val2 } , nil
}
return nil , fmt . Errorf ( "Not a string or list of strings: %s" , value )
}
2020-01-19 16:55:25 +00:00
func parseConsulResult ( data [ ] * consul . KVPair ) ( map [ string ] Entry , error ) {
aggregator := map [ string ] Entry { }
for _ , kv := range data {
2020-01-19 18:10:38 +00:00
log . Printf ( "(parseConsulResult) %s %s" , kv . Key , string ( kv . Value ) )
2020-01-19 16:55:25 +00:00
dn , attr , val := consulToDN ( kv )
if attr == "" || val == nil {
continue
}
if _ , exists := aggregator [ dn ] ; ! exists {
aggregator [ dn ] = Entry { }
}
2020-01-19 18:10:38 +00:00
value , err := parseValue ( val )
2020-01-19 16:55:25 +00:00
if err != nil {
return nil , err
}
2020-01-19 17:24:21 +00:00
aggregator [ dn ] [ attr ] = value
2020-01-19 16:55:25 +00:00
}
return aggregator , nil
}
2020-01-19 11:49:49 +00:00
type DNComponent struct {
2020-01-19 12:00:53 +00:00
Type string
2020-01-19 11:49:49 +00:00
Value string
}
func parseDN ( dn string ) ( [ ] DNComponent , error ) {
rdns := strings . Split ( dn , "," )
ret := [ ] DNComponent { }
for _ , rdn := range rdns {
splits := strings . Split ( rdn , "=" )
if len ( splits ) != 2 {
return nil , fmt . Errorf ( "Wrong DN component: %s (expected type=value)" , rdn )
}
ret = append ( ret , DNComponent {
2020-01-19 12:00:53 +00:00
Type : splits [ 0 ] ,
2020-01-19 11:49:49 +00:00
Value : splits [ 1 ] ,
} )
}
return ret , nil
}
type Config struct {
Suffix string
}
type Server struct {
config Config
2020-01-19 12:00:53 +00:00
kv * consul . KV
}
type State struct {
bindDn string
2020-01-19 11:49:49 +00:00
}
2020-01-19 17:24:21 +00:00
type Entry map [ string ] [ ] string
2020-01-19 11:49:49 +00:00
func main ( ) {
//ldap logger
ldap . Logger = log . New ( os . Stdout , "[server] " , log . LstdFlags )
// Connect to Consul
client , err := consul . NewClient ( consul . DefaultConfig ( ) )
if err != nil {
panic ( err )
}
kv := client . KV ( )
// TODO read config from somewhere
2020-01-19 12:00:53 +00:00
config := Config {
2020-01-19 11:49:49 +00:00
Suffix : "dc=gobottin,dc=eu" ,
}
gobottin := Server { config : config , kv : kv }
err = gobottin . init ( )
if err != nil {
panic ( err )
}
//Create a new LDAP Server
ldapserver := ldap . NewServer ( )
2020-01-19 12:00:53 +00:00
ldapserver . NewUserState = func ( ) ldap . UserState {
return & State { }
}
2020-01-19 11:49:49 +00:00
routes := ldap . NewRouteMux ( )
routes . Bind ( gobottin . handleBind )
2020-01-19 16:55:25 +00:00
routes . Search ( gobottin . handleSearch )
2020-01-19 17:24:21 +00:00
routes . Add ( gobottin . handleAdd )
2020-01-19 20:48:14 +00:00
routes . Compare ( gobottin . handleCompare )
routes . Delete ( gobottin . handleDelete )
2020-01-19 21:21:05 +00:00
routes . Modify ( gobottin . handleModify )
2020-01-19 11:49:49 +00:00
ldapserver . Handle ( routes )
// listen on 10389
go ldapserver . ListenAndServe ( "127.0.0.1:10389" )
// When CTRL+C, SIGINT and SIGTERM signal occurs
// Then stop server gracefully
ch := make ( chan os . Signal )
signal . Notify ( ch , syscall . SIGINT , syscall . SIGTERM )
<- ch
close ( ch )
ldapserver . Stop ( )
}
func ( server * Server ) init ( ) error {
2020-01-19 12:00:53 +00:00
pair , _ , err := server . kv . Get ( dnToConsul ( server . config . Suffix ) + "/attribute=objectClass" , nil )
2020-01-19 11:49:49 +00:00
if err != nil {
return err
}
if pair != nil {
return nil
}
2020-01-19 16:55:25 +00:00
base_attributes := Entry {
2020-01-19 12:00:53 +00:00
"objectClass" : [ ] string { "top" , "dcObject" , "organization" } ,
2020-01-19 17:24:21 +00:00
"structuralObjectClass" : [ ] string { "Organization" } ,
2020-01-19 11:49:49 +00:00
}
suffix_dn , err := parseDN ( server . config . Suffix )
if err != nil {
return err
}
2020-01-19 17:24:21 +00:00
base_attributes [ suffix_dn [ 0 ] . Type ] = [ ] string { suffix_dn [ 0 ] . Value }
2020-01-19 11:49:49 +00:00
err = server . addElements ( server . config . Suffix , base_attributes )
if err != nil {
return err
}
admin_pass := make ( [ ] byte , 8 )
rand . Read ( admin_pass )
admin_pass_str := base64 . RawURLEncoding . EncodeToString ( admin_pass )
admin_pass_hash := SSHAEncode ( [ ] byte ( admin_pass_str ) )
admin_dn := "cn=admin," + server . config . Suffix
2020-01-19 16:55:25 +00:00
admin_attributes := Entry {
2020-01-19 12:00:53 +00:00
"objectClass" : [ ] string { "simpleSecurityObject" , "organizationalRole" } ,
2020-01-19 17:24:21 +00:00
"description" : [ ] string { "LDAP administrator" } ,
"cn" : [ ] string { "admin" } ,
"userpassword" : [ ] string { admin_pass_hash } ,
"structuralObjectClass" : [ ] string { "organizationalRole" } ,
2020-01-19 12:00:53 +00:00
"permissions" : [ ] string { "read" , "write" } ,
2020-01-19 11:49:49 +00:00
}
err = server . addElements ( admin_dn , admin_attributes )
if err != nil {
return err
}
log . Printf (
"It seems to be a new installation, we created a default user for you:\n\n dn: %s\n password: %s\n\nWe didn't use true random, you should replace it as soon as possible." ,
admin_dn ,
admin_pass_str ,
)
return nil
}
2020-01-19 16:55:25 +00:00
func ( server * Server ) addElements ( dn string , attrs Entry ) error {
2020-01-19 11:49:49 +00:00
prefix := dnToConsul ( dn )
for k , v := range attrs {
json , err := json . Marshal ( v )
if err != nil {
return err
}
pair := & consul . KVPair { Key : prefix + "/attribute=" + k , Value : json }
_ , err = server . kv . Put ( pair , nil )
if err != nil {
return err
}
}
return nil
}
2020-01-19 18:10:38 +00:00
func ( server * Server ) getAttribute ( dn string , attr string ) ( [ ] string , error ) {
pair , _ , err := server . kv . Get ( dnToConsul ( dn ) + "/attribute=" + attr , nil )
if err != nil {
return nil , err
}
if pair == nil {
return nil , nil
}
return parseValue ( pair . Value )
}
func ( server * Server ) objectExists ( dn string ) ( bool , error ) {
prefix := dnToConsul ( dn ) + "/"
data , _ , err := server . kv . List ( prefix , nil )
if err != nil {
return false , err
}
return len ( data ) > 0 , nil
}
2020-01-19 18:19:34 +00:00
func ( server * Server ) checkSuffix ( dn string , allow_extend bool ) ( string , error ) {
suffix := server . config . Suffix
if len ( dn ) < len ( suffix ) {
if dn != suffix [ - len ( dn ) : ] || ! allow_extend {
return suffix , fmt . Errorf (
"Only handling stuff under DN %s" , suffix )
}
return suffix , nil
} else {
if dn [ len ( dn ) - len ( suffix ) : ] != suffix {
return suffix , fmt . Errorf (
"Only handling stuff under DN %s" , suffix )
}
return dn , nil
}
}
2020-01-19 12:00:53 +00:00
func ( server * Server ) handleBind ( s ldap . UserState , w ldap . ResponseWriter , m * ldap . Message ) {
state := s . ( * State )
2020-01-19 11:49:49 +00:00
r := m . GetBindRequest ( )
2020-01-19 17:24:21 +00:00
result_code , err := server . handleBindInternal ( state , & r )
2020-01-19 11:49:49 +00:00
res := ldap . NewBindResponse ( result_code )
if err != nil {
res . SetDiagnosticMessage ( err . Error ( ) )
log . Printf ( "Failed bind for %s: %s" , string ( r . Name ( ) ) , err . Error ( ) )
}
w . Write ( res )
}
2020-01-19 17:24:21 +00:00
func ( server * Server ) handleBindInternal ( state * State , r * message . BindRequest ) ( int , error ) {
2020-01-19 18:10:38 +00:00
passwd , err := server . getAttribute ( string ( r . Name ( ) ) , "userpassword" )
2020-01-19 11:49:49 +00:00
if err != nil {
return ldap . LDAPResultOperationsError , err
}
2020-01-19 18:10:38 +00:00
if passwd == nil {
2020-01-19 11:49:49 +00:00
return ldap . LDAPResultNoSuchObject , nil
}
2020-01-19 18:10:38 +00:00
for _ , hash := range passwd {
valid := SSHAMatches ( hash , [ ] byte ( r . AuthenticationSimple ( ) ) )
if valid {
state . bindDn = string ( r . Name ( ) )
return ldap . LDAPResultSuccess , nil
}
2020-01-19 11:49:49 +00:00
}
2020-01-19 18:10:38 +00:00
return ldap . LDAPResultInvalidCredentials , nil
2020-01-19 11:49:49 +00:00
}
2020-01-19 16:55:25 +00:00
func ( server * Server ) handleSearch ( s ldap . UserState , w ldap . ResponseWriter , m * ldap . Message ) {
state := s . ( * State )
r := m . GetSearchRequest ( )
code , err := server . handleSearchInternal ( state , w , & r )
res := ldap . NewResponse ( code )
if err != nil {
res . SetDiagnosticMessage ( err . Error ( ) )
}
w . Write ( message . SearchResultDone ( res ) )
}
func ( server * Server ) handleSearchInternal ( state * State , w ldap . ResponseWriter , r * message . SearchRequest ) ( int , error ) {
log . Printf ( "-- SEARCH REQUEST: --" )
log . Printf ( "Request BaseDn=%s" , r . BaseObject ( ) )
log . Printf ( "Request Filter=%s" , r . Filter ( ) )
log . Printf ( "Request FilterString=%s" , r . FilterString ( ) )
log . Printf ( "Request Attributes=%s" , r . Attributes ( ) )
log . Printf ( "Request TimeLimit=%d" , r . TimeLimit ( ) . Int ( ) )
// TODO check authorizations
2020-01-19 18:19:34 +00:00
baseObject , err := server . checkSuffix ( string ( r . BaseObject ( ) ) , true )
if err != nil {
return ldap . LDAPResultInvalidDNSyntax , err
2020-01-19 18:10:38 +00:00
}
2020-01-19 18:19:34 +00:00
basePath := dnToConsul ( baseObject ) + "/"
2020-01-19 16:55:25 +00:00
2020-01-19 18:19:34 +00:00
data , _ , err := server . kv . List ( basePath , nil )
2020-01-19 16:55:25 +00:00
if err != nil {
return ldap . LDAPResultOperationsError , err
}
entries , err := parseConsulResult ( data )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
2020-01-19 18:19:34 +00:00
log . Printf ( "in %s: %#v" , basePath , data )
2020-01-19 16:55:25 +00:00
log . Printf ( "%#v" , entries )
for dn , entry := range entries {
// TODO filter out if no permission to read this
matched , err := applyFilter ( entry , r . Filter ( ) )
if err != nil {
2020-01-19 18:51:01 +00:00
return ldap . LDAPResultUnwillingToPerform , err
2020-01-19 16:55:25 +00:00
}
if ! matched {
continue
}
e := ldap . NewSearchResultEntry ( dn )
for attr , val := range entry {
// If attribute is not in request, exclude it from returned entry
if len ( r . Attributes ( ) ) > 0 {
found := false
2020-01-19 17:04:42 +00:00
for _ , requested := range r . Attributes ( ) {
if strings . EqualFold ( string ( requested ) , attr ) {
2020-01-19 16:55:25 +00:00
found = true
break
}
}
if ! found {
continue
}
}
// Send result
2020-01-19 17:24:21 +00:00
for _ , v := range val {
2020-01-19 16:55:25 +00:00
e . AddAttribute ( message . AttributeDescription ( attr ) ,
2020-01-19 17:24:21 +00:00
message . AttributeValue ( v ) )
2020-01-19 16:55:25 +00:00
}
}
w . Write ( e )
}
return ldap . LDAPResultSuccess , nil
}
func applyFilter ( entry Entry , filter message . Filter ) ( bool , error ) {
if fAnd , ok := filter . ( message . FilterAnd ) ; ok {
for _ , cond := range fAnd {
res , err := applyFilter ( entry , cond )
if err != nil {
return false , err
}
if ! res {
return false , nil
}
}
return true , nil
} else if fOr , ok := filter . ( message . FilterOr ) ; ok {
for _ , cond := range fOr {
res , err := applyFilter ( entry , cond )
if err != nil {
return false , err
}
if res {
return true , nil
}
}
return false , nil
} else if fNot , ok := filter . ( message . FilterNot ) ; ok {
res , err := applyFilter ( entry , fNot . Filter )
if err != nil {
return false , err
}
return ! res , nil
} else if fPresent , ok := filter . ( message . FilterPresent ) ; ok {
what := string ( fPresent )
2020-01-19 17:04:42 +00:00
// Case insensitive search
for desc := range entry {
if strings . EqualFold ( what , desc ) {
return true , nil
}
2020-01-19 16:55:25 +00:00
}
return false , nil
} else if fEquality , ok := filter . ( message . FilterEqualityMatch ) ; ok {
desc := string ( fEquality . AttributeDesc ( ) )
target := string ( fEquality . AssertionValue ( ) )
2020-01-19 17:04:42 +00:00
// Case insensitive attribute search
for entry_desc , value := range entry {
if strings . EqualFold ( entry_desc , desc ) {
2020-01-19 17:24:21 +00:00
for _ , val := range value {
if val == target {
return true , nil
2020-01-19 16:55:25 +00:00
}
}
2020-01-19 17:24:21 +00:00
return false , nil
2020-01-19 16:55:25 +00:00
}
}
2020-01-19 17:04:42 +00:00
return false , nil
2020-01-19 16:55:25 +00:00
} else {
return false , fmt . Errorf ( "Unsupported filter: %#v %T" , filter , filter )
}
}
2020-01-19 17:24:21 +00:00
func ( server * Server ) handleAdd ( s ldap . UserState , w ldap . ResponseWriter , m * ldap . Message ) {
state := s . ( * State )
r := m . GetAddRequest ( )
code , err := server . handleAddInternal ( state , & r )
res := ldap . NewResponse ( code )
if err != nil {
res . SetDiagnosticMessage ( err . Error ( ) )
}
w . Write ( message . AddResponse ( res ) )
}
func ( server * Server ) handleAddInternal ( state * State , r * message . AddRequest ) ( int , error ) {
dn := string ( r . Entry ( ) )
2020-01-19 18:19:34 +00:00
_ , err := server . checkSuffix ( dn , false )
if err != nil {
return ldap . LDAPResultInvalidDNSyntax , err
}
2020-01-19 18:10:38 +00:00
exists , err := server . objectExists ( dn )
2020-01-19 17:24:21 +00:00
if err != nil {
return ldap . LDAPResultOperationsError , err
}
2020-01-19 18:10:38 +00:00
if exists {
2020-01-19 17:24:21 +00:00
return ldap . LDAPResultEntryAlreadyExists , nil
}
// TODO check permissions
2020-01-19 20:26:44 +00:00
2020-01-19 18:10:38 +00:00
var members [ ] string = nil
2020-01-19 17:24:21 +00:00
entry := Entry { }
for _ , attribute := range r . Attributes ( ) {
key := string ( attribute . Type_ ( ) )
vals_str := [ ] string { }
for _ , val := range attribute . Vals ( ) {
vals_str = append ( vals_str , string ( val ) )
}
2020-01-19 20:26:44 +00:00
// Fail if they are trying to write memberOf, we manage this ourselves
if strings . EqualFold ( key , "memberOf" ) {
return ldap . LDAPResultObjectClassViolation , fmt . Errorf (
"memberOf cannot be defined directly, membership must be specified in the group itself" )
}
// If they are writing a member key, we have to check they are adding valid members
2020-01-19 18:10:38 +00:00
if strings . EqualFold ( key , "member" ) {
members = vals_str
for _ , member := range members {
2020-01-19 18:19:34 +00:00
_ , err := server . checkSuffix ( member , false )
if err != nil {
return ldap . LDAPResultInvalidDNSyntax , err
}
2020-01-19 18:10:38 +00:00
exists , err = server . objectExists ( member )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
if ! exists {
return ldap . LDAPResultNoSuchObject , fmt . Errorf (
"Cannot add %s to members, it does not exist!" ,
member )
}
}
}
2020-01-19 17:24:21 +00:00
entry [ key ] = vals_str
}
err = server . addElements ( dn , entry )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
2020-01-19 18:10:38 +00:00
if members != nil {
for _ , member := range members {
memberGroups , err := server . getAttribute ( member , "memberOf" )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
if memberGroups == nil {
memberGroups = [ ] string { }
}
2020-01-19 20:26:44 +00:00
alreadyMember := false
for _ , mb := range memberGroups {
if mb == dn {
alreadyMember = true
log . Printf ( "Warning: inconsistency detected, %s was memberOf %s at a time when it didn't exist!" ,
member , dn )
break
}
}
if ! alreadyMember {
memberGroups = append ( memberGroups , dn )
err = server . addElements ( member , Entry {
"memberOf" : memberGroups ,
} )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
2020-01-19 18:10:38 +00:00
}
}
}
2020-01-19 17:24:21 +00:00
return ldap . LDAPResultSuccess , nil
}
2020-01-19 20:48:14 +00:00
func ( server * Server ) handleCompare ( s ldap . UserState , w ldap . ResponseWriter , m * ldap . Message ) {
state := s . ( * State )
r := m . GetCompareRequest ( )
code , err := server . handleCompareInternal ( state , & r )
res := ldap . NewResponse ( code )
if err != nil {
res . SetDiagnosticMessage ( err . Error ( ) )
}
w . Write ( message . CompareResponse ( res ) )
}
func ( server * Server ) handleCompareInternal ( state * State , r * message . CompareRequest ) ( int , error ) {
dn := string ( r . Entry ( ) )
attr := string ( r . Ava ( ) . AttributeDesc ( ) )
expected := string ( r . Ava ( ) . AssertionValue ( ) )
_ , err := server . checkSuffix ( dn , false )
if err != nil {
return ldap . LDAPResultInvalidDNSyntax , err
}
// TODO check user for permissions to read dn
exists , err := server . objectExists ( dn )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
if ! exists {
return ldap . LDAPResultNoSuchObject , fmt . Errorf ( "Not found: %s" , dn )
}
values , err := server . getAttribute ( dn , attr )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
for _ , v := range values {
if v == expected {
return ldap . LDAPResultCompareTrue , nil
}
}
return ldap . LDAPResultCompareFalse , nil
}
func ( server * Server ) handleDelete ( s ldap . UserState , w ldap . ResponseWriter , m * ldap . Message ) {
state := s . ( * State )
r := m . GetDeleteRequest ( )
code , err := server . handleDeleteInternal ( state , & r )
res := ldap . NewResponse ( code )
if err != nil {
res . SetDiagnosticMessage ( err . Error ( ) )
}
w . Write ( message . DelResponse ( res ) )
}
func ( server * Server ) handleDeleteInternal ( state * State , r * message . DelRequest ) ( int , error ) {
dn := string ( * r )
_ , err := server . checkSuffix ( dn , false )
if err != nil {
return ldap . LDAPResultInvalidDNSyntax , err
}
// TODO check user for permissions to write dn
// Check that this LDAP entry exists and has no children
path := dnToConsul ( dn ) + "/"
items , _ , err := server . kv . List ( path , nil )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
if len ( items ) == 0 {
return ldap . LDAPResultNoSuchObject , fmt . Errorf ( "Not found: %s" , dn )
}
for _ , item := range items {
itemDN , _ , _ := consulToDN ( item )
if itemDN != dn {
return ldap . LDAPResultNotAllowedOnNonLeaf , fmt . Errorf (
"Cannot delete %d as it has children" , dn )
}
}
// Retrieve group membership before we delete everything
memberOf , err := server . getAttribute ( dn , "memberOf" )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
// Delete the LDAP entry
_ , err = server . kv . DeleteTree ( path , nil )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
// Delete it from the member list of all the groups it was a member of
if memberOf != nil {
for _ , group := range memberOf {
groupMembers , err := server . getAttribute ( dn , "member" )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
newMembers := [ ] string { }
for _ , memb := range groupMembers {
if memb != dn {
newMembers = append ( newMembers , memb )
}
}
err = server . addElements ( group , Entry {
"member" : newMembers ,
} )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
}
}
return ldap . LDAPResultSuccess , nil
}
2020-01-19 21:21:05 +00:00
func ( server * Server ) handleModify ( s ldap . UserState , w ldap . ResponseWriter , m * ldap . Message ) {
state := s . ( * State )
r := m . GetModifyRequest ( )
code , err := server . handleModifyInternal ( state , & r )
res := ldap . NewResponse ( code )
if err != nil {
res . SetDiagnosticMessage ( err . Error ( ) )
}
w . Write ( message . ModifyResponse ( res ) )
}
func ( server * Server ) handleModifyInternal ( state * State , r * message . ModifyRequest ) ( int , error ) {
dn := string ( r . Object ( ) )
_ , err := server . checkSuffix ( dn , false )
if err != nil {
return ldap . LDAPResultInvalidDNSyntax , err
}
// TODO check user for permissions to write dn
// Retrieve previous values (by the way, check object exists)
items , _ , err := server . kv . List ( dnToConsul ( dn ) + "/attribute=" , nil )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
if len ( items ) == 0 {
return ldap . LDAPResultNoSuchObject , fmt . Errorf ( "Not found: %s" , dn )
}
prevEntry := Entry { }
for _ , item := range items {
itemDN , attr , val := consulToDN ( item )
if itemDN != dn {
panic ( "itemDN != dn in handleModifyInternal" )
}
vals , err := parseValue ( val )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
prevEntry [ attr ] = vals
}
// Keep track of group members added/deleted
addMembers , delMembers := [ ] string { } , [ ] string { }
// Produce new entry values to be saved
newEntry := Entry { }
for _ , change := range r . Changes ( ) {
attr := string ( change . Modification ( ) . Type_ ( ) )
values := change . Modification ( ) . Vals ( )
if change . Operation ( ) == ldap . ModifyRequestChangeOperationAdd {
newEntry [ attr ] = prevEntry [ attr ]
for _ , val := range values {
present := false
for _ , prevVal := range newEntry [ attr ] {
if prevVal == string ( val ) {
present = true
break
}
}
if ! present {
newEntry [ attr ] = append ( newEntry [ attr ] , string ( val ) )
if strings . EqualFold ( attr , "member" ) {
addMembers = append ( addMembers , string ( val ) )
}
}
}
} else if change . Operation ( ) == ldap . ModifyRequestChangeOperationDelete {
if len ( values ) == 0 {
// Delete everything
newEntry [ attr ] = [ ] string { }
if strings . EqualFold ( attr , "member" ) {
delMembers = append ( delMembers , prevEntry [ attr ] ... )
}
} else {
// Delete only those specified
newEntry [ attr ] = [ ] string { }
for _ , prevVal := range prevEntry [ attr ] {
keep := true
for _ , delVal := range values {
if string ( delVal ) == prevVal {
keep = false
break
}
}
if keep {
newEntry [ attr ] = append ( newEntry [ attr ] , prevVal )
} else {
if strings . EqualFold ( attr , "member" ) {
delMembers = append ( delMembers , prevVal )
}
}
}
}
} else if change . Operation ( ) == ldap . ModifyRequestChangeOperationReplace {
newEntry [ attr ] = [ ] string { }
for _ , newVal := range values {
newEntry [ attr ] = append ( newEntry [ attr ] , string ( newVal ) )
}
if strings . EqualFold ( attr , "member" ) {
for _ , newMem := range newEntry [ attr ] {
mustAdd := true
for _ , prevMem := range prevEntry [ attr ] {
if prevMem == newMem {
mustAdd = false
break
}
}
if mustAdd {
addMembers = append ( addMembers , newMem )
}
}
for _ , prevMem := range prevEntry [ attr ] {
mustDel := true
for _ , newMem := range newEntry [ attr ] {
if newMem == prevMem {
mustDel = false
break
}
}
if mustDel {
delMembers = append ( delMembers , prevMem )
}
}
}
}
}
// Check that added members actually exist
for _ , addMem := range addMembers {
exists , err := server . objectExists ( addMem )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
if ! exists {
return ldap . LDAPResultNoSuchObject , fmt . Errorf (
"Cannot add member %s, it does not exist" , addMem )
}
}
// Save the edited values
server . addElements ( dn , newEntry )
// Update memberOf for added members and deleted members
for _ , addMem := range addMembers {
memberOf , err := server . getAttribute ( addMem , "memberOf" )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
if memberOf == nil {
memberOf = [ ] string { }
}
memberOf = append ( memberOf , dn )
err = server . addElements ( addMem , Entry { "memberOf" : memberOf } )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
}
for _ , delMem := range delMembers {
memberOf , err := server . getAttribute ( delMem , "memberOf" )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
if memberOf == nil {
memberOf = [ ] string { }
}
newMemberOf := [ ] string { }
for _ , g := range memberOf {
if g != dn {
newMemberOf = append ( newMemberOf , g )
}
}
err = server . addElements ( delMem , Entry { "memberOf" : newMemberOf } )
if err != nil {
return ldap . LDAPResultOperationsError , err
}
}
return ldap . LDAPResultSuccess , nil
}