summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorShawn O. Pearce <sop@google.com>2009-10-29 14:46:03 -0700
committerShawn O. Pearce <sop@google.com>2009-10-29 14:46:03 -0700
commit02c2e8067edccb0066bdacd6173de50397d8d621 (patch)
tree1cdb6e22a179420e6d99465bbe10495d7d435495
parent4ae1b67220881a65fe290aa055ae9e085ad95d9e (diff)
Set proper LDAP defaults for Active Directory
When the directory server we have been configured to use is an instance of Microsoft's Active Directory product we want to use a different set of default attributes which is more suited to that product's schema. According to the Microsoft product documentation, all versions of Active Directory return an attribute on the rootDSE that helps to detect their "embrace and extend" features: supportedCapabilities: 1.2.840.113556.1.4.800 If present, we change the defaults in the configuration to match what the server would be more likely to expect. Bug: issue 307 Change-Id: I87e35e49354b77d0fcfbb8a57e17592a97b591b0 Signed-off-by: Shawn O. Pearce <sop@google.com>
-rw-r--r--Documentation/config-gerrit.txt38
-rw-r--r--src/main/java/com/google/gerrit/server/ioutil/BlindSSLSocketFactory.java112
-rw-r--r--src/main/java/com/google/gerrit/server/ldap/LdapRealm.java217
-rw-r--r--src/main/java/com/google/gerrit/server/ldap/LdapType.java152
4 files changed, 421 insertions, 98 deletions
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index b17b8a2c30..0c499323dd 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -471,10 +471,12 @@ LDAP integration is only enabled if `auth.type` was set to
`HTTP_LDAP` or `LDAP`. See above for a detailed description of
the auth.type settings and their implications.
-An example LDAP configuration follows, and then discussion of the
-parameters introduced here. Defaults were chosen to align closely
-with link:http://www.ietf.org/rfc/rfc2307.txt[RFC 2307], and many
-common deployments.
+An example LDAP configuration follows, and then discussion of
+the parameters introduced here. Suitable defaults for most
+parameters are automatically guessed based on the type of server
+detected during startup. The guessed defaults support both
+link:http://www.ietf.org/rfc/rfc2307.txt[RFC 2307] and Active
+Directory.
====
[ldap]
@@ -500,6 +502,14 @@ If auth.type is `LDAP` this setting should use `ldaps://` to
ensure the end user's plaintext password is transmitted only over
an encrypted connection.
+[[ldap.sslVerify]]ldap.sslVerify::
++
+If false and ldap.server is an `ldaps://` style URL, Gerrit
+will not verify the server certificate when it connects to
+perform a query.
++
+By default, true, requiring the certificate to be verified.
+
[[ldap.username]]ldap.username::
+
_(Optional)_ Username to bind to the LDAP server with. If not set,
@@ -542,7 +552,9 @@ the `ldap.accountBase` tree. A typical setting for this parameter
is `(uid=$\{username\})` or `(cn=$\{username\})`, but the proper
setting depends on the LDAP schema used by the directory server.
+
-Default is `(uid=$\{username\})`, matching RFC 2307.
+Default is `(uid=$\{username\})` for RFC 2307 servers,
+and `(&(objectClass=user)(sAMAccountName=${username}))`
+for Active Directory.
[[ldap.accountFullName]]ldap.accountFullName::
+
@@ -558,7 +570,8 @@ example to join given name and surname together use the pattern
If set, users will be unable to modify their full name field, as
Gerrit will populate it only from the LDAP data.
+
-Default is `displayName`, a common value for most servers.
+Default is `displayName` for RFC 2307 servers,
+and `${givenName} ${sn}` for Active Directory.
[[ldap.accountEmailAddress]]ldap.accountEmailAddress::
+
@@ -575,7 +588,7 @@ If set, the preferred email address will be prefilled from LDAP,
but users may still be able to register additional email address,
and select a different preferred email address.
+
-Default is `mail`, a common value for most servers.
+Default is `mail`.
[[ldap.accountSshUserName]]ldap.accountSshUserName::
+
@@ -597,7 +610,8 @@ example `$\{userPrincipalName.localPart\}` would provide only 'user'.
If set, users will be unable to modify their SSH username field, as
Gerrit will populate it only from the LDAP data.
+
-Default is `uid`, a common value for most servers.
+Default is `uid` for RFC 2307 servers,
+and `${sAMAccountName.toLowerCase}` for Active Directory.
[[ldap.accountMemberField]ldap.accountMemberField::
+
@@ -605,7 +619,8 @@ _(Optional)_ Name of an attribute on the user account object which
contains the groups the user is part of. Typically used for ActiveDirectory
servers.
+
-Default is `memberOf`, to correspond with ActiveDirectory's default.
+Default is unset for RFC 2307 servers (disabled)
+and `memberOf` for Active Directory.
[[ldap.groupBase]]ldap.groupBase::
+
@@ -629,7 +644,7 @@ Name of an attribute on the group object which matches to the name
of a group registered in the Gerrit database. Typically this would
be the display name of the group.
+
-Default is `cn`, a common value for most servers.
+Default is `cn`.
[[ldap.groupMemberPattern]]ldap.groupMemberPattern::
+
@@ -645,7 +660,8 @@ corresponding attribute (in this case, `fooBarAttribute`) as read
from the user's account object matched under `ldap.accountBase`.
Attributes such as `$\{dn\}` or `$\{uidNumber\}` may be useful.
+
-Default is `(memberUid=$\{username\})`, matching RFC 2307.
+Default is `(memberUid=$\{username\})` for RFC 2307,
+and unset (disabled) for Active Directory.
[[mimetype]]Section mimetype
diff --git a/src/main/java/com/google/gerrit/server/ioutil/BlindSSLSocketFactory.java b/src/main/java/com/google/gerrit/server/ioutil/BlindSSLSocketFactory.java
new file mode 100644
index 0000000000..853035b2d6
--- /dev/null
+++ b/src/main/java/com/google/gerrit/server/ioutil/BlindSSLSocketFactory.java
@@ -0,0 +1,112 @@
+// Copyright (C) 2009 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.ioutil;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.security.GeneralSecurityException;
+import java.security.SecureRandom;
+import java.security.cert.X509Certificate;
+
+import javax.net.SocketFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+/** SSL socket factory that ignores SSL certificate validation. */
+public class BlindSSLSocketFactory extends SSLSocketFactory {
+ private static final BlindSSLSocketFactory INSTANCE;
+
+ static {
+ final X509TrustManager dummyTrustManager = new X509TrustManager() {
+ public X509Certificate[] getAcceptedIssuers() {
+ return null;
+ }
+
+ public void checkClientTrusted(X509Certificate[] chain, String authType) {
+ }
+
+ public void checkServerTrusted(X509Certificate[] chain, String authType) {
+ }
+ };
+
+ try {
+ final SSLContext context = SSLContext.getInstance("SSL");
+ final TrustManager[] trustManagers = {dummyTrustManager};
+ final SecureRandom rng = new SecureRandom();
+ context.init(null, trustManagers, rng);
+ INSTANCE = new BlindSSLSocketFactory(context.getSocketFactory());
+ } catch (GeneralSecurityException e) {
+ throw new RuntimeException("Cannot create BlindSslSocketFactory", e);
+ }
+ }
+
+ public static SocketFactory getDefault() {
+ return INSTANCE;
+ }
+
+ private final SSLSocketFactory sslFactory;
+
+ private BlindSSLSocketFactory(final SSLSocketFactory sslFactory) {
+ this.sslFactory = sslFactory;
+ }
+
+ @Override
+ public Socket createSocket(Socket s, String host, int port, boolean autoClose)
+ throws IOException {
+ return sslFactory.createSocket(s, host, port, autoClose);
+ }
+
+ @Override
+ public String[] getDefaultCipherSuites() {
+ return sslFactory.getDefaultCipherSuites();
+ }
+
+ @Override
+ public String[] getSupportedCipherSuites() {
+ return sslFactory.getSupportedCipherSuites();
+ }
+
+ @Override
+ public Socket createSocket() throws IOException {
+ return sslFactory.createSocket();
+ }
+
+ @Override
+ public Socket createSocket(String host, int port) throws IOException,
+ UnknownHostException {
+ return sslFactory.createSocket(host, port);
+ }
+
+ @Override
+ public Socket createSocket(InetAddress host, int port) throws IOException {
+ return sslFactory.createSocket(host, port);
+ }
+
+ @Override
+ public Socket createSocket(String host, int port, InetAddress localHost,
+ int localPort) throws IOException, UnknownHostException {
+ return sslFactory.createSocket(host, port, localHost, localPort);
+ }
+
+ @Override
+ public Socket createSocket(InetAddress address, int port,
+ InetAddress localAddress, int localPort) throws IOException {
+ return sslFactory.createSocket(address, port, localAddress, localPort);
+ }
+}
diff --git a/src/main/java/com/google/gerrit/server/ldap/LdapRealm.java b/src/main/java/com/google/gerrit/server/ldap/LdapRealm.java
index 67df74a76f..cf39357c12 100644
--- a/src/main/java/com/google/gerrit/server/ldap/LdapRealm.java
+++ b/src/main/java/com/google/gerrit/server/ldap/LdapRealm.java
@@ -32,15 +32,16 @@ import com.google.gerrit.server.cache.SelfPopulatingCache;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.ioutil.BlindSSLSocketFactory;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
+import org.eclipse.jgit.lib.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.eclipse.jgit.lib.Config;
import java.util.ArrayList;
import java.util.Arrays;
@@ -55,12 +56,13 @@ import java.util.Properties;
import java.util.Set;
import javax.naming.Context;
-import javax.naming.NamingException;
import javax.naming.NamingEnumeration;
-import javax.naming.directory.DirContext;
-import javax.naming.directory.InitialDirContext;
+import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.InitialDirContext;
+import javax.net.ssl.SSLSocketFactory;
@Singleton
class LdapRealm implements Realm {
@@ -72,6 +74,8 @@ class LdapRealm implements Realm {
private final String server;
private final String username;
private final String password;
+ private final LdapType type;
+ private final boolean sslVerify;
private final AuthConfig authConfig;
private final SchemaFactory<ReviewDb> schema;
@@ -107,27 +111,44 @@ class LdapRealm implements Realm {
this.server = required(config, "server");
this.username = optional(config, "username");
this.password = optional(config, "password");
+ this.sslVerify = config.getBoolean("ldap", "sslverify", true);
+ this.type = discoverLdapType();
groupMemberQueryList = new ArrayList<LdapQuery>();
groupByNameQueryList = new ArrayList<LdapQuery>();
+ accountQueryList = new ArrayList<LdapQuery>();
+
+ final Set<String> groupAtts = new HashSet<String>();
+ final Set<String> accountAtts = new HashSet<String>();
// Group query
//
- final Set<String> groupAtts = new HashSet<String>();
- groupName = reqdef(config, "groupName", "cn");
+ groupName = reqdef(config, "groupName", type.groupName());
groupAtts.add(groupName);
- final List<String> groupBaseList = requiredList(config, "groupBase");
+
final SearchScope groupScope = scope(config, "groupScope");
final String groupMemberPattern =
- reqdef(config, "groupMemberPattern", "(memberUid=${username})");
- for (String groupBase : groupBaseList) {
- LdapQuery groupMemberQuery =
- new LdapQuery(groupBase, groupScope, groupMemberPattern, groupAtts);
- if (groupMemberQuery.getParameters().isEmpty()) {
- throw new IllegalArgumentException(
- "No variables in ldap.groupMemberPattern");
+ optdef(config, "groupMemberPattern", type.groupMemberPattern());
+
+ for (String groupBase : optionalList(config, "groupBase")) {
+ if (groupMemberPattern != null) {
+ final LdapQuery groupMemberQuery =
+ new LdapQuery(groupBase, groupScope, groupMemberPattern, groupAtts);
+ if (groupMemberQuery.getParameters().isEmpty()) {
+ throw new IllegalArgumentException(
+ "No variables in ldap.groupMemberPattern");
+ }
+
+ for (final String name : groupMemberQuery.getParameters()) {
+ if (!USERNAME.equals(name)) {
+ groupNeedsAccount = true;
+ accountAtts.add(name);
+ }
+ }
+
+ groupMemberQueryList.add(groupMemberQuery);
}
- groupMemberQueryList.add(groupMemberQuery);
+
groupByNameQueryList.add(new LdapQuery(groupBase, groupScope, "("
+ groupName + "=${groupname})", LdapQuery.ALL_ATTRIBUTES));
}
@@ -148,38 +169,29 @@ class LdapRealm implements Realm {
// Account query
//
- final Set<String> accountAtts = new HashSet<String>();
- accountFullName = paramString(config, "accountFullName", "displayName");
+ accountFullName = paramString(config, "accountFullName", type.accountFullName());
if (accountFullName != null) {
accountAtts.addAll(accountFullName.getParameterNames());
}
- accountEmailAddress = paramString(config, "accountEmailAddress", "mail");
+ accountEmailAddress = paramString(config, "accountEmailAddress", type.accountEmailAddress());
if (accountEmailAddress != null) {
accountAtts.addAll(accountEmailAddress.getParameterNames());
}
- accountSshUserName = paramString(config, "accountSshUserName", "uid");
+ accountSshUserName = paramString(config, "accountSshUserName", type.accountSshUserName());
if (accountSshUserName != null) {
accountAtts.addAll(accountSshUserName.getParameterNames());
}
- accountMemberField = reqdef(config, "accountMemberField", "memberOf");
+ accountMemberField = optdef(config, "accountMemberField", type.accountMemberField());
if (accountMemberField != null) {
accountAtts.add(accountMemberField);
}
- for (final String name : groupMemberQueryList.get(0).getParameters()) {
- if (!USERNAME.equals(name)) {
- groupNeedsAccount = true;
- accountAtts.add(name);
- }
- }
final SearchScope accountScope = scope(config, "accountScope");
final String accountPattern =
- reqdef(config, "accountPattern", "(uid=${username})");
+ reqdef(config, "accountPattern", type.accountPattern());
- final List<String> accountBaseList = requiredList(config, "accountBase");
- accountQueryList = new ArrayList<LdapQuery>();
- for (String accountBase : accountBaseList) {
- LdapQuery accountQuery =
+ for (String accountBase : requiredList(config, "accountBase")) {
+ final LdapQuery accountQuery =
new LdapQuery(accountBase, accountScope, accountPattern, accountAtts);
if (accountQuery.getParameters().isEmpty()) {
throw new IllegalArgumentException(
@@ -363,40 +375,51 @@ class LdapRealm implements Realm {
private Set<AccountGroup.Id> queryForGroups(final DirContext ctx,
final String username, LdapQuery.Result account) throws NamingException,
AccountException {
- if (account == null) {
- account = findAccount(ctx, username);
- }
+ final Set<AccountGroup.Id> actual = new HashSet<AccountGroup.Id>();
- final HashMap<String, String> params = new HashMap<String, String>();
- params.put(USERNAME, username);
- if (groupNeedsAccount) {
- for (final String name : groupMemberQueryList.get(0).getParameters()) {
- params.put(name, account.get(name));
+ if (!groupMemberQueryList.isEmpty()) {
+ final HashMap<String, String> params = new HashMap<String, String>();
+
+ if (groupNeedsAccount) {
+ if (account == null) {
+ account = findAccount(ctx, username);
+ }
+ for (final String name : groupMemberQueryList.get(0).getParameters()) {
+ params.put(name, account.get(name));
+ }
}
- }
- final Set<AccountGroup.Id> actual = new HashSet<AccountGroup.Id>();
- for (LdapQuery groupMemberQuery : groupMemberQueryList) {
- for (LdapQuery.Result r : groupMemberQuery.query(ctx, params)) {
- final String name = r.get(groupName);
- final AccountGroup group = groupCache.lookup(name);
- if (group != null && isLdapGroup(group)) {
- actual.add(group.getId());
+ params.put(USERNAME, username);
+
+ for (LdapQuery groupMemberQuery : groupMemberQueryList) {
+ for (LdapQuery.Result r : groupMemberQuery.query(ctx, params)) {
+ final String name = r.get(groupName);
+ final AccountGroup group = groupCache.lookup(name);
+ if (group != null && isLdapGroup(group)) {
+ actual.add(group.getId());
+ }
}
}
}
- NamingEnumeration<?> groups = account.getAll(accountMemberField).getAll();
- while (groups.hasMore()) {
- final String dn = (String) groups.next();
+ if (accountMemberField != null) {
+ if (account == null) {
+ account = findAccount(ctx, username);
+ }
- for (String cn : groupsFor(ctx, dn)) {
- AccountGroup group = groupCache.lookup(cn);
- if (null != group && isLdapGroup(group)) {
- actual.add(group.getId());
- }
- }
+ NamingEnumeration<?> groups = account.getAll(accountMemberField).getAll();
+ while (groups.hasMore()) {
+ final String dn = (String) groups.next();
+
+ for (String name : groupsFor(ctx, dn)) {
+ AccountGroup group = groupCache.lookup(name);
+ if (group != null && isLdapGroup(group)) {
+ actual.add(group.getId());
+ }
+ }
+ }
}
+
if (actual.isEmpty()) {
return Collections.emptySet();
} else {
@@ -404,34 +427,30 @@ class LdapRealm implements Realm {
}
}
- /*
- * We store the group names because LDAP doesn't support a query
- * that will tell us whether a user is part of a parent group
- */
- private ArrayList<String> groupsFor(DirContext ctx, String dn) {
- ArrayList<String> out = new ArrayList<String>();
- String parentDn;
-
- try {
- // add the current item's name
- String attNames[] = { groupName, accountMemberField };
- Attributes groupAtts = ctx.getAttributes(dn, attNames);
- String cn = (String) groupAtts.get(groupName).get();
- out.add(cn);
-
- // get its parents
- Attribute parentAttrs = ctx.getAttributes(dn).get(accountMemberField);
- if (parentAttrs != null) {
- NamingEnumeration<?> parents = parentAttrs.getAll();
- while (parents.hasMore()) {
- parentDn = String.valueOf(parents.next());
- out.addAll(groupsFor(ctx, parentDn));
- }
- }
- } catch (NamingException e) {
- log.warn("Could not find dn", e);
+ private Set<String> groupsFor(final DirContext ctx, final String groupDN) {
+ final Set<String> groupNames = new HashSet<String>();
+ try {
+ // Determine this group's name.
+ //
+ final String attNames[] = {groupName, accountMemberField};
+ final Attributes groupAtts = ctx.getAttributes(groupDN, attNames);
+ final String name = (String) groupAtts.get(groupName).get();
+ groupNames.add(name);
+
+ // Recursively identify the groups it is a member of.
+ //
+ final Attribute in = ctx.getAttributes(groupDN).get(accountMemberField);
+ if (in != null) {
+ final NamingEnumeration<?> otherGroups = in.getAll();
+ while (otherGroups.hasMore()) {
+ final String otherDN = (String) otherGroups.next();
+ groupNames.addAll(groupsFor(ctx, otherDN));
+ }
}
- return out;
+ } catch (NamingException e) {
+ log.warn("Could not find group " + groupDN, e);
+ }
+ return groupNames;
}
private boolean isLdapGroup(final AccountGroup group) {
@@ -535,11 +554,21 @@ class LdapRealm implements Realm {
}
}
- private DirContext open() throws NamingException {
+ private Properties createContextProperties() {
final Properties env = new Properties();
env.put(Context.INITIAL_CONTEXT_FACTORY, LDAP);
env.put(Context.PROVIDER_URL, server);
+ if (server.startsWith("ldaps:") && !sslVerify) {
+ Class<? extends SSLSocketFactory> factory = BlindSSLSocketFactory.class;
+ env.put("java.naming.ldap.factory.socket", factory.getName());
+ }
+ return env;
+ }
+
+ private DirContext open() throws NamingException {
+ final Properties env = createContextProperties();
if (username != null) {
+ env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, username);
env.put(Context.SECURITY_CREDENTIALS, password != null ? password : "");
}
@@ -547,9 +576,8 @@ class LdapRealm implements Realm {
}
private void authenticate(String dn, String password) throws AccountException {
- final Properties env = new Properties();
- env.put(Context.INITIAL_CONTEXT_FACTORY, LDAP);
- env.put(Context.PROVIDER_URL, server);
+ final Properties env = createContextProperties();
+ env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, dn);
env.put(Context.SECURITY_CREDENTIALS, password != null ? password : "");
try {
@@ -559,6 +587,21 @@ class LdapRealm implements Realm {
}
}
+ private LdapType discoverLdapType() {
+ try {
+ final DirContext ctx = open();
+ try {
+ return LdapType.guessType(ctx);
+ } finally {
+ ctx.close();
+ }
+ } catch (NamingException e) {
+ log.warn("Cannot discover type of LDAP server at " + server
+ + ", assuming the server is RFC 2307 compliant.", e);
+ return LdapType.RFC_2307;
+ }
+ }
+
private LdapQuery.Result findAccount(final DirContext ctx,
final String username) throws NamingException, AccountException {
final HashMap<String, String> params = new HashMap<String, String>();
diff --git a/src/main/java/com/google/gerrit/server/ldap/LdapType.java b/src/main/java/com/google/gerrit/server/ldap/LdapType.java
new file mode 100644
index 0000000000..eb5185d37c
--- /dev/null
+++ b/src/main/java/com/google/gerrit/server/ldap/LdapType.java
@@ -0,0 +1,152 @@
+// Copyright (C) 2009 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.ldap;
+
+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.SearchControls;
+import javax.naming.directory.SearchResult;
+
+abstract class LdapType {
+ static final LdapType RFC_2307 = new Rfc2307();
+
+ static LdapType guessType(final DirContext ctx) throws NamingException {
+ final SearchControls cons = new SearchControls();
+ final NamingEnumeration<SearchResult> res;
+
+ final Attributes rootAtts = ctx.getAttributes("");
+ Attribute supported = rootAtts.get("supportedCapabilities");
+ if (supported != null && supported.contains("1.2.840.113556.1.4.800")) {
+ return new ActiveDirectory(rootAtts);
+ }
+
+ return RFC_2307;
+ }
+
+ abstract String groupName();
+
+ abstract String groupMemberPattern();
+
+ abstract String accountFullName();
+
+ abstract String accountEmailAddress();
+
+ abstract String accountSshUserName();
+
+ abstract String accountMemberField();
+
+ abstract String accountPattern();
+
+ private static class Rfc2307 extends LdapType {
+ @Override
+ String groupName() {
+ return "cn";
+ }
+
+ @Override
+ String groupMemberPattern() {
+ return "(memberUid=${username})";
+ }
+
+ @Override
+ String accountFullName() {
+ return "displayName";
+ }
+
+ @Override
+ String accountEmailAddress() {
+ return "mail";
+ }
+
+ @Override
+ String accountSshUserName() {
+ return "uid";
+ }
+
+ @Override
+ String accountMemberField() {
+ return null; // Not defined in RFC 2307
+ }
+
+ @Override
+ String accountPattern() {
+ return "(uid=${username})";
+ }
+ }
+
+ private static class ActiveDirectory extends LdapType {
+ private final String defaultDomain;
+
+ ActiveDirectory(final Attributes atts) throws NamingException {
+ // Convert "defaultNamingContext: DC=foo,DC=example,DC=com" into
+ // the a standard DNS name as we would expect to find in the suffix
+ // part of the userPrincipalName.
+ //
+ Attribute defaultNamingContext = atts.get("defaultNamingContext");
+ if (defaultNamingContext == null || defaultNamingContext.size() < 1) {
+ throw new NamingException("rootDSE has no defaultNamingContext");
+ }
+
+ final StringBuilder b = new StringBuilder();
+ for (String n : ((String) defaultNamingContext.get(0)).split(", *")) {
+ if (n.toUpperCase().startsWith("DC=")) {
+ if (b.length() > 0) {
+ b.append('.');
+ }
+ b.append(n.substring(3));
+ }
+ }
+ defaultDomain = b.toString();
+ }
+
+ @Override
+ String groupName() {
+ return "cn";
+ }
+
+ @Override
+ String groupMemberPattern() {
+ return null; // Active Directory uses memberOf in the account
+ }
+
+ @Override
+ String accountFullName() {
+ return "${givenName} ${sn}";
+ }
+
+ @Override
+ String accountEmailAddress() {
+ return "mail";
+ }
+
+ @Override
+ String accountSshUserName() {
+ return "${sAMAccountName.toLowerCase}";
+ }
+
+ @Override
+ String accountMemberField() {
+ return "memberOf";
+ }
+
+ @Override
+ String accountPattern() {
+ return "(&(objectClass=user)(sAMAccountName=${username}))";
+ }
+ }
+}