فهرست منبع

Merge branch 'edge' into meteor-1.8

Lauri Ojansivu 6 سال پیش
والد
کامیت
df69ade422

+ 14 - 1
CHANGELOG.md

@@ -1,9 +1,22 @@
+# v2.68 2019-05-10 Wekan release
+
+This release adds the following new features:
+
+- [Option to login to the LDAP server with the user's own username and password, instead of an administrator
+  key](https://github.com/wekan/wekan/pull/2399). Default: false (use administrator key).
+  With new setting: LDAP_USER_AUTHENTICATION=true.
+  Thanks to thiagofernando.
+- [Added above new LDAP_USER_AUTHENTION=true option to Snap, Docker and Source settings](https://github.com/wekan/wekan/commit/3bbc805ee42e3c1638b50260d3fafc2b5f936923).
+  Thanks to xet7.
+
+Thanks to above GitHub users for their contributions and translators for their translations.
+
 # v2.67 2019-05-10 Wekan release
 
 This release adds the following new features:
 
 - [Move board to Archive button at each board at All Boards page](https://github.com/wekan/wekan/commit/828f6ea321020eda77fea399df52889e2081dfac).
-  Thanks to xet7. Related #2389
+  Thanks to xet7. Related [#2389](https://github.com/wekan/wekan/issues/2389).
 - [If adding Subtasks does not work on old board, added wiki page how to make it work again](https://github.com/wekan/wekan/wiki/Subtasks).
   Thanks to xet7.
 

+ 1 - 0
Dockerfile

@@ -61,6 +61,7 @@ ENV BUILD_DEPS="apt-utils bsdtar gnupg gosu wget curl bzip2 build-essential pyth
     LDAP_ENCRYPTION=false \
     LDAP_CA_CERT="" \
     LDAP_REJECT_UNAUTHORIZED=false \
+    LDAP_USER_AUTHENTICATION=false \
     LDAP_USER_SEARCH_FILTER="" \
     LDAP_USER_SEARCH_SCOPE="" \
     LDAP_USER_SEARCH_FIELD="" \

+ 1 - 1
Stackerfile.yml

@@ -1,5 +1,5 @@
 appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
-appVersion: "v2.67.0"
+appVersion: "v2.68.0"
 files:
   userUploads:
     - README.md

+ 3 - 0
docker-compose.yml

@@ -400,6 +400,9 @@ services:
       # Reject Unauthorized Certificate
       #- LDAP_REJECT_UNAUTHORIZED=false
       #
+      # Option to login to the LDAP server with the user's own username and password, instead of an administrator key. Default: false (use administrator key).
+      #- LDAP_USER_AUTHENTICATION="true"
+      #
       # Optional extra LDAP filters. Don't forget the outmost enclosing parentheses if needed
       #- LDAP_USER_SEARCH_FILTER=
       #

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "wekan",
-  "version": "v2.67.0",
+  "version": "v2.68.0",
   "description": "Open-Source kanban",
   "private": true,
   "scripts": {

+ 105 - 77
packages/wekan-ldap/server/ldap.js

@@ -1,41 +1,44 @@
 import ldapjs from 'ldapjs';
 import util from 'util';
 import Bunyan from 'bunyan';
-import { log_debug, log_info, log_warn, log_error } from './logger';
+import {log_debug, log_info, log_warn, log_error} from './logger';
+
 
 export default class LDAP {
-  constructor(){
+  constructor() {
     this.ldapjs = ldapjs;
 
     this.connected = false;
 
     this.options = {
-      host: this.constructor.settings_get('LDAP_HOST'),
-      port: this.constructor.settings_get('LDAP_PORT'),
-      Reconnect: this.constructor.settings_get('LDAP_RECONNECT'),
-      timeout: this.constructor.settings_get('LDAP_TIMEOUT'),
-      connect_timeout: this.constructor.settings_get('LDAP_CONNECT_TIMEOUT'),
-      idle_timeout: this.constructor.settings_get('LDAP_IDLE_TIMEOUT'),
-      encryption: this.constructor.settings_get('LDAP_ENCRYPTION'),
-      ca_cert: this.constructor.settings_get('LDAP_CA_CERT'),
-      reject_unauthorized: this.constructor.settings_get('LDAP_REJECT_UNAUTHORIZED') || false,
-      Authentication: this.constructor.settings_get('LDAP_AUTHENTIFICATION'),
-      Authentication_UserDN: this.constructor.settings_get('LDAP_AUTHENTIFICATION_USERDN'),
-      Authentication_Password: this.constructor.settings_get('LDAP_AUTHENTIFICATION_PASSWORD'),
-      Authentication_Fallback: this.constructor.settings_get('LDAP_LOGIN_FALLBACK'),
-      BaseDN: this.constructor.settings_get('LDAP_BASEDN'),
-      Internal_Log_Level: this.constructor.settings_get('INTERNAL_LOG_LEVEL'),
-      User_Search_Filter: this.constructor.settings_get('LDAP_USER_SEARCH_FILTER'),
-      User_Search_Scope: this.constructor.settings_get('LDAP_USER_SEARCH_SCOPE'),
-      User_Search_Field: this.constructor.settings_get('LDAP_USER_SEARCH_FIELD'),
-      Search_Page_Size: this.constructor.settings_get('LDAP_SEARCH_PAGE_SIZE'),
-      Search_Size_Limit: this.constructor.settings_get('LDAP_SEARCH_SIZE_LIMIT'),
-      group_filter_enabled: this.constructor.settings_get('LDAP_GROUP_FILTER_ENABLE'),
-      group_filter_object_class: this.constructor.settings_get('LDAP_GROUP_FILTER_OBJECTCLASS'),
-      group_filter_group_id_attribute: this.constructor.settings_get('LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE'),
+      host                               : this.constructor.settings_get('LDAP_HOST'),
+      port                               : this.constructor.settings_get('LDAP_PORT'),
+      Reconnect                          : this.constructor.settings_get('LDAP_RECONNECT'),
+      timeout                            : this.constructor.settings_get('LDAP_TIMEOUT'),
+      connect_timeout                    : this.constructor.settings_get('LDAP_CONNECT_TIMEOUT'),
+      idle_timeout                       : this.constructor.settings_get('LDAP_IDLE_TIMEOUT'),
+      encryption                         : this.constructor.settings_get('LDAP_ENCRYPTION'),
+      ca_cert                            : this.constructor.settings_get('LDAP_CA_CERT'),
+      reject_unauthorized                : this.constructor.settings_get('LDAP_REJECT_UNAUTHORIZED') || false,
+      Authentication                     : this.constructor.settings_get('LDAP_AUTHENTIFICATION'),
+      Authentication_UserDN              : this.constructor.settings_get('LDAP_AUTHENTIFICATION_USERDN'),
+      Authentication_Password            : this.constructor.settings_get('LDAP_AUTHENTIFICATION_PASSWORD'),
+      Authentication_Fallback            : this.constructor.settings_get('LDAP_LOGIN_FALLBACK'),
+      BaseDN                             : this.constructor.settings_get('LDAP_BASEDN'),
+      Internal_Log_Level                 : this.constructor.settings_get('INTERNAL_LOG_LEVEL'),
+      User_Authentication                : this.constructor.settings_get('LDAP_USER_AUTHENTICATION'),
+      User_Attributes                    : this.constructor.settings_get('LDAP_USER_ATTRIBUTES'),
+      User_Search_Filter                 : this.constructor.settings_get('LDAP_USER_SEARCH_FILTER'),
+      User_Search_Scope                  : this.constructor.settings_get('LDAP_USER_SEARCH_SCOPE'),
+      User_Search_Field                  : this.constructor.settings_get('LDAP_USER_SEARCH_FIELD'),
+      Search_Page_Size                   : this.constructor.settings_get('LDAP_SEARCH_PAGE_SIZE'),
+      Search_Size_Limit                  : this.constructor.settings_get('LDAP_SEARCH_SIZE_LIMIT'),
+      group_filter_enabled               : this.constructor.settings_get('LDAP_GROUP_FILTER_ENABLE'),
+      group_filter_object_class          : this.constructor.settings_get('LDAP_GROUP_FILTER_OBJECTCLASS'),
+      group_filter_group_id_attribute    : this.constructor.settings_get('LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE'),
       group_filter_group_member_attribute: this.constructor.settings_get('LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE'),
-      group_filter_group_member_format: this.constructor.settings_get('LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT'),
-      group_filter_group_name: this.constructor.settings_get('LDAP_GROUP_FILTER_GROUP_NAME'),
+      group_filter_group_member_format   : this.constructor.settings_get('LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT'),
+      group_filter_group_name            : this.constructor.settings_get('LDAP_GROUP_FILTER_GROUP_NAME'),
     };
   }
 
@@ -52,14 +55,16 @@ export default class LDAP {
       log_warn(`Lookup for unset variable: ${name}`);
     }
   }
+
   connectSync(...args) {
-    if (!this._connectSync) {
+     if (!this._connectSync) {
       this._connectSync = Meteor.wrapAsync(this.connectAsync, this);
     }
     return this._connectSync(...args);
   }
 
   searchAllSync(...args) {
+
     if (!this._searchAllSync) {
       this._searchAllSync = Meteor.wrapAsync(this.searchAllAsync, this);
     }
@@ -72,19 +77,19 @@ export default class LDAP {
     let replied = false;
 
     const connectionOptions = {
-      url: `${ this.options.host }:${ this.options.port }`,
-      timeout: this.options.timeout,
+      url           : `${this.options.host}:${this.options.port}`,
+      timeout       : this.options.timeout,
       connectTimeout: this.options.connect_timeout,
-      idleTimeout: this.options.idle_timeout,
-      reconnect: this.options.Reconnect,
+      idleTimeout   : this.options.idle_timeout,
+      reconnect     : this.options.Reconnect,
     };
 
     if (this.options.Internal_Log_Level !== 'disabled') {
       connectionOptions.log = new Bunyan({
-        name: 'ldapjs',
+        name     : 'ldapjs',
         component: 'client',
-        stream: process.stderr,
-        level: this.options.Internal_Log_Level,
+        stream   : process.stderr,
+        level    : this.options.Internal_Log_Level,
       });
     }
 
@@ -95,8 +100,8 @@ export default class LDAP {
     if (this.options.ca_cert && this.options.ca_cert !== '') {
       // Split CA cert into array of strings
       const chainLines = this.constructor.settings_get('LDAP_CA_CERT').split('\n');
-      let cert = [];
-      const ca = [];
+      let cert         = [];
+      const ca         = [];
       chainLines.forEach((line) => {
         cert.push(line);
         if (line.match(/-END CERTIFICATE-/)) {
@@ -108,14 +113,14 @@ export default class LDAP {
     }
 
     if (this.options.encryption === 'ssl') {
-      connectionOptions.url = `ldaps://${ connectionOptions.url }`;
+      connectionOptions.url        = `ldaps://${connectionOptions.url}`;
       connectionOptions.tlsOptions = tlsOptions;
     } else {
-      connectionOptions.url = `ldap://${ connectionOptions.url }`;
+      connectionOptions.url = `ldap://${connectionOptions.url}`;
     }
 
     log_info('Connecting', connectionOptions.url);
-    log_debug(`connectionOptions${  util.inspect(connectionOptions)}`);
+    log_debug(`connectionOptions${util.inspect(connectionOptions)}`);
 
     this.client = ldapjs.createClient(connectionOptions);
 
@@ -189,23 +194,42 @@ export default class LDAP {
 
     if (this.options.User_Search_Filter !== '') {
       if (this.options.User_Search_Filter[0] === '(') {
-        filter.push(`${ this.options.User_Search_Filter }`);
+        filter.push(`${this.options.User_Search_Filter}`);
       } else {
-        filter.push(`(${ this.options.User_Search_Filter })`);
+        filter.push(`(${this.options.User_Search_Filter})`);
       }
     }
 
-    const usernameFilter = this.options.User_Search_Field.split(',').map((item) => `(${ item }=${ username })`);
+    const usernameFilter = this.options.User_Search_Field.split(',').map((item) => `(${item}=${username})`);
 
     if (usernameFilter.length === 0) {
       log_error('LDAP_LDAP_User_Search_Field not defined');
     } else if (usernameFilter.length === 1) {
-      filter.push(`${ usernameFilter[0] }`);
+      filter.push(`${usernameFilter[0]}`);
     } else {
-      filter.push(`(|${ usernameFilter.join('') })`);
+      filter.push(`(|${usernameFilter.join('')})`);
+    }
+
+    return `(&${filter.join('')})`;
+  }
+
+  bindUserIfNecessary(username, password) {
+
+    if (this.domainBinded === true) {
+      return;
+    }
+
+    if (!this.options.User_Authentication) {
+      return;
     }
 
-    return `(&${ filter.join('') })`;
+
+    if (!this.options.BaseDN) throw new Error('BaseDN is not provided');
+
+    const userDn = `uid=${username},${this.options.BaseDN}`;
+
+    this.bindSync(userDn, password);
+    this.domainBinded = true;
   }
 
   bindIfNecessary() {
@@ -218,22 +242,24 @@ export default class LDAP {
     }
 
     log_info('Binding UserDN', this.options.Authentication_UserDN);
+
     this.bindSync(this.options.Authentication_UserDN, this.options.Authentication_Password);
     this.domainBinded = true;
   }
 
   searchUsersSync(username, page) {
     this.bindIfNecessary();
-
     const searchOptions = {
-      filter: this.getUserFilter(username),
-      scope: this.options.User_Search_Scope || 'sub',
+      filter   : this.getUserFilter(username),
+      scope    : this.options.User_Search_Scope || 'sub',
       sizeLimit: this.options.Search_Size_Limit,
     };
 
+    if (!!this.options.User_Attributes) searchOptions.attributes = this.options.User_Attributes.split(',');
+
     if (this.options.Search_Page_Size > 0) {
       searchOptions.paged = {
-        pageSize: this.options.Search_Page_Size,
+        pageSize : this.options.Search_Page_Size,
         pagePause: !!page,
       };
     }
@@ -266,11 +292,11 @@ export default class LDAP {
       Unique_Identifier_Field.forEach((item) => {
         filters.push(new this.ldapjs.filters.EqualityFilter({
           attribute: item,
-          value: new Buffer(id, 'hex'),
+          value    : new Buffer(id, 'hex'),
         }));
       });
 
-      filter = new this.ldapjs.filters.OrFilter({filters});
+      filter = new this.ldapjs.filters.OrFilter({ filters });
     }
 
     const searchOptions = {
@@ -300,7 +326,7 @@ export default class LDAP {
 
     const searchOptions = {
       filter: this.getUserFilter(username),
-      scope: this.options.User_Search_Scope || 'sub',
+      scope : this.options.User_Search_Scope || 'sub',
     };
 
     log_info('Searching user', username);
@@ -320,7 +346,7 @@ export default class LDAP {
     return result[0];
   }
 
-  getUserGroups(username, ldapUser){
+  getUserGroups(username, ldapUser) {
     if (!this.options.group_filter_enabled) {
       return true;
     }
@@ -328,13 +354,13 @@ export default class LDAP {
     const filter = ['(&'];
 
     if (this.options.group_filter_object_class !== '') {
-      filter.push(`(objectclass=${ this.options.group_filter_object_class })`);
+      filter.push(`(objectclass=${this.options.group_filter_object_class})`);
     }
 
     if (this.options.group_filter_group_member_attribute !== '') {
       const format_value = ldapUser[this.options.group_filter_group_member_format];
-      if( format_value ) {
-        filter.push(`(${ this.options.group_filter_group_member_attribute }=${ format_value })`);
+      if (format_value) {
+        filter.push(`(${this.options.group_filter_group_member_attribute}=${format_value})`);
       }
     }
 
@@ -342,7 +368,7 @@ export default class LDAP {
 
     const searchOptions = {
       filter: filter.join('').replace(/#{username}/g, username),
-      scope: 'sub',
+      scope : 'sub',
     };
 
     log_debug('Group list filter LDAP:', searchOptions.filter);
@@ -354,11 +380,11 @@ export default class LDAP {
     }
 
     const grp_identifier = this.options.group_filter_group_id_attribute || 'cn';
-    const groups = [];
+    const groups         = [];
     result.map((item) => {
-      groups.push( item[ grp_identifier ] );
+      groups.push(item[grp_identifier]);
     });
-    log_debug(`Groups: ${  groups.join(', ')}`);
+    log_debug(`Groups: ${groups.join(', ')}`);
     return groups;
 
   }
@@ -373,24 +399,24 @@ export default class LDAP {
     const filter = ['(&'];
 
     if (this.options.group_filter_object_class !== '') {
-      filter.push(`(objectclass=${ this.options.group_filter_object_class })`);
+      filter.push(`(objectclass=${this.options.group_filter_object_class})`);
     }
 
     if (this.options.group_filter_group_member_attribute !== '') {
       const format_value = ldapUser[this.options.group_filter_group_member_format];
-      if( format_value ) {
-        filter.push(`(${ this.options.group_filter_group_member_attribute }=${ format_value })`);
+      if (format_value) {
+        filter.push(`(${this.options.group_filter_group_member_attribute}=${format_value})`);
       }
     }
 
     if (this.options.group_filter_group_id_attribute !== '') {
-      filter.push(`(${ this.options.group_filter_group_id_attribute }=${ this.options.group_filter_group_name })`);
+      filter.push(`(${this.options.group_filter_group_id_attribute}=${this.options.group_filter_group_name})`);
     }
     filter.push(')');
 
     const searchOptions = {
       filter: filter.join('').replace(/#{username}/g, username),
-      scope: 'sub',
+      scope : 'sub',
     };
 
     log_debug('Group filter LDAP:', searchOptions.filter);
@@ -426,15 +452,17 @@ export default class LDAP {
   searchAllPaged(BaseDN, options, page) {
     this.bindIfNecessary();
 
-    const processPage = ({entries, title, end, next}) => {
+    const processPage = ({ entries, title, end, next }) => {
       log_info(title);
       // Force LDAP idle to wait the record processing
       this.client._updateIdle(true);
-      page(null, entries, {end, next: () => {
-        // Reset idle timer
-        this.client._updateIdle();
-        next && next();
-      }});
+      page(null, entries, {
+        end, next: () => {
+          // Reset idle timer
+          this.client._updateIdle();
+          next && next();
+        }
+      });
     };
 
     this.client.search(BaseDN, options, (error, res) => {
@@ -461,7 +489,7 @@ export default class LDAP {
           processPage({
             entries,
             title: 'Internal Page',
-            end: false,
+            end  : false,
           });
           entries = [];
         }
@@ -473,14 +501,14 @@ export default class LDAP {
           processPage({
             entries,
             title: 'Final Page',
-            end: true,
+            end  : true,
           });
         } else if (entries.length) {
           log_info('Page');
           processPage({
             entries,
             title: 'Page',
-            end: false,
+            end  : false,
             next,
           });
           entries = [];
@@ -492,7 +520,7 @@ export default class LDAP {
           processPage({
             entries,
             title: 'Final Page',
-            end: true,
+            end  : true,
           });
           entries = [];
         }
@@ -547,7 +575,7 @@ export default class LDAP {
   }
 
   disconnect() {
-    this.connected = false;
+    this.connected    = false;
     this.domainBinded = false;
     log_info('Disconecting');
     this.client.unbind();

+ 31 - 21
packages/wekan-ldap/server/loginHandler.js

@@ -41,28 +41,38 @@ Accounts.registerLoginHandler('ldap', function(loginRequest) {
   let ldapUser;
 
   try {
-    ldap.connectSync();
-    const users = ldap.searchUsersSync(loginRequest.username);
 
-    if (users.length !== 1) {
-      log_info('Search returned', users.length, 'record(s) for', loginRequest.username);
-      throw new Error('User not Found');
-    }
+      ldap.connectSync();
+
+     if (!!LDAP.settings_get('LDAP_USER_AUTHENTICATION')) {
+        ldap.bindUserIfNecessary(loginRequest.username, loginRequest.ldapPass);
+       ldapUser = ldap.searchUsersSync(loginRequest.username)[0];
+       } else {
+
+       const users = ldap.searchUsersSync(loginRequest.username);
+
+       if (users.length !== 1) {
+         log_info('Search returned', users.length, 'record(s) for', loginRequest.username);
+         throw new Error('User not Found');
+       }
+
+       if (ldap.authSync(users[0].dn, loginRequest.ldapPass) === true) {
+         if (ldap.isUserInGroup(loginRequest.username, users[0])) {
+           ldapUser = users[0];
+         } else {
+           throw new Error('User not in a valid group');
+         }
+       } else {
+         log_info('Wrong password for', loginRequest.username);
+       }
+     }
+
 
-    if (ldap.authSync(users[0].dn, loginRequest.ldapPass) === true) {
-      if (ldap.isUserInGroup(loginRequest.username, users[0])) {
-        ldapUser = users[0];
-      } else {
-        throw new Error('User not in a valid group');
-      }
-    } else {
-      log_info('Wrong password for', loginRequest.username);
-    }
   } catch (error) {
-    log_error(error);
+     log_error(error);
   }
 
-  if (ldapUser === undefined) {
+  if (!ldapUser) {
     if (LDAP.settings_get('LDAP_LOGIN_FALLBACK') === true) {
       return fallbackDefaultAccountSystem(self, loginRequest.username, loginRequest.ldapPass);
     }
@@ -76,8 +86,7 @@ Accounts.registerLoginHandler('ldap', function(loginRequest) {
 
   const Unique_Identifier_Field = getLdapUserUniqueID(ldapUser);
   let user;
-
-  // Attempt to find user by unique identifier
+   // Attempt to find user by unique identifier
 
   if (Unique_Identifier_Field) {
     userQuery = {
@@ -88,14 +97,14 @@ Accounts.registerLoginHandler('ldap', function(loginRequest) {
     log_debug('userQuery', userQuery);
 
     user = Meteor.users.findOne(userQuery);
-  }
+   }
 
   // Attempt to find user by username
 
   let username;
   let email;
 
-  if (LDAP.settings_get('LDAP_USERNAME_FIELD') !== '') {
+   if (LDAP.settings_get('LDAP_USERNAME_FIELD') !== '') {
     username = slug(getLdapUsername(ldapUser));
   } else {
     username = slug(loginRequest.username);
@@ -105,6 +114,7 @@ Accounts.registerLoginHandler('ldap', function(loginRequest) {
     email = getLdapEmail(ldapUser);
   }
 
+
   if (!user) {
     if(email && LDAP.settings_get('LDAP_EMAIL_MATCH_REQUIRE') === true) {
       if(LDAP.settings_get('LDAP_EMAIL_MATCH_VERIFIED') === true) {

+ 2 - 0
releases/virtualbox/start-wekan.sh

@@ -191,6 +191,8 @@
         # LDAP_REJECT_UNAUTHORIZED : Reject Unauthorized Certificate
         # example :  export LDAP_REJECT_UNAUTHORIZED=true
         #export LDAP_REJECT_UNAUTHORIZED=false
+        # Option to login to the LDAP server with the user's own username and password, instead of an administrator key. Default: false (use administrator key).
+        #export LDAP_USER_AUTHENTICATION=true
         # LDAP_USER_SEARCH_FILTER : Optional extra LDAP filters. Don't forget the outmost enclosing parentheses if needed
         # example :  export LDAP_USER_SEARCH_FILTER=
         #export LDAP_USER_SEARCH_FILTER=

+ 2 - 2
sandstorm-pkgdef.capnp

@@ -22,10 +22,10 @@ const pkgdef :Spk.PackageDefinition = (
     appTitle = (defaultText = "Wekan"),
     # The name of the app as it is displayed to the user.
 
-    appVersion = 269,
+    appVersion = 270,
     # Increment this for every release.
 
-    appMarketingVersion = (defaultText = "2.67.0~2019-05-10"),
+    appMarketingVersion = (defaultText = "2.68.0~2019-05-10"),
     # Human-readable presentation of the app version.
 
     minUpgradableAppVersion = 0,

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
snap-src/bin/config


+ 3 - 0
snap-src/bin/wekan-help

@@ -234,6 +234,9 @@ echo -e "Ldap Reject Unauthorized."
 echo -e "Reject Unauthorized Certificate:"
 echo -e "\t$ snap set $SNAP_NAME ldap-reject-unauthorized='true'"
 echo -e "\n"
+echo -e "Option to login to the LDAP server with the user's own username and password, instead of an administrator key. Default: false (use administrator key)."
+echo -e "\t$ snap set $SNAP_NAME ldap-user-authentication='true'"
+echo -e "\n"
 echo -e "Ldap User Search Filter."
 echo -e "Optional extra LDAP filters. Don't forget the outmost enclosing parentheses if needed:"
 echo -e "\t$ snap set $SNAP_NAME ldap-user-search-filter=''"

+ 3 - 0
start-wekan.bat

@@ -173,6 +173,9 @@ REM # LDAP_REJECT_UNAUTHORIZED : Reject Unauthorized Certificate
 REM # example : LDAP_REJECT_UNAUTHORIZED=true
 REM SET LDAP_REJECT_UNAUTHORIZED=false
 
+REM # Option to login to the LDAP server with the user's own username and password, instead of an administrator key. Default: false (use administrator key).
+REM SET LDAP_USER_AUTHENTICATION=true
+
 REM # LDAP_USER_SEARCH_FILTER : Optional extra LDAP filters. Don't forget the outmost enclosing parentheses if needed
 REM # example : LDAP_USER_SEARCH_FILTER=
 REM SET LDAP_USER_SEARCH_FILTER=

+ 2 - 0
start-wekan.sh

@@ -193,6 +193,8 @@
       # LDAP_REJECT_UNAUTHORIZED : Reject Unauthorized Certificate
       # example :  export LDAP_REJECT_UNAUTHORIZED=true
       #export LDAP_REJECT_UNAUTHORIZED=false
+      # Option to login to the LDAP server with the user's own username and password, instead of an administrator key. Default: false (use administrator key).
+      #export LDAP_USER_AUTHENTICATION=true
       # LDAP_USER_SEARCH_FILTER : Optional extra LDAP filters. Don't forget the outmost enclosing parentheses if needed
       # example :  export LDAP_USER_SEARCH_FILTER=
       #export LDAP_USER_SEARCH_FILTER=

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است