aboutsummaryrefslogblamecommitdiffstats
path: root/etherpad/src/etherpad/pad/padusers.js
blob: f04f0eb871855055206ce5f5cea387b07dfe6ae2 (plain) (tree)












































































































































































































































































































































































































                                                                             
/**
 * 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("sqlbase.sqlobj");
import("fastJSON");
import("stringutils");
import("jsutils.eachProperty");
import("sync");
import("etherpad.sessions");
import("etherpad.pro.pro_utils");
import("etherpad.pro.pro_accounts");
import("etherpad.pro.pro_accounts.getSessionProAccount");
import("etherpad.pro.domains");
import("stringutils.randomHash");

var _table = cachedSqlTable('pad_guests', 'pad_guests',
                            ['id', 'privateKey', 'userId'], processGuestRow);
function processGuestRow(row) {
  row.data = fastJSON.parse(row.data);
}

function notifySignIn() {
  /*if (pro_accounts.isAccountSignedIn()) {
    var proId = getUserId();
    var guestId = _getGuestUserId();

    var guestUser = _getGuestByKey('userId', guestId);
    if (guestUser) {
      var mods = {};
      mods.data = guestUser.data;
      // associate guest with proId
      mods.data.replacement = proId;
      // de-associate ET cookie with guest, otherwise
      // the ET cookie would provide a semi-permanent way
      // to effect changes under the pro account's name!
      mods.privateKey = "replaced$"+_randomString(20);
      _updateGuest('userId', guestId, mods);
    }
  }*/
}

function notifyActive() {
  if (isGuest(getUserId())) {
    _updateGuest('userId', getUserId(), {});
  }
}

function notifyUserData(userData) {
  var uid = getUserId();
  if (isGuest(uid)) {
    var data = _getGuestByKey('userId', uid).data;
    if (userData.name) {
      data.name = userData.name;
    }
    _updateGuest('userId', uid, {data: data});
  }
}

function getUserId() {
  if (pro_accounts.isAccountSignedIn()) {
    return "p."+(getSessionProAccount().id);
  }
  else {
    return getGuestUserId();
  }
}

function getUserName() {
  var uid = getUserId();
  if (isGuest(uid)) {
    var fromSession = sessions.getSession().guestDisplayName;
    return fromSession || _getGuestByKey('userId', uid).data.name || null;
  }
  else {
    return getSessionProAccount().fullName;
  }
}

function getAccountIdForProAuthor(uid) {
  if (uid.indexOf("p.") == 0) {
    return Number(uid.substring(2));
  }
  else {
    return -1;
  }
}

function getNameForUserId(uid) {
  if (isGuest(uid)) {
    return _getGuestByKey('userId', uid).data.name || null;
  }
  else {
    var accountNum = getAccountIdForProAuthor(uid);
    if (accountNum < 0) {
      return null;
    }
    else {
      return pro_accounts.getAccountById(accountNum).fullName;
    }
  }
}

function isGuest(userId) {
  return /^g/.test(userId);
}

function getGuestUserId() {
  // cache the userId in the requestCache,
  // for efficiency and consistency
  var c = appjet.requestCache;
  if (c.padGuestUserId === undefined) {
    c.padGuestUserId = _computeGuestUserId();
  }
  return c.padGuestUserId;
}

function _getGuestTrackerId() {
  // get ET cookie
  var tid = sessions.getTrackingId();
  if (tid == '-') {
    // no tracking cookie?  not a normal request?
    return null;
  }

  // get domain ID
  var domain = "-";
  if (pro_utils.isProDomainRequest()) {
    // e.g. "3"
    domain = String(domains.getRequestDomainId());
  }

  // combine them
  return domain+"$"+tid;
}

function _insertGuest(obj) {
  // only requires 'userId' in obj

  obj.createdDate = new Date;
  obj.lastActiveDate = new Date;
  if (! obj.data) {
    obj.data = {};
  }
  if ((typeof obj.data) == "object") {
    obj.data = fastJSON.stringify(obj.data);
  }
  if (! obj.privateKey) {
    // private keys must be unique
    obj.privateKey = "notracker$"+_randomString(20);
  }

  return _table.insert(obj);
}

function _getGuestByKey(keyColumn, value) {
  return _table.getByKey(keyColumn, value);
}

function _updateGuest(keyColumn, value, obj) {
  var obj2 = {};
  eachProperty(obj, function(k,v) {
    if (k == "data" && (typeof v) == "object") {
      obj2.data = fastJSON.stringify(v);
    }
    else {
      obj2[k] = v;
    }
  });

  obj2.lastActiveDate = new Date;

  _table.updateByKey(keyColumn, value, obj2);
}

function _newGuestUserId() {
  return "g."+_randomString(16);
}

function _computeGuestUserId() {
  // always returns some userId

  var privateKey = _getGuestTrackerId();

  if (! privateKey) {
    // no tracking cookie, pretend there is one
    privateKey = randomHash(16);
  }

  var userFromTracker = _table.getByKey('privateKey', privateKey);
  if (userFromTracker) {
    // we know this guy
    return userFromTracker.userId;
  }

  // generate userId
  var userId = _newGuestUserId();
  var guest = {userId:userId, privateKey:privateKey};
  var data = {};
  guest.data = data;

  var prefsCookieData = _getPrefsCookieData();
  if (prefsCookieData) {
    // found an old prefs cookie with an old userId
    var oldUserId = prefsCookieData.userId;
    // take the name and preferences
    if ('name' in prefsCookieData) {
      data.name = prefsCookieData.name;
    }
    /*['fullWidth','viewZoom'].forEach(function(pref) {
      if (pref in prefsCookieData) {
        data.prefs[pref] = prefsCookieData[pref];
      }
    });*/
  }

  _insertGuest(guest);
  return userId;
}

