Alex Auvolat
f8c726dcda
After an object has been updated, membership information must be propagated to other object. Such operations may fail when calling consul but if they do we don't return fail immediatly returning an error code any more. Instead we just print all the errors to our logs and try to process the remaining updates.
524 lines
14 KiB
Go
524 lines
14 KiB
Go
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
|
|
}
|
|
|
|
// If adding a group, track of who the members will be so that their memberOf field can be updated later
|
|
var members []string = nil
|
|
|
|
// Check attributes
|
|
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, ATTR_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}
|
|
|
|
// Add our intem in the DB
|
|
err = server.addElements(dn, entry)
|
|
if err != nil {
|
|
return ldap.LDAPResultOperationsError, err
|
|
}
|
|
// ~~ from this point on, our operation succeeded ~~
|
|
// ~~ future errors cause inconsistencies in the DB and are logged ~~
|
|
|
|
// If our item has a member list, add it to all of its member's memberOf attribute
|
|
if members != nil {
|
|
for _, member := range members {
|
|
memberGroups, err := server.getAttribute(member, ATTR_MEMBEROF)
|
|
if err != nil {
|
|
server.logger.Printf("Could not add %s to memberOf of %s: %s", dn, member, err)
|
|
continue
|
|
}
|
|
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 {
|
|
server.logger.Printf("Could not add %s to memberOf of %s: %s", dn, member, 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
|
|
}
|
|
memberList, err := server.getAttribute(dn, ATTR_MEMBER)
|
|
if err != nil {
|
|
return ldap.LDAPResultOperationsError, err
|
|
}
|
|
|
|
// Delete the LDAP entry
|
|
_, err = server.kv.DeleteTree(path+"/", nil)
|
|
if err != nil {
|
|
return ldap.LDAPResultOperationsError, err
|
|
}
|
|
// ~~ from this point on, our operation succeeded ~~
|
|
// ~~ future errors cause inconsistencies in the DB and are logged ~~
|
|
|
|
// 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(group, ATTR_MEMBER)
|
|
if err != nil {
|
|
server.logger.Printf("Could not remove %s from members of %s: %s", dn, group, err)
|
|
continue
|
|
}
|
|
|
|
newMembers := []string{}
|
|
for _, memb := range groupMembers {
|
|
if memb != dn {
|
|
newMembers = append(newMembers, memb)
|
|
}
|
|
}
|
|
|
|
err = server.addElements(group, Entry{
|
|
ATTR_MEMBER: newMembers,
|
|
})
|
|
if err != nil {
|
|
server.logger.Printf("Could not remove %s from members of %s: %s", dn, group, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Delete it from all of its member's memberOf info
|
|
if memberList != nil {
|
|
for _, member := range memberList {
|
|
memberOf, err := server.getAttribute(member, ATTR_MEMBEROF)
|
|
if err != nil || memberOf == nil {
|
|
server.logger.Printf("Could not remove %s from memberOf of %s: %s", dn, member, err)
|
|
continue
|
|
}
|
|
|
|
newMemberOf := []string{}
|
|
for _, group := range memberOf {
|
|
if group != dn {
|
|
newMemberOf = append(newMemberOf, group)
|
|
}
|
|
}
|
|
|
|
err = server.addElements(member, Entry{
|
|
ATTR_MEMBEROF: newMemberOf,
|
|
})
|
|
if err != nil {
|
|
server.logger.Printf("Could not remove %s from memberOf of %s: %s", dn, member, 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, 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, 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, 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, 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)
|
|
// ~~ from this point on, our operation succeeded ~~
|
|
// ~~ future errors cause inconsistencies in the DB and are logged ~~
|
|
|
|
// Update memberOf for added members and deleted members
|
|
for _, addMem := range addMembers {
|
|
memberOf, err := server.getAttribute(addMem, ATTR_MEMBEROF)
|
|
if err != nil {
|
|
server.logger.Printf("Could not add %s to memberOf of %s: %s", dn, addMem, err)
|
|
continue
|
|
}
|
|
if memberOf == nil {
|
|
memberOf = []string{}
|
|
}
|
|
|
|
alreadyMember := false
|
|
for _, mb := range memberOf {
|
|
if mb == dn {
|
|
alreadyMember = true
|
|
break
|
|
}
|
|
}
|
|
if !alreadyMember {
|
|
memberOf = append(memberOf, dn)
|
|
err = server.addElements(addMem, Entry{
|
|
ATTR_MEMBEROF: memberOf,
|
|
})
|
|
if err != nil {
|
|
server.logger.Printf("Could not add %s to memberOf of %s: %s", dn, addMem, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, delMem := range delMembers {
|
|
memberOf, err := server.getAttribute(delMem, ATTR_MEMBEROF)
|
|
if err != nil {
|
|
server.logger.Printf("Could not remove %s from memberOf of %s: %s", dn, delMem, err)
|
|
continue
|
|
}
|
|
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 {
|
|
server.logger.Printf("Could not remove %s from memberOf of %s: %s", dn, delMem, err)
|
|
}
|
|
}
|
|
|
|
return ldap.LDAPResultSuccess, nil
|
|
}
|