import javax.xml.namespace.QName;
import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentSession;
+import org.argeo.cms.CmsUserManager;
+import org.argeo.osgi.useradmin.UserDirectory;
+import org.osgi.service.useradmin.Role;
/** Utilities and routines around {@link Content}. */
public class ContentUtils {
}
+ public static Content roleToContent(CmsUserManager userManager, ContentSession contentSession, Role role) {
+ UserDirectory userDirectory = userManager.getDirectory(role);
+ String path = CmsContentRepository.DIRECTORY_BASE + SLASH + userDirectory.getName() + SLASH
+ + userDirectory.getRolePath(role);
+ Content content = contentSession.get(path);
+ return content;
+ }
+
}
import org.argeo.cms.internal.runtime.KernelConstants;
import org.argeo.cms.internal.runtime.KernelUtils;
import org.argeo.osgi.useradmin.UserAdminConf;
-import org.argeo.util.naming.AttributesDictionary;
-import org.argeo.util.naming.LdifParser;
-import org.argeo.util.naming.LdifWriter;
+import org.argeo.util.naming.ldap.AttributesDictionary;
+import org.argeo.util.naming.ldap.LdifParser;
+import org.argeo.util.naming.ldap.LdifWriter;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.argeo.osgi.useradmin.OsUserDirectory;
import org.argeo.osgi.useradmin.UserAdminConf;
import org.argeo.osgi.useradmin.UserDirectory;
-import org.argeo.util.naming.DnsBrowser;
+import org.argeo.util.naming.dns.DnsBrowser;
import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.GSSManager;
import javax.naming.NamingException;
import javax.naming.ldap.LdapName;
-import org.argeo.util.naming.DnsBrowser;
import org.argeo.util.naming.LdapAttrs;
+import org.argeo.util.naming.dns.DnsBrowser;
/** Free IPA specific conventions. */
public class IpaUtils {
import javax.naming.directory.BasicAttribute;
import javax.naming.ldap.LdapName;
-import org.argeo.util.naming.AuthPassword;
import org.argeo.util.naming.LdapAttrs;
import org.argeo.util.naming.LdapObjs;
import org.argeo.util.naming.SharedSecret;
+import org.argeo.util.naming.ldap.AuthPassword;
/** Directory user implementation */
abstract class LdifUser implements DirectoryUser {
import javax.naming.ldap.LdapName;
import org.argeo.util.naming.LdapObjs;
-import org.argeo.util.naming.LdifParser;
-import org.argeo.util.naming.LdifWriter;
+import org.argeo.util.naming.ldap.LdifParser;
+import org.argeo.util.naming.ldap.LdifWriter;
import org.osgi.framework.Filter;
import org.osgi.service.useradmin.Role;
import org.osgi.service.useradmin.User;
+++ /dev/null
-package org.argeo.util.naming;
-
-import java.util.Dictionary;
-import java.util.Enumeration;
-
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.BasicAttribute;
-
-public class AttributesDictionary extends Dictionary<String, Object> {
- private final Attributes attributes;
-
- /** The provided attributes is wrapped, not copied. */
- public AttributesDictionary(Attributes attributes) {
- if (attributes == null)
- throw new IllegalArgumentException("Attributes cannot be null");
- this.attributes = attributes;
- }
-
- @Override
- public int size() {
- return attributes.size();
- }
-
- @Override
- public boolean isEmpty() {
- return attributes.size() == 0;
- }
-
- @Override
- public Enumeration<String> keys() {
- NamingEnumeration<String> namingEnumeration = attributes.getIDs();
- return new Enumeration<String>() {
-
- @Override
- public boolean hasMoreElements() {
- return namingEnumeration.hasMoreElements();
- }
-
- @Override
- public String nextElement() {
- return namingEnumeration.nextElement();
- }
-
- };
- }
-
- @Override
- public Enumeration<Object> elements() {
- NamingEnumeration<String> namingEnumeration = attributes.getIDs();
- return new Enumeration<Object>() {
-
- @Override
- public boolean hasMoreElements() {
- return namingEnumeration.hasMoreElements();
- }
-
- @Override
- public Object nextElement() {
- String key = namingEnumeration.nextElement();
- return get(key);
- }
-
- };
- }
-
- @Override
- /** @returns a <code>String</code> or <code>String[]</code> */
- public Object get(Object key) {
- try {
- if (key == null)
- throw new IllegalArgumentException("Key cannot be null");
- Attribute attr = attributes.get(key.toString());
- if (attr == null)
- return null;
- if (attr.size() == 0)
- throw new IllegalStateException("There must be at least one value");
- else if (attr.size() == 1) {
- return attr.get().toString();
- } else {// multiple
- String[] res = new String[attr.size()];
- for (int i = 0; i < attr.size(); i++) {
- Object value = attr.get();
- if (value == null)
- throw new RuntimeException("Values cannot be null");
- res[i] = attr.get(i).toString();
- }
- return res;
- }
- } catch (NamingException e) {
- throw new RuntimeException("Cannot get value for " + key, e);
- }
- }
-
- @Override
- public Object put(String key, Object value) {
- if (key == null)
- throw new IllegalArgumentException("Key cannot be null");
- if (value == null)
- throw new IllegalArgumentException("Value cannot be null");
-
- Object oldValue = get(key);
- Attribute attr = attributes.get(key);
- if (attr == null) {
- attr = new BasicAttribute(key);
- attributes.put(attr);
- }
-
- if (value instanceof String[]) {
- String[] values = (String[]) value;
- // clean additional values
- for (int i = values.length; i < attr.size(); i++)
- attr.remove(i);
- // set values
- for (int i = 0; i < values.length; i++) {
- attr.set(i, values[i]);
- }
- } else {
- if (attr.size() > 1)
- throw new IllegalArgumentException("Attribute " + key + " is multi-valued");
- if (attr.size() == 1) {
- try {
- if (!attr.get(0).equals(value))
- attr.set(0, value.toString());
- } catch (NamingException e) {
- throw new RuntimeException("Cannot check existing value", e);
- }
- } else {
- attr.add(value.toString());
- }
- }
- return oldValue;
- }
-
- @Override
- public Object remove(Object key) {
- if (key == null)
- throw new IllegalArgumentException("Key cannot be null");
- Object oldValue = get(key);
- if (oldValue == null)
- return null;
- return attributes.remove(key.toString());
- }
-
- /**
- * Copy the <b>content</b> of an {@link Attributes} to the provided
- * {@link Dictionary}.
- */
- public static void copy(Attributes attributes, Dictionary<String, Object> dictionary) {
- AttributesDictionary ad = new AttributesDictionary(attributes);
- Enumeration<String> keys = ad.keys();
- while (keys.hasMoreElements()) {
- String key = keys.nextElement();
- dictionary.put(key, ad.get(key));
- }
- }
-
- /**
- * Copy a {@link Dictionary} into an {@link Attributes}.
- */
- public static void copy(Dictionary<String, Object> dictionary, Attributes attributes) {
- AttributesDictionary ad = new AttributesDictionary(attributes);
- Enumeration<String> keys = dictionary.keys();
- while (keys.hasMoreElements()) {
- String key = keys.nextElement();
- ad.put(key, dictionary.get(key));
- }
- }
-}
+++ /dev/null
-package org.argeo.util.naming;
-
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.StringTokenizer;
-
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.NameCallback;
-import javax.security.auth.callback.PasswordCallback;
-import javax.security.auth.callback.UnsupportedCallbackException;
-
-/** LDAP authPassword field according to RFC 3112 */
-public class AuthPassword implements CallbackHandler {
- private final String authScheme;
- private final String authInfo;
- private final String authValue;
-
- public AuthPassword(String value) {
- StringTokenizer st = new StringTokenizer(value, "$");
- // TODO make it more robust, deal with bad formatting
- this.authScheme = st.nextToken().trim();
- this.authInfo = st.nextToken().trim();
- this.authValue = st.nextToken().trim();
-
- String expectedAuthScheme = getExpectedAuthScheme();
- if (expectedAuthScheme != null && !authScheme.equals(expectedAuthScheme))
- throw new IllegalArgumentException(
- "Auth scheme " + authScheme + " is not compatible with " + expectedAuthScheme);
- }
-
- protected AuthPassword(String authInfo, String authValue) {
- this.authScheme = getExpectedAuthScheme();
- if (authScheme == null)
- throw new IllegalArgumentException("Expected auth scheme cannot be null");
- this.authInfo = authInfo;
- this.authValue = authValue;
- }
-
- protected AuthPassword(AuthPassword authPassword) {
- this.authScheme = authPassword.getAuthScheme();
- this.authInfo = authPassword.getAuthInfo();
- this.authValue = authPassword.getAuthValue();
- }
-
- protected String getExpectedAuthScheme() {
- return null;
- }
-
- protected boolean matchAuthValue(Object object) {
- return authValue.equals(object.toString());
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof AuthPassword))
- return false;
- AuthPassword authPassword = (AuthPassword) obj;
- return authScheme.equals(authPassword.authScheme) && authInfo.equals(authPassword.authInfo)
- && authValue.equals(authValue);
- }
-
- public boolean keyEquals(AuthPassword authPassword) {
- return authScheme.equals(authPassword.authScheme) && authInfo.equals(authPassword.authInfo);
- }
-
- @Override
- public int hashCode() {
- return authValue.hashCode();
- }
-
- @Override
- public String toString() {
- return toAuthPassword();
- }
-
- public final String toAuthPassword() {
- return getAuthScheme() + '$' + authInfo + '$' + authValue;
- }
-
- public String getAuthScheme() {
- return authScheme;
- }
-
- public String getAuthInfo() {
- return authInfo;
- }
-
- public String getAuthValue() {
- return authValue;
- }
-
- public static AuthPassword matchAuthValue(Attributes attributes, char[] value) {
- try {
- Attribute authPassword = attributes.get(LdapAttrs.authPassword.name());
- if (authPassword != null) {
- NamingEnumeration<?> values = authPassword.getAll();
- while (values.hasMore()) {
- Object val = values.next();
- AuthPassword token = new AuthPassword(val.toString());
- String auth;
- if (Arrays.binarySearch(value, '$') >= 0) {
- auth = token.authInfo + '$' + token.authValue;
- } else {
- auth = token.authValue;
- }
- if (Arrays.equals(auth.toCharArray(), value))
- return token;
- // if (token.matchAuthValue(value))
- // return token;
- }
- }
- return null;
- } catch (NamingException e) {
- throw new IllegalStateException("Cannot check attribute", e);
- }
- }
-
- public static boolean remove(Attributes attributes, AuthPassword value) {
- Attribute authPassword = attributes.get(LdapAttrs.authPassword.name());
- return authPassword.remove(value.toAuthPassword());
- }
-
- @Override
- public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
- for (Callback callback : callbacks) {
- if (callback instanceof NameCallback)
- ((NameCallback) callback).setName(toAuthPassword());
- else if (callback instanceof PasswordCallback)
- ((PasswordCallback) callback).setPassword(getAuthValue().toCharArray());
- }
- }
-
-}
+++ /dev/null
-package org.argeo.util.naming;
-
-import java.io.Closeable;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.util.ArrayList;
-import java.util.Base64;
-import java.util.Collections;
-import java.util.Hashtable;
-import java.util.List;
-import java.util.Map;
-import java.util.SortedSet;
-import java.util.TreeMap;
-import java.util.TreeSet;
-
-import javax.naming.Binding;
-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.InitialDirContext;
-
-public class DnsBrowser implements Closeable {
- private final DirContext initialCtx;
-
- public DnsBrowser() throws NamingException {
- this(null);
- }
-
- public DnsBrowser(String dnsServerUrls) throws NamingException {
- Hashtable<String, Object> env = new Hashtable<>();
- env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory");
- if (dnsServerUrls != null)
- env.put("java.naming.provider.url", dnsServerUrls);
- initialCtx = new InitialDirContext(env);
- }
-
- public Map<String, List<String>> getAllRecords(String name) throws NamingException {
- Map<String, List<String>> res = new TreeMap<>();
- Attributes attrs = initialCtx.getAttributes(name);
- NamingEnumeration<String> ids = attrs.getIDs();
- while (ids.hasMore()) {
- String recordType = ids.next();
- List<String> lst = new ArrayList<String>();
- res.put(recordType, lst);
- Attribute attr = attrs.get(recordType);
- addValues(attr, lst);
- }
- return Collections.unmodifiableMap(res);
- }
-
- /**
- * Return a single record (typically A, AAAA, etc. or null if not available.
- * Will fail if multiple records.
- */
- public String getRecord(String name, String recordType) throws NamingException {
- Attributes attrs = initialCtx.getAttributes(name, new String[] { recordType });
- if (attrs.size() == 0)
- return null;
- Attribute attr = attrs.get(recordType);
- if (attr.size() > 1)
- throw new IllegalArgumentException("Multiple record type " + recordType);
- assert attr.size() != 0;
- Object value = attr.get();
- assert value != null;
- return value.toString();
- }
-
- /**
- * Return records of a given type.
- */
- public List<String> getRecords(String name, String recordType) throws NamingException {
- List<String> res = new ArrayList<String>();
- Attributes attrs = initialCtx.getAttributes(name, new String[] { recordType });
- Attribute attr = attrs.get(recordType);
- addValues(attr, res);
- return res;
- }
-
- /** Ordered, with preferred first. */
- public List<String> getSrvRecordsAsHosts(String name, boolean withPort) throws NamingException {
- List<String> raw = getRecords(name, "SRV");
- if (raw.size() == 0)
- return null;
- SortedSet<SrvRecord> res = new TreeSet<>();
- for (int i = 0; i < raw.size(); i++) {
- String record = raw.get(i);
- String[] arr = record.split(" ");
- Integer priority = Integer.parseInt(arr[0]);
- Integer weight = Integer.parseInt(arr[1]);
- Integer port = Integer.parseInt(arr[2]);
- String hostname = arr[3];
- SrvRecord order = new SrvRecord(priority, weight, port, hostname);
- res.add(order);
- }
- List<String> lst = new ArrayList<>();
- for (SrvRecord order : res) {
- lst.add(order.toHost(withPort));
- }
- return Collections.unmodifiableList(lst);
- }
-
- private void addValues(Attribute attr, List<String> lst) throws NamingException {
- NamingEnumeration<?> values = attr.getAll();
- while (values.hasMore()) {
- Object value = values.next();
- if (value != null) {
- if (value instanceof byte[]) {
- String str = Base64.getEncoder().encodeToString((byte[]) value);
- lst.add(str);
- } else
- lst.add(value.toString());
- }
- }
-
- }
-
- public List<String> listEntries(String name) throws NamingException {
- List<String> res = new ArrayList<String>();
- NamingEnumeration<Binding> ne = initialCtx.listBindings(name);
- while (ne.hasMore()) {
- Binding b = ne.next();
- res.add(b.getName());
- }
- return Collections.unmodifiableList(res);
- }
-
- @Override
- public void close() throws IOException {
- destroy();
- }
-
- public void destroy() {
- try {
- initialCtx.close();
- } catch (NamingException e) {
- // silent
- }
- }
-
- public static void main(String[] args) {
- if (args.length == 0) {
- printUsage(System.err);
- System.exit(1);
- }
- try (DnsBrowser dnsBrowser = new DnsBrowser()) {
- String hostname = args[0];
- String recordType = args.length > 1 ? args[1] : "A";
- if (recordType.equals("*")) {
- Map<String, List<String>> records = dnsBrowser.getAllRecords(hostname);
- for (String type : records.keySet()) {
- for (String record : records.get(type)) {
- String typeLabel;
- if ("44".equals(type))
- typeLabel = "SSHFP";
- else if ("46".equals(type))
- typeLabel = "RRSIG";
- else if ("48".equals(type))
- typeLabel = "DNSKEY";
- else
- typeLabel = type;
- System.out.println(typeLabel + "\t" + record);
- }
- }
- } else {
- System.out.println(dnsBrowser.getRecord(hostname, recordType));
- }
-
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
- public static void printUsage(PrintStream out) {
- out.println("java org.argeo.naming.DnsBrowser <hostname> [<record type> | *]");
- }
-
-}
\ No newline at end of file
+++ /dev/null
-package org.argeo.util.naming;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Base64;
-import java.util.List;
-import java.util.SortedMap;
-import java.util.TreeMap;
-
-import javax.naming.InvalidNameException;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.BasicAttribute;
-import javax.naming.directory.BasicAttributes;
-import javax.naming.ldap.LdapName;
-import javax.naming.ldap.Rdn;
-
-/** Basic LDIF parser. */
-public class LdifParser {
- private final static Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
-
- protected Attributes addAttributes(SortedMap<LdapName, Attributes> res, int lineNumber, LdapName currentDn,
- Attributes currentAttributes) {
- try {
- Rdn nameRdn = currentDn.getRdn(currentDn.size() - 1);
- Attribute nameAttr = currentAttributes.get(nameRdn.getType());
- if (nameAttr == null)
- currentAttributes.put(nameRdn.getType(), nameRdn.getValue());
- else if (!nameAttr.get().equals(nameRdn.getValue()))
- throw new IllegalStateException(
- "Attribute " + nameAttr.getID() + "=" + nameAttr.get() + " not consistent with DN " + currentDn
- + " (shortly before line " + lineNumber + " in LDIF file)");
- Attributes previous = res.put(currentDn, currentAttributes);
- return previous;
- } catch (NamingException e) {
- throw new IllegalStateException("Cannot add " + currentDn, e);
- }
- }
-
- /** With UTF-8 charset */
- public SortedMap<LdapName, Attributes> read(InputStream in) throws IOException {
- try (Reader reader = new InputStreamReader(in, DEFAULT_CHARSET)) {
- return read(reader);
- } finally {
- try {
- in.close();
- } catch (IOException e) {
- // silent
- }
- }
- }
-
- /** Will close the reader. */
- public SortedMap<LdapName, Attributes> read(Reader reader) throws IOException {
- SortedMap<LdapName, Attributes> res = new TreeMap<LdapName, Attributes>();
- try {
- List<String> lines = new ArrayList<>();
- try (BufferedReader br = new BufferedReader(reader)) {
- String line;
- while ((line = br.readLine()) != null) {
- lines.add(line);
- }
- }
- if (lines.size() == 0)
- return res;
- // add an empty new line since the last line is not checked
- if (!lines.get(lines.size() - 1).equals(""))
- lines.add("");
-
- LdapName currentDn = null;
- Attributes currentAttributes = null;
- StringBuilder currentEntry = new StringBuilder();
-
- readLines: for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) {
- String line = lines.get(lineNumber);
- boolean isLastLine = false;
- if (lineNumber == lines.size() - 1)
- isLastLine = true;
- if (line.startsWith(" ")) {
- currentEntry.append(line.substring(1));
- if (!isLastLine)
- continue readLines;
- }
-
- if (currentEntry.length() != 0 || isLastLine) {
- // read previous attribute
- StringBuilder attrId = new StringBuilder(8);
- boolean isBase64 = false;
- readAttrId: for (int i = 0; i < currentEntry.length(); i++) {
- char c = currentEntry.charAt(i);
- if (c == ':') {
- if (i + 1 < currentEntry.length() && currentEntry.charAt(i + 1) == ':')
- isBase64 = true;
- currentEntry.delete(0, i + (isBase64 ? 2 : 1));
- break readAttrId;
- } else {
- attrId.append(c);
- }
- }
-
- String attributeId = attrId.toString();
- // TODO should we really trim the end of the string as well?
- String cleanValueStr = currentEntry.toString().trim();
- Object attributeValue = isBase64 ? Base64.getDecoder().decode(cleanValueStr) : cleanValueStr;
-
- // manage DN attributes
- if (attributeId.equals(LdapAttrs.DN) || isLastLine) {
- if (currentDn != null) {
- //
- // ADD
- //
- Attributes previous = addAttributes(res, lineNumber, currentDn, currentAttributes);
- if (previous != null) {
-// log.warn("There was already an entry with DN " + currentDn
-// + ", which has been discarded by a subsequent one.");
- }
- }
-
- if (attributeId.equals(LdapAttrs.DN))
- try {
- currentDn = new LdapName(attributeValue.toString());
- currentAttributes = new BasicAttributes(true);
- } catch (InvalidNameException e) {
-// log.error(attributeValue + " not a valid DN, skipping the entry.");
- currentDn = null;
- currentAttributes = null;
- }
- }
-
- // store attribute
- if (currentAttributes != null) {
- Attribute attribute = currentAttributes.get(attributeId);
- if (attribute == null) {
- attribute = new BasicAttribute(attributeId);
- currentAttributes.put(attribute);
- }
- attribute.add(attributeValue);
- }
- currentEntry = new StringBuilder();
- }
- currentEntry.append(line);
- }
- } finally {
- try {
- reader.close();
- } catch (IOException e) {
- // silent
- }
- }
- return res;
- }
-}
\ No newline at end of file
+++ /dev/null
-package org.argeo.util.naming;
-
-import static org.argeo.util.naming.LdapAttrs.DN;
-import static org.argeo.util.naming.LdapAttrs.member;
-import static org.argeo.util.naming.LdapAttrs.objectClass;
-import static org.argeo.util.naming.LdapAttrs.uniqueMember;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.Writer;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.util.Base64;
-import java.util.Map;
-import java.util.SortedSet;
-import java.util.TreeSet;
-
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.ldap.LdapName;
-import javax.naming.ldap.Rdn;
-
-/** Basic LDIF writer */
-public class LdifWriter {
- private final static Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
- private final Writer writer;
-
- /** Writer must be closed by caller */
- public LdifWriter(Writer writer) {
- this.writer = writer;
- }
-
- /** Stream must be closed by caller */
- public LdifWriter(OutputStream out) {
- this(new OutputStreamWriter(out, DEFAULT_CHARSET));
- }
-
- public void writeEntry(LdapName name, Attributes attributes) throws IOException {
- try {
- // check consistency
- Rdn nameRdn = name.getRdn(name.size() - 1);
- Attribute nameAttr = attributes.get(nameRdn.getType());
- if (!nameAttr.get().equals(nameRdn.getValue()))
- throw new IllegalArgumentException(
- "Attribute " + nameAttr.getID() + "=" + nameAttr.get() + " not consistent with DN " + name);
-
- writer.append(DN + ": ").append(name.toString()).append('\n');
- Attribute objectClassAttr = attributes.get(objectClass.name());
- if (objectClassAttr != null)
- writeAttribute(objectClassAttr);
- attributes: for (NamingEnumeration<? extends Attribute> attrs = attributes.getAll(); attrs.hasMore();) {
- Attribute attribute = attrs.next();
- if (attribute.getID().equals(DN) || attribute.getID().equals(objectClass.name()))
- continue attributes;// skip DN attribute
- if (attribute.getID().equals(member.name()) || attribute.getID().equals(uniqueMember.name()))
- continue attributes;// skip member and uniqueMember attributes, so that they are always written last
- writeAttribute(attribute);
- }
- // write member and uniqueMember attributes last
- for (NamingEnumeration<? extends Attribute> attrs = attributes.getAll(); attrs.hasMore();) {
- Attribute attribute = attrs.next();
- if (attribute.getID().equals(member.name()) || attribute.getID().equals(uniqueMember.name()))
- writeMemberAttribute(attribute);
- }
- writer.append('\n');
- writer.flush();
- } catch (NamingException e) {
- throw new IllegalStateException("Cannot write LDIF", e);
- }
- }
-
- public void write(Map<LdapName, Attributes> entries) throws IOException {
- for (LdapName dn : entries.keySet())
- writeEntry(dn, entries.get(dn));
- }
-
- protected void writeAttribute(Attribute attribute) throws NamingException, IOException {
- for (NamingEnumeration<?> attrValues = attribute.getAll(); attrValues.hasMore();) {
- Object value = attrValues.next();
- if (value instanceof byte[]) {
- String encoded = Base64.getEncoder().encodeToString((byte[]) value);
- writer.append(attribute.getID()).append(":: ").append(encoded).append('\n');
- } else {
- writer.append(attribute.getID()).append(": ").append(value.toString()).append('\n');
- }
- }
- }
-
- protected void writeMemberAttribute(Attribute attribute) throws NamingException, IOException {
- // Note: duplicate entries will be swallowed
- SortedSet<String> values = new TreeSet<>();
- for (NamingEnumeration<?> attrValues = attribute.getAll(); attrValues.hasMore();) {
- String value = attrValues.next().toString();
- values.add(value);
- }
-
- for (String value : values) {
- writer.append(attribute.getID()).append(": ").append(value).append('\n');
- }
- }
-}
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
+import org.argeo.util.naming.ldap.AuthPassword;
+
public class SharedSecret extends AuthPassword {
public final static String X_SHARED_SECRET = "X-SharedSecret";
private final Instant expiry;
+++ /dev/null
-package org.argeo.util.naming;
-
-class SrvRecord implements Comparable<SrvRecord> {
- private final Integer priority;
- private final Integer weight;
- private final Integer port;
- private final String hostname;
-
- public SrvRecord(Integer priority, Integer weight, Integer port, String hostname) {
- this.priority = priority;
- this.weight = weight;
- this.port = port;
- this.hostname = hostname;
- }
-
- @Override
- public int compareTo(SrvRecord other) {
- // https: // en.wikipedia.org/wiki/SRV_record
- if (priority != other.priority)
- return priority - other.priority;
- if (weight != other.weight)
- return other.weight - other.weight;
- String host = toHost(false);
- String otherHost = other.toHost(false);
- if (host.length() == otherHost.length())
- return host.compareTo(otherHost);
- else
- return host.length() - otherHost.length();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof SrvRecord) {
- SrvRecord other = (SrvRecord) obj;
- return priority == other.priority && weight == other.weight && port == other.port
- && hostname.equals(other.hostname);
- }
- return false;
- }
-
- @Override
- public String toString() {
- return priority + " " + weight;
- }
-
- public String toHost(boolean withPort) {
- String hostStr = hostname;
- if (hostname.charAt(hostname.length() - 1) == '.')
- hostStr = hostname.substring(0, hostname.length() - 1);
- return hostStr + (withPort ? ":" + port : "");
- }
-}
--- /dev/null
+package org.argeo.util.naming.dns;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.Collections;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import javax.naming.Binding;
+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.InitialDirContext;
+
+public class DnsBrowser implements Closeable {
+ private final DirContext initialCtx;
+
+ public DnsBrowser() throws NamingException {
+ this(null);
+ }
+
+ public DnsBrowser(String dnsServerUrls) throws NamingException {
+ Hashtable<String, Object> env = new Hashtable<>();
+ env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory");
+ if (dnsServerUrls != null)
+ env.put("java.naming.provider.url", dnsServerUrls);
+ initialCtx = new InitialDirContext(env);
+ }
+
+ public Map<String, List<String>> getAllRecords(String name) throws NamingException {
+ Map<String, List<String>> res = new TreeMap<>();
+ Attributes attrs = initialCtx.getAttributes(name);
+ NamingEnumeration<String> ids = attrs.getIDs();
+ while (ids.hasMore()) {
+ String recordType = ids.next();
+ List<String> lst = new ArrayList<String>();
+ res.put(recordType, lst);
+ Attribute attr = attrs.get(recordType);
+ addValues(attr, lst);
+ }
+ return Collections.unmodifiableMap(res);
+ }
+
+ /**
+ * Return a single record (typically A, AAAA, etc. or null if not available.
+ * Will fail if multiple records.
+ */
+ public String getRecord(String name, String recordType) throws NamingException {
+ Attributes attrs = initialCtx.getAttributes(name, new String[] { recordType });
+ if (attrs.size() == 0)
+ return null;
+ Attribute attr = attrs.get(recordType);
+ if (attr.size() > 1)
+ throw new IllegalArgumentException("Multiple record type " + recordType);
+ assert attr.size() != 0;
+ Object value = attr.get();
+ assert value != null;
+ return value.toString();
+ }
+
+ /**
+ * Return records of a given type.
+ */
+ public List<String> getRecords(String name, String recordType) throws NamingException {
+ List<String> res = new ArrayList<String>();
+ Attributes attrs = initialCtx.getAttributes(name, new String[] { recordType });
+ Attribute attr = attrs.get(recordType);
+ addValues(attr, res);
+ return res;
+ }
+
+ /** Ordered, with preferred first. */
+ public List<String> getSrvRecordsAsHosts(String name, boolean withPort) throws NamingException {
+ List<String> raw = getRecords(name, "SRV");
+ if (raw.size() == 0)
+ return null;
+ SortedSet<SrvRecord> res = new TreeSet<>();
+ for (int i = 0; i < raw.size(); i++) {
+ String record = raw.get(i);
+ String[] arr = record.split(" ");
+ Integer priority = Integer.parseInt(arr[0]);
+ Integer weight = Integer.parseInt(arr[1]);
+ Integer port = Integer.parseInt(arr[2]);
+ String hostname = arr[3];
+ SrvRecord order = new SrvRecord(priority, weight, port, hostname);
+ res.add(order);
+ }
+ List<String> lst = new ArrayList<>();
+ for (SrvRecord order : res) {
+ lst.add(order.toHost(withPort));
+ }
+ return Collections.unmodifiableList(lst);
+ }
+
+ private void addValues(Attribute attr, List<String> lst) throws NamingException {
+ NamingEnumeration<?> values = attr.getAll();
+ while (values.hasMore()) {
+ Object value = values.next();
+ if (value != null) {
+ if (value instanceof byte[]) {
+ String str = Base64.getEncoder().encodeToString((byte[]) value);
+ lst.add(str);
+ } else
+ lst.add(value.toString());
+ }
+ }
+
+ }
+
+ public List<String> listEntries(String name) throws NamingException {
+ List<String> res = new ArrayList<String>();
+ NamingEnumeration<Binding> ne = initialCtx.listBindings(name);
+ while (ne.hasMore()) {
+ Binding b = ne.next();
+ res.add(b.getName());
+ }
+ return Collections.unmodifiableList(res);
+ }
+
+ @Override
+ public void close() throws IOException {
+ destroy();
+ }
+
+ public void destroy() {
+ try {
+ initialCtx.close();
+ } catch (NamingException e) {
+ // silent
+ }
+ }
+
+ public static void main(String[] args) {
+ if (args.length == 0) {
+ printUsage(System.err);
+ System.exit(1);
+ }
+ try (DnsBrowser dnsBrowser = new DnsBrowser()) {
+ String hostname = args[0];
+ String recordType = args.length > 1 ? args[1] : "A";
+ if (recordType.equals("*")) {
+ Map<String, List<String>> records = dnsBrowser.getAllRecords(hostname);
+ for (String type : records.keySet()) {
+ for (String record : records.get(type)) {
+ String typeLabel;
+ if ("44".equals(type))
+ typeLabel = "SSHFP";
+ else if ("46".equals(type))
+ typeLabel = "RRSIG";
+ else if ("48".equals(type))
+ typeLabel = "DNSKEY";
+ else
+ typeLabel = type;
+ System.out.println(typeLabel + "\t" + record);
+ }
+ }
+ } else {
+ System.out.println(dnsBrowser.getRecord(hostname, recordType));
+ }
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static void printUsage(PrintStream out) {
+ out.println("java org.argeo.naming.DnsBrowser <hostname> [<record type> | *]");
+ }
+
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.util.naming.dns;
+
+class SrvRecord implements Comparable<SrvRecord> {
+ private final Integer priority;
+ private final Integer weight;
+ private final Integer port;
+ private final String hostname;
+
+ public SrvRecord(Integer priority, Integer weight, Integer port, String hostname) {
+ this.priority = priority;
+ this.weight = weight;
+ this.port = port;
+ this.hostname = hostname;
+ }
+
+ @Override
+ public int compareTo(SrvRecord other) {
+ // https: // en.wikipedia.org/wiki/SRV_record
+ if (priority != other.priority)
+ return priority - other.priority;
+ if (weight != other.weight)
+ return other.weight - other.weight;
+ String host = toHost(false);
+ String otherHost = other.toHost(false);
+ if (host.length() == otherHost.length())
+ return host.compareTo(otherHost);
+ else
+ return host.length() - otherHost.length();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof SrvRecord) {
+ SrvRecord other = (SrvRecord) obj;
+ return priority == other.priority && weight == other.weight && port == other.port
+ && hostname.equals(other.hostname);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return priority + " " + weight;
+ }
+
+ public String toHost(boolean withPort) {
+ String hostStr = hostname;
+ if (hostname.charAt(hostname.length() - 1) == '.')
+ hostStr = hostname.substring(0, hostname.length() - 1);
+ return hostStr + (withPort ? ":" + port : "");
+ }
+}
--- /dev/null
+package org.argeo.util.naming.ldap;
+
+import java.util.Dictionary;
+import java.util.Enumeration;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttribute;
+
+public class AttributesDictionary extends Dictionary<String, Object> {
+ private final Attributes attributes;
+
+ /** The provided attributes is wrapped, not copied. */
+ public AttributesDictionary(Attributes attributes) {
+ if (attributes == null)
+ throw new IllegalArgumentException("Attributes cannot be null");
+ this.attributes = attributes;
+ }
+
+ @Override
+ public int size() {
+ return attributes.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return attributes.size() == 0;
+ }
+
+ @Override
+ public Enumeration<String> keys() {
+ NamingEnumeration<String> namingEnumeration = attributes.getIDs();
+ return new Enumeration<String>() {
+
+ @Override
+ public boolean hasMoreElements() {
+ return namingEnumeration.hasMoreElements();
+ }
+
+ @Override
+ public String nextElement() {
+ return namingEnumeration.nextElement();
+ }
+
+ };
+ }
+
+ @Override
+ public Enumeration<Object> elements() {
+ NamingEnumeration<String> namingEnumeration = attributes.getIDs();
+ return new Enumeration<Object>() {
+
+ @Override
+ public boolean hasMoreElements() {
+ return namingEnumeration.hasMoreElements();
+ }
+
+ @Override
+ public Object nextElement() {
+ String key = namingEnumeration.nextElement();
+ return get(key);
+ }
+
+ };
+ }
+
+ @Override
+ /** @returns a <code>String</code> or <code>String[]</code> */
+ public Object get(Object key) {
+ try {
+ if (key == null)
+ throw new IllegalArgumentException("Key cannot be null");
+ Attribute attr = attributes.get(key.toString());
+ if (attr == null)
+ return null;
+ if (attr.size() == 0)
+ throw new IllegalStateException("There must be at least one value");
+ else if (attr.size() == 1) {
+ return attr.get().toString();
+ } else {// multiple
+ String[] res = new String[attr.size()];
+ for (int i = 0; i < attr.size(); i++) {
+ Object value = attr.get();
+ if (value == null)
+ throw new RuntimeException("Values cannot be null");
+ res[i] = attr.get(i).toString();
+ }
+ return res;
+ }
+ } catch (NamingException e) {
+ throw new RuntimeException("Cannot get value for " + key, e);
+ }
+ }
+
+ @Override
+ public Object put(String key, Object value) {
+ if (key == null)
+ throw new IllegalArgumentException("Key cannot be null");
+ if (value == null)
+ throw new IllegalArgumentException("Value cannot be null");
+
+ Object oldValue = get(key);
+ Attribute attr = attributes.get(key);
+ if (attr == null) {
+ attr = new BasicAttribute(key);
+ attributes.put(attr);
+ }
+
+ if (value instanceof String[]) {
+ String[] values = (String[]) value;
+ // clean additional values
+ for (int i = values.length; i < attr.size(); i++)
+ attr.remove(i);
+ // set values
+ for (int i = 0; i < values.length; i++) {
+ attr.set(i, values[i]);
+ }
+ } else {
+ if (attr.size() > 1)
+ throw new IllegalArgumentException("Attribute " + key + " is multi-valued");
+ if (attr.size() == 1) {
+ try {
+ if (!attr.get(0).equals(value))
+ attr.set(0, value.toString());
+ } catch (NamingException e) {
+ throw new RuntimeException("Cannot check existing value", e);
+ }
+ } else {
+ attr.add(value.toString());
+ }
+ }
+ return oldValue;
+ }
+
+ @Override
+ public Object remove(Object key) {
+ if (key == null)
+ throw new IllegalArgumentException("Key cannot be null");
+ Object oldValue = get(key);
+ if (oldValue == null)
+ return null;
+ return attributes.remove(key.toString());
+ }
+
+ /**
+ * Copy the <b>content</b> of an {@link Attributes} to the provided
+ * {@link Dictionary}.
+ */
+ public static void copy(Attributes attributes, Dictionary<String, Object> dictionary) {
+ AttributesDictionary ad = new AttributesDictionary(attributes);
+ Enumeration<String> keys = ad.keys();
+ while (keys.hasMoreElements()) {
+ String key = keys.nextElement();
+ dictionary.put(key, ad.get(key));
+ }
+ }
+
+ /**
+ * Copy a {@link Dictionary} into an {@link Attributes}.
+ */
+ public static void copy(Dictionary<String, Object> dictionary, Attributes attributes) {
+ AttributesDictionary ad = new AttributesDictionary(attributes);
+ Enumeration<String> keys = dictionary.keys();
+ while (keys.hasMoreElements()) {
+ String key = keys.nextElement();
+ ad.put(key, dictionary.get(key));
+ }
+ }
+}
--- /dev/null
+package org.argeo.util.naming.ldap;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.StringTokenizer;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+
+import org.argeo.util.naming.LdapAttrs;
+
+/** LDAP authPassword field according to RFC 3112 */
+public class AuthPassword implements CallbackHandler {
+ private final String authScheme;
+ private final String authInfo;
+ private final String authValue;
+
+ public AuthPassword(String value) {
+ StringTokenizer st = new StringTokenizer(value, "$");
+ // TODO make it more robust, deal with bad formatting
+ this.authScheme = st.nextToken().trim();
+ this.authInfo = st.nextToken().trim();
+ this.authValue = st.nextToken().trim();
+
+ String expectedAuthScheme = getExpectedAuthScheme();
+ if (expectedAuthScheme != null && !authScheme.equals(expectedAuthScheme))
+ throw new IllegalArgumentException(
+ "Auth scheme " + authScheme + " is not compatible with " + expectedAuthScheme);
+ }
+
+ protected AuthPassword(String authInfo, String authValue) {
+ this.authScheme = getExpectedAuthScheme();
+ if (authScheme == null)
+ throw new IllegalArgumentException("Expected auth scheme cannot be null");
+ this.authInfo = authInfo;
+ this.authValue = authValue;
+ }
+
+ protected AuthPassword(AuthPassword authPassword) {
+ this.authScheme = authPassword.getAuthScheme();
+ this.authInfo = authPassword.getAuthInfo();
+ this.authValue = authPassword.getAuthValue();
+ }
+
+ protected String getExpectedAuthScheme() {
+ return null;
+ }
+
+ protected boolean matchAuthValue(Object object) {
+ return authValue.equals(object.toString());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof AuthPassword))
+ return false;
+ AuthPassword authPassword = (AuthPassword) obj;
+ return authScheme.equals(authPassword.authScheme) && authInfo.equals(authPassword.authInfo)
+ && authValue.equals(authValue);
+ }
+
+ public boolean keyEquals(AuthPassword authPassword) {
+ return authScheme.equals(authPassword.authScheme) && authInfo.equals(authPassword.authInfo);
+ }
+
+ @Override
+ public int hashCode() {
+ return authValue.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return toAuthPassword();
+ }
+
+ public final String toAuthPassword() {
+ return getAuthScheme() + '$' + authInfo + '$' + authValue;
+ }
+
+ public String getAuthScheme() {
+ return authScheme;
+ }
+
+ public String getAuthInfo() {
+ return authInfo;
+ }
+
+ public String getAuthValue() {
+ return authValue;
+ }
+
+ public static AuthPassword matchAuthValue(Attributes attributes, char[] value) {
+ try {
+ Attribute authPassword = attributes.get(LdapAttrs.authPassword.name());
+ if (authPassword != null) {
+ NamingEnumeration<?> values = authPassword.getAll();
+ while (values.hasMore()) {
+ Object val = values.next();
+ AuthPassword token = new AuthPassword(val.toString());
+ String auth;
+ if (Arrays.binarySearch(value, '$') >= 0) {
+ auth = token.authInfo + '$' + token.authValue;
+ } else {
+ auth = token.authValue;
+ }
+ if (Arrays.equals(auth.toCharArray(), value))
+ return token;
+ // if (token.matchAuthValue(value))
+ // return token;
+ }
+ }
+ return null;
+ } catch (NamingException e) {
+ throw new IllegalStateException("Cannot check attribute", e);
+ }
+ }
+
+ public static boolean remove(Attributes attributes, AuthPassword value) {
+ Attribute authPassword = attributes.get(LdapAttrs.authPassword.name());
+ return authPassword.remove(value.toAuthPassword());
+ }
+
+ @Override
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+ for (Callback callback : callbacks) {
+ if (callback instanceof NameCallback)
+ ((NameCallback) callback).setName(toAuthPassword());
+ else if (callback instanceof PasswordCallback)
+ ((PasswordCallback) callback).setPassword(getAuthValue().toCharArray());
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.util.naming.ldap;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.List;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import javax.naming.InvalidNameException;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttribute;
+import javax.naming.directory.BasicAttributes;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+
+import org.argeo.util.naming.LdapAttrs;
+
+/** Basic LDIF parser. */
+public class LdifParser {
+ private final static Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
+
+ protected Attributes addAttributes(SortedMap<LdapName, Attributes> res, int lineNumber, LdapName currentDn,
+ Attributes currentAttributes) {
+ try {
+ Rdn nameRdn = currentDn.getRdn(currentDn.size() - 1);
+ Attribute nameAttr = currentAttributes.get(nameRdn.getType());
+ if (nameAttr == null)
+ currentAttributes.put(nameRdn.getType(), nameRdn.getValue());
+ else if (!nameAttr.get().equals(nameRdn.getValue()))
+ throw new IllegalStateException(
+ "Attribute " + nameAttr.getID() + "=" + nameAttr.get() + " not consistent with DN " + currentDn
+ + " (shortly before line " + lineNumber + " in LDIF file)");
+ Attributes previous = res.put(currentDn, currentAttributes);
+ return previous;
+ } catch (NamingException e) {
+ throw new IllegalStateException("Cannot add " + currentDn, e);
+ }
+ }
+
+ /** With UTF-8 charset */
+ public SortedMap<LdapName, Attributes> read(InputStream in) throws IOException {
+ try (Reader reader = new InputStreamReader(in, DEFAULT_CHARSET)) {
+ return read(reader);
+ } finally {
+ try {
+ in.close();
+ } catch (IOException e) {
+ // silent
+ }
+ }
+ }
+
+ /** Will close the reader. */
+ public SortedMap<LdapName, Attributes> read(Reader reader) throws IOException {
+ SortedMap<LdapName, Attributes> res = new TreeMap<LdapName, Attributes>();
+ try {
+ List<String> lines = new ArrayList<>();
+ try (BufferedReader br = new BufferedReader(reader)) {
+ String line;
+ while ((line = br.readLine()) != null) {
+ lines.add(line);
+ }
+ }
+ if (lines.size() == 0)
+ return res;
+ // add an empty new line since the last line is not checked
+ if (!lines.get(lines.size() - 1).equals(""))
+ lines.add("");
+
+ LdapName currentDn = null;
+ Attributes currentAttributes = null;
+ StringBuilder currentEntry = new StringBuilder();
+
+ readLines: for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) {
+ String line = lines.get(lineNumber);
+ boolean isLastLine = false;
+ if (lineNumber == lines.size() - 1)
+ isLastLine = true;
+ if (line.startsWith(" ")) {
+ currentEntry.append(line.substring(1));
+ if (!isLastLine)
+ continue readLines;
+ }
+
+ if (currentEntry.length() != 0 || isLastLine) {
+ // read previous attribute
+ StringBuilder attrId = new StringBuilder(8);
+ boolean isBase64 = false;
+ readAttrId: for (int i = 0; i < currentEntry.length(); i++) {
+ char c = currentEntry.charAt(i);
+ if (c == ':') {
+ if (i + 1 < currentEntry.length() && currentEntry.charAt(i + 1) == ':')
+ isBase64 = true;
+ currentEntry.delete(0, i + (isBase64 ? 2 : 1));
+ break readAttrId;
+ } else {
+ attrId.append(c);
+ }
+ }
+
+ String attributeId = attrId.toString();
+ // TODO should we really trim the end of the string as well?
+ String cleanValueStr = currentEntry.toString().trim();
+ Object attributeValue = isBase64 ? Base64.getDecoder().decode(cleanValueStr) : cleanValueStr;
+
+ // manage DN attributes
+ if (attributeId.equals(LdapAttrs.DN) || isLastLine) {
+ if (currentDn != null) {
+ //
+ // ADD
+ //
+ Attributes previous = addAttributes(res, lineNumber, currentDn, currentAttributes);
+ if (previous != null) {
+// log.warn("There was already an entry with DN " + currentDn
+// + ", which has been discarded by a subsequent one.");
+ }
+ }
+
+ if (attributeId.equals(LdapAttrs.DN))
+ try {
+ currentDn = new LdapName(attributeValue.toString());
+ currentAttributes = new BasicAttributes(true);
+ } catch (InvalidNameException e) {
+// log.error(attributeValue + " not a valid DN, skipping the entry.");
+ currentDn = null;
+ currentAttributes = null;
+ }
+ }
+
+ // store attribute
+ if (currentAttributes != null) {
+ Attribute attribute = currentAttributes.get(attributeId);
+ if (attribute == null) {
+ attribute = new BasicAttribute(attributeId);
+ currentAttributes.put(attribute);
+ }
+ attribute.add(attributeValue);
+ }
+ currentEntry = new StringBuilder();
+ }
+ currentEntry.append(line);
+ }
+ } finally {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ // silent
+ }
+ }
+ return res;
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.util.naming.ldap;
+
+import static org.argeo.util.naming.LdapAttrs.DN;
+import static org.argeo.util.naming.LdapAttrs.member;
+import static org.argeo.util.naming.LdapAttrs.objectClass;
+import static org.argeo.util.naming.LdapAttrs.uniqueMember;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+
+/** Basic LDIF writer */
+public class LdifWriter {
+ private final static Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
+ private final Writer writer;
+
+ /** Writer must be closed by caller */
+ public LdifWriter(Writer writer) {
+ this.writer = writer;
+ }
+
+ /** Stream must be closed by caller */
+ public LdifWriter(OutputStream out) {
+ this(new OutputStreamWriter(out, DEFAULT_CHARSET));
+ }
+
+ public void writeEntry(LdapName name, Attributes attributes) throws IOException {
+ try {
+ // check consistency
+ Rdn nameRdn = name.getRdn(name.size() - 1);
+ Attribute nameAttr = attributes.get(nameRdn.getType());
+ if (!nameAttr.get().equals(nameRdn.getValue()))
+ throw new IllegalArgumentException(
+ "Attribute " + nameAttr.getID() + "=" + nameAttr.get() + " not consistent with DN " + name);
+
+ writer.append(DN + ": ").append(name.toString()).append('\n');
+ Attribute objectClassAttr = attributes.get(objectClass.name());
+ if (objectClassAttr != null)
+ writeAttribute(objectClassAttr);
+ attributes: for (NamingEnumeration<? extends Attribute> attrs = attributes.getAll(); attrs.hasMore();) {
+ Attribute attribute = attrs.next();
+ if (attribute.getID().equals(DN) || attribute.getID().equals(objectClass.name()))
+ continue attributes;// skip DN attribute
+ if (attribute.getID().equals(member.name()) || attribute.getID().equals(uniqueMember.name()))
+ continue attributes;// skip member and uniqueMember attributes, so that they are always written last
+ writeAttribute(attribute);
+ }
+ // write member and uniqueMember attributes last
+ for (NamingEnumeration<? extends Attribute> attrs = attributes.getAll(); attrs.hasMore();) {
+ Attribute attribute = attrs.next();
+ if (attribute.getID().equals(member.name()) || attribute.getID().equals(uniqueMember.name()))
+ writeMemberAttribute(attribute);
+ }
+ writer.append('\n');
+ writer.flush();
+ } catch (NamingException e) {
+ throw new IllegalStateException("Cannot write LDIF", e);
+ }
+ }
+
+ public void write(Map<LdapName, Attributes> entries) throws IOException {
+ for (LdapName dn : entries.keySet())
+ writeEntry(dn, entries.get(dn));
+ }
+
+ protected void writeAttribute(Attribute attribute) throws NamingException, IOException {
+ for (NamingEnumeration<?> attrValues = attribute.getAll(); attrValues.hasMore();) {
+ Object value = attrValues.next();
+ if (value instanceof byte[]) {
+ String encoded = Base64.getEncoder().encodeToString((byte[]) value);
+ writer.append(attribute.getID()).append(":: ").append(encoded).append('\n');
+ } else {
+ writer.append(attribute.getID()).append(": ").append(value.toString()).append('\n');
+ }
+ }
+ }
+
+ protected void writeMemberAttribute(Attribute attribute) throws NamingException, IOException {
+ // Note: duplicate entries will be swallowed
+ SortedSet<String> values = new TreeSet<>();
+ for (NamingEnumeration<?> attrValues = attribute.getAll(); attrValues.hasMore();) {
+ String value = attrValues.next().toString();
+ values.add(value);
+ }
+
+ for (String value : values) {
+ writer.append(attribute.getID()).append(": ").append(value).append('\n');
+ }
+ }
+}
import org.argeo.cms.swt.CmsSwtUtils;
import org.argeo.eclipse.ui.specific.UiContext;
import org.argeo.jcr.JcrUtils;
-import org.argeo.util.naming.AuthPassword;
import org.argeo.util.naming.SharedSecret;
+import org.argeo.util.naming.ldap.AuthPassword;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.rap.rwt.application.AbstractEntryPoint;
import org.eclipse.rap.rwt.client.WebClient;