-
-
Notifications
You must be signed in to change notification settings - Fork 3k
Expand file tree
/
Copy pathSecurityManager.ts
More file actions
153 lines (136 loc) · 5.9 KB
/
Copy pathSecurityManager.ts
File metadata and controls
153 lines (136 loc) · 5.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
'use strict';
/**
* Controls the security of pad access
*/
/*
* 2011 Peter 'Pita' Martischka (Primary Technology Ltd)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {UserSettingsObject} from "../types/UserSettingsObject";
const authorManager = require('./AuthorManager');
const hooks = require('../../static/js/pluginfw/hooks');
const padManager = require('./PadManager');
import readOnlyManager from './ReadOnlyManager';
const sessionManager = require('./SessionManager');
import settings from '../utils/Settings';
const webaccess = require('../hooks/express/webaccess');
const log4js = require('log4js');
const authLogger = log4js.getLogger('auth');
import padutils from '../../static/js/pad_utils'
const DENY = Object.freeze({accessStatus: 'deny'});
/**
* Determines whether the user can access a pad.
*
* @param padID identifies the pad the user wants to access.
* @param sessionCookie identifies the sessions the user created via the HTTP API, if any.
* Note: The term "session" used here is unrelated to express-session.
* @param token is a random token of the form t.randomstring_of_length_20 generated by the client
* when using the web UI (not the HTTP API). This token is only used if settings.requireSession
* is false and the user is accessing a public pad. If there is not an author already associated
* with this token then a new author object is created (including generating an author ID) and
* associated with this token.
* @param userSettings is the settings.users[username] object (or equivalent from an authn plugin).
* @return {accessStatus: grant|deny, authorID: a.xxxxxx}. The caller must use the author ID
* returned in this object when making any changes associated with the author.
*
* WARNING: Tokens and session IDs MUST be kept secret, otherwise users will be able to impersonate
* each other (which might allow them to gain privileges).
* @param {String} padID
* @param {String} sessionCookie
* @param {String} token
* @param {Object} userSettings
* @return {DENY|{accessStatus: String, authorID: String}}
*/
exports.checkAccess = async (padID:string, sessionCookie:string, token:string, userSettings:UserSettingsObject) => {
if (!padID) {
authLogger.debug('access denied: missing padID');
return DENY;
}
let canCreate = !settings.editOnly;
if (readOnlyManager.isReadOnlyId(padID)) {
canCreate = false;
padID = await readOnlyManager.getPadId(padID);
if (padID == null) {
authLogger.debug('access denied: read-only pad ID for a pad that does not exist');
return DENY;
}
}
// Authentication and authorization checks.
// settings.loadTest just short-circuits authn/authz; the user-facing
// warning about this configuration choice is logged from Settings.ts
// during settings load/reload, not on every request. Re-logging it
// here was costing ~4% of process CPU in the 100-400 author dive
// sweep (#7756): the routed-console-warn went through log4js's
// clustering dispatch on every message.
if (!settings.loadTest && settings.requireAuthentication) {
if (userSettings == null) {
authLogger.debug('access denied: authentication is required');
return DENY;
}
if (userSettings.canCreate != null && !userSettings.canCreate) canCreate = false;
if (userSettings.readOnly) canCreate = false;
// Note: userSettings.padAuthorizations should still be populated even if
// settings.requireAuthorization is false.
const padAuthzs = userSettings.padAuthorizations || {};
const level = webaccess.normalizeAuthzLevel(padAuthzs[padID]);
if (!level) {
authLogger.debug('access denied: unauthorized');
return DENY;
}
if (level !== 'create') canCreate = false;
}
// allow plugins to deny access
const isFalse = (x:boolean) => x === false;
if (hooks.callAll('onAccessCheck', {padID, token, sessionCookie}).some(isFalse)) {
authLogger.debug('access denied: an onAccessCheck hook function returned false');
return DENY;
}
const padExists = await padManager.doesPadExist(padID);
if (!padExists && !canCreate) {
authLogger.debug('access denied: user attempted to create a pad, which is prohibited');
return DENY;
}
const sessionAuthorID = await sessionManager.findAuthorID(padID.split('$')[0], sessionCookie);
if (settings.requireSession && !sessionAuthorID) {
authLogger.debug('access denied: HTTP API session is required');
return DENY;
}
if (!sessionAuthorID && token != null && !padutils.isValidAuthorToken(token)) {
// The author token should be kept secret, so do not log it.
authLogger.debug('access denied: invalid author token');
return DENY;
}
const grant = {
accessStatus: 'grant',
authorID: sessionAuthorID || await authorManager.getAuthorId(token, userSettings),
};
if (!padID.includes('$')) {
// Only group pads can be private, so there is nothing more to check for this non-group pad.
return grant;
}
if (!padExists) {
if (sessionAuthorID == null) {
authLogger.debug('access denied: must have an HTTP API session to create a group pad');
return DENY;
}
// Creating a group pad, so there is no public status to check.
return grant;
}
const pad = await padManager.getPad(padID);
if (!pad.getPublicStatus() && sessionAuthorID == null) {
authLogger.debug('access denied: must have an HTTP API session to access private group pads');
return DENY;
}
return grant;
};