From 0e078f01be084e25ef7b4d0f6ee04f824b8b5768 Mon Sep 17 00:00:00 2001 From: McKayla Washburn Date: Fri, 7 Mar 2025 18:50:05 +0000 Subject: [PATCH 1/3] fix: improve permissions checks in organization settings --- site/src/pages/GroupsPage/GroupsPage.tsx | 21 ++++-- .../IdpSyncPage/IdpSyncPage.tsx | 25 +++++-- .../OrganizationMembersPage.tsx | 12 +++- .../OrganizationProvisionersPage.tsx | 32 ++++++--- .../OrganizationSettingsPage.tsx | 71 +++++++++++++------ .../ProvisionersPage/ProvisionersPage.tsx | 31 ++++---- 6 files changed, 137 insertions(+), 55 deletions(-) diff --git a/site/src/pages/GroupsPage/GroupsPage.tsx b/site/src/pages/GroupsPage/GroupsPage.tsx index a99ec44334530..b6280437ce61e 100644 --- a/site/src/pages/GroupsPage/GroupsPage.tsx +++ b/site/src/pages/GroupsPage/GroupsPage.tsx @@ -10,6 +10,7 @@ import { Loader } from "components/Loader/Loader"; import { SettingsHeader } from "components/SettingsHeader/SettingsHeader"; import { Stack } from "components/Stack/Stack"; import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility"; +import { RequirePermission } from "modules/permissions/RequirePermission"; import { type FC, useEffect } from "react"; import { Helmet } from "react-helmet-async"; import { useQuery } from "react-query"; @@ -54,16 +55,26 @@ export const GroupsPage: FC = () => { return ; } + const helmet = ( + + {pageTitle("Groups")} + + ); + const permissions = permissionsQuery.data?.[organization.id]; - if (!permissions) { - return ; + + if (!permissions?.viewGroups) { + return ( + <> + {helmet} + + + ); } return ( <> - - {pageTitle("Groups")} - + {helmet} { const { organization: organizationName } = useParams() as { organization: string; }; - const { organizations } = useOrganizationSettings(); - const organization = organizations?.find((o) => o.name === organizationName); + const { organization, organizationPermissions } = useOrganizationSettings(); const [groupField, setGroupField] = useState(""); const [roleField, setRoleField] = useState(""); @@ -80,6 +80,23 @@ export const IdpSyncPage: FC = () => { return ; } + const helmet = ( + + + {pageTitle("IdP Sync", organization.display_name || organization.name)} + + + ); + + if (!organizationPermissions?.viewIdpSyncSettings) { + return ( + <> + {helmet} + + + ); + } + const patchGroupSyncSettingsMutation = useMutation( patchGroupSyncSettings(organizationName, queryClient), ); @@ -103,9 +120,7 @@ export const IdpSyncPage: FC = () => { return ( <> - - {pageTitle("IdP Sync")} - + {helmet}
diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationMembersPage.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationMembersPage.tsx index 7ae0eb72bec91..ffa7b08b83742 100644 --- a/site/src/pages/OrganizationSettingsPage/OrganizationMembersPage.tsx +++ b/site/src/pages/OrganizationSettingsPage/OrganizationMembersPage.tsx @@ -15,6 +15,7 @@ import { displayError, displaySuccess } from "components/GlobalSnackbar/utils"; import { Stack } from "components/Stack/Stack"; import { useAuthenticated } from "contexts/auth/RequireAuth"; import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout"; +import { RequirePermission } from "modules/permissions/RequirePermission"; import { type FC, useState } from "react"; import { Helmet } from "react-helmet-async"; import { useMutation, useQuery, useQueryClient } from "react-query"; @@ -54,7 +55,7 @@ const OrganizationMembersPage: FC = () => { const [memberToDelete, setMemberToDelete] = useState(); - if (!organization || !organizationPermissions) { + if (!organization) { return ; } @@ -66,6 +67,15 @@ const OrganizationMembersPage: FC = () => { ); + if (!organizationPermissions) { + return ( + <> + {helmet} + + + ); + } + return ( <> {helmet} diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionersPage.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionersPage.tsx index 5a4965c039e1f..fc736975c07f5 100644 --- a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionersPage.tsx +++ b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionersPage.tsx @@ -4,6 +4,7 @@ import { EmptyState } from "components/EmptyState/EmptyState"; import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata"; import { useDashboard } from "modules/dashboard/useDashboard"; import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout"; +import { RequirePermission } from "modules/permissions/RequirePermission"; import type { FC } from "react"; import { Helmet } from "react-helmet-async"; import { useQuery } from "react-query"; @@ -15,7 +16,7 @@ const OrganizationProvisionersPage: FC = () => { const { organization: organizationName } = useParams() as { organization: string; }; - const { organization } = useOrganizationSettings(); + const { organization, organizationPermissions } = useOrganizationSettings(); const { entitlements } = useDashboard(); const { metadata } = useEmbeddedMetadata(); const buildInfoQuery = useQuery(buildInfo(metadata["build-info"])); @@ -25,16 +26,29 @@ const OrganizationProvisionersPage: FC = () => { return ; } + const helmet = ( + + + {pageTitle( + "Provisioners", + organization.display_name || organization.name, + )} + + + ); + + if (!organizationPermissions?.viewProvisioners) { + return ( + <> + {helmet} + + + ); + } + return ( <> - - - {pageTitle( - "Provisioners", - organization.display_name || organization.name, - )} - - + {helmet} { @@ -24,36 +27,58 @@ const OrganizationSettingsPage: FC = () => { deleteOrganization(queryClient), ); - if (!organization || !organizationPermissions?.editSettings) { + if (!organization) { return ; } + const helmet = ( + + + {pageTitle("Settings", organization.display_name || organization.name)} + + + ); + + if (!organizationPermissions?.editSettings) { + return ( + <> + {helmet} + + + ); + } + const error = updateOrganizationMutation.error ?? deleteOrganizationMutation.error; return ( - { - const updatedOrganization = - await updateOrganizationMutation.mutateAsync({ - organizationId: organization.id, - req: values, - }); - navigate(`/organizations/${updatedOrganization.name}/settings`); - displaySuccess("Organization settings updated."); - }} - onDeleteOrganization={async () => { - try { - await deleteOrganizationMutation.mutateAsync(organization.id); - displaySuccess("Organization deleted"); - navigate("/organizations"); - } catch (error) { - displayError(getErrorMessage(error, "Failed to delete organization")); - } - }} - /> + <> + {helmet} + { + const updatedOrganization = + await updateOrganizationMutation.mutateAsync({ + organizationId: organization.id, + req: values, + }); + navigate(`/organizations/${updatedOrganization.name}/settings`); + displaySuccess("Organization settings updated."); + }} + onDeleteOrganization={async () => { + try { + await deleteOrganizationMutation.mutateAsync(organization.id); + displaySuccess("Organization deleted"); + navigate("/organizations"); + } catch (error) { + displayError( + getErrorMessage(error, "Failed to delete organization"), + ); + } + }} + /> + ); }; diff --git a/site/src/pages/OrganizationSettingsPage/ProvisionersPage/ProvisionersPage.tsx b/site/src/pages/OrganizationSettingsPage/ProvisionersPage/ProvisionersPage.tsx index 051f916c3ad99..ced95a95e02c0 100644 --- a/site/src/pages/OrganizationSettingsPage/ProvisionersPage/ProvisionersPage.tsx +++ b/site/src/pages/OrganizationSettingsPage/ProvisionersPage/ProvisionersPage.tsx @@ -2,6 +2,7 @@ import { EmptyState } from "components/EmptyState/EmptyState"; import { TabLink, Tabs, TabsList } from "components/Tabs/Tabs"; import { useSearchParamsKey } from "hooks/useSearchParamsKey"; import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout"; +import { RequirePermission } from "modules/permissions/RequirePermission"; import type { FC } from "react"; import { Helmet } from "react-helmet-async"; import { pageTitle } from "utils/page"; @@ -16,26 +17,32 @@ const ProvisionersPage: FC = () => { }); if (!organization || !organizationPermissions?.viewProvisionerJobs) { + return ; + } + + const helmet = ( + + + {pageTitle( + "Provisioners", + organization.display_name || organization.name, + )} + + + ); + + if (!organizationPermissions?.viewProvisioners) { return ( <> - - {pageTitle("Provisioners")} - - + {helmet} + ); } return ( <> - - - {pageTitle( - "Provisioners", - organization.display_name || organization.name, - )} - - + {helmet}
From f0a52d626cc66fc4e3b6228b033edbff44054f22 Mon Sep 17 00:00:00 2001 From: McKayla Washburn Date: Fri, 7 Mar 2025 21:19:53 +0000 Subject: [PATCH 2/3] missed a spot --- .../CustomRolesPage/CustomRolesPage.tsx | 106 +++++++++--------- 1 file changed, 54 insertions(+), 52 deletions(-) diff --git a/site/src/pages/OrganizationSettingsPage/CustomRolesPage/CustomRolesPage.tsx b/site/src/pages/OrganizationSettingsPage/CustomRolesPage/CustomRolesPage.tsx index ca567fdce7836..67d511c0665d3 100644 --- a/site/src/pages/OrganizationSettingsPage/CustomRolesPage/CustomRolesPage.tsx +++ b/site/src/pages/OrganizationSettingsPage/CustomRolesPage/CustomRolesPage.tsx @@ -2,8 +2,8 @@ import { getErrorMessage } from "api/errors"; import { deleteOrganizationRole, organizationRoles } from "api/queries/roles"; import type { Role } from "api/typesGenerated"; import { DeleteDialog } from "components/Dialogs/DeleteDialog/DeleteDialog"; +import { EmptyState } from "components/EmptyState/EmptyState"; import { displayError, displaySuccess } from "components/GlobalSnackbar/utils"; -import { Loader } from "components/Loader/Loader"; import { SettingsHeader } from "components/SettingsHeader/SettingsHeader"; import { Stack } from "components/Stack/Stack"; import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility"; @@ -22,7 +22,7 @@ export const CustomRolesPage: FC = () => { const { organization: organizationName } = useParams() as { organization: string; }; - const { organizationPermissions } = useOrganizationSettings(); + const { organization, organizationPermissions } = useOrganizationSettings(); const [roleToDelete, setRoleToDelete] = useState(); @@ -49,65 +49,67 @@ export const CustomRolesPage: FC = () => { } }, [organizationRolesQuery.error]); - if (!organizationPermissions) { - return ; + if (!organization) { + return ; } return ( - + <> - {pageTitle("Custom Roles")} + + {pageTitle( + "Custom Roles", + organization.display_name || organization.name, + )} + - - - - + + + - + - setRoleToDelete(undefined)} - onConfirm={async () => { - try { - if (roleToDelete) { - await deleteRoleMutation.mutateAsync(roleToDelete.name); + setRoleToDelete(undefined)} + onConfirm={async () => { + try { + if (roleToDelete) { + await deleteRoleMutation.mutateAsync(roleToDelete.name); + } + setRoleToDelete(undefined); + await organizationRolesQuery.refetch(); + displaySuccess("Custom role deleted successfully!"); + } catch (error) { + displayError( + getErrorMessage(error, "Failed to delete custom role"), + ); } - setRoleToDelete(undefined); - await organizationRolesQuery.refetch(); - displaySuccess("Custom role deleted successfully!"); - } catch (error) { - displayError( - getErrorMessage(error, "Failed to delete custom role"), - ); - } - }} - /> - + }} + /> + + ); }; From c4177f825653701812a872c5426cf48d39190345 Mon Sep 17 00:00:00 2001 From: McKayla Washburn Date: Fri, 7 Mar 2025 21:28:08 +0000 Subject: [PATCH 3/3] missed some more --- site/e2e/tests/auditLogs.spec.ts | 1 - site/src/pages/GroupsPage/GroupsPage.tsx | 1 - .../pages/TemplatePage/TemplatePageHeader.tsx | 1 - .../ExternalAuthPage/ExternalAuthPageView.tsx | 19 ------------------- site/src/pages/UserSettingsPage/Sidebar.tsx | 2 +- site/src/pages/UsersPage/UsersPage.tsx | 3 +-- 6 files changed, 2 insertions(+), 25 deletions(-) diff --git a/site/e2e/tests/auditLogs.spec.ts b/site/e2e/tests/auditLogs.spec.ts index 8afb2e714c695..31d3208c636fa 100644 --- a/site/e2e/tests/auditLogs.spec.ts +++ b/site/e2e/tests/auditLogs.spec.ts @@ -35,7 +35,6 @@ test("logins are logged", async ({ page }) => { await page.goto("/audit"); const username = users.auditor.username; - const user = currentUser(page); const loginMessage = `${username} logged in`; // Make sure those things we did all actually show up await resetSearch(page, username); diff --git a/site/src/pages/GroupsPage/GroupsPage.tsx b/site/src/pages/GroupsPage/GroupsPage.tsx index b6280437ce61e..d5ef810f9ff9d 100644 --- a/site/src/pages/GroupsPage/GroupsPage.tsx +++ b/site/src/pages/GroupsPage/GroupsPage.tsx @@ -2,7 +2,6 @@ import GroupAdd from "@mui/icons-material/GroupAddOutlined"; import { getErrorMessage } from "api/errors"; import { groupsByOrganization } from "api/queries/groups"; import { organizationsPermissions } from "api/queries/organizations"; -import { ErrorAlert } from "components/Alert/ErrorAlert"; import { Button } from "components/Button/Button"; import { EmptyState } from "components/EmptyState/EmptyState"; import { displayError } from "components/GlobalSnackbar/utils"; diff --git a/site/src/pages/TemplatePage/TemplatePageHeader.tsx b/site/src/pages/TemplatePage/TemplatePageHeader.tsx index b04a2c6d103f5..7bb1d9e54a4c2 100644 --- a/site/src/pages/TemplatePage/TemplatePageHeader.tsx +++ b/site/src/pages/TemplatePage/TemplatePageHeader.tsx @@ -168,7 +168,6 @@ export const TemplatePageHeader: FC = ({ onDeleteTemplate, }) => { const getLink = useLinks(); - const hasIcon = template.icon && template.icon !== ""; const templateLink = getLink( linkToTemplate(template.organization_name, template.name), ); diff --git a/site/src/pages/UserSettingsPage/ExternalAuthPage/ExternalAuthPageView.tsx b/site/src/pages/UserSettingsPage/ExternalAuthPage/ExternalAuthPageView.tsx index 5cb1e4fddeac0..845918a7b75ed 100644 --- a/site/src/pages/UserSettingsPage/ExternalAuthPage/ExternalAuthPageView.tsx +++ b/site/src/pages/UserSettingsPage/ExternalAuthPage/ExternalAuthPageView.tsx @@ -110,25 +110,6 @@ interface ExternalAuthRowProps { onValidateExternalAuth: () => void; } -const StyledBadge = styled(Badge)(({ theme }) => ({ - "& .MuiBadge-badge": { - // Make a circular background for the icon. Background provides contrast, with a thin - // border to separate it from the avatar image. - backgroundColor: `${theme.palette.background.paper}`, - borderStyle: "solid", - borderColor: `${theme.palette.secondary.main}`, - borderWidth: "thin", - - // Override the default minimum sizes, as they are larger than what we want. - minHeight: "0px", - minWidth: "0px", - // Override the default "height", which is usually set to some constant value. - height: "auto", - // Padding adds some room for the icon to live in. - padding: "0.1em", - }, -})); - const ExternalAuthRow: FC = ({ app, unlinked, diff --git a/site/src/pages/UserSettingsPage/Sidebar.tsx b/site/src/pages/UserSettingsPage/Sidebar.tsx index 5cc8c54dcbda9..69d51ae3bb227 100644 --- a/site/src/pages/UserSettingsPage/Sidebar.tsx +++ b/site/src/pages/UserSettingsPage/Sidebar.tsx @@ -22,7 +22,7 @@ interface SidebarProps { } export const Sidebar: FC = ({ user }) => { - const { entitlements, experiments } = useDashboard(); + const { entitlements } = useDashboard(); const showSchedulePage = entitlements.features.advanced_template_scheduling.enabled; diff --git a/site/src/pages/UsersPage/UsersPage.tsx b/site/src/pages/UsersPage/UsersPage.tsx index 9d2aaadefc96d..c8677e3a44f47 100644 --- a/site/src/pages/UsersPage/UsersPage.tsx +++ b/site/src/pages/UsersPage/UsersPage.tsx @@ -23,7 +23,7 @@ import { useDashboard } from "modules/dashboard/useDashboard"; import { type FC, useState } from "react"; import { Helmet } from "react-helmet-async"; import { useMutation, useQuery, useQueryClient } from "react-query"; -import { useLocation, useNavigate, useSearchParams } from "react-router-dom"; +import { useNavigate, useSearchParams } from "react-router-dom"; import { pageTitle } from "utils/page"; import { generateRandomString } from "utils/random"; import { ResetPasswordDialog } from "./ResetPasswordDialog"; @@ -39,7 +39,6 @@ type UserPageProps = { const UsersPage: FC = ({ defaultNewPassword }) => { const queryClient = useQueryClient(); const navigate = useNavigate(); - const location = useLocation(); const searchParamsResult = useSearchParams(); const { entitlements } = useDashboard(); const [searchParams] = searchParamsResult; pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy