permission/Permissions.js

var Collection = require("./../Collection").Collection;
var Permission = require("./Permission").Permission;
var Users = require("./../user/Users").Users;
var CORS = require("./../CORS").CORS;
var Q = require("q");

/**
 * @class Permissions
 * @name  Permissions
 *
 * @description A collection of open ended permissions which can be applied to any object
 * (usually a corpus, but could be a datalist, or datum, or datumField). A permission
 * can be thought of roughly as a phrase:
 * <pre>
 *
 * User [{username: "lingllama", gravatar: "123"}]
 * has permission ["admin","write","read","comment","export","import",etc]
 * to "corpus"/"datalist"/"datum"/"datumField"
 *
 * <pre>
 *
 * @property {Permission} admins Users who can perform admin operations on the corpus/datalist/datum/datumField.
 * @property {Permission} exporters Users who can export the items in a corpus/datalist/datum/datumField.
 * @property {Permission} writers Users who can modify the items in a corpus/datalist/datum/datumField.
 * @property {Permission} commenters Users who can comment on the items in a corpus/datalist/datum/datumField.
 * @property {Permission} readers Users who can read the items in a corpus/datalist/datum/datumField.
 *
 * @property {Object} viewAsBasePermissionSystem  This is just syntactic sugar which points to the actual
 *    permission system. The actual permission system should be used by apps who want provide a power user or
 *    fine grained and open ended control over the permissions, or whose users want to understand
 *    the fine graned control at a lower level.
 *    - Kind of like Unix permissions.
 * @property {Object} viewAsDataBasePermissionSystem This is syntactic sugar which makes
 *    the permission system look like there is an implicative relationship betwween roles.
 *    - Kind of like PhP/MySQL systems.
 * @property {Object} viewAsGroupedPermissionSystem This is syntactic sugar which shows users in
 *    only one category (readOnly, writeOnly, read/write, admins) it also makes
 *    the permission system look like there is an implicative relationship betwween roles, but with some hint that
 *    there are some rare roles (such as write only) for crowdsourcing/non-standard data entry contexts.
 *    - Kind of like PhP/MySQL systems.
 * @property {Object} viewAsEmailingTeamPermissionSystem This might be the way that
 *    some computational linguistics teams will understand the permission system best,
 *    but the words `collaborator` and `contributor` are so similar that thus far no one
 *    has used these terms (that we know of).
 *    - Kind of like GitHub.
 *
 * @extends Collection
 * @tutorial tests/permission/PermissionTest.js
 */
var Permissions = function Permissions(options) {
  if (!this._fieldDBtype) {
    this._fieldDBtype = "Permissions";
  }
  this.debug("Constructing Permissions ", options);
  this.cacheTimeLength = 1000;
  Collection.apply(this, arguments);
};

