Integration User
GRAX Auto Config
creates the GRAX Integrations user permission set with the recommended configuration in Salesforce and assigns the permission set to the user you use to first connect the GRAX Application to your Salesforce org. This user is then used by GRAX to interact with Salesforce. We refer to this as the GRAX Integration User.
Once connected, the integration user may be reviewed or updated within the Settings
tab of your GRAX app. GRAX uses this user for reading metadata and records for backup, deleting records for archives, and writing new records for restores. We require that you use dedicated Salesforce user and Permission Set for GRAX, rather than sharing a user and/or profile for GRAX and other integrations. This simplifies security, allows GRAX to automatically enforce and monitor permission problems, allows you to better audit issues, and maximizes concurrent API request limits that Salesforce imposes.
For general Salesforce best practices on creating integration users, see the Permission Sets example here.
Salesforce Permissions
Required permissions are feature-specific where possible, allowing users to scope GRAX access as narrowly as possible for their use case while protecting against data loss where necessary. The table below illustrates the permissions required by each major feature. For rows marked "recommended," the GRAX product won't block usage of the feature without the related permission, but care should be taken to avoid data loss.
For compatibility with GRAX-provided scripts, these permissions must be assigned via a Permission Set named GRAX_Integration_User
.
Feature | Permission | Required/Recommended | Notes |
---|---|---|---|
All Features | API Enabled | Required | Required for login and API access by the integration user. |
Auto Backup (Records) | View All Data | Recommended | Ensures that records are included in Auto Backup regardless of sharing rules. |
View Encrypted Data | Recommended | Ensures that encrypted fields are included in Auto Backup. Omitting this causes encrypted fields to be absent from backup data. | |
Auto Backup (Files) | Query All Files | Required | Ensures access to read all files for backup regardless of library and sharing rules. Omitting this may lead to a significant number of files being missed in Auto Backup. |
Archive | View All Data | Recommended | Ensures Archive verification can find and match against necessary records. Omitting this may cause Archive verifications to fail. |
Modify All Data | Recommended | Ensures records contained within the Archive are able to be deleted. Omitting this may cause Archive executions to fail during the delete phase. | |
Restore | Modify All Data | Recommended | Ensures records can be updated and modified to match the record versions being restored. Omitting this may cause Restores to fail during record modification/creation. |
Create Audit Fields | Recommended | Ensures original audit field values can be written for restore. Omitting this causes restored records to list the integration user as the source of creates or modifications to records instead of the original editor/creator. |
NOTE: to grant the "Set Audit Fields upon Record Creation" permission, you must first enable it at the organization level under the "User Interface" menu within Setup. Look for the two-in-one option labeled "Enable 'Set Audit Fields upon Record Creation' and 'Update Records with Inactive Owners' User Permissions." See the Enable the 'Create Audit Fields' permission guide.
Creating the GRAX_Integration_User Permission Set manually
- Open the Salesforce Developer Console
- Open the
Debug
menu - Select
Open Execute Anonymous Window
(or pressCTRL + E
) - Copy the script below into the
Enter Apex Code
dialog - Select the
Open Log
checkbox - Click
Execute
- Assign the new 'GRAX_Integration_User' permission set to the GRAX Integration User.
PermissionSet piu = new PermissionSet(
Name = 'GRAX_Integration_User',
Label = 'GRAX Integration User Permission',
Description='Grants users permission to read and update records, fields and files for GRAX backup, archive and restore',
PermissionsApiEnabled=true,
PermissionsViewAllData=true,
PermissionsModifyAllData=true,
PermissionsQueryAllFiles=true
);
DescribeSobjectResult permissionSetDescribe = Schema.PermissionSet.SObjectType.getDescribe();
Map<String, SObjectField> fieldMap = permissionSetDescribe.fields.getMap();
List<String> availablePermissions = new List<String>();
for (String fieldName : fieldMap.keySet()) {
if (!fieldName.startsWithIgnoreCase('Permissions')) {
continue;
}
if (fieldName == 'PermissionsCreateAuditFields') {
System.debug('Setting PermissionsCreateAuditFields=true');
piu.put(fieldName, true);
}
if (fieldName == 'PermissionsViewAllData' ||
fieldName == 'PermissionsModifyAllData' ||
fieldName == 'PermissionsQueryAllFiles') {
continue;
}
DescribeFieldResult fd = fieldMap.get(fieldName).getDescribe();
if (fd.isCreateable() && fd.isUpdateable()) {
availablePermissions.add(fieldName);
}
}
Integer count = 1;
while (count < 11) {
try {
insert piu;
count = 100;
} catch(DmlException e) {
count++;
List<String> splitError = e.getMessage().split(' ');
for (String str : splitError){
string check = str.removeEnd(',');
check = check.removeEnd(':');
for (String fieldName : availablePermissions){
if (fieldName == 'Permissions' + check){
piu.put(fieldName, true);
}
}
}
}
}
Recommended User Settings
These settings modify the Salesforce features that users are allowed to access. Without these, GRAX may not be able to read certain portions of Salesforce data. They can be assigned from the "User" page within Salesforce Setup.
Permission | Comments |
---|---|
Salesforce CRM Content User | Ensures access to read and write all Content Documents and related binary data. |
Marketing User | Ensures access to read Campaign and related objects. |
Field Level Security
Given how SFDC permission sets work, even when "View All Data" is given it's possible that GRAX is missing access to read fields on objects in a way that is transparent to the Integration User.
To help detect and correct this, GRAX offers two approaches:
- Apex script to fix as many objects as possible at once
- Web tool to generate object-specifc Apex scripts
Option 1: Apex FLS Script
The GRAX-provided Apex script below is an effective solution for updating field-level permissions on a large number of objects. However, there are a few cases in which the script may not be ideal:
- Very large number of objects
- Very large number of fields
The script makes a best effort to handle Apex row/batch limits. This means that you need to run the script multiple times if a large number of objects are processed. The script outputs Apex batch limits reached. Please run again for next batch.
if another run is required.
If you encounter Apex limit errors, errors related to specific objects, or validation errors while running this script, fallback to the web-tools option below for updating the objects that remain.
NOTE: this section assumes you've used the script above to provision a GRAX_Integration_User
Permission Set. If you don't have a permission set with this name in your org, the script fails.
ALSO NOTE: this script grants access to fields we're able to find in the Salesforce metadata. This may not be a comprehensive list. GRAX Admins should review the GRAX_Integration_User
Permission Set's Field Permissions for each object to ensure access is granted to all fields.
Running the FLS Script
- Open the Salesforce Developer Console
- Open the
Debug
menu - Select
Open Execute Anonymous Window
(or pressCTRL + E
) - Copy the script below into the
Enter Apex Code
dialog - Select the
Open Log
checkbox - Click
Execute
- In the
Execution Log
window, select theDebug Only
checkbox - If the last log entry prompts a re-run, return to the
Execute Anonymous Window
and repeat the subsequent steps until the last entry no longer prompts a re-run.
// decrease the maxBatchSize if a `System.LimitException: Too many query rows: 50001` error is returned
Integer maxBatchSize = 1000;
PermissionSet[] lpsGIU = [SELECT Id FROM PermissionSet WHERE name = 'GRAX_Integration_User'];
if (lpsGIU.isempty()) {
System.debug('GRAX_Integration_User PermissionSet not found');
return;
}
// Use the 25 oldest permissionSets with ViewAllData Permissions to model the fields to grant access to
PermissionSet[] lpsModel = [
SELECT Id
FROM PermissionSet
WHERE PermissionsViewAllData = true
AND id != :lpsGIU[0].Id
ORDER BY CreatedDate
LIMIT 25];
List<Id> modelPSIds = new List<Id>();
for (PermissionSet psID : lpsModel) {
Id tmpID = (Id) psID.get('Id');
modelPSIds.add(tmpID);
}
if (modelPSIds.isempty()) {
// No viewAllData PermissionSet were found, use the 25 oldest permissionSets
PermissionSet[] lpsProfile = [
SELECT Id
FROM PermissionSet
WHERE ProfileId != ''
AND id != :lpsGIU[0].Id
ORDER BY CreatedDate
LIMIT 25];
for (PermissionSet psID : lpsProfile) {
Id tmpID = (Id) psID.get('Id');
modelPSIds.add(tmpID);
}
}
if (modelPSIds.isempty()) {
System.debug('no PermissionSets to model');
return;
}
system.debug(modelPSIds);
List<AggregateResult> aggHas = new List<AggregateResult>([
SELECT SObjectType
FROM FieldPermissions
WHERE ParentID = :lpsGIU[0].Id
GROUP BY SObjectType]);
Set<String> hasObj = new Set<String>();
for (AggregateResult obj : aggHas) {
String tmpObj = (String) obj.get('SobjectType');
hasObj.add(tmpObj);
}
List<AggregateResult> aggAll = new List<AggregateResult>([
SELECT SObjectType
FROM ObjectPermissions
GROUP BY SObjectType
ORDER BY SObjectType
]);
String [] objectsToFix = new List<String>();
for (AggregateResult obj : aggAll) {
String tmpObj = (String) obj.get('SobjectType');
if (!hasObj.contains(tmpObj)){
objectsToFix.add(tmpObj);
}
if (objectsToFix.size() >= maxBatchSize) {
System.debug('Max batch size reached. Please run again for next batch.');
break;
}
}
List<AggregateResult> aggFP = new List<AggregateResult>([
SELECT SObjectType, min(Id) Id
FROM FieldPermissions
WHERE SObjectType IN :objectsToFix
AND ParentId IN :modelPSIds
GROUP BY SObjectType, Field
]);
Map<String, List<Id>> fieldsToFix = new Map<String, List<Id>>();
for (AggregateResult fpID : aggFP) {
String tmpObj = (String) fpID.get('SObjectType');
Id tmpID = (Id) fpID.get('Id');
if (fieldsToFix.containsKey(tmpObj)) {
fieldsToFix.get(tmpObj).add(tmpID);
} else {
fieldsToFix.put(tmpObj, new List <Id> { tmpID });
}
}
for(String obj : objectsToFix){
if (Limits.getQueries() > 90 || Limits.getQueryRows() > 40000) {
System.debug('Apex batch limits reached. Please run again for next batch. exiting');
break;
}
if (!fieldsToFix.containsKey(obj)) {
System.debug('No permissionable field found in object ' + obj + ' - skipping');
continue;
}
list<FieldPermissions>dup=[
SELECT Id, SobjectType, Field
FROM FieldPermissions
WHERE SObjectType = :obj
AND ParentId = :lpsGIU[0].Id
];
list<FieldPermissions>fields=[SELECT SobjectType, Field FROM FieldPermissions WHERE Id IN :fieldsToFix.get(obj)];
Integer count = 0;
List<FieldPermissions> listOfFieldPermissions = new List<FieldPermissions>();
for(FieldPermissions fp : fields){
count++;
FieldPermissions newFP = new FieldPermissions(
Field = fp.Field,
SobjectType = fp.SobjectType,
ParentId = lpsGIU[0].Id,
PermissionsRead = true
);
for(FieldPermissions d : dup){
if (fp.Field == d.Field) {
newFP.id=d.id ;
}
}
listOfFieldPermissions.add(newFP);
}
try {
upsert listOfFieldPermissions;
System.debug('Completed successfully for object ' + obj + ' - granted access to ' + String.valueOf(count) + ' fields ');
} catch(DmlException e) {
System.debug('Error updating [' + obj + ']: ' + e.getMessage());
}
}
Option 2: Web-tools Script Builder
GRAX Web-tools can generate an FLS script that is scoped to a single-object at a time. This prevents many issues with Apex limits that may result from the larger script above while still making life easy for SFDC admins.
Generating and Running a Script
- Navigate to
/web/tools
on your GRAX app, or use the link at the top right of theSettings
page - Select the
Missing Field Permissions
option - Select the
Show Apex Script
option for the intended object - Copy the generated script to your clipboard
- Open the Salesforce Developer Console
- Open the
Debug
menu - Select
Open Execute Anonymous Window
(or pressCTRL + E
) - Copy the script below into the
Enter Apex Code
dialog - Select the
Open Log
checkbox - Click
Execute
- In the
Execution Log
window, select theDebug Only
checkbox - The script outputs the number of corrected fields in the last log line.
Repeat the steps above for each necessary object.
Reference Docs
Updated 14 days ago