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-2016 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/wiki/home/Documentation/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                     LABKEY.Utils.getOnSuccess(config).call(config.scope || this, data.roles, req);
1121 
1122                 }, this),
1123                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true)
1124             });
1125         },
1126 
1127         /**
1128          * Retrieves the security policy for the requested resource id. Note that this will return the
1129          * policy in effect for this resource, which might be the policy from a parent resource if there
1130          * is no explicit policy set on the requested resource. Use the isInherited method on the returned
1131          * LABKEY.SecurityPolicy object to determine if the policy is inherited or not.
1132          * Note that the securable resource must be within the current container, or one of its descendants.
1133          * @param config A configuration object with the following properties
1134          * @param {String} config.resourceId The unique id of the securable resource.
1135          * @param {Function} config.success A reference to a function to call with the API results. This
1136          * function will be passed the following parameters:
1137          * <ul>
1138          * <li><b>policy:</b> an instance of a LABKEY.SecurityPolicy object.</li>
1139          * <li><b>relevantRoles:</b> an array of role ids that are relevant for the given resource.</li>
1140          * <li><b>response:</b> The XMLHttpResponse object</li>
1141          * </ul>
1142          * @param {Function} [config.failure] A reference to a function to call when an error occurs. This
1143          * function will be passed the following parameters:
1144          * <ul>
1145          * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
1146          * <li><b>response:</b> The XMLHttpResponse object</li>
1147          * </ul>
1148          * @param {object} [config.scope] A scoping object for the success and error callback functions (default to this).
1149          * @param {string} [config.containerPath] An alternate container path to get permissions from. If not specified,
1150          * the current container path will be used.
1151          * @returns {Mixed} In client-side scripts, this method will return a transaction id
1152          * for the async request that can be used to cancel the request
1153          * (see <a href="http://dev.sencha.com/deploy/dev/docs/?class=Ext.data.Connection&member=abort" target="_blank">Ext.data.Connection.abort</a>).
1154          * In server-side scripts, this method will return the JSON response object (first parameter of the success or failure callbacks.)
1155          */
1156         getPolicy : function(config)
1157         {
1158             return LABKEY.Ajax.request({
1159                 url: LABKEY.ActionURL.buildURL("security", "getPolicy", config.containerPath),
1160                 method: "GET",
1161                 params: { resourceId: config.resourceId },
1162                 success: LABKEY.Utils.getCallbackWrapper(function(data, req){
1163                     data.policy.requestedResourceId = config.resourceId;
1164                     var policy = new LABKEY.SecurityPolicy(data.policy);
1165                     LABKEY.Utils.getOnSuccess(config).call(config.scope || this, policy, data.relevantRoles, req);
1166                 }, this),
1167                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true)
1168             });
1169         },
1170 
1171         /**
1172          * Deletes the security policy for the requested resource id. This will cause resource to inherit its
1173          * security policy from its parent resource.
1174          * @param config A configuration object with the following properties
1175          * @param {String} config.resourceId The unique id of the securable resource.
1176          * @param {Function} config.success A reference to a function to call with the API results. This
1177          * function will be passed the following parameters:
1178          * <ul>
1179          * <li><b>data:</b> a simple object with one property called 'success' which will be set to true.</li>
1180          * <li><b>response:</b> The XMLHttpResponse object</li>
1181          * </ul>
1182          * @param {Function} [config.failure] A reference to a function to call when an error occurs. This
1183          * function will be passed the following parameters:
1184          * <ul>
1185          * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
1186          * <li><b>response:</b> The XMLHttpResponse object</li>
1187          * </ul>
1188          * @param {Object} [config.scope] A scoping object for the success and error callback functions (default to this).
1189          * @param {string} [config.containerPath] An alternate container path to get permissions from. If not specified,
1190          * the current container path will be used.
1191          * @returns {Mixed} In client-side scripts, this method will return a transaction id
1192          * for the async request that can be used to cancel the request
1193          * (see <a href="http://dev.sencha.com/deploy/dev/docs/?class=Ext.data.Connection&member=abort" target="_blank">Ext.data.Connection.abort</a>).
1194          * In server-side scripts, this method will return the JSON response object (first parameter of the success or failure callbacks.)
1195          */
1196         deletePolicy : function(config)
1197         {
1198             return LABKEY.Ajax.request({
1199                 url: LABKEY.ActionURL.buildURL("security", "deletePolicy", config.containerPath),
1200                 method: "POST",
1201                 success: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnSuccess(config), config.scope),
1202                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true),
1203                 jsonData: { resourceId: config.resourceId },
1204                 headers : {
1205                     'Content-Type' : 'application/json'
1206                 }
1207             });
1208         },
1209 
1210         /**
1211          * Saves the supplied security policy. This object should be a LABKEY.SecurityPolicy object. This
1212          * method will completely overwrite the existing policy for the resource. If another user has changed
1213          * the policy in between the time it was selected and this method is called, the save will fail with
1214          * an optimistic concurrency exception. To force your policy over the other, call the setModified()
1215          * method on the policy passing null.
1216          * @param config A configuration object with the following properties
1217          * @param {String} config.policy The LABKEY.SecurityPolicy object
1218          * @param {Function} config.success A reference to a function to call with the API results. This
1219          * function will be passed the following parameters:
1220          * <ul>
1221          * <li><b>data:</b> a simple object with one property called 'success' which will be set to true.</li>
1222          * <li><b>response:</b> The XMLHttpResponse object</li>
1223          * </ul>
1224          * @param {Function} [config.failure] A reference to a function to call when an error occurs. This
1225          * function will be passed the following parameters:
1226          * <ul>
1227          * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
1228          * <li><b>response:</b> The XMLHttpResponse object</li>
1229          * </ul>
1230          * @param {Object} [config.scope] A scoping object for the success and error callback functions (default to this).
1231          * @param {string} [config.containerPath] An alternate container path to get permissions from. If not specified,
1232          * the current container path will be used.
1233          * @returns {Mixed} In client-side scripts, this method will return a transaction id
1234          * for the async request that can be used to cancel the request
1235          * (see <a href="http://dev.sencha.com/deploy/dev/docs/?class=Ext.data.Connection&member=abort" target="_blank">Ext.data.Connection.abort</a>).
1236          * In server-side scripts, this method will return the JSON response object (first parameter of the success or failure callbacks.)
1237          */
1238         savePolicy : function(config)
1239         {
1240             return LABKEY.Ajax.request({
1241                 url: LABKEY.ActionURL.buildURL("security", "savePolicy", config.containerPath),
1242                 method : 'POST',
1243                 success: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnSuccess(config), config.scope),
1244                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true),
1245                 jsonData : config.policy.policy,
1246                 headers : {
1247                     'Content-Type' : 'application/json'
1248                 }
1249             });
1250         },
1251 
1252         /**
1253          * Creates a new group. The new group will be created at the project level when the current
1254          * container is a folder or project, or will be created at the system level if the current
1255          * container is the root.
1256          * @param config A configuration object with the following properties:
1257          * @param {String} config.groupName The name of the group to create
1258          * @param {Function} config.success A reference to a function to call with the API results. This
1259          * function will be passed the following parameters:
1260          * <ul>
1261          * <li><b>data:</b> a simple object with two properties: id and name (the new group id and name respectively)</li>
1262          * <li><b>response:</b> The XMLHttpResponse object</li>
1263          * </ul>
1264          * @param {Function} [config.failure] A reference to a function to call when an error occurs. This
1265          * function will be passed the following parameters:
1266          * <ul>
1267          * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
1268          * <li><b>response:</b> The XMLHttpResponse object</li>
1269          * </ul>
1270          * @param {Object} [config.scope] A scoping object for the success and error callback functions (default to this).
1271          * @param {string} [config.containerPath] An alternate container path to get permissions from. If not specified,
1272          * the current container path will be used.
1273          * @returns {Mixed} In client-side scripts, this method will return a transaction id
1274          * for the async request that can be used to cancel the request
1275          * (see <a href="http://dev.sencha.com/deploy/dev/docs/?class=Ext.data.Connection&member=abort" target="_blank">Ext.data.Connection.abort</a>).
1276          * In server-side scripts, this method will return the JSON response object (first parameter of the success or failure callbacks.)
1277          */
1278         createGroup : function(config)
1279         {
1280             var params = {name: config.groupName};
1281             return LABKEY.Ajax.request({
1282                 url: LABKEY.ActionURL.buildURL("security", "createGroup", config.containerPath),
1283                 method: "POST",
1284                 success: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnSuccess(config), config.scope),
1285                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true),
1286                 jsonData: params,
1287                 headers : {
1288                     'Content-Type' : 'application/json'
1289                 }
1290             });
1291         },
1292 
1293         /**
1294          * Deletes a group.
1295          * @param config A configuration object with the following properties:
1296          * @param {int} config.groupId The id of the group to delete
1297          * @param {Function} config.success A reference to a function to call with the API results. This
1298          * function will be passed the following parameters:
1299          * <ul>
1300          * <li><b>data:</b> a simple object with a property named "deleted" that contains the deleted group id.</li>
1301          * <li><b>response:</b> The XMLHttpResponse object</li>
1302          * </ul>
1303          * @param {Function} [config.failure] A reference to a function to call when an error occurs. This
1304          * function will be passed the following parameters:
1305          * <ul>
1306          * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
1307          * <li><b>response:</b> The XMLHttpResponse object</li>
1308          * </ul>
1309          * @param {Object} [config.scope] A scoping object for the success and error callback functions (default to this).
1310          * @param {string} [config.containerPath] An alternate container path to get permissions from. If not specified,
1311          * the current container path will be used.
1312          * @returns {Mixed} In client-side scripts, this method will return a transaction id
1313          * for the async request that can be used to cancel the request
1314          * (see <a href="http://dev.sencha.com/deploy/dev/docs/?class=Ext.data.Connection&member=abort" target="_blank">Ext.data.Connection.abort</a>).
1315          * In server-side scripts, this method will return the JSON response object (first parameter of the success or failure callbacks.)
1316          */
1317         deleteGroup : function(config)
1318         {
1319             var params = {id: config.groupId};
1320             return LABKEY.Ajax.request({
1321                 url: LABKEY.ActionURL.buildURL("security", "deleteGroup", config.containerPath),
1322                 method: "POST",
1323                 success: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnSuccess(config), config.scope),
1324                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true),
1325                 jsonData: params,
1326                 headers : {
1327                     'Content-Type' : 'application/json'
1328                 }
1329             });
1330         },
1331 
1332         /**
1333          * Renames a group.
1334          * @param config A configuration object with the following properties:
1335          * @param {int} config.groupId The id of the group to delete
1336          * @param {String} config.newName The new name for the group
1337          * @param {Function} config.success A reference to a function to call with the API results. This
1338          * function will be passed the following parameters:
1339          * <ul>
1340          * <li><b>data:</b> a simple object with the following properties: 'renamed'=the group id; 'oldName'=the old name; 'newName'=the new name.</li>
1341          * <li><b>response:</b> The XMLHttpResponse object</li>
1342          * </ul>
1343          * @param {Function} [config.failure] A reference to a function to call when an error occurs. This
1344          * function will be passed the following parameters:
1345          * <ul>
1346          * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
1347          * <li><b>response:</b> The XMLHttpResponse object</li>
1348          * </ul>
1349          * @param {Object} [config.scope] A scoping object for the success and error callback functions (default to this).
1350          * @param {string} [config.containerPath] An alternate container path to get permissions from. If not specified,
1351          * the current container path will be used.
1352          * @returns {Mixed} In client-side scripts, this method will return a transaction id
1353          * for the async request that can be used to cancel the request
1354          * (see <a href="http://dev.sencha.com/deploy/dev/docs/?class=Ext.data.Connection&member=abort" target="_blank">Ext.data.Connection.abort</a>).
1355          * In server-side scripts, this method will return the JSON response object (first parameter of the success or failure callbacks.)
1356          */
1357         renameGroup : function(config) {
1358             return LABKEY.Ajax.request({
1359                 url: LABKEY.ActionURL.buildURL("security", "renameGroup", config.containerPath),
1360                 method: "POST",
1361                 success: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnSuccess(config), config.scope),
1362                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true),
1363                 jsonData: { id: config.groupId, newName: config.newName },
1364                 headers : {
1365                     'Content-Type' : 'application/json'
1366                 }
1367             });
1368 
1369         },
1370 
1371         /**
1372          * Adds a new member to an existing group.
1373          * @param config A configuration object with the following properties:
1374          * @param {int} config.groupId The id of the group to which you want to add the member.
1375          * @param {int|int[]} config.principalIds An integer id or array of ids of the users or groups you want to add as members.
1376          * @param {Function} config.success A reference to a function to call with the API results. This
1377          * function will be passed the following parameters:
1378          * <ul>
1379          * <li><b>data:</b> a simple object with a property named "added" that contains the added principal id.</li>
1380          * <li><b>response:</b> The XMLHttpResponse object</li>
1381          * </ul>
1382          * @param {Function} [config.failure] A reference to a function to call when an error occurs. This
1383          * function will be passed the following parameters:
1384          * <ul>
1385          * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
1386          * <li><b>response:</b> The XMLHttpResponse object</li>
1387          * </ul>
1388          * @param {Object} [config.scope] A scoping object for the success and error callback functions (default to this).
1389          * @param {string} [config.containerPath] An alternate container path to get permissions from. If not specified,
1390          * the current container path will be used.
1391          * @returns {Mixed} In client-side scripts, this method will return a transaction id
1392          * for the async request that can be used to cancel the request
1393          * (see <a href="http://dev.sencha.com/deploy/dev/docs/?class=Ext.data.Connection&member=abort" target="_blank">Ext.data.Connection.abort</a>).
1394          * In server-side scripts, this method will return the JSON response object (first parameter of the success or failure callbacks.)
1395          */
1396         addGroupMembers : function(config)
1397         {
1398             var params = {
1399                 groupId: config.groupId,
1400                 principalIds: (LABKEY.Utils.isArray(config.principalIds) ? config.principalIds : [config.principalIds])
1401             };
1402             return LABKEY.Ajax.request({
1403                 url: LABKEY.ActionURL.buildURL("security", "addGroupMember", config.containerPath),
1404                 method: "POST",
1405                 success: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnSuccess(config), config.scope),
1406                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true),
1407                 jsonData: params,
1408                 headers : {
1409                     'Content-Type' : 'application/json'
1410                 }
1411             });
1412 
1413         },
1414 
1415         /**
1416          * Removes a member from an existing group.
1417          * @param config A configuration object with the following properties:
1418          * @param {int} config.groupId The id of the group from which you want to remove the member.
1419          * @param {int|int[]} config.principalIds An integer id or array of ids of the users or groups you want to remove.
1420          * @param {Function} config.success A reference to a function to call with the API results. This
1421          * function will be passed the following parameters:
1422          * <ul>
1423          * <li><b>data:</b> a simple object with a property named "removed" that contains the removed principal id.</li>
1424          * <li><b>response:</b> The XMLHttpResponse object</li>
1425          * </ul>
1426          * @param {Function} [config.failure] A reference to a function to call when an error occurs. This
1427          * function will be passed the following parameters:
1428          * <ul>
1429          * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
1430          * <li><b>response:</b> The XMLHttpResponse object</li>
1431          * </ul>
1432          * @param {Object} [config.scope] A scoping object for the success and error callback functions (default to this).
1433          * @param {string} [config.containerPath] An alternate container path to get permissions from. If not specified,
1434          * the current container path will be used.
1435          * @returns {Mixed} In client-side scripts, this method will return a transaction id
1436          * for the async request that can be used to cancel the request
1437          * (see <a href="http://dev.sencha.com/deploy/dev/docs/?class=Ext.data.Connection&member=abort" target="_blank">Ext.data.Connection.abort</a>).
1438          * In server-side scripts, this method will return the JSON response object (first parameter of the success or failure callbacks.)
1439          */
1440         removeGroupMembers : function(config)
1441         {
1442             var params = {
1443                 groupId: config.groupId,
1444                 principalIds: (LABKEY.Utils.isArray(config.principalIds) ? config.principalIds : [config.principalIds])
1445             };
1446             return LABKEY.Ajax.request({
1447                 url: LABKEY.ActionURL.buildURL("security", "removeGroupMember", config.containerPath),
1448                 method: "POST",
1449                 success: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnSuccess(config), config.scope),
1450                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true),
1451                 jsonData: params,
1452                 headers : {
1453                     'Content-Type' : 'application/json'
1454                 }
1455             });
1456         },
1457 
1458         /**
1459          * Creates a new user account
1460          * @param config A configuration object with the following properties:
1461          * @param {String} config.email The new user's email address.
1462          * @param {Boolean} config.sendEmail Set to false to stop the server from sending a welcome email to the user.
1463          * @param {Function} config.success A reference to a function to call with the API results. This
1464          * function will be passed the following parameters:
1465          * <ul>
1466          * <li><b>data:</b> a simple object with three properties: userId, email, and message.</li>
1467          * <li><b>response:</b> The XMLHttpResponse object</li>
1468          * </ul>
1469          * @param {Function} [config.failure] A reference to a function to call when an error occurs. This
1470          * function will be passed the following parameters:
1471          * <ul>
1472          * <li><b>errorInfo:</b> an object containing detailed error information (may be null)</li>
1473          * <li><b>response:</b> The XMLHttpResponse object</li>
1474          * </ul>
1475          * @param {Object} [config.scope] A scoping object for the success and error callback functions (default to this).
1476          * @param {string} [config.containerPath] An alternate container path to get permissions from. If not specified,
1477          * the current container path will be used.
1478          * @returns {Mixed} In client-side scripts, this method will return a transaction id
1479          * for the async request that can be used to cancel the request
1480          * (see <a href="http://dev.sencha.com/deploy/dev/docs/?class=Ext.data.Connection&member=abort" target="_blank">Ext.data.Connection.abort</a>).
1481          * In server-side scripts, this method will return the JSON response object (first parameter of the success or failure callbacks.)
1482          */
1483         createNewUser : function(config)
1484         {
1485             var params = {
1486                 email: config.email,
1487                 sendEmail: config.sendEmail
1488             };
1489             return LABKEY.Ajax.request({
1490                 url: LABKEY.ActionURL.buildURL("security", "createNewUser", config.containerPath),
1491                 method: "POST",
1492                 success: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnSuccess(config), config.scope),
1493                 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true),
1494                 jsonData: params,
1495                 headers : {
1496                     'Content-Type' : 'application/json'
1497                 }
1498             });
1499         },
1500 
1501         /**
1502          * Returns the name of the home container, which is automatically created when your server is set up.  It is usually 'home'
1503          * @returns {string} The name of the home container automatically created on this server.
1504          */
1505         getHomeContainer: function(){
1506             return LABKEY.homeContainer;
1507         },
1508 
1509         /**
1510          * Returns the name of the shared container, which is automatically created when your server is setup. It is usually 'Shared'
1511          * @returns {string} The name of the shared container automatically created on this server.
1512          */
1513         getSharedContainer: function(){
1514             return LABKEY.sharedContainer;
1515         }
1516     };
1517 };
1518