Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8196770: Add JNDI test com/sun/jndi/ldap/blits/AddTests/AddNewEntry.java #583

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions jdk/test/com/sun/jndi/ldap/blits/AddTests/AddNewEntry.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

/*
* @test
* @bug 8196770
* @summary Verify capability to add a new entry to the directory using the
* ADD operation.
* @library ../../lib/
* @build LDAPServer LDAPTestUtils
* @run main/othervm AddNewEntry
*/

import javax.naming.NamingEnumeration;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.SearchControls;
import java.net.ServerSocket;
import java.util.Hashtable;

public class AddNewEntry {

public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(0);

Hashtable<Object, Object> env;

// initialize test
env = LDAPTestUtils
.initEnv(serverSocket, AddNewEntry.class.getName(), args, true);

/* Build attribute set */
String[] ids = { "objectClass", "sn", "cn", "telephoneNumber", "mail",
"description", "uid" };
Attribute objectClass = new BasicAttribute(ids[0]);
objectClass.add("top");
objectClass.add("person");
objectClass.add("organizationalPerson");
objectClass.add("inetOrgPerson");

Attribute sn = new BasicAttribute(ids[1], "Powers");
Attribute cn = new BasicAttribute(ids[2],
"Austin \\\"Danger\\\" Powers");
Attribute telephoneNumber = new BasicAttribute(ids[3], "+44 582 10101");
Attribute mail = new BasicAttribute(ids[4], "secret_agent_man@imc.org");
Attribute description = new BasicAttribute(ids[5], "Yea Baby!!");
description.add("Behave!");
Attribute uid = new BasicAttribute(ids[6], "secret_agent_man");

Attributes attrs = new BasicAttributes();
attrs.put(objectClass);
attrs.put(sn);
attrs.put(cn);
attrs.put(telephoneNumber);
attrs.put(mail);
attrs.put(description);
attrs.put(uid);

DirContext ctx = null;
String[] bases = new String[] { (String) env.get("client"),
(String) env.get("vendor"), "Add" };
String baseDN = LDAPTestUtils.buildDN(bases, (String) env.get("root"));
String entryDN = "cn=Austin Powers," + baseDN;
String expect = ""; // relative name

try {
// connect to server
ctx = new InitialDirContext(env);

// add entry
ctx.createSubcontext(entryDN, attrs);

// specify base search
SearchControls constraints = new SearchControls();
constraints.setSearchScope(SearchControls.OBJECT_SCOPE);

NamingEnumeration results = ctx
.search(entryDN, "(objectclass=*)", constraints);

int found = LDAPTestUtils.checkResult(results, expect);

if (found != 1) {
throw new RuntimeException(
"Check result failed, expect found 1 but actual is "
+ found);
}

} finally {
LDAPTestUtils.cleanupSubcontext(ctx, entryDN);
LDAPTestUtils.cleanup(ctx);
}
}
}
135 changes: 135 additions & 0 deletions jdk/test/com/sun/jndi/ldap/blits/AddTests/AddNewEntry.ldap
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#
# Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# This code is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 2 only, as
# published by the Free Software Foundation.
#
# This code is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# version 2 for more details (a copy is included in the LICENSE file that
# accompanied this code).
#
# You should have received a copy of the GNU General Public License version
# 2 along with this work; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
# or visit www.oracle.com if you need additional information or have any
# questions.
#

################################################################################
# Capture file for AddNewEntry.java
#
# NOTE: This hexadecimal dump of LDAP protocol messages was generated by
# running the AddNewEntry application program against a real LDAP
# server and setting the JNDI/LDAP environment property:
# com.sun.jndi.ldap.trace.ber to activate LDAP message tracing.
#
################################################################################


# LDAP BindRequest

0000: 30 27 02 01 01 60 22 02 01 03 04 13 63 6E 3D 61 0'...`".....cn=a
0010: 64 6D 69 6E 2C 6F 3D 49 4D 43 2C 63 3D 55 53 80 dmin,o=IMC,c=US.
0020: 08 73 65 63 72 65 74 39 39 .secret99


