Просмотр исходного кода

Merge pull request #224 from weseek/feat/219-ldap-group-filter

Feat/219 ldap group filter
Yuki Takei 8 лет назад
Родитель
Сommit
a5f9186886

+ 5 - 1
CHANGES.md

@@ -1,6 +1,10 @@
 CHANGES
 ========
 
+## 2.3.2
+
+* Improvement: Add LDAP group search options
+
 ## 2.3.1
 
 * Fix: Blockquote doesn't work
@@ -10,7 +14,7 @@ CHANGES
 ## 2.3.0
 
 * Feature: LDAP Authentication
-* Imprv: Prevent XSS
+* Improvement: Prevent XSS
 * Fix: node versions couldn't be shown
 * Support: Upgrade libs
     * express-pino-logger

+ 4 - 1
lib/form/admin/securityPassportLdap.js

@@ -15,6 +15,9 @@ module.exports = form(
       .is(/^(,?[^,=\s]+=[^,=\s]+){1,}$/, 'Bind DN is invalid. <small><a href="https://regex101.com/r/jK8lpO/1">&gt;&gt; Regex</a></small>'),
   field('settingForm[security:passport-ldap:bindDNPassword]'),
   field('settingForm[security:passport-ldap:searchFilter]'),
-  field('settingForm[security:passport-ldap:attrMapUsername]')
+  field('settingForm[security:passport-ldap:attrMapUsername]'),
+  field('settingForm[security:passport-ldap:groupSearchBase]'),
+  field('settingForm[security:passport-ldap:groupSearchFilter]'),
+  field('settingForm[security:passport-ldap:groupDnProperty]')
 );
 

+ 3 - 0
lib/models/config.js

