Upgrade to Pro — share decks privately, control downloads, hide ads and more …

TDX22: User-Mode DB Ops

TDX22: User-Mode DB Ops

Chris Peterson

April 27, 2022
Tweet

More Decks by Chris Peterson

Other Decks in Programming

Transcript

  1. Bindu Nair
    Anand Subramanian
    Chris Peterson
    Secure Apex Code
    with User Mode Database
    Operations
    #TDX22 - presented April 27, 2022

    View Slide

  2. Lead Engineer, Apex
    [email protected]
    Principal Engineer, Apex
    [email protected]
    @anand13s
    Bindu Nair Anand Subramanian
    Sr Director, Product
    [email protected]
    @ca_peterson
    Chris Peterson
    Secure Apex Code: User-mode DB Ops

    View Slide

  3. View Slide

  4. “With great power system mode
    comes great responsibility”
    - Codey (attributed)

    View Slide

  5. System mode is trusted, admin-vetted logic run with extra rights
    • For trusted business logic
    • For data consistency
    Turns the running user into a super-user
    • Sometimes breaks the rules
    i.e. a AutoProc user with Customize App
    • Makes the site guest user interesting
    What is System Mode?

    View Slide

  6. When doesn't Apex use System Mode?
    ● Execute Anonymous
    ○ Unless you have "Author Apex"
    ● Custom Checkpoint actions
    ○ "breakpoints" in developer tools
    ● Connect in Apex (aka ConnectAPI)
    ● That's it. Really. Everything else is system
    mode.
    (well, now DB ops can too, if you ask!)

    View Slide

  7. with sharing vs without sharing
    •These are two flavors of system mode
    System mode doesn't run as another user
    •Same user, but with wall-climbing superpowers
    Doesn't impact user rights in other contexts or requests
    • Request-scoped
    What System Mode is Not

    View Slide

  8. - Andrew Waite, circa 2006
    “System mode can do whatever the highest
    privilege user in that org can do, full stop”

    View Slide

  9. "Full" aka without sharing
    • All user permissions are granted
    • Including modify all data
    • Not versioned
    • Flow & Apex Triggers use this
    What Really is System Mode?
    "Sharing Honored" aka with sharing
    ● Most user permissions are granted
    ● Excluding modify all data
    ● Versioned
    ● Full FLS and CRUD is granted
    ● Can access Apex classes without SetupEntityAccess to them

    View Slide

  10. No, *Really* how does it work?
    Depending on API version, Full does, well, everything.
    Sharing Honored doesn't grant:
    ● editPublicFilters*
    ● manageUsers*
    ● manageCssUsers*
    ● manageInternalUsers*
    ● portalSuperUser*
    ● managePartners*
    ● delegatedPortalUserAdmin*
    ● manageSvcCatalog*
    ● modifyAllData
    ● viewAllData
    ● viewAllUsers
    ● viewAllActivities
    ● viewPublicDashboards
    ● manageDashbdsInPubFolders
    ● viewPublicReports
    ● manageReportsInPubFolders
    ● insightsAppAdmin
    ● manageUnlistedGroups
    ● modifyMetadata
    ● viewAllForeignKeyNames
    * = may be granted in older API versions

    View Slide

  11. About that great responsibility….
    ● Easy to forget to add FLS/CRUD checks
    ○ Or do them wrong
    ● Exceptions to rules (Custom Settings)
    ● Deserialization attacks
    ● Manipulated client-side input
    ● Admins have limited insight

    View Slide

  12. Today's answers are… there
    Describe-based checks get verbose, labor-intensive
    if (Schema.SObjectType.Account.isAccessible() &&
    Schema.SObjectType.Account.fields.Name.isAccessible() &&
    Schema.SObjectType.Account.fields.OwnerId.isAccessible() &&
    Schema.SObjectType.Account.fields.Support__c.isAccessible() &&
    Schema.SObjectType.Account.fields.OwnerId.getReferenceTo()[0].getDescribe().isAccessible()) {
    return [SELECT Name, Owner.Name FROM Account WHERE Support__C = 'Premier'];
    } else {
    throw new AuraHandledException('Access Denied');
    }
    return [SELECT Name, Owner.Name FROM Account];

    View Slide

  13. There is.
    return
    [SELECT Name, Owner.Name FROM Account
    WHERE Support__C = 'Premier' WITH USER_MODE];
    There's got to be a better way!
    14 chars, 96% security less code

    View Slide

  14. Introducing: User-Mode DB Ops
    Compile-time syntax
    insert as user new Account(Name = 'GenWatt Throwback');
    Account a = [SELECT Id, Name FROM Account
    WHERE Support__c = 'Premier' WITH USER_MODE];
    a.Rating = 'Hot';
    update as system a;

    View Slide

  15. Introducing: User-Mode DB Ops
    Dynamic at runtime syntax
    Database.insert(
    new Account(Name = 'GenWatt Throwback'),
    AccessLevel.USER_MODE
    );
    Account a = Database.query('SELECT Id, Name FROM Account
    WHERE Rating = \'Hot\'',
    AccessLevel.USER_MODE
    );

    View Slide

  16. Introducing: User-Mode DB Ops
    User mode pilot feedback implemented - thank you Daniel Ballinger!
    List srList = Database.insert(
    new SObject[] {
    new Account(Name='foo', AnnualRevenue=2000), // no FLS edit on AnnualRevenue
    new Contact(LastName='foo', Email='foo'), // no FLS view on Email
    },
    false, // allOrNone
    AccessLevel.USER_MODE
    );
    System.assert(!srList.get(0).getErrors()[0].getFields().contains('AnnualRevenue’),
    'Missing Account.AnnualRevenue FLS');
    System.assert(!srList.get(1).getErrors()[0].getFields().contains('Email’),
    'Missing Contact.Email FLS');

    View Slide

  17. Introducing: User-Mode DB Ops
    Full Error Reporting - not just the first error!
    try {
    integer count = Database.countQuery(
    'select count(email) cnt from Contact where Account.website =\'foo\'',
    AccessLevel.USER_MODE
    );
    } catch (QueryException qe) {
    Map> inaccessibleFields = qe.getInaccessibleFields();
    System.assert(!inaccessibleFields.containsKey('Contact'), 'Missing Contact CRUD');
    System.assert(!inaccessibleFields.get('Contact').contains('Email'), 'Missing Contact.Email
    FLS');
    System.assert(!inaccessibleFields.get('Account').contains('Website'), 'Missing Account.Website
    FLS');
    }

    View Slide

  18. Introducing: User-Mode DB Ops
    SOSL returns no results if matched on inaccessible field
    String searchquery = 'Find \'test\' IN ALL FIELDS RETURNING Account(id,name)';
    List> searchList = Search.query(searchquery, AccessLevel.USER_MODE);
    Search.SearchResults sr = Search.find(searchquery, AccessLevel.USER_MODE);
    Search.SuggestionResults sgr = Search.suggest('all', 'Account', null,
    AccessLevel.USER_MODE);
    List> searchList = [FIND 'website123' RETURNING Account(id,name)
    with user_mode];

    View Slide

  19. Why this is Different
    ● Historical focus on easier CRUD/FLS checks
    ● Describes don’t scale well
    ● Instead of re-implementing CRUD/FLS on top of system mode
    We actually, for real, drop out of system mode for the operation
    ○ Then we re-enter system mode.
    This means that as Salesforce launches new features, like Restriction
    Rules, that operate on unique security models we can support them fully.

    View Slide

  20. Edge cases work right
    ● WITH SECURITY_ENFORCED doesn't work with Task.WhatId
    ● User-mode db ops returns all errors, not just the first
    ● Sharing based (or anything else we think of later!) works like the API
    ○ SOSL is a great example of a place where CRUD/FLS wasn't sufficient.

    View Slide

  21. No, if:
    ● You want to gracefully degrade,
    keep using stripInaccessible
    ● You use a framework that can
    be upgraded. It's better to swap
    this out low level.
    Yes, if:
    ● You want to support
    new-fangled features like
    restriction rules
    ● You're using WITH
    SECURITY_ENFORCED and
    don't want the WHERE or
    ORDER BY clause to be system
    mode.
    ● Be explicit in your intent
    Do I need to refactor all my security checks?

    View Slide

  22. Demo!

    View Slide

  23. Where we go from here
    Beta - Summer 22 Release
    ● Compile-time SOQL, SOSL, and DML syntax
    ● enhanced error handling
    GA - Winter 23 Release
    ● Database.Immediate
    ● Database.Async
    ● Bug fixes, if you find any

    View Slide

  24. Where we go from here
    User-mode gets us to a binary state: escalated, or not
    But that's not good enough. We want to make it flexible escalation:
    ● User-mode PLUS a permission set
    ○ Scoped for a single DB op
    ● Gives admins visibility into scope of escalation
    ● Compliance and security wins: code can't do unexpected things
    //this is one idea, final syntax is likely to differ greatly!
    PermissionSet permSet =
    [SELECT Id FROM PermissionSet WHERE Name = 'System_Mode_is_scary'];
    AccessLevel escalatedRights =
    AccessLevel.USER_MODE.addEscalation(permSet);
    Database.insert(stuff, escalatedRights);

    View Slide

  25. Where we go from here
    Secure by default
    ● We want to make escalation a conscious decision
    ● Someday, we're going to flip the default to user mode
    ○ We're going to give plenty of notice when we do that
    ○ Very open to suggestions on how to make that change smoother!
    ● We hope that use of full system mode becomes rare
    ○ But it's not going away!
    ○ Strong preference for permset based escalation when possible
    ○ Make it safe for our superhero to take a vacation
    ● Worth making your code explicit soon to ease migration
    ○ Yes, even if you want to keep using system mode, add it.

    View Slide

  26. Thank You
    Want the slides?
    https://speakerdeck.com/ca_peterso
    n/tdx22-user-mode
    Feedback?
    https://sfdc.co/UserMode

    View Slide