function _getPrefsCookieData() {
  // get userId from old prefs cookie if possible,
  // but don't allow modern usernames

  var prefsCookie = request.cookies['prefs'];
  if (! prefsCookie) {
    return null;
  }
  if (prefsCookie.charAt(0) != '%') {
    return null;
  }
  try {
    var cookieData = fastJSON.parse(unescape(prefsCookie));
    // require one to three digits followed by dot at beginning of userId
    if (/^[0-9]{1,3}\./.test(String(cookieData.userId))) {
      return cookieData;
    }
  }
  catch (e) {
    return null;
  }

  return null;
}

function _randomString(len) {
  // use only numbers and lowercase letters
  var pieces = [];
  for(var i=0;i<len;i++) {
    pieces.push(Math.floor(Math.random()*36).toString(36).slice(-1));
  }
  return pieces.join('');
}


function cachedSqlTable(cacheName, tableName, keyColumns, processFetched) {
  // Keeps a cache of sqlobj rows for the case where
  // you want to select one row at a time by a single column
  // at a time, taken from some set of key columns.
  // The cache maps (keyColumn, value), e.g. ("id", 4) or
  // ("secondaryKey", "foo123"), to an object, and each
  // object is either present for all keyColumns
  // (e.g. "id", "secondaryKey") or none.

  if ((typeof keyColumns) == "string") {
    keyColumns = [keyColumns];
  }
  processFetched = processFetched || (function(o) {});

  function getCache() {
    // this function is normally fast, only slow when cache
    // needs to be created for the first time
    var cache = appjet.cache[cacheName];
    if (cache) {
      return cache;
    }
    else {
      // initialize in a synchronized block (double-checked locking);
      // uses same lock as cache_utils.syncedWithCache would use.
      sync.doWithStringLock("cache/"+cacheName, function() {
        if (! appjet.cache[cacheName]) {
          // values expire after 10 minutes
          appjet.cache[cacheName] =
            new net.appjet.common.util.ExpiringMapping(10*60*1000);
        }
      });
      return appjet.cache[cacheName];
    }
  }

  function cacheKey(keyColumn, value) {
    // e.g. "id$4"
    return keyColumn+"$"+String(value);
  }

  function getFromCache(keyColumn, value) {
    return getCache().get(cacheKey(keyColumn, value));
  }
  function putInCache(obj) {
    var cache = getCache();
    // put in cache, keyed on all keyColumns we care about
    keyColumns.forEach(function(keyColumn) {
      cache.put(cacheKey(keyColumn, obj[keyColumn]), obj);
    });
  }
  function touchInCache(obj) {
    var cache = getCache();
    keyColumns.forEach(function(keyColumn) {
      cache.touch(cacheKey(keyColumn, obj[keyColumn]));
    });
  }
  function removeObjFromCache(obj) {
    var cache = getCache();
    keyColumns.forEach(function(keyColumn) {
      cache.remove(cacheKey(keyColumn, obj[keyColumn]));
    });
  }
  function removeFromCache(keyColumn, value) {
    var cached = getFromCache(keyColumn, value);
    if (cached) {
      removeObjFromCache(cached);
    }
  }

  var self = {
    clearCache: function() {
      getCache().clear();
    },
    getByKey: function(keyColumn, value) {
      // get cached object, if any
      var cached = getFromCache(keyColumn, value);
      if (! cached) {
        // nothing in cache for this query, fetch from SQL
        var keyToValue = {};
        keyToValue[keyColumn] = value;
        var fetched = sqlobj.selectSingle(tableName, keyToValue);
        if (fetched) {
          processFetched(fetched);
          // fetched something, stick it in the cache
          putInCache(fetched);
        }
        return fetched;
      }
      else {
        // touch cached object and return
        touchInCache(cached);
        return cached;
      }
    },
    updateByKey: function(keyColumn, value, obj) {
      var keyToValue = {};
      keyToValue[keyColumn] = value;
      sqlobj.updateSingle(tableName, keyToValue, obj);
      // remove old object from caches but
      // don't put obj in cache, because it
      // is likely a partial object
      removeFromCache(keyColumn, value);
    },
    insert: function(obj) {
      var returnVal = sqlobj.insert(tableName, obj);
      // remove old object from caches but
      // don't put obj in the cache; it doesn't
      // have all values, e.g. for auto-generated ids
      removeObjFromCache(obj);
      return returnVal;
    },
    deleteByKey: function(keyColumn, value) {
      var keyToValue = {};
      keyToValue[keyColumn] = value;
      sqlobj.deleteRows(tableName, keyToValue);
      removeFromCache(keyColumn, value);
    }
  };
  return self;
}

function _getClientIp() {
  return (request.isDefined && request.clientIp) || '';
}

function getUserIdCreatedDate(userId) {
  var record = sqlobj.selectSingle('pad_cookie_userids', {id: userId});
  if (! record) { return; } // hm. weird case.
  return record.createdDate;
}