1

Until recently we were able to display Attachments to our guest user on our Site using a URL like
Site.getbaseUrl() + '/servlet/servlet.FileDownload?file=' + attachment.Id

But now, we're not sure what's changed (most likely within the past week or so) and this is no longer working.

We were embedding the Attachment in an iframe, but recently began getting browser alerts with an error message like Javascript proxies were not generated for controller ControllerName: may not use public remoted methods inside an iframe. (Sent from a different Visualforce page, the one that has the link to the page with the iframe).

After some digging it turns out that the link to the attachment is no longer valid (inputting that in a browser's URL gives us an nginx 404 page).
So the current assumption is that for some reason the servlet download is no longer valid, and then whatever runs Visualforce throws a fit and makes the browser send an alert with a nonsensical error message.

To make things more interesting we are unable to reproduce this in Sandbox.
The servlet url just works.

EDIT:
This is not a permanent solution
The production org had been migrated to a new instance (with Winter '21), and therefore all View All permissions for the guest user had been revoked. Including the object to which this Attachment had been attached.
So that's why it couldn't download, and why it still worked in Sandbox.
Simply adding back that permission in Production resolved the issue.

We're still going to need a permanent solution before Spring '21.

3 Answers 3

1

The "correct" answer is to create a Sharing Set on the relevant object.
There is a special Sharing Rule Type for guest users, Guest user access, based on criteria. Select that.
Then for the criteria you can select something that will actually filter the records, or if you just don't care and want all records visible everywhere, you can select all the records with Created By STARTS WITH 005.

1

My solution is get the Body of Attachment as Base64 and then create download link at the frontend.

Note: The attachment body should not exceed 4.5MB due to the Salesforce governor limits.

In your Apex Controller you can create a @RemoteAction method which gets the body of the attachment, encodes it to String using EncodingUtil.base64Encode and return it to Visualforce page. for aura or lwc, the solution is similar.

public class YourController {
    @RemoteAction
    public static Base64File download(String recordId) {
        Base64File b64File;
        Attachment att = [SELECT Id, Name, Body, ContentType FROM Attachment WHERE Id = :recordId];
        if (att != null) {
            b64File = new Base64File(att.Id, att.Name, att.ContentType);
            b64File.body = EncodingUtil.base64Encode(att.Body);
        }
        return b64File;
    }

    public class Base64File {
        public String id {get; set;}
        public String name {get; set;}
        public String contentType {get; set;}
        public String body {get; set;}

        public Base64File(String Id, String Name, String ContentType) {
            this.id = Id;
            this.name = Name;
            this.contentType = ContentType;
        }
    }
}

In your Visualforce page, you request the attachment body and transfer it to Blob, then download it by an a tag or Javascript library like FileSaver

<script>
  function download(recordId) {
    Visualforce.remoting.Manager.invokeAction(
      '{!$RemoteAction.YourController.download}',
      recordId, 
      function(result, event){
        if (event.status === true) {

          var binary = atob(result.body);
          var len = binary.length;
          var buffer = new ArrayBuffer(len);
          var view = new Uint8Array(buffer);
          for (var i = 0; i < len; i++) {
              view[i] = binary.charCodeAt(i);
          }
          
          // you can use FileSaver.js to save it to local or use a tag
          // saveAs(blob, result.name);

          saveFile(result.name, result.contentType, view);
        }
    });
  }
  // This function will open a modal with a link in it
  // when user click the link and download the file
  // the link will be removed and the modal will hide
  function saveFile (name, type, data) {
      if (data !== null && navigator.msSaveBlob)
          return navigator.msSaveBlob(new Blob([data], { type: type }), name);
      var a = $("<a />", {text: 'Download:' + name, target: "_blank"});
      var url = window.URL.createObjectURL(new Blob([data], {type: type}));
      a.attr("href", url);
      a.attr("download", name);
      a.on('click', function(e){
        $('#download').hide();
        setTimeout(function(){
          a.remove();
          window.URL.revokeObjectURL(url);
        }, 10);
      });
      $("#downloadContainer").append(a);
      $('#download').show();
  }
</script>
0

It sounds like your temporary solution to add "View All" permission on the parent object means access to that attachment's download URL depends on read access to the parent record.

If that is the case, then the Spring 21 compatible solution (with View All no longer available for Guest Users) would be to add a sharing rule to grant read access to records that this needs to be enabled for.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.