aboutsummaryrefslogtreecommitdiffstats
path: root/trunk/etherpad/src/etherpad/pro/pro_accounts.js
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/etherpad/src/etherpad/pro/pro_accounts.js')
-rw-r--r--trunk/etherpad/src/etherpad/pro/pro_accounts.js496
1 files changed, 496 insertions, 0 deletions
diff --git a/trunk/etherpad/src/etherpad/pro/pro_accounts.js b/trunk/etherpad/src/etherpad/pro/pro_accounts.js
new file mode 100644
index 0000000..2024970
--- /dev/null
+++ b/trunk/etherpad/src/etherpad/pro/pro_accounts.js
@@ -0,0 +1,496 @@
+/**
+ * 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.
+ */
+
+// library for pro accounts
+
+import("funhtml.*");
+import("sqlbase.sqlobj");
+import("sqlbase.sqlcommon.inTransaction");
+import("email.sendEmail");
+import("cache_utils.syncedWithCache");
+import("stringutils.*");
+
+import("etherpad.globals.*");
+import("etherpad.sessions");
+import("etherpad.sessions.getSession");
+import("etherpad.utils.*");
+import("etherpad.pro.domains");
+import("etherpad.control.pro.account_control");
+import("etherpad.pro.pro_utils");
+import("etherpad.pro.pro_quotas");
+import("etherpad.pad.padusers");
+import("etherpad.log");
+import("etherpad.billing.team_billing");
+
+jimport("org.mindrot.BCrypt");
+jimport("java.lang.System.out.println");
+
+function _dmesg(m) {
+ if (!isProduction()) {
+ println(m);
+ }
+}
+
+function _computePasswordHash(p) {
+ var pwh;
+ pwh = BCrypt.hashpw(p, BCrypt.gensalt(10));
+ return pwh;
+}
+
+function _withCache(name, fn) {
+ return syncedWithCache('pro_accounts.'+name, fn);
+}
+
+//----------------------------------------------------------------
+// validation
+//----------------------------------------------------------------
+
+function validateEmail(email) {
+ if (!email) { return "Email is required."; }
+ if (!isValidEmail(email)) { return "\""+email+"\" does not look like a valid email address."; }
+ return null;
+}
+
+function validateFullName(name) {
+ if (!name) { return "Full name is required."; }
+ if (name.length < 2) { return "Full name must be at least 2 characters."; }
+ return null;
+}
+
+function validatePassword(p) {
+ if (!p) { return "Password is required."; }
+ if (p.length < 6) { return "Passwords must be at least 6 characters."; }
+ return null;
+}
+
+function validateEmailDomainPair(email, domainId) {
+ // TODO: make sure the same email address cannot exist more than once within
+ // the same domainid.
+}
+
+/* if domainId is null, then use domainId of current request. */
+function createNewAccount(domainId, fullName, email, password, isAdmin) {
+ if (!domainId) {
+ domainId = domains.getRequestDomainId();
+ }
+ email = trim(email);
+ isAdmin = !!isAdmin; // convert to bool
+
+ // validation
+ var e;
+ e = validateEmail(email); if (e) { throw Error(e); }
+ e = validateFullName(fullName); if (e) { throw Error(e); }
+ e = validatePassword(password); if (e) { throw Error(e); }
+
+ // xss normalization
+ fullName = toHTML(fullName);
+
+ // make sure account does not already exist on this domain.
+ var ret = inTransaction(function() {
+ var existingAccount = getAccountByEmail(email, domainId);
+ if (existingAccount) {
+ throw Error("There is already an account with that email address.");
+ }
+ // No existing account. Proceed.
+ var now = new Date();
+ var account = {
+ domainId: domainId,
+ fullName: fullName,
+ email: email,
+ passwordHash: _computePasswordHash(password),
+ createdDate: now,
+ isAdmin: isAdmin
+ };
+ return sqlobj.insert('pro_accounts', account);
+ });
+
+ _withCache('does-domain-admin-exist', function(cache) {
+ delete cache[domainId];
+ });
+
+ pro_quotas.updateAccountUsageCount(domainId);
+ updateCachedActiveCount(domainId);
+
+ if (ret) {
+ log.custom('pro-accounts',
+ {type: "account-created",
+ accountId: ret,
+ domainId: domainId,
+ name: fullName,
+ email: email,
+ admin: isAdmin});
+ }
+
+ return ret;
+}
+
+function _checkAccess(account) {
+ if (sessions.isAnEtherpadAdmin()) {
+ return;
+ }
+ if (account.domainId != domains.getRequestDomainId()) {
+ throw Error("access denied");
+ }
+}
+
+function setPassword(account, newPass) {
+ _checkAccess(account);
+ var passHash = _computePasswordHash(newPass);
+ sqlobj.update('pro_accounts', {id: account.id}, {passwordHash: passHash});
+ markDirtySessionAccount(account.id);
+}
+
+function setTempPassword(account, tempPass) {
+ _checkAccess(account);
+ var tempPassHash = _computePasswordHash(tempPass);
+ sqlobj.update('pro_accounts', {id: account.id}, {tempPassHash: tempPassHash});
+ markDirtySessionAccount(account.id);
+}
+
+function setEmail(account, newEmail) {
+ _checkAccess(account);
+ sqlobj.update('pro_accounts', {id: account.id}, {email: newEmail});
+ markDirtySessionAccount(account.id);
+}
+
+function setFullName(account, newName) {
+ _checkAccess(account);
+ sqlobj.update('pro_accounts', {id: account.id}, {fullName: newName});
+ markDirtySessionAccount(account.id);
+}
+
+function setIsAdmin(account, newVal) {
+ _checkAccess(account);
+ sqlobj.update('pro_accounts', {id: account.id}, {isAdmin: newVal});
+ markDirtySessionAccount(account.id);
+}
+
+function setDeleted(account) {
+ _checkAccess(account);
+ if (!isNumeric(account.id)) {
+ throw new Error("Invalid account id: "+account.id);
+ }
+ sqlobj.update('pro_accounts', {id: account.id}, {isDeleted: true});
+ markDirtySessionAccount(account.id);
+ pro_quotas.updateAccountUsageCount(account.domainId);
+ updateCachedActiveCount(account.domainId);
+
+ log.custom('pro-accounts',
+ {type: "account-deleted",
+ accountId: account.id,
+ domainId: account.domainId,
+ name: account.fullName,
+ email: account.email,
+ admin: account.isAdmin,
+ createdDate: account.createdDate.getTime()});
+}
+
+//----------------------------------------------------------------
+
+function doesAdminExist() {
+ var domainId = domains.getRequestDomainId();
+ return _withCache('does-domain-admin-exist', function(cache) {
+ if (cache[domainId] === undefined) {
+ _dmesg("cache miss for doesAdminExist (domainId="+domainId+")");
+ var admins = sqlobj.selectMulti('pro_accounts', {domainId: domainId, isAdmin: true}, {});
+ cache[domainId] = (admins.length > 0);
+ }
+ return cache[domainId]
+ });
+}
+
+function getSessionProAccount() {
+ if (sessions.isAnEtherpadAdmin()) {
+ return getEtherpadAdminAccount();
+ }
+ var account = getSession().proAccount;
+ if (!account) {
+ return null;
+ }
+ if (account.isDeleted) {
+ delete getSession().proAccount;
+ return null;
+ }
+ return account;
+}
+
+function isAccountSignedIn() {
+ if (getSessionProAccount()) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+function isAdminSignedIn() {
+ return isAccountSignedIn() && getSessionProAccount().isAdmin;
+}
+
+function requireAccount(message) {
+ if ((request.path == "/ep/account/sign-in") ||
+ (request.path == "/ep/account/sign-out") ||
+ (request.path == "/ep/account/guest-sign-in") ||
+ (request.path == "/ep/account/guest-knock") ||
+ (request.path == "/ep/account/forgot-password")) {
+ return;
+ }
+
+ function checkSessionAccount() {
+ if (!getSessionProAccount()) {
+ if (message) {
+ account_control.setSigninNotice(message);
+ }
+ response.redirect('/ep/account/sign-in?cont='+encodeURIComponent(request.url));
+ }
+ }
+
+ checkSessionAccount();
+
+ if (getSessionProAccount().domainId != domains.getRequestDomainId()) {
+ // This should theoretically never happen unless the account is spoofing cookies / trying to
+ // hack the site.
+ pro_utils.renderFramedMessage("Permission denied.");
+ response.stop();
+ }
+ // update dirty session account if necessary
+ _withCache('dirty-session-accounts', function(cache) {
+ var uid = getSessionProAccount().id;
+ if (cache[uid]) {
+ reloadSessionAccountData(uid);
+ cache[uid] = false;
+ }
+ });
+
+ // need to check again in case dirty update caused account to be marked
+ // deleted.
+ checkSessionAccount();
+}
+
+function requireAdminAccount() {
+ requireAccount();
+ if (!getSessionProAccount().isAdmin) {
+ pro_utils.renderFramedMessage("Permission denied.");
+ response.stop();
+ }
+}
+
+/* returns undefined on success, error string otherise. */
+function authenticateSignIn(email, password) {
+ var accountRecord = getAccountByEmail(email, null);
+ if (!accountRecord) {
+ return "Account not found: "+email;
+ }
+
+ if (BCrypt.checkpw(password, accountRecord.passwordHash) != true) {
+ return "Incorrect password. Please try again.";
+ }
+
+ signInSession(accountRecord);
+
+ return undefined; // success
+}
+
+function signOut() {
+ delete getSession().proAccount;
+}
+
+function authenticateTempSignIn(uid, tempPass) {
+ var emsg = "That password reset link that is no longer valid.";
+
+ var account = getAccountById(uid);
+ if (!account) {
+ return emsg+" (Account not found.)";
+ }
+ if (account.domainId != domains.getRequestDomainId()) {
+ return emsg+" (Wrong domain.)";
+ }
+ if (!account.tempPassHash) {
+ return emsg+" (Expired.)";
+ }
+ if (BCrypt.checkpw(tempPass, account.tempPassHash) != true) {
+ return emsg+" (Bad temp pass.)";
+ }
+
+ signInSession(account);
+
+ getSession().accountMessage = "Please choose a new password";
+ getSession().changePass = true;
+
+ response.redirect("/ep/account/");
+}
+
+function signInSession(account) {
+ account.lastLoginDate = new Date();
+ account.tempPassHash = null;
+ sqlobj.updateSingle('pro_accounts', {id: account.id}, account);
+ reloadSessionAccountData(account.id);
+ padusers.notifySignIn();
+}
+
+function listAllDomainAccounts(domainId) {
+ if (domainId === undefined) {
+ domainId = domains.getRequestDomainId();
+ }
+ var records = sqlobj.selectMulti('pro_accounts',
+ {domainId: domainId, isDeleted: false}, {});
+ return records;
+}
+
+function listAllDomainAdmins(domainId) {
+ if (domainId === undefined) {
+ domainId = domains.getRequestDomainId();
+ }
+ var records = sqlobj.selectMulti('pro_accounts',
+ {domainId: domainId, isDeleted: false, isAdmin: true},
+ {});
+ return records;
+}
+
+function getActiveCount(domainId) {
+ var records = sqlobj.selectMulti('pro_accounts',
+ {domainId: domainId, isDeleted: false}, {});
+ return records.length;
+}
+
+/* getAccountById works for deleted and non-deleted accounts.
+ * The assumption is that cases whewre you look up an account by ID, you
+ * want the account info even if the account has been deleted. For
+ * example, when asking who created a pad.
+ */
+function getAccountById(accountId) {
+ var r = sqlobj.selectSingle('pro_accounts', {id: accountId});
+ if (r) {
+ return r;
+ } else {
+ return undefined;
+ }
+}
+
+/* getting an account by email only returns the account if it is
+ * not deleted. The assumption is that when you look up an account by
+ * email address, you only want active accounts. Furthermore, some
+ * deleted accounts may match a given email, but only one non-deleted
+ * account should ever match a single (email,domainId) pair.
+ */
+function getAccountByEmail(email, domainId) {
+ if (!domainId) {
+ domainId = domains.getRequestDomainId();
+ }
+ var r = sqlobj.selectSingle('pro_accounts', {domainId: domainId, email: email, isDeleted: false});
+ if (r) {
+ return r;
+ } else {
+ return undefined;
+ }
+}
+
+function getFullNameById(id) {
+ if (!id) {
+ return null;
+ }
+
+ return _withCache('names-by-id', function(cache) {
+ if (cache[id] === undefined) {
+ _dmesg("cache miss for getFullNameById (accountId="+id+")");
+ var r = getAccountById(id);
+ if (r) {
+ cache[id] = r.fullName;
+ } else {
+ cache[id] = false;
+ }
+ }
+ if (cache[id]) {
+ return cache[id];
+ } else {
+ return null;
+ }
+ });
+}
+
+function getTempSigninUrl(account, tempPass) {
+ return [
+ 'https://', httpsHost(pro_utils.getFullProHost()), '/ep/account/sign-in?',
+ 'uid=', account.id, '&tp=', tempPass
+ ].join('');
+}
+
+
+// TODO: this session account object storage / dirty cache is a
+// ridiculous hack. What we should really do is have a caching/access
+// layer for accounts similar to accessPad() and accessProPadMeta(), and
+// have that abstraction take care of caching and marking accounts as
+// dirty. This can be incorporated into getSessionProAccount(), and we
+// should actually refactor that into accessSessionProAccount().
+
+/* will force session data for this account to be updated next time that
+ * account requests a page. */
+function markDirtySessionAccount(uid) {
+ var domainId = domains.getRequestDomainId();
+
+ _withCache('dirty-session-accounts', function(cache) {
+ cache[uid] = true;
+ });
+ _withCache('names-by-id', function(cache) {
+ delete cache[uid];
+ });
+ _withCache('does-domain-admin-exist', function(cache) {
+ delete cache[domainId];
+ });
+}
+
+function reloadSessionAccountData(uid) {
+ if (!uid) {
+ uid = getSessionProAccount().id;
+ }
+ getSession().proAccount = getAccountById(uid);
+}
+
+function getAllAccountsWithEmail(email) {
+ var accountRecords = sqlobj.selectMulti('pro_accounts', {email: email, isDeleted: false}, {});
+ return accountRecords;
+}
+
+function getEtherpadAdminAccount() {
+ return {
+ id: 0,
+ isAdmin: true,
+ fullName: "ETHERPAD ADMIN",
+ email: "support@pad.spline.inf.fu-berlin.de",
+ domainId: domains.getRequestDomainId(),
+ isDeleted: false
+ };
+}
+
+function getCachedActiveCount(domainId) {
+ return _withCache('user-counts.'+domainId, function(c) {
+ if (!c.count) {
+ c.count = getActiveCount(domainId);
+ }
+ return c.count;
+ });
+}
+
+function updateCachedActiveCount(domainId) {
+ _withCache('user-counts.'+domainId, function(c) {
+ c.count = getActiveCount(domainId);
+ });
+}
+
+
+
+
+
+