# LDAP BindResponse

0000: 30 0C 02 01 01 61 07 0A 01 00 04 00 04 00 0....a........


# LDAP AddRequest

0000: 30 82 01 5F 02 01 02 68 82 01 3B 04 38 63 6E 3D 0.._...h..;.8cn=
0010: 41 75 73 74 69 6E 20 50 6F 77 65 72 73 2C 6F 75 Austin Powers,ou
0020: 3D 43 6C 69 65 6E 74 31 2C 6F 75 3D 56 65 6E 64 =Client1,ou=Vend
0030: 6F 72 31 2C 6F 75 3D 41 64 64 2C 6F 3D 49 4D 43 or1,ou=Add,o=IMC
0040: 2C 63 3D 55 53 30 81 FE 30 41 04 0B 6F 62 6A 65 ,c=US0..0A..obje
0050: 63 74 43 6C 61 73 73 31 32 04 03 74 6F 70 04 06 ctClass12..top..
0060: 70 65 72 73 6F 6E 04 14 6F 72 67 61 6E 69 7A 61 person..organiza
0070: 74 69 6F 6E 61 6C 50 65 72 73 6F 6E 04 0D 69 6E tionalPerson..in
0080: 65 74 4F 72 67 50 65 72 73 6F 6E 30 22 04 04 6D etOrgPerson0"..m
0090: 61 69 6C 31 1A 04 18 73 65 63 72 65 74 5F 61 67 ail1...secret_ag
00A0: 65 6E 74 5F 6D 61 6E 40 69 6D 63 2E 6F 72 67 30 ent_man@imc.org0
00B0: 19 04 03 75 69 64 31 12 04 10 73 65 63 72 65 74 ...uid1...secret
00C0: 5F 61 67 65 6E 74 5F 6D 61 6E 30 24 04 0B 64 65 _agent_man0$..de
00D0: 73 63 72 69 70 74 69 6F 6E 31 15 04 0A 59 65 61 scription1...Yea
00E0: 20 42 61 62 79 21 21 04 07 42 65 68 61 76 65 21 Baby!!..Behave!
00F0: 30 0E 04 02 73 6E 31 08 04 06 50 6F 77 65 72 73 0...sn1...Powers
0100: 30 22 04 0F 74 65 6C 65 70 68 6F 6E 65 4E 75 6D 0"..telephoneNum
0110: 62 65 72 31 0F 04 0D 2B 34 34 20 35 38 32 20 31 ber1...+44 582 1
0120: 30 31 30 31 30 20 04 02 63 6E 31 1A 04 18 41 75 01010 ..cn1...Au
0130: 73 74 69 6E 20 5C 22 44 61 6E 67 65 72 5C 22 20 stin \"Danger\"
0140: 50 6F 77 65 72 73 A0 1B 30 19 04 17 32 2E 31 36 Powers..0...2.16
0150: 2E 38 34 30 2E 31 2E 31 31 33 37 33 30 2E 33 2E .840.1.113730.3.
0160: 34 2E 32 4.2


# LDAP AddResponse

0000: 30 0C 02 01 02 69 07 0A 01 00 04 00 04 00 0....i........


# LDAP SearchRequest

0000: 30 7A 02 01 03 63 58 04 38 63 6E 3D 41 75 73 74 0z...cX.8cn=Aust
0010: 69 6E 20 50 6F 77 65 72 73 2C 6F 75 3D 43 6C 69 in Powers,ou=Cli
0020: 65 6E 74 31 2C 6F 75 3D 56 65 6E 64 6F 72 31 2C ent1,ou=Vendor1,
0030: 6F 75 3D 41 64 64 2C 6F 3D 49 4D 43 2C 63 3D 55 ou=Add,o=IMC,c=U
0040: 53 0A 01 00 0A 01 03 02 01 00 02 01 00 01 01 00 S...............
0050: 87 0B 6F 62 6A 65 63 74 63 6C 61 73 73 30 00 A0 ..objectclass0..
0060: 1B 30 19 04 17 32 2E 31 36 2E 38 34 30 2E 31 2E .0...2.16.840.1.
0070: 31 31 33 37 33 30 2E 33 2E 34 2E 32 113730.3.4.2


