Add abac for db replications

This commit is contained in:
Joshen Lim committed 2022-08-29 17:31:26 +08:00
1 parent a036513bbc
commit e45f7d02f7
8 files changed
+109 -88

No files matched your search

@@ -1,6 +1,5 @@
import { FC, useState } from 'react'
import { observer } from 'mobx-react-lite'
import * as Tooltip from '@radix-ui/react-tooltip'
import { Badge, IconLoader, Toggle } from '@supabase/ui'
import { PermissionAction } from '@supabase/shared-types/out/constants'
@@ -82,31 +81,12 @@ const ExtensionCard: FC<Props> = ({ extension }) => {
{loading ? (
<IconLoader className="animate-spin" size={16} />
) : (
<Tooltip.Root delayDuration={0}>
<Tooltip.Trigger>
<Toggle
size="tiny"
checked={isOn}
disabled={!canUpdateExtentions}
onChange={() => (isOn ? disableExtension() : enableExtension())}
/>
</Tooltip.Trigger>
{!canUpdateExtentions && (
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'bg-scale-100 rounded py-1 px-2 leading-none shadow',
'border-scale-200 border',
].join(' ')}
>
<span className="text-scale-1200 text-xs">
You need additional permissions to update database extensions
</span>
</div>
</Tooltip.Content>
)}
</Tooltip.Root>
<Toggle
size="tiny"
checked={isOn}
disabled={!canUpdateExtentions}
onChange={() => (isOn ? disableExtension() : enableExtension())}
/>
)}
</div>
<div
@@ -1,12 +1,14 @@
import { FC, useState } from 'react'
import { observer } from 'mobx-react-lite'
import { partition, isNull } from 'lodash'
import { Input, IconSearch } from '@supabase/ui'
import { Input, IconSearch, IconAlertCircle } from '@supabase/ui'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useStore, useFlag } from 'hooks'
import { useStore, useFlag, checkPermissions } from 'hooks'
import ExtensionCard from './ExtensionCard'
import { HIDDEN_EXTENSIONS } from './Extensions.constants'
import NoSearchResults from 'components/to-be-cleaned/NoSearchResults'
import InformationBox from 'components/ui/InformationBox'
interface Props {}
@@ -31,10 +33,15 @@ const Extensions: FC<Props> = ({}) => {
(ext: any) => !isNull(ext.installed_version)
)
const canUpdateExtentions = checkPermissions(
PermissionAction.TENANT_SQL_ADMIN_WRITE,
'extensions'
)
return (
<div className="p-4">
<div className="mb-4">
<div className="flex">
<div className="flex items-center justify-between">
<Input
size="small"
placeholder={'Filter'}
@@ -42,6 +49,14 @@ const Extensions: FC<Props> = ({}) => {
onChange={(e) => setFilterString(e.target.value)}
icon={<IconSearch size="tiny" />}
/>
{!canUpdateExtentions && (
<div className="w-[500px]">
<InformationBox
icon={<IconAlertCircle className="text-scale-1100" strokeWidth={2} />}
title="You need additional permissions to update database extensions"
/>
</div>
)}
</div>
</div>
@@ -1,11 +1,13 @@
import { FC, useState } from 'react'
import { observer } from 'mobx-react-lite'
import { Button, Input, Toggle, IconSearch } from '@supabase/ui'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { Button, Input, Toggle, IconSearch, IconAlertCircle } from '@supabase/ui'
import { useStore } from 'hooks'
import { checkPermissions, useStore } from 'hooks'
import { confirmAlert } from 'components/to-be-cleaned/ModalsDeprecated/ConfirmModal'
import Table from 'components/to-be-cleaned/Table'
import NoSearchResults from 'components/to-be-cleaned/NoSearchResults'
import InformationBox from 'components/ui/InformationBox'
interface Props {
onSelectPublication: (id: number) => void
@@ -15,6 +17,17 @@ const PublicationsList: FC<Props> = ({ onSelectPublication = () => {} }) => {
const { ui, meta } = useStore()
const [filterString, setFilterString] = useState<string>('')
const canUpdatePublications = checkPermissions(
PermissionAction.TENANT_SQL_ADMIN_WRITE,
'publications'
)
const publicationEvents = [
{ event: 'Insert', key: 'publish_insert' },
{ event: 'Update', key: 'publish_update' },
{ event: 'Delete', key: 'publish_delete' },
{ event: 'Truncate', key: 'publish_truncate' },
]
const publications =
filterString.length === 0
? meta.publications.list()
@@ -51,17 +64,22 @@ const PublicationsList: FC<Props> = ({ onSelectPublication = () => {} }) => {
<div className="mb-4">
<div className="flex items-center justify-between">
<div className="flex items-center">
<div>
<Input
size="small"
icon={<IconSearch size="tiny" />}
placeholder={'Filter'}
value={filterString}
onChange={(e) => setFilterString(e.target.value)}
<Input
size="small"
icon={<IconSearch size="tiny" />}
placeholder={'Filter'}
value={filterString}
onChange={(e) => setFilterString(e.target.value)}
/>
</div>
{!canUpdatePublications && (
<div className="w-[500px]">
<InformationBox
icon={<IconAlertCircle className="text-scale-1100" strokeWidth={2} />}
title="You need additional permissions to update database replications"
/>
</div>
</div>
<div className=""></div>
)}
</div>
</div>
{publications.length === 0 ? (
@@ -98,42 +116,18 @@ const PublicationsList: FC<Props> = ({ onSelectPublication = () => {} }) => {
<Table.td className="hidden lg:table-cell" style={{ width: '25%' }}>
{x.id}
</Table.td>
<Table.td>
<div className="flex justify-center">
<Toggle
size="tiny"
checked={x.publish_insert}
onChange={() => toggleListenEvent(x, 'insert', x.publish_insert)}
/>
</div>
</Table.td>
<Table.td>
<div className="flex justify-center">
<Toggle
size="tiny"
checked={x.publish_update}
onChange={() => toggleListenEvent(x, 'update', x.publish_update)}
/>
</div>
</Table.td>
<Table.td>
<div className="flex justify-center">
<Toggle
size="tiny"
checked={x.publish_delete}
onChange={() => toggleListenEvent(x, 'delete', x.publish_delete)}
/>
</div>
</Table.td>
<Table.td>
<div className="flex justify-center">
<Toggle
size="tiny"
checked={x.publish_truncate}
onChange={() => toggleListenEvent(x, 'truncate', x.publish_truncate)}
/>
</div>
</Table.td>
{publicationEvents.map((event) => (
<Table.td key={event.key}>
<div className="flex justify-center">
<Toggle
size="tiny"
checked={x[event.key]}
disabled={!canUpdatePublications}
onChange={() => toggleListenEvent(x, 'insert', x[event.key])}
/>
</div>
</Table.td>
))}
<Table.td className="px-4 py-3 pr-2">
<div className="flex justify-end gap-2">
<Button
@@ -2,8 +2,9 @@ import { FC, useState } from 'react'
import { observer } from 'mobx-react-lite'
import { Badge, Toggle } from '@supabase/ui'
import { PostgresPublication, PostgresTable } from '@supabase/postgres-meta'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useStore } from 'hooks'
import { checkPermissions, useStore } from 'hooks'
import Table from 'components/to-be-cleaned/Table'
interface Props {
@@ -20,6 +21,11 @@ const PublicationsTableItem: FC<Props> = ({ table, selectedPublication }) => {
selectedPublication.tables?.find((x: any) => x.id == table.id) != undefined
)
const canUpdatePublications = checkPermissions(
PermissionAction.TENANT_SQL_ADMIN_WRITE,
'publications'
)
const toggleReplicationForTable = async (
table: PostgresTable,
publication: PostgresPublication
@@ -72,7 +78,7 @@ const PublicationsTableItem: FC<Props> = ({ table, selectedPublication }) => {
<Toggle
size="tiny"
align="right"
disabled={loading}
disabled={!canUpdatePublications || loading}
className="m-0 ml-2 mt-1 -mb-1 p-0"
checked={checked}
onChange={() => toggleReplicationForTable(table, selectedPublication)}
@@ -1,12 +1,14 @@
import { FC, useState } from 'react'
import { observer } from 'mobx-react-lite'
import { Button, Input, IconChevronLeft, IconSearch } from '@supabase/ui'
import { Button, Input, IconChevronLeft, IconSearch, IconAlertCircle } from '@supabase/ui'
import { PostgresPublication } from '@supabase/postgres-meta'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useStore } from 'hooks'
import { checkPermissions, useStore } from 'hooks'
import PublicationsTableItem from './PublicationsTableItem'
import Table from 'components/to-be-cleaned/Table'
import NoSearchResults from 'components/to-be-cleaned/NoSearchResults'
import InformationBox from 'components/ui/InformationBox'
interface Props {
selectedPublication: PostgresPublication
@@ -17,6 +19,11 @@ const PublicationsTables: FC<Props> = ({ selectedPublication, onSelectBack }) =>
const { meta } = useStore()
const [filterString, setFilterString] = useState<string>('')
const canUpdatePublications = checkPermissions(
PermissionAction.TENANT_SQL_ADMIN_WRITE,
'publications'
)
const tables =
filterString.length === 0
? meta.tables.list((table: any) => !meta.excludedSchemas.includes(table.schema))
@@ -73,7 +80,14 @@ const PublicationsTables: FC<Props> = ({ selectedPublication, onSelectBack }) =>
/>
</div>
</div>
<div className=""></div>
{!canUpdatePublications && (
<div className="w-[500px]">
<InformationBox
icon={<IconAlertCircle className="text-scale-1100" strokeWidth={2} />}
title="You need additional permissions to update database replications"
/>
</div>
)}
</div>
</div>
{tables.length === 0 ? (
@@ -31,12 +31,17 @@ const ProjectLayout = ({
hideHeader = false,
hideIconBar = false,
}: PropsWithChildren<Props>) => {
const { ui } = useStore()
const ongoingIncident = useFlag('ongoingIncident')
const projectName = ui.selectedProject?.name
return (
<>
<Head>
<title>{title ? `${title} | Supabase` : 'Supabase'}</title>
<title>
{title ? `${title} | Supabase` : projectName ? `${projectName} | Supabase` : 'Supabase'}
</title>
<meta name="description" content="Supabase Studio" />
<link rel="icon" href="/favicon.ico" />
</Head>
@@ -13,11 +13,7 @@ const DatabaseExtensions: NextPageWithLayout = () => {
return <NoPermission isFullPage resourceText="view database extensions" />
}
return (
<div className="p-4">
<Extensions />
</div>
)
return <Extensions />
}
DatabaseExtensions.getLayout = (page) => <DatabaseLayout title="Database">{page}</DatabaseLayout>
@@ -1,10 +1,12 @@
import { useState } from 'react'
import { observer } from 'mobx-react-lite'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useStore } from 'hooks'
import { checkPermissions, useStore } from 'hooks'
import { NextPageWithLayout } from 'types'
import { DatabaseLayout } from 'components/layouts'
import { PublicationsList, PublicationsTables } from 'components/interfaces/Database'
import NoPermission from 'components/ui/NoPermission'
// [Joshen] Technically, best that we have these as separate URLs
// makes it easier to manage state, but foresee that this page might
@@ -14,9 +16,18 @@ const DatabaseReplication: NextPageWithLayout = () => {
const { meta } = useStore()
const publications = meta.publications.list()
const canViewPublications = checkPermissions(
PermissionAction.TENANT_SQL_ADMIN_READ,
'publications'
)
const [selectedPublicationId, setSelectedPublicationId] = useState<number>()
const selectedPublication = publications.find((pub) => pub.id === selectedPublicationId)
if (!canViewPublications) {
return <NoPermission isFullPage resourceText="view database publications" />
}
return (
<div className="p-4">
{selectedPublicationId === undefined ? (