@@ -61,6 +61,9 @@ module.exports = function(crowi) {
       'security:passport-ldap:bindDNPassword' : undefined,
       'security:passport-ldap:searchFilter' : undefined,
       'security:passport-ldap:attrMapUsername' : undefined,
+      'security:passport-ldap:groupSearchBase' : undefined,
+      'security:passport-ldap:groupSearchFilter' : undefined,
+      'security:passport-ldap:groupDnProperty' : undefined,
 
       'aws:bucket'          : 'crowi',
       'aws:region'          : 'ap-northeast-1',

+ 0 - 1
lib/routes/index.js

@@ -64,7 +64,6 @@ module.exports = function(crowi, app) {
   app.post('/_api/admin/security/google'        , loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.securityGoogle, admin.api.securitySetting);
   app.post('/_api/admin/security/mechanism'     , loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.securityMechanism, admin.api.securitySetting);
   app.post('/_api/admin/security/passport-ldap' , loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.securityPassportLdap, admin.api.securityPassportLdapSetting);
-  app.post('/_api/admin/security/passport-ldap-test' , loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.securityPassportLdap, admin.api.securitySetting);
 
   // markdown admin
   app.get('/admin/markdown'                   , loginRequired(crowi, app) , middleware.adminRequired() , admin.markdown.index);

+ 27 - 0
lib/routes/login-passport.js

@@ -42,6 +42,22 @@ module.exports = function(crowi, app) {
     return res.redirect('/login');
   };
 
+  /**
+   * return true(valid) or false(invalid)
+   *
+   *  true ... group filter is not defined or the user has one or more groups
+   *  false ... group filter is defined and the user has any group
+   *
+   */
+  function isValidLdapUserByGroupFilter(user) {
+    let bool = true;
+    if (user._groups != null) {
+      if (user._groups.length == 0) {
+        bool = false;
+      }
+    }
+    return bool;
+  }
   /**
    * middleware that login with LdapStrategy
    * @param {*} req
@@ -79,6 +95,10 @@ module.exports = function(crowi, app) {
 
       // authentication failure
       if (!ldapAccountInfo) { return next(); }
+      // check groups
+      if (!isValidLdapUserByGroupFilter(ldapAccountInfo)) {
+        return loginFailure(req, res, next);
+      }
 
       /*
        * authentication success
@@ -153,6 +173,13 @@ module.exports = function(crowi, app) {
         });
       }
       if (user) {
+        // check groups
+        if (!isValidLdapUserByGroupFilter(user)) {
+          return res.json({
+            status: 'warning',
+            message: 'The user is found, but that has no groups.',
+          });
+        }
         return res.json({
           status: 'success',
           message: 'Successfully authenticated.',

+ 17 - 6
lib/service/passport.js

@@ -156,11 +156,14 @@ class PassportService {
    */
   getLdapConfigurationFunc(config, opts) {
     // get configurations
-    const isUserBind      = config.crowi['security:passport-ldap:isUserBind'];
-    const serverUrl       = config.crowi['security:passport-ldap:serverUrl'];
-    const bindDN          = config.crowi['security:passport-ldap:bindDN'];
-    const bindCredentials = config.crowi['security:passport-ldap:bindDNPassword'];
-    const searchFilter    = config.crowi['security:passport-ldap:searchFilter'] || '(uid={{username}})';
+    const isUserBind          = config.crowi['security:passport-ldap:isUserBind'];
+    const serverUrl           = config.crowi['security:passport-ldap:serverUrl'];
+    const bindDN              = config.crowi['security:passport-ldap:bindDN'];
+    const bindCredentials     = config.crowi['security:passport-ldap:bindDNPassword'];
+    const searchFilter        = config.crowi['security:passport-ldap:searchFilter'] || '(uid={{username}})';
+    const groupSearchBase     = config.crowi['security:passport-ldap:groupSearchBase'];
+    const groupSearchFilter   = config.crowi['security:passport-ldap:groupSearchFilter'];
+    const groupDnProperty     = config.crowi['security:passport-ldap:groupDnProperty'] || 'uid';
 
     // parse serverUrl
     // see: https://regex101.com/r/0tuYBB/1
@@ -180,6 +183,9 @@ class PassportService {
       debug(`LdapStrategy: bindCredentials=${bindCredentials}`);
     }
     debug(`LdapStrategy: searchFilter=${searchFilter}`);
+    debug(`LdapStrategy: groupSearchBase=${groupSearchBase}`);
+    debug(`LdapStrategy: groupSearchFilter=${groupSearchFilter}`);
+    debug(`LdapStrategy: groupDnProperty=${groupDnProperty}`);
 
     return (req, callback) => {
       // get credentials from form data
@@ -193,12 +199,17 @@ class PassportService {
           bindDN.replace(/{{username}}/, loginForm.username):
           bindDN;
       const fixedBindCredentials = (isUserBind) ? loginForm.password : bindCredentials;
+      let serverOpt = { url, bindDN: fixedBindDN, bindCredentials: fixedBindCredentials, searchBase, searchFilter };
+
+      if (groupSearchBase && groupSearchFilter) {
+        serverOpt = Object.assign(serverOpt, { groupSearchBase, groupSearchFilter, groupDnProperty });
+      }
 
       process.nextTick(() => {
         const mergedOpts = Object.assign({
           usernameField: PassportService.USERNAME_FIELD,
           passwordField: PassportService.PASSWORD_FIELD,
-          server: { url, bindDN: fixedBindDN, bindCredentials: fixedBindCredentials, searchBase, searchFilter },
+          server: serverOpt,
         }, opts);
         debug('ldap configuration: ', mergedOpts);
         callback(null, mergedOpts);

+ 57 - 7
lib/views/admin/widget/passport/ldap.html

@@ -110,17 +110,67 @@
         </div>
       </div>
 
-      <h4>Attribute Mapping</h4>
+      <h4>Attribute Mapping (Optional)</h4>
 
-      <p class="well well-sm">Specification of mappings when creating new users</p>
+      <div class="form-group">
+        <label for="settingForm[security:passport-ldap:attrMapUsername]" class="col-xs-3 control-label">username</label>
+        <div class="col-xs-6">
+          <input class="form-control" type="text" placeholder="Default: uid"
+              name="settingForm[security:passport-ldap:attrMapUsername]" value="{{ settingForm['security:passport-ldap:attrMapUsername'] || '' }}">
+          <p class="help-block">
+            <small>
+              Specification of mappings when creating new users
+            </small>
+          </p>
+        </div>
+      </div>
+
+      <h4>Group Search Filter (Optional)</h4>
 
       <div class="form-group">
-          <label for="settingForm[security:passport-ldap:attrMapUsername]" class="col-xs-3 control-label">username</label>
-          <div class="col-xs-6">
-            <input class="form-control" type="text" placeholder="Default: uid"
-                name="settingForm[security:passport-ldap:attrMapUsername]" value="{{ settingForm['security:passport-ldap:attrMapUsername'] || '' }}">
-          </div>
+        <label for="settingForm[security:passport-ldap:groupSearchBase]" class="col-xs-3 control-label">Group Search Base DN</label>
+        <div class="col-xs-6">
+          <input class="form-control" type="text"
+              name="settingForm[security:passport-ldap:groupSearchBase]" value="{{ settingForm['security:passport-ldap:groupSearchBase'] || '' }}">
+          <p class="help-block">
+            <small>
+              The base DN from which to search for groups. If defined, also <code>Group Search Filter</code> must be defined for the search to work.<br>
+              Example: <code>ou=groups,dc=domain,dc=com</code><br>
+            </small>
+          </p>
         </div>
+      </div>
+
+      <div class="form-group">
+        <label for="settingForm[security:passport-ldap:groupSearchFilter]" class="col-xs-3 control-label">Group Search Filter</label>
+        <div class="col-xs-6">
+          <input class="form-control" type="text"
+              name="settingForm[security:passport-ldap:groupSearchFilter]" value="{{ settingForm['security:passport-ldap:groupSearchFilter'] || '' }}">
+          <p class="help-block">
+            <small>
+              The query used to filter for groups.<br>
+              Use <code>{% raw %}{{dn}}{% endraw %}</code> to have it replaced of the found user object.<br>
+              <br>
+              Example: <code>(&(cn=group1)(memberUid={% raw %}{{dn}}{% endraw %}))</code> hits the groups
+              which has <code>cn=group1</code> and <code>memberUid</code> includes the user's <code>uid</code>
+              (when <code>Group DN Property</code> is not changed from the default value.)
+            </small>
+          </p>
+        </div>
+      </div>
+
+      <div class="form-group">
+        <label for="settingForm[security:passport-ldap:groupSearchFilter]" class="col-xs-3 control-label">Group DN Property</label>
+        <div class="col-xs-6">
+          <input class="form-control" type="text" placeholder="Default: uid"
+              name="settingForm[security:passport-ldap:groupDnProperty]" value="{{ settingForm['security:passport-ldap:groupDnProperty'] || '' }}">
+          <p class="help-block">
+            <small>
+              The property of user object to use in <code>{% raw %}{{dn}}{% endraw %}</code> interpolation of <code>Group Search Filter</code>.
+            </small>
+          </p>
+        </div>
+      </div>
 
     </div><!-- /.passport-ldap-configurations -->