FreeIPA as ldap backend

Currently ldap backends configurations are limited to
Active Directory and RFC2307 compliant servers.
Commit bddb0234c7430fc322ddc959a75635d4b34bbc2e disabled ability
to modify accountMemberField for RFC2307 which in effect makes
FreeIPA non-usable because it uses nested groups.

The commit adds support for FreeIPA so Gerrit uses memberOf
attribute. The directory expands nested groups in account object
so group expansion in Gerrit is not necessary.

It is possible to change expansion of nested groups with
ldap.accountMemberExpandGroups config parameter. By default it is
set to true to keep current behavior and to false for FreeIPA.

Edit gerrit.config to enable it:

[auth]
  type = LDAP
[ldap]
  server = ldap://freeipa.example.com
  username = uid=gerrit,cn=sysaccounts,cn=etc,dc=example
  password = p1
  accountBase = cn=users,cn=accounts,dc=example
  groupBase = cn=groups,cn=accounts,dc=example

Username and password is required to read memberOf attribute
starting with FreeIPA version 4.

Change-Id: I0da7f2b22a723065b721c88178b933ecc965c345
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;
+    }
   }
 }