Skip to content

fix(auth): let non-admins authenticate to the Tooling API - #21

Open
RubenHalman wants to merge 1 commit into
mainfrom
fix/non-admin-oauth-config-access
Open

fix(auth): let non-admins authenticate to the Tooling API#21
RubenHalman wants to merge 1 commit into
mainfrom
fix/non-admin-oauth-config-access

Conversation

@RubenHalman

Copy link
Copy Markdown
Member

Problem

A non-admin user with the Flow Scanner permission set can't use the app at all. The Scan Flows tab falls back to the setup wizard with:

Flow Scanner couldn't authenticate with the Tooling API.

The underlying error, pulled off the wire in a subscriber org, is:

Insufficient permissions to access OAuth configuration

Cause

One guard, in two places — ToolingAPI.getProductionAccessToken() and LFSSetupController.getConfig():

if (!Schema.sObjectType.Flow_Scanner_OAuth_Config_Protected__mdt.fields.Consumer_Key__c.isAccessible()) {
    throw auraError('Insufficient permissions to access OAuth configuration');
}

isAccessible() on that field returns false for every non-admin subscriber user, so the Consumer Key can never be read and the JWT is never minted. In LFSSetupController the same guard returns null, so getSetupStatus() also reports "Consumer Key not configured" to non-admins even when it is configured.

The guard buys no security. Flow_Scanner_OAuth_Config_Protected__mdt is a protected custom metadata type, so its records are only readable by Apex inside this package's namespace no matter what the running user's field permissions say. The namespace boundary is the control; the FLS check just excludes non-admins.

Writes are unaffected — saveConsumerKey() and testConnection() still go through requireAdmin().

Reproduction

In a subscriber org with the managed package installed (flow-scanner 3.4.0.1):

  1. Create a user on the Read Only profile and assign the Flow Scanner permission set.
  2. Pre-authorize them on the External Client App (see note below).
  3. Grant ApiEnabled, ViewSetup, ViewRoles, ViewAllCustomSettings.
  4. Open the Scan Flows tab as that user → setup wizard, and Insufficient permissions to access OAuth configuration in the Aura response.

Reverting the same user to System Administrator makes it work immediately.

Why there's no unit test

This only reproduces with the managed package in a subscriber org. In an unmanaged scratch org the type isn't protected, so isAccessible() returns true for a standard user and any such test passes with or without the guard — I wrote one, confirmed it passed with the bug deliberately reintroduced, and removed it rather than ship false comfort.

Validated instead by deploying this branch to a fresh scratch org: 39/39 local tests pass, 85% coverage.

Two related things worth a separate look

  • You can't pre-authorize the app with the packaged permission set. Adding lfscanner__Flow_Scanner to the External Client App's OAuth policies fails with Cannot modify managed object: entity=SetupEntityAccess. Admins have to create a throwaway unmanaged permission set purely to authorize the app. That isn't in docs/installation.md.
  • docs/installation.md steps 3–4 can't be followed in some orgs. ExternalClientAppManager/home, ExternalClientApps/home and ExternalClientAppSettings/home all return "Page not found" in a current Developer Edition org. The Metadata API route works (ExternalClientApplication + ExtlClntAppOauthSettings + ExtlClntAppGlobalOauthSettings + ExtlClntAppOauthConfigurablePolicies, deployed together — the OAuth settings and global settings must be in the same deploy).

🤖 Generated with Claude Code

Reading the Consumer Key from Flow_Scanner_OAuth_Config_Protected__mdt was
gated on Consumer_Key__c.isAccessible(). That check returns false for every
non-admin subscriber user, so ToolingAPI threw 'Insufficient permissions to
access OAuth configuration' and the app fell back to its setup wizard for
anyone without Customize Application.

The gate bought no security: the custom metadata type is *protected*, so its
records are only readable by Apex inside this package's namespace regardless
of the running user's field permissions. Setup actions that write the config
remain admin-gated via LFSSetupController.requireAdmin().

No unit test accompanies this: the behaviour only reproduces with the managed
package installed in a subscriber org. In an unmanaged scratch org the type is
not protected, so isAccessible() returns true for a standard user and a test
passes with or without the guard. Verified manually instead — see PR body.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant