7

Is this possible? I'm not finding help, Google or otherwise, for this.

Situation:

A trigger runs and cycles through groups of Opportunities. Some of these Opportunities are Closed Won, and most users of the system are not allowed to Edit Closed Wons (especially the Sales team). But I need this trigger to be able to touch the Opps (it's fired when the User touches the Account and subsequently a group of Opps under that account gets touched). It is a VALIDATION RULE blocking them.

Ultimately, I may investigate, if no other option, setting a HIDDEN FIELD on the Opp before updating, then clearing if after, and putting bypass code in the Validation rule. This is a backup plan. I'd like to know how to "Run As" a specific profile though.

Anyone have an example of this? I have three profiles out of twelve that can touch Closed Wons...

0

6 Answers 6

7

No, simulating another user/profile is not possible in the scenario you describe (and an undesirable platform feature for 3rd party code security reasons). You should always build backdoors into hard-fail validation rules for this reason. I prefer doing this to enforcing the logic via triggers because then the validation rules are still maintained via clicks admin, and you can do things like add error messages to specific fields.

The pattern I follow for Sys Admin manual overrides is to exclude the privileged profile(s) from the validation rule altogether (a la twamley's note). But that doesn't get you what you need, which is a code-only override.

For situations where only code is allowed to bypass a validation rule, you've mentioned the option of a hidden field but I'd suggest a significant tweak to it. I create a (18,0) numeric field called something like "Apex Updated" that is hidden from page layouts and read-only for FLS for user profiles. In any validation rule you want a code backdoor to, you just add a check for NOT(ISCHANGED(Apex_Updated__c)). Then, in your code, to bypass those validation rules you just set Apex_Updated__c to System.currentTimeMillis(). This has the advantage of not having to reset fields, or worse yet, missing the workflow to reset them and ending up with records that are permanently exempt from validation rules.

You say it's a backup plan, but it's just one custom field, one line of Apex code (to set the field when a bypass is desired), and one line of validation rule modification (to bypass the rule when the field is changed).

5

One option is to remove the Validation Rule entirely and enforce the same rule(s) in the trigger. This will give you more flexibility to bypass the rule as required.

If you enforce the rules in a before insert/update trigger you can use addError method to show any errors on the page or against a particular field.

You will probably want the trigger to run in what is known as the "system context". When running in this mode it won't be limited by the current users sharing rules etc...

You can alter your Apex Controllers to run in the system context by using the without sharing keywords if they are currently using with sharing.

See Using the with sharing or without sharing Keywords


UPDATE: This is only applicable to test methods

Seems like you were almost there with your "Run As" terminology.

There is a System.runAs(user) method that allows you to run tests as another user.

See Using the runAs Method.

5
  • Damn. That's the phrase I should have searched. Thanks!
    – AMM
    Apr 3, 2013 at 21:45
  • Hmm. Based on that post, it's only for test methods, so it sounds like it would not work for actually editing live data(?)...
    – AMM
    Apr 3, 2013 at 21:50
  • 1
    Ah, your are correct, runAs this is only for testing. I have another option that I'll add to the answer. Apr 3, 2013 at 21:52
  • "Without sharing" is not necessary in the vast majority of cases, since Apex code runs in system mode by default.
    – jkraybill
    Apr 5, 2013 at 0:03
  • @jkraybill I was thinking more to cases where the apex classes currently had with sharing and changing it to without sharing. That said, your method to use a changing field with the current milliseconds to disable the validation rule seems better than moving all the affected logic into the trigger. Apr 5, 2013 at 0:29
1

Try excluding those three profiles in the validation rule by adding $Profile.Name to the Error Condition Formula. Something like:

AND(
    NOT(OR(
        $Profile.Name = 'profile1',
        $Profile.Name = 'profile2',
        $Profile.Name = 'profile3'
    )),
    ... your regular validation rule here ...
)

On a related note, I have a similar requirement where Closed Won Opps are very locked down. I ended up writing a trigger that locked 95% of the fields but white listed a handful of fields that were allowed to be changed. That idea might work for you better than the validation rule. This answer got me most of the way there.

4
  • The profiles must not be able to edit Closed Wons, so excluding them from the Validation Rule would defeat that. Thanks anyway.
    – AMM
    Apr 4, 2013 at 12:52
  • I meant to exempt the profiles where the validation rule should not apply. The validation rule should apply to your Sales profiles. I assumed you have only a handful of profiles that have a legitimate need to bypass the validation rule so I wrote it that way. You can invert the logic if the other set is dramatically larger: AND($Profile.Name = 'Sales', ...rule that only applies to sales...) If I'm still confused, ignore my code, but give $Profile a second thought.
    – twamley
    Apr 6, 2013 at 16:28
  • I currently do this quite frequently. It is not the solution no matter how you do it, because a Profile that can NOT edit a Closed Won must be able to "edit/save" it from the Account level for what I'm trying to do. A solution was found by using two markers on the ACCOUNT to bypass the validation logic, thankfully, regardless of profile.
    – AMM
    Apr 8, 2013 at 13:00
  • FYI - solution was worked out here: salesforce.stackexchange.com/questions/10350/… ... basically marking on the Account temporarily a TRUE, which Validation looks for to bypass with, then marking it False when done (all via trigger). Was tricky due to the circular relationship but ultimately worked. ;)
    – AMM
    Apr 8, 2013 at 13:24
1

If we want to run a trigger for a specific user then follow the below code:

first we have to call custom setting object there by we can get particular user id then follow the below piece of code:

trigger AvoidDuplicate on Contact_Relation__c (before insert)
{
    set<string> contd= new set<string>();
    set<string> orgcontains= new set<string>();
    if(trigger.isbefore && trigger.isinsert)
    {
        **hierachy_us__c ob=hierachy_us__c.getInstance(Userinfo.getUserId());//'getting present user id with the help of custom hierachy obj'
        if(ob.SetupOwnerId==Userinfo.getUserId())**// 'checking with the present user id'
        {
        System.debug('profile id:'+Userinfo.getProfileId());
        System.debug('user id:'+Userinfo.getUserId());
        for(Contact_Relation__c con : trigger.new)
        {
            contd.add(con.Name);
         }
        for(Contact_Relation__c dup:[select id, Name from Contact_Relation__c where name in:contd])
        {
           orgcontains.add(dup.Name) ;
        }
        for(Contact_Relation__c con : trigger.new)
        {
            if(orgcontains.contains(con.Name))
                con.adderror('this record already exists');
        }
        }
        }
} 
1
  • working good...
    – user44509
    May 1, 2017 at 4:19
0

Ultimately it seems that the way to bypass this is to do what I'd normally do in Validation Rules (along the lines of what @twamley mentions) - bypass it with a value in a field (versus a Profile reference). To accomplish this more simply, I'm going to try setting a hidden field to True on the ACCOUNT, and add that bypass to the Validation Rule on the OPP. This SHOULD work..........

2
  • I can't do it this way either. Touching the account, whether in Trigger A or Trigger B, causes bad results (spawns Trigger A again if done in Trigger B, Account is Read Only if done in Trigger A). Back to square one.
    – AMM
    Apr 4, 2013 at 13:59
  • Opened a new case and ended up resolving this there. salesforce.stackexchange.com/questions/10350/…
    – AMM
    Apr 5, 2013 at 18:12
0

A field on Opportunity just for temporary "bypass val. rule" sounds a bit fishy. If you're willing to rewrite your validation into a trigger on Opportunity, try this:

  1. Make a class (or edit existing one) that'd have a static Boolean variable called skipOpportunityCheck or similar.
  2. In your Account update trigger, just before the code that goes update relatedOpportunities set this static variable to true.
  3. In the Opportunities trigger where you'll have your rewritten "validation rule" go:

    Boolean canEditClosedWons = ...; // your val. rule here
    if(!(canEditClosedWons && SomeClass.skipOpportunityCheck)){
        // addError to all of them
    }
    

That way normal edit of Opportunities is blocked like it used to. Only when the "entry point" is edit of Account you'll have your bypass.

It's a tricky topic with no silver bullet. Other solutions include usage of custom settings, some flags on user object... Control Fields vs Heap "Mock" Fields for Branching Trigger Behavior would be a nice starting point if you want to dig into more generic discussion.

As others have mentioned - runAs method is good only for testing context.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .