diff --git a/core/src/test/java/org/springframework/ldap/core/MockDirContext.java b/core/src/test/java/org/springframework/ldap/core/MockDirContext.java new file mode 100644 index 00000000..b888534d --- /dev/null +++ b/core/src/test/java/org/springframework/ldap/core/MockDirContext.java @@ -0,0 +1,451 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.core; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.naming.Binding; +import javax.naming.Context; +import javax.naming.Name; +import javax.naming.NameAlreadyBoundException; +import javax.naming.NameClassPair; +import javax.naming.NameNotFoundException; +import javax.naming.NameParser; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.InvalidAttributeValueException; +import javax.naming.directory.ModificationItem; +import javax.naming.directory.SearchControls; +import javax.naming.directory.SearchResult; + +import org.springframework.ldap.support.LdapUtils; +import org.springframework.util.CollectionUtils; + +class MockDirContext implements DirContext { + + private boolean closed; + + private Map>> entries = new HashMap<>(); + + private Map objects = new HashMap<>(); + + private Map> require(Name name) throws NamingException { + return require(name.toString()); + } + + private Map> require(String name) throws NamingException { + if (!this.entries.containsKey(name)) { + throw new NameNotFoundException("name not found"); + } + return this.entries.get(name); + } + + private NameAwareAttributes toAttributes(Map> map) { + NameAwareAttributes attributes = new NameAwareAttributes(); + for (Map.Entry> entry : map.entrySet()) { + NameAwareAttribute attribute = new NameAwareAttribute(entry.getKey(), true); + entry.getValue().forEach(attribute::add); + attributes.put(attribute); + } + return attributes; + } + + private Map> toEntry(NameAwareAttributes attributes) { + Map> map = new HashMap<>(); + Iterator iterator = CollectionUtils.toIterator(attributes.getAll()); + while (iterator.hasNext()) { + NameAwareAttribute attribute = iterator.next(); + List values = new ArrayList<>(); + CollectionUtils.toIterator(attribute.getAll()).forEachRemaining(values::add); + map.put(attribute.getID(), values); + } + return map; + } + + @Override + public Attributes getAttributes(Name name) throws NamingException { + return toAttributes(require(name)); + } + + @Override + public Attributes getAttributes(String name) throws NamingException { + return toAttributes(require(name)); + } + + @Override + public Attributes getAttributes(Name name, String[] attrIds) throws NamingException { + Map> entry = require(name); + NameAwareAttributes toReturn = new NameAwareAttributes(); + for (String attrId : attrIds) { + entry.get(attrId); + toReturn.put(attrId, entry.get(attrId)); + } + return toReturn; + } + + @Override + public Attributes getAttributes(String name, String[] attrIds) throws NamingException { + return getAttributes(LdapUtils.newLdapName(name), attrIds); + } + + @Override + public void modifyAttributes(Name name, int mod_op, Attributes attrs) throws NamingException { + modifyAttributes(name.toString(), mod_op, attrs); + } + + @Override + public void modifyAttributes(String name, int mod_op, Attributes attrs) throws NamingException { + Map> entry = require(name); + Map> toModify = toEntry((NameAwareAttributes) attrs); + if (mod_op == DirContext.ADD_ATTRIBUTE) { + for (Map.Entry> attribute : toModify.entrySet()) { + List toAdd = attribute.getValue(); + if (toAdd.isEmpty()) { + throw new InvalidAttributeValueException("cannot add empty attribute"); + } + entry.computeIfPresent(attribute.getKey(), (k, v) -> { + v.addAll(toAdd); + return v; + }); + entry.computeIfAbsent(attribute.getKey(), (k) -> new ArrayList<>()).addAll(toAdd); + } + return; + } + if (mod_op == DirContext.REPLACE_ATTRIBUTE) { + for (Map.Entry> attribute : toModify.entrySet()) { + List toAdd = attribute.getValue(); + if (toAdd.isEmpty()) { + entry.remove(attribute.getKey()); + } + else { + entry.put(attribute.getKey(), toAdd); + } + } + return; + } + if (mod_op == DirContext.REMOVE_ATTRIBUTE) { + for (Map.Entry> attribute : toModify.entrySet()) { + List toRemove = attribute.getValue(); + entry.computeIfPresent(attribute.getKey(), (k, v) -> { + v.removeAll(toRemove); + return v; + }); + if (entry.get(attribute.getKey()).isEmpty()) { + entry.remove(attribute.getKey()); + } + } + } + } + + @Override + public void modifyAttributes(Name name, ModificationItem[] mods) throws NamingException { + modifyAttributes(name.toString(), mods); + } + + @Override + public void modifyAttributes(String name, ModificationItem[] mods) throws NamingException { + for (ModificationItem item : mods) { + NameAwareAttributes attributes = new NameAwareAttributes(); + attributes.put(item.getAttribute()); + modifyAttributes(name, item.getModificationOp(), attributes); + } + } + + @Override + public void bind(Name name, Object obj, Attributes attrs) throws NamingException { + bind(name.toString(), obj, attrs); + } + + @Override + public void bind(String name, Object obj, Attributes attrs) throws NamingException { + if (this.objects.containsKey(name)) { + throw new NameAlreadyBoundException("name already bound"); + } + if (obj != null) { + this.objects.put(name, obj); + } + if (attrs.size() > 0) { + Map> entry = toEntry((NameAwareAttributes) attrs); + this.entries.put(name, entry); + return; + } + if (obj instanceof DirContext ctx) { + Map> entry = toEntry((NameAwareAttributes) ctx.getAttributes(name)); + this.entries.put(name, entry); + } + } + + @Override + public void rebind(Name name, Object obj, Attributes attrs) throws NamingException { + + } + + @Override + public void rebind(String name, Object obj, Attributes attrs) throws NamingException { + + } + + @Override + public DirContext createSubcontext(Name name, Attributes attrs) throws NamingException { + return null; + } + + @Override + public DirContext createSubcontext(String name, Attributes attrs) throws NamingException { + return null; + } + + @Override + public DirContext getSchema(Name name) throws NamingException { + return null; + } + + @Override + public DirContext getSchema(String name) throws NamingException { + return null; + } + + @Override + public DirContext getSchemaClassDefinition(Name name) throws NamingException { + return null; + } + + @Override + public DirContext getSchemaClassDefinition(String name) throws NamingException { + return null; + } + + @Override + public NamingEnumeration search(Name name, Attributes matchingAttributes, String[] attributesToReturn) + throws NamingException { + return null; + } + + @Override + public NamingEnumeration search(String name, Attributes matchingAttributes, + String[] attributesToReturn) throws NamingException { + return null; + } + + @Override + public NamingEnumeration search(Name name, Attributes matchingAttributes) throws NamingException { + return null; + } + + @Override + public NamingEnumeration search(String name, Attributes matchingAttributes) throws NamingException { + return null; + } + + @Override + public NamingEnumeration search(Name name, String filter, SearchControls cons) + throws NamingException { + return null; + } + + @Override + public NamingEnumeration search(String name, String filter, SearchControls cons) + throws NamingException { + return null; + } + + @Override + public NamingEnumeration search(Name name, String filterExpr, Object[] filterArgs, + SearchControls cons) throws NamingException { + return null; + } + + @Override + public NamingEnumeration search(String name, String filterExpr, Object[] filterArgs, + SearchControls cons) throws NamingException { + return null; + } + + @Override + public Object lookup(Name name) throws NamingException { + return lookup(name.toString()); + } + + @Override + public Object lookup(String name) throws NamingException { + return this.objects.get(name); + } + + @Override + public void bind(Name name, Object obj) throws NamingException { + bind(name.toString(), obj); + } + + @Override + public void bind(String name, Object obj) throws NamingException { + bind(name, obj, toAttributes(Map.of(name, Collections.emptyList()))); + } + + @Override + public void rebind(Name name, Object obj) throws NamingException { + rebind(name.toString(), obj); + } + + @Override + public void rebind(String name, Object obj) throws NamingException { + if (!this.objects.containsKey(name)) { + throw new NameNotFoundException("name not found"); + } + this.objects.put(name, obj); + if (obj instanceof DirContext ctx) { + this.entries.put(name, toEntry((NameAwareAttributes) ctx.getAttributes(name))); + } + } + + @Override + public void unbind(Name name) throws NamingException { + unbind(name.toString()); + } + + @Override + public void unbind(String name) throws NamingException { + this.entries.remove(name); + this.objects.remove(name); + } + + @Override + public void rename(Name oldName, Name newName) throws NamingException { + rename(oldName.toString(), newName.toString()); + } + + @Override + public void rename(String oldName, String newName) throws NamingException { + if (this.entries.containsKey(newName)) { + throw new NameAlreadyBoundException("name already bound"); + } + if (!this.entries.containsKey(oldName)) { + throw new NameNotFoundException("name not found"); + } + Map> attributes = this.entries.remove(oldName); + Object object = this.entries.remove(oldName); + this.entries.put(newName, attributes); + this.objects.put(newName, object); + } + + @Override + public NamingEnumeration list(Name name) throws NamingException { + return null; + } + + @Override + public NamingEnumeration list(String name) throws NamingException { + return null; + } + + @Override + public NamingEnumeration listBindings(Name name) throws NamingException { + return null; + } + + @Override + public NamingEnumeration listBindings(String name) throws NamingException { + return null; + } + + @Override + public void destroySubcontext(Name name) throws NamingException { + + } + + @Override + public void destroySubcontext(String name) throws NamingException { + + } + + @Override + public Context createSubcontext(Name name) throws NamingException { + return null; + } + + @Override + public Context createSubcontext(String name) throws NamingException { + return null; + } + + @Override + public Object lookupLink(Name name) throws NamingException { + return null; + } + + @Override + public Object lookupLink(String name) throws NamingException { + return null; + } + + @Override + public NameParser getNameParser(Name name) throws NamingException { + return null; + } + + @Override + public NameParser getNameParser(String name) throws NamingException { + return null; + } + + @Override + public Name composeName(Name name, Name prefix) throws NamingException { + return null; + } + + @Override + public String composeName(String name, String prefix) throws NamingException { + return ""; + } + + @Override + public Object addToEnvironment(String propName, Object propVal) throws NamingException { + return null; + } + + @Override + public Object removeFromEnvironment(String propName) throws NamingException { + return null; + } + + @Override + public Hashtable getEnvironment() throws NamingException { + return null; + } + + @Override + public void close() throws NamingException { + this.closed = true; + } + + boolean isClosed() { + return this.closed; + } + + @Override + public String getNameInNamespace() throws NamingException { + return ""; + } + +} diff --git a/core/src/test/java/org/springframework/ldap/core/TestDirContextAdapters.java b/core/src/test/java/org/springframework/ldap/core/TestDirContextAdapters.java new file mode 100644 index 00000000..1b0a8419 --- /dev/null +++ b/core/src/test/java/org/springframework/ldap/core/TestDirContextAdapters.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.core; + +import javax.naming.Name; + +final class TestDirContextAdapters { + + private TestDirContextAdapters() { + + } + + static DirContextAdapter forUpdate(Name dn) { + return forUpdate(dn, TestNameAwareAttributes.attributes()); + } + + static DirContextAdapter forUpdate(Name dn, NameAwareAttributes attributes) { + DirContextAdapter adapter = new DirContextAdapter(attributes, dn); + adapter.setUpdateMode(true); + return adapter; + } + +} diff --git a/core/src/test/java/org/springframework/ldap/core/TestModificationItems.java b/core/src/test/java/org/springframework/ldap/core/TestModificationItems.java new file mode 100644 index 00000000..66585abd --- /dev/null +++ b/core/src/test/java/org/springframework/ldap/core/TestModificationItems.java @@ -0,0 +1,59 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.core; + +import javax.naming.directory.Attribute; +import javax.naming.directory.ModificationItem; + +import static org.assertj.core.api.Assertions.assertThat; + +final class TestModificationItems { + + private TestModificationItems() { + + } + + static ModificationItem add(String name, Object... values) { + return add(TestNameAwareAttributes.attribute(name, values)); + } + + static ModificationItem add(Attribute attribute) { + return new ModificationItem(MockDirContext.ADD_ATTRIBUTE, attribute); + } + + static ModificationItem replace(String name, Object... values) { + return remove(name, TestNameAwareAttributes.attribute(name, values)); + } + + static ModificationItem replace(Attribute attribute) { + return new ModificationItem(MockDirContext.REPLACE_ATTRIBUTE, attribute); + } + + static ModificationItem remove(String name, Object... values) { + return remove(TestNameAwareAttributes.attribute(name, values)); + } + + static ModificationItem remove(Attribute attribute) { + return new ModificationItem(MockDirContext.REMOVE_ATTRIBUTE, attribute); + } + + static void assertEquals(ModificationItem actual, ModificationItem expected) { + assertThat(actual.getAttribute()).isEqualTo(expected.getAttribute()); + assertThat(actual.getModificationOp()).isEqualTo(expected.getModificationOp()); + } + +} diff --git a/core/src/test/java/org/springframework/ldap/core/TestNameAwareAttributes.java b/core/src/test/java/org/springframework/ldap/core/TestNameAwareAttributes.java new file mode 100644 index 00000000..2189512a --- /dev/null +++ b/core/src/test/java/org/springframework/ldap/core/TestNameAwareAttributes.java @@ -0,0 +1,51 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.core; + +final class TestNameAwareAttributes { + + private TestNameAwareAttributes() { + + } + + static NameAwareAttributes attributes() { + NameAwareAttributes attributes = new NameAwareAttributes(); + attributes.put(attribute()); + return attributes; + } + + static NameAwareAttributes attributes(NameAwareAttribute... list) { + NameAwareAttributes attributes = new NameAwareAttributes(); + for (NameAwareAttribute attribute : list) { + attributes.put(attribute); + } + return attributes; + } + + static NameAwareAttribute attribute() { + return new NameAwareAttribute("name", "value"); + } + + static NameAwareAttribute attribute(String name, Object... values) { + NameAwareAttribute attribute = new NameAwareAttribute(name); + for (Object value : values) { + attribute.add(value); + } + return attribute; + } + +}