A Step-by-Step Guide to Restoring Guest User Permissions After Spring ’21 Using Flow
Co-Authors: Hemant Bhatia, and Simon Ta, Salesforce Consultants at Bluewave Technology
As many will know by now, the Salesforce Spring ’21 Release revoked guest user permissions including View All, Modify All, Edit, and Delete for both custom and standard objects. A “guest” user referring to any context in which an unauthenticated experience runs and the ramifications are pretty far reaching. (Think: site.com, force.com, Communities). In this post, we outline a declarative solution with flow to partially restore these permissions and potentially save your existing guest user related business processes.
Guest Users Are No Longer Able to Attach Files
That’s right, the news gets a little worse before it starts to get better. Any business processes or workflows that rely on guest users being able to Edit existing records (even those they own), are curtailed by Spring ’21. This means that guest users can no longer attach files to existing records. For example, the Create Case Form component, a standard Salesforce component available for Experience Cloud, contains a file upload subcomponent which is no longer visible when the Create Case Form renders in the context of a guest user. Beyond declarative implications, the standard Apex Lightning Input now also prevents a guest user from attaching files. “Wow,” you might be thinking, “these are some major changes, so isn’t it about time for some good news?” We think so!
How Salesforce Flow Saves The Guest User Experience
As a starting point, let’s zero in on the ability to attach files to records because many community implementations involve an unauthenticated guest user attaching a file to a record. Working in a screen flow, for example, users intuitively relate files to a record in the database simply because the flow itself can keep track of the record’s id (from a record created or retrieved during flow runtime). Unfortunately from Spring ’21, when executed in the context of a guest user, this relationship between uploaded files and existing records is severed resulting in a runtime exception if you attempt a DML.
insufficient access rights on cross-reference id
Salesforce provides a clear explanation for this intentional runtime exception.
The file upload component screen is [now] configured without mapping the record Id into it… so the file is essentially uploaded without a parent, and the guest user no longer has access to it – which is what we would want as a secure approach for anonymous users uploading files.
Guest User Record Access Development Best Practices | Pattern 9
Accepting that a guest user no longer has Edit access to the Related Record ID above, we will have to get a little bit creative with the running context of flow. But before we continue solutioning, let’s briefly recap the database objects at play here.
|ContentDocument||Represents a Salesforce file. The record id being a ContentDocumentId.|
|ContentDocumentLink||Represents a junction between a Salesforce file and where it’s shared (a LinkedEntityId)|
|ContentVersion||Represents a specific version of a Salesforce file|
Thanks to a Spring ‘21 update to the file upload screen component pictured above, we will be returned a list of ContentVersionIds for the uploaded file(s). We also do have the Related Record ID available during Flow runtime (We just can’t create ContentDocumentLink records within the unauthenticated guest user context). See where we are going? Instead, let’s try passing the record data we do have into a subflow set to run in “System Context Without Sharing-Access All Data.”
Subflow To The Rescue!
So that you can recreate this solution, let’s go through each step one by one.
- First, we will be creating a new autolaunched flow set to run in “System Context Without Sharing-Access All Data.”
- Next, because we plan to pass ContentVersionIds from the parent screen flow into this subflow, we will want to create a new ContentVersionIds input collection variable of data type text with the option “Available for Input” checked. Make sure to allow multiple values.
- Then, we will also create a Related Record ID input variable also with “Available for Input” checked. For our specific example, we chose the business case behind the Create Case Form component and so are creating and storing a related Case Record Id like so…
Right about now you may be wondering, “hang on, what about the ContentDocumentIds?” And rightly so, as that is a key ingredient we will need to establish a junction between Salesforce file(s) and where they’re shared (Ie the Case Record).
Here is the logic of this subflow: Using our new ContentVersionId Input Collection, we will LOOP through each individual ContentVersionId to GET the corresponding ContentDocumentId and then ASSIGN that value to a retrieved ContentDocumentId collection variable. Voilà! Once all the ContentDocumentIds are retrieved from the database, we can use a second LOOP to iterate over the retrieved ContentDocumentId collection to finally creating ContendDocumentLink records which store the junction between a Salesforce file (ContentDocumentId) and where it’s shared (Related Record ID). We compile and ASSIGN finished records to a new ContentDocumentLink (CDL) collection within the LOOP. After exiting we’ll INSERT the new ContentDocumentLink collection in a single DML call which will successfully relate all the files uploaded by the guest user to the right record thanks to the subflow’s more powerful running context.
For the purpose of creating the ContentDocumentLink (CDL) collection, remember to create a single ContentDocumentLink record variable, and ASSIGN both the Related Record ID and ContentDocumentId to that record variable before adding it to the collection variable. See below in the second LOOP where we do this in two assignment elements.
With the subflow completed and activated, we can return to the parent screen flow and incorporate the new subflow into it’s design. Below is what the subflow call will look like when we pass in the Related Record ID and ContentVersionId collection. Again, for the purposes of our example, we are passing in the Case Record Id as CaseRecord_Var.
The ContentVersionIds_Collection is set using the available attribute in the file upload screen component pictured below.
However frustrating it may be to refactor your existing guest user related business processes, it is important for us to spend a moment to acknowledge the necessity of these updates in the context of increased platform security. What’s achieved here is Private Org Wide Defaults (OWDs) when an unauthenticated experience runs and this does make sense. Additionally, revoking View All, Modify All, Edit, and Delete for both custom and standard objects safeguards against unwanted interactions with Salesforce data from unauthenticated sources. We encourage you to review Salesforce’s Guest User Security Policies and Timelines to understand the broader mission.
Finally, while we have demonstrated in this post that it is possible to declaratively restore guest user permissions by leveraging subflow, we do have some clarifications. First, our subflow design contains a DML within a LOOP which means the throughput of the flow is not optimized. As this subflow is called from a screen flow and it is likely a guest user would not be attaching many files, this should not impact performance, but we call it out all the same. Second, of course there are programmatic solutions to successfully create these ContentDocumentLink records including invocable methods or LWCs. We are attaching some further resources to help you on your journey.
- For admins, learnexperiencecloud.com
- For developers, Enable Guest Users to Upload Files with a custom Lightning Web Component
- For everyone, join the Experience Cloud Trailblazer Community Group