mirror of
https://github.com/supabase/supabase.git
synced 2026-06-18 13:43:53 +08:00
## Context For a table that has RLS enabled, but a policy with just `true` for the role `public` The RLS tester was incorrectly reporting that `anon` doesn't have access Was happening as we weren't considering policies that apply to the `public` role (which applies to _all_ roles) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * RLS tester now treats explicitly-public policies as applicable regardless of the impersonated role, improving policy coverage accuracy. * **Refactor** * Consolidated RLS test state computation to improve consistency of access badges and policy messaging. * **Tests** * Added comprehensive tests validating RLS scenarios, badge states, and policy/role messaging. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
169 lines
6.4 KiB
TypeScript
169 lines
6.4 KiB
TypeScript
import {
|
|
Badge,
|
|
cn,
|
|
Tabs_Shadcn_,
|
|
TabsContent_Shadcn_,
|
|
TabsList_Shadcn_,
|
|
TabsTrigger_Shadcn_,
|
|
} from 'ui'
|
|
import { Admonition } from 'ui-patterns'
|
|
|
|
import { Results } from '../../SQLEditor/UtilityPanel/Results'
|
|
import { RLSTableCard } from './RLSTableCard'
|
|
import { ParseQueryResults } from './RLSTester.types'
|
|
import { deriveRLSTestState } from './RLSTesterResults.utils'
|
|
import { useTestQueryRLS } from './useTestQueryRLS'
|
|
import type { Policy } from '@/components/interfaces/Auth/Policies/PolicyTableRow/PolicyTableRow.utils'
|
|
|
|
interface RLSTesterResultsProps {
|
|
results: Object[]
|
|
autoLimit: boolean
|
|
parseQueryResults: ParseQueryResults
|
|
handleSelectEditPolicy: (policy: Policy) => void
|
|
}
|
|
|
|
export const RLSTesterResults = ({
|
|
results,
|
|
autoLimit,
|
|
parseQueryResults,
|
|
handleSelectEditPolicy,
|
|
}: RLSTesterResultsProps) => {
|
|
const { limit } = useTestQueryRLS()
|
|
|
|
const {
|
|
isServiceRole,
|
|
tableWithRLSEnabledButNoPolicies,
|
|
tableWithRLSEnabledWithPolicyFalse,
|
|
noAccessToData,
|
|
} = deriveRLSTestState(parseQueryResults)
|
|
|
|
return (
|
|
<div className="p-5 pt-4">
|
|
<div className="flex items-center gap-x-2 mb-2">
|
|
<p className="text-sm">Summary</p>
|
|
{noAccessToData ? (
|
|
<Badge variant="destructive">No access</Badge>
|
|
) : (
|
|
<Badge variant="success">{results.length > 0 ? 'Can access' : 'Has access'}</Badge>
|
|
)}
|
|
</div>
|
|
|
|
<Tabs_Shadcn_ defaultValue="policies">
|
|
<TabsList_Shadcn_ className="gap-x-3">
|
|
<TabsTrigger_Shadcn_ value="policies" className="px-2">
|
|
Policies applied
|
|
</TabsTrigger_Shadcn_>
|
|
<TabsTrigger_Shadcn_ value="data" className="px-2">
|
|
Data preview
|
|
</TabsTrigger_Shadcn_>
|
|
</TabsList_Shadcn_>
|
|
|
|
{!!parseQueryResults && (
|
|
<div className="border rounded-sm flex items-center justify-between px-3 py-1.5 mt-3">
|
|
<div className="flex items-center gap-x-2">
|
|
<p className="text-xs text-foreground-light">Ran as</p>
|
|
{!parseQueryResults.role ? (
|
|
<code className="text-code-inline">postgres</code>
|
|
) : parseQueryResults.user ? (
|
|
<p className="text-sm truncate max-w-52">{parseQueryResults.user.email}</p>
|
|
) : parseQueryResults.role === 'anon' ? (
|
|
<p className="text-xs">an Anonymous user</p>
|
|
) : null}
|
|
</div>
|
|
|
|
{parseQueryResults.role === 'anon' && (
|
|
<p className="text-foreground-light text-xs">Not logged in user</p>
|
|
)}
|
|
{!!parseQueryResults.user && (
|
|
<code className="text-code-inline">ID: {parseQueryResults.user.id}</code>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
<TabsContent_Shadcn_ value="policies" className="mt-0">
|
|
{!isServiceRole &&
|
|
(!!tableWithRLSEnabledButNoPolicies ? (
|
|
<Admonition showIcon={false} type="default" className="rounded-sm mt-2">
|
|
<p className="mb-0.5!">This user has no access to any rows from this query</p>
|
|
<p className="text-foreground-light">
|
|
The table{' '}
|
|
<code className="text-code-inline">
|
|
{tableWithRLSEnabledButNoPolicies.schema}.
|
|
{tableWithRLSEnabledButNoPolicies.table}
|
|
</code>{' '}
|
|
has RLS enabled but no policies set up for the{' '}
|
|
<code className="text-code-inline break-keep!">{parseQueryResults.role}</code>{' '}
|
|
role.
|
|
</p>
|
|
</Admonition>
|
|
) : tableWithRLSEnabledWithPolicyFalse ? (
|
|
<Admonition showIcon={false} type="default" className="rounded-sm mt-2">
|
|
<p className="mb-0.5!">This user has no access to any rows from this query</p>
|
|
<p className="text-foreground-light">
|
|
The table{' '}
|
|
<code className="text-code-inline">
|
|
{tableWithRLSEnabledWithPolicyFalse.schema}.
|
|
{tableWithRLSEnabledWithPolicyFalse.table}
|
|
</code>{' '}
|
|
has a policy that evaluates to
|
|
<code className="text-code-inline break-keep!">false</code> for the{' '}
|
|
<code className="text-code-inline break-keep!">{parseQueryResults.role}</code>{' '}
|
|
role.
|
|
</p>
|
|
</Admonition>
|
|
) : null)}
|
|
|
|
{isServiceRole && (
|
|
<Admonition showIcon={false} type="default" className="rounded-sm mt-2">
|
|
<p className="mb-0.5!">
|
|
The <code className="text-code-inline">postgres</code> role has access to all rows
|
|
for this query
|
|
</p>
|
|
<p className="text-foreground-light">
|
|
The <code className="text-code-inline">postgres</code> role has admin privileges and
|
|
bypasses all RLS policies.
|
|
</p>
|
|
</Admonition>
|
|
)}
|
|
|
|
<div className="flex flex-col gap-y-2 mt-4">
|
|
<p className="text-sm">Table access</p>
|
|
{!isServiceRole && (
|
|
<div className="flex flex-col gap-y-2">
|
|
{parseQueryResults?.tables.map((x) => {
|
|
const { schema, table, tablePolicies, isRLSEnabled } = x
|
|
return (
|
|
<RLSTableCard
|
|
key={`${schema}.${table}`}
|
|
table={{ schema, name: table, isRLSEnabled }}
|
|
role={parseQueryResults.role}
|
|
policies={tablePolicies}
|
|
handleSelectEditPolicy={handleSelectEditPolicy}
|
|
/>
|
|
)
|
|
})}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</TabsContent_Shadcn_>
|
|
<TabsContent_Shadcn_ value="data" className="mt-2">
|
|
<div
|
|
className={cn(
|
|
'grow flex flex-col border overflow-hidden',
|
|
results.length === 0 ? 'rounded-sm h-32' : 'rounded-t h-56'
|
|
)}
|
|
>
|
|
<Results rows={results} />
|
|
</div>
|
|
{results.length > 0 && (
|
|
<p className="border border-t-0 rounded-b font-mono text-xs text-foreground-light p-2">
|
|
{results.length} row{results.length > 1 ? 's' : ''}
|
|
{autoLimit && results.length >= limit && ` (Limited to only ${limit} rows)`}
|
|
</p>
|
|
)}
|
|
</TabsContent_Shadcn_>
|
|
</Tabs_Shadcn_>
|
|
</div>
|
|
)
|
|
}
|