aboutsummaryrefslogtreecommitdiffstats
path: root/trunk/etherpad/src/etherpad/pad/pad_security.js
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/etherpad/src/etherpad/pad/pad_security.js')
-rw-r--r--trunk/etherpad/src/etherpad/pad/pad_security.js237
1 files changed, 237 insertions, 0 deletions
diff --git a/trunk/etherpad/src/etherpad/pad/pad_security.js b/trunk/etherpad/src/etherpad/pad/pad_security.js
new file mode 100644
index 0000000..0ff8783
--- /dev/null
+++ b/trunk/etherpad/src/etherpad/pad/pad_security.js
@@ -0,0 +1,237 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * 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("stringutils");
+import("cache_utils.syncedWithCache");
+
+import("etherpad.sessions.getSession");
+import("etherpad.sessions");
+
+import("etherpad.pad.model");
+import("etherpad.pad.padutils");
+import("etherpad.pad.padusers");
+import("etherpad.pro.domains");
+import("etherpad.pro.pro_accounts");
+import("etherpad.pro.pro_accounts.getSessionProAccount");
+import("etherpad.pro.pro_padmeta");
+import("etherpad.pro.pro_utils");
+import("etherpad.pro.pro_utils.isProDomainRequest");
+import("etherpad.pad.noprowatcher");
+
+//--------------------------------------------------------------------------------
+// granting session permanent access to pads (for the session)
+//--------------------------------------------------------------------------------
+
+function _grantSessionAccessTo(globalPadId) {
+ var userId = padusers.getUserId();
+ syncedWithCache("pad-auth."+globalPadId, function(c) {
+ c[userId] = true;
+ });
+}
+
+function _doesSessionHaveAccessTo(globalPadId) {
+ var userId = padusers.getUserId();
+ return syncedWithCache("pad-auth."+globalPadId, function(c) {
+ return c[userId];
+ });
+}
+
+function revokePadUserAccess(globalPadId, userId) {
+ syncedWithCache("pad-auth."+globalPadId, function(c) {
+ delete c[userId];
+ });
+}
+
+function revokeAllPadAccess(globalPadId) {
+ syncedWithCache("pad-auth."+globalPadId, function(c) {
+ for (k in c) {
+ delete c[k];
+ }
+ });
+}
+
+//--------------------------------------------------------------------------------
+// knock/answer
+//--------------------------------------------------------------------------------
+
+function clearKnockStatus(userId, globalPadId) {
+ syncedWithCache("pad-guest-knocks."+globalPadId, function(c) {
+ delete c[userId];
+ });
+}
+
+// called by collab_server when accountholders approve or deny
+function answerKnock(userId, globalPadId, status) {
+ // status is either "approved" or "denied"
+ syncedWithCache("pad-guest-knocks."+globalPadId, function(c) {
+ // If two account-holders respond to the knock, keep the first one.
+ if (!c[userId]) {
+ c[userId] = status;
+ }
+ });
+}
+
+// returns "approved", "denied", or undefined
+function getKnockAnswer(userId, globalPadId) {
+ return syncedWithCache("pad-guest-knocks."+globalPadId, function(c) {
+ return c[userId];
+ });
+}
+
+//--------------------------------------------------------------------------------
+// main entrypoint called for every accessPad()
+//--------------------------------------------------------------------------------
+
+var _insideCheckAccessControl = false;
+
+function checkAccessControl(globalPadId, rwMode) {
+ if (!request.isDefined) {
+ return; // TODO: is this the right thing to do here?
+ // Empirical evidence indicates request.isDefined during comet requests,
+ // but not during tasks, which is the behavior we want.
+ }
+
+ if (_insideCheckAccessControl) {
+ // checkAccessControl is always allowed to access pads itself
+ return;
+ }
+ if (isProDomainRequest() && (request.path == "/ep/account/guest-knock")) {
+ return;
+ }
+ if (!isProDomainRequest() && (request.path == "/ep/admin/padinspector")) {
+ return;
+ }
+ if (isProDomainRequest() && (request.path == "/ep/padlist/all-pads.zip")) {
+ return;
+ }
+ try {
+ _insideCheckAccessControl = true;
+
+ if (!padutils.isProPadId(globalPadId)) {
+ // no access control on non-pro pads yet.
+ return;
+ }
+
+ if (sessions.isAnEtherpadAdmin()) {
+ return;
+ }
+ if (_doesSessionHaveAccessTo(globalPadId)) {
+ return;
+ }
+ _checkDomainSecurity(globalPadId);
+ _checkGuestSecurity(globalPadId);
+ _checkPasswordSecurity(globalPadId);
+
+ // remember that this user has access
+ _grantSessionAccessTo(globalPadId);
+ }
+ finally {
+ // this always runs, even on error or stop
+ _insideCheckAccessControl = false;
+ }
+}
+
+function _checkDomainSecurity(globalPadId) {
+ var padDomainId = padutils.getDomainId(globalPadId);
+ if (!padDomainId) {
+ return; // global pad
+ }
+ if (pro_utils.isProDomainRequest()) {
+ var requestDomainId = domains.getRequestDomainId();
+ if (requestDomainId != padDomainId) {
+ throw Error("Request cross-domain pad access not allowed.");
+ }
+ }
+}
+
+function _checkGuestSecurity(globalPadId) {
+ if (!getSession().guestPadAccess) {
+ getSession().guestPadAccess = {};
+ }
+
+ var padDomainId = padutils.getDomainId(globalPadId);
+ var isAccountHolder = pro_accounts.isAccountSignedIn();
+ if (isAccountHolder) {
+ if (getSessionProAccount().domainId != padDomainId) {
+ throw Error("Account cross-domain pad access not allowed.");
+ }
+ return; // OK
+ }
+
+ // Not an account holder ==> Guest
+
+ // returns either "allow", "ask", or "deny"
+ var guestPolicy = model.accessPadGlobal(globalPadId, function(p) {
+ if (!p.exists()) {
+ return "deny";
+ } else {
+ return p.getGuestPolicy();
+ }
+ });
+
+ var numProUsers = model.accessPadGlobal(globalPadId, function(pad) {
+ return noprowatcher.getNumProUsers(pad);
+ });
+
+ if (guestPolicy == "allow") {
+ return;
+ }
+ if (guestPolicy == "deny") {
+ pro_accounts.requireAccount("Guests are not allowed to join that pad. Please sign in.");
+ }
+ if (guestPolicy == "ask") {
+ if (numProUsers < 1) {
+ pro_accounts.requireAccount("This pad's security policy does not allow guests to join unless an account-holder is connected to the pad.");
+ }
+ var userId = padusers.getUserId();
+
+ // one of {"approved", "denied", undefined}
+ var knockAnswer = getKnockAnswer(userId, globalPadId);
+ if (knockAnswer == "approved") {
+ return;
+ } else {
+ var localPadId = padutils.globalToLocalId(globalPadId);
+ response.redirect('/ep/account/guest-sign-in?padId='+encodeURIComponent(localPadId));
+ }
+ }
+}
+
+function _checkPasswordSecurity(globalPadId) {
+ if (!getSession().padPasswordAuth) {
+ getSession().padPasswordAuth = {};
+ }
+ if (getSession().padPasswordAuth[globalPadId] == true) {
+ return;
+ }
+ var domainId = padutils.getDomainId(globalPadId);
+ var localPadId = globalPadId.split("$")[1];
+
+ if (stringutils.startsWith(request.path, "/ep/admin/recover-padtext")) {
+ return;
+ }
+
+ var p = pro_padmeta.accessProPad(globalPadId, function(propad) {
+ if (propad.exists()) {
+ return propad.getPassword();
+ } else {
+ return null;
+ }
+ });
+ if (p) {
+ response.redirect('/ep/pad/auth/'+localPadId+'?cont='+encodeURIComponent(request.url));
+ }
+}
+