30 September 2013

Build Your Own Custom Sharing Button

Judi Sohn gave me a great use case last Friday on twitter. While discussing the issue of whether to use only profiles or also use permission sets, the question of why a user has access to a record came up.

This is an interesting question because there is a sharing button on every record designed to provide this information. However, there are two shortcomings with using this button:

  1. The sharing button is disabled if org wide defaults are set to public read/write for an object
  2. It only tells you about sharing rows, not the object permissions contained in a user's profile or permission sets

As a result, it's possible to use this button to see which users have access to a record, only to find out that the user lacked the object permissions necessary to work with this record.

However, there is a workaround for these issues. You can create your own custom sharing button that will take both sharing and profile or permission set permissions into consideration, regardless of the sharing settings on the object.

For instance, we have a user, Doug Bitting. His profile grants him View Setup and Configuration permission and nothing else.

While on an account record, we can click on the sharing button to view which users have access to the record as long as accounts do not have their org wide default set to public read/write.

Viewing this sharing page tells us that our user, Doug Bitting, has access to the record even though he doesn't actually have the object permissions necessary to read the record.

By clicking on a custom button that I added called 'User Access', I can look up more information on what level of access a user like Doug actually has. This button launches a visualforce page with a custom apex controller in a new window.

In this case, when I look up Doug Bitting, I find out that he doesn't have access to the record.

I can add access to accounts through an accountRead permission set that has read on accounts.

Now when I use the custom 'User Access' button, I find that Doug actually has the read access I need him to have.

I built this solution with a custom button, visualforce page, and custom apex controller.

The button can be applied to any object where sharing and custom buttons are supported. Here's an example of the account button that I created. All that is required is to pass in the record Id in the URL when the page is called.

