Andy in the Cloud

From BBC Basic to and beyond…

Permission Sets and Packaging


Permission Sets have been giving me and an unlucky few who are unable to install my Declarative Rollup Summary Tool some headaches since i introduced them. Thankfully I’ve recently discovered why and thought it worth sharing along with some other best practices i have adopted around packaging permission sets since…

Package install failures due to Permission Sets

After including Permission Sets in my package, I found that some users where failing to install the package, they received via email, the following type of error (numbers in brackets tended to differ).

Your requested install failed. Please try this again.
None of the data or setup information in your organization should have been 
 affected by this error.
If this error persists, contact Support through your normal channels
 and reference number: 604940119-57161 (929381962)

It was frustrating that i could not help them decode this message, it required them to raise a Salesforce support case and pass their org ID and error codes on the case. For those that did this, it became apparent after several instances, a theme was developing, a package platform feature dependency!

Normally such things surface in the install UI for the user to review and resolve. Dependencies also appear when you click the View Dependencies button prior to uploading your package. However it seems that Salesforce is currently not quite so smart in the area of Permission Sets and dependency management, as these are hidden from both!

Basically i had inadvertently packaged dependencies to features like Ideas and Streaming API via including my Permission Sets, one of which i had chosen to enable the Author Apex permission. This system permission seems to enable a lot of other permissions as well as enabling other object permissions (including optional features like Ideas, which happened to be enabled in my packaging org). Then during package install if for historic reasons the subscriber org didn’t have these features enabled the rather unhelpful error above occurs!

Scrubbing my Permission Sets

I decided enough was enough and wanted to eradicate any Salesforce standard permission from my packaged Permission Sets. This was harder than i thought, since simply editing the .permissionset file and uploading it didn’t not remove anything (this approach is only suitable for changing or adding permissions within a permission set).

Next having removed entries from the .permissionset file, i went into the Permission Set UI within the packaging org and set about un-ticking everything not related to my objects, classes and pages. This soon became quite an undertaking and i wanted to be sure. I then recalled that Permission Sets are cool because they have an Object API!

I started to use the following queries in the Developer Console to view and delete on mass anything i didn’t want, now i was sure i had removed any traces of any potentially dangerous and hidden dependencies that would block my package installs. I obtained the Permission Set ID below from the URL when browsing it in the UI.

select Id, SobjectType, PermissionsCreate, PermissionsEdit, PermissionsDelete, PermissionsRead
  from ObjectPermissions where ParentId = '0PSb0000000HT5A'
select Id, SobjectType, Field
  from FieldPermissions where ParentId = '0PSb0000000HT5A'


Note that System Permissions (such as Author Apex) which are shown in the UI, currently don’t have an Object API, so you cannot validate them via SOQL. However at least these are listed on a single page so are easier to check visually.

Lessons learned so far…

So here are a few lessons i’ve learned to date around using Permission Sets…

  • Don’t enable System Permissions unless your 100% sure what other permissions they enable!
  • When editing your .permissionset files don’t assume entries you remove will be removed from the org.
  • Use SOQL to query your Permission Sets to check they are exactly what you want before uploading your package
  • Don’t make your Permission Sets based on a specific User License type (this is covered in the Salesforce docs but i thought worth a reminder here as it cannot be undone).
  • Do design your Permission Sets based on feature and function and not based on user roles (which are less likely to match your perception of the role from one subscriber to another). Doing the former will reduce the chances of admins cloning your Permission Sets and loosing out on them being upgraded in the future as your package evolves.

9 thoughts on “Permission Sets and Packaging

  1. Good tips on using SOQL to verify the state of the permission set. A few noteworthy items:

    * The shape of the .permissionset metadata file depends on your package.xml. i.e. it omits objects/classes/etc. that are not part of your package.xml so you can inadvertantly think you have a canonical representation of the PermissionSet when you really don’t
    * System permissions or permissions on Standard objects don’t propagate to subscriber orgs — we’ve never seen the behavior you report (with an Internal Error upon install) — normally they’re just silently ignored
    * To ‘work around’ the lack of support for system permissions and standard object permissions, you can manipulate the PermissionSet model via Apex — see this thread — we are cautious in recommending this approach since it does seem to capitalize on a loophole in Apex:

    • Thanks David, good additions!

      When i learnt the install failures are due to system object references in the permission sets i also thought, “but they don’t get copied across anyway?”. However i can only assume that standard object permissions get filtered out after whatever validation occurs on them. I’ve seen it really only in older orgs, where new features are not enabled. It’s of course a platform bug and now that i understand it i’m going to try to reproduce it.

      Either way it still bugs me these references are laying around in the packaging org permission sets and getting packaged at all if Salesforce has no intention of using them in subscriber orgs.

  2. Reblogged this on Code Avenues and commented:
    I didn’t get time to write any post yet. I think it is better to share the good stuff I see around. It is a wonderful article based on totally personal experience of Andy. Thanks Andy.

  3. After years of Profile and PermissionSet headaches, I went back to the drawing board and figured out an approach to them which has made my life and deployments much simpler ever since.

    The essence is that my .profile and .permissionset files contain POSITIVE PERMISSIONS ONLY. As we all know, when SFDC downloads a profile, it includes every single permission, positive or negative, yielding sprawling, multi-thousand-line files with dependencies on every custom field and tons of org-specific permissions. I wrote a nodejs script to scrub those files, removing all “false” permissions, leaving much shorter files (20-200 liness, typically), which cause me fewer dependency headaches b/c it’s easy for me to verify that they only contain the permissions needed.

    The downside is that there’s now an extra step if I want to deploy a change where I remove a previously granted permission from a profile. I either need to manually add it back in to my file for deployment, or I need to do this as a manual post-deployment step in the environment. It’s such a rare case, though, that it’s been a much smaller problem.

    I should say that I do most of my work in-house, with unmanaged packages, and don’t have insight into how this approach would work with managed packages. My guess is that the deployment benefit is smaller, but if you’re using a DCVS, this approach would help you find (and eliminate) unnecessary permissions and dependencies faster.

    • Awesome insight, yes, including only want you want to ‘enable’ is key followed by never retrieve them from Salesforce as it adds so much junk. Thanks for sharing!

      • Script to scrub unwanted permissions from metadata files following a full retrieve: .gist table { margin-bottom: 0; } This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters Show hidden characters /* By Benj Kamm, */ // Look for the value of the first text node within the current node. // Returns 'false' if not found, otherwise the value of the first text node in the subnode. function getXMLNodeValue(el, key) { var matches = el.getElementsByTagName(key); if (!matches.length) { return 'false'; } return matches[0].childNodes[0].nodeValue; } // Look for all permissions of type <tagName>, return as an array function getXMLTags(doc, tagName) { return; } module.exports = { getXMLNodeValue: getXMLNodeValue, getXMLTags: getXMLTags }; view raw common.js hosted with ❤ by GitHub This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters Show hidden characters /* By Benj Kamm, */ var gulp = require('gulp'), profileCleaner = require('./profileCleaner.js'); gulp.task('cleanProfiles', function() { var SRC_URLS = [ 'src/profiles/*.profile', 'src/permissionsets/*.permissionset' ]; return gulp.src(SRC_URLS, {base: './'}) .pipe(profileCleaner()) .pipe(gulp.dest('./')); }); view raw gulpfile.js hosted with ❤ by GitHub This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters Show hidden characters // through2 is a thin wrapper around node transform streams var through = require('through2'); var DOMParser = require('xmldom').DOMParser; var pd = require('pretty-data').pd; var gutil = require('gulp-util'); var getPermissionValue = require('./common').getXMLNodeValue; var getXMLTags = require('./common').getXMLTags; // Removes all empty permission nodes from metadata xml files for Profile and PermissionSet function removeEmptyPermissions(content) { content = content.toString(); var doc = new DOMParser().parseFromString(content); // Remove empty <applicationVisibilities> // – Profiles must have visible=false and default=false. // – PermisisionSets must have visible=false (no default tag) getXMLTags(doc, 'applicationVisibilities').forEach(function(el) { if (getPermissionValue(el, 'visible') === 'false' && getPermissionValue(el, 'default') === 'false') { el.parentNode.removeChild(el); } }); // Remove empty <classAccesses> – enabled=false getXMLTags(doc, 'classAccesses').forEach(function(el) { if (getPermissionValue(el, 'enabled') === 'false') { el.parentNode.removeChild(el); } }); // Remove empty <fieldPermissions> – editable=false and readable=false. getXMLTags(doc, 'fieldPermissions').forEach(function(el) { if (getPermissionValue(el, 'editable') === 'false' && getPermissionValue(el, 'readable') === 'false') { el.parentNode.removeChild(el); } }); // Remove empty <objectPermissions> – only if no access at all getXMLTags(doc, 'objectPermissions').forEach(function(el) { if (getPermissionValue(el, 'allowCreate') === 'false' && getPermissionValue(el, 'allowDelete') === 'false' && getPermissionValue(el, 'allowEdit') === 'false' && getPermissionValue(el, 'allowRead') === 'false' && getPermissionValue(el, 'modifyAllRecords') === 'false' && getPermissionValue(el, 'viewAllRecords') === 'false') { el.parentNode.removeChild(el); } }); // Remove empty <pageAccesses> – enabled=false. getXMLTags(doc, 'pageAccesses').forEach(function(el) { if (getPermissionValue(el, 'enabled') === 'false') { el.parentNode.removeChild(el); } }); // Remove empty <recordTypeVisibilities> – visible=false. getXMLTags(doc, 'recordTypeVisibilities').forEach(function(el) { if (getPermissionValue(el, 'visible') === 'false') { el.parentNode.removeChild(el); } }); // Remove empty <tabSettings> – visibility=None. getXMLTags(doc, 'tabSettings').forEach(function(el) { if (getPermissionValue(el, 'visibility') === 'None') { el.parentNode.removeChild(el); } }); // remove blank lines return pd.xml(doc.toString()); /* var stream = through(); stream.write(prefixText); return stream; */ } // Plugin level function(dealing with files) function profileCleaner() { var filesProcessed = 0; // Creating a stream through which each file will pass var stream = through.obj(function(file, enc, callback) { filesProcessed += 1; if (file.isNull()) { // Do nothing if no contents } if (file.isBuffer()) { file.contents = new Buffer(removeEmptyPermissions(file.contents)); } if (file.isStream()) { console.log('stream'); //file.contents = through().write(removeEmptyPermissions(file.contents)); } this.push(file); return callback(); }, function(callback) { gutil.log(gutil.colors.cyan('cleanProfiles')+':', 'Processed', gutil.colors.magenta(filesProcessed), 'files'); callback(); }); // returning the file stream return stream; }; // Exporting the plugin main function module.exports = profileCleaner; view raw profileCleaner.js hosted with ❤ by GitHub
      • Awesome thanks for sharing!

  4. Hi Andrew, I love what you’ve done with the Declarative Rollup Summary Tool. However, I’m having problems deploying it into my sandbox. I’m pretty new to Github, but I think I’ve done what I’m supposed to. However, I get the error message “No Salesforce files found in repository”. I’m not sure how to fix this, or who to ask for help. I’m not sure if this is the right place to ask, but happy to discuss through pm’s if that’s more appropriate.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s