UNPKG

11 kBJavaScriptView Raw
1/**
2* vim:set sw=2 ts=2 sts=2 ft=javascript expandtab:
3*
4* # Permission 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 module contains all functions about permission for groups and pads,
28* according to authenticated user.
29*/
30
31// External dependencies
32var getPad;
33var getPadID;
34var getPadHTML;
35var isWaitingforDeletion;
36try {
37 // Normal case : when installed as a plugin
38 var epVersion = parseFloat(require('ep_etherpad-lite/package.json').version);
39 if (epVersion >= 1.8) {
40 var utils = require('./utils');
41 getPad = utils.callbackify2(require('ep_etherpad-lite/node/db/PadManager').getPad);
42 getPadID = utils.callbackify1(require('ep_etherpad-lite/node/db/API').getPadID);
43 getPadHTML = utils.callbackify2(require('ep_etherpad-lite/node/utils/ExportHtml').getPadHTML);
44 } else {
45 getPad = require('ep_etherpad-lite/node/db/PadManager').getPad;
46 getPadID = require('ep_etherpad-lite/node/db/API').getPadID;
47 getPadHTML = require('ep_etherpad-lite/node/utils/ExportHtml').getPadHTML;
48 }
49}
50catch (e) {
51 // Testing case : noop functions
52 getPad = function (padId, callback) { callback(null); };
53 getPadID = function (padId, callback) { callback(null); };
54 getPadHTML = function (pad, rev, callback) {
55 callback(null, '<p>Testing only</p>');
56 };
57 isWaitingforDeletion = function(padId, callback) { callback(null, null); };
58
59}
60var ld = require('lodash');
61var decode = require('js-base64').Base64.decode;
62var conf = require('./configuration.js');
63var auth = require('./auth.js');
64var pad = require('./model/pad.js');
65var group = require('./model/group.js');
66var storage = require('./storage.js');
67
68var JOBQ_PREFIX = storage.DBPREFIX.JOBQ;
69
70if (typeof(isWaitingforDeletion) === 'undefined') {
71 isWaitingforDeletion = function(padId, callback) {
72 storage.db.get(JOBQ_PREFIX+'deletePad:'+padId, callback);
73 };
74}
75
76module.exports = (function () {
77 'use strict';
78
79 var perm = {};
80
81 /**
82 * ## Private functions
83 */
84
85 perm.fn = {};
86
87 /**
88 * ### getVarFromReferer
89 *
90 * `getVarFromReferer` synchronous function for getting an URL passed variable
91 * from referer. Usefull for cases like timeslider or exports.
92 *
93 */
94
95 perm.fn.getVarFromReferer = function (varName, req) {
96 var ref = req.headers.referer;
97 if (!ref) { return false; }
98 var rg = new RegExp(varName + '=([^&]+)');
99 var rgxres = rg.exec(ref);
100 return (rgxres ? rgxres[1] : false);
101 };
102
103 /**
104 * ### getPadAndGroup
105 *
106 * `getPadAndGroup` asynchronous function returns to callback the *pad* and
107 * parent *group* or an *error* if there is. It takes mandatory pad id and
108 * callback function.
109 *
110 */
111
112 perm.fn.getPadAndGroup = function (pid, callback) {
113 pad.get(pid, function (err, p) {
114 if (err) { console.error(err); }
115 if (err || !p) { return callback(null, null); }
116 group.get(p.group, function (err, g) {
117 if (err) { return callback(err); }
118 return callback(null, { pad : p, group: g });
119 });
120 });
121 };
122
123 /**
124 * ### check
125 *
126 * `check` internal function checks if the pad is handled by MyPads or not and
127 * if, allows to see it according its *visibility* and user authentication.
128 */
129
130 perm.fn.check = function (params) {
131 var callback = ld.partial(params.callback, params);
132 var checkPass = function (el) {
133 var rq = params.req.query;
134 var password = (rq ? rq.mypadspassword : false );
135 if (!password) {
136 password = perm.fn.getVarFromReferer('mypadspassword', params.req);
137 }
138 if (!password) { return params.refuse(); }
139 auth.fn.isPasswordValid(el, decode(password), function (err, valid) {
140 if (err) { return params.unexpected(err); }
141 return (valid ? callback() : params.refuse());
142 });
143 };
144 var token = (params.req.query ? params.req.query.auth_token : false);
145 if (!token) {
146 var ref = params.req.headers.referer;
147 if (ref) {
148 var rgxres = /&auth_token=(.+)&?/.exec(ref);
149 if (rgxres) { token = rgxres[1]; }
150 }
151 }
152 var u = auth.fn.getUser(token);
153 var uid = u && u._id || false;
154 // Key not found, not a MyPads pad so depends on allowEtherPads
155 if (!params.pg) {
156 return isWaitingforDeletion(params.pid, function(err, val) {
157 if (err) { return params.unexpected(err); }
158
159 if (val === null) { // not waiting for deletion
160 return params[(conf.get('allowEtherPads') ? 'next' : 'unexpected')]();
161 } else { // don't give access to a pad that is waiting for deletion
162 return params.unexpected();
163 }
164 });
165 }
166 var g = params.pg.group;
167 var p = params.pg.pad;
168 // If admin of the group or pad or group publics, ok
169 // If pad or group is private, check password
170 if (ld.includes(g.admins, uid)) { return callback(); }
171
172 // allPadsPublicsAuthentifiedOnly feature
173 if (conf.get('allPadsPublicsAuthentifiedOnly')) {
174 if (u) {
175 return callback();
176 } else {
177 return params.refuse();
178 }
179 }
180
181 switch (p.visibility) {
182 case 'public':
183 return callback();
184 case 'private':
185 return checkPass(p);
186 default:
187 switch (g.visibility) {
188 case 'public':
189 return callback();
190 case 'private':
191 return (ld.includes(g.users, uid) ? checkPass(p) : checkPass(g));
192 // Restricted case : if user, ok
193 default:
194 return (ld.includes(g.users, uid) ? callback() : params.refuse());
195 }
196 }
197 };
198
199 /**
200 * ### readonly
201 *
202 * `readonly` checks if the pad belongs to MyPads and has been archived. If
203 * yes, it redirects to HTML export.
204 */
205
206 perm.fn.readonly = function (params) {
207 var g = params.pg.group;
208 var p = params.pg.pad;
209 if (p.readonly || (g.readonly && p.readonly === null)) {
210 return getPad(params.pg.pad._id, function (err, p) {
211 if (err) { return params.unexpected(err); }
212 getPadHTML(p, undefined, function (err, html) {
213 if (err) { return params.unexpected(err); }
214 html = '<!DOCTYPE HTML><html><body>' + html + '</body></html>';
215 return params.res.status(200).send(html);
216 });
217 });
218 } else {
219 return params.next();
220 }
221 };
222
223 /**
224 * ### check
225 *
226 * `check` is a middleware-like function for all requests to pads.
227 * It regroups permissions work according to groups and pads visibility and
228 * readonly verifications. It retrieves pad and group requested and pass the
229 * result alongside request, result and chained function.
230 */
231
232 var trimRgx = new RegExp('/.*');
233 perm.check = function (req, res, next) {
234 var params;
235 var pid = req.params.pid || req.params[0];
236 pid = pid.replace(trimRgx, '');
237 var unexpected = function (err) {
238 return res.status(401).send({
239 error: 'BACKEND.ERROR.PERMISSION.UNEXPECTED',
240 extra: err
241 });
242 };
243 var refuse = function () {
244 var mypadsRoute = conf.get('rootUrl') + '/mypads/index.html?/mypads/group/' +
245 params.pg.group._id + '/pad/view/' + params.pg.pad._id;
246 return res.redirect(mypadsRoute);
247 };
248 params = {
249 req: req,
250 res: res,
251 next: next,
252 callback: perm.fn.readonly,
253 unexpected: unexpected,
254 refuse: refuse,
255 pid: pid
256 };
257 // Is pid a real pad id or a read only pad id?
258 if (pid.match(/^r\.[A-Za-z0-9_-]{32}$/)) {
259 // It's a read only pid, let's get the real pad id
260 getPadID(pid, function(err, result) {
261 if (err) { return unexpected(err); }
262 pid = result.padID;
263 // And then fetch the pad through MyPads API
264 perm.fn.getPadAndGroup(pid, function (err, pg) {
265 if (err) { return unexpected(err); }
266 params.pg = pg;
267 perm.fn.check(params);
268 });
269 });
270 } else {
271 // It's a real pad id, fetch the pad through MyPads API
272 perm.fn.getPadAndGroup(pid, function (err, pg) {
273 if (err) { return unexpected(err); }
274 params.pg = pg;
275 perm.fn.check(params);
276 });
277 }
278 };
279
280 /**
281 * ### setNameAndColor
282 *
283 * Internal function `setNameAndColor` is an Express middleware used in
284 * conjunction with `padAndAuthor` public JS object. According to *user*
285 * object in `auth.tokens` and user preference for `useLoginAndColorInPads`,
286 * it passes, for a given pad identifier, the last `login` and user `color`
287 * values.
288 */
289
290 perm.padAndAuthor = {};
291
292 perm.setNameAndColor = function (req, res, next) {
293 var token = (req.query ? req.query.auth_token : false);
294 if (!token) { token = perm.fn.getVarFromReferer('auth_token', req); }
295 var u = auth.fn.getUser(token);
296 if (u && u.useLoginAndColorInPads) {
297 var opts = { userName: u.login };
298 if (u.padNickname) {
299 opts.userName = u.padNickname;
300 }
301 if (conf.get('useFirstLastNameInPads')) {
302 var firstname = (u.firstname) ? u.firstname : '';
303 var lastname = (u.lastname) ? u.lastname : '';
304 opts.userName = firstname+' '+lastname;
305 }
306 if (u.color) {
307 opts.userColor = u.color;
308 req.query.userColor = opts.userColor;
309 }
310 perm.padAndAuthor[req.params.pid] = opts;
311 req.query.userName = opts.userName;
312 } else {
313 delete perm.padAndAuthor[req.params.pid];
314 }
315 return next();
316 };
317
318
319 /**
320 * ## Public functions
321 *
322 * ### init
323 *
324 * `init` is a synchronous function used to set up authentification. It :
325 *
326 * - initializes routes with `check` and `setNameAndColor`
327 * - if configuration option `forbidPublicPads` is *true*, redirects etherpad
328 * homepage to MyPads and forbids direct creation from /p/pid (via `check`)
329 */
330
331 perm.init = function (app) {
332 //app.all('/p/([A-Za-z0-9_-]+)', perm.check);
333 var rgx = new RegExp('/p/(r\.[A-Za-z0-9_-]{32}$|[.A-Za-z0-9_-]+[A-Za-z0-9_/-]*)');
334 app.all(rgx, perm.check);
335 app.all('/p/:pid', perm.setNameAndColor);
336 app.get('/', function (req, res, next) {
337 if (conf.get('allowEtherPads')) {
338 return next();
339 } else {
340 return res.redirect(conf.get('rootUrl') + '/mypads/');
341 }
342 });
343 };
344
345 return perm;
346
347}).call(this);