├── README.md └── UserAccessVisualization ├── .project ├── .settings └── com.salesforce.ide.core.prefs ├── LICENSE.txt ├── salesforce.schema └── src ├── classes ├── MapHolder.cls ├── MapHolder.cls-meta.xml ├── NameLabel.cls ├── NameLabel.cls-meta.xml ├── UserAccessDetailsController.cls └── UserAccessDetailsController.cls-meta.xml ├── components ├── PermsetAccessTable.component └── PermsetAccessTable.component-meta.xml ├── package.xml ├── pages ├── UserAccessDetails.page └── UserAccessDetails.page-meta.xml └── staticresources ├── css.resource ├── css.resource-meta.xml ├── jQuery.resource ├── jQuery.resource-meta.xml ├── js.resource └── js.resource-meta.xml /README.md: -------------------------------------------------------------------------------- 1 | user-access-visualization 2 | ========================= 3 | This repository contains a Force.com IDE project related to the 4 | Dreamforce '12 session entitled "Building Administrative Tools with 5 | Permission Set API." Specifically, this project provides a set of 6 | VisualForce pages and Apex classes that demonstrate one possible way 7 | of visualizing a user's complete set of access rights, at least those 8 | access rights that are accessible via the SObject API. 9 | 10 | installation 11 | ========================= 12 | Probably the easiest way to install this visualization into your org 13 | is to make use of the Force.com IDE. Simply pull the repo down 14 | locally and open the project within the IDE. Once the project is open 15 | within the IDE, you can either Deploy or Synchronize the code and 16 | related resources with your organization. 17 | 18 | For those without the Force.com IDE, the code is still relatively easy 19 | to deploy. The first step is to pull the repo locally and zip the src 20 | directory and all it's contents (i.e., package.xml, classes/, etc.). 21 | Then, open http://workbench.developerforce.com/ and login to the 22 | desired organization with a user that has Modify All Data. Select 23 | *Deploy* from the *migration* menu. Select the ZIP file you created 24 | as the first step and deploy. 25 | 26 | configuration and usage 27 | = 28 | The code contained within this repository creates a new VisualForce 29 | page that is accessible at `/apex/UserAccessDetails`. In order for 30 | the page to operate properly, however, a user id must be provided via 31 | a `uid` query parameter. For example, to see the user access details 32 | for a user with an id of `005E00000021x09`, the URL would be 33 | `/apex/UserAccessDetails?uid=005E00000021x09`. Failure to include an 34 | appropriate uid query parameter will result in a fun error message. 35 | 36 | Probably the best way make use of this is to create a custom link on 37 | the user object and then add it to the user page layout. This can be 38 | done in a few quick steps. First, select *Customize* | *Users* | 39 | *Custom Links* from the setup page. Click on *New* to create the 40 | custom link and name the link whatever you'd like. For the behavior, 41 | I prefer *Display in existing window with sidebar*, but choose 42 | whatever works best for you. The "formula" for the custom link is 43 | `/apex/UserAccessDetails?uid={!User.Id}`. Once the custom link is 44 | created, select *Customize* | *Users* | *Page Layouts* and edit the 45 | desired page layout. In the layout editor, drag the newly created 46 | custom link onto the User layout as desired. 47 | 48 | Keep in mind that you may need to grant access to the 49 | `UserAccessDetails` Visualforce Page to the users who will be making 50 | use of this. If needed, this is done via either the user's profile or 51 | (preferably!) via the appropriate permission set. -------------------------------------------------------------------------------- /UserAccessVisualization/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | UserAccessVisualization 4 | 5 | 6 | 7 | 8 | 9 | com.salesforce.ide.builder.default 10 | 11 | 12 | 13 | 14 | 15 | com.salesforce.ide.nature.default 16 | 17 | 18 | -------------------------------------------------------------------------------- /UserAccessVisualization/.settings/com.salesforce.ide.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | endpointApiVersion=25.0 3 | endpointEnvironment=Production/Developer Edition 4 | endpointServer=www.salesforce.com 5 | httpsProtocol=true 6 | ideVersion=25.0 7 | keependpoint=false 8 | metadataFormatVersion=25.0 9 | namespacePrefix= 10 | packageName=unpackaged 11 | readTimeout=400 12 | username=doug@perms.df12 13 | -------------------------------------------------------------------------------- /UserAccessVisualization/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, Salesforce.com, Inc. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in 12 | the documentation and/or other materials provided with the 13 | distribution. 14 | 15 | * Neither the name of Salesforce.com nor the names of its 16 | contributors may be used to endorse or promote products derived 17 | from this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /UserAccessVisualization/salesforce.schema: -------------------------------------------------------------------------------- 1 | place holder -------------------------------------------------------------------------------- /UserAccessVisualization/src/classes/MapHolder.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2012, Salesforce.com, Inc. All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are 6 | * met: 7 | * 8 | * * Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in 13 | * the documentation and/or other materials provided with the 14 | * distribution. 15 | * 16 | * * Neither the name of Salesforce.com nor the names of its 17 | * contributors may be used to endorse or promote products derived 18 | * from this software without specific prior written permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | public class MapHolder { 33 | public Map it { get; private set; } 34 | 35 | public MapHolder(Map it) { 36 | this.it = it; 37 | } 38 | } -------------------------------------------------------------------------------- /UserAccessVisualization/src/classes/MapHolder.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 25.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /UserAccessVisualization/src/classes/NameLabel.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2012, Salesforce.com, Inc. All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are 6 | * met: 7 | * 8 | * * Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in 13 | * the documentation and/or other materials provided with the 14 | * distribution. 15 | * 16 | * * Neither the name of Salesforce.com nor the names of its 17 | * contributors may be used to endorse or promote products derived 18 | * from this software without specific prior written permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | global class NameLabel implements Comparable { 33 | public Id id { get; private set; } 34 | public String apiName { get; private set; } 35 | public String label { get; private set; } 36 | 37 | public NameLabel(String apiName, String label) { 38 | this(null, apiName, label); 39 | } 40 | 41 | public NameLabel(Id id, String apiName, String label) { 42 | this.id = id; 43 | this.apiName = apiName; 44 | this.label = label; 45 | } 46 | 47 | global Integer compareTo(Object obj) { 48 | NameLabel other = (NameLabel) obj; 49 | return label.compareTo(other.label); 50 | } 51 | } -------------------------------------------------------------------------------- /UserAccessVisualization/src/classes/NameLabel.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 25.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /UserAccessVisualization/src/classes/UserAccessDetailsController.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2012, Salesforce.com, Inc. All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are 6 | * met: 7 | * 8 | * * Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in 13 | * the documentation and/or other materials provided with the 14 | * distribution. 15 | * 16 | * * Neither the name of Salesforce.com nor the names of its 17 | * contributors may be used to endorse or promote products derived 18 | * from this software without specific prior written permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | public class UserAccessDetailsController { 33 | // Define some constants so we can avoid magic strings. At the very least, by using 34 | // constants, we avoid mismatches due to typos. 35 | private static final String PERMISSIONS = 'permissions'; 36 | private static final String CREATE = 'permissionscreate'; 37 | private static final String READ = 'permissionsread'; 38 | private static final String EDIT = 'permissionsedit'; 39 | private static final String DELET = 'permissionsdelete'; 40 | private static final String VAR = 'permissionsviewallrecords'; 41 | private static final String MAR = 'permissionsmodifyallrecords'; 42 | 43 | // 44 | // Be careful about the amount of viewstate to carry. Too much viewstate has a 45 | // negative impact on performance. Plus, it's just a bad idea to carry around stuff 46 | // that you don't need from request to request. 47 | // 48 | 49 | // The user is pretty central to this demonstration page and is needed for almost any 50 | // and every request. We don't actually hold on to a lot of user data, so this should 51 | // be OK to carry around the view state. 52 | private Id userId; 53 | public String userFullName { get; private set; } 54 | 55 | // Likewise, the list of permsets for which we're displaying information is pretty 56 | // important for every request, so we should hold on to that in viewstate. 57 | public List permsetInfo { get; private set; } 58 | 59 | // A note about the various MapHolder variables. These are Maps for permission set id 60 | // to access settings construct. Each of these maps will have one entry with a null 61 | // key which represents the users total access. 62 | 63 | // User perm info is only used for the initial request, so don't hold on to it. The 64 | // comments on the following lines identify the patterns used for variables declared 65 | // in subsequent blocks. 66 | private transient List xUserPermLabels; // API Names and Labels, and IDs 67 | private transient MapHolder xUserPermStatus; // Which perms are enabled for this user 68 | private transient Map xUserPerms; // Map from permset Id to PermissionSet Object, 69 | // which has the userperm settings 70 | 71 | // Likewise, object perm info is only used for the initial request, so don't hold on to it 72 | private transient List xObjectLabels; 73 | private transient MapHolder xObjectStatus; 74 | // Map from permset id -> object name -> ObjectPermissions object. 75 | private transient Map> xObjectPerms; 76 | 77 | // FLS info used for rendering is only used a single time per request and not needed 78 | // from request to request, so we don't need to hold on to it. 79 | private transient List xFieldLabels; 80 | private transient MapHolder xFieldStatus; 81 | // Map from permset id -> field name -> FieldPermissions object. This is the set of 82 | // FLS data relevant for the object defined by flsObjectType (see below) 83 | private transient Map> xFls; 84 | 85 | // flsObjectType contains the name of the object for which we display FLS information. 86 | // the VisualForce page uses this as a way to handle actionFunctions, allowing for 87 | // click-based interaction via javascript 88 | public String flsObjectType { 89 | get { 90 | if (null != userId && null == flsObjectType) { 91 | // Default flsObjectType to the first "visible" object. 92 | for (NameLabel nl : getObjectLabels()) { 93 | if (getObjectPermStatus().it.get(nl.apiName)) { 94 | flsObjectType = nl.apiName; 95 | break; 96 | } 97 | } 98 | } 99 | return flsObjectType; 100 | } 101 | set { 102 | if (value != flsObjectType) { 103 | // When setting flsObjectType, ensure that all the 104 | // FLS stuff is cleared out so that we are forced to 105 | // recalculate 106 | xFls = null; 107 | xFieldLabels = null; 108 | xFieldStatus = null; 109 | flsObjectType = value; 110 | } 111 | } 112 | } 113 | 114 | // ApexClass info is only used during the initial request. Don't need to hold on to it. 115 | private transient List xClassNames; 116 | private transient Map> xApexClasses; 117 | 118 | // Likewise, we don't need to hold onto VisualForce page information. 119 | private transient List xPageNames; 120 | private transient Map> xPages; 121 | 122 | public UserAccessDetailsController() { 123 | // Expect a uid Query Parameter 124 | if (ApexPages.currentPage().getParameters().containsKey('uid')) { 125 | lookup(ApexPages.currentPage().getParameters().get('uid')); 126 | } 127 | } 128 | 129 | /** 130 | * Looks up the target user and loads all their permission set info (name / label) 131 | */ 132 | private void lookup(Id uid) { 133 | try { 134 | User userData = [SELECT Id, Name, 135 | (SELECT PermissionSet.Id, PermissionSet.Name, PermissionSet.Label, 136 | PermissionSet.IsOwnedByProfile, PermissionSet.Profile.Name 137 | FROM PermissionSetAssignments) 138 | FROM User 139 | WHERE Id = :uid]; 140 | userId = userData.Id; 141 | userFullName = userData.Name; 142 | 143 | NameLabel totalAccess = new NameLabel('1TOTAL_ACCESS', 'TOTAL ACCESS'); 144 | NameLabel profilePermsetInfo; 145 | List nls = new List(); 146 | for (PermissionSetAssignment psa : userData.PermissionSetAssignments) { 147 | if (psa.PermissionSet.isOwnedByProfile) { 148 | profilePermsetInfo = new NameLabel( 149 | psa.PermissionSet.Id, psa.PermissionSet.Name, psa.PermissionSet.Profile.Name 150 | ); 151 | } else { 152 | nls.add(new NameLabel( 153 | psa.PermissionSet.Id, psa.PermissionSet.Name, psa.PermissionSet.Label 154 | )); 155 | } 156 | } 157 | 158 | permsetInfo = new List(); 159 | permsetInfo.add(totalAccess); 160 | permsetInfo.add(profilePermsetInfo); 161 | if (!nls.isEmpty()) { 162 | nls.sort(); 163 | permsetInfo.addAll(nls); 164 | } 165 | } catch (DMLException ex) { 166 | // Don't care? 167 | } 168 | } 169 | 170 | /** 171 | * Use Apex describe information to determine the set of User Permissions available within 172 | * this organization. 173 | */ 174 | public List getUserPermLabels() { 175 | if (null == xUserPermLabels) { 176 | List result = new List(); 177 | Map fMap = Schema.SObjectType.PermissionSet.fields.getMap(); 178 | for (String fName : fMap.keySet()) { 179 | if (fName.startsWith(PERMISSIONS)) { 180 | String label = fMap.get(fName).getDescribe().getLabel(); 181 | result.add(new NameLabel(fName, label)); 182 | } 183 | } 184 | result.sort(); 185 | xUserPermLabels = result; 186 | } 187 | return xUserPermLabels; 188 | } 189 | 190 | /** 191 | * Load the permission set information. Yes, we could slightly optimize this by loading 192 | * this at the same time we load the permission set label information, but separating 193 | * the two keeps the code cleaner. 194 | */ 195 | public Map getUserPerms() { 196 | if (null == xUserPerms) { 197 | Map result = new Map(); 198 | // Compute total access along the way. 199 | PermissionSet totalAccess = new PermissionSet(); 200 | result.put(null, totalAccess); 201 | for (SObject sobj : queryUserPerms(extractPsIds(permsetInfo))) { 202 | PermissionSet ps = (PermissionSet) sobj; 203 | result.put(ps.Id, ps); 204 | for (NameLabel nl : getUserPermLabels()) { 205 | // Total access computation 206 | totalAccess.put(nl.apiName, 207 | getWithDefault(totalAccess, nl.apiName, false) 208 | || getWithDefault(sobj, nl.apiName, false)); 209 | } 210 | } 211 | xUserPerms = result; 212 | } 213 | return xUserPerms; 214 | } 215 | 216 | /** 217 | * Helper function to encapsulate default handling of Boolean fields. This is 218 | * assuredly overkill, but oh well... 219 | */ 220 | private Boolean getWithDefault(SObject sobj, String fld, Boolean dflt) { 221 | try { 222 | if (null == sobj.get(fld)) return false; 223 | return (Boolean) sobj.get(fld); 224 | } catch (Exception ex) { 225 | return dflt; 226 | } 227 | } 228 | 229 | /** 230 | * Basically provides an alternate representation of total access to make our 231 | * grid display consistent between access settings. 232 | */ 233 | public MapHolder getUserPermStatus() { 234 | if (null == xUserPermStatus) { 235 | PermissionSet totalAccess = getUserPerms().get(null); 236 | Map msb = new Map(); 237 | for (NameLabel nl : getUserPermLabels()) { 238 | msb.put(nl.apiName, (Boolean) totalAccess.get(nl.apiName)); 239 | } 240 | xUserPermStatus = new MapHolder(msb); 241 | } 242 | return xUserPermStatus; 243 | } 244 | 245 | /** 246 | * Transforms a list of NameLabel objects into just a List of Id objects 247 | */ 248 | private List extractPsIds(List psInfo) { 249 | List result = new List(); 250 | for (NameLabel nl : psInfo) { 251 | if (null != nl.Id) result.add(nl.Id); 252 | } 253 | return result; 254 | } 255 | 256 | /** 257 | * Queries all the available user perms from the permission set object. Makes 258 | * use of getUserPermLabels, which uses Apex Describe to get the set of available 259 | * user perms. This is used to construct a dynamic SOQL query. 260 | */ 261 | private List queryUserPerms(List psIds) { 262 | String soql = 'SELECT Id'; 263 | for (NameLabel nl : getUserPermLabels()) { 264 | soql += ', ' + nl.apiName; 265 | } 266 | soql += ' FROM PermissionSet WHERE Id IN :psIds'; 267 | return Database.query(soql); 268 | } 269 | 270 | /** 271 | * Computes the API Name and Label information for the set of business objects available 272 | * to the organization 273 | */ 274 | public List getObjectLabels() { 275 | if (null == xObjectLabels) { 276 | List result = new List(); 277 | Map describe = Schema.getGlobalDescribe(); 278 | 279 | // We can't really depend on describe to give us the object we want 280 | // since that includes a lot of stuff that doesn't support CRUD 281 | // or other supporting bits of metadata. We're really just interested 282 | // in business data, so we'll rely on the set of object perms from 283 | // the system administrator profile to tell us what those objects are. 284 | // 285 | // Note: this is likely not a viable long-term solution, but it works 286 | // for now. 287 | for (ObjectPermissions op : [SELECT SObjectType FROM ObjectPermissions 288 | WHERE Parent.Profile.Name = 'System Administrator']) { 289 | result.add(new NameLabel(op.SObjectType, 290 | describe.get(op.SObjectType).getDescribe().getLabel())); 291 | } 292 | result.sort(); 293 | xObjectLabels = result; 294 | } 295 | return xObjectLabels; 296 | } 297 | 298 | /** 299 | * List of object perm API Names, in the order we wish them displayed 300 | */ 301 | public List getObjectPermFieldNames() { 302 | return new String[] { CREATE, READ, EDIT, DELET, VAR, MAR }; 303 | } 304 | 305 | /** 306 | * 307 | */ 308 | public MapHolder getObjectPermStatus() { 309 | if (null == xObjectStatus) { 310 | Map msb = new Map(); 311 | for (NameLabel nl : getObjectLabels()) { 312 | // Converts total access into our MapHolder data structure. 313 | msb.put(nl.apiName, 314 | getWithDefault(getObjectPerms().get(null).get(nl.apiName), READ, false)); 315 | } 316 | xObjectStatus = new MapHolder(msb); 317 | } 318 | return xObjectStatus; 319 | } 320 | 321 | /** 322 | * Computes the set of object permissions for all permsets assigned to the user, 323 | * including the user's total access 324 | */ 325 | public Map> getObjectPerms() { 326 | if (null == xObjectPerms) { 327 | Map> result = new Map>(); 328 | 329 | // Ensure that each permset and object has an entry in our map structure 330 | for (NameLabel ps : permsetInfo) { 331 | result.put(ps.Id, new Map()); 332 | for (NameLabel op : getObjectLabels()) { 333 | result.get(ps.Id).put(op.apiName, new ObjectPermissions()); 334 | } 335 | } 336 | 337 | // Create a record for total access and also make sure that it has 338 | // an ObjectPermissions entry for every object. 339 | Map totalAccess = new Map(); 340 | result.put(null, totalAccess); 341 | for (NameLabel op : getObjectLabels()) { 342 | totalAccess.put(op.apiName, new ObjectPermissions()); 343 | } 344 | 345 | // Retrieve the set of ObjectPermissions in our permission sets and use 346 | // that to populate our result 347 | for (ObjectPermissions op : [SELECT Id, SObjectType, ParentId, 348 | PermissionsCreate, PermissionsRead, PermissionsEdit, PermissionsDelete, 349 | PermissionsViewAllRecords, PermissionsModifyAllRecords 350 | FROM ObjectPermissions 351 | WHERE ParentId IN :extractPsIds(permsetInfo)]) { 352 | Map opMap = result.get(op.ParentId); 353 | opMap.put(op.SObjectType, op); 354 | ObjectPermissions totalOp = totalAccess.get(op.SObjectType); 355 | for (String apiName : getObjectPermFieldNames()) { 356 | totalOp.put(apiName, getWithDefault(totalOp, apiName, false) || getWithDefault(op, apiName, false)); 357 | } 358 | } 359 | 360 | xObjectPerms = result; 361 | } 362 | return xObjectPerms; 363 | } 364 | 365 | /** 366 | * Returns the set of FLS settings we're interested in, in the order we wis to display them 367 | */ 368 | public List getFlsPermFieldNames() { 369 | return new String[] { READ, EDIT }; 370 | } 371 | 372 | /** 373 | * Computes the list of fields. NOTE: there is a subtle bug in here. Because it uses 374 | * describe, it will return the set of fields available to the viewing user, not the user 375 | * in question. There isn't a good way around this, as far as I know. In reality, this 376 | * shouldn't be much of an issue here since a user won't have FieldPermissions assigned 377 | * for fields they do not have access to 378 | */ 379 | public List getFieldLabels() { 380 | if (null == flsObjectType) return null; 381 | if (null == xFieldLabels) { 382 | List result = new List(); 383 | Map fmap = Schema.getGlobalDescribe().get(flsObjectType).getDescribe().fields.getMap(); 384 | for (String key : fmap.keySet()) { 385 | result.add(new NameLabel(key, fmap.get(key).getDescribe().getLabel())); 386 | } 387 | result.sort(); 388 | xFieldLabels = result; 389 | } 390 | return xFieldLabels; 391 | } 392 | 393 | /** 394 | * This is the action invoked by the VisualForce actionFunction. 395 | */ 396 | public void loadFls() { 397 | // Nothing to do here. The action call her sets flsObjectType, and getFls() does the loading lazily. 398 | } 399 | 400 | /** 401 | * Computes the set of FLS information for display, including total access calculation 402 | */ 403 | public Map> getFls() { 404 | if (null == xFls) { 405 | Map> result = new Map>(); 406 | Set psIds = new Set(); 407 | 408 | for (NameLabel ps : permsetInfo) { 409 | result.put(ps.Id, new Map()); 410 | psIds.add(ps.Id); 411 | } 412 | 413 | for (FieldPermissions fp : [SELECT ParentId, SObjectType, Field, PermissionsRead, PermissionsEdit 414 | FROM FieldPermissions 415 | WHERE ParentId IN :psIds 416 | AND SObjectType = :flsObjectType]) { 417 | if (!fp.Field.startsWith(fp.SObjectType + '.')) continue; 418 | String fName = fp.Field.substring(fp.SObjectType.length() + 1).toLowerCase(); 419 | result.get(fp.ParentId).put(fName, fp); 420 | if (!result.get(null).containsKey(fName)) { 421 | result.get(null).put(fName, fp.clone(false)); 422 | } else { 423 | FieldPermissions tot = result.get(null).get(fName); 424 | tot.PermissionsEdit |= fp.PermissionsEdit; 425 | tot.PermissionsRead |= fp.PermissionsRead; 426 | } 427 | } 428 | 429 | // Some fields do not support FLS, but the user is considered to have 430 | // certain permissions on them. These settings are controlled by 431 | // something other than FLS, but for the purposes of this exercise, 432 | // these settings are included in the user's total access. NOTE: 433 | // because we use describe, the information is returned as appropriate 434 | // for the viewing user, not the user under scrutiny. As such, the 435 | // information may be subtly incorrect. Caveat Emptor. 436 | Map fmap = Schema.getGlobalDescribe().get(flsObjectType).getDescribe().fields.getMap(); 437 | for (String key : fmap.keySet()) { 438 | DescribeFieldResult fDesc = fmap.get(key).getDescribe(); 439 | if (!fDesc.isPermissionable()) { 440 | result.get(null).put(fDesc.getName().toLowerCase(), new FieldPermissions( 441 | PermissionsRead = fDesc.isAccessible(), 442 | PermissionsEdit = fDesc.isUpdateable() 443 | )); 444 | } 445 | } 446 | xFls = result; 447 | } 448 | return xFls; 449 | } 450 | 451 | public MapHolder getFieldStatus() { 452 | if (null == xFieldStatus) { 453 | Map msb = new Map(); 454 | for (NameLabel nl : getFieldLabels()) { 455 | for (NameLabel ps : permsetInfo) { 456 | if (!getFls().get(ps.Id).containsKey(nl.apiName)) { 457 | getFls().get(ps.Id).put(nl.apiName, new FieldPermissions()); 458 | } 459 | } 460 | msb.put(nl.apiName, getFls().get(null).get(nl.apiName).PermissionsRead); 461 | } 462 | xFieldStatus = new MapHolder(msb); 463 | } 464 | return xFieldStatus; 465 | } 466 | 467 | /** 468 | * Computes the list of classes available in the org 469 | */ 470 | public List getClassNames() { 471 | if (null == xClassNames) { 472 | List result = new List(); 473 | for (ApexClass ac : [SELECT Id, Name FROM ApexClass]) { 474 | result.add(new NameLabel(ac.Id, ac.Name, ac.Name)); 475 | } 476 | result.sort(); 477 | xClassNames = result; 478 | } 479 | return xClassNames; 480 | } 481 | 482 | /** 483 | * Computes which Apex Classes are available per permset 484 | */ 485 | public Map> getApexClasses() { 486 | if (null == xApexClasses) { 487 | xApexClasses = calculateSetupEntityAccess('ApexClass', getClassNames()); 488 | } 489 | return xApexClasses; 490 | } 491 | 492 | public MapHolder getClassStatus() { 493 | return new MapHolder(getApexClasses().get(null)); 494 | } 495 | 496 | /** 497 | * Computes which Visual Force pages are present in the org 498 | */ 499 | public List getPageNames() { 500 | if (null == xPageNames) { 501 | List result = new List(); 502 | for (ApexPage ap : [SELECT Id, Name FROM ApexPage]) { 503 | result.add(new NameLabel(ap.Id, ap.Name, ap.Name)); 504 | } 505 | result.sort(); 506 | xPageNames = result; 507 | } 508 | return xPageNames; 509 | } 510 | 511 | public Map> getPages() { 512 | if (null == xPages) { 513 | xPages = calculateSetupEntityAccess('ApexPage', getPageNames()); 514 | } 515 | return xPages; 516 | } 517 | 518 | public MapHolder getPageStatus() { 519 | return new MapHolder(getPages().get(null)); 520 | } 521 | 522 | /** 523 | * Both ApexClass and ApexPage access settings are stored as SetupEntityAccess objects. As such, 524 | * they can easily be processed using common logic with some variables used to account for 525 | * differences 526 | */ 527 | private Map> calculateSetupEntityAccess(String seaType, List seaNames) { 528 | Map> result = new Map>(); 529 | Map seaMap = new Map(); 530 | 531 | Map total = new Map(); 532 | result.put(null, total); 533 | 534 | // Queries all the SetupEntityAccess objects of the appropriate type, and adds 535 | // the info to the result datastructure 536 | Map> enabledByPermset = new Map>(); 537 | for (SetupEntityAccess sea : [SELECT ParentId, SetupEntityId 538 | FROM SetupEntityAccess 539 | WHERE SetupEntityType = :seaType]) { 540 | Set enabled = enabledByPermset.get(sea.ParentId); 541 | if (null == enabled) { 542 | enabled = new Set(); 543 | enabledByPermset.put(sea.ParentId, enabled); 544 | } 545 | enabled.add(sea.SetupEntityId); 546 | } 547 | 548 | // Ensure that each permset and setup entity has a row in our result datastructure. This 549 | // keeps VisualForce happy when it does Map processing. 550 | for (NameLabel ps : permsetInfo) { 551 | if (null != ps.Id) { 552 | Map seaStatus = result.get(ps.Id); 553 | if (null == seaStatus) { 554 | seaStatus = new Map(); 555 | result.put(ps.Id, seaStatus); 556 | } 557 | for (NameLabel nl : seaNames) { 558 | boolean hasSea = enabledByPermset.containsKey(ps.Id) && enabledByPermset.get(ps.Id).contains(nl.id); 559 | seaStatus.put(nl.apiName, hasSea); 560 | if (hasSea || !total.containsKey(nl.apiName)) total.put(nl.apiName, hasSea); 561 | } 562 | } 563 | } 564 | 565 | return result; 566 | } 567 | } -------------------------------------------------------------------------------- /UserAccessVisualization/src/classes/UserAccessDetailsController.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 28.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /UserAccessVisualization/src/components/PermsetAccessTable.component: -------------------------------------------------------------------------------- 1 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |   42 | 43 | 44 | 45 | 47 | 48 |
49 |
{!heading.label}
50 |
51 |
52 | 53 |
54 |
55 |
56 |
-------------------------------------------------------------------------------- /UserAccessVisualization/src/components/PermsetAccessTable.component-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 25.0 4 | This is a generic template for Visualforce Component. With this template, you may adjust the default elements and values and add new elements and values. 5 | 6 | 7 | -------------------------------------------------------------------------------- /UserAccessVisualization/src/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | * 5 | ApexClass 6 | 7 | 8 | * 9 | ApexComponent 10 | 11 | 12 | * 13 | ApexPage 14 | 15 | 16 | * 17 | ApexTrigger 18 | 19 | 20 | * 21 | StaticResource 22 | 23 | 25.0 24 | 25 | -------------------------------------------------------------------------------- /UserAccessVisualization/src/pages/UserAccessDetails.page: -------------------------------------------------------------------------------- 1 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 49 |
50 | 52 |
53 |
54 |
55 | 56 | 57 | 58 | 59 | Legend: 60 | Create, 61 | Read, 62 | Edit, 63 | Delete, 64 | View All Records, 65 | Modify All Records 66 | 67 | 68 | 72 | 73 | 74 | 75 | 80 | 81 | 82 |
76 |
77 |   78 |
79 |
83 |
84 | 85 | 86 | 88 | 89 | 90 | 91 | 92 | 93 | 94 |
95 | 96 |
97 | 98 | 99 |
Field Level Security: {!flsObjectType}
100 | 104 | 105 | 106 | 107 | 110 | 111 | 112 |
108 |
 
