9

I am attempting to add pagination, and I followed this great article by Jeff Douglas.

I am an admin so when I run the page it runs great, I can navigate forwards, back. When I log in as a user my pagination code fails with:

Read access denied for Opportunity, controller action methods may not execute.

I don't understand why the following runs great for my user:

lstSearchOpportunities = (list<opportunity>)Database.query(strSOQL);

but the same query throws an access denied error:

setConOpportunities = new ApexPages.StandardsetController(Database.getQueryLocator(strSOQL));

The class is marked 'with sharing'. I wasn't able to locate any differences in execution context between these two methods in the help documentation.

Any ideas on what I am failing to understand?

  • First of all, can you confirm whether your user actually has "read" access on Opportunity or not? – Sebastian Kessel May 6 '16 at 22:11
  • They do not, I should have asked how the existing code was getting around the sharing rules since the class is declared 'with sharing' – Steve MunLeeuw May 6 '16 at 22:26
  • 1
    "With Sharing" does not affect FLS. The code was simply working because Apex, in the context of the code as it was written before, runs in "God Mode". Is it not possible to get these users access? That is the correct way to fix this. – Sebastian Kessel May 6 '16 at 22:27
  • Can you also check the security settings on the Visualforce pages and make sure that the profile you are using have access to all the wizard pages you are using? – Carlos Naranjo May 6 '16 at 22:28
  • 1
    @sfdcfox I think what you are telling me is the ApexPages.StandardsetController() that is wrapped around the database call dictates the context. If I want to paginate over data that the user doesn't have access to via sharing rules, I need to figure out how not to use the StandardsetController's context. – Steve MunLeeuw May 6 '16 at 23:28
0

user license type is likely the issue - Salesforce License would have access to CRM Objects but most other types do not have this access to read these - check your other user license type.

other options would be to execute your code in a "without sharing" context and populate a wrapper to output your values. Sample wrapper would be possible like below:

public class Wrapper{
    public Map<String, Object> fields { get; set; }
    public List<String> fieldNames { get; set; }
}

Page output can be however you would like to iterate these to output each record. when you query opportunity you would create a Wrapper for each record fields would be used to populate the values from the describe information from the SObjectType of the Opportunity fields so you can capture any fields that were returned with data. It is not very efficient but if you are not otherwise able to get your users access to the Opportunity object - it is a workable option.

Controller:

public with sharing class ParentClass{
    public List<Wrapper> opps { get; set; }

    public void setOpportunities(){
        InnerWorkerClass worker = new InnerWorkerClass();
        opps = worker.methodToGetData( params here );
    }


    public without sharing class InnerWorkerClass{
         public List<Wrapper> methodToGetData( params here ){
              List<Wrapper> results = new List<Wrapper>();

              for( Opportunity opp : [ SELECT .... FROM Opportunity WHERE .... ]){

                  results.add( CreateWrapper( opp ));
              }
              return results;
         }

         public Wrapper CreateWrapper( Opportunity opp ){
              Map<String, Schema.SObjectField> oppFields = Schema.getGlobalDescribe().get( 'Opportunity' ).getDescribe().fields.getMap();
              Wrapper result = new Wrapper();
              for( String fieldName : oppFields.keySet() ){
                  if( result.fields == null ){ result.fields = new Map<String, Object>(); }
                  if( result.fieldNames == null ){ result.fieldName = new List<String>(); }

                  result.fields.put( fieldName, opp.get( fieldName ));
                  result.fieldNames.add( fieldName );

              }
              return result;
         }
    }
}

Page:

<apex:page controller="ParentClass" action="{!setOpportunities}">
   <apex:pageBlock rendered="{!opps != null}">
       <table>
       <tr>
         <apex:repeat value="{!opps[ 0 ].fieldNames}" var="header">
            <th>{!header}</th>
         </apex:repeat>
       </tr>
       <apex:repeat value={!opps}" var="opp">
           <tr>
           <apex:repeat value="{!opp.fieldName}" var="f">
                <!-- build you data rows in here can use outputField as you used object to store the field objects in the wrapper object -->
                 <td><apex:outputField value="{!opp.fields[ f ]}" /></td>
           </apex:repeat>
           </tr>
       </apex:repeat>
       </table>
   </apex:pageBlock>

</apex:page>

Again it is not the absolute most efficient but of course if the user cannot get access - this will be a doable workaround and then you won't need to worry about it and can control access via page access here

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.