Code coverage report for api/permission/Permissions.js

Statements: 71.62% (159 / 222)      Branches: 77.1% (101 / 131)      Functions: 51.85% (28 / 54)      Lines: 71.62% (159 / 222)      Ignored: none     

All files » api/permission/ » Permissions.js
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 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 8301 1 1 1 1                                                                                         1 28 28   28 28 28     1                                                                           9                                                 9                                                             51 1 1   50 1 1       49               49 85     49 79       49 49 49         49 217 217 8 8     49 217     49 36 36   36       36                 36 217       49 323   49 4     45 391 236 236       45 50 50       50   50 22     23                                   22 16   6 6   22                                                       22 16   6 6   22                                         22 16   6 6   22                                     22 16   6 6   22                                     49 42   7 7   49                                       1                                   43 36     7 7       43                                 42 35   7 7   42                                                                                                                                                                                                             22                                               8                                       9                   4       4 4 4   4 7 7 7 7             7 7                       2 1       1   2         2 2 2   2 7 7 2   7   7 2 2   5   7                     2     2 2 10               142 142 142 142   142   142                                                                                                                                                                           26 13 13   26 26 26 26 26   26 26   26 26   26 26   26 26 142   142 142 142 142   142 142               142           1  
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) {
  Eif (!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();
      Eif (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);
 
        Iif (!self[permissionType]) {
          continue;
        }
 
        Iif (!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];
        Iif (!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) {
      Iif (!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];
        Eif (!this[permissionType]) {
          permissionType = permissionType.toLowerCase() + "s";
          Iif (!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.");
      }
      Iif (!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";
        }
        Eif (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() {
      Iif (!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;
      Eif (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) {
        Eif (users.hasOwnProperty(permissionType) && permissionType) {
          /* if the permission is just an array of users, construct basic permission meta data around it */
          Eif (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;