# LDAP SearchResultEntry

0000: 30 82 01 52 02 01 03 64 82 01 4B 04 38 63 6E 3D 0..R...d..K.8cn=
0010: 41 75 73 74 69 6E 20 50 6F 77 65 72 73 2C 6F 75 Austin Powers,ou
0020: 3D 43 6C 69 65 6E 74 31 2C 6F 75 3D 56 65 6E 64 =Client1,ou=Vend
0030: 6F 72 31 2C 6F 75 3D 41 64 64 2C 6F 3D 49 4D 43 or1,ou=Add,o=IMC
0040: 2C 63 3D 55 53 30 82 01 0D 30 41 04 0B 6F 62 6A ,c=US0...0A..obj
0050: 65 63 74 43 6C 61 73 73 31 32 04 03 74 6F 70 04 ectClass12..top.
0060: 06 70 65 72 73 6F 6E 04 14 6F 72 67 61 6E 69 7A .person..organiz
0070: 61 74 69 6F 6E 61 6C 50 65 72 73 6F 6E 04 0D 69 ationalPerson..i
0080: 6E 65 74 4F 72 67 50 65 72 73 6F 6E 30 22 04 04 netOrgPerson0"..
0090: 6D 61 69 6C 31 1A 04 18 73 65 63 72 65 74 5F 61 mail1...secret_a
00A0: 67 65 6E 74 5F 6D 61 6E 40 69 6D 63 2E 6F 72 67 gent_man@imc.org
00B0: 30 19 04 03 75 69 64 31 12 04 10 73 65 63 72 65 0...uid1...secre
00C0: 74 5F 61 67 65 6E 74 5F 6D 61 6E 30 24 04 0B 64 t_agent_man0$..d
00D0: 65 73 63 72 69 70 74 69 6F 6E 31 15 04 0A 59 65 escription1...Ye
00E0: 61 20 42 61 62 79 21 21 04 07 42 65 68 61 76 65 a Baby!!..Behave
00F0: 21 30 0E 04 02 73 6E 31 08 04 06 50 6F 77 65 72 !0...sn1...Power
0100: 73 30 22 04 0F 74 65 6C 65 70 68 6F 6E 65 4E 75 s0"..telephoneNu
0110: 6D 62 65 72 31 0F 04 0D 2B 34 34 20 35 38 32 20 mber1...+44 582
0120: 31 30 31 30 31 30 2F 04 02 63 6E 31 29 04 18 41 101010/..cn1)..A
0130: 75 73 74 69 6E 20 5C 22 44 61 6E 67 65 72 5C 22 ustin \"Danger\"
0140: 20 50 6F 77 65 72 73 04 0D 41 75 73 74 69 6E 20 Powers..Austin
0150: 50 6F 77 65 72 73 Powers


# LDAP SearchResultDone

0000: 30 0C 02 01 03 65 07 0A 01 00 04 00 04 00 0....e........


# LDAP DeleteRequest

0000: 30 5A 02 01 04 4A 38 63 6E 3D 41 75 73 74 69 6E 0Z...J8cn=Austin
0010: 20 50 6F 77 65 72 73 2C 6F 75 3D 43 6C 69 65 6E Powers,ou=Clien
0020: 74 31 2C 6F 75 3D 56 65 6E 64 6F 72 31 2C 6F 75 t1,ou=Vendor1,ou
0030: 3D 41 64 64 2C 6F 3D 49 4D 43 2C 63 3D 55 53 A0 =Add,o=IMC,c=US.
0040: 1B 30 19 04 17 32 2E 31 36 2E 38 34 30 2E 31 2E .0...2.16.840.1.
0050: 31 31 33 37 33 30 2E 33 2E 34 2E 32 113730.3.4.2


# LDAP DeleteResponse

0000: 30 0C 02 01 04 6B 07 0A 01 00 04 00 04 00 0....k........

