1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc.
  5  *
  6  * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at: https://www.zimbra.com/license
  9  * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15
 10  * have been added to cover use of software over a computer network and provide for limited attribution
 11  * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B.
 12  *
 13  * Software distributed under the License is distributed on an "AS IS" basis,
 14  * WITHOUT WARRANTY OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing rights and limitations under the License.
 16  * The Original Code is Zimbra Open Source Web Client.
 17  * The Initial Developer of the Original Code is Zimbra, Inc.  All rights to the Original Code were
 18  * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015.
 19  *
 20  * All portions of the code are Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 /**
 25  * @overview
 26  * 
 27  * This file defines an access control list and associated classes.
 28  *
 29  */
 30 
 31 /**
 32  * Creates an empty access control list (ACL).
 33  * @class
 34  * An access control list is a collection of access control entries (ACEs). Each entry contains
 35  * information about a certain permission applied by the current user to another user or users
 36  * for a particular type of action. So far, there are two types of rights that are managed in
 37  * this way:
 38  * 
 39  * <ul>
 40  * <li><b>viewFreeBusy</b> - governs whether other users may view this user's free/busy information</li>
 41  * <li><b>invite</b> - determines whether an invite from other users will automatically create a tentative appointment on this user's calendar</li>
 42  * </ul>
 43  * 
 44  * Note: that shared organizers ({@link ZmShare}) manage rights (read/write/manage) in their own way.
 45  * 
 46  * @author Conrad Damon
 47  * 
 48  * @param {Array}	aces		the list of {@link ZmAccessControlEntry} objects
 49  */
 50 ZmAccessControlList = function(aces) {
 51 	this._aces = {};
 52 }
 53 
 54 /**
 55  * Returns a string representation of the object.
 56  * 
 57  * @return		{String}		a string representation of the object
 58  */
 59 ZmAccessControlList.prototype.toString =
 60 function() {
 61 	return "ZmAccessControlList";
 62 };
 63 
 64 /**
 65  * Loads the list.
 66  * 
 67  * @param	{AjxCallback}	callback	the function to callback after the loaded
 68  * 
 69  * @private
 70  */
 71 ZmAccessControlList.prototype.load =
 72 function(callback) {
 73 	var jsonObj = {GetRightsRequest:{_jsns:"urn:zimbraAccount"}};
 74 	var respCallback = new AjxCallback(this, this._handleResponseLoad, [callback]);
 75 	appCtxt.getAppController().sendRequest({jsonObj:jsonObj, asyncMode:true, callback:respCallback});
 76 };
 77 
 78 /**
 79  * @private
 80  */
 81 ZmAccessControlList.prototype._handleResponseLoad =
 82 function(callback, result) {
 83 	var response = result.getResponse();
 84 	var aces = response.GetRightsResponse.ace;
 85 	if (aces && aces.length) {
 86 		for (var i = 0; i < aces.length; i++) {
 87 			this.add(ZmAccessControlEntry.createFromDom(aces[i]));
 88 		}
 89 	}
 90 	if (callback) {
 91 		callback.run();
 92 	}
 93 };
 94 
 95 /**
 96  * Gets the access control entry by right.
 97  * 
 98  * @param	{String}	right		the right
 99  * @return	{ZmAccessControlEntry}	the entry
100  */
101 ZmAccessControlList.prototype.getACLByRight =
102 function(right) {
103 	return this._aces[right];
104 };
105 
106 /**
107  * Gets the grantee type.
108  * 
109  * @param	{String}	right		the right
110  * @return	{constant}	the grantee type (see <code>ZmSetting.ACL_</code> constants)
111  * 
112  * @see		ZmSetting
113  */
114 ZmAccessControlList.prototype.getGranteeType =
115 function(right) {
116 	var aces = this._aces[right];
117 	var gt = ZmSetting.ACL_PUBLIC;
118 	
119 	var gtMap = {};
120 	if(aces && aces.length) {
121 		for (var i = 0; i < aces.length; i++) {
122 			var ace = aces[i];
123 			DBG.println("<font color=red>ace:</font>" + (ace.negative?"-":"") + ace.granteeType +"," +  ace.grantee );
124 			var aceGranteeType =  (ace.granteeType == ZmSetting.ACL_USER || ace.granteeType == ZmSetting.ACL_GROUP)  ? ZmSetting.ACL_USER : ace.granteeType;
125 			gtMap[aceGranteeType] = ace.negative ? -1 : 1;
126 		}
127 	}
128 	
129 	var allowPublic = (gtMap[ZmSetting.ACL_PUBLIC] == 1);
130 	var denyPublic  = (gtMap[ZmSetting.ACL_PUBLIC] == -1);
131 	var allowLocal  = (gtMap[ZmSetting.ACL_AUTH] == 1);
132 	var denyLocal   = (gtMap[ZmSetting.ACL_AUTH] == -1);
133     var allowDomainOnly   = (gtMap[ZmSetting.ACL_DOMAIN] == 1);
134 	
135 	var allowUser = (gtMap[ZmSetting.ACL_USER] == 1);
136 	var allowNone = (denyPublic || denyLocal) && (gtMap[ZmSetting.ACL_USER] == null);
137 				
138 	if(allowPublic) {
139 		return ZmSetting.ACL_PUBLIC;
140 	}
141 	
142 	if(allowLocal) {
143 		return ZmSetting.ACL_AUTH;
144 	}
145 	
146 	if(denyPublic) {
147 		if(allowLocal) {
148 			return ZmSetting.ACL_AUTH;
149 		}
150 	}
151 	
152 	if(allowUser) {
153 		return ZmSetting.ACL_USER;
154 	}
155 
156     if(allowDomainOnly) {
157 		return ZmSetting.ACL_DOMAIN;
158 	}
159 	
160 	if(allowNone) {
161 		return ZmSetting.ACL_NONE;
162 	}
163 	return gt;
164 };
165 
166 /**
167  * Gets the access control entry by grantee type.
168  * 
169  * @param	{String}	right	the right
170  * @param	{constant}	gt		the grantee type (see <code>ZmSetting.ACL_</code> constants)
171  * @return	{Array}	an array of {@link ZmAccessControlEntry} objects
172  */
173 ZmAccessControlList.prototype.getACLByGranteeType =
174 function(right, gt) {
175 	var aces = this._aces[right];
176 	var list = [];
177 	if (aces && aces.length) {
178 		for (var i = 0; i < aces.length; i++) {
179 			var ace = aces[i];
180 			if (ace.granteeType == gt) {
181 				list.push(ace);
182 			}
183 		}
184 	}
185 	list.sort();
186 	return list;
187 };
188 
189 /**
190  * Gets the grantees.
191  * 
192  * @param	{String}	right	the right
193  * @return	{Array}		an array of grantees
194  */
195 ZmAccessControlList.prototype.getGrantees =
196 function(right) {
197 	var aces = this._aces[right];
198 	var list = [];
199 	if (aces && aces.length) {
200 		for (var i = 0; i < aces.length; i++) {
201 			var ace = aces[i];
202 			if (ace.granteeType == ZmSetting.ACL_USER || ace.granteeType == ZmSetting.ACL_GROUP) {
203 				list.push(ace.grantee);
204 			}
205 		}
206 	}
207 	list.sort();
208 	return list;
209 };
210 
211 /**
212  * Gets the grantees info.
213  * 
214  * @param	{String}	right		the right
215  * @return	{Array}	an array of grantree info objects (obj.grantee, obj.zid)
216  */
217 ZmAccessControlList.prototype.getGranteesInfo =
218 function(right) {
219 	var aces = this._aces[right];
220 	var list = [];
221 	if (aces && aces.length) {
222 		for (var i = 0; i < aces.length; i++) {
223 			var ace = aces[i];
224 			if (ace.granteeType == ZmSetting.ACL_USER || ace.granteeType == ZmSetting.ACL_GROUP) {
225 				list.push({grantee: ace.grantee, zid: ace.zid});
226 			}
227 		}
228 	}
229 	list.sort(ZmAccessControlList.sortByGrantee);
230 	return list;
231 };
232 
233 /**
234  * Grants permissions on the access control entries.
235  * 
236  * @param	{Array}	aces		an array of {@link ZmAccessControlEntry} objects
237  * @param	{AjxCallback}	callback	the callback
238  * @param	{Boolean}	batchCmd	<code>true</code> to submit as a batch command
239  */
240 ZmAccessControlList.prototype.grant =
241 function(aces, callback, batchCmd) {
242 	this._setPerms(aces, false, callback, batchCmd);
243 };
244 
245 /**
246  * Revokes and denies permissions the access control entries.
247  * 
248  * @param	{Array}	aces		an array of {@link ZmAccessControlEntry} objects
249  * @param	{AjxCallback}	callback	the callback
250  * @param	{Boolean}	batchCmd	<code>true</code> to submit as a batch command
251  */
252 ZmAccessControlList.prototype.revoke =
253 function(aces, callback, batchCmd) {
254 	this._setPerms(aces, true, callback, batchCmd);
255 };
256 
257 /**
258  * Sets the permissions.
259  * 
260  * @param	{Array}	aces		an array of {@link ZmAccessControlEntry} objects
261  * @param	{Boolean}	revoke	<code>true</code> to deny; <code>false</code> to grant
262  * @param	{AjxCallback}	callback	the callback
263  * @param	{Boolean}	batchCmd	<code>true</code> to submit as a batch command
264  *
265  * @private
266  */
267 ZmAccessControlList.prototype._setPerms =
268 function(aces, revoke, callback, batchCmd) {
269 	var reqName = revoke ? "RevokeRightsRequest" : "GrantRightsRequest";
270 	var soapDoc = AjxSoapDoc.create(reqName, "urn:zimbraAccount");
271 	for (var i = 0; i < aces.length; i++) {
272 		var ace = aces[i];
273 		var aceNode = soapDoc.set("ace");
274 		aceNode.setAttribute("right", ace.right);
275 		aceNode.setAttribute("gt", ace.granteeType);
276 		if(ace.grantee) {
277 			aceNode.setAttribute("d", ace.grantee);
278 		}
279 		if (ace.zid) {
280 			aceNode.setAttribute("zid", ace.zid);
281 		}
282 		if (ace.negative) {
283 			aceNode.setAttribute("deny", 1);
284 		}
285 	}
286 	var respCallback = new AjxCallback(this, this._handleResponseSetPerms, [revoke, callback]);
287 	if (batchCmd) {
288 		batchCmd.addNewRequestParams(soapDoc, respCallback);
289 	} else {
290 		appCtxt.getAppController().sendRequest({soapDoc:soapDoc, asyncMode:true, callback:respCallback});
291 	}
292 };
293 
294 /**
295  * @private
296  */
297 ZmAccessControlList.prototype._handleResponseSetPerms =
298 function(revoke, callback, result) {
299 	var response = result.getResponse();
300 	var resp = revoke ? response.RevokeRightsResponse : response.GrantRightsResponse;
301 	var aces = resp && resp.ace;
302 	var aceList = [];
303 	if (aces && aces.length) {
304 		for (var i = 0; i < aces.length; i++) {
305 			var ace = ZmAccessControlEntry.createFromDom(aces[i]);
306 			aceList.push(ace);
307 			if (revoke) {
308 				this.remove(ace);
309 			} else {
310 				this.update(ace);
311 			}
312 		}
313 	}
314 
315 	if (callback) {
316 		callback.run(aceList);
317 	}
318 };
319 
320 /**
321  * Adds the entry to the ACL.
322  * 
323  * @param	{ZmAccessControlEntry}	ace	the entry to add
324  */
325 ZmAccessControlList.prototype.add =
326 function(ace) {
327 	if (!ace) { return; }
328 	var right = ace.right;
329 	if (!this._aces[right]) {
330 		this._aces[right] = [];
331 	}
332 	this._aces[right].push(ace);
333 };
334 
335 /**
336  * Removes the entry to the ACL.
337  * 
338  * @param	{ZmAccessControlEntry}	ace	the entry to remove
339  */
340 ZmAccessControlList.prototype.remove =
341 function(ace) {
342 	if (!ace) { return; }
343 	var list = this._aces[ace.right];
344 	var newList = [];
345 	if (list && list.length) {
346 		for (var i = 0; i < list.length; i++) {
347 			if (list[i].grantee != ace.grantee) {
348 				newList.push(list[i]);
349 			}
350 		}
351 	}
352 	this._aces[ace.right] = newList;
353 };
354 
355 /**
356  * Updates the entry to the ACL.
357  * 
358  * @param	{ZmAccessControlEntry}	ace	the entry to update
359  * @param	{Boolean}	removeEnty	not used
360  */
361 ZmAccessControlList.prototype.update =
362 function(ace, removeEntry) {
363 	if (!ace || !ace.right) { return; }
364 	var found = false;
365 	
366 	if(!this._aces[ace.right]) {
367 		this._aces[ace.right] = [];
368 	}
369 
370 	var list = this._aces[ace.right];	
371 	if (list.length) {
372 		//search for ace to update
373 		for (var i = 0; i < list.length; i++) {
374 			if ((list[i].grantee == ace.grantee) && (list[i].granteeType == ace.granteeType)) {
375 				this._aces[ace.right][i] = ace;
376 				found = true;
377 			}
378 		}
379 	}
380 	if(!found) {
381 		//adding new entry to ace list
382 		this._aces[ace.right].push(ace);
383 	}
384 };
385 
386 /**
387  * Cleans up the ACL.
388  * 
389  */
390 ZmAccessControlList.prototype.cleanup =
391 function() {
392 	this._aces = {};
393 };
394 
395 /**
396  * Sorts the ACL by grantee.
397  * 
398  * @param	{Hash}	a		grantee "a"
399  * @param	{String}	a.grantee	the grantee
400  * @param	{Hash}	b		grantee "b"
401  * @param	{Hash}	b.grantee		grantee "b"
402  * @return	{int}	0 if "a" and "b" are the same; 1 if "a" is before "b"; -1 if "b" is before "a"
403  */
404 ZmAccessControlList.sortByGrantee =
405 function(a, b) {
406 
407     var granteeA = a.grantee || "";
408     var granteeB = b.grantee || "";
409 
410     if (granteeA.toLowerCase() > granteeB.toLowerCase()) { return 1; }
411     if (granteeA.toLowerCase() < granteeB.toLowerCase()) { return -1; }
412     
413 	return 0;
414 };
415 
416 
417 /**
418  * Creates an access control entry.
419  * @class
420  * An access control entry encapsulates the permission information pertaining to a user or users
421  * regarding a certain right.
422  * 
423  * @param {Hash}	params		a hash of parameters
424  * @param	{String}	params.right		the action governed by this ACE
425  * @param	{String}	params.grantee		the account name of user or group permission applies to
426  * @param	{String}	params.zid			the ZID of grantee
427  * @param	{constant}	params.granteeType	type of grantee (see <code>ZmSetting.ACL_</code> constants)
428  * @param	{Boolean}	params.negative		if <code>true</code>, permission is denied by this ACE
429  * @see		ZmSetting
430  */
431 ZmAccessControlEntry =
432 function(params) {
433 	this.grantee = params.grantee;
434 	this.zid = params.zid;
435 	this.granteeType = params.granteeType;
436 	this.right = params.right;
437 	this.negative = params.negative;
438 }
439 
440 /**
441  * Returns a string representation of the object.
442  * 
443  * @return		{String}		a string representation of the object
444  */
445 ZmAccessControlEntry.prototype.toString =
446 function() {
447 	return "ZmAccessControlEntry";
448 };
449 
450 /**
451  * Creates an entry from the DOM object.
452  * 
453  * @param	{Hash}	obj		the DOM object
454  * @param	{String}	obj.right		the action governed by this ACE
455  * @param	{String}	obj.d		the account name of user or group permission applies to
456  * @param	{String}	obj.zid			the ZID of grantee
457  * @param	{constant}	obj.gt		the type of grantee (see <code>ZmSetting.ACL_</code> constants)
458  * @param	{Boolean}	obj.deny		if <code>1</code>, permission is denied by this ACE
459  * @return	{ZmAccessControlEntry}	the newly created entry
460  */
461 ZmAccessControlEntry.createFromDom =
462 function(obj) {
463 	var params = {};
464 	params.grantee = obj.d;
465 	params.granteeType = obj.gt;
466 	params.zid = obj.zid;
467 	params.right = obj.right;
468 	params.negative = (obj.deny == "1");
469 	
470 	return new ZmAccessControlEntry(params);
471 };
472