UNPKG

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