Permissions.prototype = Object.create(Collection.prototype, /** @lends Permissions.prototype */ {
  constructor: {
    value: Permissions
  },

  INTERNAL_MODELS: {
    value: {
      item: Permission,
      usersNotOnTeam: Users,
      allUsers: Users
    }
  },

  /*** Syntactic sugar for research teams how have been collaborating via email
  sharing (To, BCC, CC) and/or Dropbox and/or github ***/

  /**
   * Syntactic sugar for users who have reader+commenter+writer permissions.
   *
   * This is a common permission in version control systems
   * where the user can do everything, including categorize data, assign data cleaning
   * to team members, edit information about the database but cannot perform
   * except admin functions such as adding other users to the database.
   *
   * This should be the default permission for most users on the corpus, as the corpus
   * is version controlled any action can be undone if users use their power too much,
   * they can be downgraded to a collaborator or read only role.
   *
   * Email collaboration analog: This is for users who would be in a
   * To or CC category for emails,
   *
   * Dropbox analog: This is for users who would be invited to join a shared
   * folder on Dropbox.
   *
   * @type {Permission}
   */
  contributors: {
    get: function() {
      return this.readerCommenterWriters;
    },
    set: function() {
      this.warn("contributors cannot be set. it is only syntactic sugar. If you want to modify roles, see the addUser and removeUser functions");
    }
  },

  /**
   * Syntactic sugar for users who have reader+commenter permissions.
   *
   * This is a common permission used by research teams where an external reviewer,
   * or a second language consultant can review and coment on the data,
   * but cannot modify the data itself.
   *
   * Email collaboration analog: This is for users who would be in a BCC category
   * for emails (they can read the paper, but they arent supposed to send back a
   * version which they modified for the team to accept),
   *
   * Dropbox analog: This is for uswers who would be given a URL to a folder in Dropbox
   * (but cannot download the files and modify them with the others)
   *
   * @type {Permission}
   */
  collaborators: {
    get: function() {
      return this.readerCommenterOnlys;
    },
    set: function() {
      this.warn("collaborators cannot be set. it is only syntactic sugar. If you want to modify roles, see the addUser and removeUser functions");
    }
  },

  /**
   * Syntactic sugar for users who have reader+writer+admin permissions.
   *
   * This is a common permission used by most data base management systems meaning the user can do anything.
   *
   * Email collaboration analog: The person who initiates the email thread
   *
   * Dropbox analog: The person who creates the folder in dropbox and adds others to it.
   *
   * @type {Permission}
   */
  owners: {
    get: function() {
      return this.admins;
    },
    set: function(value) {
      this.admins = value;
    }
  },

  /*** Gropued permissions (useful to show permissions in an app) ***/

  giveMeUsersWithTheseRolesAndNotTheseRoles: {
    value: function(wantTheseRoles, dontWantTheseRoles) {
      if (!wantTheseRoles || !dontWantTheseRoles) {
        this.bug("Invalid request for users, you must supply the roles you want, and the roles you dont want.");
        return new Users();
      }
      if (Object.prototype.toString.call(wantTheseRoles) !== "[object Array]" || Object.prototype.toString.call(dontWantTheseRoles) !== "[object Array]") {
        this.bug("Invalid request for users, you must supply an array of the roles you want, and the roles you dont want.");
        return new Users();
      }
      // this.debugMode = true;

      var empty = new Users(),
        permissionType,
        permissionTypeIndex,
        self = this,
        groupLabel = wantTheseRoles.join(" ") + " onlys",
        usersWhoAreOnlyInThisPermissionType = [];

      /* accept requests for the plural or singular of the permission type */
      wantTheseRoles = wantTheseRoles.map(function(role) {
        return (role + "s").replace(/ss$/, "s");
      });

      dontWantTheseRoles = dontWantTheseRoles.map(function(role) {
        return (role + "s").replace(/ss+$/, "s");
      });

      // Add users who have the wanted roles
      permissionType = wantTheseRoles.pop();
      if (this[permissionType]) {
        usersWhoAreOnlyInThisPermissionType = new Users(this[permissionType].users.toJSON());
      } else {
        usersWhoAreOnlyInThisPermissionType = new Users();
      }

      var howToRemoveSomeoneWhoIsntInAllRoles = function(user) {
        self.debug("  Checking on " + user.username);
        if (self[permissionType].users.indexOf(user.username) === -1) {
          self.debug("   - removing " + user.username + " from " + groupLabel + " because they are not in " + permissionType);
          usersWhoAreOnlyInThisPermissionType.remove(user.username);
        }
      };
      var showUsernames = function(user) {
        return user.username;
      };

      for (permissionTypeIndex = wantTheseRoles.length - 1; permissionTypeIndex >= 0; permissionTypeIndex--) {
        permissionType = wantTheseRoles[permissionTypeIndex];
        self.debug("Making sure " + usersWhoAreOnlyInThisPermissionType.map(showUsernames) + " are " + permissionType);

        if (!self[permissionType]) {
          continue;
        }

        if (!self[permissionType]) {
          this.warn("  This team doesn't have a " + permissionType + " so this means the " + groupLabel + " is empty.");
          return empty;
        } else {
          // self.debug("  who are ", self[permissionType].users.map(function(user) {
          //   return user.username;
          // }));
        }
        // If the user isn't in this permisisonType also, remove it
        for (var userIndex = usersWhoAreOnlyInThisPermissionType._collection.length - 1; userIndex >= 0; userIndex--) {
          howToRemoveSomeoneWhoIsntInAllRoles(usersWhoAreOnlyInThisPermissionType._collection[userIndex]);
        }
      }

      this.debug("These users " + usersWhoAreOnlyInThisPermissionType.map(function(user) {
        return user.username;
      }).join(" ") + " have all the roles requested: " + groupLabel.replace("onlys", ""));
      if (!usersWhoAreOnlyInThisPermissionType || usersWhoAreOnlyInThisPermissionType.length === 0) {
        return empty;
      }

      var howToRemoveSomeoneWhoIsInTheUnWantedPermissions = function(user) {
        if (usersWhoAreOnlyInThisPermissionType[user.username]) {
          self.debug("  - " + user.username + " is also in " + permissionType + " so removing them from the " + groupLabel + ", there are " + usersWhoAreOnlyInThisPermissionType.length + " left");
          usersWhoAreOnlyInThisPermissionType.remove(user.username);
        }
      };

      for (permissionTypeIndex = dontWantTheseRoles.length - 1; permissionTypeIndex >= 0; permissionTypeIndex--) {
        permissionType = dontWantTheseRoles[permissionTypeIndex];
        if (!self[permissionType]) {
          continue;
        }
        // remove users who are in the permission type we dont want
        self[permissionType].users.map(howToRemoveSomeoneWhoIsInTheUnWantedPermissions);

        if (usersWhoAreOnlyInThisPermissionType.length === 0) {
          return empty;
        }
      }
      return usersWhoAreOnlyInThisPermissionType;
    }
  },

  /**
   * Syntactic sugar for apps who want to show users in one category, not in each low level permissions.
   *
   * This permission is side effect of when a user has only admin permission.
   * In this case the user can only access to only admin functionalities such
   * as adding new users to the database. This role is used by project managers or IT staff
   * who dont know anything about the data itself, and are only setting up the corpus for
   * the team to use if the PI or corpus owner doesnt know how to do the permissions.
   * set-up themselves.
   *
   * @type {Permission}
   */
  adminOnlys: {
    get: function() {
      if (this._adminOnlys && this._adminOnlys.timestamp && (Date.now() - this._adminOnlys.timestamp < this.cacheTimeLength)) {
        this.debug("Not regenerating the list of adminOnlys, its fresh enough. " + new Date(this._adminOnlys.timestamp));
      } else {
        this._adminOnlys = this.giveMeUsersWithTheseRolesAndNotTheseRoles(["admin"], ["writer", "reader"]);
        this._adminOnlys.timestamp = Date.now();
      }
      return this._adminOnlys;
    },
    set: function() {
      this.warn("adminOnlys cannot be set. it is only syntactic sugar. If you want to modify roles, see the addUser and removeUser functions");
    }
  },

  /**
   * Syntactic sugar for apps who want to show users in one category, not grouped by low level permissions.
   *
   * This permission is side effect of when a user has only write permission.
   * In this case the user can add new data to the database, but cannot
   * review or read or see existing data. They can edit data if it is specifically
   * presented to them, they cannot query the database. As all data is version
   * controlled edits can be undone so this user is not able to destroy a database that they cant read.
   *
   * This is not a common permission to use in the system.
   *
   * This permission can be used for psycholinguistics or crowdsourcing experiemnts
   * where users are anonymous (identified anoymously by session or by device)
   * and visit a given website or Android app and can respond to stimuli and their responses
   * become new data points in the system. These users cannot access fieldlinguistics apps
   * which permit browsing of the data, but is a rare permission used by web widgets or other smaller apps which write to a corpus.
   *
   * @type {Permission}
   */
  writeOnlys: {
    get: function() {
      if (this._writeOnlys && this._writeOnlys.timestamp && (Date.now() - this._writeOnlys.timestamp < this.cacheTimeLength)) {
        this.debug("Not regenerating the list of writeOnlys, its fresh enough. " + new Date(this._writeOnlys.timestamp));
      } else {
        this._writeOnlys = this.giveMeUsersWithTheseRolesAndNotTheseRoles(["writer"], ["admin", "reader"]);
        this._writeOnlys.timestamp = Date.now();
      }
      return this._writeOnlys;
    },
    set: function() {
      this.warn("writeOnlys cannot be set. it is only syntactic sugar. If you want to modify roles, see the addUser and removeUser functions");
    }
  },

  /**
   * Syntactic sugar for apps who want to show users in one category, not grouped by low level permissions.
   *
   * This permission is side effect of when a user has only read permission.
   * In this case the user might be part of a grant commitees or the general public (for
   * the aspects of the corpus which have been made @publicic),
   * language consultants who might leave mean comments on other consultant's data,
   * or other sorts of external viewers of the data who the team doesnt want to leave
   * comments.
   *
   * @type {Permission}
   */
  readOnlys: {
    get: function() {
      if (this._readOnlys && this._readOnlys.timestamp && (Date.now() - this._readOnlys.timestamp < this.cacheTimeLength)) {
        this.debug("Not regenerating the list of readOnlys, its fresh enough. " + new Date(this._readOnlys.timestamp));
      } else {
        this._readOnlys = this.giveMeUsersWithTheseRolesAndNotTheseRoles(["reader"], ["admin", "writer"]);
        this._readOnlys.timestamp = Date.now();
      }
      return this._readOnlys;
    },
    set: function() {
      this.warn("readOnlys cannot be set. it is only syntactic sugar. If you want to modify roles, see the addUser and removeUser functions");
    }
  },

  /**
   * Syntactic sugar for apps who want to show users in one category, not grouped by low level permissions.
   *
   * This permission is side effect of when a user has only comment permission.
   * This is a rare case which might be used in a web widget external to the data entry apps
   * where users or anonymous visitors can leave a comment on data if it is presented to them,
   * but cannot browse the database.
   *
   * @type {Permission}
   */
  commentOnlys: {
    get: function() {
      if (this._commentOnlys && this._commentOnlys.timestamp && (Date.now() - this._commentOnlys.timestamp < this.cacheTimeLength)) {
        this.debug("Not regenerating the list of commentOnlys, its fresh enough. " + new Date(this._commentOnlys.timestamp));
      } else {
        this._commentOnlys = this.giveMeUsersWithTheseRolesAndNotTheseRoles(["commenter"], ["admin", "writer", "reader"]);
        this._commentOnlys.timestamp = Date.now();
      }
      return this._commentOnlys;
    },
    set: function() {
      this.warn("commentOnlys cannot be set. it is only syntactic sugar. If you want to modify roles, see the addUser and removeUser functions");
    }
  },

  /**
   * Syntactic sugar for apps who want to show users in one category, not grouped by low level permissions.
   *
   * This permission is side effect of when a user has only read and comment permission.
   * This is a common permission used by research teams where an external reviewer,
   * or a second language consultant can review and coment on the data,
   * but cannot modify the data itself.
   *
   * @type {Permission}
   */
  readerCommenterOnlys: {
    get: function() {
      if (this._readerCommenterOnlys && this._readerCommenterOnlys.timestamp && (Date.now() - this._readerCommenterOnlys.timestamp < this.cacheTimeLength)) {
        this.debug("Not regenerating the list of readerCommenterOnlys, its fresh enough. " + new Date(this._readerCommenterOnlys.timestamp));
      } else {
        this._readerCommenterOnlys = this.giveMeUsersWithTheseRolesAndNotTheseRoles(["reader", "commenter"], ["admin", "writer"]);
        this._readerCommenterOnlys.timestamp = Date.now();
      }
      return this._readerCommenterOnlys;
    },
    set: function() {
      this.warn("readerCommenterOnlys cannot be set. it is only syntactic sugar. If you want to modify roles, see the addUser and removeUser functions");
    }
  },

  /**
   * Syntactic sugar for apps who want to show users in one category, not grouped by low level permissions.
   *
   * This permission is side effect of when a user has only read and write permission.
   * This is a rare permission where the user has been leaving abusive comments,
   * and are thus only permitted to browse and edit the data itself, not add or edit comments.
   *
   * If you want to give someone access as a reader+writer, use readerWritersComenters.
   *
   * @type {Permission}
   */
  readerWriters: {
    get: function() {
      return this.readerCommenterWriters;
    },
    set: function() {
      this.warn("readerWriters cannot be set. it is only syntactic sugar. If you want to modify roles, see the addUser and removeUser functions");
    }
  },

  /**
   * Syntactic sugar for apps who want to show users in one category, not grouped by low level permissions.
   *
   * This permission is side effect of when a user has only read comment and write permission.
   * This is syntactic sugar which is often refered to as "writers" in traditional databases permissions.
   * It means the user can write data, read data and comment on data
   *
   * @return {Permission} A permissions group with an array of Users who fall into this category
   */
  readerCommenterWriters: {
    get: function() {
      if (this._readerCommenterWriterOnlys && this._readerCommenterWriterOnlys.timestamp && (Date.now() - this._readerCommenterWriterOnlys.timestamp < this.cacheTimeLength)) {
        this.debug("Not regenerating the list of readerCommenterWriterOnlys, its fresh enough. " + new Date(this._readerCommenterWriterOnlys.timestamp));
      } else {
        // this.debugMode = true;
        this._readerCommenterWriterOnlys = this.giveMeUsersWithTheseRolesAndNotTheseRoles(["reader", "writer", "commenter"], ["admin"]);
        this._readerCommenterWriterOnlys.timestamp = Date.now();
        // this.debugMode = false;
      }

      return this._readerCommenterWriterOnlys;
    },
    set: function() {
      this.warn("readerCommenterWriters cannot be set. it is only syntactic sugar. If you want to modify roles, see the addUser and removeUser functions");
    }
  },

  /**
   * Syntactic sugar for apps who want to show users in one category, not grouped by low level permissions.
   *
   * Often refered to as "admins" in traditional databases permissio ns
   * It means the user can write data, read data and comment on data, and perform any operation on the data.
   *
   * @return {Permission} A permission group with an array of Users who fall into this category
   */
  readerCommenterWriterAdmins: {
    get: function() {
      if (this._readerCommenterWriterAdminAlso && this._readerCommenterWriterAdminAlso.timestamp && (Date.now() - this._readerCommenterWriterAdminAlso.timestamp < this.cacheTimeLength)) {
        this.debug("Not regenerating the list of readerCommenterWriterAdminAlso, its fresh enough. " + new Date(this._readerCommenterWriterAdminAlso.timestamp));
      } else {
        this._readerCommenterWriterAdminAlso = this.giveMeUsersWithTheseRolesAndNotTheseRoles(["reader", "writer", "admin"], []);
        this._readerCommenterWriterAdminAlso.timestamp = Date.now();
      }
      return this._readerCommenterWriterAdminAlso;
    },
    set: function() {
      this.warn("readerCommenterWriterAdmins cannot be set. it is only syntactic sugar. If you want to modify roles, see the addUser and removeUser functions");
    }
  },

  /*** Permissions to control unruly users who the team is not able to convince to work as a team ***/

  /**
   * Syntactic sugar for apps who want to show users in one category, not grouped by low level permissions.
   *
   * This is a permission provided for teams who want many language consultants to contribute to one
   * database without seeing the data other consultants have added. We expect this may be a common
   * permission especially for teams where there is no `standard` dialect and speakers of other dialects
   * are very prescriptive and think others are making `errors` which need to be corrected, this permission prevents them from `correcting` others data.
   *
   * @return {Permission} A permission group with an array of Users who fall into this category
   */
  readOwnDataWriteOwnDataOnlys: {
    get: function() {
      return this.readOwnDataWriteOwnDataOnly || [];
    },
    set: function() {
      this.warn("readOwnDataWriteOwnDataOnlys cannot be set. it is only syntactic sugar. If you want to modify roles, see the addUser and removeUser functions");
    }
  },

  /**
   * Syntactic sugar for apps who want to show users in one category, not grouped by low level permissions.
   *
   * This is a rare permission provided for team members who have been editing other people's
   * data either to update it to their own dialect (when it was supposed to be the dialect
   * of the original source) or other sorts of 'destructive' edits. A user with this permission
   * will know that they have been limited to commenting on others' data, and editing their own
   * data only (similar to Facebook).
   *
   * This permission should not be a default permission among research teams, instead the readerCommenterWriter
   * permission should be used to reduce territoriality in the data and makes it harder for the database to be kept clean
   * because users cannot update/comment on data when they notice a problem. All data is versioned so any
   * mistakes can be undone.
   *
   * @return {Permission} A permission group with an array of Users who fall into this category
   */
  readCommentAllWriteOwnDataOnlys: {
    get: function() {
      return this.readCommentAllWriteOwnDataOnly || [];
    },
    set: function() {
      this.warn("readCommentAllWriteOwnDataOnlys cannot be set. it is only syntactic sugar. If you want to modify roles, see the addUser and removeUser functions");
    }
  },

  /**
   * Syntactic sugar for apps who want to show users in one category, not grouped by low level permissions.
   *
   * This is a rare permission provided for team members who have been editing other people's
   * data either to update it to their own dialect (when it was supposed to be the dialect
   * of the original source) or other sorts of 'destructive' edits which the team wants to
   * prevent the user from doing this without making the user uncomfortable. A user with this permission
   * will know that they have been limited to editing their own data only (similar to Facebook) and cannot
   * edit other people's data.
   *
   * This permission should not be a default permission among research teams, instead the readerCommenterWriter
   * permission should be used to reduce territoriality in the data and makes it harder for the database to be kept clean
   * because users cannot update/comment on data when they notice a problem. All data is versioned so any
   * mistakes can be undone.
   *
   * @return {Permission} A permission group with an array of Users who fall into this category
   */
  readAllWriteOwnDataOnlys: {
    get: function() {
      return this.readAllWriteOwnDataOnly || [];
    },
    set: function() {
      this.warn("readAllWriteOwnDataOnlys cannot be set. it is only syntactic sugar. If you want to modify roles, see the addUser and removeUser functions");
    }
  },

  /**
   * This might be used by teams who want to have fine grained control over the
   * permissions, or who want to understand the fine grained control at a lower level.
   *
   * @return {Object} an object with the 3 categories of permission like you
   * would have in a PHPmyAdmin console.
   */
  viewAsBasePermissionSystem: {
    get: function() {
      return this;
    }
  },

  /**
   * This view will be missing some members of the corpora since not all combinations of
   * permission have syntactic sugar methods in this library. This shoudlnt be used
   * unless the app dev is sure that the users wont be using corpora for psycholinguistics or
   * crowdsourcing or computational linguistics.
   *
   * @return {Object} an object with the 3 categories of permission like you
   * would have in a PHPmyAdmin console.
   */
  viewAsGroupedPermissionSystem: {
    get: function() {
      return {
        adminOnlys: this.adminOnlys,
        writeOnlys: this.writeOnlys,
        readOnlys: this.readOnlys,
        readerCommenterOnlys: this.readerCommenterOnlys,
        readerWriters: this.readerCommenterWriters,
        admins: this.readerCommenterWriterAdmins,
        commentOnlys: this.commentOnlys,
        // readerCommenterWriters: readerCommenterWriters,
        //writerCommenterOnlys
        //writerAdminOnlys
        //writerCommenterAdminOnlys
      };
    }
  },

  /**
   * This is the way most field linguistics apps present the permissions system.
   *
   * @return {Object} an object with the 3 categories of permission like you
   * would have in a PHPmyAdmin console.
   */
  viewAsDataBasePermissionSystem: {
    get: function() {
      return {
        admins: this.readerCommenterWriterAdmins,
        writers: this.readerCommenterWriters,
        readers: this.readerCommenterOnlys,
        commenters: this.readerCommenterOnlys
      };
    }
  },

  /**
   * This might be the way that some computational linguistics teams will
   * understand the permission system best, but the words
   * `collaborator` and `contributor` are so similar that thus far no one
   * has used these terms.
   *
   * @return {Object} an object with the 2 categories of permission like you
   * would have on GitHub
   */
  viewAsEmailingTeamPermissionSystem: {
    get: function() {
      return {
        contributors: this.contributors,
        collaborators: this.collaborators,
        owners: this.readerCommenterWriterAdmins
      };
    }
  },

  addUser: {
    value: function(user) {
      if (!user || !user.roles) {
        this.warn("You should provide roles you want to add... doing nothing.");
        return;
      }
      this.debug(user.roles);
      var roles = user.roles;
      delete user.roles;

      for (var roleIndex = roles.length - 1; roleIndex >= 0; roleIndex--) {
        var permissionType = roles[roleIndex];
        if (!this[permissionType]) {
          permissionType = permissionType.toLowerCase() + "s";
          if (!this[permissionType]) {
            var newPermission = this.buildPermissionFromAPermissionType(permissionType);
            this.debug(" Creating a permssion group for " + permissionType, newPermission);

            this.add(newPermission);
          }
        }
        this[permissionType].users.add(user);
        this.debug("the user was added to the permission group " + permissionType + " there are a total of " + this[permissionType].length + " users with this sort of access ", this[permissionType].users);
      }
    }
  },

  /**
   * Removes user from roles on this permissions team, or removes user entirely if only a username is supplied.
   *
   * @param  {Object} user A user with minimally a username and roles which should be removed, or alternatively can be just a username if you want to remove the user entirely from the team.
   */
  removeUser: {
    value: function(user) {
      if (typeof user === "string") {
        user = {
          username: user,
          roles: this.currentPermissions
        };
        this.warn("Remove was requested of a username, removing the username entirely from the permission team.");
      }
      if (!user || !user.roles) {
        this.warn("You should provide roles you want to remove... doing nothing.");
        return;
      }
      // this.debugMode = true;
      this.debug(user.roles);
      var roles = user.roles;
      delete user.roles;

      for (var roleIndex = roles.length - 1; roleIndex >= 0; roleIndex--) {
        var permissionType = roles[roleIndex];
        if (!this[permissionType]) {
          permissionType = permissionType.toLowerCase() + "s";
        }
        if (this[permissionType]) {
          // this[permissionType].users.debugMode = true;
          if (this[permissionType].users[user.username]) {
            var userWasInThisPermission = this[permissionType].users.remove(user);
            this.debug("removed ", userWasInThisPermission);
          } else {
            this.warn("The user " + user.username + " was not in the " + permissionType + " anyway.");
          }
          this.debug("the user " + user.username + " is not in " + permissionType + " there are a total of " + this[permissionType].length + " users with this sort of access ", this[permissionType].users);
        }
      }
    }
  },

  /**
   * A list of "hats" which team members can wear in this team.
   */
  currentPermissions: {
    get: function() {
      if (!this._collection || this._collection === 0) {
        return [];
      }
      var self = this;
      return this._collection.map(function(permission) {
        return permission[self.primaryKey];
      });

    }
  },

  buildPermissionFromAPermissionType: {
    value: function(permissionType) {
      var verb = permissionType.replace(/s/, "").replace(/writer/, "write").replace(/er/, "");
      var label = permissionType;
      if (label && label.length > 2) {
        label = label[0].toUpperCase() + label.substring(1, label.length);
      }
      var helpLinguists = "These users can perform " + verb + " operations on the corpus";

      return {
        users: [],
        verb: verb,
        id: permissionType,
        labelFieldLinguists: label,
        helpLinguists: helpLinguists,
        directObject: "corpus",
        // debugMode: true
      };
    }
  },

  fetch: {
    value: function() {
      var deferred = Q.defer(),
        self = this;

      if (!this.parent) {
        Q.nextTick(function() {
          self.fetching = self.loading = false;
          deferred.reject({
            error: "Cannot fetch if the permissions are not attached to a corpus"
          });
        });
        return deferred.promise;
      }

      if (!this.application || !this.application.authentication || !this.application.authentication.user || !this.application.authentication.user.username) {
        Q.nextTick(function() {
          self.fetching = self.loading = false;
          deferred.reject({
            error: "Cannot fetch if the permissions are not in an application."
          });
        });
        return deferred.promise;
      }

      if (this.loaded) {
        this.warn("not fetching permissions, they are fresh.");
        Q.nextTick(function() {
          self.fetching = self.loading = false;
          deferred.resolve(self);
        });
        return deferred.promise;
      }

      var dataToPost = {
        connection: this.parent.connection.toJSON(),
        username: this.application.authentication.user.username,
      };
      dataToPost.connection = dataToPost.couchConnection = dataToPost.connection;

      this.fetching = this.loading = true;
      CORS.makeCORSRequest({
        type: "POST",
        data: dataToPost,
        dataType: "json",
        url: this.parent.connection.authUrl + "/corpusteam"
      }).then(function(result) {
          self.fetching = self.loading = false;
          self.loaded = true;
          self.populate(result.users);
          deferred.resolve(self);
        },
        function(reason) {
          self.fetching = self.loading = false;
          self.debug(reason);
          deferred.reject(reason);
        }).fail(
        function(error) {
          console.error(error.stack, self);
          deferred.reject(error);
        });

      return deferred.promise;
    }
  },

  /**
   * Accepts an object containing permission groups which are an array of
   * users masks which have this permission. This simple object is used to populate the permissions model.
   *
   * @param  {Object} users an object containing keys which are verbs (permission types) and the value is an array of users who are in this category
   */
  populate: {
    value: function(users) {
      if (!users || users === "defaults") {
        users = {};
        this.debug("Using default permission types");
      }
      users.admins = users.admins || [];
      users.writers = users.writers || [];
      users.readers = users.readers || [];
      users.commenters = users.commenters || [];
      users.exporters = users.exporters || [];

      users.notonteam = users.notonteam || [];
      users.allusers = users.allusers || [];

      this.usersNotOnTeam = new Users(users.notonteam);
      this.allUsers = new Users(users.allusers);

      delete users.allusers;
      delete users.notonteam;

      var permissionType;
      for (permissionType in users) {
        if (users.hasOwnProperty(permissionType) && permissionType) {
          /* if the permission is just an array of users, construct basic permission meta data around it */
          if (Object.prototype.toString.call(users[permissionType]) === "[object Array]") {
            var usersArray = users[permissionType];
            users[permissionType] = this.buildPermissionFromAPermissionType(permissionType);
            users[permissionType].users = usersArray;
          }
          this.debug("adding " + permissionType, users[permissionType]);
          this.add(new Permission(users[permissionType]));
        }
      }
    }
  },

  sanitizeStringForPrimaryKey: {
    value: function(value) {
      return value;
    }
  }

});

exports.Permissions = Permissions;