function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
emlizmueemlizmue 

Sharing Settings and Guest Users

I am trying to do 2 things:

 

1 - have one user (API user) who has access to only contact records of a specific record type

 

2 - use a force.com site as a webhook to record unsubscribes from an outside email marketing system

 

I had the force.com site webhook working great when I had our Sharing Settings set to Contacts: Public Read/Write. But I had to change the setting to Private in order to restrict the access of the API user and now the webhook no longer works because the Guest User of that site can no longer edit the Email Opt Out field. The field accessibility is not the problem, as it was working before I changed Sharing Settings and I've double checked.

 

Does anyone have any idea on how I can accomplish both of these tasks? Is there another way to restrict the access of that user without changing the Sharing Settings of the Contact object to Private and then creating exceptions?

bob_buzzardbob_buzzard

Are you using standard controllers for the page that allows the editing of the Email Opt Out field?

emlizmueemlizmue

Actually, it's a custom controller created by the email marketing company (MailChimp). Here is the apex class body:

 

/**
UnsubscribeController
Created: 04/06/10
Updated: 04/08/10
*/

public with sharing class UnsubscribeController {

    public PageReference handleUnsubscribe() {
        
        System.debug('*** Starting handleUnsubscribe()');
        
        // Retrieve the "secret" code for the page
        String code = getCode();
        System.debug('*** Code = ' + code);
        
        // Check the "security code" to make (somewhat) sure the request
        // is from our MailChimp WebHook
        String requestCode = ApexPages.currentPage().getParameters().get('code');
        if (requestCode == null || !code.equals(requestCode)) {
            // Missing or wrong security code - fail silently so we don't reveal anything to the caller
            System.debug('*** Invalid security code: ' + requestCode);
            return null;
        }
        
        // Get email address from posted parameters
        String email = ApexPages.currentPage().getParameters().get('data[email]');
        if (email == null || email.length() <= 0) {
            System.debug('*** Missing email parameter');
            return null;
        }
        else {
            System.debug('*** Email to unsubscribe: ' + email);
        }
        
        // "Neutralize" characters in the email which could be used maliciously in search query
        String saferEmail = String.escapeSingleQuotes(email);
        saferEmail.replace('*', '\\*');
        saferEmail.replace('?', '\\?');
        
        // Search for any lead(s) and contact(s) record(s) with the email address
        List<List<SObject>> searchList = [FIND :saferEmail IN EMAIL FIELDS RETURNING Contact(id), Lead(id) ];      
        List<Contact> contacts = ((List<Contact>)searchList[0]);
        List<Lead> leads = ((List<Lead>)searchList[1]);
        
        // Update the opt-out field of record(s) (make sure the
        // current user has permission to view and update the opt-out field)
        for (Contact c : contacts) {
            c.HasOptedOutOfEmail = true;
        }
        for (Lead l : leads) {
            l.HasOptedOutOfEmail = true;
        }
        
        // Save the updates
        if (contacts.size() > 0) {
            update contacts;
        }
        if (leads.size() > 0) {
            update leads;
        }
        
        return null;
    } 
    
    private String getCode() {
    
        try {
            StaticResource sr = [ SELECT Body, Name FROM StaticResource WHERE Name = 'WebHooksConfig' LIMIT 1 ];
             
            String xml = sr.Body.toString();
            System.debug('**** XML: \n' + xml);
            
            Dom.Document doc = new Dom.Document();
      doc.load(xml);
      Dom.XMLNode webhooks = doc.getRootElement();
      for (Dom.XMLNode node : webhooks.getChildElements()) {
        String id = node.getAttribute('id', '');
        if (id.equalsIgnoreCase('unsubscribe')) {
          return node.getAttribute('code', '');
        }
      }
        }
        catch (Exception e) {
            System.debug('*** Error: ' + e);
        }
        
        return null;
    }
    
    public static testMethod void testHandleUnsubscribe() {
        
        // Create lead and contact for testing 
        Lead myLead = new Lead( firstName = 'John', lastName = 'Tester', company = 'Test Corp.', email = 'tester@test.com');
        Contact myContact = new Contact( firstName = 'Jane', lastName = 'Tester', email = 'jtester@coolcorp.com');
        insert myLead;
        insert myContact;
        
        // Verify that lead has not opted out
        myLead = [ SELECT Id, Name, email, HasOptedOutOfEmail FROM Lead WHERE Id = :myLead.id ];
        System.assert(!myLead.HasOptedOutOfEmail, '*** Lead should not have opted out!');
        
        // Set up fixed SOSL search results (otherwise SOSL returns empty in testing)
        Id[] fixedSearchResults = new Id[2];
        fixedSearchResults[0] = myLead.Id;
        fixedSearchResults[1] = myContact.Id;
        Test.setFixedSearchResults(fixedSearchResults);
        
        // Instantiate controller
        UnsubscribeController controller = new UnsubscribeController();         
        
        // Verify that we have a "secret" code and can read it
        String code = controller.getCode();
        System.assert(code != NULL && code.length() > 0, 'Code is null!');
        
        // Set up page context and parameters
        PageReference pageRef = Page.Unsubscribe;
        Test.setCurrentPage(pageRef);   
        ApexPages.currentPage().getParameters().put('type', 'unsubscribe');
        ApexPages.currentPage().getParameters().put('data[email]', myLead.Email);
        ApexPages.currentPage().getParameters().put('code', code);
        
        // Call controller and verify that lead has now opted out
        controller.handleUnsubscribe();
        myLead = [ SELECT Id, Name, HasOptedOutOfEmail FROM Lead WHERE Id = :myLead.id ];
        System.assert(myLead.HasOptedOutOfEmail, '*** Lead should have opted out!');
        
        // Try again, but without the right security code
        myLead.HasOptedOutOfEmail = false;
        update myLead;
        ApexPages.currentPage().getParameters().remove('code');
        controller.handleUnsubscribe();
        myLead = [ SELECT Id, Name, HasOptedOutOfEmail FROM Lead WHERE Id = :myLead.Id ];
        System.assert(!myLead.HasOptedOutOfEmail, '*** Lead should not have opted out!');
        
        // Try again with correct code and contact's email
        ApexPages.currentPage().getParameters().put('data[email]', myContact.Email);
        ApexPages.currentPage().getParameters().put('code', code);
        controller.handleUnsubscribe();
        myContact = [ SELECT Id, Name, HasOptedOutOfEmail FROM Contact WHERE Id = :myContact.Id ];
        System.assert(myContact.HasOptedOutOfEmail, '*** Contact should have opted out!');
        
        // Try again without email parameter
        ApexPages.currentPage().getParameters().remove('data[email]');
        controller.handleUnsubscribe();
    }
}

 

bob_buzzardbob_buzzard

This class is declared as with sharing, which means it will respect the sharing rules of the current user - in this case the guest user won't have any access to these records.  You could change this to without sharing, which would give access to all data.

 

There's more information in this at:

 

http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_classes_keywords_sharing.htm

SidharthSidharth

Hello Bob

 

I was encountering the same problem with my site guest user login and accessing standard object (with Sharing Setting set to PRIVATE)

 

But when i changed the Apex Controlelr defination from 'with sharing' to 'without sharing', i can access the Object.

 

Just wondering if its the correct way / workaround. Will there be any CONS to this alternative?

 

Thanks

Sid

SidharthSidharth

Hello emlizmue

 

I am facing the same issue. Have you used the same solution advised by Bob or come up with some other alternative?

 

Thanks

Sid

emlizmueemlizmue

We never did get this to work long-term. It worked for a while and when it stopped working we were never able to determine if the error was caused by a change we made or a change that MailChimp (the provider of the code above) made.