├── 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 |