Minimum access to create/delete Salesforce scratch orgs

Working with scratch orgs in Salesforce requires that the DevHub feature is enabled in a non-sandbox (i.e. Production, Developer or Trial) org and that you’ve authenticated to the org. Normally from your Salesforce CLI or CI/CD pipeline.

Since the DevHub is required to be enabled in Production this has led to some concern from some customers I’ve spoken to. Following up on this with a customer who now really needed the scratch org capability I set out to figure out the least amount of access required to the DevHub Salesforce org.

It’s important to remember that license is one thing another is object access. If you are out of licenses Salesforce

Well one thing is license another is access. License seems well described but not so access. Played a bit around with access and was able to get it working with minimum access to an org. I started with a no-access profile and added API access and then figured out object access required. Minimum access required to create a scratch org is:
Read, Create on ScratchOrgInfos
Read on ActiveScratchOrgsTo delete a scratch org you need:
Read, Create, Edit on ScratchOrgInfos
Read, Create, Edit, Delete on ActiveScratchOrgs

Unable to force:source:push ExperienceBundle from API version 50.0 with API version 51.0

Yesterday I was trying to deploy some source including an Experience Cloud Site (using ExperienceBundle) created with API version 50.0 to a scratch org but it failed when I updated the API version to 51.0 in sfdx-project.json . Deploying with API version 50.0 worked just fine. The deploy (w/ API version 51.0) failed with the following message:

Error  force-app/main/default/experiences/Digital_Capability_Assessment_Aura1.site-meta.xml  You seem to be missing the property configurationTags in Digital_Capability_Assessment_Aura1/routes/register.json with component ID: f7c5ea49-0bde-4848-a72f-82ace4ea6760

And the error message was right – that key is not in the file but that’s was also true for some of the other Experience Cloud files. Deploying with API version 50.0 and trying to pull with API version 51.0 didn’t change any source. Clever people told me that this was expected as nothing changed in the org (regardless of a publish or similar).

Solution was to deploy with force:source:push with API version 50.0 and then do a force:source:retrieve specifying API version 51.0 on the command line. Then afterwards toggle the API version in sfdx-project.json.

sfdx force:source:retrieve -u dca_scratch_comm -a 51.0 -m ExperienceBundle

Turning on trace debugging with the Salesforce CLI

When using the Salesforce CLI as the primary way to interact with a scratch org turning on Apex trace debugging can be a tiresome-I-have-to-click-into-the-org situation. Usually you are not able to use force:apex:log:list and force:apex:log:get commands to work with Apex logs without first opening the Web Developer in the org or setting up trace logging.

But it turns out there is a better way using a simple script.

The below script does the trick for you can easily be added to your process for creating new scratch orgs. The script sets up some timestamps and then queries for the scratch org user userId. Then we get the Id of the trace log configuration for the user and then updates the record to enable trace logging for 24 hours.

NOW=`date -u +"%Y-%m-%dT%H:%M:%SZ"`
EXP=`date -v+24H -u +"%Y-%m-%dT%H:%M:%SZ"`

USERID=`sfdx force:data:soql:query -q "select id,name from user where name='User User'" --json | jq ".result.records[0].Id" -r`

TRACEID=`sfdx force:data:soql:query --query "SELECT Id, DebugLevel.DeveloperName, ExpirationDate, TracedEntityId FROM Traceflag WHERE TracedEntityId IN (SELECT ID from USER WHERE ID = '${USERID}')" --usetoolingapi --json | jq ".result.records[0].Id" -r`

sfdx force:data:record:update --sobjecttype TraceFlag --sobjectid $TRACEID -v "StartDate=$NOW ExpirationDate=$EXP" --usetoolingapi --json --loglevel fatal