The apex code is pretty simple. The first part enables you to find a user using SOQL.

    // query user from the User's Name input Text
   public void queryUserName() {
    // prevent SOQL Injection - oh no Mr. Bill!
    String queryU = '%' + uName + '%'; 
    // create query passing in queryLabel from input text in page
    // this can easily be changed with other search parameters 
    // only return one user,may require narrowing search results
    queryUser = 
   [SELECT Id, Username, Name, Title, Profile.Name, UserRole.Name
    FROM User
    WHERE Name like :queryU
    OR Title like :queryU
    OR Profile.Name like :queryU 
    ORDER By Name
    LIMIT 1];

The second part enables you to use the user Id from the SOQL query and the record Id from the URL in the UserRecordAccess where clause to determine the user's actual access.

   // Determine User Access based on the queryUser  
  // and the Id passed in through page reference in the URL 
   public void queryAccess()
       // Store queryUser results in a new object
       User u = [SELECT Id 
                 FROM User 
                 WHERE Id = :queryUser];
       // Extract the Id from the user results
       String uId = u.Id;     
       // UserRecordAccess using the userId and recordId 
       ura = [SELECT MaxAccessLevel,RecordId
              FROM UserRecordAccess 
              WHERE RecordId =      :ApexPages.currentPage().getParameters().get('Id') 
                    AND UserId = :uId];

This solution works because the UserRecordAccess sObject which is available in the API. If you haven't seen this before, it's a great way to determine who has access to any record by combining profiles, permission sets, and sharing together.

The source code for the apex page and controller can be found on my github page: https://github.com/atorman/CustomSharingButton. Feel free to use it although there is some more work that I am planning to do so keep in mind that there is more to come.

While this doesn't address the original intent of the twitter conversation, it does address a couple of shortcomings with the Sharing button in particular.

23 September 2013

Master Record Types with Permission Sets

I had an interesting question come up the other day about permission sets and record types.

The customer wanted to remove all record type assignments from their user's profiles and configure record type assignments on two different permission sets. Unfortunately, that's not possible since, unlike other settings on permission sets, record types have a default setting and the profile is the only place where we can set that default. In addition, the 'Master' or 'null' record type simply means we won't set any record type when a record is created or edited.

For example, if I have a profile called 'Account Only' with the Master record type defaulted:

and I assign it to John Doe user with no permission sets:

when I Login-As John Doe and create a new Account, the Master record type (null) is automatically selected and I do not get a jump screen where I can choose a different record type:

I log out from John Doe and create a permission set with one of five record types selected:

and another permission set with the rest of the record types selected:

Now when I assign the single record type permission set to the John Doe user:

and login as John Doe to create a new Account record, I still skip the jump screen and the one custom record type (note Master is ignored in the single record type case) is added to the newly created record:

I log out from John Doe user and change his permission set assignment to grant access to the remaining record types:

Now when I log in as John Doe user, I get the jump screen and can select from a list of different custom record types:

This behavior is unlike any other setting that we've migrated to permission sets from profile in that it has a default that remains on the profile and it's possible to have that default set to a 'null' value called Master.

As a guideline for using the Master and default record types with permission sets, if I have:
  1. Only Master [default on profile and no permission sets] - the user will skip the jump screen and the Master (null) record type will automatically be set on the newly created record
  2. Master defaulted on the profile + only 1 custom record type on a permission set - the user will skip the jump screen and the custom record type will automatically be set on the newly created record
  3. Master defaulted on the profile + > 1 custom record type on one or more permission sets -  the user will get the jump screen every time in order to choose which custom record type they want to assign to the record they are creating

16 September 2013

What's in a profile name? Well, apparently a lot! Migrating standard profiles using the MdAPI

I had an interesting scenario come up the other day while trying to migrate some permissions on both standard and custom profiles using the Metadata API (MdAPI).

It turns out that what we call the 'System Administrator' standard profile actually maps to the Admin.profile file in the MdAPI. This means, when retrieving the System Administrator profile, you'll find all of the permissions and settings in the Admin.profile file in the profile directory.

Here's where it goes a little sideways. If you create a custom profile called 'Admin', there's some ambiguity when you try to migrate permissions for the 'System Administrator' standard profile which goes by the same name.

As a result, given the right order of operations, the System Administrator profile will be ignored when migrating permissions for the custom Admin profile using the MdAPI.

The same is true for any of the standard profiles:

Standard Profile Name in the User InterfaceStandard Profile File Name in the MdAPI
System AdministratorAdmin.profile
Standard UserStandard.profile
Marketing UserMarketingProfile.profile
Contract ManagerContractManager.profile
Solution ManagerSolutionManager.profile
Read OnlyReadOnly.profile
Customer Portal ManagerCustomerManager.profile
Customer Portal UserCustomerUser.profile
High Volume Customer PortalHighVolumePortal.profile
Partner UserPartner.profile
Authenticated WebsitePlatformPortal.profile
Standard Platform UserStandardAul.profile

Give it a try - steps to reproduce:
  1. retrieve 'Admin' profile in MdAPI which retrieves the 'System Administrator' profile
  2. add an assigned app visibility to the .Admin profile file and deploy
  3. System Administrator profile updates with additional app visibility
  4. clone System Administrator profile and call it 'Admin'
  5. retrieve 'Admin' profile in MdAPI which retrieves the cloned/custom 'Admin' profile thereby ignoring the 'System Administrator' profile
  6. add an assigned app visibility to the .Admin profile file and deploy
  7. custom Admin profile updates with additional app visibility, System Administrator profile is ignored
Use the following steps to find out if you already have any custom profiles with one of these names and rename them:
  1. log into workbench
  2. add the ability to query parent relationships by going to Workbench > Settings and enabling 'Allows SOQL Parent Relationship Queries' before selecting 'Apply Settings' button at the top of the screen 
  3. run the following query by going to queries > SOQL Query:

SELECT Profile.Id, Profile.Name FROM PermissionSet WHERE
(Profile.Name = 'Admin' OR
 Profile.Name = 'Standard' OR
 Profile.Name = 'MarketingProfile' OR
 Profile.Name = 'ContractManager' OR
 Profile.Name = 'SolutionManager' OR
 Profile.Name = 'ReadOnly' OR
 Profile.Name = 'CustomerManager' OR
 Profile.Name = 'CustomerUser' OR
 Profile.Name = 'HighVolumePortal' OR
 Profile.Name = 'Partner' OR
 Profile.Name = 'PlatformPortal' OR
 Profile.Name = 'StandardAul')

As a result of this edge case, the best practice is to name custom profiles something different from what the MdAPI understands as a standard profile name in order to effectively migrate permissions between environments.