[Pki-devel] [PATCH] 0027..0029 support external authorization LDAP server

Fraser Tweedale ftweedal at redhat.com
Thu Mar 12 08:18:43 UTC 2015


New patch attached (squashed some bugs and cleaned up the
implementation considerably ; squashed back down into one commit).

Other comments inline below.

On Thu, Mar 12, 2015 at 09:40:20AM +0700, Endi Sukma Dewata wrote:
> On 3/12/2015 8:12 AM, Fraser Tweedale wrote:
> >On Wed, Mar 11, 2015 at 02:04:56PM -0400, Ade Lee wrote:
> >>Looks good in general.
> >>
> >>I notice that your patch adds the use of the Vector class.
> >>Vector is old and synchronized - which can slow things down
> >>unnecessarily. Use ArrayList or similar instead.
> >>
> >>Ade
> >>
> >Roger that; I will switch to ArrayList.  For now you can all
> >s/Vector/ArrayList/g in your heads while you review this patch :)
> 
> Quick comments:
> 
> 1. The TOKEN_GROUPS probably should be a List<String> to simplify the
> creation and the usage of the list of groups.
> 
We are constrained by the IAuthToken interface, which has only the
String[] method.

> 2. I'm not quite clear the purpose of this enhancement. If it's meant to be
> a general-purpose directory-based authentication plugin, it would make sense
> to have a fully configurable parameters for retrieving the group
> information. However, if this is only to be used for Dogtag authentication,
> there's already a user-group subsystem that can provide the information. See
> PKIRealm.getRoles().
> 
It is apparently needed for retrieving groups from external
directories.  https://fedorahosted.org/pki/ticket/1174

Cheers,
Fraser

> I may have more comments later.
> 
> -- 
> Endi S. Dewata
-------------- next part --------------
>From 24d1144636c08841ff57503b18bf7f67010e3555 Mon Sep 17 00:00:00 2001
From: Christina Fu <cfu at redhat.com>
Date: Tue, 10 Mar 2015 02:06:02 -0400
Subject: [PATCH] Store groups on AuthToken and update group evaluator

Update the UidPwdDirAuthentication plugin to retrieve all the user's
groups from a directory and store them on the AuthToken.

Also update the group evaluator to match against all the groups
stored in the AuthToken.  The "gid" and "groups" are merged into a
single collection, if the ACL operation is "=" the collection is
checked under disjunction, and if the operation is "!=", then
conjunction.
---
 .../certsrv/authentication/IAuthToken.java         |   3 +
 .../cms/authentication/DirBasedAuthentication.java |  36 ++++++-
 .../authentication/UidPwdDirAuthentication.java    | 117 ++++++++++++++++++---
 .../cms/evaluators/GroupAccessEvaluator.java       |  33 ++++--
 4 files changed, 164 insertions(+), 25 deletions(-)

diff --git a/base/common/src/com/netscape/certsrv/authentication/IAuthToken.java b/base/common/src/com/netscape/certsrv/authentication/IAuthToken.java
index 3c03cc1f5e85a92237067fde98e20d9f13a0b947..c552c4ce24730dc84906c95131f8d5ea9392d883 100644
--- a/base/common/src/com/netscape/certsrv/authentication/IAuthToken.java
+++ b/base/common/src/com/netscape/certsrv/authentication/IAuthToken.java
@@ -38,6 +38,9 @@ public interface IAuthToken {
      * Constant for userid.
      */
     public static final String USER_ID = "userid";
+    public static final String UID = "uid";
+    public static final String GID = "gid";
+    public static final String GROUPS = "groups";
 
     /**
      * Sets an attribute value within this AttrSet.
diff --git a/base/server/cms/src/com/netscape/cms/authentication/DirBasedAuthentication.java b/base/server/cms/src/com/netscape/cms/authentication/DirBasedAuthentication.java
index f2d09df9e410ac817e2a90d4f82bbd4c488a8ab2..78aa399b41263c3da1b050f1729eb48157d730e4 100644
--- a/base/server/cms/src/com/netscape/cms/authentication/DirBasedAuthentication.java
+++ b/base/server/cms/src/com/netscape/cms/authentication/DirBasedAuthentication.java
@@ -74,6 +74,13 @@ public abstract class DirBasedAuthentication
     /* configuration parameter keys */
     protected static final String PROP_LDAP = "ldap";
     protected static final String PROP_BASEDN = "basedn";
+    protected static final String PROP_GROUPS_ENABLE = "groupsEnable";
+    protected static final String PROP_GROUPS_BASEDN = "groupsBasedn";
+    protected static final String PROP_GROUPS = "groups";
+    protected static final String PROP_GROUP_OBJECT_CLASS = "groupObjectClass";
+    protected static final String PROP_GROUP_USERID_NAME = "groupUseridName";
+    protected static final String PROP_USERID_NAME = "useridName";
+    protected static final String PROP_SEARCH_GROUP_USER_BY_USERDN = "searchGroupUserByUserdn";
     protected static final String PROP_DNPATTERN = "dnpattern";
     protected static final String PROP_LDAPSTRINGATTRS = "ldapStringAttributes";
     protected static final String PROP_LDAPBYTEATTRS = "ldapByteAttributes";
@@ -94,6 +101,14 @@ public abstract class DirBasedAuthentication
 
     /* ldap base dn */
     protected String mBaseDN = null;
+    protected boolean mGroupsEnable = false;
+    protected String mGroups = null; // e.g. "ou=Groups"
+    protected String mGroupsBaseDN = null; // in case it's different from mBaseDN
+    protected String mGroupObjectClass = null;
+    protected String mUserIDName = null; // e.g. "uid"
+    protected String mGroupUserIDName = null;  // in case it's different from mUserIDName
+    /* whether to search for member=<userDN> or member=<mGroupUserIdName>=<uid> */
+    protected boolean mSearchGroupUserByUserdn = true;
 
     /* factory of anonymous ldap connections */
     protected ILdapConnFactory mConnFactory = null;
@@ -243,10 +258,25 @@ public abstract class DirBasedAuthentication
 
         /* initialize ldap server configuration */
         mLdapConfig = mConfig.getSubStore(PROP_LDAP);
-        if (needBaseDN)
+        if (needBaseDN) {
             mBaseDN = mLdapConfig.getString(PROP_BASEDN);
-        if (needBaseDN && ((mBaseDN == null) || (mBaseDN.length() == 0) || (mBaseDN.trim().equals(""))))
-            throw new EPropertyNotFound(CMS.getUserMessage("CMS_BASE_GET_PROPERTY_FAILED", "basedn"));
+            if (mBaseDN == null || mBaseDN.trim().equals(""))
+                throw new EPropertyNotFound(CMS.getUserMessage("CMS_BASE_GET_PROPERTY_FAILED", "basedn"));
+            mGroupsEnable = mLdapConfig.getBoolean(PROP_GROUPS_ENABLE, false);
+            CMS.debug("DirBasedAuthentication: mGroupsEnable=" + (mGroupsEnable ? "true" : "false"));
+            mGroupsBaseDN = mLdapConfig.getString(PROP_GROUPS_BASEDN, mBaseDN);
+            CMS.debug("DirBasedAuthentication: mGroupsBaseDN="+ mGroupsBaseDN);
+            mGroups= mLdapConfig.getString(PROP_GROUPS, "ou=groups");
+            CMS.debug("DirBasedAuthentication: mGroups="+ mGroups);
+            mGroupObjectClass = mLdapConfig.getString(PROP_GROUP_OBJECT_CLASS, "groupofuniquenames");
+            CMS.debug("DirBasedAuthentication: mGroupObjectClass="+ mGroupObjectClass);
+            mUserIDName = mLdapConfig.getString(PROP_USERID_NAME, "uid");
+            CMS.debug("DirBasedAuthentication: mUserIDName="+ mUserIDName);
+            mSearchGroupUserByUserdn = mLdapConfig.getBoolean(PROP_SEARCH_GROUP_USER_BY_USERDN, true);
+            CMS.debug("DirBasedAuthentication: mSearchGroupUserByUserdn="+ mSearchGroupUserByUserdn);
+            mGroupUserIDName = mLdapConfig.getString(PROP_GROUP_USERID_NAME, "cn");
+            CMS.debug("DirBasedAuthentication: mGroupUserIDName="+ mGroupUserIDName);
+        }
         mConnFactory = CMS.getLdapAnonConnFactory();
         mConnFactory.init(mLdapConfig);
 
diff --git a/base/server/cms/src/com/netscape/cms/authentication/UidPwdDirAuthentication.java b/base/server/cms/src/com/netscape/cms/authentication/UidPwdDirAuthentication.java
index c85644d59eb4ab0354517140c623f3e36eb5d93c..78244c9c6016776b481b77e9a795bc96fb0839cd 100644
--- a/base/server/cms/src/com/netscape/cms/authentication/UidPwdDirAuthentication.java
+++ b/base/server/cms/src/com/netscape/cms/authentication/UidPwdDirAuthentication.java
@@ -18,10 +18,12 @@
 package com.netscape.cms.authentication;
 
 // ldap java sdk
+import java.util.ArrayList;
 import java.util.Enumeration;
 import java.util.Locale;
 import java.util.Vector;
 
+import netscape.ldap.LDAPAttribute;
 import netscape.ldap.LDAPConnection;
 import netscape.ldap.LDAPEntry;
 import netscape.ldap.LDAPException;
@@ -45,6 +47,7 @@ import com.netscape.certsrv.profile.IProfileAuthenticator;
 import com.netscape.certsrv.property.Descriptor;
 import com.netscape.certsrv.property.IDescriptor;
 import com.netscape.certsrv.request.IRequest;
+import com.netscape.certsrv.usrgrp.EUsrGrpException;
 
 /**
  * uid/pwd directory based authentication manager
@@ -59,7 +62,6 @@ public class UidPwdDirAuthentication extends DirBasedAuthentication
     public static final String CRED_UID = "uid";
     public static final String CRED_PWD = "pwd";
     protected static String[] mRequiredCreds = { CRED_UID, CRED_PWD };
-    public static final String USERID = "userid";
 
     /* Holds configuration parameters accepted by this implementation.
      * This list is passed to the configuration console so configuration
@@ -89,10 +91,68 @@ public class UidPwdDirAuthentication extends DirBasedAuthentication
     };
 
     /**
-     * Default constructor, initialization must follow.
+     * Retrieves group base dn.
      */
-    public UidPwdDirAuthentication() {
-        super();
+    private String getGroupBaseDN() {
+        return mGroups + "," + mGroupsBaseDN;
+    }
+
+    /**
+     * List groups of which user is a member.
+     */
+    private ArrayList<String> listGroups(LDAPConnection ldapconn, String uid, String userdn)
+            throws EUsrGrpException {
+        String method = "UidPwdDirAuthentication: listGroups: ";
+        CMS.debug(method + " begins");
+        String[] attrs = {};
+
+        String k = null;
+        if (mGroupObjectClass.equalsIgnoreCase("groupOfUniqueNames"))
+            k = "uniquemember";
+        else if (mGroupObjectClass.equalsIgnoreCase("groupOfNames"))
+            k = "member";
+        else {
+            CMS.debug("UidPwdDirAuthentication: isMemberOfLdapGroup: unrecognized mGroupObjectClass: " + mGroupObjectClass);
+            return null;
+        }
+
+        String filter = null;
+        if (mSearchGroupUserByUserdn)
+            filter = k + "=" + userdn;
+        else
+            filter = k + "=" + mGroupUserIDName + "=" + uid;
+
+        CMS.debug(method + "searching " + getGroupBaseDN() + " for (&(objectclass=" + mGroupObjectClass + ")(" + filter + "))");
+        try {
+            LDAPSearchResults res = ldapconn.search(
+                getGroupBaseDN(),
+                LDAPv2.SCOPE_SUB,
+                "(&(objectclass=" + mGroupObjectClass + ")(" + filter + "))",
+                attrs, true /* attrsOnly */ );
+            return buildGroups(res);
+        } catch (LDAPException e) {
+            String errMsg = "listGroups()" + e.toString();
+
+            if (e.getLDAPResultCode() == LDAPException.UNAVAILABLE) {
+                errMsg = "listGroups: " + "LDAP database is unavailable";
+            }
+            CMS.debug(method + e);
+        } finally {
+            CMS.debug(method + " ends");
+        }
+        return null;
+    }
+
+    private ArrayList<String> buildGroups(LDAPSearchResults res) {
+        ArrayList<String> v = new ArrayList<>();
+
+        while (res.hasMoreElements()) {
+            LDAPEntry entry = (LDAPEntry) res.nextElement();
+            String groupDN = entry.getDN();
+            CMS.debug("UidPwdDirAuthentication: Authenticate: Found group membership: " + groupDN);
+            v.add(groupDN);
+        }
+        return v;
     }
 
     /**
@@ -131,18 +191,29 @@ public class UidPwdDirAuthentication extends DirBasedAuthentication
                 throw new EInvalidCredentials(CMS.getUserMessage("CMS_AUTHENTICATION_INVALID_CREDENTIAL"));
             }
 
+            /*
+             * first try and see if the directory server supports "memberOf"
+             * if so, use it, if not, then pull all groups to check
+             */
+            String emptyAttrs[] = {};
+            String groupAttrs[] = {"memberOf"};
+
             // get user dn.
-            CMS.debug("Authenticating: Searching for UID=" + uid +
-                      " base DN=" + mBaseDN);
-            LDAPSearchResults res = conn.search(mBaseDN,
-                    LDAPv2.SCOPE_SUB, "(uid=" + uid + ")", null, false);
+            CMS.debug("UidPwdDirAuthentication: Authenticating: Searching for " +
+                    mUserIDName + "=" + uid + " base DN=" + mBaseDN);
+            LDAPSearchResults res = conn.search(
+                mBaseDN,
+                LDAPv2.SCOPE_SUB,
+                "(" + mUserIDName + "=" + uid + ")",
+                (mGroupsEnable ? groupAttrs : emptyAttrs),
+                false);
 
+            LDAPEntry entry = null;
             if (res.hasMoreElements()) {
-                //LDAPEntry entry = (LDAPEntry)res.nextElement();
-                LDAPEntry entry = res.next();
+                entry = res.next();
 
                 userdn = entry.getDN();
-                CMS.debug("Authenticating: Found User DN=" + userdn);
+                CMS.debug("UidPwdDirAuthentication: Authenticating: Found User DN=" + userdn);
             } else {
                 log(ILogger.LL_SECURITY, CMS.getLogMessage("CMS_AUTH_USER_NOT_EXIST", uid));
                 throw new EInvalidCredentials(CMS.getUserMessage("CMS_AUTHENTICATION_INVALID_CREDENTIAL"));
@@ -150,9 +221,29 @@ public class UidPwdDirAuthentication extends DirBasedAuthentication
 
             // bind as user dn and pwd - authenticates user with pwd.
             conn.authenticate(userdn, pwd);
+
+            LDAPAttribute attribute = entry.getAttribute("memberOf");
+            if ( attribute != null ) {
+                CMS.debug("UidPwdDirAuthentication: Authenticate: Found memberOf attribute");
+                String[] groups = attribute.getStringValueArray();
+                token.set(IAuthToken.GROUPS, groups);
+            } else if (mGroupsEnable) {
+                CMS.debug("UidPwdDirAuthentication: Authenticate: memberOf attribute not found.");
+                ArrayList<String> groups = null;
+                try {
+                    groups = listGroups(conn, uid, userdn);
+                } catch (Exception ex) {
+                    CMS.debug("UidPwdDirAuthentication: authenticate: failed listGroups():" + ex + ". ok to continue");
+                }
+                if (groups != null) {
+                    String[] groupsArray = new String[groups.size()];
+                    token.set(IAuthToken.GROUPS, groups.toArray(groupsArray));
+                }
+            }
+
             // set uid in the token.
-            token.set(CRED_UID, uid);
-            token.set(USERID, uid);
+            token.set(IAuthToken.UID, uid);
+            token.set(IAuthToken.USER_ID, uid);
 
             return userdn;
         } catch (ELdapException e) {
diff --git a/base/server/cms/src/com/netscape/cms/evaluators/GroupAccessEvaluator.java b/base/server/cms/src/com/netscape/cms/evaluators/GroupAccessEvaluator.java
index e8a32d752be327bf8cbb4c45d5717f84900fbc4a..9d3cb0e9725a795c06110a2e308f1fc98f3b2238 100644
--- a/base/server/cms/src/com/netscape/cms/evaluators/GroupAccessEvaluator.java
+++ b/base/server/cms/src/com/netscape/cms/evaluators/GroupAccessEvaluator.java
@@ -17,6 +17,7 @@
 // --- END COPYRIGHT BLOCK ---
 package com.netscape.cms.evaluators;
 
+import java.util.ArrayList;
 import com.netscape.certsrv.apps.CMS;
 import com.netscape.certsrv.authentication.IAuthToken;
 import com.netscape.certsrv.base.EBaseException;
@@ -101,9 +102,9 @@ public class GroupAccessEvaluator implements IAccessEvaluator {
             // should define "uid" at a common place
             String uid = null;
 
-            uid = authToken.getInString("userid");
+            uid = authToken.getInString(IAuthToken.USER_ID);
             if (uid == null) {
-                uid = authToken.getInString("uid");
+                uid = authToken.getInString(IAuthToken.UID);
                 if (uid == null) {
                     CMS.debug("GroupAccessEvaluator: evaluate: uid null");
                     log(ILogger.LL_FAILURE, CMS.getLogMessage("EVALUTOR_UID_NULL"));
@@ -112,15 +113,29 @@ public class GroupAccessEvaluator implements IAccessEvaluator {
             }
             CMS.debug("GroupAccessEvaluator: evaluate: uid=" + uid + " value=" + value);
 
-            String groupname = authToken.getInString("gid");
+            ArrayList<String> groups = new ArrayList<>();
 
-            if (groupname != null) {
-                CMS.debug("GroupAccessEvaluator: evaluate: authToken gid=" + groupname);
-                if (op.equals("=")) {
-                    return groupname.equals(Utils.stripQuotes(value));
-                } else if (op.equals("!=")) {
-                    return !groupname.equals(Utils.stripQuotes(value));
+            String gid = authToken.getInString(IAuthToken.GID);
+            if (gid != null)
+                groups.add(gid);
+
+            String[] groupsArray = authToken.getInStringArray(IAuthToken.GROUPS);
+            if (groupsArray != null) {
+                for (int i = 0; i < groupsArray.length; i++)
+                    groups.add(groupsArray[i]);
+            }
+
+            if (groups.size() > 0) {
+                boolean matched = false;
+                for (String group : groups) {
+                    CMS.debug("GroupAccessEvaluator: evaluate: authToken gid=" + group);
+                    if (group.equals(Utils.stripQuotes(value)))
+                        matched = true;
                 }
+                if (op.equals("="))
+                    return matched;
+                else if (op.equals("!="))
+                    return !matched;
             } else {
                 CMS.debug("GroupAccessEvaluator: evaluate: no gid in authToken");
                 IUser id = null;
-- 
2.1.0



More information about the Pki-devel mailing list