The above script uses jq (https://stedolan.github.io/jq/) for JSON parsing and works on Mac (date command switches is slightly different on Linux) so YMMV.

Salesforce API Postman Collection

If you develop with the Salesforce API’s the following will save you a lot of time and make your life a lot easier. In our ongoing effort to make the Salesforce API’s easier to consume our Developer Relations have built a Postman Collection with 200+ requests. I know I’ve spent a lot of time to maintain my own collection for demos – that time is over!

The collection covers the following Salesforce APIs:

  • Auth
  • Bulk (V1 & V2)
  • Async Query
  • Rest
  • UI
  • Tooling
  • Metadata
  • Composite
  • Chatter
  • Connect

The collection and instructions are available on Github and there is even a webinar to help you get going.

Time Warp now on the AppExchange

For a while you’ve been able to install the #AWESOME timeline Lightning Web Component from Github (https://github.com/deejay-hub/timeline-lwc) into any Salesforce org using CLI tools or similar. Besides being an awesome example how just how nice components you can build with Salesforce it also serves as nice addition to any org to provide nice visualizations.

It just came to my attention that the component has been renamed to Time Warp and is available on the AppExchange (https://appexchange.salesforce.com/appxListingDetail?listingId=a0N4V00000GXVf4UAH). w00t! Being on the AppExchange makes it even easier to install and I highly recommend you take a look at this awesome component.

Image from Github, https://github.com/deejay-hub/timeline-lwc

(Image from Github, https://github.com/deejay-hub/timeline-lwc)

Export private key from Salesforce in Java Keystore format and convert to PEM format

Today I needed to use the JWT flow to get an access token from Salesforce to use the Salesforce API in a server to server scenario. To do that you need a private key (usually in PEM format) for the client and the corresponding public key in a keystore on the Salesforce side. Being lazy I simply generated a public / private key pair in Salesforce with an exportable private key and exported the keystore.

Since Salesforce exports the keystore in Java Keystore Format (JKS) I need to work with the Java keytool and openssl to export the private key. Below are the steps. The landing_site_pk alias below is the same of the private key entry in the keystore and the API name of the keystore in Salesforce and Passw0rd is the password specified when exporting the keystore. Adjust as needed.

$ keytool -importkeystore -srckeystore ./00D090000046d3F.jks -srcstorepass Passw0rd -srcalias landing_site_pk -destalias landing_site_pk -destkeystore ./00D090000046d3F.p12 -deststoretype PKCS12 -deststorepass Passw0rd -destkeypass Passw0rd
Importing keystore ./00D090000046d3F.jks to ./00D090000046d3F.p12...

$ openssl pkcs12 -in ./00D090000046d3F.p12 -nodes -nocerts -out 00D090000046d3F.pem
Enter Import Password:
MAC verified OK

$ cat 00D090000046d3F.pem
Bag Attributes
friendlyName: landing_site_pk
localKeyID: 54 69 6D 65 20 31 36 30 36 33 39 30 30 35 36 38 30 38
Key Attributes:
-----BEGIN PRIVATE KEY-----
MIIEuwIBA....bTe+Hzyz
-----END PRIVATE KEY-----

Adding myself back into a Salesforce Community

Playing around with Salesforce Communities I accidentally removed my own profile from the Members of the Community. This in effect locked me out of administering the Community in that I couldn’t access the Builder or the Workspaces. Bummer! What to do? Now this was a test environment and I was the only user in the org so not exactly critical but I wanted to remember the way I got back in.

As I had full administrator rights to the org I could use the NetworkMemberGroup object to add myself (or rather the Profile I was mapped to) back into the Community. Now this cannot be done from Apex so it has to be done using the REST API, Data Loader or similar. I opted for the REST API approach.

To do this simply POST to the object as you would any other object passing in the networkId (the ID of the Community) and the parentId (the ID of the Profile or Permission Set to grant access).

POST https://mydomain.my.salesforce.com/services/data/v49.0/sobjects/NetworkMemberGroup
Content-Type: application/json
Content-Length: <length>
Authorization: Bearer <access_token>

{"networkId": "0DB3V000000blOT","parentId": "00e3V000000OkzG"}