All pages
1 of 2

Loading...

Loading...

Scripts

These scripts are intended for use only when Auto Config does not work in your Salesforce org. They are not required for most customers.

Creating the GRAX_Integration_User Permission Set manually

  1. Open the Salesforce Developer Console

  2. Open the Debug menu

  3. Select Open Execute Anonymous Window (or press CTRL + E)

  4. Copy the script below into the Enter Apex Code dialog

  5. Select the Open Log checkbox

  6. Click Execute

  7. Assign the new 'GRAX_Integration_User' permission set to the GRAX Integration User.

Fixing Field Level Security manually

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:

  1. Very large number of objects

  2. 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

  1. Open the

  2. Open the Debug menu

  3. Select Open Execute Anonymous Window (or press CTRL + E)

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

  1. Navigate to /web/tools on your GRAX Application, or use the link at the top right of the Settings page

  2. Select the Missing Field Permissions option

  3. Select the Show Apex Script option for the intended object

Repeat the steps above for each necessary object.

Copy the script below into the Enter Apex Code dialog
  • Select the Open Log checkbox

  • Click Execute

  • In the Execution Log window, select the Debug 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.

  • Copy the generated script to your clipboard

  • Open the Salesforce Developer Console

  • Open the Debug menu

  • Select Open Execute Anonymous Window (or press CTRL + E)

  • Copy the script below into the Enter Apex Code dialog

  • Select the Open Log checkbox

  • Click Execute

  • In the Execution Log window, select the Debug Only checkbox

  • The script outputs the number of corrected fields in the last log line.

  • Salesforce Developer Console
    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);
                    }
                }
            }
        }
    }
    // 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());
        }
    }

    Integration User

    For optimal security and performance, we require the use of a 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. GRAX uses this user for reading metadata and records for backup, deleting records for archives, and writing new records for restores. We refer to this user as the GRAX Integration User.

    Creating an Integration User

    Within Salesforce, create a dedicated integration user for GRAX with access to all objects you wish to back up. We recommend using a clear and descriptive name, such as GRAX Integration. This user needs a User License of Salesforce. The System Administrator profile is the easiest to get started with.

    The SFDC Free Platform Integration User license (API-only users) cannot be used for the GRAX Integration user as this license type does not support several of the high level permissions required by GRAX.

    Integration User Permissions

    Auto Config

    GRAX Auto Config automatically creates the GRAX Integration user permission set with the recommended configuration in Salesforce and assigns the permission set to the GRAX Integration User (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

    Overview

    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. This Permission Set is created automatically by GRAX Auto Config; do not manually create a Permission Set with this name.

    Feature
    Permission
    Required/Recommended
    Notes

    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 .

    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. This can lead to fields missing completely from your backups. GRAX addresses this limitation in several ways:

    1. GRAX will automatically set all known fields to be readable by the Integration User permission set during initial setup.

    2. All users will be shown a warning banner explaining how many objects and fields are missing Field Level access when they log in to GRAX.

    3. GRAX provides an in-app tool for resolving Field Level Security issues on demand without the need to manually update each object yourself.

    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

    Mitigating Security Risks

    Due to the nature of the GRAX Application, it is important to understand the security risks associated with the GRAX Integration User. The GRAX Integration User is a highly privileged user that has access to all data in your Salesforce org. This user is used to read, write, and delete data in your Salesforce org. If this user is compromised, it could lead to data loss or data corruption. To mitigate this risk, we recommend the following:

    1. Enable Multi-Factor Authentication (MFA) for the GRAX Integration User.

      MFA is only required at time of login, after which GRAX will use a refresh and access token to interact with Salesforce. The Salesforce admin will need an MFA code at the time of setting up GRAX, but will not need to provide an MFA code again unless the refresh token is revoked or expires.

    2. Restrict login IP address ranges for the Integration User's profile.

      Logins and requests will only come from GRAX's HQ server, the VM running your GRAX environment, and any admins who may need to set up GRAX initially. This means only 4 static IP addresses need to be allowed to log in as the GRAX Integration User in most cases.

    More information on secure best-practices for Integration Users can be found .

    Integration User Token Refresh

    There may be the occasional need to refresh the tokens associated with the integration user due to a sandbox refresh, expired/revoked tokens, or server configuration changes. If this is the case, you will receive an email notification and you will also see a banner notification within the GRAX Application upon admin log-in. To reauthorize the Integration User, either click on the GRAX Auto Config button in the email and follow the prompts, or navigate to the Settings page in the application, expand the Salesforce section, click on Update in the blue box, and follow the prompts.

    Reference Docs

    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 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.

    Insights

    Manage Users

    Recommended

    Ensures that GRAX can monitor data and file storage details, enabling proactive management of your Salesforce storage quota and providing visibility into the impact of archiving.

    Enforce login IP ranges on every request.

    This is a Salesforce setting that enforces the IP login ranges on every request instead of just at login. Documentation can be found here. This can prevent hijacking of a session and reuse by malicious actors.

  • Lock sessions to the IP address from which they originated.

    This is another Salesforce setting that prevents sessions from changing IP after creation including switching between allowed IP ranges. Documentation can be found here.

  • Enable "API Only" restrictions on the Integration User's profile.

    This prevents the user from having UI access to your Salesforce org. While this doesn't limit the user's data access, it limits the usability of the user if compromised. Documentation can be found here.

  • Set Audit Fields upon Record Creation Permission
  • Salesforce CRM Content User

  • Marketing User

  • View Encrypted Data

  • Licensing for Managed Packages

  • Field Level Security

  • All Features

    API Enabled

    Required

    Required for login and API access by the integration user.

    Backup (Records)

    View All Data

    Recommended

    Ensures that records are included in Backup regardless of sharing rules.

    View Encrypted Data

    Recommended

    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.

    Enable the 'Create Audit Fields' permission guide
    Auto Config
    here
    API Enabled Permission
    View All Data Permission
    Modify All Data Permission
    Query All Files Permission

    Ensures that encrypted fields are included in the Backup. Omitting this causes encrypted fields to be absent from backup data.