mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix: fix permissions for workspace creation (#17241)
This fixes the permissions check when creating a workspace by setting
the owner_id to the current user's id. This was originally setting
owner_id to *
```
createWorkspace: {
object: {
resource_type: "workspace",
organization_id: organizationId,
owner_id: userId,
},
action: "create",
},
```
This commit is contained in:
@@ -306,6 +306,7 @@ export const organizationsPermissions = (
|
||||
|
||||
export const workspacePermissionsByOrganization = (
|
||||
organizationIds: string[] | undefined,
|
||||
userId: string,
|
||||
) => {
|
||||
if (!organizationIds) {
|
||||
return { enabled: false };
|
||||
@@ -315,10 +316,9 @@ export const workspacePermissionsByOrganization = (
|
||||
queryKey: ["workspaces", organizationIds.sort(), "permissions"],
|
||||
queryFn: async () => {
|
||||
const prefixedChecks = organizationIds.flatMap((orgId) =>
|
||||
Object.entries(workspacePermissionChecks(orgId)).map(([key, val]) => [
|
||||
`${orgId}.${key}`,
|
||||
val,
|
||||
]),
|
||||
Object.entries(workspacePermissionChecks(orgId, userId)).map(
|
||||
([key, val]) => [`${orgId}.${key}`, val],
|
||||
),
|
||||
);
|
||||
|
||||
const response = await API.checkAuthorization({
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
export const workspacePermissionChecks = (organizationId: string) =>
|
||||
export const workspacePermissionChecks = (
|
||||
organizationId: string,
|
||||
userId: string,
|
||||
) =>
|
||||
({
|
||||
createWorkspaceForUser: {
|
||||
createWorkspace: {
|
||||
object: {
|
||||
resource_type: "workspace",
|
||||
organization_id: organizationId,
|
||||
owner_id: "*",
|
||||
owner_id: userId,
|
||||
},
|
||||
action: "create",
|
||||
},
|
||||
|
||||
@@ -17,10 +17,6 @@ import { Loader } from "components/Loader/Loader";
|
||||
import { useAuthenticated } from "contexts/auth/RequireAuth";
|
||||
import { useEffectEvent } from "hooks/hookPolyfills";
|
||||
import { useDashboard } from "modules/dashboard/useDashboard";
|
||||
import {
|
||||
type WorkspacePermissions,
|
||||
workspacePermissionChecks,
|
||||
} from "modules/permissions/workspaces";
|
||||
import { generateWorkspaceName } from "modules/workspaces/generateWorkspaceName";
|
||||
import { type FC, useCallback, useEffect, useRef, useState } from "react";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
@@ -30,6 +26,7 @@ import { pageTitle } from "utils/page";
|
||||
import type { AutofillBuildParameter } from "utils/richParameters";
|
||||
import { paramsUsedToCreateWorkspace } from "utils/workspace";
|
||||
import { CreateWorkspacePageView } from "./CreateWorkspacePageView";
|
||||
import { type CreateWSPermissions, createWorkspaceChecks } from "./permissions";
|
||||
|
||||
export const createWorkspaceModes = ["form", "auto", "duplicate"] as const;
|
||||
export type CreateWorkspaceMode = (typeof createWorkspaceModes)[number];
|
||||
@@ -67,7 +64,7 @@ const CreateWorkspacePage: FC = () => {
|
||||
const permissionsQuery = useQuery(
|
||||
templateQuery.data
|
||||
? checkAuthorization({
|
||||
checks: workspacePermissionChecks(templateQuery.data.organization_id),
|
||||
checks: createWorkspaceChecks(templateQuery.data.organization_id),
|
||||
})
|
||||
: { enabled: false },
|
||||
);
|
||||
@@ -209,7 +206,7 @@ const CreateWorkspacePage: FC = () => {
|
||||
externalAuthPollingState={externalAuthPollingState}
|
||||
startPollingExternalAuth={startPollingExternalAuth}
|
||||
hasAllRequiredExternalAuth={hasAllRequiredExternalAuth}
|
||||
permissions={permissionsQuery.data as WorkspacePermissions}
|
||||
permissions={permissionsQuery.data as CreateWSPermissions}
|
||||
parameters={realizedParameters as TemplateVersionParameter[]}
|
||||
presets={templateVersionPresetsQuery.data ?? []}
|
||||
creatingWorkspace={createWorkspaceMutation.isLoading}
|
||||
|
||||
@@ -28,7 +28,6 @@ import { Stack } from "components/Stack/Stack";
|
||||
import { Switch } from "components/Switch/Switch";
|
||||
import { UserAutocomplete } from "components/UserAutocomplete/UserAutocomplete";
|
||||
import { type FormikContextType, useFormik } from "formik";
|
||||
import type { WorkspacePermissions } from "modules/permissions/workspaces";
|
||||
import { generateWorkspaceName } from "modules/workspaces/generateWorkspaceName";
|
||||
import { type FC, useCallback, useEffect, useMemo, useState } from "react";
|
||||
import {
|
||||
@@ -47,7 +46,7 @@ import type {
|
||||
ExternalAuthPollingState,
|
||||
} from "./CreateWorkspacePage";
|
||||
import { ExternalAuthButton } from "./ExternalAuthButton";
|
||||
|
||||
import type { CreateWSPermissions } from "./permissions";
|
||||
export const Language = {
|
||||
duplicationWarning:
|
||||
"Duplicating a workspace only copies its parameters. No state from the old workspace is copied over.",
|
||||
@@ -69,7 +68,7 @@ export interface CreateWorkspacePageViewProps {
|
||||
parameters: TypesGen.TemplateVersionParameter[];
|
||||
autofillParameters: AutofillBuildParameter[];
|
||||
presets: TypesGen.Preset[];
|
||||
permissions: WorkspacePermissions;
|
||||
permissions: CreateWSPermissions;
|
||||
creatingWorkspace: boolean;
|
||||
onCancel: () => void;
|
||||
onSubmit: (
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
export const createWorkspaceChecks = (organizationId: string) =>
|
||||
({
|
||||
createWorkspaceForUser: {
|
||||
object: {
|
||||
resource_type: "workspace",
|
||||
organization_id: organizationId,
|
||||
owner_id: "*",
|
||||
},
|
||||
action: "create",
|
||||
},
|
||||
}) as const;
|
||||
|
||||
export type CreateWSPermissions = Record<
|
||||
keyof ReturnType<typeof createWorkspaceChecks>,
|
||||
boolean
|
||||
>;
|
||||
@@ -5,6 +5,7 @@ import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import { Margins } from "components/Margins/Margins";
|
||||
import { TabLink, Tabs, TabsList } from "components/Tabs/Tabs";
|
||||
import { useAuthenticated } from "contexts/auth/RequireAuth";
|
||||
import { workspacePermissionChecks } from "modules/permissions/workspaces";
|
||||
import {
|
||||
type FC,
|
||||
@@ -73,6 +74,7 @@ export const TemplateLayout: FC<PropsWithChildren> = ({
|
||||
children = <Outlet />,
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
const { user: me } = useAuthenticated();
|
||||
const { organization: organizationName = "default", template: templateName } =
|
||||
useParams() as { organization?: string; template: string };
|
||||
const { data, error, isLoading } = useQuery({
|
||||
@@ -81,7 +83,7 @@ export const TemplateLayout: FC<PropsWithChildren> = ({
|
||||
});
|
||||
const workspacePermissionsQuery = useQuery(
|
||||
checkAuthorization({
|
||||
checks: workspacePermissionChecks(organizationName),
|
||||
checks: workspacePermissionChecks(organizationName, me.id),
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ const meta: Meta<typeof TemplatePageHeader> = {
|
||||
canUpdateTemplate: true,
|
||||
},
|
||||
workspacePermissions: {
|
||||
createWorkspaceForUser: true,
|
||||
createWorkspace: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -35,7 +35,7 @@ export const CanNotUpdate: Story = {
|
||||
export const CannotCreateWorkspace: Story = {
|
||||
args: {
|
||||
workspacePermissions: {
|
||||
createWorkspaceForUser: false,
|
||||
createWorkspace: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -179,17 +179,16 @@ export const TemplatePageHeader: FC<TemplatePageHeaderProps> = ({
|
||||
<PageHeader
|
||||
actions={
|
||||
<>
|
||||
{!template.deprecated &&
|
||||
workspacePermissions.createWorkspaceForUser && (
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={<AddIcon />}
|
||||
component={RouterLink}
|
||||
to={`${templateLink}/workspace`}
|
||||
>
|
||||
Create Workspace
|
||||
</Button>
|
||||
)}
|
||||
{!template.deprecated && workspacePermissions.createWorkspace && (
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={<AddIcon />}
|
||||
component={RouterLink}
|
||||
to={`${templateLink}/workspace`}
|
||||
>
|
||||
Create Workspace
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{permissions.canUpdateTemplate && (
|
||||
<TemplateMenu
|
||||
|
||||
@@ -11,7 +11,7 @@ import { pageTitle } from "utils/page";
|
||||
import { TemplatesPageView } from "./TemplatesPageView";
|
||||
|
||||
export const TemplatesPage: FC = () => {
|
||||
const { permissions } = useAuthenticated();
|
||||
const { permissions, user: me } = useAuthenticated();
|
||||
const { showOrganizations } = useDashboard();
|
||||
|
||||
const searchParamsResult = useSearchParams();
|
||||
@@ -30,6 +30,7 @@ export const TemplatesPage: FC = () => {
|
||||
const workspacePermissionsQuery = useQuery(
|
||||
workspacePermissionsByOrganization(
|
||||
templatesQuery.data?.map((template) => template.organization_id),
|
||||
me.id,
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ export const WithTemplates: Story = {
|
||||
examples: [],
|
||||
workspacePermissions: {
|
||||
[MockTemplate.organization_id]: {
|
||||
createWorkspaceForUser: true,
|
||||
createWorkspace: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -94,7 +94,7 @@ export const CannotCreateWorkspaces: Story = {
|
||||
...WithTemplates.args,
|
||||
workspacePermissions: {
|
||||
[MockTemplate.organization_id]: {
|
||||
createWorkspaceForUser: false,
|
||||
createWorkspace: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -160,7 +160,7 @@ const TemplateRow: FC<TemplateRowProps> = ({
|
||||
{template.deprecated ? (
|
||||
<DeprecatedBadge />
|
||||
) : workspacePermissions?.[template.organization_id]
|
||||
?.createWorkspaceForUser ? (
|
||||
?.createWorkspace ? (
|
||||
<MuiButton
|
||||
size="small"
|
||||
css={styles.actionButton}
|
||||
|
||||
@@ -40,7 +40,7 @@ const WorkspacesPage: FC = () => {
|
||||
// each hook.
|
||||
const searchParamsResult = useSafeSearchParams();
|
||||
const pagination = usePagination({ searchParamsResult });
|
||||
const { permissions } = useAuthenticated();
|
||||
const { permissions, user: me } = useAuthenticated();
|
||||
const { entitlements } = useDashboard();
|
||||
|
||||
const templatesQuery = useQuery(templates());
|
||||
@@ -48,6 +48,7 @@ const WorkspacesPage: FC = () => {
|
||||
const orgPermissionsQuery = useQuery(
|
||||
workspacePermissionsByOrganization(
|
||||
templatesQuery.data?.map((template) => template.organization_id),
|
||||
me.id,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -59,7 +60,7 @@ const WorkspacesPage: FC = () => {
|
||||
|
||||
return templatesQuery.data.filter((template) => {
|
||||
const orgPermission = orgPermissionsQuery.data[template.organization_id];
|
||||
return orgPermission?.createWorkspaceForUser;
|
||||
return orgPermission?.createWorkspace;
|
||||
});
|
||||
}, [templatesQuery.data, orgPermissionsQuery.data]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user