1 /**
  2  * @fileOverview
  3  * @author <a href="https://www.labkey.org">LabKey</a> (<a href="mailto:info@labkey.com">info@labkey.com</a>)
  4  * @license Copyright (c) 2008-2018 LabKey Corporation
  5  * <p/>
  6  * Licensed under the Apache License, Version 2.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
  9  * <p/>
 10  * http://www.apache.org/licenses/LICENSE-2.0
 11  * <p/>
 12  * Unless required by applicable law or agreed to in writing, software
 13  * distributed under the License is distributed on an "AS IS" BASIS,
 14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing permissions and
 16  * limitations under the License.
 17  * <p/>
 18  */
 19 
 20 /**
 21  * @namespace LabKey Security Reporting and Helper class.
 22  * This class provides several static methods and data members for
 23  * calling the security-related APIs, and interpreting the results.
 24  *            <p>Additional Documentation:
 25  *              <ul>
 26  *                  <li><a href="https://www.labkey.org/Documentation/wiki-page.view?name=security">LabKey Security and Accounts</a></li>
 27  *              </ul>
 28  *           </p>
 29  */
 30 LABKEY.Security = new function()
 31 {
 32     /*-- public methods --*/
 33     /** @scope LABKEY.Security */
 34     return {
 35 
 36         /**
 37          * A map of the various permission bits supported in the LabKey Server.
 38          * You can use these values with the hasPermission() method to test if
 39          * a user or group has a particular permission. The values in this map
 40          * are as follows:
 41          * <ul>
 42          * <li>read</li>
 43          * <li>insert</li>
 44          * <li>update</li>
 45          * <li>del</li>
 46          * <li>readOwn</li>
 47          * <li>updateOwn</li>
 48          * <li>deleteOwn</li>
 49          * <li>all</li>
 50          * </ul>
 51          * For example, to refer to the update permission, the syntax would be:<br/>
 52          * <pre><code>LABKEY.Security.permissions.update</code></pre>
 53          */
 54         permissions : {
 55             read: 1,
 56             insert: 2,
 57             update: 4,
 58             del: 8,
 59             readOwn: 16,
 60             updateOwn: 64,
 61             deleteOwn: 128,
 62             admin: 32768,
 63             all: 65535
 64         },
 65 
 66         /**
 67          * A map of commonly used effective permissions supported in the LabKey Server.
 68          * You can use these values with the hasEffectivePermission() method to test if
 69          * a user or group has a particular permission. The values in this map
 70          * are as follows:
 71          * <ul>
 72          * <li>read</li>
 73          * <li>insert</li>
 74          * <li>update</li>
 75          * <li>del</li>
 76          * <li>readOwn</li>
 77          * </ul>
 78          * For example, to refer to the update permission, the syntax would be:<br/>
 79          * <pre><code>LABKEY.Security.effectivePermissions.update</code></pre>
 80          */
 81         effectivePermissions : {
 82             insert: "org.labkey.api.security.permissions.InsertPermission",
 83             read: "org.labkey.api.security.permissions.ReadPermission",
 84             admin: "org.labkey.api.security.permissions.AdminPermission",
 85             del: "org.labkey.api.security.permissions.DeletePermission",
 86             readOwn: "org.labkey.api.security.permissions.ReadSomePermission",
 87             update: "org.labkey.api.security.permissions.UpdatePermission"
 88         },
 89 
 90         /**
 91          * A map of the various permission roles exposed in the user interface.
 92          * The members are as follows:
 93          * <ul>
 94          * <li>admin</li>
 95          * <li>editor</li>
 96          * <li>author</li>
 97          * <li>reader</li>
 98          * <li>restrictedReader</li>
 99          * <li>noPerms</li>
100          * </ul>
101          * For example, to refer to the author role, the syntax would be:<br/>
102          * <pre><code>LABKEY.Security.roles.author</code></pre>
103          */
104         roles : {
105             admin: 65535,
106             editor: 15,
107             author: 195,
108             reader: 1,
109             restrictedReader: 16,
110             submitter: 2,
111             noPerms: 0
112         },
113 
114         /**
115          * A map of the special system group ids. These ids are assigned by the system
116          * at initial startup and are constant across installations. The values in
117          * this map are as follows:
118          * <ul>
119          * <li>administrators</li>
120          * <li>users</li>
121          * <li>guests</li>
122          * <li>developers</li>
123          * </ul>
124          * For example, to refer to the administrators group, the syntax would be:<br/>
125          * <pre><code>LABKEY.Security.systemGroups.administrators</code></pre>
126          */
127         systemGroups : {
128             administrators: -1,
129             users: -2,
130             guests: -3,
131             developers: -4
132         },
133 
134         /**
135          * Get the effective permissions for all groups within the container, optionally
136          * recursing down the container hierarchy.
137          * @param config A configuration object with the following properties:
138          * @param {function} config.success A reference to a function to call with the API results. This
139          * function will be passed the following parameters:
140          * <ul>
141          * <li><b>groupPermsInfo:</b> an object containing properties about the container and group permissions.
142          * This object will have the following shape:
143          *  <ul>
144          *  <li>container
145          *      <ul>
146          *          <li>id: the container id</li>
147          *          <li>name: the container name</li>
148          *          <li>path: the container path</li>
149          *          <li>isInheritingPerms: true if the container is inheriting permissions from its parent</li>
150          *          <li>groups: an array of group objects, each of which will have the following properties:
151          *              <ul>
152          *                  <li>id: the group id</li>
153          *                  <li>name: the group's name</li>
154          *                  <li>type: the group's type ('g' for group, 'r' for role, 'm' for module-specific)</li>
155          *                  <li>roleLabel: (DEPRECATED) a description of the group's permission role. This will correspond
156          *                      to the visible labels shown on the permissions page (e.g., 'Admin (all permissions)'.</li>
157          *                  <li>role: (DEPRECATED) the group's role value (e.g., 'ADMIN'). Use this property for programmatic checks.</li>
158          *                  <li>permissions: (DEPRECATED) The group's effective permissions as a bit mask.
159          *                          Use this with the hasPermission() method to test for specific permissions.</li>
160          *                  <li>roles: An array of role unique names that this group is playing in the container. This replaces the
161          *                              existing roleLabel, role and permissions properties. Groups may now play multiple roles in a container
162          *                              and each role grants the user a set of permissions. Use the getRoles() method to retrieve information
163          *                              about the roles, including which permissions are granted by each role.
164          *                  </li>
165          *                  <li>effectivePermissions: An array of effective permission unique names the group has.</li>
166          *              </ul>
167          *          </li>
168          *          <li>children: if includeSubfolders was true, this will contain an array of objects, each of
169          *              which will have the same shape as the parent container object.</li>
170          *      </ul>
171          *  </li>
172          * </ul>
173          * </li>
174          * <li><b>response:</b> The XMLHttpResponse object</li>
175          * </ul>
176          * @param {function} [config.failure] A reference to a function to call when an error occurs. This
177          * function will be passed the following parameters:
178          * <ul>
179          * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
180          * <li><b>response:</b> The XMLHttpResponse object</li>
181          * </ul>
182          * @param {boolean} [config.includeSubfolders] Set to true to recurse down the subfolders (defaults to false)
183          * @param {string} [config.containerPath] An alternate container path to get permissions from. If not specified,
184          * the current container path will be used.
185          * @param {object} [config.scope] A scoping object for the success and error callback functions (default to this).
186          * @returns {Mixed} In client-side scripts, this method will return a transaction id
187          * for the async request that can be used to cancel the request
188          * (see <a href="http://dev.sencha.com/deploy/dev/docs/?class=Ext.data.Connection&member=abort" target="_blank">Ext.data.Connection.abort</a>).
189          * In server-side scripts, this method will return the JSON response object (first parameter of the success or failure callbacks.)
190          */
191         getGroupPermissions : function(config)
192         {
193             var params = {};
194             if (config.includeSubfolders != undefined)
195                 params.includeSubfolders = config.includeSubfolders;
196 
197             return LABKEY.Ajax.request({
198                 url: LABKEY.ActionURL.buildURL("security", "getGroupPerms", config.containerPath),
199                 method : 'GET',
200                 params: params,
201                 success: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnSuccess(config), config.scope),
202                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true)
203             });
204         },
205 
206         /**
207          * Returns true if the permission passed in 'perm' is on in the permissions
208          * set passed as 'perms'. This is a local function and does not make a call to the server.
209          * @param {int} perms The permission set, typically retrieved for a given user or group.
210          * @param {int} perm A specific permission bit to check for.
211          */
212         hasPermission : function(perms, perm)
213         {
214             return perms & perm;
215         },
216 
217         /**
218          * Returns true if the permission passed in 'desiredPermission' is in the permissions
219          * array passed as 'effectivePermissions'. This is a local function and does not make a call to the server.
220          * @param {Array} effectivePermissions The permission set, typically retrieved for a given user or group.
221          * @param {String} desiredPermission A specific permission bit to check for.
222          * @returns {boolean}
223          */
224         hasEffectivePermission : function(effectivePermissions, desiredPermission)
225         {
226             for (var i = 0; i < effectivePermissions.length; i++)
227             {
228                 if (effectivePermissions[i] == desiredPermission)
229                 {
230                     return true;
231                 }
232             }
233             return false;
234         },
235 
236         /**
237          * Returns the name of the security role represented by the permissions passed as 'perms'.
238          * The return value will be the name of a property in the LABKEY.Security.roles map.
239          * This is a local function, and does not make a call to the server.
240          * @param {int} perms The permissions set
241          * @deprecated Do not use this anymore. Use the roles array in the various responses and the
242          * getRoles() method to obtain extra information about each role.
243          */
244         getRole : function(perms)
245         {
246             for (var role in LABKEY.Security.roles)
247             {
248                 if (LABKEY.Security.roles.hasOwnProperty(role))
249                 {
250                     if (perms == LABKEY.Security.roles[role])
251                     {
252                         return role;
253                     }
254                 }
255             }
256         },
257 
258         /**
259          * Returns information about a user's permissions within a container. If you don't specify a user id, this
260          * will return information about the current user.
261          * @param config A configuration object containing the following properties
262          * @param {int} config.userId The id of the user. Omit to get the current user's information
263          * @param {string} config.userEmail The email address (user name) of the user (specify only userId or userEmail, not both)
264          * @param {Function} config.success A reference to a function to call with the API results. This
265          * function will be passed the following parameters:
266          * <ul>
267          * <li><b>userPermsInfo:</b> an object containing properties about the user's permissions.
268          * This object will have the following shape:
269          *  <ul>
270          *  <li>container: information about the container and the groups the user belongs to in that container
271          *      <ul>
272          *          <li>id: the container id</li>
273          *          <li>name: the container name</li>
274          *          <li>path: the container path</li>
275          *          <li>roleLabel: (DEPRECATED) a description of the user's permission role in this container. This will correspond
276          *               to the visible labels shown on the permissions page (e.g., 'Admin (all permissions)'.</li>
277          *          <li>role: (DEPRECATED) the user's role value (e.g., 'ADMIN'). Use this property for programmatic checks.</li>
278          *          <li>permissions: (DEPRECATED) The user's effective permissions in this container as a bit mask.
279          *               Use this with the hasPermission() method to test for specific permissions.</li>
280          *          <li>roles: An array of role unique names that this user is playing in the container. This replaces the
281          *               existing roleLabel, role and permissions properties. Users may now play multiple roles in a container
282          *               and each role grants the user a set of permissions. Use the getRoles() method to retrieve information
283          *               about the roles, including which permissions are granted by each role.
284          *          </li>
285          *          <li>effectivePermissions: An array of effective permission unique names the user has.</li>
286          *          <li>groups: an array of group objects to which the user belongs, each of which will have the following properties:
287          *              <ul>
288          *                  <li>id: the group id</li>
289          *                  <li>name: the group's name</li>
290          *                  <li>roleLabel: (DEPRECATED) a description of the group's permission role. This will correspond
291          *                      to the visible labels shown on the permissions page (e.g., 'Admin (all permissions)'.</li>
292          *                  <li>role: (DEPRECATED) the group's role value (e.g., 'ADMIN'). Use this property for programmatic checks.</li>
293          *                  <li>permissions: (DEPRECATED) The group's effective permissions as a bit mask.
294          *                          Use this with the hasPermission() method to test for specific permissions.</li>
295          *                  <li>roles: An array of role unique names that this group is playing in the container. This replaces the
296          *                              existing roleLabel, role and permissions properties. Groups may now play multiple roles in a container
297          *                              and each role grants the user a set of permissions. Use the getRoles() method to retrieve information
298          *                              about the roles, including which permissions are granted by each role.
299          *                  </li>
300          *                  <li>effectivePermissions: An array of effective permission unique names the group has.</li>
301          *              </ul>
302          *          </li>
303          *          <li>children: if includeSubfolders was true, this will contain an array of objects, each of
304          *              which will have the same shape as the parent container object.</li>
305          *      </ul>
306          *  </li>
307          *  <li>user: information about the requested user
308          *      <ul>
309          *          <li>userId: the user's id</li>
310          *          <li>displayName: the user's display name</li>
311          *      </ul>
312          *  </li>
313          * </ul>
314          * </li>
315          * <li><b>response:</b> The XMLHttpResponse object</li>
316          * </ul>
317          * @param {function} [config.failure] A reference to a function to call when an error occurs. This
318          * function will be passed the following parameters:
319          * <ul>
320          * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
321          * <li><b>response:</b> The XMLHttpResponse object</li>
322          * </ul>
323          * @param {boolean} [config.includeSubfolders] Set to true to recurse down the subfolders (defaults to false)
324          * @param {string} [config.containerPath] An alternate container path to get permissions from. If not specified,
325          * the current container path will be used.
326          * @param {Object} [config.scope] A scoping object for the success and error callback functions (default to this).
327          * @returns {Mixed} In client-side scripts, this method will return a transaction id
328          * for the async request that can be used to cancel the request
329          * (see <a href="http://dev.sencha.com/deploy/dev/docs/?class=Ext.data.Connection&member=abort" target="_blank">Ext.data.Connection.abort</a>).
330          * In server-side scripts, this method will return the JSON response object (first parameter of the success or failure callbacks.)
331          */
332         getUserPermissions : function(config)
333         {
334             var params = {};
335 
336             if(config.userId != undefined)
337                 params.userId = config.userId;
338             else if(config.userEmail != undefined)
339                 params.userEmail = config.userEmail;
340 
341             if(config.includeSubfolders != undefined)
342                 params.includeSubfolders = config.includeSubfolders;
343 
344             return LABKEY.Ajax.request({
345                 url: LABKEY.ActionURL.buildURL("security", "getUserPerms", config.containerPath),
346                 method : 'GET',
347                 params: params,
348                 success: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnSuccess(config), config.scope),
349                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true)
350             });
351         },
352 
353         /**
354          * Returns a list of users given selection criteria. This may be called by any logged-in user.
355          * @param config A configuration object containing the following properties
356          * @param {int} [config.groupId] The id of a project group for which you want the members.
357          * @param {string} [config.group] The name of a project group for which you want the members (specify groupId or group, not both).
358          * @param {string} [config.name] The first part of the user name, useful for user name completion. If specified,
359          * only users whose email address or display name starts with the value supplied will be returned.
360          * @param {boolean} [config.allMembers] This value is used to fetch all members in subgroups.
361          * @param {boolean} [config.active] This value is used to filter members based on activity (defaults to false).
362          * @param {Mixed} [config.permissions] A permissions string or an Array of permissions strings.
363          * If not present, no permission filtering occurs. If multiple permissions, all permissions are required.
364          * @param {function} config.success A reference to a function to call with the API results. This
365          * function will be passed the following parameters:
366          * <ul>
367          * <li><b>usersInfo:</b> an object with the following shape:
368          *  <ul>
369          *      <li>users: an array of user objects in the following form:
370          *          <ul>
371          *              <li>userId: the user's id</li>
372          *              <li>displayName: the user's display name</li>
373          *              <li>email: the user's email address</li>
374          *          </ul>
375          *      </li>
376          *      <li>container: the path of the requested container</li>
377          *  </ul>
378          * </li>
379          * <li><b>response:</b> The XMLHttpResponse object</li>
380          * </ul>
381          * @param {function} [config.failure] A reference to a function to call when an error occurs. This
382          * function will be passed the following parameters:
383          * <ul>
384          * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
385          * <li><b>response:</b> The XMLHttpResponse object</li>
386          * </ul>
387          * @param {string} [config.containerPath] An alternate container path to get permissions from. If not specified,
388          * the current container path will be used.
389          * @param {object} [config.scope] A scoping object for the success and error callback functions (default to this).
390          * @returns {Mixed} In client-side scripts, this method will return a transaction id
391          * for the async request that can be used to cancel the request
392          * (see <a href="http://dev.sencha.com/deploy/dev/docs/?class=Ext.data.Connection&member=abort" target="_blank">Ext.data.Connection.abort</a>).
393          * In server-side scripts, this method will return the JSON response object (first parameter of the success or failure callbacks.)
394          */
395         getUsers : function(config)
396         {
397             var params = {};
398             if(undefined != config.groupId)
399                 params.groupId = config.groupId;
400             else if(undefined != config.group)
401                 params.group = config.group;
402 
403             if(undefined != config.name)
404                 params.name = config.name;
405 
406             if(undefined != config.allMembers)
407                 params.allMembers = config.allMembers;
408 
409             if (undefined != config.active)
410                 params.active = config.active;
411 
412             if (undefined != config.permissions)
413             {
414                 if (!LABKEY.Utils.isArray(config.permissions))
415                 {
416                     config.permissions = [ config.permissions ];
417                 }
418                 params.permissions = config.permissions;
419             }
420 
421             return LABKEY.Ajax.request({
422                 url: LABKEY.ActionURL.buildURL("user", "getUsers.api", config.containerPath),
423                 method : 'GET',
424                 params: params,
425                 success: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnSuccess(config), config.scope),
426                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true)
427             });
428         },
429 
430         /**
431          * Creates a new container, which may be a project, folder, or workbook.
432          * @param config A configuration object with the following properties
433          * @param {String} [config.name] Required for projects or folders. The name of the container.
434          * @param {String} [config.title] The title of the container, used primarily for workbooks.
435          * @param {String} [config.description] The description of the container, used primarily for workbooks.
436          * @param {boolean} [config.isWorkbook] Whether this a workbook should be created. Defaults to false.
437          * @param {String} [config.folderType] The name of the folder type to be applied.
438          * @param {function} [config.success] A reference to a function to call with the API results. This
439          * function will be passed the following parameters:
440          * <ul>
441          * <li><b>containersInfo:</b> an object with the following properties:
442          *  <ul>
443          *      <li>id: the id of the requested container</li>
444          *      <li>name: the name of the requested container</li>
445          *      <li>path: the path of the requested container</li>
446          *      <li>sortOrder: the relative sort order of the requested container</li>
447          *      <li>description: an optional description for the container (may be null or missing)</li>
448          *      <li>title: an optional non-unique title for the container (may be null or missing)</li>
449          *      <li>isWorkbook: true if this container is a workbook. Workbooks do not appear in the left-hand project tree.</li>
450          *      <li>effectivePermissions: An array of effective permission unique names the group has.</li>
451          *  </ul>
452          * </li>
453          * <li><b>response:</b> The XMLHttpResponse object</li>
454          * </ul>
455          * @param {function} [config.failure] A reference to a function to call when an error occurs. This
456          * function will be passed the following parameters:
457          * <ul>
458          * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
459          * <li><b>response:</b> The XMLHttpResponse object</li>
460          * </ul>
461          * @param {string} [config.containerPath] An alternate container in which to create a new container. If not specified,
462          * the current container path will be used.
463          * @param {object} [config.scope] A scoping object for the success and error callback functions (default to this).
464          * @returns {Mixed} In client-side scripts, this method will return a transaction id
465          * for the async request that can be used to cancel the request
466          * (see <a href="http://dev.sencha.com/deploy/dev/docs/?class=Ext.data.Connection&member=abort" target="_blank">Ext.data.Connection.abort</a>).
467          * In server-side scripts, this method will return the JSON response object (first parameter of the success or failure callbacks.)
468          */
469         createContainer : function(config)
470         {
471             var params = {};
472             params.name = config.name;
473             params.title = config.title;
474             params.description = config.description;
475             params.isWorkbook = config.isWorkbook;
476             params.folderType = config.folderType;
477 
478             return LABKEY.Ajax.request({
479                 url: LABKEY.ActionURL.buildURL("core", "createContainer", config.containerPath),
480                 method : 'POST',
481                 jsonData : params,
482                 success: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnSuccess(config), config.scope),
483                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true)
484             });
485         },
486         
487         /**
488          * Deletes an existing container, which may be a project, folder, or workbook.
489          * @param config A configuration object with the following properties
490          * @param {function} [config.success] A reference to a function to call with the API results. This
491          * function will be passed the following parameter:
492          * <ul>
493          * <li><b>object:</b> Empty JavaScript object</li>
494          * <li><b>response:</b> The XMLHttpResponse object</li>
495          * </ul>
496          * @param {function} [config.failure] A reference to a function to call when an error occurs. This
497          * function will be passed the following parameters:
498          * <ul>
499          * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
500          * <li><b>response:</b> The XMLHttpResponse object</li>
501          * </ul>
502          * @param {string} [config.containerPath] The container which should be deleted. If not specified,
503          * the current container path will be deleted.
504          * @param {object} [config.scope] A scoping object for the success and error callback functions (default to this).
505          * @returns {Mixed} In client-side scripts, this method will return a transaction id
506          * for the async request that can be used to cancel the request
507          * (see <a href="http://dev.sencha.com/deploy/dev/docs/?class=Ext.data.Connection&member=abort" target="_blank">Ext.data.Connection.abort</a>).
508          * In server-side scripts, this method will return the JSON response object (first parameter of the success or failure callbacks.)
509          */
510         deleteContainer : function(config)
511         {
512             return LABKEY.Ajax.request({
513                 url: LABKEY.ActionURL.buildURL("core", "deleteContainer", config.containerPath),
514                 method : 'POST',
515                 jsonData : {},
516                 success: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnSuccess(config), config.scope),
517                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true)
518             });
519         },
520 
521         /**
522          * Moves an existing container, which may be a folder or workbook to be the subfolder of another folder and/or project.
523          * @param config A configuration object with the following properties
524          * @param {string} config.containerPath The current container path of the container that is going to be moved. Additionally, the container
525          * entity id is also valid.
526          * @param {string} config.destinationParent The container path of destination parent. Additionally, the destination parent entity id
527          * is also valid.
528          * @param {boolean} [config.addAlias] Add alias of current container path to container that is being moved (defaults to True).
529          * @param {function} [config.success] A reference to a function to call with the API results. This function will
530          * be passed the following parameters:
531          * <ul>
532          * <li><b>object:</b> Empty JavaScript object</li>
533          * <li><b>response:</b> The XMLHttpResponse object</li>
534          * </ul>
535          * @param {function} [config.failure] A reference to a function to call when an error occurs. This
536          * function will be passed the following parameters:
537          * <ul>
538          * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
539          * <li><b>response:</b> The XMLHttpResponse object</li>
540          * </ul>
541          * @param {object} [config.scope] A scoping object for the success and error callback functions (default to this).
542          */
543         moveContainer : function(config) {
544 
545             var params = {};
546             params.container = config.container || config.containerPath;
547             params.parent = config.destinationParent || config.parent || config.parentPath;
548             params.addAlias = true;
549 
550             if (!params.container) {
551                 console.error("'containerPath' must be specified for LABKEY.Security.moveContainer invocation.");
552                 return;
553             }
554 
555             if (!params.parent) {
556                 console.error("'parent' must be specified for LABKEY.Security.moveContainer invocation.");
557                 return;
558             }
559             
560             if (config.addAlias === false) {
561                 params.addAlias = false;
562             }
563             
564             return LABKEY.Ajax.request({
565                 url : LABKEY.ActionURL.buildURL("core", "moveContainer", params.container),
566                 method : 'POST',
567                 jsonData : params,
568                 success: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnSuccess(config), config.scope),
569                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true)
570             });
571         },
572 
573         /**
574          * Retrieves the full set of folder types that are available on the server.
575          * @param config A configuration object with the following properties
576          * @param {function} config.success A reference to a function to call with the API results. This
577          * function will be passed the following parameter:
578          * <ul>
579          * <li><b>folderTypes:</b> Map from folder type name to folder type object, which consists of the following properties:
580          *  <ul>
581          *      <li><b>name:</b> the cross-version stable name of the folder type</li>
582          *      <li><b>description:</b> a short description of the folder type</li>
583          *      <li><b>label:</b> the name that's shown to the user for this folder type</li>
584          *      <li><b>defaultModule:</b> name of the module that provides the home screen for this folder type</li>
585          *      <li><b>activeModules:</b> an array of module names that are automatically active for this folder type</li>
586          *      <li><b>workbookType:</b> boolean that indicates if this is specifically intended to use as a workbook type
587          *      <li><b>requiredWebParts:</b> an array of web parts that are part of this folder type and cannot be removed
588          *          <ul>
589          *              <li><b>name:</b> the name of the web part</li>
590          *              <li><b>properties:</b> a map of properties that are automatically set</li>
591          *          </ul>
592          *      </li>
593          *      <li><b>preferredWebParts:</b> an array of web parts that are part of this folder type but may be removed. Same structure as requiredWebParts</li>
594          *  </ul>
595          * </li>
596          * <li><b>response:</b> The XMLHttpResponse object</li>
597          * </ul>
598          * @param {function} [config.failure] A reference to a function to call when an error occurs. This
599          * function will be passed the following parameters:
600          * <ul>
601          * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
602          * <li><b>response:</b> The XMLHttpResponse object</li>
603          * </ul>
604          * @param {object} [config.scope] A scoping object for the success and error callback functions (default to this).
605          * @returns {Mixed} In client-side scripts, this method will return a transaction id
606          * for the async request that can be used to cancel the request
607          * (see <a href="http://dev.sencha.com/deploy/dev/docs/?class=Ext.data.Connection&member=abort" target="_blank">Ext.data.Connection.abort</a>).
608          * In server-side scripts, this method will return the JSON response object (first parameter of the success or failure callbacks.)
609          */
610         getFolderTypes : function(config)
611         {
612             return LABKEY.Ajax.request({
613                 url: LABKEY.ActionURL.buildURL("core", "getFolderTypes", config.containerPath),
614                 method : 'POST',
615                 jsonData : {},
616                 success: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnSuccess(config), config.scope),
617                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true)
618             });
619         },
620 
621 
622         /**
623          * Retrieves the full set of modules that are installed on the server.
624          * @param config A configuration object with the following properties
625          * @param {function} config.success A reference to a function to call with the API results. This
626          * function will be passed the following parameter:
627          * <ul>
628          * <li><b>folderType:</b> the folderType, based on the container used when calling this API</li>
629          * <li><b>modules:</b> Array of all modules present on this site, each of which consists of the following properties:
630          *  <ul>
631          *      <li><b>name:</b> the name of the module</li>
632          *      <li><b>required:</b> whether this module is required in the folder type specified above</li>
633          *      <li><b>tabName:</b> name of the tab associated with this module</li>
634          *      <li><b>active:</b> whether this module should be active for this container</li>
635          *      <li><b>enabled:</b> whether this module should be enabled by default for this container</li>
636          *  </ul>
637          * </li>
638          * <li><b>response:</b> The XMLHttpResponse object</li>
639          * </ul>
640          * @param {function} [config.failure] A reference to a function to call when an error occurs. This
641          * function will be passed the following parameters:
642          * <ul>
643          * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
644          * <li><b>response:</b> The XMLHttpResponse object</li>
645          * </ul>
646          * @param {object} [config.scope] A scoping object for the success and error callback functions (default to this).
647          * @returns {Mixed} In client-side scripts, this method will return a transaction id
648          * for the async request that can be used to cancel the request
649          * (see <a href="http://dev.sencha.com/deploy/dev/docs/?class=Ext.data.Connection&member=abort" target="_blank">Ext.data.Connection.abort</a>).
650          * In server-side scripts, this method will return the JSON response object (first parameter of the success or failure callbacks.)
651          */
652         getModules : function(config)
653         {
654             return LABKEY.Ajax.request({
655                 url: LABKEY.ActionURL.buildURL("admin", "getModules", config.containerPath),
656                 method : 'POST',
657                 jsonData : {},
658                 success: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnSuccess(config), config.scope),
659                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true)
660             });
661         },
662 
663 
664         /**
665          * Returns information about the specified container, including the user's current permissions within
666          * that container. If the includeSubfolders config option is set to true, it will also return information
667          * about all descendants the user is allowed to see.
668          * @param config A configuration object with the following properties
669          * @param {Mixed} [config.container] A container id or full-path String or an Array of container id/full-path Strings.  If not present, the current container is used.
670          * @param {boolean} [config.includeSubfolders] If set to true, the entire branch of containers will be returned.
671          * If false, only the immediate children of the starting container will be returned (defaults to false).
672          * @param {boolean} [config.includeEffectivePermissions] If set to false, the effective permissions for this container resource
673          * will not be included. (defaults to true)
674          * @param {int} [config.depth] May be used to control the depth of recursion if includeSubfolders is set to true.
675          * @param {Array} [config.moduleProperties] The names (Strings) of modules whose Module Property values should be included for each container.
676          * Use "*" to get the value of all Module Properties for all modules.
677          * @param {function} config.success A reference to a function to call with the API results. This
678          * function will be passed the following parameters:
679          * <ul>
680          * <li><b>containersInfo:</b>
681          * If <code>config.container</code> is an Array, an object with property
682          * <code>containers</code> and value Array of 'container info' is returned.
683          * If <code>config.container</code> is a String, a object of 'container info' is returned.
684          * <br>
685          * The 'container info' properties are as follows:
686          *  <ul>
687          *      <li>id: the id of the requested container</li>
688          *      <li>name: the name of the requested container</li>
689          *      <li>path: the path of the requested container</li>
690          *      <li>sortOrder: the relative sort order of the requested container</li>
691          *      <li>activeModules: an assay of the names (strings) of active modules in the container</li>
692          *      <li>folderType: the name (string) of the folder type, matched with getFolderTypes()</li>
693          *      <li>description: an optional description for the container (may be null or missing)</li>
694          *      <li>title: an optional non-unique title for the container (may be null or missing)</li>
695          *      <li>isWorkbook: true if this container is a workbook. Workbooks do not appear in the left-hand project tree.</li>
696          *      <li>isContainerTab: true if this container is a Container Tab. Container Tabs do not appear in the left-hand project tree.</li>
697          *      <li>userPermissions: (DEPRECATED) the permissions the current user has in the requested container.
698          *          Use this value with the hasPermission() method to test for specific permissions.</li>
699          *      <li>effectivePermissions: An array of effective permission unique names the group has.</li>
700          *      <li>children: if the includeSubfolders parameter was true, this will contain
701          *          an array of child container objects with the same shape as the parent object.</li>
702          *      <li>moduleProperties: if requested in the config object, an array of module properties for each included module:
703          *          <ul>
704          *              <li>name: the name of the Module Property.</li>
705          *              <li>moduleName: the name of the module specifying this property.</li>
706          *              <li>effectiveValue: the value of the property, including a value potentially inherited from parent containers.</li>
707          *              <li>value: the value of the property as set for this specific container</li>
708          *          </ul>
709          *      </li>
710          *  </ul>
711          * </li>
712          * <li><b>response:</b> The XMLHttpResponse object</li>
713          * </ul>
714          * @param {function} [config.failure] A reference to a function to call when an error occurs. This
715          * function will be passed the following parameters:
716          * <ul>
717          * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
718          * <li><b>response:</b> The XMLHttpResponse object</li>
719          * </ul>
720          * @param {string} [config.containerPath] An alternate container path to get permissions from. If not specified,
721          * the current container path will be used.
722          * @param {object} [config.scope] A scoping object for the success and error callback functions (default to this).
723          * @returns {Mixed} In client-side scripts, this method will return a transaction id
724          * for the async request that can be used to cancel the request
725          * (see <a href="http://dev.sencha.com/deploy/dev/docs/?class=Ext.data.Connection&member=abort" target="_blank">Ext.data.Connection.abort</a>).
726          * In server-side scripts, this method will return the JSON response object (first parameter of the success or failure callbacks.)
727          */
728         getContainers : function(config)
729         {
730             var params = {};
731             config = config || {};
732             if (undefined != config.container)
733             {
734                 if (LABKEY.Utils.isArray(config.container))
735                 {
736                     params.multipleContainers = true;
737                 }
738                 else
739                 {
740                     config.container = [ config.container ];
741                 }
742                 params.container = config.container;
743             }
744             if (undefined != config.includeSubfolders)
745                 params.includeSubfolders = config.includeSubfolders;
746             if (undefined != config.depth)
747                 params.depth = config.depth;
748             if (undefined != config.moduleProperties)
749                 params.moduleProperties = config.moduleProperties;
750             if (undefined != config.includeEffectivePermissions)
751                 params.includeEffectivePermissions = config.includeEffectivePermissions;
752 
753             return LABKEY.Ajax.request({
754                 url: LABKEY.ActionURL.buildURL("project", "getContainers", config.containerPath),
755                 method : 'GET',
756                 params: params,
757                 success: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnSuccess(config), config.scope),
758                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true)
759             });
760         },
761 
762         /**
763          * Exposes limited information about the current user. This property returns a JavaScript object
764          * with the following properties:
765          * <ul>
766          * <li>id: the user's unique id number</li>
767          * <li>displayName: the user's display name</li>
768          * <li>email: the user's email address</li>
769          * <li>canInsert: set to true if this user can insert data in the current folder</li>
770          * <li>canUpdate: set to true if this user can update data in the current folder</li>
771          * <li>canUpdateOwn: set to true if this user can update data this user created in the current folder</li>
772          * <li>canDelete: set to true if this user can delete data in the current folder</li>
773          * <li>canDeleteOwn: set to true if this user can delete data this user created in the current folder</li>
774          * <li>isAdmin: set to true if this user has admin permissions in the current folder</li>
775          * <li>isGuest: set to true if this user is the guest (anonymous) user</li>
776          * <li>isSystemAdmin: set to true if this user is a system administrator</li>
777          * <li>isDeveloper: set to true if this user is a developer</li>
778          * <li>isSignedIn: set to true if this user is signed in</li>
779          * </ul>
780          */
781         currentUser : LABKEY.user,
782 
783         /**
784          * Exposes limited information about the current container. This property returns a JavaScript object
785          * with the following properties:
786          * <ul>
787          * <li>id: the container's unique id (entityid)</li>
788          * <li>name: the name of the container</li>
789          * <li>path: the path of the current container</li>
790          * <li>type: the type of container, either project, folder or workbook</li>
791          * </ul>
792          */
793         currentContainer : LABKEY.container,
794 
795         /**
796          * Returns the set of groups the current user belongs to in the current container or specified containerPath.
797          * This may be called by any user.
798          * @param {object} config A configuration object with the following properties:
799          * @param {function} config.success A reference to a function that will be called with the results.
800          * This function will receive the follwing parameters:
801          * <ul>
802          * <li>results: an object with the following properties:
803          *  <ul>
804          *      <li>groups: An array of group information objects, each of which has the following properties:
805          *          <ul>
806          *              <li>id: The unique id of the group.</li>
807          *              <li>name: The name of the group.</li>
808          *              <li>isProjectGroup: true if this group is defined at the project level.</li>
809          *              <li>isSystemGroup: true if this group is defined at the system level.</li>
810          *          </ul>
811          *      </li>
812          *  </ul>
813          * </li>
814          * </ul>
815          * @param {function} [config.failure] A reference to a function to call when an error occurs. This
816          * function will be passed the following parameters:
817          * <ul>
818          * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
819          * <li><b>response:</b> The XMLHttpResponse object</li>
820          * </ul>
821          * @param {string} [config.containerPath] An alternate container path to get permissions from. If not specified,
822          * the current container path will be used.
823          * @param {object} [config.scope] A scoping object for the success and error callback functions (default to this).
824          * @returns {Mixed} In client-side scripts, this method will return a transaction id
825          * for the async request that can be used to cancel the request
826          * (see <a href="http://dev.sencha.com/deploy/dev/docs/?class=Ext.data.Connection&member=abort" target="_blank">Ext.data.Connection.abort</a>).
827          * In server-side scripts, this method will return the JSON response object (first parameter of the success or failure callbacks.)
828          */
829         getGroupsForCurrentUser : function(config)
830         {
831             return LABKEY.Ajax.request({
832                 url: LABKEY.ActionURL.buildURL("security", "getGroupsForCurrentUser", config.containerPath),
833                 method: 'GET',
834                 success: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnSuccess(config), config.scope),
835                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true)
836             });
837         },
838 
839         /**
840          * Ensures that the current user is logged in.
841          * @param {object} config A configuration object with the following properties:
842          * @param {boolean} [config.useSiteLoginPage] Set to true to redirect the browser to the normal site login page.
843          * After the user logs in, the browser will be redirected back to the current page, and the current user information
844          * will be available via {@link LABKEY.Security.currentUser}. If omitted or set to false, this function
845          * will attempt to login via an AJAX request, which will cause the browser to display the basic authentication
846          * dialog. After the user logs in successfully, the config.success function will be called.
847          * @param {function} config.success A reference to a function that will be called after a successful login.
848          * It is passed the following parameters:
849          * <ul>
850          *  <li>results: an object with the following properties:
851          *      <ul>
852          *          <li>currentUser: a reference to the current user. See {@link LABKEY.Security.currentUser} for more details.</li>
853          *      </ul>
854          *  </li>
855          * </ul>
856          * Note that if the current user is already logged in, the successCallback function will be called immediately,
857          * passing the current user information from {@link LABKEY.Security.currentUser}.
858          * @param {function} [config.failure] A reference to a function to call when an error occurs. This
859          * function will be passed the following parameters:
860          * <ul>
861          * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
862          * <li><b>response:</b> The XMLHttpResponse object</li>
863          * </ul>
864          * @param {object} [config.scope] A scoping object for the success and error callback functions (default to this).
865          * @param {boolean} [config.force] Set to true to force a login even if the user is already logged in.
866          * This is useful for keeping a session alive during a long-lived page. To do so, call this function
867          * with config.force set to true, and config.useSiteLoginPage to false (or omit).
868          * @returns {Mixed} In client-side scripts, this method will return a transaction id
869          * for the async request that can be used to cancel the request
870          * (see <a href="http://dev.sencha.com/deploy/dev/docs/?class=Ext.data.Connection&member=abort" target="_blank">Ext.data.Connection.abort</a>).
871          * In server-side scripts, this method will return the JSON response object (first parameter of the success or failure callbacks.)
872          */
873         ensureLogin : function(config)
874         {
875             if (LABKEY.Security.currentUser.isGuest || config.force)
876             {
877                 if (config.useSiteLoginPage)
878                 {
879                     window.location = LABKEY.ActionURL.buildURL("login", "login") + "?returnUrl=" + window.location;
880                 }
881                 else
882                 {
883                     return LABKEY.Ajax.request({
884                         url: LABKEY.ActionURL.buildURL("security", "ensureLogin"),
885                         method: 'GET',
886                         success: LABKEY.Utils.getCallbackWrapper(function(data, req){
887                             if(data.currentUser)
888                                 LABKEY.Security.currentUser = data.currentUser;
889 
890                             if(LABKEY.Utils.getOnSuccess(config))
891                                 LABKEY.Utils.getOnSuccess(config).call(config.scope || this, data, req);
892 
893                         }, this),
894                         failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true)
895                     });
896                 }
897             }
898             else
899             {
900                 LABKEY.Utils.getOnSuccess(config).call(config.scope || this, {currentUser: LABKEY.Security.currentUser});
901             }
902         },
903 
904         /**
905          * Returns the tree of securable resources from the current container downward
906          * @param config A configuration object with the following properties:
907          * @param {Boolean} config.includeSubfolders If set to true, the response will include subfolders
908          * and their contained securable resources (defaults to false).
909          * @param {Boolean} config.includeEffectivePermissions If set to true, the response will include the
910          * list of effective permissions (unique names) the current user has to each resource (defaults to false).
911          * These permissions are calcualted based on the current user's group memberships and role assignments, and
912          * represent the actual permissions the user has to these resources at the time of the API call.
913          * @param {Function} config.success A reference to a function to call with the API results. This
914          * function will be passed the following parameters:
915          * <ul>
916          * <li><b>data:</b> an object with a property named "resources" which contains the root resource.
917          * Each resource has the following properties:
918          *  <ul>
919          *      <li>id: The unique id of the resource (String, typically a GUID).</li>
920          *      <li>name: The name of the resource suitable for showing to a user.</li>
921          *      <li>description: The description of the reosurce.</li>
922          *      <li>resourceClass: The fully-qualified Java class name of the resource.</li>
923          *      <li>sourceModule: The name of the module in which the resource is defined and managed</li>
924          *      <li>parentId: The parent resource's id (may be omitted if no parent)</li>
925          *      <li>parentContainerPath: The parent resource's container path (may be omitted if no parent)</li>
926          *      <li>children: An array of child resource objects.</li>
927          *      <li>effectivePermissions: An array of permission unique names the current user has on the resource. This will be
928          *          present only if the includeEffectivePermissions property was set to true on the config object.</li>
929          *  </ul>
930          * </li>
931          * <li><b>response:</b> The XMLHttpResponse object</li>
932          * </ul>
933          * @param {Function} [config.failure] A reference to a function to call when an error occurs. This
934          * function will be passed the following parameters:
935          * <ul>
936          * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
937          * <li><b>response:</b> The XMLHttpResponse object</li>
938          * </ul>
939          * @param {object} [config.scope] A scoping object for the success and error callback functions (default to this).
940          * @param {string} [config.containerPath] An alternate container path to get permissions from. If not specified,
941          * the current container path will be used.
942          * @returns {Mixed} In client-side scripts, this method will return a transaction id
943          * for the async request that can be used to cancel the request
944          * (see <a href="http://dev.sencha.com/deploy/dev/docs/?class=Ext.data.Connection&member=abort" target="_blank">Ext.data.Connection.abort</a>).
945          * In server-side scripts, this method will return the JSON response object (first parameter of the success or failure callbacks.)
946          */
947         getSecurableResources : function(config)
948         {
949             var params = {};
950             if(undefined != config.includeSubfolders)
951                 params.includeSubfolders = config.includeSubfolders;
952             if(undefined != config.includeEffectivePermissions)
953                 params.includeEffectivePermissions = config.includeEffectivePermissions;
954 
955             return LABKEY.Ajax.request({
956                 url: LABKEY.ActionURL.buildURL("security", "getSecurableResources", config.containerPath),
957                 method: "GET",
958                 params: params,
959                 success: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnSuccess(config), config.scope),
960                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true)
961             });
962         },
963 
964         /*
965          * EXPERIMENTAL! gets permissions for a set of tables within a schema.
966          * Currently only study tables have individual permissions so only works on the study schema
967          * @param config A configuration object with the following properties:
968          * @param {object} [config.scope] A scoping object for the success and error callback functions (default to this).
969          * @param {object} [config.schemaName] Name of the schema to retrieve information on.
970          * @param {string} [config.containerPath] An alternate container path to get permissions from. If not specified,
971          * the current container path will be used.
972          * @param {Function} config.success A reference to a function to call with the API results. This
973          * function will be passed the following parameters:
974          * <ul>
975          * <li><b>data:</b> an object with a property named "schemas" which contains a queries object.
976          * The queries object property with the name of each table/queries. So
977          * schemas.study.queries.Demographics would yield the following results
978          *  <ul>
979          *      <li>id: The unique id of the resource (String, typically a GUID).</li>
980          *      <li>name: The name of the resource suitable for showing to a user.</li>
981          *      <li>description: The description of the reosurce.</li>
982          *      <li>resourceClass: The fully-qualified Java class name of the resource.</li>
983          *      <li>sourceModule: The name of the module in which the resource is defined and managed</li>
984          *      <li>parentId: The parent resource's id (may be omitted if no parent)</li>
985          *      <li>parentContainerPath: The parent resource's container path (may be omitted if no parent)</li>
986          *      <li>children: An array of child resource objects.</li>
987          *      <li>effectivePermissions: An array of permission unique names the current user has on the resource. This will be
988          *          present only if the includeEffectivePermissions property was set to true on the config object.</li>
989          *      <li>permissionMap: An object with one property per effectivePermission allowed the user. This restates
990          *          effectivePermissions in a slightly more convenient way
991          *  </ul>
992          * </li>
993          * <li><b>response:</b> The XMLHttpResponse object</li>
994          * </ul>
995          * @param {Function} [config.failure] A reference to a function to call when an error occurs. This
996          * function will be passed the following parameters:
997          * <ul>
998          * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
999          * <li><b>response:</b> The XMLHttpResponse object</li>
1000          * </ul>
1001          * @returns {Mixed} In client-side scripts, this method will return a transaction id
1002          * for the async request that can be used to cancel the request
1003          * (see <a href="http://dev.sencha.com/deploy/dev/docs/?class=Ext.data.Connection&member=abort" target="_blank">Ext.data.Connection.abort</a>).
1004          * In server-side scripts, this method will return the JSON response object (first parameter of the success or failure callbacks.)
1005          */
1006         getSchemaPermissions: function(config) {
1007             if (config.schemaName && config.schemaName != "study")
1008                 throw "Method only works for the study schema";
1009 
1010             var successCallback = function(json, response) {
1011 
1012                 //First lets make sure there is a study in here.
1013                 var studyResource = null;
1014                 for (var i = 0; i < json.resources.children.length; i++)
1015                 {
1016                     var resource = json.resources.children[i];
1017                     if (resource.resourceClass == "org.labkey.study.model.StudyImpl"){
1018                         studyResource = resource;
1019                         break;
1020                     }
1021                 }
1022 
1023                 if (null == studyResource)
1024                 {
1025                     config.failure.apply(config.scope || this, [{description:"No study found in container."}, response]);
1026                     return;
1027                 }
1028 
1029                 var result = {queries:{}}, dataset;
1030 
1031                 for (i=0; i < studyResource.children.length; i++) {
1032                     dataset = studyResource.children[i];
1033                     result.queries[dataset.name] = dataset;
1034                     dataset.permissionMap = {};
1035                     for (var j=0; j < dataset.effectivePermissions.length; j++) {
1036                         dataset.permissionMap[dataset.effectivePermissions[j]] = true;
1037                     }
1038                 }
1039 
1040                 config.success.apply(config.scope || this, [{schemas:{study:result}}, response]);
1041             };
1042 
1043             var myConfig = {};
1044             if (config) {
1045                 for (var c in config) {
1046                     if (config.hasOwnProperty(c)) {
1047                         myConfig[c] = config[c];
1048                     }
1049                 }
1050             }
1051             myConfig.includeEffectivePermissions = true;
1052             myConfig.success = successCallback;
1053             return LABKEY.Security.getSecurableResources(myConfig);
1054         },
1055 
1056         /**
1057          * Returns the complete set of roles defined on the server, along with the permissions each role grants.
1058          * @param config A configuration object with the following properties:
1059          * @param {Function} config.success A reference to a function to call with the API results. This
1060          * function will be passed the following parameters:
1061          * <ul>
1062          * <li><b>roles:</b> An array of role objects, each of which has the following properties:
1063          *  <ul>
1064          *      <li>uniqueName: The unique name of the resource (String, typically a fully-qualified class name).</li>
1065          *      <li>name: The name of the role suitable for showing to a user.</li>
1066          *      <li>description: The description of the role.</li>
1067          *      <li>sourceModule: The name of the module in which the role is defined.</li>
1068          *      <li>permissions: An array of permissions the role grants. Each permission has the following properties:
1069          *          <ul>
1070          *              <li>uniqueName: The unique name of the permission (String, typically a fully-qualified class name).</li>
1071          *              <li>name: The name of the permission.</li>
1072          *              <li>description: A description of the permission.</li>
1073          *              <li>sourceModule: The module in which the permission is defined.</li>
1074          *          </ul>
1075          *      </li>
1076          *  </ul>
1077          * </li>
1078          * <li><b>response:</b> The XMLHttpResponse object</li>
1079          * </ul>
1080          * @param {Function} [config.failure] A reference to a function to call when an error occurs. This
1081          * function will be passed the following parameters:
1082          * <ul>
1083          * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
1084          * <li><b>response:</b> The XMLHttpResponse object</li>
1085          * </ul>
1086          * @param {object} [config.scope] A scoping object for the success and error callback functions (default to this).
1087          * @returns {Mixed} In client-side scripts, this method will return a transaction id
1088          * for the async request that can be used to cancel the request
1089          * (see <a href="http://dev.sencha.com/deploy/dev/docs/?class=Ext.data.Connection&member=abort" target="_blank">Ext.data.Connection.abort</a>).
1090          * In server-side scripts, this method will return the JSON response object (first parameter of the success or failure callbacks.)
1091          */
1092         getRoles : function(config)
1093         {
1094             return LABKEY.Ajax.request({
1095                 url: LABKEY.ActionURL.buildURL("security", "getRoles"),
1096                 method: "GET",
1097                 success: LABKEY.Utils.getCallbackWrapper(function(data, req){
1098 
1099                     //roles and perms are returned in two separate blocks for efficiency
1100                     var idx;
1101                     var permMap = {};
1102                     var perm;
1103                     for(idx = 0; idx < data.permissions.length; ++idx)
1104                     {
1105                         perm = data.permissions[idx];
1106                         permMap[perm.uniqueName] = perm;
1107                     }
1108 
1109                     var idxPerm;
1110                     var role;
1111                     for(idx = 0; idx < data.roles.length; ++idx)
1112                     {
1113                         role = data.roles[idx];
1114                         for(idxPerm = 0; idxPerm < role.permissions.length; ++idxPerm)
1115                         {
1116                             role.permissions[idxPerm] = permMap[role.permissions[idxPerm]];
1117                         }
1118                     }
1119 
1120                     var fn = LABKEY.Utils.getOnSuccess(config);
1121                     if (fn)
1122                         fn.call(config.scope || this, data.roles, req);
1123 
1124                 }, this),
1125                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true)
1126             });
1127         },
1128 
1129         /**
1130          * Retrieves the security policy for the requested resource id. Note that this will return the
1131          * policy in effect for this resource, which might be the policy from a parent resource if there
1132          * is no explicit policy set on the requested resource. Use the isInherited method on the returned
1133          * LABKEY.SecurityPolicy object to determine if the policy is inherited or not.
1134          * Note that the securable resource must be within the current container, or one of its descendants.
1135          * @param config A configuration object with the following properties
1136          * @param {String} config.resourceId The unique id of the securable resource.
1137          * @param {Function} config.success A reference to a function to call with the API results. This
1138          * function will be passed the following parameters:
1139          * <ul>
1140          * <li><b>policy:</b> an instance of a LABKEY.SecurityPolicy object.</li>
1141          * <li><b>relevantRoles:</b> an array of role ids that are relevant for the given resource.</li>
1142          * <li><b>response:</b> The XMLHttpResponse object</li>
1143          * </ul>
1144          * @param {Function} [config.failure] A reference to a function to call when an error occurs. This
1145          * function will be passed the following parameters:
1146          * <ul>
1147          * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
1148          * <li><b>response:</b> The XMLHttpResponse object</li>
1149          * </ul>
1150          * @param {object} [config.scope] A scoping object for the success and error callback functions (default to this).
1151          * @param {string} [config.containerPath] An alternate container path to get permissions from. If not specified,
1152          * the current container path will be used.
1153          * @returns {Mixed} In client-side scripts, this method will return a transaction id
1154          * for the async request that can be used to cancel the request
1155          * (see <a href="http://dev.sencha.com/deploy/dev/docs/?class=Ext.data.Connection&member=abort" target="_blank">Ext.data.Connection.abort</a>).
1156          * In server-side scripts, this method will return the JSON response object (first parameter of the success or failure callbacks.)
1157          */
1158         getPolicy : function(config)
1159         {
1160             return LABKEY.Ajax.request({
1161                 url: LABKEY.ActionURL.buildURL("security", "getPolicy", config.containerPath),
1162                 method: "GET",
1163                 params: { resourceId: config.resourceId },
1164                 success: LABKEY.Utils.getCallbackWrapper(function(data, req){
1165                     data.policy.requestedResourceId = config.resourceId;
1166                     var policy = new LABKEY.SecurityPolicy(data.policy);
1167                     LABKEY.Utils.getOnSuccess(config).call(config.scope || this, policy, data.relevantRoles, req);
1168                 }, this),
1169                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true)
1170             });
1171         },
1172 
1173         /**
1174          * Deletes the security policy for the requested resource id. This will cause resource to inherit its
1175          * security policy from its parent resource.
1176          * @param config A configuration object with the following properties
1177          * @param {String} config.resourceId The unique id of the securable resource.
1178          * @param {Function} config.success A reference to a function to call with the API results. This
1179          * function will be passed the following parameters:
1180          * <ul>
1181          * <li><b>data:</b> a simple object with one property called 'success' which will be set to true.</li>
1182          * <li><b>response:</b> The XMLHttpResponse object</li>
1183          * </ul>
1184          * @param {Function} [config.failure] A reference to a function to call when an error occurs. This
1185          * function will be passed the following parameters:
1186          * <ul>
1187          * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
1188          * <li><b>response:</b> The XMLHttpResponse object</li>
1189          * </ul>
1190          * @param {Object} [config.scope] A scoping object for the success and error callback functions (default to this).
1191          * @param {string} [config.containerPath] An alternate container path to get permissions from. If not specified,
1192          * the current container path will be used.
1193          * @returns {Mixed} In client-side scripts, this method will return a transaction id
1194          * for the async request that can be used to cancel the request
1195          * (see <a href="http://dev.sencha.com/deploy/dev/docs/?class=Ext.data.Connection&member=abort" target="_blank">Ext.data.Connection.abort</a>).
1196          * In server-side scripts, this method will return the JSON response object (first parameter of the success or failure callbacks.)
1197          */
1198         deletePolicy : function(config)
1199         {
1200             return LABKEY.Ajax.request({
1201                 url: LABKEY.ActionURL.buildURL("security", "deletePolicy", config.containerPath),
1202                 method: "POST",
1203                 success: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnSuccess(config), config.scope),
1204                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true),
1205                 jsonData: { resourceId: config.resourceId },
1206                 headers : {
1207                     'Content-Type' : 'application/json'
1208                 }
1209             });
1210         },
1211 
1212         /**
1213          * Saves the supplied security policy. This object should be a LABKEY.SecurityPolicy object. This
1214          * method will completely overwrite the existing policy for the resource. If another user has changed
1215          * the policy in between the time it was selected and this method is called, the save will fail with
1216          * an optimistic concurrency exception. To force your policy over the other, call the setModified()
1217          * method on the policy passing null.
1218          * @param config A configuration object with the following properties
1219          * @param {String} config.policy The LABKEY.SecurityPolicy object
1220          * @param {Function} config.success A reference to a function to call with the API results. This
1221          * function will be passed the following parameters:
1222          * <ul>
1223          * <li><b>data:</b> a simple object with one property called 'success' which will be set to true.</li>
1224          * <li><b>response:</b> The XMLHttpResponse object</li>
1225          * </ul>
1226          * @param {Function} [config.failure] A reference to a function to call when an error occurs. This
1227          * function will be passed the following parameters:
1228          * <ul>
1229          * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
1230          * <li><b>response:</b> The XMLHttpResponse object</li>
1231          * </ul>
1232          * @param {Object} [config.scope] A scoping object for the success and error callback functions (default to this).
1233          * @param {string} [config.containerPath] An alternate container path to get permissions from. If not specified,
1234          * the current container path will be used.
1235          * @returns {Mixed} In client-side scripts, this method will return a transaction id
1236          * for the async request that can be used to cancel the request
1237          * (see <a href="http://dev.sencha.com/deploy/dev/docs/?class=Ext.data.Connection&member=abort" target="_blank">Ext.data.Connection.abort</a>).
1238          * In server-side scripts, this method will return the JSON response object (first parameter of the success or failure callbacks.)
1239          */
1240         savePolicy : function(config)
1241         {
1242             return LABKEY.Ajax.request({
1243                 url: LABKEY.ActionURL.buildURL("security", "savePolicy", config.containerPath),
1244                 method : 'POST',
1245                 success: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnSuccess(config), config.scope),
1246                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true),
1247                 jsonData : config.policy.policy,
1248                 headers : {
1249                     'Content-Type' : 'application/json'
1250                 }
1251             });
1252         },
1253 
1254         /**
1255          * Creates a new group. The new group will be created at the project level when the current
1256          * container is a folder or project, or will be created at the system level if the current
1257          * container is the root.
1258          * @param config A configuration object with the following properties:
1259          * @param {String} config.groupName The name of the group to create
1260          * @param {Function} config.success A reference to a function to call with the API results. This
1261          * function will be passed the following parameters:
1262          * <ul>
1263          * <li><b>data:</b> a simple object with two properties: id and name (the new group id and name respectively)</li>
1264          * <li><b>response:</b> The XMLHttpResponse object</li>
1265          * </ul>
1266          * @param {Function} [config.failure] A reference to a function to call when an error occurs. This
1267          * function will be passed the following parameters:
1268          * <ul>
1269          * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
1270          * <li><b>response:</b> The XMLHttpResponse object</li>
1271          * </ul>
1272          * @param {Object} [config.scope] A scoping object for the success and error callback functions (default to this).
1273          * @param {string} [config.containerPath] An alternate container path to get permissions from. If not specified,
1274          * the current container path will be used.
1275          * @returns {Mixed} In client-side scripts, this method will return a transaction id
1276          * for the async request that can be used to cancel the request
1277          * (see <a href="http://dev.sencha.com/deploy/dev/docs/?class=Ext.data.Connection&member=abort" target="_blank">Ext.data.Connection.abort</a>).
1278          * In server-side scripts, this method will return the JSON response object (first parameter of the success or failure callbacks.)
1279          */
1280         createGroup : function(config)
1281         {
1282             var params = {name: config.groupName};
1283             return LABKEY.Ajax.request({
1284                 url: LABKEY.ActionURL.buildURL("security", "createGroup", config.containerPath),
1285                 method: "POST",
1286                 success: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnSuccess(config), config.scope),
1287                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true),
1288                 jsonData: params,
1289                 headers : {
1290                     'Content-Type' : 'application/json'
1291                 }
1292             });
1293         },
1294 
1295         /**
1296          * Deletes a group.
1297          * @param config A configuration object with the following properties:
1298          * @param {int} config.groupId The id of the group to delete
1299          * @param {Function} config.success A reference to a function to call with the API results. This
1300          * function will be passed the following parameters:
1301          * <ul>
1302          * <li><b>data:</b> a simple object with a property named "deleted" that contains the deleted group id.</li>
1303          * <li><b>response:</b> The XMLHttpResponse object</li>
1304          * </ul>
1305          * @param {Function} [config.failure] A reference to a function to call when an error occurs. This
1306          * function will be passed the following parameters:
1307          * <ul>
1308          * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
1309          * <li><b>response:</b> The XMLHttpResponse object</li>
1310          * </ul>
1311          * @param {Object} [config.scope] A scoping object for the success and error callback functions (default to this).
1312          * @param {string} [config.containerPath] An alternate container path to get permissions from. If not specified,
1313          * the current container path will be used.
1314          * @returns {Mixed} In client-side scripts, this method will return a transaction id
1315          * for the async request that can be used to cancel the request
1316          * (see <a href="http://dev.sencha.com/deploy/dev/docs/?class=Ext.data.Connection&member=abort" target="_blank">Ext.data.Connection.abort</a>).
1317          * In server-side scripts, this method will return the JSON response object (first parameter of the success or failure callbacks.)
1318          */
1319         deleteGroup : function(config)
1320         {
1321             var params = {id: config.groupId};
1322             return LABKEY.Ajax.request({
1323                 url: LABKEY.ActionURL.buildURL("security", "deleteGroup", config.containerPath),
1324                 method: "POST",
1325                 success: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnSuccess(config), config.scope),
1326                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true),
1327                 jsonData: params,
1328                 headers : {
1329                     'Content-Type' : 'application/json'
1330                 }
1331             });
1332         },
1333 
1334         /**
1335          * Renames a group.
1336          * @param config A configuration object with the following properties:
1337          * @param {int} config.groupId The id of the group to delete
1338          * @param {String} config.newName The new name for the group
1339          * @param {Function} config.success A reference to a function to call with the API results. This
1340          * function will be passed the following parameters:
1341          * <ul>
1342          * <li><b>data:</b> a simple object with the following properties: 'renamed'=the group id; 'oldName'=the old name; 'newName'=the new name.</li>
1343          * <li><b>response:</b> The XMLHttpResponse object</li>
1344          * </ul>
1345          * @param {Function} [config.failure] A reference to a function to call when an error occurs. This
1346          * function will be passed the following parameters:
1347          * <ul>
1348          * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
1349          * <li><b>response:</b> The XMLHttpResponse object</li>
1350          * </ul>
1351          * @param {Object} [config.scope] A scoping object for the success and error callback functions (default to this).
1352          * @param {string} [config.containerPath] An alternate container path to get permissions from. If not specified,
1353          * the current container path will be used.
1354          * @returns {Mixed} In client-side scripts, this method will return a transaction id
1355          * for the async request that can be used to cancel the request
1356          * (see <a href="http://dev.sencha.com/deploy/dev/docs/?class=Ext.data.Connection&member=abort" target="_blank">Ext.data.Connection.abort</a>).
1357          * In server-side scripts, this method will return the JSON response object (first parameter of the success or failure callbacks.)
1358          */
1359         renameGroup : function(config) {
1360             return LABKEY.Ajax.request({
1361                 url: LABKEY.ActionURL.buildURL("security", "renameGroup", config.containerPath),
1362                 method: "POST",
1363                 success: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnSuccess(config), config.scope),
1364                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true),
1365                 jsonData: { id: config.groupId, newName: config.newName },
1366                 headers : {
1367                     'Content-Type' : 'application/json'
1368                 }
1369             });
1370 
1371         },
1372 
1373         /**
1374          * Adds a new member to an existing group.
1375          * @param config A configuration object with the following properties:
1376          * @param {int} config.groupId The id of the group to which you want to add the member.
1377          * @param {int|int[]} config.principalIds An integer id or array of ids of the users or groups you want to add as members.
1378          * @param {Function} config.success A reference to a function to call with the API results. This
1379          * function will be passed the following parameters:
1380          * <ul>
1381          * <li><b>data:</b> a simple object with a property named "added" that contains the added principal id.</li>
1382          * <li><b>response:</b> The XMLHttpResponse object</li>
1383          * </ul>
1384          * @param {Function} [config.failure] A reference to a function to call when an error occurs. This
1385          * function will be passed the following parameters:
1386          * <ul>
1387          * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
1388          * <li><b>response:</b> The XMLHttpResponse object</li>
1389          * </ul>
1390          * @param {Object} [config.scope] A scoping object for the success and error callback functions (default to this).
1391          * @param {string} [config.containerPath] An alternate container path to get permissions from. If not specified,
1392          * the current container path will be used.
1393          * @returns {Mixed} In client-side scripts, this method will return a transaction id
1394          * for the async request that can be used to cancel the request
1395          * (see <a href="http://dev.sencha.com/deploy/dev/docs/?class=Ext.data.Connection&member=abort" target="_blank">Ext.data.Connection.abort</a>).
1396          * In server-side scripts, this method will return the JSON response object (first parameter of the success or failure callbacks.)
1397          */
1398         addGroupMembers : function(config)
1399         {
1400             var params = {
1401                 groupId: config.groupId,
1402                 principalIds: (LABKEY.Utils.isArray(config.principalIds) ? config.principalIds : [config.principalIds])
1403             };
1404             return LABKEY.Ajax.request({
1405                 url: LABKEY.ActionURL.buildURL("security", "addGroupMember", config.containerPath),
1406                 method: "POST",
1407                 success: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnSuccess(config), config.scope),
1408                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true),
1409                 jsonData: params,
1410                 headers : {
1411                     'Content-Type' : 'application/json'
1412                 }
1413             });
1414 
1415         },
1416 
1417         /**
1418          * Removes a member from an existing group.
1419          * @param config A configuration object with the following properties:
1420          * @param {int} config.groupId The id of the group from which you want to remove the member.
1421          * @param {int|int[]} config.principalIds An integer id or array of ids of the users or groups you want to remove.
1422          * @param {Function} config.success A reference to a function to call with the API results. This
1423          * function will be passed the following parameters:
1424          * <ul>
1425          * <li><b>data:</b> a simple object with a property named "removed" that contains the removed principal id.</li>
1426          * <li><b>response:</b> The XMLHttpResponse object</li>
1427          * </ul>
1428          * @param {Function} [config.failure] A reference to a function to call when an error occurs. This
1429          * function will be passed the following parameters:
1430          * <ul>
1431          * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
1432          * <li><b>response:</b> The XMLHttpResponse object</li>
1433          * </ul>
1434          * @param {Object} [config.scope] A scoping object for the success and error callback functions (default to this).
1435          * @param {string} [config.containerPath] An alternate container path to get permissions from. If not specified,
1436          * the current container path will be used.
1437          * @returns {Mixed} In client-side scripts, this method will return a transaction id
1438          * for the async request that can be used to cancel the request
1439          * (see <a href="http://dev.sencha.com/deploy/dev/docs/?class=Ext.data.Connection&member=abort" target="_blank">Ext.data.Connection.abort</a>).
1440          * In server-side scripts, this method will return the JSON response object (first parameter of the success or failure callbacks.)
1441          */
1442         removeGroupMembers : function(config)
1443         {
1444             var params = {
1445                 groupId: config.groupId,
1446                 principalIds: (LABKEY.Utils.isArray(config.principalIds) ? config.principalIds : [config.principalIds])
1447             };
1448             return LABKEY.Ajax.request({
1449                 url: LABKEY.ActionURL.buildURL("security", "removeGroupMember", config.containerPath),
1450                 method: "POST",
1451                 success: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnSuccess(config), config.scope),
1452                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true),
1453                 jsonData: params,
1454                 headers : {
1455                     'Content-Type' : 'application/json'
1456                 }
1457             });
1458         },
1459 
1460         /**
1461          * Creates a new user account
1462          * @param config A configuration object with the following properties:
1463          * @param {String} config.email The new user's email address.
1464          * @param {Boolean} config.sendEmail Set to false to stop the server from sending a welcome email to the user.
1465          * @param {Function} config.success A reference to a function to call with the API results. This
1466          * function will be passed the following parameters:
1467          * <ul>
1468          * <li><b>data:</b> a simple object with three properties: userId, email, and message.</li>
1469          * <li><b>response:</b> The XMLHttpResponse object</li>
1470          * </ul>
1471          * @param {Function} [config.failure] A reference to a function to call when an error occurs. This
1472          * function will be passed the following parameters:
1473          * <ul>
1474          * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
1475          * <li><b>response:</b> The XMLHttpResponse object</li>
1476          * </ul>
1477          * @param {Object} [config.scope] A scoping object for the success and error callback functions (default to this).
1478          * @param {string} [config.containerPath] An alternate container path to get permissions from. If not specified,
1479          * the current container path will be used.
1480          * @returns {Mixed} In client-side scripts, this method will return a transaction id
1481          * for the async request that can be used to cancel the request
1482          * (see <a href="http://dev.sencha.com/deploy/dev/docs/?class=Ext.data.Connection&member=abort" target="_blank">Ext.data.Connection.abort</a>).
1483          * In server-side scripts, this method will return the JSON response object (first parameter of the success or failure callbacks.)
1484          */
1485         createNewUser : function(config)
1486         {
1487             var params = {
1488                 email: config.email,
1489                 sendEmail: config.sendEmail
1490             };
1491             return LABKEY.Ajax.request({
1492                 url: LABKEY.ActionURL.buildURL("security", "createNewUser", config.containerPath),
1493                 method: "POST",
1494                 success: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnSuccess(config), config.scope),
1495                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true),
1496                 jsonData: params,
1497                 headers : {
1498                     'Content-Type' : 'application/json'
1499                 }
1500             });
1501         },
1502 
1503         /**
1504          * Returns the name of the home container, which is automatically created when your server is set up.  It is usually 'home'
1505          * @returns {string} The name of the home container automatically created on this server.
1506          */
1507         getHomeContainer: function(){
1508             return LABKEY.homeContainer;
1509         },
1510 
1511         /**
1512          * Returns the name of the shared container, which is automatically created when your server is setup. It is usually 'Shared'
1513          * @returns {string} The name of the shared container automatically created on this server.
1514          */
1515         getSharedContainer: function(){
1516             return LABKEY.sharedContainer;
1517         }
1518     };
1519 };
1520