2

I have a REST endpoint that, when called by a guest user, creates a ContentVersion file. When the process happens synchronously, the ContentVersion file gets created without issue. However, when the logic is changed to use Queueable, file creation fails with the message "INVALID_STATUS, Documents in a user's private library must always be owned by that user.: [OwnerId]"

Calling this service as an authenticated Salesforce user works as expected both synchronously and queueable.

I tried using the fix listed in this article but in my case, I don't have a NetworkId field available on ContentVersion since Communities is not enabled. I also tried setting all classes to "without sharing" but that didn't help either.

I've also tried setting the FirstPublishLocationId as recommended in some other places but this caused the synchronous process to begin failing with the message "INSUFFICIENT_ACCESS_OR_READONLY, You do not have the level of access necessary to perform the operation you requested. Please contact the owner of the record or your administrator if access is necessary"

All files below are greatly simplified to illustrate the issue without any noise. Again, everything works as an authenticated Salesforce user but not as a guest user.

Basic ContentVersion creation logic

public with sharing class CVDemo {
    public static Id createContentVersion(){
        String filename = 'Sample';
        filename += DateTime.now().formatGmt('yyyy-MM-dd\'T\'HH:mm:ss');
        ContentVersion contentVersion = new ContentVersion(
            Title = filename,
            PathOnClient = filename + '.txt',
            VersionData = Blob.valueOf('Hello World'),
            Origin = 'C',
            ContentLocation = 'S',
            FirstPublishLocationId = UserInfo.getUserId()
        );
        insert contentVersion;
        return contentVersion.Id;
    } 
}

Simple Queueable to call the logic

    public with sharing class CVQueueable implements Queueable{
public void execute(QueueableContext context) { 

    Id cvid = CVDemo.createContentVersion();
    system.debug('queueable CVID: ' + cvid);
}

}

REST service @RestResource(urlMapping='/cvDemo') global with sharing class CVRestService { @HttpPost global static void handleRequest() { RestRequest request = RestContext.request; RestResponse response = RestContext.response; response.addHeader('Content-Type', 'application/json'); Map<String, Object> responseBody = new Map<String, Object>(); System.debug('exec sync'); //creating the document synchronously succeeds Id cvid = CVDemo.createContentVersion(); System.debug('sync CVID: ' + cvid);

    //but trying to enqueue the document creation fails
    System.debug('enqueue');
    String jobId = System.enqueueJob(new CVQueueable());
    responseBody.put('jobID', jobId);
    response.responseBody = Blob.valueOf(JSON.serialize(responseBody));

    response.statusCode = 200;

}

}

Natalya Murphy
  • 321
  • 2
  • 6
  • Are you explicitly setting the ownerid value anywhere? Are you relying on the running user context to implicitly set it? Are you relying on the org's 'default owner' to set this value? Do you expect the file to be in the running user's personal library? – Mark Pond Sep 06 '22 at 19:59
  • The code as originally written does not explicitly set the owner ID anywhere, but instead leaves it to Salesforce to reassign the file to the default (internal user) file owner. In synchronous mode this works as expected and the ContentVersion is reassigned without issue. But in Queueable it instead throws the error. It's almost as if the code WANTS to reassign it to the default user but Salesforce is preventing it. But why is it only preventing it in Queueable? The file is NOT expected to stay in the site guest user's library. – Natalya Murphy Sep 07 '22 at 00:04

1 Answers1

2

The final solution to this comes down to 3 steps:

  1. Use a "without sharing" class
  2. Create a record that this file will be attached to
  3. Use the ID of that record as FirstPublishLocationId in the ContentVersion record (this is where the "without sharing" annotation is needed to avoid access issues)

Final class:

//Note the "without sharing" annotation - this is needed to allow the linking of the file
public without sharing class CVDemo {
    public static Id createContentVersion(){
    //In a real implementation this is some record that the file will be linked to
    Account dummy = new Account(Name='Dummy');
    insert dummy;

    String filename = 'Sample';
    filename += DateTime.now().formatGmt('yyyy-MM-dd\'T\'HH:mm:ss');
    ContentVersion contentVersion = new ContentVersion(
        Title = filename,
        PathOnClient = filename + '.txt',
        VersionData = Blob.valueOf('Hello World'),
        Origin = 'C',
        ContentLocation = 'S',
       //immediately link the file to the parent record
        FirstPublishLocationId = dummy.Id 
    );
    insert contentVersion;
    return contentVersion.Id;
}

}

Natalya Murphy
  • 321
  • 2
  • 6