296 changes: 296 additions & 0 deletions jdk/test/com/sun/jndi/ldap/lib/LDAPServer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,296 @@
/*
* Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

package test;

import java.io.*;
import java.nio.file.*;
import java.math.BigInteger;
import java.net.*;
import java.util.*;
import java.util.regex.*;

/*
* A dummy LDAP server.
*
* Loads a sequence of LDAP messages from a capture file into its cache.
* It listens for LDAP requests, finds a match in its cache and sends the
* corresponding LDAP responses.
*
* The capture file contains an LDAP protocol exchange in the hexadecimal
* dump format emitted by sun.misc.HexDumpEncoder:
*
* xxxx: 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff ................
*
* Typically, LDAP protocol exchange is generated by running the LDAP client
* application program against a real LDAP server and setting the JNDI/LDAP
* environment property: com.sun.jndi.ldap.trace.ber to activate LDAP message
* tracing.
*/
public class LDAPServer {

/*
* A cache of LDAP requests and responses.
* Messages with the same ID are stored in a list.
* The first element in the list is the LDAP request,
* the remaining elements are the LDAP responses.
*/
private final Map<Integer,List<byte[]>> cache = new HashMap<>();

public LDAPServer(ServerSocket serverSocket, String filename)
throws Exception {

System.out.println("LDAPServer: Loading LDAP cache from: " + filename);
loadCaptureFile(filename);

System.out.println("LDAPServer: listening on port " +
serverSocket.getLocalPort());

try (Socket clientSocket = serverSocket.accept();
OutputStream out = clientSocket.getOutputStream();
InputStream in = clientSocket.getInputStream();) {

byte[] inBuffer = new byte[8192];
int count;

while ((count = in.read(inBuffer)) > 0) {
byte[] request = Arrays.copyOf(inBuffer, count);
int[] ids = getIDs(request);
int messageID = ids[0];
String operation = getOperation(ids[1]);
System.out.println("\nLDAPServer: received LDAP " + operation +
" [message ID " + messageID + "]");

List<byte[]> encodings = cache.get(messageID);
if (encodings == null ||
(!Arrays.equals(request, encodings.get(0)))) {
throw new Exception(
"LDAPServer: ERROR: received an LDAP " + operation +
" (ID=" + messageID + ") not present in cache");
}

for (int i = 1; i < encodings.size(); i++) {
// skip the request (at index 0)
byte[] response = encodings.get(i);
out.write(response, 0, response.length);
ids = getIDs(response);
System.out.println("\nLDAPServer: Sent LDAP " +
getOperation(ids[1]) + " [message ID " + ids[0] + "]");
}
}
} catch (IOException e) {
System.out.println("LDAPServer: ERROR: " + e);
throw e;
}

System.out.println("\n[LDAP server exited normally]");
}

/*
* Load a capture file containing an LDAP protocol exchange in the
* hexadecimal dump format emitted by sun.misc.HexDumpEncoder:
*
* xxxx: 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff ................
*/
private void loadCaptureFile(String filename) throws IOException {
StringBuilder hexString = new StringBuilder();
String pattern = "(....): (..) (..) (..) (..) (..) (..) (..) (..) (..) (..) (..) (..) (..) (..) (..) (..).*";

try (Scanner fileScanner = new Scanner(Paths.get(filename))) {
while (fileScanner.hasNextLine()){

try (Scanner lineScanner =
new Scanner(fileScanner.nextLine())) {
if (lineScanner.findInLine(pattern) == null) {
continue;
}
MatchResult result = lineScanner.match();
for (int i = 1; i <= result.groupCount(); i++) {
String digits = result.group(i);
if (digits.length() == 4) {
if (digits.equals("0000")) { // start-of-message
if (hexString.length() > 0) {
addToCache(hexString.toString());
hexString = new StringBuilder();
}
}
continue;
} else if (digits.equals(" ")) { // short message
continue;
}
hexString.append(digits);
}
}
}
}
addToCache(hexString.toString());
}

/*
* Add an LDAP encoding to the cache (by messageID key).
*/
private void addToCache(String hexString) throws IOException {
byte[] encoding = parseHexBinary(hexString);
int[] ids = getIDs(encoding);
int messageID = ids[0];
List<byte[]> encodings = cache.get(messageID);
if (encodings == null) {
encodings = new ArrayList<>();
}
System.out.println(" adding LDAP " + getOperation(ids[1]) +
" with message ID " + messageID + " to the cache");
encodings.add(encoding);
cache.put(messageID, encodings);
}

/*
* Extracts the message ID and operation ID from an LDAP protocol encoding
* and returns them in a 2-element array of integers.
*/
private static int[] getIDs(byte[] encoding) throws IOException {
if (encoding[0] != 0x30) {
throw new IOException("Error: bad LDAP encoding in capture file: " +
"expected ASN.1 SEQUENCE tag (0x30), encountered " +
encoding[0]);
}

int index = 2;
if ((encoding[1] & 0x80) == 0x80) {
index += (encoding[1] & 0x0F);
}

if (encoding[index] != 0x02) {
throw new IOException("Error: bad LDAP encoding in capture file: " +
"expected ASN.1 INTEGER tag (0x02), encountered " +
encoding[index]);
}
int length = encoding[index + 1];
index += 2;
int messageID =
new BigInteger(1,
Arrays.copyOfRange(encoding, index, index + length)).intValue();
index += length;
int operationID = encoding[index];

return new int[]{messageID, operationID};
}

/*
* Maps an LDAP operation ID to a string description
*/
private static String getOperation(int operationID) {
switch (operationID) {
case 0x60:
return "BindRequest"; // [APPLICATION 0]
case 0x61:
return "BindResponse"; // [APPLICATION 1]
case 0x42:
return "UnbindRequest"; // [APPLICATION 2]
case 0x63:
return "SearchRequest"; // [APPLICATION 3]
case 0x64:
return "SearchResultEntry"; // [APPLICATION 4]
case 0x65:
return "SearchResultDone"; // [APPLICATION 5]
case 0x66:
return "ModifyRequest"; // [APPLICATION 6]
case 0x67:
return "ModifyResponse"; // [APPLICATION 7]
case 0x68:
return "AddRequest"; // [APPLICATION 8]
case 0x69:
return "AddResponse"; // [APPLICATION 9]
case 0x4A:
return "DeleteRequest"; // [APPLICATION 10]
case 0x6B:
return "DeleteResponse"; // [APPLICATION 11]
case 0x6C:
return "ModifyDNRequest"; // [APPLICATION 12]
case 0x6D:
return "ModifyDNResponse"; // [APPLICATION 13]
case 0x6E:
return "CompareRequest"; // [APPLICATION 14]
case 0x6F:
return "CompareResponse"; // [APPLICATION 15]
case 0x50:
return "AbandonRequest"; // [APPLICATION 16]
case 0x73:
return "SearchResultReference"; // [APPLICATION 19]
case 0x77:
return "ExtendedRequest"; // [APPLICATION 23]
case 0x78:
return "ExtendedResponse"; // [APPLICATION 24]
case 0x79:
return "IntermediateResponse"; // [APPLICATION 25]
default:
return "Unknown";
}
}

public static byte[] parseHexBinary(String s) {

final int len = s.length();

// "111" is not a valid hex encoding.
if (len % 2 != 0) {
throw new IllegalArgumentException("hexBinary needs to be even-length: " + s);
}

byte[] out = new byte[len / 2];

for (int i = 0; i < len; i += 2) {
int h = hexToBin(s.charAt(i));
int l = hexToBin(s.charAt(i + 1));
if (h == -1 || l == -1) {
throw new IllegalArgumentException("contains illegal character for hexBinary: " + s);
}

out[i / 2] = (byte) (h * 16 + l);
}

return out;
}

private static int hexToBin(char ch) {
if ('0' <= ch && ch <= '9') {
return ch - '0';
}
if ('A' <= ch && ch <= 'F') {
return ch - 'A' + 10;
}
if ('a' <= ch && ch <= 'f') {
return ch - 'a' + 10;
}
return -1;
}
private static final char[] hexCode = "0123456789ABCDEF".toCharArray();

public static String printHexBinary(byte[] data) {
StringBuilder r = new StringBuilder(data.length * 2);
for (byte b : data) {
r.append(hexCode[(b >> 4) & 0xF]);
r.append(hexCode[(b & 0xF)]);
}
return r.toString();
}
}
358 changes: 358 additions & 0 deletions jdk/test/com/sun/jndi/ldap/lib/LDAPTestUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,358 @@
/*
* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

import com.sun.jndi.ldap.LdapURL;

import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.SearchResult;
import java.io.FileNotFoundException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

public class LDAPTestUtils {
public static final String TEST_LDAP_SERVER_THREAD = "test.ldap.server.thread";
public static final int CERTS_LOOKUP_MAX_DEPTH = 4;

protected static boolean debug = true;

/*
* Process command line arguments and return properties in a Hashtable.
*/
public static Hashtable<Object, Object> initEnv(String testname,
String[] args) {
return initEnv(null, testname, args, false);
}

