/* * The contents of this file are subject to the terms of the Common Development and * Distribution License (the License). You may not use this file except in compliance with the * License. * * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the * specific language governing permission and limitations under the License. * * When distributing Covered Software, include this CDDL Header Notice in each file and include * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL * Header, with the fields enclosed by brackets [] replaced by your own identifying * information: "Portions copyright [year] [name of copyright owner]". * * Copyright 2026 3A Systems, LLC. */ package org.opends.guitools.controlpanel.ui; import static org.assertj.core.api.Assertions.assertThat; import java.io.IOException; import java.io.StringReader; import java.util.ArrayList; import java.util.List; import org.forgerock.opendj.ldap.Attribute; import org.forgerock.opendj.ldap.ByteString; import org.forgerock.opendj.ldap.DN; import org.forgerock.opendj.ldap.Entry; import org.forgerock.opendj.ldap.LinkedHashMapEntry; import org.forgerock.opendj.ldap.schema.Schema; import org.forgerock.opendj.ldif.LDIFEntryReader; import org.opends.server.DirectoryServerTestCase; import org.testng.annotations.Test; /** Tests the LDIF generated by {@link DuplicateEntryPanel}. */ @SuppressWarnings("javadoc") public class DuplicateEntryPanelTestCase extends DirectoryServerTestCase { private static final Schema SCHEMA = Schema.getCoreSchema(); private static final String NEW_DN = "uid=user.2,ou=people,dc=example,dc=com"; /** An entry whose RDN attribute is single valued and which holds a password. */ private static Entry userEntry() { return new LinkedHashMapEntry( "dn: uid=user.1,ou=people,dc=example,dc=com", "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "uid: user.1", "userPassword: {SSHA}originalHashOfTheEntryBeingDuplicated", "cn: User 1", "sn: One"); } /** * When the user types no password, the original password must not be copied and no empty line * must be written: an empty line terminates the LDIF record and every attribute written after * the password would be silently dropped. */ @Test public void testEmptyPasswordDoesNotTruncateTheEntry() throws Exception { String ldif = DuplicateEntryPanel.getLDIF(userEntry(), NEW_DN, "uid", "", SCHEMA); assertThat(ldif).doesNotContain("\n\n"); Entry duplicate = readEntry(ldif); assertThat((Object) duplicate.getName()).isEqualTo(DN.valueOf(NEW_DN)); assertThat(valuesOf(duplicate, "userPassword")).isEmpty(); assertThat(valuesOf(duplicate, "objectClass")) .containsExactly("top", "person", "organizationalPerson", "inetOrgPerson"); assertThat(valuesOf(duplicate, "uid")).containsExactly("user.2"); assertThat(valuesOf(duplicate, "cn")).containsExactly("User 1"); assertThat(valuesOf(duplicate, "sn")).containsExactly("One"); } /** The password of the duplicated entry is the one typed by the user, not the original hash. */ @Test public void testTypedPasswordReplacesTheOriginalOne() throws Exception { String ldif = DuplicateEntryPanel.getLDIF(userEntry(), NEW_DN, "uid", "typedPassword", SCHEMA); Entry duplicate = readEntry(ldif); assertThat(valuesOf(duplicate, "userPassword")).containsExactly("typedPassword"); assertThat(valuesOf(duplicate, "sn")).containsExactly("One"); } /** The value of the RDN of the new entry replaces the value of the RDN of the original entry. */ @Test public void testSingleValuedRDNAttributeUsesTheNewValue() throws Exception { String ldif = DuplicateEntryPanel.getLDIF(userEntry(), NEW_DN, "uid", "", SCHEMA); assertThat(valuesOf(readEntry(ldif), "uid")).containsExactly("user.2"); } /** * The attributes used in an RDN have a case insensitive equality matching rule, so the value * stored in the entry may differ from the value written in the DN. The old value must be * replaced nonetheless, otherwise the duplicated entry keeps the value of the original RDN. */ @Test public void testMultiValuedRDNAttributeReplacesTheOldValueCaseInsensitively() throws Exception { Entry entry = new LinkedHashMapEntry( "dn: cn=John Smith,ou=people,dc=example,dc=com", "objectClass: top", "objectClass: person", "cn: john smith", "cn: Johnny", "sn: Smith"); String ldif = DuplicateEntryPanel.getLDIF(entry, "cn=John Smith-1,ou=people,dc=example,dc=com", "cn", "", SCHEMA); assertThat(valuesOf(readEntry(ldif), "cn")).containsExactlyInAnyOrder("John Smith-1", "Johnny"); } /** Attributes the user cannot modify must not be copied into the duplicated entry. */ @Test public void testNoUserModificationAttributesAreNotCopied() throws Exception { Entry entry = new LinkedHashMapEntry( "dn: uid=user.1,ou=people,dc=example,dc=com", "objectClass: top", "objectClass: person", "uid: user.1", "createTimestamp: 20260101000000Z", "sn: One"); String ldif = DuplicateEntryPanel.getLDIF(entry, NEW_DN, "uid", "", SCHEMA); Entry duplicate = readEntry(ldif); assertThat(valuesOf(duplicate, "createTimestamp")).isEmpty(); assertThat(valuesOf(duplicate, "sn")).containsExactly("One"); } /** Attributes with a binary syntax must be base64 encoded. */ @Test public void testBinaryAttributesAreBase64Encoded() throws Exception { Entry entry = new LinkedHashMapEntry(DN.valueOf("uid=user.1,ou=people,dc=example,dc=com")) .addAttribute("objectClass", "top", "person") .addAttribute("uid", "user.1") .addAttribute("sn", "One") .addAttribute("userCertificate", ByteString.wrap(new byte[] { 0x30, 0x00, (byte) 0xFF })); String ldif = DuplicateEntryPanel.getLDIF(entry, NEW_DN, "uid", "", SCHEMA); // "MAD/" is the base64 encoding of the three bytes above. assertThat(ldif).contains("userCertificate:: MAD/"); assertThat(valuesOf(readEntry(ldif), "sn")).containsExactly("One"); } /** * Reads the generated LDIF the same way {@code AbstractNewEntryPanel.getEntry()} does: an empty * line terminates the record, so anything written after one is not part of the entry that is * added to the server. */ private static Entry readEntry(String ldif) throws IOException { try (LDIFEntryReader reader = new LDIFEntryReader(new StringReader(ldif))) { return reader.readEntry(); } } private static List valuesOf(Entry entry, String attrName) { List values = new ArrayList<>(); Attribute attr = entry.getAttribute(attrName); if (attr != null) { for (ByteString value : attr) { values.add(value.toString()); } } return values; } }