Question
User object-level permissions limit the results of DescribeSObjectResult.getChildRelationships()
. Are there any workarounds?
I have submitted an Idea about this (https://success.salesforce.com/ideaView?id=0873A000000CZLzQAO).
(Note: This seems very closely related to the "Fixed" Known Issue In API version 44, DescribeSObjectResult.getChildRelationships() checks Read Access to the child entity and parent relationship field)
Actual goal
I would like to dynamically generate a query on Case
, and several child objects. E.g.: SELECT Id, (SELECT Id, CaseId FROM WorkOrders) FROM Case WHERE ...
I would like to pass in the child SObjectTypes (WorkOrder.getSObjectType()
) without worrying about the exact fields and the names of the relationship. Apex provides exactly one path to get the names of child relationships, and it all starts with SObjectType.Case.getChildRelationships()
.
However, it looks like this is user-permissions dependent, even in a trigger context, which makes it worthless for my goal.
Description
In Spring '19 (and maybe before -- I have no idea), running getChildRelationships()
will not return a relationship if the executing user does not have allowRead
permissions on the child SObject.
(I describe this by example, below.)
I believe this is a bug because the documentation says (emphasis mine):
Apex classes and triggers run in system mode. Classes and triggers have no restrictions on dynamically looking up any sObject that is available in the org. You can generate a map of all the sObjects for your org regardless of the current user’s permission, unless you are executing anonymous Apex.
Sample Code
Below is a simple method that finds the relationship between a parentSObjectType
and a childSObjectType
. It assumes that there is only one relationship between the two and will throw an error otherwise.
public without sharing class SObjectUtils {
public static ChildRelationship getRelationshipForSObjectType(SObjectType parentSObjType, SObjectType childSObjType) {
ChildRelationship value;
for (ChildRelationship rel : parentSObjType.getDescribe().getChildRelationships()) {
if (childSObjType == rel.getChildSObject()) {
if (value != null) {
throw new RelationshipException('Cannot deduce relationship name from ' + parentSObjType +
'. There is more than one related object of type ' + childSObjType);
}
value = rel;
}
}
return value;
}
}
Elsewhere in a test, I run:
@isTest
private static class TEST_SObjectUtils {
static User getUserWithoutWorkOrderPermissions() {
// return a user without workorder object permissions
}
static User getUserWithWorkOrderPermissions() {
// return a user with workorder object permissions
}
@isTest
static void worksForUserWithWorkOrderPermissions() {
ChildRelationship rel;
System.runAs(getUserWithWorkOrderPermissions()) {
Test.startTest();
rel = SObjectUtils.getRelationshipForSObjectType(
Case.getSObjectType(),
WorkOrder.getSObjectType()
);
Test.stopTest();
}
System.assertEquals('WorkOrders', rel.getRelationshipName());
}
@isTest
static void worksForUserWithoutWorkOrderPermissions() {
ChildRelationship rel;
System.runAs(getUserWithoutWorkOrderPermissions()) {
Test.startTest();
rel = SObjectUtils.getRelationshipForSObjectType(
Case.getSObjectType(),
WorkOrder.getSObjectType()
);
Test.stopTest();
}
System.assertNotEquals(null, rel); // this fails!!
}
}
System.runAs()
, does the observed behavior change?getChildRelationships()
useless (even if it works but is untestable, which I do not think is the issue).