public static Hashtable<Object, Object> initEnv(ServerSocket socket,
String testname, String[] args, boolean authInfo) {

Hashtable<Object, Object> env = new Hashtable<>();
String root = "o=IMC,c=US";
String vendor = "Vendor1";
String client = "Client1";
String realm = "";
Vector<String> refs = new Vector<>();

// set defaults for some JNDI properties
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");

if (authInfo) {
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, "cn=admin,o=IMC,c=US");
env.put(Context.SECURITY_CREDENTIALS, "secret99");
}

env.put("root", root);
env.put("vendor", vendor);
env.put("client", client);

boolean traceEnable = false;
for (int i = 0; i < args.length; i++) {
if (args[i].equals("-D") && (args.length > i + 1)) {
extractProperty(args[++i], env);
} else if (args[i].startsWith("-D")) {
extractProperty(args[i].substring(2), env);
} else if (args[i].equals("-referral") && (args.length > i + 1)) {
refs.addElement(args[++i]);
} else if (args[i].equals("-trace")) {
traceEnable = true;
}
}

env.put("disabled.realm", realm);

if (refs.size() > 0) {
env.put("referrals", refs);
}

if (traceEnable) {
enableLDAPTrace(env, testname);
} else {
if (socket != null) {
env.put(TEST_LDAP_SERVER_THREAD,
startLDAPServer(socket, getCaptureFile(testname)));
env.put("java.naming.provider.url",
"ldap://localhost:" + socket.getLocalPort());
} else {
// for tests which run against remote server or no server
// required
debug("Skip local LDAP Server creation "
+ "since ServerSocket is null");
}
}

