# Source: src/js/models/UserModel.js

    /*global define */
    define(['jquery', 'underscore', 'backbone', 'jws', 'models/Search', "collections/SolrResults"],
    	function($, _, Backbone, JWS, SearchModel, SearchResults) {
    	'use strict';

    	/**
      * @class UserModel
      * @classcategory Models
      * @extends Backbone.Model
      * @constructor
      */
    	var UserModel = Backbone.Model.extend(
        /** @lends UserModel.prototype */{
    		defaults: function(){
    			return{
    				type: "person", //assume this is a person unless we are told otherwise - other possible type is a "group"
    				checked: false, //Is set to true when we have checked the account/subject info of this user
            tokenChecked: false, //Is set to true when the uer auth token has been checked
    				basicUser: false, //Set to true to only query for basic info about this user - prevents sending queries for info that will never be displayed in the UI
    				lastName: null,
    				firstName: null,
    				fullName: null,
    				email: null,
    				logo: null,
    				description: null,
    				verified: null,
    				username: null,
    				usernameReadable: null,
    				orcid: null,
    				searchModel: null,
    				searchResults: null,
    				loggedIn: false,
    				ldapError: false, //Was there an error logging in to LDAP
    				registered: false,
    				isMemberOf: [],
    				isOwnerOf: [],
    				identities: [],
    				identitiesUsernames: [],
            allIdentitiesAndGroups: [],
    				pending: [],
    				token: null,
    				expires: null,
    				timeoutId: null,
    				rawData: null,
            portalQuota: -1,
            isAuthorizedCreatePortal: null,
            dataoneQuotas: null,
            dataoneSubscription: null
    			}
    		},

    		initialize: function(options){
    			if(typeof options !== "undefined"){
    				if(options.username) this.set("username", options.username);
    				if(options.rawData)  this.set(this.parseXML(options.rawData));
    			}

    			this.on("change:identities", this.pluckIdentityUsernames);

    			this.on("change:username change:identities change:type", this.updateSearchModel);
    			this.createSearchModel();

    			this.on("change:username", this.createReadableUsername());

    			//Create a search results model for this person
    			var searchResults = new SearchResults([], { rows: 5, start: 0 });
    			this.set("searchResults", searchResults);

          if( MetacatUI.appModel.get("enableBookkeeperServices") ){
            //When the user is logged in, see if they have a DataONE subscription
            this.on("change:loggedIn", this.fetchSubscription);
          }
    		},

    		createSearchModel: function(){
    			//Create a search model that will retrieve data created by this person
    			this.set("searchModel", new SearchModel());
    			this.updateSearchModel();
    		},

    		updateSearchModel: function(){
    			if(this.get("type") == "node"){
    				this.get("searchModel").set("dataSource", [this.get("node").identifier]);
    				this.get("searchModel").set("username", []);
    			}
    			else{
    				//Get all the identities for this person
    				var ids = [this.get("username")];

    				_.each(this.get("identities"), function(equivalentUser){
    					ids.push(equivalentUser.get("username"));
    				});
    				this.get("searchModel").set("username", ids);
    			}

    			this.trigger("change:searchModel");
    		},

    		parseXML: function(data){
    			var model = this,
    				username = this.get("username");

    			//Reset the group list so we don't just add it to it with push()
    			this.set("isMemberOf", this.defaults().isMemberOf, {silent: true});
    			this.set("isOwnerOf", this.defaults().isOwnerOf, {silent: true});
    			//Reset the equivalent id list so we don't just add it to it with push()
    			this.set("identities", this.defaults().identities, {silent: true});

    			//Find this person's node in the XML
    			var userNode = null;
    			if(!username)
    				var username = $(data).children("subject").text();
    			if(username){
    				var subjects = $(data).find("subject");
    				for(var i=0; i<subjects.length; i++){
    					if($(subjects[i]).text().toLowerCase() == username.toLowerCase()){
    						userNode = $(subjects[i]).parent();
    						break;
    					}
    				}
    			}
    			if(!userNode)
    				userNode = $(data).first();

    			//Get the type of user - either a person or group
    			var type = $(userNode).prop("tagName");
    			if(type) type = type.toLowerCase();

    			if(type == "group"){
    				var fullName = $(userNode).find("groupName").first().text();
    			}
    			else if(type){
    				//Find the person's info
    				var	firstName  = $(userNode).find("givenName").first().text(),
    					lastName   = $(userNode).find("familyName").first().text(),
    					email      = $(userNode).find("email").first().text(),
    					verified   = $(userNode).find("verified").first().text(),
    					memberOf   = this.get("isMemberOf"),
    					ownerOf	   = this.get("isOwnerOf"),
    					identities = this.get("identities"),
              equivUsernames = [];

    				//Sometimes names are saved as "NA" when they are not available - translate these to false values
    				if(firstName == "NA")
    					firstName = null;
    				if(lastName == "NA")
    					lastName = null;

    				//Construct the fullname from the first and last names, but watch out for falsely values
    				var fullName = "";
    					fullName += firstName? firstName : "";
    					fullName += lastName? (" " + lastName) : "";

    				if(!fullName)
    					fullName = this.getNameFromSubject(username);

    				//Don't get this detailed info about basic users
    				if(!this.get("basicUser")){
    					//Get all the equivalent identities for this user
    					var equivalentIds = $(userNode).find("equivalentIdentity");
    					if(equivalentIds.length > 0)
    						var allPersons = $(data).find("person subject");

    					_.each(equivalentIds, function(identity, i){
    						//push onto the list
    						var username = $(identity).text(),
    							equivUserNode;

    						//Find the matching person node in the response
    						_.each(allPersons, function(person){
    							if($(person).text().toLowerCase() == username.toLowerCase()){
    								equivUserNode = $(person).parent().first();
    								allPersons = _.without(allPersons, person);
    							}
    						});

    						var equivalentUser = new UserModel({ username: username, basicUser: true, rawData: equivUserNode });
    						identities.push(equivalentUser);
                equivUsernames.push(username);
    					});
    				}

    				//Get each group and save
    				_.each($(data).find("group"), function(group, i){
    					//Save group ID
    					var groupId = $(group).find("subject").first().text(),
    						groupName = $(group).find("groupName").text();

    					memberOf.push({ groupId: groupId, name: groupName });

    					//Check if this person is a rightsholder
    					var allRightsHolders = [];
    					_.each($(group).children("rightsHolder"), function(rightsHolder){
    						allRightsHolders.push($(rightsHolder).text().toLowerCase());
    					});
    					if(_.contains(allRightsHolders, username.toLowerCase()))
    						ownerOf.push(groupId);
    				});
    			}

          var allSubjects = _.pluck( this.get("isMemberOf"), "groupId" );
          allSubjects.push(this.get("username"));
          allSubjects = allSubjects.concat(equivUsernames);

    			return {
    				isMemberOf: memberOf,
    				isOwnerOf: ownerOf,
    				identities: identities,
            allIdentitiesAndGroups: allSubjects,
    				verified: verified,
    				username: username,
    				firstName: firstName,
    				lastName: lastName,
    				fullName: fullName,
    				email: email,
    				registered: true,
    				type: type,
    				rawData: data
    			}
    		},

    		getInfo: function(){
    			var model = this;

    			//If the accounts service is not on, flag this user as checked/completed
    			if(!MetacatUI.appModel.get("accountsUrl")){
    				this.set("fullName", this.getNameFromSubject());
    				this.set("checked", true);
    				return;
    			}

    			//Only proceed if there is a username
    			if(!this.get("username")) return;

    			//Get the user info using the DataONE API
    			var url = MetacatUI.appModel.get("accountsUrl") + encodeURIComponent(this.get("username"));

    			var requestSettings = {
    				type: "GET",
    				url: url,
    				success: function(data, textStatus, xhr) {
    					//Parse the XML response to get user info
    					var userProperties = model.parseXML(data);
    					//Filter out all the falsey values
    					_.each(userProperties, function(v, k) {
    				      if(!v) {
    				        delete userProperties[k];
    				      }
    				    });
    					model.set(userProperties);

    					 //Trigger the change events
    					model.trigger("change:isMemberOf");
    					model.trigger("change:isOwnerOf");
    					model.trigger("change:identities");

    					model.set("checked", true);
    				},
    				error: function(xhr, textStatus, errorThrown){
    					// Sometimes the node info has not been received before this getInfo() is called.
    					// If the node info was received while this getInfo request was pending, and this user was determined
    					// to be a node, then we can skip any further action here.
    					if(model.get("type") == "node")
    						return;

    					if((xhr.status == 404) && MetacatUI.nodeModel.get("checked")){
    						model.set("fullName", model.getNameFromSubject());
    						model.set("checked", true);
    					}
    					else if((xhr.status == 404) && !MetacatUI.nodeModel.get("checked")){
    						model.listenToOnce(MetacatUI.nodeModel, "change:checked", function(){
    							if(!model.isNode()){
    								model.set("fullName", model.getNameFromSubject());
    								model.set("checked", true);
    							}
    						});
    					}
    					else{
    						//As a backup, search for this user instead
    						var requestSettings = {
    								type: "GET",
    								url: MetacatUI.appModel.get("accountsUrl") + "?query=" + encodeURIComponent(model.get("username")),
    								success: function(data, textStatus, xhr) {
    									//Parse the XML response to get user info
    									model.set(model.parseXML(data));

    									 //Trigger the change events
    									model.trigger("change:isMemberOf");
    									model.trigger("change:isOwnerOf");
    									model.trigger("change:identities");

    									model.set("checked", true);
    								},
    								error: function(){
    									//Set some blank values and flag as checked
    									//model.set("username", "");
    									//model.set("fullName", "");
    									model.set("notFound", true);
    									model.set("checked", true);
    								}
    							}
    						//Send the request
    						$.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));

    					}
    				}
    			}

    			//Send the request
    			$.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
    		},

    		//Get the pending identity map requests, if the service is turned on
    		getPendingIdentities: function(){
    			if(!MetacatUI.appModel.get("pendingMapsUrl")) return false;

    			var model = this;

    			//Get the pending requests
    			var requestSettings = {
    				url: MetacatUI.appModel.get("pendingMapsUrl") + encodeURIComponent(this.get("username")),
    				success: function(data, textStatus, xhr){
    					//Reset the equivalent id list so we don't just add it to it with push()
    					model.set("pending", model.defaults().pending);
    					var pending = model.get("pending");
    					_.each($(data).find("person"), function(person, i) {

    						//Don't list yourself as a pending map request
    						var personsUsername = $(person).find("subject").text();
    						if(personsUsername.toLowerCase() == model.get("username").toLowerCase())
    							return;

    						//Create a new User Model for this pending identity
    						var pendingUser = new UserModel({ rawData: person });

    						if(pendingUser.isOrcid())
    							pendingUser.getInfo();

    						pending.push(pendingUser);
    					});
    					model.set("pending", pending);
    					model.trigger("change:pending"); //Trigger the change event
    				},
    				error: function(xhr, textStatus){
    					if(xhr.responseText.indexOf("error code 34")){
    						model.set("pending", model.defaults().pending);
    						model.trigger("change:pending"); //Trigger the change event
    					}
    				}
    			}

    			$.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
    		},

    		getNameFromSubject: function(username){
    			var username  = username || this.get("username"),
    				fullName = "";

    			if(!username) return;

    			if((username.indexOf("uid=") > -1) && (username.indexOf(",") > -1))
    				fullName = username.substring(username.indexOf("uid=") + 4, username.indexOf(","));
    			else if((username.indexOf("CN=") > -1) && (username.indexOf(",") > -1))
    				fullName = username.substring(username.indexOf("CN=") + 3, username.indexOf(","));

    			//Cut off the last string after the name when it contains digits - not part of this person's names
    			if(fullName.lastIndexOf(" ") > fullName.indexOf(" ")){
    				var lastWord = fullName.substring(fullName.lastIndexOf(" "));
    				if(lastWord.search(/\d/) > -1)
    					fullName = fullName.substring(0, fullName.lastIndexOf(" "));
    			}

    			//Default to the username
    			if(!fullName) fullName = this.get("fullname") || username;

    			return fullName;
    		},

    		isOrcid: function(orcid){
    			var username = (typeof orcid === "string")? orcid : this.get("username");

    			//Have we already verified this?
    			if((typeof orcid == "undefined") && (username == this.get("orcid"))) return true;

    			//Checks for ORCIDs using the orcid base URL as a prefix
    			if(username.indexOf("orcid.org/") > -1){
    				return true;
    			}

    			//If the ORCID base url is not present, we will check if this is a 19-digit ORCID ID
    			//A simple and fast check first
    			//ORCiDs are 16 digits and 3 dashes - 19 characters
    			if(username.length != 19) return false;

    			/* The ORCID checksum algorithm to determine is a character string is an ORCiD
    			 * http://support.orcid.org/knowledgebase/articles/116780-structure-of-the-orcid-identifier
    			 */
    			var total = 0,
    				baseDigits = username.replace(/-/g, "").substr(0, 15);

    			for(var i=0; i<baseDigits.length; i++){
    				var digit = parseInt(baseDigits.charAt(i));
    				total = (total + digit) * 2;
    			}

    			var remainder = total % 11,
    				result = (12 - remainder) % 11,
    				checkDigit = (result == 10) ? "X" : result.toString(),
    				isOrcid = (checkDigit == username.charAt(username.length-1));

    			if(isOrcid)
    				this.set("orcid", username);

    			return isOrcid;
    		},

    		isNode: function(){
    			var model = this;
    			var node = _.find(MetacatUI.nodeModel.get("members"), function(nodeModel) {
    				return nodeModel.shortIdentifier.toLowerCase() == (model.get("username")).toLowerCase();
    			  });

    			return (node && (node !== undefined))
    		},

    		// Will check if this user is a Member Node. If so, it will save the MN info to the model
    		saveAsNode: function(){
    			if(!this.isNode()) return;

    			var model = this;
    			var node = _.find(MetacatUI.nodeModel.get("members"), function(nodeModel) {
    				return nodeModel.shortIdentifier.toLowerCase() == (model.get("username")).toLowerCase();
    			  });

    			this.set({
    				type: "node",
    				logo: node.logo,
    				description: node.description,
    				node: node,
    				fullName: node.name,
    				usernameReadable: this.get("username")
    			});
    			this.updateSearchModel();
    			this.set("checked", true);
    		},

    		loginLdap: function(formData, success, error){
    			if(!formData || !appModel.get("signInUrlLdap")) return false;

    			var model = this;

    			var requestSettings = {
    				type: "POST",
    				url: MetacatUI.appModel.get("signInUrlLdap") + window.location.href,
    				data: formData,
    				success: function(data, textStatus, xhr){
    					if(success)
    						success(this);

    					model.getToken();

    				},
    				error: function(){
    					/*if(error)
    						error(this);
    					*/
    					model.getToken();
    				}
    			}

    			$.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
    		},

    		logout: function(){

    			//Construct the sign out url and redirect
    			var signOutUrl = MetacatUI.appModel.get('signOutUrl'),
    				  target = Backbone.history.location.href;

    			// DO NOT include the route otherwise we have an infinite redirect
    			// target  = target.split("#")[0];
    			target = target.slice(0, -8);

    			// make sure to include the target
    			signOutUrl += "?target=" + target;

    			// do it!
    			window.location.replace(signOutUrl);
    		},

    		// call Metacat or the DataONE CN to validate the session and tell us the user's name
    		checkStatus: function(onSuccess, onError) {
    			var model = this;

    			if (!MetacatUI.appModel.get("tokenUrl")) {
    				// look up the URL
    				var metacatUrl = MetacatUI.appModel.get('metacatServiceUrl');

    				// ajax call to validate the session/get the user info
    				var requestSettings = {
    					type: "POST",
    					url: metacatUrl,
    					data: { action: "validatesession" },
    					success: function(data, textStatus, xhr) {
    						// the Metacat (XML) response should have a fullName element
    						var username = $(data).find("name").text();

    						// set in the model
    						model.set('username', username);

    						//Are we logged in?
    						if(username){
    							model.set("loggedIn", true);
    							model.getInfo();
    						}
    						else{
    							model.set("loggedIn", false);
    							model.trigger("change:loggedIn");
    							model.set("checked", true);
    						}

    						if(onSuccess) onSuccess(data);

    					},
    					error: function(data, textStatus, xhr){
    						//User is not logged in
    						model.reset();

    						if(onError) onError();
    					}
    				}

    				$.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
    			} else {
    				// use the token method for checking authentication
    				this.getToken();
    			}
    		},

    		getToken: function(customCallback) {
    			var tokenUrl = MetacatUI.appModel.get('tokenUrl');
    			var model = this;

    			if(!tokenUrl) return false;

    			//Set up the function that will be called when we retrieve a token
    			var callback = (typeof customCallback === "function") ? customCallback : function(data, textStatus, xhr) {

    				// the response should have the token
    				var payload = model.parseToken(data),
    					username = payload ? payload.userId : null,
    					fullName = payload ? payload.fullName : model.getNameFromSubject(username) || null,
    					token    = payload ? data : null,
    					loggedIn = payload ? true : false;

    				// set in the model
    				model.set('fullName', fullName);
    				model.set('username', username);
    				model.set("token", token);
    				model.set("loggedIn", loggedIn);
            model.set("tokenChecked", true);

    				model.getTokenExpiration(payload);

    				if(username)
    					model.getInfo();
    				else
    					model.set("checked", true);
    			};

    			// ajax call to get token
    			var requestSettings = {
    				type: "GET",
    				dataType: "text",
    				xhrFields: {
    					withCredentials: true
    				},
    				url: tokenUrl,
    				data: {},
    				success: callback,
    				error: function(xhr, textStatus, errorThrown){
    					model.set("checked", true);
    				}
    			}

    			$.ajax(requestSettings);
    		},

    		getTokenExpiration: function(payload){
    			if(!payload && this.get("token")) var payload = this.parseToken(this.get("token"));
    			if(!payload) return;

    			//The exp claim should be standard - it is in UTC seconds
    			var expires = payload.exp? new Date(payload.exp * 1000) : null;

    			//Use the issuedAt and ttl as a backup (only used in d1 2.0.0 and 2.0.1)
    			if(!expires){
    				var issuedAt = payload.issuedAt? new Date(payload.issuedAt) : null,
    					lifeSpan = payload.ttl? payload.ttl : null;

    				if(issuedAt && lifeSpan && (lifeSpan > 99999))
    					issuedAt.setMilliseconds(lifeSpan);
    				else if(issuedAt && lifeSpan)
    					issuedAt.setSeconds(lifeSpan);

    				expires = issuedAt;
    			}

    			this.set("expires", expires);
    		},

    		checkToken: function(onSuccess, onError){

    			//First check if the token has expired
    			if(MetacatUI.appUserModel.get("expires") > new Date()){
    				if(onSuccess) onSuccess();

    				return;
    			}

    			var model = this;

    			var url = MetacatUI.appModel.get("tokenUrl");
    			if(!url) return;

    			var requestSettings = {
    					type: "GET",
    					url: url,
              headers: {
    			      "Cache-Control": "no-cache"
    				  },
    					success: function(data, textStatus, xhr){
    						if(data){
    							// the response should have the token
    							var payload = model.parseToken(data),
    								username = payload ? payload.userId : null,
    								fullName = payload ? payload.fullName : null,
    								token    = payload ? data : null,
    								loggedIn = payload ? true : false;

    							// set in the model
    							model.set('fullName', fullName);
    							model.set('username', username);
    							model.set("token", token);
    							model.set("loggedIn", loggedIn);

    							model.getTokenExpiration(payload);

    							MetacatUI.appUserModel.set("checked", true);

    							if(onSuccess) onSuccess(data, textStatus, xhr);
    						}
    						else if(onError)
    							onError(data, textStatus, xhr);
    					},
    					error: function(data, textStatus, xhr){
    						//If this token in invalid, then reset the user model/log out
    						MetacatUI.appUserModel.reset();

    						if(onError) onError(data, textStatus, xhr);
    					}
    			}

    			$.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
    		},

    		parseToken: function(token) {
    			if(typeof token == "undefined")
    				var token = this.get("token");

    			var jws = new KJUR.jws.JWS();
    			var result = 0;
    			try {
    				result = jws.parseJWS(token);
    			} catch (ex) {
    			    result = 0;
    			}

    			if(!jws.parsedJWS) return "";

    			var payload = $.parseJSON(jws.parsedJWS.payloadS);
    			return payload;
    		},

    		update: function(onSuccess, onError){
    			var model = this;

    			var person =
    				'<?xml version="1.0" encoding="UTF-8"?>'
    				+ '<d1:person xmlns:d1="http://ns.dataone.org/service/types/v1">'
    					+ '<subject>' + this.get("username") + '</subject>'
    					+ '<givenName>' + this.get("firstName") + '</givenName>'
    					+ '<familyName>' + this.get("lastName") + '</familyName>'
    					+ '<email>' + this.get("email") + '</email>'
    				+ '</d1:person>';

    			var xmlBlob = new Blob([person], {type : 'application/xml'});
    			var formData = new FormData();
    			formData.append("subject", this.get("username"));
    			formData.append("person", xmlBlob, "person");

    			var updateUrl = MetacatUI.appModel.get("accountsUrl") + encodeURIComponent(this.get("username"));

    			// ajax call to update
    			var requestSettings = {
    				type: "PUT",
    				cache: false,
    			    contentType: false,
    			    processData: false,
    				url: updateUrl,
    				data: formData,
    				success: function(data, textStatus, xhr) {
    					if(typeof onSuccess != "undefined")
    						onSuccess(data);

    					//model.getInfo();
    				},
    				error: function(data, textStatus, xhr) {
    					if(typeof onError != "undefined")
    						onError(data);
    				}
    			}

    			$.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
    		},

    		confirmMapRequest: function(otherUsername, onSuccess, onError){
    			if(!otherUsername) return;

    			var mapUrl = MetacatUI.appModel.get("pendingMapsUrl") + encodeURIComponent(otherUsername),
    				model = this;

    			if(!onSuccess)
    				var onSuccess = function(){};
    			if(!onError)
    				var onError = function(){};

    			// ajax call to confirm map
    			var requestSettings = {
    				type: "PUT",
    				url: mapUrl,
    				success: function(data, textStatus, xhr) {
    					if(onSuccess)
    						onSuccess(data, textStatus, xhr);

    					//Get updated info
    					model.getInfo();
    				},
    				error: function(xhr, textStatus, error) {
    					if(onError)
    						onError(xhr, textStatus, error);
    				}
    			}
    			$.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
    		},

    		denyMapRequest: function(otherUsername, onSuccess, onError){
    			if(!otherUsername) return;

    			var mapUrl = MetacatUI.appModel.get("pendingMapsUrl") + encodeURIComponent(otherUsername),
    				model = this;

    			// ajax call to reject map
    			var requestSettings = {
    				type: "DELETE",
    				url: mapUrl,
    				success: function(data, textStatus, xhr) {
    					if(typeof onSuccess == "function")
    						onSuccess(data, textStatus, xhr);

    					model.getInfo();
    				},
    				error: function(xhr, textStatus, error) {
    					if(typeof onError == "function")
    						onError(xhr, textStatus, error);
    				}
    			}
    			$.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
    		},

    		addMap: function(otherUsername, onSuccess, onError){
    			if(!otherUsername) return;

    			var mapUrl = MetacatUI.appModel.get("pendingMapsUrl"),
    				model = this;

    			if(mapUrl.charAt(mapUrl.length-1) == "/"){
    				mapUrl = mapUrl.substring(0, mapUrl.length-1)
    			}

    			// ajax call to map
    			var requestSettings = {
    				type: "POST",
    				xhrFields: {
    					withCredentials: true
    				},
    				headers: {
    			        "Authorization": "Bearer " + this.get("token")
    			  },
    				url: mapUrl,
    				data: {
    					subject: otherUsername
    				},
    				success: function(data, textStatus, xhr) {
    					if(typeof onSuccess == "function")
    						onSuccess(data, textStatus, xhr);

    					model.getInfo();
    				},
    				error: function(xhr, textStatus, error) {

    					//Check if the username might have been spelled or formatted incorrectly
    					//ORCIDs, in particular, have different formats that we should account for
    					if(xhr.responseText.indexOf("LDAP: error code 32 - No Such Object") > -1 && model.isOrcid(otherUsername)){
    						if(otherUsername.length == 19)
    							model.addMap("http://orcid.org/" + otherUsername, onSuccess, onError);
    						else if(otherUsername.indexOf("https://orcid.org") == 0)
    							model.addMap(otherUsername.replace("https", "http"), onSuccess, onError);
    						else if(otherUsername.indexOf("orcid.org") == 0)
    							model.addMap("http://" + otherUsername, onSuccess, onError);
    						else if(otherUsername.indexOf("www.orcid.org") == 0)
    								model.addMap(otherUsername.replace("www.", "http://"), onSuccess, onError);
    						else if(otherUsername.indexOf("http://www.orcid.org") == 0)
    								model.addMap(otherUsername.replace("www.", ""), onSuccess, onError);
    						else if(otherUsername.indexOf("https://www.orcid.org") == 0)
    								model.addMap(otherUsername.replace("https://www.", "http://"), onSuccess, onError);
    						else if(typeof onError == "function")
    							onError(xhr, textStatus, error);
    					}
    					else{
    						if(typeof onError == "function")
    							onError(xhr, textStatus, error);
    				  }
    				}
    			}

    			$.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
    		},

    		removeMap: function(otherUsername, onSuccess, onError){
    			if(!otherUsername) return;

    			var mapUrl = MetacatUI.appModel.get("accountsMapsUrl") + encodeURIComponent(otherUsername),
    				model = this;

    			// ajax call to remove mapping
    			var requestSettings = {
    				type: "DELETE",
    				url: mapUrl,
    				success: function(data, textStatus, xhr) {
    					if(typeof onSuccess == "function")
    						onSuccess(data, textStatus, xhr);

    					model.getInfo();
    				},
    				error: function(xhr, textStatus, error) {
    					if(typeof onError == "function")
    						onError(xhr, textStatus, error);
    				}
    			}
    			$.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
    		},

    		failedLdapLogin: function(){
    			this.set("loggedIn", false);
    			this.set("checked", true);
    			this.set("ldapError", true);
    		},

    		pluckIdentityUsernames: function(){
    			var models = this.get("identities"),
    				usernames = [];

    			_.each(models, function(m){
    				usernames.push(m.get("username").toLowerCase());
    			});

    			this.set("identitiesUsernames", usernames);
    			this.trigger("change:identitiesUsernames");
    		},

    		createReadableUsername: function(){
    			if(!this.get("username")) return;

    			var username = this.get("username"),
    				readableUsername = username.substring(username.indexOf("=")+1, username.indexOf(",")) || username;

    			this.set("usernameReadable", readableUsername);
    		},

    		createAjaxSettings: function(){
    			if(!this.get("token")) return {}

    			return { xhrFields: {
    					withCredentials: true
    					},
    				  headers: {
    			        "Authorization": "Bearer " + this.get("token")
    				  }
    				}
    		},

        /**
        * Checks if this user has the quota to perform the given action
        * @param {string} action - The action to be performed
        * @param {string} customerGroup - The subject or identifier of the customer/membership group
        * to use this quota against
        */
        checkQuota: function(action, customerGroup){

          //Temporarily reset the quota so a trigger event is changed when the XHR is complete
          this.set("portalQuota", -1, {silent: true});

          //Start of temporary code
          //TODO: Replace this function with real code once the quota service is working
          this.set("portalQuota", 999);
          return;
          //End of temporary code

        /*  var model = this;

          var requestSettings = {
            url: "",
            type: "GET",
            success: function(data, textStatus, xhr) {
              model.set("portalQuota", data.remainingQuota);
            },
            error: function(xhr, textStatus, errorThrown) {
              model.set("portalQuota", 0);
            }
          }

          $.ajax(_.extend(requestSettings, this.createAjaxSettings()));
    */
        },

        /**
        * Checks if the user has authorization to perform the given action.
        */
        isAuthorizedCreatePortal: function(){

          //Reset the isAuthorized attribute silently so a change event is always triggered
          this.set("isAuthorizedCreatePortal", null, {silent: true});

          //If the user isn't logged in, set authorization to false
          if( !this.get("loggedIn") ){
            this.set("isAuthorizedCreatePortal", false);
            return;
          }

          //If creating portals has been disabled app-wide, then set to false
          if( MetacatUI.appModel.get("enableCreatePortals") === false ){
            this.set("isAuthorizedCreatePortal", false);
            return;
          }
          //If creating portals has been limited to only certain subjects, check if this user is one of them
          else if( MetacatUI.appModel.get("limitPortalsToSubjects").length ){
            if( !this.get("allIdentitiesAndGroups").length ){
              this.on("change:allIdentitiesAndGroups", this.isAuthorizedCreatePortal);
              return;
            }

            //Find the subjects that have access to create portals. Could be specific users or groups.
            var subjectsThatHaveAccess = _.intersection(MetacatUI.appModel.get("limitPortalsToSubjects"), this.get("allIdentitiesAndGroups"));
            if( !subjectsThatHaveAccess.length ){
              //If this user is not in the whitelist, set to false
              this.set("isAuthorizedCreatePortal", false);
            }
            else{
              //If this user is in the whitelist, set to true
              this.set("isAuthorizedCreatePortal", true);
            }
            return;
          }
          //If anyone is allowed to create a portal, check if they have the quota to create a portal
          else if( MetacatUI.appModel.get("enableBookkeeperServices") ){

            //Get the Quotas for this user
            var quotas = this.get("dataoneQuotas"),
                portalQuotas;

            //If the Quotas are still being fetched,
            if(quotas == this.defaults().dataoneQuotas && !quotas){
              this.on("change:dataoneQuotas", this.isAuthorizedCreatePortal);
              return;
            }
            else{
              portalQuotas = quotas.where({ quotaType: "portal" });
            }

            //If this user has no portal Quota at all, they are not auth to create a portal
            if( !portalQuotas ){
              this.set("isAuthorizedCreatePortal", false);
            }
            else{

              //Check that there is at least one Quota where the totalUsage < softLimit
              var hasRemainingUsage = _.some(portalQuotas, function(quota){
                return quota.get("totalUsage") < quota.get("softLimit");
              });

              //If there is remaining usage left in at least one Quota, then the user can create a portal
              if( hasRemainingUsage ){
                this.set("isAuthorizedCreatePortal", true);
              }
              //Otherwise they cannot create a new portal
              else{
                this.set("isAuthorizedCreatePortal", false);
              }
            }

            //@todoGet the admin group and force admins to have at least one quota left

          }
          else{
            //Default to letting people create portals
            this.set("isAuthorizedCreatePortal", true);
          }

        },

        /**
        * Given a list of user and/or group subjects, this function checks if this user
        * has an equivalent identity in that list, or is a member of a group in the list.
        * A single subject string can be passed instead of an array of subjects.
        * TODO: This needs to support nested group membership.
        * @param {string|string[]} subjects
        * @returns {boolean}
        */
        hasIdentityOverlap: function(subjects){

          try{
            //If only a single subject is given, put it in an array
            if( typeof subjects == "string" ){
              subjects = [subjects];
            }
            //If the subjects are not a string or an array, or if it's an empty array, exit this function.
            else if( !Array.isArray(subjects) || !subjects.length ){
              return false;
            }

            return _.intersection(this.get("allIdentitiesAndGroups"), subjects).length;
          }
          catch(e){
            console.error(e);
            return false;
          }

        },

        /**
        * Retrieve all the info about this user's DataONE Subscription
        */
        fetchSubscription: function(){

          //If Bookkeeper services are disabled, exit
          if( !MetacatUI.appModel.get("enableBookkeeperServices") ){
            return;
          }

          try{
            var thisUser = this;
            require(["collections/bookkeeper/Quotas", "models/bookkeeper/Subscription"], function(Quotas, Subscription){

              //Create a Quotas collection
              var quotas = new Quotas();

              //Create a Subscription model
              var subscription = new Subscription();

              if( MetacatUI.appModel.get("dataonePlusPreviewMode") ){
                //Create Quota models for preview mode
                quotas.add({
                  softLimit: MetacatUI.appModel.get("portalLimit"),
                  hardLimit: MetacatUI.appModel.get("portalLimit"),
                  quotaType: "portal",
                  unit: "portal",
                  subject: thisUser.get("username"),
                  subscription: subscription
                });

                //Default to all people being in trial mode
                subscription.set("status", "trialing");

                //Save a reference to the Quotas on this UserModel
                thisUser.set("dataoneQuotas", quotas);

                //Save a reference to the Subscriptioin on this UserModel
                thisUser.set("dataoneSubscription", subscription);

              }
              else{
                thisUser.listenToOnce(quotas, "reset", function(){
                  //Save a reference to the Quotas on this UserModel
                  thisUser.set("dataoneQuotas", quotas);
                });

                thisUser.listenToOnce(subscription, "sync", function(){
                  //Save a reference to the Subscriptioin on this UserModel
                  thisUser.set("dataoneSubscription", subscription);
                });

                //Fetch the Quotas
                quotas.fetch({ subscriber: thisUser.get("username") });

                //Fetch the Subscriptioin
                subscription.fetch();
              }

            });
          }
          catch(e){
            console.error("Couldn't get DataONE Subscription info. Proceeding as an unsubscribed user. ", e);
          }

        },

        /**
        * Gets the already-fetched Quotas for the User, filters down to the type given, and returns them.
        * @param {string} [type] - The Quota type to return
        * @returns {Quota[]} The filtered array of Quota models or an empty array, if none are found
        */
        getQuotas: function(type){
          var quotas = this.get("dataoneQuotas");

          if( quotas && type ){
            return quotas.where({ quotaType: type });
          }
          else if( quotas && !type ){
            return quotas;
          }
          else{
            return [];
          }
        },

    		reset: function(){
    			var defaults = _.omit(this.defaults(), ["searchModel", "searchResults"]);
    			this.set(defaults);
    		}
    	});

    	return UserModel;
    });