package ldapentry import ( "errors" "github.com/go-ldap/ldap/v3" "golang.org/x/text/cases" ) func ApplyModify(old *ldap.Entry, mod *ldap.ModifyRequest) (newEntry *ldap.Entry, err error) { oldDN, err := ldap.ParseDN(old.DN) if err != nil { return nil, err } rdn := oldDN.RDNs[0] modDN, err := ldap.ParseDN(mod.DN) if err != nil { return nil, err } // This shouldn't happen if we ge here (TM) if !oldDN.EqualFold(modDN) { return nil, ldap.NewError(ldap.LDAPResultUnwillingToPerform, errors.New("DNs do not match")) } casefold := cases.Fold() newEntry = ldap.NewEntry(old.DN, map[string][]string{}) newEntry.Attributes = old.Attributes for _, c := range mod.Changes { nType := casefold.String(c.Modification.Type) switch c.Operation { case ldap.AddAttribute: newValues := entryApplyModAdd(newEntry.GetEqualFoldAttributeValues(nType), c.Modification.Vals) newEntry.Attributes = entryReplaceValues(newEntry.Attributes, c.Modification.Type, newValues) case ldap.ReplaceAttribute: // Modifies on RDN attributes need special care to make sure that the rdn Value is not removed for _, rdnAttr := range rdn.Attributes { if nType == casefold.String(rdnAttr.Type) { rdnPresent := false nRdnVal := casefold.String(rdnAttr.Value) for _, newVal := range c.Modification.Vals { if nRdnVal == casefold.String(newVal) { rdnPresent = true break } } if !rdnPresent { return nil, ldap.NewError(ldap.LDAPResultNotAllowedOnRDN, errors.New("")) } } } newEntry.Attributes = entryReplaceValues(newEntry.Attributes, c.Modification.Type, c.Modification.Vals) case ldap.DeleteAttribute: for _, rdnAttr := range rdn.Attributes { // Modifies on RDN attributes need special care if nType == casefold.String(rdnAttr.Type) { if len(c.Modification.Vals) == 0 { return nil, ldap.NewError(ldap.LDAPResultNotAllowedOnRDN, errors.New("")) } nRdnVal := casefold.String(rdnAttr.Value) for _, delVal := range c.Modification.Vals { if nRdnVal == casefold.String(delVal) { return nil, ldap.NewError(ldap.LDAPResultNotAllowedOnRDN, errors.New("")) } } } } newValues := entryApplyModDelete(old.GetEqualFoldAttributeValues(nType), c.Modification.Vals) newEntry.Attributes = entryReplaceValues(newEntry.Attributes, c.Modification.Type, newValues) } } return newEntry, nil } func entryReplaceValues(ea []*ldap.EntryAttribute, attrType string, newValues []string) (updatedAttrs []*ldap.EntryAttribute) { casefold := cases.Fold() nType := casefold.String(attrType) updated := false for _, attr := range ea { if casefold.String(attr.Name) == nType { updated = true if len(newValues) == 0 { continue } updatedAttrs = append(updatedAttrs, ldap.NewEntryAttribute(attr.Name, newValues)) } else { updatedAttrs = append(updatedAttrs, attr) } } if !updated { if len(newValues) != 0 { updatedAttrs = append(updatedAttrs, ldap.NewEntryAttribute(attrType, newValues)) } } return updatedAttrs } func entryApplyModAdd(curVals, addVals []string) (newVals []string) { newVals = curVals casefold := cases.Fold() for _, newVal := range addVals { present := false for _, val := range curVals { if casefold.String(newVal) == casefold.String(val) { present = true break } } if !present { newVals = append(newVals, newVal) } } return newVals } func entryApplyModDelete(curVals, delVals []string) (newVals []string) { casefold := cases.Fold() if len(delVals) == 0 { return []string{} } for _, curVal := range curVals { nCurVal := casefold.String(curVal) keep := true for _, del := range delVals { if nCurVal == casefold.String(del) { keep = false break } } if keep { newVals = append(newVals, curVal) } } return newVals } func EntryFromAddRequest(add *ldap.AddRequest) *ldap.Entry { attrs := map[string][]string{} for _, a := range add.Attributes { attrs[a.Type] = a.Vals } return ldap.NewEntry(add.DN, attrs) }