109 |
113 |
114 |
115 |
116 |
117 | 118 | 119 | 121 |
122 | 124 |
125 |
126 |
127 | 128 | 129 | 131 |
132 | 134 |
135 |
136 |
137 |
138 |
139 |
-------------------------------------------------------------------------------- /UserAccessVisualization/src/pages/UserAccessDetails.page-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 25.0 4 | 5 | 6 | -------------------------------------------------------------------------------- /UserAccessVisualization/src/staticresources/css.resource: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2012, Salesforce.com, Inc. All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are 6 | * met: 7 | * 8 | * * Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in 13 | * the documentation and/or other materials provided with the 14 | * distribution. 15 | * 16 | * * Neither the name of Salesforce.com nor the names of its 17 | * contributors may be used to endorse or promote products derived 18 | * from this software without specific prior written permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | /* 33 | * PermsetAccessTable 34 | */ 35 | .permsetAccessTable { 36 | border-spacing: 0; 37 | border-collapse: collapse; 38 | } 39 | 40 | .permsetAccessTable thead { 41 | border-bottom: 1px solid black; 42 | vertical-align: bottom; 43 | } 44 | 45 | .headingContainer { 46 | position: relative; 47 | } 48 | 49 | .headingContainer .headingLabel { 50 | position: absolute; 51 | bottom: 0; 52 | -webkit-transform-origin: 0; 53 | -moz-transform-origin: 0; 54 | -ms-transform-origin: 0; 55 | } 56 | 57 | .permsetAccessTable tbody td { 58 | border-right: 1px solid lightgrey; 59 | } 60 | 61 | .permsetAccessTable tbody tr:last-child { 62 | border-bottom: 1px solid black; 63 | } 64 | 65 | .permsetAccessTable tbody tr { 66 | border-bottom: 1px solid lightgrey; 67 | } 68 | 69 | .permsetAccessTable tbody { 70 | border: 1px solid black; 71 | } 72 | 73 | .psLabel { 74 | padding: 0 5px 0 2px; 75 | } 76 | 77 | .colDisabled { 78 | display: none; 79 | } 80 | 81 | /* 82 | * User Perm Table, Apex Class Table, Apex Page Table 83 | */ 84 | .vfPageTable, 85 | .apexClassTable, 86 | .userPermTable { 87 | margin-right: 135px; 88 | } 89 | 90 | .vfPageTable th, 91 | .apexClassTable th, 92 | .userPermTable th { 93 | height: 150px; 94 | min-width: 22px; 95 | width: 22px; 96 | } 97 | 98 | .vfPageTable .headingContainer, 99 | .apexClassTable .headingContainer, 100 | .userPermTable .headingContainer { 101 | left: 10px; 102 | min-width: 21px; 103 | } 104 | 105 | .vfPageTable .headingContainer .headingLabel, 106 | .apexClassTable .headingContainer .headingLabel, 107 | .userPermTable .headingContainer .headingLabel { 108 | -webkit-transform: rotate(-34deg); 109 | -moz-transform: rotate(-34deg); 110 | -ms-transform: rotate(-34deg); 111 | } 112 | 113 | /* 114 | * Object Perm Table (and some FLS) 115 | */ 116 | .objectPermTable .headingContainer .headingLabel { 117 | -webkit-transform: rotate(-20deg); 118 | -moz-transform: rotate(-20deg); 119 | -ms-transform: rotate(-20deg); 120 | } 121 | 122 | .objectPermTable th { 123 | height: 40px; 124 | } 125 | 126 | .permsetAccessTable .flsDetail td, 127 | .permsetAccessTable .objectPermDetail td { 128 | border: none; 129 | overflow: hidden; 130 | } 131 | 132 | .objectPermField { 133 | width: 8px; 134 | } 135 | 136 | .objectColumn:hover { 137 | cursor: pointer; 138 | } 139 | 140 | .permissionscreate_true { 141 | background-color: #00FF00; 142 | } 143 | 144 | .permissionsread_true { 145 | background-color: #0000FF; 146 | } 147 | 148 | .permissionsedit_true { 149 | background-color: #FFFF00; 150 | } 151 | 152 | .permissionsdelete_true { 153 | background-color: #FF0000; 154 | } 155 | 156 | .permissionsviewallrecords_true { 157 | background-color: #00FFFF; 158 | } 159 | 160 | .permissionsmodifyallrecords_true { 161 | background-color: #FF00FF; 162 | } 163 | 164 | /* 165 | * FLS 166 | */ 167 | .flsTableContainer { 168 | display: inline-block; 169 | margin: 0; 170 | padding: 0; 171 | } 172 | 173 | .flsTableContainerHeading { 174 | font-weight: bold; 175 | text-align: center; 176 | margin-bottom: 3px; 177 | background-color: #666666; 178 | color: #FFFFFF; 179 | } 180 | 181 | .flsTable { 182 | margin-right: 85px; 183 | } 184 | 185 | .flsTable th { 186 | height: 80px; 187 | } 188 | 189 | .flsTable .headingContainer { 190 | left: 7px; 191 | } 192 | 193 | .flsCell { 194 | width: 10px; 195 | } 196 | 197 | .flsTable .headingContainer .headingLabel { 198 | -webkit-transform: rotate(-35deg); 199 | -moz-transform: rotate(-35deg); 200 | -ms-transform: rotate(-35deg); 201 | width: 120px; 202 | overflow: hidden; 203 | text-overflow: ellipsis; 204 | } 205 | 206 | /* 207 | * Misc 208 | */ 209 | .nowrap { 210 | white-space: nowrap; 211 | } 212 | 213 | .border-right { 214 | border-right: 1px solid black; 215 | } -------------------------------------------------------------------------------- /UserAccessVisualization/src/staticresources/css.resource-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Private 4 | text/css 5 | 6 | -------------------------------------------------------------------------------- /UserAccessVisualization/src/staticresources/jQuery.resource: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forcedotcom/user-access-visualization/4dcdb5b5084871ab13788cc353a7c6e4570cdcc9/UserAccessVisualization/src/staticresources/jQuery.resource -------------------------------------------------------------------------------- /UserAccessVisualization/src/staticresources/jQuery.resource-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Public 4 | application/zip 5 | 6 | -------------------------------------------------------------------------------- /UserAccessVisualization/src/staticresources/js.resource: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2012, Salesforce.com, Inc. All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are 6 | * met: 7 | * 8 | * * Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in 13 | * the documentation and/or other materials provided with the 14 | * distribution. 15 | * 16 | * * Neither the name of Salesforce.com nor the names of its 17 | * contributors may be used to endorse or promote products derived 18 | * from this software without specific prior written permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | j = jQuery.noConflict(); 33 | 34 | /* 35 | * Method to draw a polygon connection each 2d point found in pts 36 | */ 37 | function poly(context, xOffs, yOffs, pts) { 38 | var ii; 39 | context.moveTo(pts[0][0] + xOffs, pts[0][1] + yOffs); 40 | for (ii = 1; ii < pts.length; ii++) { 41 | context.lineTo(pts[ii][0] + xOffs, pts[ii][1] + yOffs); 42 | } 43 | context.closePath(); 44 | } 45 | 46 | /* 47 | * Wrapper used to invoke loadFls(..) with the appropriate arguments. 48 | * Uses jQuery to traverse the event target's hierarchy until it 49 | * finds an element matching the colName_* pattern. It then extracts 50 | * the column name from the CSS class and call loadFls with that. 51 | */ 52 | function showFls(e) { 53 | var tgt = j(e.target) 54 | while (!tgt.hasClass('objectColumn')) tgt = tgt.parent(); 55 | 56 | loadFls(/.*colName_([^ ]+)/.exec(tgt.attr('class'))[1], e); 57 | } 58 | 59 | /* 60 | * A pretty hacky function that draws our cool polygon on the HTML5 canvas. 61 | * I certainly don't know HTML5 Canvas APIs well, and I'm a complete thrasher 62 | * when it comes to HTML's box model, so the below, while functional may 63 | * not make much sense, either. :) 64 | * 65 | * Caveat Emptor. 66 | */ 67 | var lastLeft = null; 68 | function drawCanvas(e) { 69 | var jl = j('.fls-canvas'); 70 | if (jl.length == 0) return; // No canvas in which to draw 71 | var canvas = jl.get(0); 72 | var tgt = j(e.target); 73 | while (tgt.length > 0 && !tgt.hasClass('objectColumn')) tgt = tgt.parent(); 74 | if (tgt.length == 0) return; // No objectColumn ancestor 75 | var opPos = tgt.position(); 76 | if (lastLeft && opPos.left == lastLeft) return 77 | lastLeft = opPos.left; 78 | var flsTableElt = j('.flsTableContainer'); 79 | var flsPos = flsTableElt.position(); 80 | var context = canvas.getContext("2d"); 81 | var offs = 1; 82 | 83 | context.clearRect(0, 0, canvas.width, canvas.height); 84 | context.beginPath(); 85 | poly(context, offs, 0, [[0, canvas.height], 86 | [opPos.left - flsPos.left, 5], 87 | [opPos.left - flsPos.left, 0], 88 | [opPos.left - flsPos.left + tgt.width(), 0], 89 | [opPos.left - flsPos.left + tgt.width(), 5], 90 | [flsTableElt.width() - 2, canvas.height]]); 91 | 92 | context.lineWidth = 1; 93 | context.fillStyle = '#666666'; 94 | context.strokeStyle = '#666666'; 95 | context.fill(); 96 | context.stroke(); 97 | } 98 | 99 | /* 100 | * When the document is loaded, compute the appropriate width for the FLS table display, 101 | * setup some click handlers for FLS display, and then draw our cool polygon for the first 102 | * object with object perms. 103 | */ 104 | j(document).ready(function() { 105 | j('.fls-canvas').attr('width', Math.max(j('.objectPermTable').width(), j('.flsTableContainer').width())); 106 | j('.objectPermDetail').click(showFls); 107 | j('.objectPermTable .headingLabel').click(showFls); 108 | drawCanvas({target: j('.objectPermTable td.objectColumn.colEnabled').get(0)}); 109 | }); -------------------------------------------------------------------------------- /UserAccessVisualization/src/staticresources/js.resource-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Private 4 | text/javascript 5 | 6 | --------------------------------------------------------------------------------