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) 2009-2018 LabKey Corporation
  5  * <p/>
  6  * Licensed under the Apache License, Version 2.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at
  9  * <p/>
 10  * http://www.apache.org/licenses/LICENSE-2.0
 11  * <p/>
 12  * Unless required by applicable law or agreed to in writing, software
 13  * distributed under the License is distributed on an "AS IS" BASIS,
 14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing permissions and
 16  * limitations under the License.
 17  * <p/>
 18  */
 19 
 20 
 21 /**
 22  * @class Represents a security policy for a particular securable resource on the server. In general, you
 23  * should obtain an instance of this class from the LABKEY.Security.getPolicy() method. You may use the methods
 24  * of this class to alter the policy and save it back to the server using the LABKEY.Security.savePolicy() method.
 25  * <p>
 26  * The following definitions should be helpful in understanding the methods of this class:
 27  * <ul>
 28  * <li><b>Principal:</b> A user principal, which can be either a user or a group. Users and groups are both
 29  * user principals, and in a security policy, a user principal is assigned to a given role.</li>
 30  * <li><b>Role:</b> A role grants a specific set of permissions. For example, the 'Reader' role grants the read permission.
 31  * Roles are identified by unique names (usually a fully-qualified Java class name). A full set of roles is obtainable
 32  * from the LABKEY.Security.getRoles() method.</li>
 33  * <li><b>Direct vs Effective Assignment:</b> In a policy, principals are assigned to one or more roles. However, because a
 34  * principal might be a group, the users that belong to that group are effectively in whatever role the group is
 35  * assigned to. In this situation, the user is 'effectively' assigned to the role, while the group is 'directly'
 36  * assigned to the role. Asking for a user's effective roles will return all roles the user is directly assigned to
 37  * plus all roles the groups the user belongs to are assigned to.</li>
 38  * </ul>
 39  *            <p>Additional Documentation:
 40  *              <ul>
 41  *                  <li><a href="https://www.labkey.org/Documentation/wiki-page.view?name=security">LabKey Security and Accounts</a></li>
 42  *              </ul>
 43  *           </p>
 44  * @example
 45 <script type="text/javascript">
 46     LABKEY.Security.getPolicy({
 47      resourceId: ....
 48      successCallback: onGetPolicy
 49  });
 50 
 51  function onGetPolicy(policy, relevantRoles)
 52  {
 53      //policy is an instance of this class
 54      //relevantRoles is an array of role unique names that are relevant to the resource
 55  }
 56 </script>
 57  */
 58 LABKEY.SecurityPolicy = Ext.extend(Ext.util.Observable, {
 59 
 60     guestsPrincipal:-3,
 61     noPermissionsRole: "org.labkey.api.security.roles.NoPermissionsRole",
 62     
 63     constructor : function(config)
 64     {
 65         LABKEY.SecurityPolicy.superclass.constructor.apply(this, arguments);
 66 
 67         this.policy = config;
 68         this._dirty = false;
 69 
 70         /**
 71          * @memberOf LABKEY.SecurityPolicy#
 72          * @name change
 73          * @event
 74          * @description Fired after the policy has been changed in some way.
 75          */
 76         this.addEvents({
 77             "change": true
 78         });
 79 
 80     },
 81 
 82     /**
 83      * Returns the resource ID this policy applies to. Note that this may not be same ID that was requested.
 84      * If the requested resource inherits its permissions from an ancestor resource, this method will return
 85      * the ID of the nearest resource that has an policy associated with it.
 86      * @name getResourceId
 87      * @function
 88      * @memberOf LABKEY.SecurityPolicy#
 89      * @returns The resource ID for this policy.
 90      */
 91     getResourceId : function()
 92     {
 93         return this.policy.resourceId;
 94     },
 95 
 96     /**
 97      * Returns true if this policy is empty (i.e., has no role assignments).
 98      * @name isEmpty
 99      * @function
100      * @memberOf LABKEY.SecurityPolicy#
101      * @returns true if this policy is empty, false otherwise.
102      */
103     isEmpty : function()
104     {
105         return this.policy.assignments.length == 0;
106     },
107 
108     /**
109      * Returns true if this policy was inherited from an ancestor resource (see getResourceId())
110      * @name isInherited
111      * @function
112      * @memberOf LABKEY.SecurityPolicy#
113      * @returns true if this policy was inherited, false otherwise.
114      */
115     isInherited : function()
116     {
117         return this.policy.requestedResourceId != this.policy.resourceId;
118     },
119 
120     /**
121      * Returns the array of roles to which the given principal is directly assigned.
122      * @name getAssignedRoles
123      * @function
124      * @memberOf LABKEY.SecurityPolicy#
125      * @param principalId The ID of the principal.
126      * @returns An array of role unique names.
127      */
128     getAssignedRoles : function(principalId)
129     {
130         var idx, assgn;
131         var roles = [];
132         for (idx = 0; idx < this.policy.assignments.length; ++idx)
133         {
134             assgn = this.policy.assignments[idx];
135             if(assgn.userId == principalId)
136                 roles.push(assgn.role);
137         }
138         return roles;
139     },
140 
141     /**
142      * Returns an array of principal IDs that are directly assigned to a given role.
143      * @name getAssignedPrincipals
144      * @function
145      * @memberOf LABKEY.SecurityPolicy#
146      * @param role The unique name of the role
147      * @returns An array of principal IDs
148      */
149     getAssignedPrincipals : function(role)
150     {
151         var idx, len, assgn;
152         var principals = [];
153         for (idx = 0, len=this.policy.assignments.length ; idx < len ; ++idx)
154         {
155             assgn = this.policy.assignments[idx];
156             if (assgn.role == role)
157                 principals.push(assgn.userId);
158         }
159         return principals;
160     },
161 
162     /**
163      * Adds a direct role assignment to the policy.
164      * @name addRoleAssignment
165      * @function
166      * @memberOf LABKEY.SecurityPolicy#
167      * @param principalId The principal ID
168      * @param role The role unique name
169      */
170     addRoleAssignment : function(principalId, role)
171     {
172         this.removeRoleAssignment(principalId, this.noPermissionsRole);
173 
174         var idx, len, assgn;
175         for (idx = 0, len = this.policy.assignments.length; idx < len ; ++idx)
176         {
177             assgn = this.policy.assignments[idx];
178             if (assgn.userId == principalId && assgn.role == role)
179                 return;
180         }
181         this.policy.assignments.push({
182             userId: principalId,
183             role: role
184         });
185         this.fireEvent("change");
186         this._dirty = true;
187     },
188 
189     /**
190      * Removes a direct role assignment from the policy.
191      * @name removeRoleAssignment
192      * @function
193      * @memberOf LABKEY.SecurityPolicy#
194      * @param principalId The principal ID
195      * @param role The role unique name
196      */
197     removeRoleAssignment : function(principalId, role)
198     {
199         var idx, assgn;
200         for (idx = 0; idx < this.policy.assignments.length; ++idx)
201         {
202             assgn = this.policy.assignments[idx];
203             if (assgn.userId == principalId && assgn.role == role)
204                 break;
205         }
206         if(idx < this.policy.assignments.length)
207         {
208             this.policy.assignments.splice(idx, 1);
209             this.fireEvent("change");
210             this._dirty = true;
211         }
212     },
213 
214     /**
215      * Removes all direct role assignments for the given principal
216      * @name clearRoleAssignments
217      * @function
218      * @memberOf LABKEY.SecurityPolicy#
219      * @param principalId The principal ID
220      */
221     clearRoleAssignments : function(principalId)
222     {
223         if (undefined === principalId)
224         {
225             this.policy.assignments = [];
226             this.fireEvent("change");
227             this._dirty = true;
228             return;
229         }
230 
231         var idx, assgn, len = this.policy.assignments.length;
232         for (idx = len-1 ; idx >= 0 ; --idx)
233         {
234             assgn = this.policy.assignments[idx];
235             if (assgn.userId == principalId)
236                 this.policy.assignments.splice(idx, 1);
237         }
238         if (len != this.policy.assignments.length)
239         {
240             this.fireEvent("change");
241             this._dirty = true;
242         }
243     },
244 
245     /**
246      * Returns all the roles the principal is effectively assigned to in this policy. See the definitions
247      * in the class description for the distinction between effective and direct assignment.
248      * @name getEffectiveRoles
249      * @function
250      * @memberOf LABKEY.SecurityPolicy#
251      * @param principalId The principal ID
252      * @param membershipsTable The group memberships table. This is required to determine the groups
253      * the principal belongs to. You can obtain this table by requesting the 'Members' table from the 'Core'
254      * schema using LABKEY.Query.selectRows().
255      * @returns An array of roles the principal is effectively playing.
256      */
257     getEffectiveRoles : function(principalId, membershipsTable)
258     {
259         var ids = this.getGroupsForPrincipal(principalId, membershipsTable);
260         ids.push(principalId);
261         return this.getEffectiveRolesForIds(ids);
262     },
263 
264     /**
265      * Returns an object containing a property per role the given principals are effectively playing.
266      * The name of each property is the role unique name, and the value of each property is simply 'true'.
267      * Thus, the returned object is essentially a Set.
268      * @name getEffectiveRolesForIds
269      * @function
270      * @memberOf LABKEY.SecurityPolicy#
271      * @param ids An array of principal IDs
272      * @returns An object with a property per unique role name the users are effectively playing.
273      */
274     getEffectiveRolesForIds : function(ids)
275     {
276         var idxAssgn, assgn, idxIds;
277         var set = {};
278         for (idxAssgn = 0; idxAssgn < this.policy.assignments.length; ++idxAssgn)
279         {
280             assgn = this.policy.assignments[idxAssgn];
281             for (idxIds = 0; idxIds < ids.length; ++idxIds)
282             {
283                 if(ids[idxIds] == assgn.userId)
284                     set[assgn.role]=true;
285             }
286         }
287         return set;
288 //        var roles = [];
289 //        for (var role in set)
290 //            roles.push(role);
291 //        return roles;
292     },
293 
294 
295     /**
296      * Returns all groups this principal belongs to. This function allows for the possibility
297      * that groups may contain other groups.
298      * @name getGroupsForPrincipal
299      * @function
300      * @memberOf LABKEY.SecurityPolicy#
301      * @param principalId The principal
302      * @param membershipsTable The group memberships table. This is required to determine the groups
303      * the principal belongs to. You can obtain this table by requesting the 'Members' table from the 'Core'
304      * schema using LABKEY.Query.selectRows().
305      * @returns An array of group IDs this user belongs to.
306      */
307     getGroupsForPrincipal : function(principalId, membershipsTable)
308     {
309         //recurses to determine all relevant groups for a given principal id
310         var rows = membershipsTable.rows || membershipsTable;
311         var idx, row;
312         var groups = [];
313 
314         for(idx = 0; idx < rows.length; ++idx)
315         {
316             row = rows[idx];
317             if(row.UserId == principalId)
318                 groups = groups.concat(row.GroupId, this.getGroupsForPrincipal(row.GroupId, membershipsTable));
319         }
320         return groups;
321     },
322 
323     /**
324      * Sets the modified property to a new value. The modified property is used during save to determine if the policy has
325      * been modified since it was selected. You may pass null to this method to disable this optimistic concurrency
326      * check and force the policy to save, even if another user modified it since it was selected.
327      * @name setModified
328      * @function
329      * @memberOf LABKEY.SecurityPolicy#
330      * @param modified New modified value, or null to override optimistic concurrency check.
331      */
332     setModified : function(modified)
333     {
334         this.policy.modified = modified;
335         this.fireEvent("change");
336         this._dirty = true;
337     },
338 
339     /**
340      * Returns true if this policy has been modified.
341      * @name isDirty
342      * @function
343      * @memberOf LABKEY.SecurityPolicy#
344      * @returns true if modified, false otherwise.
345      */
346     isDirty : function()
347     {
348         return this._dirty;
349     },
350 
351     /**
352      * Creates a new copy of this policy, optionally resetting the resource ID.
353      * @name copy
354      * @function
355      * @memberOf LABKEY.SecurityPolicy#
356      * @param resourceid A different resource ID to use. This is typically used when you
357      * want to create a new policy for a resource using the policy from another resource as a template.
358      * @returns A new instance of this class which is a deep copy of the current instance.
359      */
360     copy : function(resourceid)
361     {
362         var config = Ext.apply(this.policy);
363         if (resourceid)
364             config.requestedResourceId = config.resourceId = resourceid;
365         config.assignments = this.copyArray(config.assignments);
366         return new LABKEY.SecurityPolicy(this.policy);
367     },
368 
369     /* private shallow copy*/
370     copyArray : function(a)
371     {
372     var copy = [];
373     for (var i=0 ; i<a.length ; i++)
374         copy.push(a[i]);
375     return copy;
376     }
377 });
378