Merge "FreeIPA as ldap backend"
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 759e5cd..031f025 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -2679,9 +2679,9 @@
 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.
+detected during startup.  The guessed defaults support
+link:http://www.ietf.org/rfc/rfc2307.txt[RFC 2307], Active
+Directory and link:https://www.freeipa.org[FreeIPA].
 
 ----
 [ldap]
@@ -2785,7 +2785,7 @@
 is `(uid=${username})` or `(cn=${username})`, but the proper
 setting depends on the LDAP schema used by the directory server.
 +
-Default is `(uid=${username})` for RFC 2307 servers,
+Default is `(uid=${username})` for FreeIPA and RFC 2307 servers,
 and `(&(objectClass=user)(sAMAccountName=${username}))`
 for Active Directory.
 
@@ -2803,7 +2803,7 @@
 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` for RFC 2307 servers,
+Default is `displayName` for FreeIPA and RFC 2307 servers,
 and `${givenName} ${sn}` for Active Directory.
 
 [[ldap.accountEmailAddress]]ldap.accountEmailAddress::
@@ -2846,17 +2846,25 @@
 recommended not to make changes to this setting that would cause the
 value to differ, as this will prevent users from logging in.
 +
-Default is `uid` for RFC 2307 servers,
+Default is `uid` for FreeIPA and RFC 2307 servers,
 and `${sAMAccountName.toLowerCase}` for Active Directory.
 
 [[ldap.accountMemberField]]ldap.accountMemberField::
 +
 _(Optional)_ Name of an attribute on the user account object which
 contains the groups the user is part of. Typically used for Active
-Directory servers.
+Directory and FreeIPA servers.
 +
 Default is unset for RFC 2307 servers (disabled)
-and `memberOf` for Active Directory.
+and `memberOf` for Active Directory and FreeIPA.
+
+[[ldap.accountMemberExpandGroups]]ldap.accountMemberExpandGroups::
++
+_(Optional)_ Whether to expand nested groups recursively. This
+setting is used only if `ldap.accountMemberField` is set.
++
+Default is unset for FreeIPA and `true` for RFC 2307 servers
+and Active Directory.
 
 [[ldap.fetchMemberOfEagerly]]ldap.fetchMemberOfEagerly::
 +
@@ -2866,7 +2874,7 @@
 as this will result in a much faster LDAP login.
 +
 Default is unset for RFC 2307 servers (disabled) and `true` for
-Active Directory.
+Active Directory and FreeIPA.
 
 [[ldap.groupBase]]ldap.groupBase::
 +
@@ -2895,7 +2903,7 @@
 `${groupname}` is replaced with the search term supplied by the
 group owner.
 +
-Default is `(cn=${groupname})` for RFC 2307,
+Default is `(cn=${groupname})` for FreeIPA and RFC 2307 servers,
 and `(&(objectClass=group)(cn=${groupname}))` for Active Directory.
 
 [[ldap.groupMemberPattern]]ldap.groupMemberPattern::
@@ -2913,7 +2921,7 @@
 Attributes such as `${dn}` or `${uidNumber}` may be useful.
 +
 Default is `(|(memberUid=${username})(gidNumber=${gidNumber}))` for
-RFC 2307, and unset (disabled) for Active Directory.
+RFC 2307, and unset (disabled) for Active Directory and FreeIPA.
 
 [[ldap.groupName]]ldap.groupName::
 +
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java
index 354dc62..1e45387 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java
@@ -276,7 +276,8 @@
 
   private void recursivelyExpandGroups(final Set<String> groupDNs,
       final LdapSchema schema, final DirContext ctx, final String groupDN) {
-    if (groupDNs.add(groupDN) && schema.accountMemberField != null) {
+    if (groupDNs.add(groupDN) && schema.accountMemberField != null
+        && schema.accountMemberExpandGroups) {
       ImmutableSet<String> cachedParentsDNs = parentGroups.getIfPresent(groupDN);
       if (cachedParentsDNs == null) {
         // Recursively identify the groups it is a member of.
@@ -319,6 +320,7 @@
     final ParameterizedString accountEmailAddress;
     final ParameterizedString accountSshUserName;
     final String accountMemberField;
+    final boolean accountMemberExpandGroups;
     final String[] accountMemberFieldArray;
     final List<LdapQuery> accountQueryList;
     final List<LdapQuery> accountWithMemberOfQueryList;
@@ -390,6 +392,9 @@
       } else {
         accountMemberFieldArray = null;
       }
+      accountMemberExpandGroups =
+          LdapRealm.optional(config, "accountMemberExpandGroups",
+              type.accountMemberExpandGroups());
 
       final SearchScope accountScope = LdapRealm.scope(config, "accountScope");
       final String accountPattern =
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapType.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapType.java
index 3c1b0d2..4e68653 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapType.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapType.java
@@ -30,6 +30,11 @@
       return new ActiveDirectory();
     }
 
+    supported = rootAtts.get("supportedExtension");
+    if (supported != null && supported.contains("2.16.840.1.113730.3.8.10.1")) {
+      return new FreeIPA();
+    }
+
     return RFC_2307;
   }
 
@@ -47,6 +52,8 @@
 
   abstract String accountMemberField();
 
+  abstract boolean accountMemberExpandGroups();
+
   abstract String accountPattern();
 
   private static class Rfc2307 extends LdapType {
@@ -89,6 +96,11 @@
     String accountPattern() {
       return "(uid=${username})";
     }
+
+    @Override
+    boolean accountMemberExpandGroups() {
+      return true;
+    }
   }
 
   private static class ActiveDirectory extends LdapType {
@@ -131,5 +143,58 @@
     String accountPattern() {
       return "(&(objectClass=user)(sAMAccountName=${username}))";
     }
+
+    @Override
+    boolean accountMemberExpandGroups() {
+      return true;
+    }
+  }
+
+  private static class FreeIPA extends LdapType {
+
+    @Override
+    String groupPattern() {
+      return "(cn=${groupname})";
+    }
+
+    @Override
+    String groupName() {
+      return "cn";
+    }
+
+    @Override
+    String groupMemberPattern() {
+      return null; // FreeIPA uses memberOf in the account
+    }
+
+    @Override
+    String accountFullName() {
+      return "displayName";
+    }
+
+    @Override
+    String accountEmailAddress() {
+      return "mail";
+    }
+
+    @Override
+    String accountSshUserName() {
+      return "uid";
+    }
+
+    @Override
+    String accountMemberField() {
+      return "memberOf";
+    }
+
+    @Override
+    String accountPattern() {
+      return "(uid=${username})";
+    }
+
+    @Override
+    boolean accountMemberExpandGroups() {
+      return false;
+    }
   }
 }