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

More Decks by Chris Peterson

Other Decks in Programming


  1. Bindu Nair Anand Subramanian Chris Peterson Secure Apex Code with

    User Mode Database Operations #TDX22 - presented April 27, 2022
  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
  3. 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?
  4. 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!)
  5. 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
  6. - Andrew Waite, circa 2006 “System mode can do whatever

    the highest privilege user in that org can do, full stop”
  7. "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
  8. 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
  9. 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
  10. 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];
  11. 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
  12. 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;
  13. 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 );
  14. Introducing: User-Mode DB Ops User mode pilot feedback implemented -

    thank you Daniel Ballinger! List<Database.SaveResult> 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');
  15. 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<String, Set<String>> 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'); }
  16. 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<List<SObject>> 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<List<SObject>> searchList = [FIND 'website123' RETURNING Account(id,name) with user_mode];
  17. 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.
  18. 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.
  19. 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?
  20. Where we go from here Beta - Summer 22 Release

    • Compile-time SOQL, SOSL, and DML syntax • enhanced error handling GA - Winter 23 Release • Database.<insert/update/delete>Immediate • Database.<insert/update/delete>Async • Bug fixes, if you find any
  21. 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);
  22. 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.