6

I have an Apex class with sharing where I get a list of every Pricebook2 available in the database. Considering the fact that we use sharing rules to prevent access to some of them, my SOQL shouldn't return a full list to every users.

Here's my super simple method:

public SelectOption[] getPricebookList() {
    Pricebook2[] Records =
    [
        SELECT
            Name
        FROM
            Pricebook2
        WHERE
            IsActive = true
        ORDER BY
            Name ASC
    ];

    SelectOption[] Options = new SelectOption[]{};

    for(Pricebook2 R : Records)
        Options.add(new SelectOption(R.Id, R.Name));

    return Options;
}

The data returned by this method is then outputted using a selectoption component inside a Visualforce page.

The problem I have is that every price books are displayed to every single users. Also, any user can use the Visualforce module I created to create Quotes and QuoteLineItems with any Pricebook, even if they don't have access to them according to the sharing settings.

How can I prevent this from happening?

EDIT (@SF_Ninja)

I tried what you answer, here's my code:

// Requesting available price books
Map<ID, Pricebook2> Records = new Map<ID, Pricebook2>([
    SELECT   Name
    FROM     Pricebook2
    WHERE    IsActive = true
    ORDER BY Name ASC
]);

SelectOption[] Options = new SelectOption[]{};

// Requesting price books user access
// NOTE: Workaround since Pricebook2 doesn't enforce Sharing Rules
UserRecordAccess[] UserRecordAccessList =
[
    SELECT  RecordId, HasReadAccess, HasEditAccess, HasDeleteAccess
    FROM    UserRecordAccess
    WHERE   UserId = :UserInfo.getUserId() AND 
            RecordID IN :Records.keySet()
];

System.debug('User ID: ' + UserInfo.getUserId());
System.debug('Pricebook IDs size: ' + Records.keySet().size());

System.debug('IDs to search:');

for(Pricebook2 R : Records.values())
    System.debug('----> ' + R.Name + ' (' + R.Id + ')');

System.debug('Result: ' + UserRecordAccessList); // <-- returning () as admin

According to the documentation, I'm requesting the right values, have the right WHERE operations (http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_objects_userrecordaccess.htm).

Here's the Developer console output:

enter image description here

UserID and Pricebook IDs are both returning values, but the last debug is returning an empty list. Still trying to figure out whats going on here.

EDIT (2013-11-14 10:00)

Here's the final version of the code provided above. The reason why the version above wasn't working is because I passed a set instead of a list in the WHERE condition. Normally, this would be working in a SOQL request, but not in this case. I guess there's an undocumented limitation in UserRecordAccessList.

// Requesting available price books
Map<ID, Pricebook2> Records = new Map<ID, Pricebook2>([
    SELECT   Name
    FROM     Pricebook2
    WHERE    IsActive = true
    ORDER BY Name ASC
]);

SelectOption[] Options = new SelectOption[]{};

Id[] PBIDs = new Id[]{};

PBIDs.addAll(Records.keySet());

// Requesting price books user access
// NOTE: Workaround since Pricebook2 doesn't enforce Sharing Rules
UserRecordAccess[] UserRecordAccessList =
[
    SELECT  RecordId, HasReadAccess, HasEditAccess, HasDeleteAccess
    FROM    UserRecordAccess
    WHERE   UserId = :UserInfo.getUserId() AND 
            RecordID IN :PBIDs
];

System.debug('User ID: ' + UserInfo.getUserId());
System.debug('Pricebook IDs size: ' + Records.keySet().size());

System.debug('IDs to search:');

for(Pricebook2 R : Records.values())
    System.debug('----> ' + R.Name + ' (' + R.Id + ')');

System.debug('Result: ' + UserRecordAccessList);

Hope this might fix others issue.

EDIT (2013-11-14 10:37)

The code above is working (we can get UserRecordAccessList records), but every permissions are set to yes, so it's pointless.

What is left to do?

Hack in Salesforce features to set the Pricebook:

https://[INSTANCE].salesforce.com/oppitm/choosepricebook.jsp?id=[OPP_ID]&retURL=[RET_URL]