return env;
}

/*
* Clean-up the directory context.
*/
public static void cleanup(DirContext ctx) {
if (ctx != null) {
try {
ctx.close();
} catch (NamingException e) {
// ignore
}
}
}

/*
* Clean-up the sub context.
*/
public static void cleanupSubcontext(DirContext ctx, String name) {
if (ctx != null) {
try {
ctx.destroySubcontext(name);
} catch (NamingException ne) {
// ignore
}
}
}

/*
* Assemble a distinguished name from the base components and the
* namespace root.
*
* The components are prefixed with 'dc=' if the root is a DC-style name.
* Otherwise they are prefixed with 'ou='.
*/
public static String buildDN(String[] bases, String root) {

StringBuilder dn = new StringBuilder();
String prefix;

if (!root.contains("dc=")) {
prefix = "ou=";
} else {
prefix = "dc=";
}

for (String base : bases) {
dn.append(prefix).append(base).append(",");
}

return dn.append(root).toString();
}

/*
* Scan the results to confirm that the expected name is present.
*/
public static int checkResult(NamingEnumeration results, String name)
throws NamingException {

return checkResult(results, new String[] { name }, null);
}

/*
* Scan the results to confirm that the expected names and attributes
* are present.
*/
public static int checkResult(NamingEnumeration results, String[] names,
Attributes attrs) throws NamingException {

int found = 0;

while (results != null && results.hasMore()) {

SearchResult entry = (SearchResult) results.next();
String entryDN = entry.getName();

debug(">>> received: " + entryDN);

if (entry.isRelative()) {
entryDN = entryDN.toLowerCase(); // normalize
} else {
LdapURL url = new LdapURL(entryDN); // extract DN
entryDN = url.getDN().toLowerCase(); // normalize
}

for (String name : names) {
if ((entryDN.contains(name.toLowerCase())) || (entryDN
.equalsIgnoreCase(name))) {

debug(">>> checked results: found '" + name + "'");

if (attrs == null || foundAttributes(entry, attrs)) {
found++;
break;
}
}
}
}

debug(">>> checked results: found " + found
+ " entries that meet the criteria.");

return found;
}

