UNPKG

11.3 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' },
91 defaultLanguage: 'en',
92 HTMLExtraHead: '',
93 checkMails: false,
94 SMTPPort: undefined,
95 SMTPHost: undefined,
96 SMTPUser: undefined,
97 SMTPPass: undefined,
98 SMTPEmailFrom: undefined,
99 SMTPSSL: false,
100 SMTPTLS: true,
101 tokenDuration: 60, // in minutes
102 useFirstLastNameInPads: false,
103 insensitiveMailMatch: false,
104 authMethod: 'internal',
105 availableAuthMethods: [ 'internal', 'ldap', 'cas' ],
106 authLdapSettings: {
107 url: 'ldaps://ldap.example.org',
108 bindDN: 'uid=ldap,ou=users,dc=example,dc=org',
109 bindCredentials: 'S3cr3t',
110 searchBase: 'ou=users,dc=example,dc=org',
111 searchFilter: '(uid={{username}})',
112 tlsOptions: {
113 rejectUnauthorized: true
114 },
115 properties: {
116 login: 'uid',
117 email: 'mail',
118 firstname: 'givenName',
119 lastname: 'sn'
120 },
121 defaultLang: 'en'
122 },
123 authCasSettings: {
124 serverUrl: 'https://cas.example.org/cas',
125 protocolVersion: 3.0,
126 properties: {
127 login: 'login',
128 email: 'email',
129 firstname: 'firstname',
130 lastname: 'lastname'
131 },
132 defaultLang: 'en'
133 },
134 allPadsPublicsAuthentifiedOnly: false,
135 },
136
137 /**
138 * cache object` stored current configuration for faster access than
139 * database
140 */
141
142 cache: {},
143
144 /**
145 * `init` is called when mypads plugin is initialized. It fixes the default
146 * data for the configuration into the database and populate the configuration
147 * cache.
148 * It takes an optional `callback` function used after `db.set` abstraction
149 * to return an eventual *error*.
150 */
151
152 init: function (callback) {
153 callback = callback || function () {};
154 if (!ld.isFunction(callback)) {
155 throw new TypeError('BACKEND.ERROR.TYPE.CALLBACK_FN');
156 }
157
158 var confDefaults = ld.transform(configuration.DEFAULTS,
159 function (memo, val, key) { memo[DBPREFIX + key] = val; });
160
161 var initFromDatabase = function () {
162 storage.fn.getKeys(ld.keys(confDefaults), function (err, res) {
163 if (err) { return callback(err); }
164
165 var pushLdapSettingsToDB = false;
166 configuration.cache = ld.transform(res, function (memo, val, key) {
167 key = key.replace(DBPREFIX, '');
168
169 /* get ldap settings from settings.json if exists and database
170 * informations are empty */
171 if (key === 'authLdapSettings' && ld.isEqual(val, configuration.DEFAULTS.authLdapSettings) &&
172 settings.ep_mypads && settings.ep_mypads.ldap) {
173 /* json parsing of the settings from settings.json is made by Etherpad,
174 * no need to check */
175 val = settings.ep_mypads.ldap;
176 pushLdapSettingsToDB = true;
177 }
178
179 if (ld.isUndefined(val)) {
180 memo[key] = configuration.DEFAULTS[key];
181 } else {
182 memo[key] = val;
183 }
184 });
185
186 if (!pushLdapSettingsToDB) {
187 return callback(null);
188 }
189
190 /* If DB's LDAP settings are the defaults and settings.json contains
191 * LDAP settings, use LDAP auth and put the settings in the database */
192 configuration.cache.authMethod = 'ldap';
193
194 var kv = {
195 authLdapSettings: configuration.cache.authLdapSettings,
196 authMethod: 'ldap'
197 };
198 var dbKv = ld.transform(kv,
199 function (memo, val, key) { memo[DBPREFIX + key] = val; });
200
201 storage.fn.setKeys(dbKv, callback);
202 });
203 };
204
205 // Those settings will evolve with MyPads, thus those from MyPads should
206 // always be used
207 var force = {
208 availableAuthMethods: configuration.DEFAULTS.availableAuthMethods,
209 languages: configuration.DEFAULTS.languages
210 };
211 var dbForce = ld.transform(force,
212 function (memo, val, key) { memo[DBPREFIX + key] = val; });
213
214 storage.fn.setKeys(dbForce, function(err) {
215 if (err) { return callback(err); }
216 // Set the default values in database if they are not already in it
217 storage.fn.setKeysIfNotExists(confDefaults, function (err) {
218 if (err) { return callback(err); }
219 initFromDatabase();
220 });
221 });
222 },
223
224 /**
225 * `get` is an synchronous function taking : a mandatory `key` string
226 * argument. It throws *Error* if error, returns *undefined* if key does
227 * not exist and the result otherwise.
228 */
229
230 get: function (key) {
231 if (!ld.isString(key)) {
232 throw new TypeError('BACKEND.ERROR.TYPE.KEY_STR');
233 }
234 return configuration.cache[key];
235 },
236
237 /**
238 * `set` is an asynchronous function taking two mandatory arguments:
239 *
240 * - `key` string;
241 * - `value`.
242 * - `callback` function argument returning *Error* if error, *null*
243 * otherwise
244 *
245 * `set` sets the `value` for the configuration `key` and takes care of
246 * `cache`.
247 */
248
249 set: function (key, value, callback) {
250 if (!ld.isString(key)) {
251 throw new TypeError('BACKEND.ERROR.TYPE.KEY_STR');
252 }
253 if (ld.isUndefined(value)) {
254 throw new TypeError('BACKEND.ERROR.TYPE.VALUE_REQUIRED');
255 }
256 if (!ld.isFunction(callback)) {
257 throw new TypeError('BACKEND.ERROR.TYPE.CALLBACK_FN');
258 }
259 var dbSet = function(key, value) {
260 db.set(DBPREFIX + key, value, function (err) {
261 if (err) { return callback(err); }
262 configuration.cache[key] = value;
263 callback();
264 });
265 };
266 if (key === 'authLdapSettings' || key === 'authCasSettings') {
267 delete value.attrs;
268 }
269
270 if (key === 'authLdapSettings') {
271 /* Test LDAP settings before registering them */
272 var ldapErr = new Error('BACKEND.ERROR.CONFIGURATION.UNABLE_TO_BIND_TO_LDAP');
273 var ldapSettings = ld.cloneDeep(value);
274
275 /* Not passed to ldapjs, we don't want to autobind
276 * https://github.com/mcavage/node-ldapjs/blob/v1.0.1/lib/client/client.js#L343-L356 */
277 delete ldapSettings.bindDN;
278 delete ldapSettings.bindCredentials;
279
280 var client = ldap.createClient(ldapSettings);
281 client.on('error', function (err) {
282 console.error('LDAP settings change: error. See below for details.');
283 console.error(err);
284 return callback(ldapErr);
285 });
286 client.bind(value.bindDN, value.bindCredentials, function(err) {
287 if (err) {
288 console.error(err);
289 return callback(ldapErr);
290 }
291 client.unbind(function(err) {
292 client.destroy();
293 if (err) {
294 console.error(err);
295 } else {
296 /* Now, we can register new LDAP settings */
297 dbSet(key, value);
298 }
299 });
300 });
301 } else {
302 dbSet(key, value);
303 }
304 },
305
306 /**
307 * `del` is an asynchronous function that removes a configuration option.
308 * It takes two mandatory arguments :
309 *
310 * - a `key` string,
311 * - a `callback` function argument returning *Error* if error
312 *
313 * It takes care of config `cache`.
314 */
315
316 del: function (key, callback) {
317 if (!ld.isString(key)) {
318 throw new TypeError('BACKEND.ERROR.TYPE.KEY_STR');
319 }
320 if (!ld.isFunction(callback)) {
321 throw new TypeError('BACKEND.ERROR.TYPE.CALLBACK_FN');
322 }
323 db.remove(DBPREFIX + key, function (err) {
324 if (err) { return callback(err); }
325 delete configuration.cache[key];
326 callback();
327 });
328 },
329
330 /**
331 * `all` is a synchronous function that returns the whole configuration
332 * from `cache`. Fields / keys are unprefixed.
333 */
334
335 all: function () { return configuration.cache; },
336
337 /**
338 * `public` is a synchronous function that returns the whole publicly
339 * available configuration from `cache`. Fields / keys are unprefixed.
340 */
341
342 public: function () {
343 var all = configuration.all();
344 return ld.pick(all, 'title', 'passwordMin', 'passwordMax', 'languages',
345 'HTMLExtraHead', 'openRegistration', 'hideHelpBlocks', 'useFirstLastNameInPads',
346 'authMethod', 'authCasSettings', 'allPadsPublicsAuthentifiedOnly'
347 );
348 },
349
350 /**
351 * `isNotInternalAuth` is a synchronous function that returns true if the
352 * authentication method is not `internal`
353 */
354
355 isNotInternalAuth: function() {
356 return (configuration.get('authMethod') !== 'internal');
357 }
358 };
359
360 return configuration;
361
362}).call(this);
363
364