From 4da99981affffabe93db1dbbe6fb72147101fcd7 Mon Sep 17 00:00:00 2001
From: Rodrigo Nascimento <rodrigoknascimento@gmail.com>
Date: Mon, 8 Feb 2016 22:27:53 -0200
Subject: [PATCH] Add i18n strings and rename some files

---
 i18n/en.i18n.json                             |  37 ++--
 .../rocketchat-ldap/client/loginHelper.js     |  45 +++++
 .../rocketchat-ldap/server/loginHandler.js    | 172 ++++++++++++++++++
 .../rocketchat-ldap/server/settings.coffee    |  38 ++++
 4 files changed, 281 insertions(+), 11 deletions(-)
 create mode 100644 packages/rocketchat-ldap/client/loginHelper.js
 create mode 100644 packages/rocketchat-ldap/server/loginHandler.js
 create mode 100644 packages/rocketchat-ldap/server/settings.coffee

diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json
index 70902a285e1..1f50f89c005 100644
--- a/i18n/en.i18n.json
+++ b/i18n/en.i18n.json
@@ -263,25 +263,40 @@
   "Layout_Sidenav_Footer_description" : "Footer size is 260 x 70px",
   "Layout_Terms_of_Service" : "Terms of Service",
   "LDAP" : "LDAP",
-  "LDAP_Bind_Search" : "Bind Search",
-  "LDAP_Bind_Search_Description" : "A piece of JSON that governs bind and connection info and is of the form {\"filter\": \"(&(objectCategory=person)(objectclass=user)(memberOf=CN=ROCKET_ACCESS,CN=Users,DC=domain,DC=com)(sAMAccountName=#{username}))\", \"scope\": \"sub\", \"userDN\": \"rocket.service@domain.com\", \"password\": \"urpass\"}",
   "LDAP_CA_Cert" : "CA Cert",
+  "LDAP_Custom_Domain_Search" : "Custom Domain Search",
+  "LDAP_Custom_Domain_Search_Description" : "A piece of JSON that governs bind and connection info and is of the form:<br/> <code>{\"filter\": \"(&(objectCategory=person)(objectclass=user)(memberOf=CN=ROCKET_ACCESS,CN=Users,DC=domain,DC=com)(sAMAccountName=#{username}))\", \"scope\": \"sub\", \"userDN\": \"rocket.service@domain.com\", \"password\": \"urpass\"}</code>",
   "LDAP_Default_Domain" : "Default Domain",
   "LDAP_Description" : "LDAP is a hierarchical database that many companies use to provide single sign on - a facility for sharing one password between multiple sites and services. For advanced configuration information and examples, please consult our wiki: https://github.com/RocketChat/Rocket.Chat/wiki/LDAP-Authentication.",
-  "LDAP_DN" : "Distinguished Name (DN)",
-  "LDAP_DN_Description" : "Search root; example: dc=domain,dc=com",
-  "LDAP_Enable" : "Enable LDAP",
+  "LDAP_Domain_Base" : "Domain Base",
+  "LDAP_Domain_Base_Description" : "The fully qualified Distinguished Name (DN) of an LDAP subtree you want to search for users and groups. You can add as many as you like; however, each group must be defined in the same domain base as the users that belong to it. If you specify restricted user groups, only users that belong to those groups will be in scope. We recommend that you specify the top level of your LDAP directory tree as your domain base and use restricted user groups to control access.",
+  "LDAP_Domain_Search_Object_Category" : "Domain Search Object Category",
+  "LDAP_Domain_Search_Object_Category_Description" : "The *objectCategory* that identify your users. <br/>E.g. `person`, etc.",
+  "LDAP_Domain_Search_Object_Class" : "Domain Search Object Class",
+  "LDAP_Domain_Search_Object_Class_Description" : "The *objectclass* that identify your users. <br/>E.g. `organizationalPerson`, `user`, `inetOrgPerson`, etc.",
+  "LDAP_Domain_Search_Password" : "Domain Search Password",
+  "LDAP_Domain_Search_Password_Description" : "The password for the domain search user.",
+  "LDAP_Domain_Search_User" : "Domain Search User",
+  "LDAP_Domain_Search_User_Description" : "The LDAP user that performs user lookups to authenticate other users when they sign in. <br/>This is typically a service account created specifically for third-party integrations. Use a fully qualified name, such as `cn=Administrator,cn=Users,dc=Example,dc=com`.",
+  "LDAP_Domain_Search_User_ID" : "Domain Search User ID",
+  "LDAP_Domain_Search_User_ID_Description" : "The LDAP attribute that identifies the LDAP user who attempts authentication. This field should be `sAMAccountName` for most Active Directory installations, but it may be `uid` for other LDAP solutions, such as OpenLDAP. You can use `mail` to identify users by e-mail or whatever attribute you want.",
+  "LDAP_Enable" : "Enable",
   "LDAP_Enable_Description" : "Attempt to utilize LDAP for authentication.",
