UNPKG

11.5 kBJavaScriptView Raw
1/**
2* vim:set sw=2 ts=2 sts=2 ft=javascript expandtab:
3*
4* # Configuration Module
5*
6* ## License
7*
8* Licensed to the Apache Software Foundation (ASF) under one
9* or more contributor license agreements. See the NOTICE file
10* distributed with this work for additional information
11* regarding copyright ownership. The ASF licenses this file
12* to you under the Apache License, Version 2.0 (the
13* "License"); you may not use this file except in compliance
14* with the License. You may obtain a copy of the License at
15*
16* http://www.apache.org/licenses/LICENSE-2.0
17*
18* Unless required by applicable law or agreed to in writing,
19* software distributed under the License is distributed on an
20* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21* KIND, either express or implied. See the License for the
22* specific language governing permissions and limitations
23* under the License.
24*
25* ## Description
26*
27* This is the module for MyPads configuration.
28*/
29
30var settings;
31try {
32 settings = require('ep_etherpad-lite/node/utils/Settings');
33}
34catch (e) {
35 if (process.env.TEST_LDAP) {
36 settings = {
37 'ep_mypads': {
38 'ldap': {
39 'url': 'ldap://rroemhild-test-openldap',
40 'bindDN': 'cn=admin,dc=planetexpress,dc=com',
41 'bindCredentials': 'GoodNewsEveryone',
42 'searchBase': 'ou=people,dc=planetexpress,dc=com',
43 'searchFilter': '(uid={{username}})',
44 'properties': {
45 'login': 'uid',
46 'email': 'mail',
47 'firstname': 'givenName',
48 'lastname': 'sn'
49 },
50 'defaultLang': 'fr'
51 }
52 }
53 };
54 } else {
55 settings = {};
56 }
57}
58module.exports = (function() {
59 'use strict';
60
61 // Dependencies
62 var ld = require('lodash');
63 var ldap = require('ldapjs');
64 var storage = require('./storage.js');
65 var db = storage.db;
66
67 var DBPREFIX = storage.DBPREFIX.CONF;
68
69 /**
70 * `configuration` object is a closure to interact with the whole
71 * config. It will be exported.
72 */
73
74 var configuration = {
75
76 /**
77 * The object contains a private `DEFAULTS` field, holding defaults
78 * settings. Configuration data is taken from the database, applying
79 * defaults when necessary, for example at the plugin initialization.
80 */
81
82 DEFAULTS: {
83 title: 'MyPads',
84 rootUrl: '',
85 allowEtherPads: true,
86 openRegistration: true,
87 hideHelpBlocks: false,
88 passwordMin: 8,
89 passwordMax: 30,
90 languages: { en: 'English', fr: 'Français', de: 'Deutsch', es: 'Español', it: 'Italiano'},
91 loginMsg: { en: '', fr: '', de: '', es: '', it: '' },
92 defaultLanguage: 'en',
93 HTMLExtraHead: '',
94 checkMails: false,
95 SMTPPort: undefined,
96 SMTPHost: undefined,
97 SMTPUser: undefined,
98 SMTPPass: undefined,
99 SMTPEmailFrom: undefined,
100 SMTPSSL: false,
101 SMTPTLS: true,
102 tokenDuration: 60, // in minutes
103 useFirstLastNameInPads: false,
104 insensitiveMailMatch: false,
105 authMethod: 'internal',
106 availableAuthMethods: [ 'internal', 'ldap', 'cas' ],
107 authLdapSettings: {
108 url: 'ldaps://ldap.example.org',
109 bindDN: 'uid=ldap,ou=users,dc=example,dc=org',
110 bindCredentials: 'S3cr3t',
111 searchBase: 'ou=users,dc=example,dc=org',
112 searchFilter: '(uid={{username}})',
113 tlsOptions: {
114 rejectUnauthorized: true
115 },
116 properties: {
117 login: 'uid',
118 email: 'mail',
119 firstname: 'givenName',
120 lastname: 'sn'
121 },
122 defaultLang: 'en'
123 },
124 authCasSettings: {
125 serverUrl: 'https://cas.example.org/cas',
126 protocolVersion: 3.0,
127 properties: {
128 login: 'login',
129 email: 'email',
130 firstname: 'firstname',
131 lastname: 'lastname'
132 },
133 defaultLang: 'en'
134 },
135 allPadsPublicsAuthentifiedOnly: false,
136 deleteJobQueue: false
137 },
138
139 /**
140 * cache object` stored current configuration for faster access than
141 * database
142 */
143
144 cache: {},
145
146 /**
147 * `init` is called when mypads plugin is initialized. It fixes the default
148 * data for the configuration into the database and populate the configuration
149 * cache.
150 * It takes an optional `callback` function used after `db.set` abstraction
151 * to return an eventual *error*.
152 */
153
154 init: function (callback) {
155 callback = callback || function () {};
156 if (!ld.isFunction(callback)) {
157 throw new TypeError('BACKEND.ERROR.TYPE.CALLBACK_FN');
158 }
159
160 var confDefaults = ld.transform(configuration.DEFAULTS,
161 function (memo, val, key) { memo[DBPREFIX + key] = val; });
162
163 var initFromDatabase = function () {
164 storage.fn.getKeys(ld.keys(confDefaults), function (err, res) {
165 if (err) { return callback(err); }
166
167 var pushLdapSettingsToDB = false;
168 configuration.cache = ld.transform(res, function (memo, val, key) {
169 key = key.replace(DBPREFIX, '');
170
171 /* get ldap settings from settings.json if exists and database
172 * informations are empty */
173 if (key === 'authLdapSettings' && ld.isEqual(val, configuration.DEFAULTS.authLdapSettings) &&
174 settings.ep_mypads && settings.ep_mypads.ldap) {
175 /* json parsing of the settings from settings.json is made by Etherpad,
176 * no need to check */
177 val = settings.ep_mypads.ldap;
178 pushLdapSettingsToDB = true;
179 }
180
181 if (ld.isUndefined(val)) {
182 memo[key] = configuration.DEFAULTS[key];
183 } else {
184 memo[key] = val;
185 }
186 });
187
188 if (!pushLdapSettingsToDB) {
189 return callback(null);
190 }
191
192 /* If DB's LDAP settings are the defaults and settings.json contains
193 * LDAP settings, use LDAP auth and put the settings in the database */
194 configuration.cache.authMethod = 'ldap';
195
196 var kv = {
197 authLdapSettings: configuration.cache.authLdapSettings,
198 authMethod: 'ldap'
199 };
200 var dbKv = ld.transform(kv,
201 function (memo, val, key) { memo[DBPREFIX + key] = val; });
202
203 storage.fn.setKeys(dbKv, callback);
204 });
205 };
206
207 // Those settings will evolve with MyPads, thus those from MyPads should
208 // always be used
209 var force = {
210 availableAuthMethods: configuration.DEFAULTS.availableAuthMethods,
211 languages: configuration.DEFAULTS.languages
212 };
213 var dbForce = ld.transform(force,
214 function (memo, val, key) { memo[DBPREFIX + key] = val; });
215
216 storage.fn.setKeys(dbForce, function(err) {
217 if (err) { return callback(err); }
218 // Set the default values in database if they are not already in it
219 storage.fn.setKeysIfNotExists(confDefaults, function (err) {
220 if (err) { return callback(err); }
221 initFromDatabase();
222 });
223 });
224 },
225
226 /**
227 * `get` is an synchronous function taking : a mandatory `key` string
228 * argument. It throws *Error* if error, returns *undefined* if key does
229 * not exist and the result otherwise.
230 */
231
232 get: function (key) {
233 if (!ld.isString(key)) {
234 throw new TypeError('BACKEND.ERROR.TYPE.KEY_STR');
235 }
236 return configuration.cache[key];
237 },
238
239 /**
240 * `set` is an asynchronous function taking two mandatory arguments:
241 *
242 * - `key` string;
243 * - `value`.
244 * - `callback` function argument returning *Error* if error, *null*
245 * otherwise
246 *
247 * `set` sets the `value` for the configuration `key` and takes care of
248 * `cache`.
249 */
250
251 set: function (key, value, callback) {
252 if (!ld.isString(key)) {
253 throw new TypeError('BACKEND.ERROR.TYPE.KEY_STR');
254 }
255 if (ld.isUndefined(value)) {
256 throw new TypeError('BACKEND.ERROR.TYPE.VALUE_REQUIRED');
257 }
258 if (!ld.isFunction(callback)) {
259 throw new TypeError('BACKEND.ERROR.TYPE.CALLBACK_FN');
260 }
261 var dbSet = function(key, value) {
262 db.set(DBPREFIX + key, value, function (err) {
263 if (err) { return callback(err); }
264 configuration.cache[key] = value;
265 callback();
266 });
267 };
268 if (key === 'authLdapSettings' || key === 'authCasSettings') {
269 delete value.attrs;
270 }
271
272 if (key === 'authLdapSettings') {
273 /* Test LDAP settings before registering them */
274 var ldapErr = new Error('BACKEND.ERROR.CONFIGURATION.UNABLE_TO_BIND_TO_LDAP');
275 var ldapSettings = ld.cloneDeep(value);
276
277 /* Not passed to ldapjs, we don't want to autobind
278 * https://github.com/mcavage/node-ldapjs/blob/v1.0.1/lib/client/client.js#L343-L356 */
279 delete ldapSettings.bindDN;
280 delete ldapSettings.bindCredentials;
281
282 var client = ldap.createClient(ldapSettings);
283 client.on('error', function (err) {
284 console.error('LDAP settings change: error. See below for details.');
285 console.error(err);
286 return callback(ldapErr);
287 });
288 client.bind(value.bindDN, value.bindCredentials, function(err) {
289 if (err) {
290 console.error(err);
291 return callback(ldapErr);
292 }
293 client.unbind(function(err) {
294 client.destroy();
295 if (err) {
296 console.error(err);
297 } else {
298 /* Now, we can register new LDAP settings */
299 dbSet(key, value);
300 }
301 });
302 });
303 } else {
304 dbSet(key, value);
305 }
306 },
307
308 /**
309 * `del` is an asynchronous function that removes a configuration option.
310 * It takes two mandatory arguments :
311 *
312 * - a `key` string,
313 * - a `callback` function argument returning *Error* if error
314 *
315 * It takes care of config `cache`.
316 */
317
318 del: function (key, callback) {
319 if (!ld.isString(key)) {
320 throw new TypeError('BACKEND.ERROR.TYPE.KEY_STR');
321 }
322 if (!ld.isFunction(callback)) {
323 throw new TypeError('BACKEND.ERROR.TYPE.CALLBACK_FN');
324 }
325 db.remove(DBPREFIX + key, function (err) {
326 if (err) { return callback(err); }
327 delete configuration.cache[key];
328 callback();
329 });
330 },
331
332 /**
333 * `all` is a synchronous function that returns the whole configuration
334 * from `cache`. Fields / keys are unprefixed.
335 */
336
337 all: function () { return configuration.cache; },
338
339 /**
340 * `public` is a synchronous function that returns the whole publicly
341 * available configuration from `cache`. Fields / keys are unprefixed.
342 */
343
344 public: function () {
345 var all = configuration.all();
346 return ld.pick(all, 'title', 'passwordMin', 'passwordMax', 'languages',
347 'HTMLExtraHead', 'openRegistration', 'hideHelpBlocks', 'useFirstLastNameInPads',
348 'authMethod', 'authCasSettings', 'allPadsPublicsAuthentifiedOnly', 'defaultLanguage',
349 'loginMsg'
350 );
351 },
352
353 /**
354 * `isNotInternalAuth` is a synchronous function that returns true if the
355 * authentication method is not `internal`
356 */
357
358 isNotInternalAuth: function() {
359 return (configuration.get('authMethod') !== 'internal');
360 }
361 };
362
363 return configuration;
364
365}).call(this);
366
367