package org.argeo.osgi.useradmin;
+import static org.argeo.osgi.useradmin.LdifName.gidNumber;
+import static org.argeo.osgi.useradmin.LdifName.homeDirectory;
import static org.argeo.osgi.useradmin.LdifName.inetOrgPerson;
import static org.argeo.osgi.useradmin.LdifName.objectClass;
import static org.argeo.osgi.useradmin.LdifName.organizationalPerson;
import static org.argeo.osgi.useradmin.LdifName.person;
+import static org.argeo.osgi.useradmin.LdifName.posixAccount;
import static org.argeo.osgi.useradmin.LdifName.top;
+import static org.argeo.osgi.useradmin.LdifName.uid;
+import static org.argeo.osgi.useradmin.LdifName.uidNumber;
import java.io.File;
import java.net.URI;
import java.util.List;
import javax.naming.InvalidNameException;
+import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+import org.argeo.ArgeoException;
import org.osgi.framework.Filter;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.InvalidSyntaxException;
private String memberAttributeId = "member";
private List<String> credentialAttributeIds = Arrays
- .asList(new String[] { LdifName.userpassword.name() });
+ .asList(new String[] { LdifName.userPassword.name() });
private TransactionManager transactionManager;
// private TransactionSynchronizationRegistry transactionRegistry;
// private Xid editingTransactionXid = null;
private WcXaResource xaResource = new WcXaResource(this);
+ // POSIX
+ private String homeDirectoryBase = "/home";
+
AbstractUserDirectory(Dictionary<String, ?> props) {
properties = new Hashtable<String, Object>();
for (Enumeration<String> keys = props.keys(); keys.hasMoreElements();) {
protected void doGetUser(String key, String value,
List<DirectoryUser> collectedUsers) {
try {
- Filter f = FrameworkUtil.createFilter("(&(" + objectClass + "="
- + getUserObjectClass() + ")(" + key + "=" + value + "))");
+ Filter f = FrameworkUtil
+ .createFilter("(" + key + "=" + value + ")");
List<DirectoryUser> users = doGetRoles(f);
collectedUsers.addAll(users);
} catch (InvalidSyntaxException e) {
LdifUser newRole;
BasicAttribute objClass = new BasicAttribute(objectClass.name());
if (type == Role.USER) {
- String userObjClass = getUserObjectClass();
+ String userObjClass = newUserObjectClass(dn);
objClass.add(userObjClass);
- if (inetOrgPerson.name().equals(userObjClass)) {
+ if (posixAccount.name().equals(userObjClass)) {
+ objClass.add(inetOrgPerson.name());
+ objClass.add(organizationalPerson.name());
+ objClass.add(person.name());
+
+ String username;
+ try {
+ username = dn.getRdn(dn.size() - 1).toAttributes()
+ .get(uid.name()).get().toString();
+ } catch (NamingException e) {
+ throw new UserDirectoryException(
+ "Cannot extract username from " + dn, e);
+ }
+ // TODO look for uid in attributes too?
+ attrs.put(uidNumber.name(), new Long(max(uidNumber.name()) + 1));
+ attrs.put(homeDirectory.name(), generateHomeDirectory(username));
+ // TODO create user private group
+ // NB: on RHEL, the 'users' group has gid 100
+ attrs.put(gidNumber.name(), 100);
+ // attrs.put(LdifName.loginShell.name(),"/sbin/nologin");
+ } else if (inetOrgPerson.name().equals(userObjClass)) {
objClass.add(organizationalPerson.name());
objClass.add(person.name());
} else if (organizationalPerson.name().equals(userObjClass)) {
attrs.put(objClass);
newRole = new LdifUser(this, dn, attrs);
} else if (type == Role.GROUP) {
- objClass.add(getGroupObjectClass());
+ String groupObjClass = getGroupObjectClass();
+ objClass.add(groupObjClass);
+ objClass.add(LdifName.extensibleObject.name());
+ attrs.put(gidNumber.name(), new Long(max(gidNumber.name()) + 1));
objClass.add(top);
attrs.put(objClass);
newRole = new LdifGroup(this, dn, attrs);
return actuallyDeleted;
}
+ // POSIX
+ /** Generate path for a new user home */
+ protected String generateHomeDirectory(String username) {
+ String base = homeDirectoryBase;
+ int atIndex = username.indexOf('@');
+ if (atIndex > 0) {
+ String domain = username.substring(0, atIndex);
+ String name = username.substring(atIndex + 1);
+ return base + '/' + firstCharsToPath(domain, 2) + '/' + domain
+ + '/' + firstCharsToPath(name, 2) + '/' + name;
+ } else if (atIndex == 0 || atIndex == (username.length() - 1)) {
+ throw new ArgeoException("Unsupported username " + username);
+ } else {
+ return base + '/' + firstCharsToPath(username, 2) + '/' + username;
+ }
+ }
+
+ protected long max(String attr) {
+ long max;
+ try {
+ List<DirectoryUser> users = doGetRoles(FrameworkUtil
+ .createFilter("(" + attr + "=*)"));
+ max = 1000;
+ for (DirectoryUser user : users) {
+ long uid = Long.parseLong(user.getAttributes().get(attr).get()
+ .toString());
+ if (uid > max)
+ max = uid;
+ }
+ } catch (Exception e) {
+ throw new UserDirectoryException("Cannot get max of " + attr, e);
+ }
+ return max;
+ }
+
+ /**
+ * Creates depth from a string (typically a username) by adding levels based
+ * on its first characters: "aBcD",2 => a/aB
+ */
+ public static String firstCharsToPath(String str, Integer nbrOfChars) {
+ if (str.length() < nbrOfChars)
+ throw new ArgeoException("String " + str
+ + " length must be greater or equal than " + nbrOfChars);
+ StringBuffer path = new StringBuffer("");
+ StringBuffer curr = new StringBuffer("");
+ for (int i = 0; i < nbrOfChars; i++) {
+ curr.append(str.charAt(i));
+ path.append(curr);
+ if (i < nbrOfChars - 1)
+ path.append('/');
+ }
+ return path.toString();
+ }
+
// TRANSACTION
protected void prepare(UserDirectoryWorkingCopy wc) {
return baseDn;
}
+ /** dn can be null, in that case a default should be returned. */
protected String getUserObjectClass() {
return userObjectClass;
}
+ protected String newUserObjectClass(LdapName dn) {
+ if (dn != null
+ && dn.getRdn(dn.size() - 1).toAttributes().get(uid.name()) != null)
+ return posixAccount.name();
+ else
+ return getUserObjectClass();
+ }
+
protected String getGroupObjectClass() {
return groupObjectClass;
}
"com.sun.jndi.ldap.LdapCtxFactory");
connEnv.put(Context.PROVIDER_URL, getUri().toString());
connEnv.put("java.naming.ldap.attributes.binary",
- LdifName.userpassword.name());
+ LdifName.userPassword.name());
initialLdapContext = new InitialLdapContext(connEnv, null);
// StartTlsResponse tls = (StartTlsResponse) ctx
@Override
protected List<DirectoryUser> doGetRoles(Filter f) {
- // TODO Auto-generated method stub
try {
String searchFilter = f != null ? f.toString() : "(|("
+ objectClass + "=" + getUserObjectClass() + ")("
while (results.hasMoreElements()) {
SearchResult searchResult = results.next();
Attributes attrs = searchResult.getAttributes();
+ LdapName dn = toDn(searchBase, searchResult);
LdifUser role;
if (attrs.get(objectClass.name()).contains(
getGroupObjectClass()))
- role = new LdifGroup(this, toDn(searchBase, searchResult),
- attrs);
+ role = new LdifGroup(this, dn, attrs);
else if (attrs.get(objectClass.name()).contains(
getUserObjectClass()))
- role = new LdifUser(this, toDn(searchBase, searchResult),
- attrs);
+ role = new LdifUser(this, dn, attrs);
else
throw new UserDirectoryException(
"Unsupported LDAP type for "
*/
public enum LdifName {
// Attributes
- dn, cn, sn, uid, mail, displayName, objectClass, userpassword, givenname, description,
+ dn, cn, sn, uid, mail, displayName, objectClass, userPassword, givenName, description,
+ // POSIX attributes
+ uidNumber, gidNumber, homeDirectory, loginShell, gecos,
// Object classes
- inetOrgPerson, organizationalPerson, person, groupOfNames, top;
+ posixAccount, inetOrgPerson, organizationalPerson, person, groupOfNames, groupOfUniqueNames, extensibleObject, top;
public final static String PREFIX = "ldap:";
package org.argeo.osgi.useradmin;
+import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.Dictionary;
import java.util.Enumeration;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
+import java.util.Set;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
// TODO check other sources (like PKCS12)
char[] password = toChars(value);
byte[] hashedPassword = hash(password);
- return hasCredential(LdifName.userpassword.name(), hashedPassword);
+ return hasCredential(LdifName.userPassword.name(), hashedPassword);
}
Object storedValue = getCredentials().get(key);
publishedAttributes = modifiedAttributes;
}
- // protected synchronized void stopEditing(boolean apply) {
- // assert getModifiedAttributes() != null;
- // if (apply)
- // publishedAttributes = getModifiedAttributes();
- // // modifiedAttributes = null;
- // }
-
public DirectoryUser getPublished() {
return new LdifUser(userAdmin, dn, publishedAttributes, true);
}
@Override
public Object nextElement() {
String key = it.next();
- try {
- return getAttributes().get(key).get();
- } catch (NamingException e) {
- throw new UserDirectoryException(
- "Cannot get value for key " + key, e);
- }
+ return get(key);
}
};
Attribute attr = getAttributes().get(key.toString());
if (attr == null)
return null;
- return attr.get();
+ Object value = attr.get();
+ if (value instanceof byte[]) {
+ if (key.equals(LdifName.userPassword.name()))
+ // TODO other cases (certificates, images)
+ return value;
+ value = new String((byte[]) value, Charset.forName("UTF-8"));
+ }
+ if (attr.size() == 1)
+ return value;
+ if (!attr.getID().equals(LdifName.objectClass.name()))
+ return value;
+ // special case for object class
+ NamingEnumeration<?> en = attr.getAll();
+ Set<String> objectClasses = new HashSet<String>();
+ while (en.hasMore()) {
+ String objectClass = en.next().toString();
+ objectClasses.add(objectClass);
+ }
+
+ if (objectClasses.contains(userAdmin.getUserObjectClass()))
+ return userAdmin.getUserObjectClass();
+ else if (objectClasses
+ .contains(userAdmin.getGroupObjectClass()))
+ return userAdmin.getGroupObjectClass();
+ else
+ return value;
} catch (NamingException e) {
throw new UserDirectoryException(
"Cannot get value for attribute " + key, e);
// TODO persist to other sources (like PKCS12)
char[] password = toChars(value);
byte[] hashedPassword = hash(password);
- return put(LdifName.userpassword.name(), hashedPassword);
+ return put(LdifName.userPassword.name(), hashedPassword);
}
userAdmin.checkEdit();
Attribute attribute = getModifiedAttributes().get(
key.toString());
attribute = new BasicAttribute(key.toString());
- attribute.add(value);
+ if (value instanceof String
+ && !isAsciiPrintable(((String) value)))
+ try {
+ attribute.add(((String) value).getBytes("UTF-8"));
+ } catch (UnsupportedEncodingException e) {
+ throw new UserDirectoryException("Cannot encode "
+ + value, e);
+ }
+ else
+ attribute.add(value);
Attribute previousAttribute = getModifiedAttributes().put(
attribute);
if (previousAttribute != null)
}
}
+ private static boolean isAsciiPrintable(String str) {
+ if (str == null) {
+ return false;
+ }
+ int sz = str.length();
+ for (int i = 0; i < sz; i++) {
+ if (isAsciiPrintable(str.charAt(i)) == false) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static boolean isAsciiPrintable(char ch) {
+ return ch >= 32 && ch < 127;
+ }
+
}
package org.argeo.osgi.useradmin;
+import static org.argeo.osgi.useradmin.LdifName.inetOrgPerson;
+import static org.argeo.osgi.useradmin.LdifName.objectClass;
+
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Dictionary;
+import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
+import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
SortedMap<LdapName, Attributes> allEntries = ldifParser.read(in);
for (LdapName key : allEntries.keySet()) {
Attributes attributes = allEntries.get(key);
+ // check for inconsistency
+ Set<String> lowerCase = new HashSet<String>();
+ NamingEnumeration<String> ids = attributes.getIDs();
+ while (ids.hasMoreElements()) {
+ String id = ids.nextElement().toLowerCase();
+ if (lowerCase.contains(id))
+ throw new UserDirectoryException(key
+ + " has duplicate id " + id);
+ lowerCase.add(id);
+ }
+
+ // analyse object classes
NamingEnumeration<?> objectClasses = attributes.get(
- "objectClass").getAll();
+ objectClass.name()).getAll();
+ // System.out.println(key);
objectClasses: while (objectClasses.hasMore()) {
String objectClass = objectClasses.next().toString();
- if (objectClass.equals("inetOrgPerson")) {
+ // System.out.println(" " + objectClass);
+ if (objectClass.equals(inetOrgPerson.name())) {
users.put(key, new LdifUser(this, key, attributes));
break objectClasses;
- } else if (objectClass.equals("groupOfNames")) {
+ } else if (objectClass.equals(getGroupObjectClass())) {
groups.put(key, new LdifGroup(this, key, attributes));
break objectClasses;
}
res.addAll(users.values());
res.addAll(groups.values());
} else {
- // Filter f = FrameworkUtil.createFilter(filter);
- for (DirectoryUser user : users.values())
+ for (DirectoryUser user : users.values()) {
+// System.out.println("\n" + user.getName());
+// Dictionary<String, Object> props = user.getProperties();
+// for (Enumeration<String> keys = props.keys(); keys
+// .hasMoreElements();) {
+// String key = keys.nextElement();
+// System.out.println(" " + key + "=" + props.get(key));
+// }
if (f.match(user.getProperties()))
res.add(user);
+ }
for (DirectoryUser group : groups.values())
if (f.match(group.getProperties()))
res.add(group);
String firstNameStr = firstNameTxt.getText();
if (UiAdminUtils.notNull(firstNameStr))
- props.put(LdifName.givenname.name(), firstNameStr);
+ props.put(LdifName.givenName.name(), firstNameStr);
String cn = UiAdminUtils
.getDefaultCn(firstNameStr, lastNameStr);
private static final long serialVersionUID = 5080437561015853124L;
private final String[] knownProps = { LdifName.uid.name(),
LdifName.dn.name(), LdifName.cn.name(),
- LdifName.givenname.name(), LdifName.sn.name(),
+ LdifName.givenName.name(), LdifName.sn.name(),
LdifName.mail.name() };
public ChooseUserTableViewer(Composite parent, int style) {
commonName.setEnabled(false);
final Text firstName = createLT(tk, body, "First name",
- UiAdminUtils.getProperty(user, LdifName.givenname.name()));
+ UiAdminUtils.getProperty(user, LdifName.givenName.name()));
final Text lastName = createLT(tk, body, "Last name",
UiAdminUtils.getProperty(user, LdifName.sn.name()));
@SuppressWarnings("unchecked")
public void commit(boolean onSave) {
// TODO Sanity checks (mail validity...)
- user.getProperties().put(LdifName.givenname.name(),
+ user.getProperties().put(LdifName.givenName.name(),
firstName.getText());
user.getProperties()
.put(LdifName.sn.name(), lastName.getText());
commonName.setText(UiAdminUtils.getProperty(user,
LdifName.cn.name()));
firstName.setText(UiAdminUtils.getProperty(user,
- LdifName.givenname.name()));
+ LdifName.givenName.name()));
lastName.setText(UiAdminUtils.getProperty(user,
LdifName.sn.name()));
email.setText(UiAdminUtils.getProperty(user,
private final String[] knownProps = { LdifName.uid.name(),
LdifName.dn.name(), LdifName.cn.name(),
- LdifName.givenname.name(), LdifName.sn.name(),
+ LdifName.givenName.name(), LdifName.sn.name(),
LdifName.mail.name() };
public MyUserTableViewer(Composite parent, int style) {
package org.argeo.security.ui.admin.internal.providers;
import org.argeo.osgi.useradmin.LdifName;
+import org.argeo.security.ui.admin.internal.UiAdminUtils;
import org.osgi.service.useradmin.User;
/** Simply declare a label provider that returns the common name of a user */
@Override
public String getText(User user) {
- Object obj = user.getProperties().get(LdifName.cn.name());
- if (obj != null)
- return (String) obj;
- else
- return "";
+ return UiAdminUtils.getProperty(user, LdifName.cn.name());
}
}
\ No newline at end of file
private boolean showSystemRole = true;
private final String[] knownProps = { LdifName.dn.name(),
- LdifName.cn.name(), LdifName.givenname.name(), LdifName.sn.name(),
+ LdifName.cn.name(), LdifName.givenName.name(), LdifName.sn.name(),
LdifName.uid.name(), LdifName.description.name(),
LdifName.mail.name() };