-  "LDAP_Port" : "LDAP Port",
-  "LDAP_Port_Description" : "Port to access LDAP on; eg: 389",
+  "LDAP_Encryption" : "Encryption",
+  "LDAP_Encryption_Description" : "The encryption method used to secure communications to the LDAP server. Examples include `plain` (no encryption), `SSL/LDAPS` (encrypted from the start), and `StartTLS` (upgrade to encrypted communication once connected).",
+  "LDAP_Host" : "Host",
+  "LDAP_Host_Description": "The LDAP host, e.g. `ldap.example.com` or `10.0.0.30`.",
+  "LDAP_Port" : "Port",
+  "LDAP_Port_Description" : "Port to access LDAP. eg: `389` or `636` for LDAPS",
   "LDAP_Reject_Unauthorized" : "Reject Unauthorized",
+  "LDAP_Restricted_User_Groups" : "Restricted User Groups",
+  "LDAP_Restricted_User_Groups_Description" : "If specified, only users in these groups will be allowed to log in. You only need to specify the common names (CNs) of the groups, and you can add as many groups as you like. If no groups are specified, all users within the scope of the specified domain base will be able to sign in. E.g `cn=ROCKET_CHAT,ou=General Groups`.",
   "LDAP_Sync_User_Data" : "Sync Data",
   "LDAP_Sync_User_Data_Description" : "Keep user data in sync with server on login (eg: name, email).",
   "LDAP_Sync_User_Data_FieldMap" : "User Data Field Map",
-  "LDAP_Sync_User_Data_FieldMap_Description" : "Configure how user account fields (like email) are populated from a record in LDAP (once found). As an example, {\"cn\":\"name\", \"mail\":\"email\"} will choose a person's human readable name from the cn attribute, and their email from the mail attribute. Available fields include name, and email.",
-  "LDAP_TLS" : "TLS",
-  "LDAP_Url" : "LDAP URL",
-  "LDAP_Url_Description" : "URL of the LDAP server; example: ldap://company.dns.com",
+  "LDAP_Sync_User_Data_FieldMap_Description" : "Configure how user account fields (like email) are populated from a record in LDAP (once found). <br/>As an example, `{\"cn\":\"name\", \"mail\":\"email\"}` will choose a person's human readable name from the cn attribute, and their email from the mail attribute.<br/> Available fields include `name`, and `email`.",
+  "LDAP_Use_Custom_Domain_Search" : "Use Custom Domain Search",
+  "LDAP_Use_Custom_Domain_Search_Description" : "Write your own filter to search users in the LDAP server.",
   "Leave_Group_Warning": "Are you sure you want to leave the group \"%s\"?",
   "Leave_Private_Warning": "Are you sure you want to leave the discussion with \"%s\"?",
   "Leave_room" : "Leave room",
diff --git a/packages/rocketchat-ldap/client/loginHelper.js b/packages/rocketchat-ldap/client/loginHelper.js
new file mode 100644
index 00000000000..0eb98010f94
--- /dev/null
+++ b/packages/rocketchat-ldap/client/loginHelper.js
@@ -0,0 +1,45 @@
+// Pass in username, password as normal
+// customLdapOptions should be passed in if you want to override LDAP_DEFAULTS
+// on any particular call (if you have multiple ldap servers you'd like to connect to)
+// You'll likely want to set the dn value here {dn: "..."}
+Meteor.loginWithLDAP = function(username, password, customLdapOptions, callback) {
+	// Retrieve arguments as array
+	var args = [];
+	for (var i = 0; i < arguments.length; i++) {
+		args.push(arguments[i]);
+	}
+	// Pull username and password
+	username = args.shift();
+	password = args.shift();
+
+	// Check if last argument is a function
+	// if it is, pop it off and set callback to it
+	if (typeof args[args.length-1] == 'function') callback = args.pop(); else callback = null;
+
+	// if args still holds options item, grab it
+	if (args.length > 0) customLdapOptions = args.shift(); else customLdapOptions = {};
+
+	// Set up loginRequest object
+	var loginRequest = {
+		username: username,
+		ldapPass: password,
+		ldapOptions: customLdapOptions
+	};
+
+	Accounts.callLoginMethod({
+		// Call login method with ldap = true
+		// This will hook into our login handler for ldap
+		methodArguments: [loginRequest],
+		userCallback: function(error, result) {
+			if (error) {
+				if (callback) {
+					callback(error);
+				}
+			} else {
+				if (callback) {
+					callback();
+				}
+			}
+		}
+	});
+};
diff --git a/packages/rocketchat-ldap/server/loginHandler.js b/packages/rocketchat-ldap/server/loginHandler.js
new file mode 100644
index 00000000000..54d6e86b5c5
--- /dev/null
+++ b/packages/rocketchat-ldap/server/loginHandler.js
@@ -0,0 +1,172 @@
+var slug = function (text) {
+	text = slugify(text, '.');
+	return text.replace(/[^0-9a-z-_.]/g, '');
+};
+
+function fallbackDefaultAccountSystem(bind, username, password) {
+	if (typeof username === 'string')
+		if (username.indexOf('@') === -1)
+			username = {username: username};
+		else
+			username = {email: username};
+
+	loginRequest = {
+		user: username,
+		password: {
+			digest: SHA256(password),
+			algorithm: "sha-256"
+		}
+	};
+
+	return Accounts._runLoginHandlers(bind, loginRequest);
+}
+
+function getDataToSyncUserData(ldapUser) {
+	const syncUserData = RocketChat.settings.get('LDAP_Sync_User_Data');
+	const syncUserDataFieldMap = RocketChat.settings.get('LDAP_Sync_User_Data_FieldMap').trim();
+
+	if (syncUserData && syncUserDataFieldMap) {
+		const fieldMap = JSON.parse(syncUserDataFieldMap);
+		let userData = {};
+
+		let emailList = [];
+		_.map(fieldMap, function(userField, ldapField) {
+			if (!ldapUser.hasOwnProperty(ldapField)) {
+				return;
+			}
+
+			switch (userField) {
+				case 'email':
+					if (_.isObject(ldapUser[ldapField] === 'object')) {
+						_.map(ldapUser[ldapField], function (item) {
+							emailList.push({ address: item, verified: true });
+						});
+					} else {
+						emailList.push({ address: ldapUser[ldapField], verified: true });
+					}
+					break;
+
+				case 'name':
+					userData.name = ldapUser[ldapField];
+					break;
+			}
+		});
+
+		if (emailList.length > 0) {
+			userData.emails = emailList;
+		}
+
+		if (_.size(userData)) {
+			return userData;
+		}
+	}
+}
+
+function syncUserData(userId, ldapUser) {
+	const userData = getDataToSyncUserData(ldapUser);
+	if (userId && userData) {
+		Meteor.users.update(userId, { $set: userData });
+	}
+}
+
+Accounts.registerLoginHandler("ldap", function(loginRequest) {
+	const self = this;
+
+	if (!loginRequest.ldapOptions) {
+		return undefined;
+	}
+
+	if (RocketChat.settings.get('LDAP_Enable') !== true) {
+		return fallbackDefaultAccountSystem(self, loginRequest.username, loginRequest.ldapPass);
+	}
+
+	const ldap = new LDAP();
+	let ldapUser;
+
+	try {
+		ldap.connectSync();
+		users = ldap.searchUsersSync(loginRequest.username);
+
+		if (users.length !== 1) {
+			console.log('LDAP: Search returned', users.length, 'record(s)');
+			throw new Error('User not Found');
+		}
+
+		if (ldap.authSync(users[0].dn, loginRequest.ldapPass) === true) {
+			ldapUser = users[0];
+		} else {
+			console.log('wrong password');
+		}
+	} catch(error) {
+		console.log(error);
+	}
+
+	ldap.disconnect();
+
+	if (ldapUser === undefined) {
+		console.log('[LDAP] Falling back to standard account base');
+		return fallbackDefaultAccountSystem(self, loginRequest.username, loginRequest.ldapPass);
+	}
+
+	const username = slug(loginRequest.username);
+
+	// Look to see if user already exists
+	const user = Meteor.users.findOne({
+		username: username
+	});
+
+	// Login user if they exist
+	if (user) {
+		if (user.ldap !== true) {
+			throw new Meteor.Error("LDAP-login-error", "LDAP Authentication succeded, but there's already an existing user with provided username ["+username+"] in Mongo.");
+		}
+
+		const stampedToken = Accounts._generateStampedLoginToken();
+		const hashStampedToken =
+		Meteor.users.update(user._id, {
+			$push: {
+				'services.resume.loginTokens': Accounts._hashStampedToken(stampedToken)
+			}
+		});
+
+		syncUserData(user._id, ldapUser);
+		return {
+			userId: user._id,
+			token: stampedToken.token
+		};
+	}
+
+	// Create new user
+	var userObject = {
+		username: username
+	};
+
+	let userData = getDataToSyncUserData(ldapUser);
+
+	if (userData && userData.emails) {
+		userObject.email = userData.emails[0].address;
+	} else if (ldapUser.mail && ldapUser.mail.indexOf('@') > -1) {
+		userObject.email = ldapUser.mail;
+	} else if (RocketChat.settings.get('LDAP_Default_Domain') !== '') {
+		userObject.email = username + '@' + RocketChat.settings.get('LDAP_Default_Domain');
+	} else {
+		throw new Meteor.Error("LDAP-login-error", "LDAP Authentication succeded, there is no email to create an account.");
+	}
+
+	let userId = Accounts.createUser(userObject);
+
+	syncUserData(userId, ldapUser);
+	Meteor.users.update(userId, {
+		$set: {
+			ldap: true
+		}
+	});
+
+	Meteor.runAsUser(userId, function() {
+		Meteor.call('joinDefaultChannels');
+	});
+
+	return {
+		userId: userId
+	};
+});
diff --git a/packages/rocketchat-ldap/server/settings.coffee b/packages/rocketchat-ldap/server/settings.coffee
new file mode 100644
index 00000000000..5621bc55900
--- /dev/null
+++ b/packages/rocketchat-ldap/server/settings.coffee
@@ -0,0 +1,38 @@
+Meteor.startup ->
+	RocketChat.settings.addGroup 'LDAP', ->
+		enableQuery = {_id: 'LDAP_Enable', value: true}
+		enableTLSQuery = [
+			{_id: 'LDAP_Enable', value: true}
+			{_id: 'LDAP_Encryption', value: {$in: ['tls', 'ssl']}}
+		]
+		customBindSearchEnabledQuery = [
+			{_id: 'LDAP_Enable', value: true}
+			{_id: 'LDAP_Use_Custom_Domain_Search', value: true}
+		]
+		customBindSearchDisabledQuery = [
+			{_id: 'LDAP_Enable', value: true}
+			{_id: 'LDAP_Use_Custom_Domain_Search', value: false}
+		]
+		syncDataQuery = [
+			{_id: 'LDAP_Enable', value: true}
+			{_id: 'LDAP_Sync_User_Data', value: true}
+		]
+
+		@add 'LDAP_Enable', false, { type: 'boolean', public: true }
+		@add 'LDAP_Host', '', { type: 'string', enableQuery: enableQuery }
+		@add 'LDAP_Port', '389', { type: 'string', enableQuery: enableQuery }
+		@add 'LDAP_Encryption', 'plain', { type: 'select', values: [ { key: 'plain', i18nLabel: 'No_Encryption' }, { key: 'tls', i18nLabel: 'StartTLS' }, { key: 'ssl', i18nLabel: 'SSL/LDAPS' } ], enableQuery: enableQuery }
+		@add 'LDAP_CA_Cert', '', { type: 'string', multiline: true, enableQuery: enableTLSQuery }
+		@add 'LDAP_Reject_Unauthorized', true, { type: 'boolean', enableQuery: enableTLSQuery }
+		@add 'LDAP_Domain_Base', '', { type: 'string', enableQuery: enableQuery }
+		@add 'LDAP_Use_Custom_Domain_Search', false, { type: 'boolean' , enableQuery: enableQuery }
+		@add 'LDAP_Custom_Domain_Search', '', { type: 'string' , enableQuery: customBindSearchEnabledQuery }
+		@add 'LDAP_Domain_Search_User', '', { type: 'string', enableQuery: customBindSearchDisabledQuery }
+		@add 'LDAP_Domain_Search_Password', '', { type: 'string', enableQuery: customBindSearchDisabledQuery }
+		@add 'LDAP_Restricted_User_Groups', '', { type: 'string', enableQuery: customBindSearchDisabledQuery }
+		@add 'LDAP_Domain_Search_User_ID', 'sAMAccountName', { type: 'string', enableQuery: customBindSearchDisabledQuery }
+		@add 'LDAP_Domain_Search_Object_Class', 'user', { type: 'string', enableQuery: customBindSearchDisabledQuery }
+		@add 'LDAP_Domain_Search_Object_Category', 'person', { type: 'string', enableQuery: customBindSearchDisabledQuery }
+		@add 'LDAP_Sync_User_Data', false, { type: 'boolean' , enableQuery: enableQuery }
+		@add 'LDAP_Sync_User_Data_FieldMap', '{"cn":"name", "mail":"email"}', { type: 'string', enableQuery: syncDataQuery }
+		@add 'LDAP_Default_Domain', '', { type: 'string' , enableQuery: enableQuery }
-- 
GitLab