fix(rbac): redirect on unauthorized namespace [r8s-564] (#1246)

Merging because this PR doesn't introduce any CI failures, compared to the release 2.33 CI run https://github.com/portainer/portainer-suite/actions/runs/17957775674
release/2.33
Ali 2025-09-24 13:22:42 +12:00 committed by GitHub
parent 732337615e
commit c21c91632f
7 changed files with 79 additions and 4 deletions

View File

@ -473,7 +473,7 @@ angular.module('portainer.kubernetes', ['portainer.app', registriesModule, custo
},
};
const resourcePools = {
const namespaces = {
name: 'kubernetes.resourcePools',
url: '/namespaces',
views: {
@ -499,7 +499,7 @@ angular.module('portainer.kubernetes', ['portainer.app', registriesModule, custo
},
};
const resourcePool = {
const namespace = {
name: 'kubernetes.resourcePools.resourcePool',
url: '/:id?tab',
views: {
@ -681,9 +681,9 @@ angular.module('portainer.kubernetes', ['portainer.app', registriesModule, custo
$stateRegistryProvider.register(node);
$stateRegistryProvider.register(nodeStats);
$stateRegistryProvider.register(kubectlShell);
$stateRegistryProvider.register(resourcePools);
$stateRegistryProvider.register(namespaces);
$stateRegistryProvider.register(namespaceCreation);
$stateRegistryProvider.register(resourcePool);
$stateRegistryProvider.register(namespace);
$stateRegistryProvider.register(namespaceAccess);
$stateRegistryProvider.register(volumes);
$stateRegistryProvider.register(volume);

View File

@ -2,6 +2,7 @@ import { AlertTriangle, Code, History, Minimize2 } from 'lucide-react';
import { useCurrentStateAndParams } from '@uirouter/react';
import LaptopCode from '@/assets/ico/laptop-code.svg?c';
import { useNamespaceAccessRedirect } from '@/react/kubernetes/namespaces/hooks/useNamespaceAccessRedirect';
import { PageHeader } from '@@/PageHeader';
import { Tab, WidgetTabs, findSelectedTabIndex } from '@@/Widget/WidgetTabs';
@ -30,6 +31,7 @@ export function ApplicationDetailsView() {
const {
params: { namespace, name },
} = stateAndParams;
useNamespaceAccessRedirect(namespace, { to: 'kubernetes.applications' });
// placements table data
const { placementsData, isPlacementsTableLoading, hasPlacementWarning } =

View File

@ -156,6 +156,20 @@ function createCommonHandlers() {
http.get('/api/endpoints/3/kubernetes/helm/test-release/history', () =>
HttpResponse.json(helmReleaseHistory)
),
http.get('/api/kubernetes/3/namespaces', () =>
HttpResponse.json([
{
Id: 'default',
Name: 'default',
Status: { phase: 'Active' },
Annotations: null,
CreationDate: '2021-01-01T00:00:00Z',
NamespaceOwner: '',
IsSystem: false,
IsDefault: true,
},
])
),
http.get('/api/kubernetes/3/namespaces/default/events', () =>
HttpResponse.json([])
),

View File

@ -5,6 +5,7 @@ import helm from '@/assets/ico/vendor/helm.svg?c';
import { PageHeader } from '@/react/components/PageHeader';
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
import { Authorized } from '@/react/hooks/useUser';
import { useNamespaceAccessRedirect } from '@/react/kubernetes/namespaces/hooks/useNamespaceAccessRedirect';
import { WidgetTitle, WidgetBody, Widget, Loading } from '@@/Widget';
import { Card } from '@@/Card';
@ -26,6 +27,7 @@ export function HelmApplicationView() {
const queryClient = useQueryClient();
const { params } = useCurrentStateAndParams();
const { name, namespace, revision } = params;
useNamespaceAccessRedirect(namespace, { to: 'kubernetes.applications' });
const helmHistoryQuery = useHelmHistory(environmentId, name, namespace);
const latestRevision = helmHistoryQuery.data?.[0]?.version;
const earlistRevision =

View File

@ -9,6 +9,7 @@ import { notifyError, notifySuccess } from '@/portainer/services/notifications';
import { useAuthorizations } from '@/react/hooks/useUser';
import { Annotation } from '@/react/kubernetes/annotations/types';
import { prepareAnnotations } from '@/react/kubernetes/utils';
import { useNamespaceAccessRedirect } from '@/react/kubernetes/namespaces/hooks/useNamespaceAccessRedirect';
import { Link } from '@@/Link';
import { PageHeader } from '@@/PageHeader';
@ -43,6 +44,7 @@ import {
export function CreateIngressView() {
const environmentId = useEnvironmentId();
const { params } = useCurrentStateAndParams();
useNamespaceAccessRedirect(params.namespace, { to: 'kubernetes.ingresses' });
const { authorized: isAuthorizedToAddEdit } = useAuthorizations([
'K8sIngressesW',
]);

View File

@ -2,6 +2,7 @@ import { useCurrentStateAndParams } from '@uirouter/react';
import { AlertTriangle, Code, Layers, History } from 'lucide-react';
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
import { useNamespaceAccessRedirect } from '@/react/kubernetes/namespaces/hooks/useNamespaceAccessRedirect';
import { PageHeader } from '@@/PageHeader';
import { findSelectedTabIndex, Tab, WidgetTabs } from '@@/Widget/WidgetTabs';
@ -20,6 +21,9 @@ export function NamespaceView() {
const {
params: { id: namespace },
} = stateAndParams;
useNamespaceAccessRedirect(namespace, {
to: 'kubernetes.resourcePools',
});
const environmentId = useEnvironmentId();
const eventWarningCount = useEventWarningsCount(environmentId, namespace);

View File

@ -0,0 +1,51 @@
import { useEffect } from 'react';
import { useCurrentStateAndParams, useRouter } from '@uirouter/react';
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
import { useNamespacesQuery } from '../queries/useNamespacesQuery';
type RedirectOptions = {
to: string;
params?: Record<string, unknown>;
};
/**
* Redirects away when the provided namespace is not in the allowed namespaces list for the current environment.
*/
export function useNamespaceAccessRedirect(
namespace?: string,
{ to, params } = { to: 'kubernetes.dashboard', params: {} } as RedirectOptions
) {
const router = useRouter();
const namespaceInParams = useCurrentStateAndParams().params.namespace;
const currentNamespace = namespace || namespaceInParams;
const environmentId = useEnvironmentId();
const namespacesQuery = useNamespacesQuery(environmentId);
useEffect(() => {
if (!currentNamespace) {
return;
}
if (namespacesQuery.isLoading || namespacesQuery.isFetching) {
return;
}
const namespaces = namespacesQuery.data ?? [];
const isAllowed = namespaces.some((ns) => ns.Name === currentNamespace);
if (!isAllowed) {
router.stateService.go(to, params);
}
}, [
currentNamespace,
to,
params,
router.stateService,
namespacesQuery.isLoading,
namespacesQuery.isFetching,
namespacesQuery.data,
]);
}