/*
* Confirm that the attributes are present in the entry.
*/
public static boolean foundAttributes(SearchResult entry, Attributes attrs)
throws NamingException {

Attributes eattrs = entry.getAttributes();
int found = 0;

if ((eattrs == null) || (attrs == null)) {
return false;
}

for (NamingEnumeration ne = attrs.getAll(); ne.hasMoreElements(); ) {

Attribute attr = (Attribute) ne.next();

if (equalsIgnoreCase(eattrs.get(attr.getID()), attr)) {
found++;
} else {
debug(">>> foundAttributes: no match for " + attr.getID());
}
}
debug(">>> foundAttributes: found " + found + " attributes");
return (found == attrs.size());
}

public static Thread startLDAPServer(ServerSocket serverSocket,
String fileName) {
if (serverSocket == null) {
throw new RuntimeException("Error: failed to create LDAPServer "
+ "since ServerSocket is null");
}

if (!Files.exists(Paths.get(fileName))) {
throw new RuntimeException(
"Error: failed to create LDAPServer, not found ldap "
+ "cache file " + fileName);
}

Thread thread = new Thread(() -> {
try {
new test.LDAPServer(serverSocket, fileName);
} catch (Exception e) {
System.out.println("Warning: LDAP server running with issue");
e.printStackTrace();
}
});

thread.start();
return thread;
}

private static boolean equalsIgnoreCase(Attribute received,
Attribute expected) {

if (received == null || !received.getID()
.equalsIgnoreCase(expected.getID())) {
return false;
}

try {

Enumeration expectedVals = expected.getAll();
Object obj;
while (expectedVals.hasMoreElements()) {
obj = expectedVals.nextElement();
if (!received.contains(obj)) {
if (!(obj instanceof String)) {
return false;
}
if (!received.contains(((String) obj).toLowerCase())) {
return false;
}
}
}

} catch (NamingException e) {
return false;
}

return true;
}

private static void extractProperty(String propString,
Hashtable<Object, Object> env) {
int index;

if ((index = propString.indexOf('=')) > 0) {
env.put(propString.substring(0, index),
propString.substring(index + 1));
} else {
throw new RuntimeException(
"Failed to extract test args property from " + propString);
}
}

private static void enableLDAPTrace(Hashtable<Object, Object> env,
String testname) {
try {
PrintStream outStream = new PrintStream(getCaptureFile(testname));
env.put("com.sun.jndi.ldap.trace.ber", outStream);
} catch (FileNotFoundException e) {
throw new RuntimeException(
"Error: failed to enable ldap trace: " + e.getMessage(), e);
}
}

private static String getCaptureFile(String testname) {
return Paths.get(System.getProperty("test.src"))
.resolve(testname + ".ldap").toString();
}

public static void debug(Object object) {
if (debug) {
System.out.println(object);
}
}

public static String findCertsHome(int depth) {
Path path = Paths.get(System.getProperty("test.src", "."))
.toAbsolutePath();
for (int i = depth; i >= 0; i--) {
Path homePath = path.resolve("certs");
if (Files.exists(homePath) && Files.isDirectory(homePath)) {
return homePath.toString();
}

path = path.getParent();
if (path == null) {
break;
}
}

return System.getProperty("test.src", ".");
}
}