diff --git a/main.go b/main.go index d73ada4..d905d72 100644 --- a/main.go +++ b/main.go @@ -43,7 +43,7 @@ func consulToDN(pair *consul.KVPair) (string, string, []byte) { } dn = cpath + dn } - return dn, "", nil + panic("Consul key " + pair.Key + " does not end with attribute=something") } func parseValue(value []byte) ([]string, error) { @@ -156,6 +156,7 @@ func main() { routes.Add(gobottin.handleAdd) routes.Compare(gobottin.handleCompare) routes.Delete(gobottin.handleDelete) + routes.Modify(gobottin.handleModify) ldapserver.Handle(routes) // listen on 10389 @@ -686,3 +687,191 @@ func (server *Server) handleDeleteInternal(state *State, r *message.DelRequest) return ldap.LDAPResultSuccess, nil } + +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 +}