diff options
author | Alex Auvolat <alex@adnab.me> | 2020-01-26 22:08:27 +0100 |
---|---|---|
committer | Alex Auvolat <alex@adnab.me> | 2020-01-26 22:08:27 +0100 |
commit | b27eb45239f039859cfd3e9211a7e29b1df7c33b (patch) | |
tree | 1d290b32f8c7ed65bb32b5ffa896a21e175b9841 /write.go | |
parent | 15745b1b390792d5defbc73788adadf1c0656fbe (diff) | |
download | bottin-b27eb45239f039859cfd3e9211a7e29b1df7c33b.tar.gz bottin-b27eb45239f039859cfd3e9211a7e29b1df7c33b.zip |
Split off read and write functions in separate files
Diffstat (limited to 'write.go')
-rw-r--r-- | write.go | 472 |
1 files changed, 472 insertions, 0 deletions
diff --git a/write.go b/write.go new file mode 100644 index 0000000..33779c4 --- /dev/null +++ b/write.go @@ -0,0 +1,472 @@ +package main + +import ( + "fmt" + "strings" + + ldap "./ldapserver" + message "github.com/vjeantet/goldap/message" +) + + +// Add request ------------------------ + +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()) + } + if code == ldap.LDAPResultSuccess { + server.logger.Printf("Successfully added %s", string(r.Entry())) + } else { + server.logger.Printf("Failed to add %s (%s)", string(r.Entry()), err) + } + w.Write(message.AddResponse(res)) +} + +func (server *Server) handleAddInternal(state *State, r *message.AddRequest) (int, error) { + dn := string(r.Entry()) + + _, err := server.checkSuffix(dn, false) + if err != nil { + return ldap.LDAPResultInvalidDNSyntax, err + } + + dnSplit, err := parseDN(dn) + if err != nil { + return ldap.LDAPResultInvalidDNSyntax, err + } + + // Check permissions + attrListStr := []string{} + for _, attribute := range r.Attributes() { + attrListStr = append(attrListStr, string(attribute.Type_())) + } + if !server.config.Acl.Check(&state.login, "add", dn, attrListStr) { + return ldap.LDAPResultInsufficientAccessRights, nil + } + + // Check that object does not already exist + exists, err := server.objectExists(dn) + if err != nil { + return ldap.LDAPResultOperationsError, err + } + if exists { + return ldap.LDAPResultEntryAlreadyExists, nil + } + + // Add object + + // If adding a group, track of who the members will be so that their memberOf field can be updated later + var members []string = nil + + 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)) + } + + // Fail if they are trying to write memberOf, we manage this ourselves + err = checkRestrictedAttr(key) + if err != nil { + return ldap.LDAPResultObjectClassViolation, err + } + // If they are writing a member key, we have to check they are adding valid members + if strings.EqualFold(key, "member") { + members = vals_str + for _, member := range members { + _, err := server.checkSuffix(member, false) + if err != nil { + return ldap.LDAPResultInvalidDNSyntax, err + } + 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) + } + } + } + entry[key] = vals_str + } + + entry[ATTR_CREATORSNAME] = []string{state.login.user} + entry[ATTR_CREATETIMESTAMP] = []string{genTimestamp()} + entry[ATTR_ENTRYUUID] = []string{genUuid()} + entry[dnSplit[0].Type] = []string{dnSplit[0].Value} + + err = server.addElements(dn, entry) + if err != nil { + return ldap.LDAPResultOperationsError, err + } + + if members != nil { + for _, member := range members { + memberGroups, err := server.getAttribute(member, ATTR_MEMBEROF) + if err != nil { + return ldap.LDAPResultOperationsError, err + } + if memberGroups == nil { + memberGroups = []string{} + } + + alreadyMember := false + for _, mb := range memberGroups { + if mb == dn { + alreadyMember = true + server.logger.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{ + ATTR_MEMBEROF: memberGroups, + }) + if err != nil { + return ldap.LDAPResultOperationsError, err + } + } + } + } + + return ldap.LDAPResultSuccess, nil +} + + +// Delete request ------------------------ + +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()) + } + if code == ldap.LDAPResultSuccess { + server.logger.Printf("Successfully deleted %s", string(r)) + } else { + server.logger.Printf("Failed to delete %s (%s)", string(r), err) + } + 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 + } + + // Check for delete permission + if !server.config.Acl.Check(&state.login, "delete", dn, []string{}) { + return ldap.LDAPResultInsufficientAccessRights, nil + } + + // Check that this LDAP entry exists and has no children + path, err := dnToConsul(dn) + if err != nil { + return ldap.LDAPResultInvalidDNSyntax, err + } + + 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, _, err := consulToDN(item.Key) + if err != nil { + continue + } + 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, ATTR_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 +} + + +// Modify request ------------------------ + +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()) + } + if code == ldap.LDAPResultSuccess { + server.logger.Printf("Successfully modified %s", string(r.Object())) + } else { + server.logger.Printf("Failed to modifiy %s (%s)", string(r.Object()), err) + } + 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 + } + + dnSplit, err := parseDN(dn) + if err != nil { + return ldap.LDAPResultInvalidDNSyntax, err + } + + // First permission check with no particular attributes + if !server.config.Acl.Check(&state.login, "modify", dn, []string{}) { + return ldap.LDAPResultInsufficientAccessRights, nil + } + + // Retrieve previous values (by the way, check object exists) + path, err := dnToConsul(dn) + if err != nil { + return ldap.LDAPResultInvalidDNSyntax, err + } + + items, _, err := server.kv.List(path+"/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, err := consulToDN(item.Key) + if err != nil { + continue + } + if itemDN != dn { + panic("itemDN != dn in handleModifyInternal") + } + vals, err := parseValue(item.Value) + 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() + + err = checkRestrictedAttr(attr) + if err != nil { + return ldap.LDAPResultObjectClassViolation, err + } + if strings.EqualFold(attr, dnSplit[0].Type) { + return ldap.LDAPResultObjectClassViolation, fmt.Errorf("%s may not be changed as it is part of object path", attr) + } + + // Check for permission to modify this attribute + if !server.config.Acl.Check(&state.login, "modify", dn, []string{attr}) { + return ldap.LDAPResultInsufficientAccessRights, nil + } + + 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) + } + } + + newEntry[ATTR_MODIFIERSNAME] = []string{state.login.user} + newEntry[ATTR_MODIFYTIMESTAMP] = []string{genTimestamp()} + + // 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, ATTR_MEMBEROF) + if err != nil { + return ldap.LDAPResultOperationsError, err + } + if memberOf == nil { + memberOf = []string{} + } + memberOf = append(memberOf, dn) + err = server.addElements(addMem, Entry{ATTR_MEMBEROF: memberOf}) + if err != nil { + return ldap.LDAPResultOperationsError, err + } + } + + for _, delMem := range delMembers { + memberOf, err := server.getAttribute(delMem, ATTR_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{ATTR_MEMBEROF: newMemberOf}) + if err != nil { + return ldap.LDAPResultOperationsError, err + } + } + + return ldap.LDAPResultSuccess, nil +} |