Daily Processing For Permission Set Access
I came across a use case earlier this month where a Salesforce business owner needed to be able to role out some functionality in a series of waves. Basically, the business was planning on running a pilot with a subset of users for a period of time and then at some interval they planned on granting access to additional users. The business owners wanted to be able to maintain that access and they wanted to be able to turn it on easily.
The org in which this logic was being utilized was large (approximately 5,000 users) and the users that would be accessing the functionality were grouped into territories but the management of that territory designation was simply a field on the User record not the out-of-the-box territory management provided by Salesforce natively.
Also, the waves were going to span a number of months and access was anticipated to be to a couple hundred users at a time.
You are probably thinking that the natural choice here is to use permission sets to grant application/functionality access. I agree. The issue with permission sets is the maintenance. One-off assignment to a given permission set is fine but granting access to many people requires a little more work and manually could be done with the data loader. But, as I mentioned, the business owners wanted something simple and easy. So no data loader.
The final solution was to create a permission set with the proper configuration and object privileges but then to automate the process of granting access. This was accomplished by creating a table (custom setting) and populating that table with the various territory values that each User might have. Then I created a checkbox (boolean) field for denoting when a specific territory should have access to the permission set. Next, I created some batch apex logic that would do the heavy lifting of granting access to the permission set. Finally, I scheduled that apex logic to run on a daily basis.
By scheduling the batch process to run daily, the business owners could indicates the territories that should and should not have access to the fundamental application logic and know that their User's would have access granted/revoked that same day. This allowed the business owners the flexibility they were seeking without having to ask for development or admin help, which could cause delays to their processes.
Now that you know the background you can make some sense of the apex class below:
/*
Created by: Greg Hacic
Last Update: 10 February 2014 by Greg Hacic
Questions?: greg@interactiveties.com
*/
global class batchPermissionSetAccess implements Database.Batchable<SObject> {
//the "start" method is called at the beginning of a batch Apex job
//use this method to collect the records (of objects) to be passed to the "execute" method for processing
global Database.QueryLocator start(Database.BatchableContext BC) {
//query for Users with the active territory values
Set<String> activeTerritories = new Set<String>(); //holds customTerritoryObject__c values
String soql = 'SELECT Id FROM User WHERE isActive = true AND'; //query string variable
for (sasiCTARegions__c t : [SELECT territoryName__c FROM customTerritoryObject__c WHERE isActive__c = true]) { //for all active territories
activeTerritories.add(t.territoryName__c); //add the territory name to our list
}
if (activeTerritories.isEmpty()) { //if we did not find any active territories
soql += ' Territory__c = \'nothingtoquerytoday\''; //use some random filter to prevent logic from actually getting SOQL results
} else { //otherwise, we found some territories
soql += ' ('; //open parenthesis for SOQL statement filters
Integer counter = 0; //create a counter variable
for (String a : activeTerritories) { //for all active territories
counter++; //increment the counter
if (counter != 1) { //if the counter is not equal to 1
soql += ' OR'; //append the OR filter
}
soql += ' Territory__c = \''+a+'\''; //append the spcific territory filter
}
soql += ')'; //close the parenthesis
}
return Database.getQueryLocator(soql); //run the query
}
//the "execute" method is called after the "start" method has been invoked and passed a batch of records
global void execute(Database.BatchableContext BC, List<SObject> scope) {
List<PermissionSetAssignment> newPermissionSetAccess = new List<PermissionSetAssignment>(); //list for new permission sets
Set<String> usersWithAccess = new Set<String>(); //set for users Ids with access to the permission set already
Id psaId; //Id of the permission set we want Users to be assigned
//query for the permission set Id
for (PermissionSet ps : [SELECT Id FROM PermissionSet WHERE Name = 'permissionSetAPIName']) {
psaId = ps.Id; //assign the premission set Id
for (PermissionSetAssignment psa : [SELECT AssigneeId FROM PermissionSetAssignment WHERE PermissionSetId = :ps.Id]) { //query for the permission set Users that are already assigned
usersWithAccess.add(psa.AssigneeId); //add the Id of each assigned User to our set
}
}
//compare the list of all users to the list of already granted users and make another list of those missing access
//take that list of missing access and grant access
for (SObject s : scope) { //for all objects from our batch
User u = (User)s; //grab the individual User record
if (!usersWithAccess.contains(u.Id)) { //if the User is not in our 'already has access' set
PermissionSetAssignment newPSA = new PermissionSetAssignment(); //PermissionSetAssignment sobject
newPSA.PermissionSetId = psaId; //set the permission set Id
newPSA.AssigneeId = u.Id; //set the User Id
newPermissionSetAccess.add(newPSA); //add the record to our list
}
}
if (!newPermissionSetAccess.isEmpty()) { //if there are records to insert
insert newPermissionSetAccess; //insert
}
}
//the "finish" method is called once all the batches are processed
global void finish(Database.BatchableContext info) {
//do nothing...
}
}
Again, this Apex class is the logic that is scheduled to run on a daily basis. You will need the scheduleable class in order to actual create the schedule. Let me know if you need something like that and I can make publicly available on this site...