This might break your flow, but it should be working and support sharing settings.

  • This seems very odd. The query should work. If I change the RecordID IN : map.keyset() to just RecordId = (any id from the keyset), it works fine. very odd. Messing around in my dev org with the query to try to figure it out – Chris Duncombe Nov 14 '13 at 14:25
  • @SF_Ninja When I insert IDs manually (WHERE RecordID IN ('id', 'id2', 'id3'...)), it is working and returning the right information. Maybe this special SOQL request is allergic to my Records.keySet(). Considering all the limitation mentioned in the documentation, I feel there's some magic done behind the scene. It is written like a regular SOQL request, but probably parsed before executing it. I might have to craft my request dynamically and populate the WHERE condition using a loop. – jpmonette Nov 14 '13 at 14:43
  • 1
    keyset() returns a set, not a list. I might try creating a list in this way: list<Id> keySetList = new list<Id>(Records.keyset()). Then you could use this list for RecordID IN : keySetList. I havent tried this, but it occured to me that keyset() does return a set, not a list. This shouldnt be an issue, but it occured to me maybe this is what is causing the iodd behavior – Chris Duncombe Nov 14 '13 at 14:48
  • Yeah, it's returning a set. I dug a bit more online and end up on this message on the forum (boards.developerforce.com/t5/Apex-Code-Development/…) saying that Set<ID> doesn't work, while List<ID> does when requesting UserRecordAccess object. It's strange, since they normally work in SOQL queries. Like I mentioned, it must be the magic done in the background that doesn't support this. There's not even an error returned! – jpmonette Nov 14 '13 at 14:53
  • API v39 still no change: SELECT Id, Name, UserRecordAccess.HasReadAccess, UserRecordAccess.HasTransferAccess, UserRecordAccess.MaxAccessLevel FROM Pricebook2 returns all true resp MaxAccessLvl: "Id":"01s0Y000005JVMyQAO","Name":"Not shared with anyone","UserRecordAccess":{"attributes":{"type":"UserRecordAccess"},"HasReadAccess":true,"HasTransferAccess":true,"MaxAccessLevel":"All"} - taken from a vf page viewed as standard user with none shared pricebooks (dev org, can't test in production). – Christian Szandor Knapp May 11 '17 at 20:44
12

This is a known issue with the pricebook2 object in Salesforce.

From the Apex Developers guide.

enter image description here

See here

http://www.salesforce.com/us/developer/docs/apexcode/index_Left.htm#StartTopic=Content/apex_security_sharing_rules.htm?SearchType=Stem

There is an idea you can vote on to get this feature here

https://success.salesforce.com/ideaView?id=08730000000ZE28AAG

As a workaround you can use the UserRecordAccess object in your Apex to only add the pricebook that the current user has access to, to the list of selectoptions. This is not ideal, but its a work around.

Note I did this in a text browser, so it hasnt been tested, but something like this should work for you

public SelectOption[] getPricebookList() {
    Pricebook2[] Records = [SELECT Name FROM Pricebook2 WHERE IsActive = true ORDER BY Name ASC];

    SelectOption[] Options = new SelectOption[]{};

    set<Id> pricebookIDs = new set<Id>();
    for(Pricebook2 R : Records){
        pricebookIDs.add(R.Id);
    }

    List<UserRecordAccess> listUserRecordAccess = [SELECT RecordId, HasReadAccess, HasEditAccess, 
       HasDeleteAccess FROM UserRecordAccess WHERE UserId=:UserInfo.getUserId() AND RecordId IN: pricebookIDs];

    map<Id,boolean> accessMap = new map<Id,boolean>();

    for(UserRecordAccess recAccess : listUserRecordAccess){
        accessMap.put(recAccess.RecordId, recAccess.HasReadAccess);
    }

    for(Pricebook2 R : Records){
        if(accessMap.get(R.Id)){
            Options.add(new SelectOption(R.Id, R.Name));
        }
    }

    return Options;
}
  • Thanks for the answer! Just by the amount of upvote this question received in 18 hours (7 at this time!), it's clear that it will help others. – jpmonette Nov 14 '13 at 13:37
  • Thanks. Did it work for you? Or did you come up with another workaround? – Chris Duncombe Nov 14 '13 at 14:00
  • Troubleshooting a problem in the UserRecordAccess request. I'll update as soon as I find the issue, but I'm on a good track :)! – jpmonette Nov 14 '13 at 14:04
  • Good to hear. Good luck – Chris Duncombe Nov 14 '13 at 14:08
  • I'm surprised to see that it is not working :S! Pasting code in my initial question – jpmonette Nov 14 '13 at 14:11

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.