mirror of https://github.com/portainer/portainer
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/17957775674release/2.33
parent
732337615e
commit
c21c91632f
|
@ -473,7 +473,7 @@ angular.module('portainer.kubernetes', ['portainer.app', registriesModule, custo
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const resourcePools = {
|
const namespaces = {
|
||||||
name: 'kubernetes.resourcePools',
|
name: 'kubernetes.resourcePools',
|
||||||
url: '/namespaces',
|
url: '/namespaces',
|
||||||
views: {
|
views: {
|
||||||
|
@ -499,7 +499,7 @@ angular.module('portainer.kubernetes', ['portainer.app', registriesModule, custo
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const resourcePool = {
|
const namespace = {
|
||||||
name: 'kubernetes.resourcePools.resourcePool',
|
name: 'kubernetes.resourcePools.resourcePool',
|
||||||
url: '/:id?tab',
|
url: '/:id?tab',
|
||||||
views: {
|
views: {
|
||||||
|
@ -681,9 +681,9 @@ angular.module('portainer.kubernetes', ['portainer.app', registriesModule, custo
|
||||||
$stateRegistryProvider.register(node);
|
$stateRegistryProvider.register(node);
|
||||||
$stateRegistryProvider.register(nodeStats);
|
$stateRegistryProvider.register(nodeStats);
|
||||||
$stateRegistryProvider.register(kubectlShell);
|
$stateRegistryProvider.register(kubectlShell);
|
||||||
$stateRegistryProvider.register(resourcePools);
|
$stateRegistryProvider.register(namespaces);
|
||||||
$stateRegistryProvider.register(namespaceCreation);
|
$stateRegistryProvider.register(namespaceCreation);
|
||||||
$stateRegistryProvider.register(resourcePool);
|
$stateRegistryProvider.register(namespace);
|
||||||
$stateRegistryProvider.register(namespaceAccess);
|
$stateRegistryProvider.register(namespaceAccess);
|
||||||
$stateRegistryProvider.register(volumes);
|
$stateRegistryProvider.register(volumes);
|
||||||
$stateRegistryProvider.register(volume);
|
$stateRegistryProvider.register(volume);
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { AlertTriangle, Code, History, Minimize2 } from 'lucide-react';
|
||||||
import { useCurrentStateAndParams } from '@uirouter/react';
|
import { useCurrentStateAndParams } from '@uirouter/react';
|
||||||
|
|
||||||
import LaptopCode from '@/assets/ico/laptop-code.svg?c';
|
import LaptopCode from '@/assets/ico/laptop-code.svg?c';
|
||||||
|
import { useNamespaceAccessRedirect } from '@/react/kubernetes/namespaces/hooks/useNamespaceAccessRedirect';
|
||||||
|
|
||||||
import { PageHeader } from '@@/PageHeader';
|
import { PageHeader } from '@@/PageHeader';
|
||||||
import { Tab, WidgetTabs, findSelectedTabIndex } from '@@/Widget/WidgetTabs';
|
import { Tab, WidgetTabs, findSelectedTabIndex } from '@@/Widget/WidgetTabs';
|
||||||
|
@ -30,6 +31,7 @@ export function ApplicationDetailsView() {
|
||||||
const {
|
const {
|
||||||
params: { namespace, name },
|
params: { namespace, name },
|
||||||
} = stateAndParams;
|
} = stateAndParams;
|
||||||
|
useNamespaceAccessRedirect(namespace, { to: 'kubernetes.applications' });
|
||||||
|
|
||||||
// placements table data
|
// placements table data
|
||||||
const { placementsData, isPlacementsTableLoading, hasPlacementWarning } =
|
const { placementsData, isPlacementsTableLoading, hasPlacementWarning } =
|
||||||
|
|
|
@ -156,6 +156,20 @@ function createCommonHandlers() {
|
||||||
http.get('/api/endpoints/3/kubernetes/helm/test-release/history', () =>
|
http.get('/api/endpoints/3/kubernetes/helm/test-release/history', () =>
|
||||||
HttpResponse.json(helmReleaseHistory)
|
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', () =>
|
http.get('/api/kubernetes/3/namespaces/default/events', () =>
|
||||||
HttpResponse.json([])
|
HttpResponse.json([])
|
||||||
),
|
),
|
||||||
|
|
|
@ -5,6 +5,7 @@ import helm from '@/assets/ico/vendor/helm.svg?c';
|
||||||
import { PageHeader } from '@/react/components/PageHeader';
|
import { PageHeader } from '@/react/components/PageHeader';
|
||||||
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
||||||
import { Authorized } from '@/react/hooks/useUser';
|
import { Authorized } from '@/react/hooks/useUser';
|
||||||
|
import { useNamespaceAccessRedirect } from '@/react/kubernetes/namespaces/hooks/useNamespaceAccessRedirect';
|
||||||
|
|
||||||
import { WidgetTitle, WidgetBody, Widget, Loading } from '@@/Widget';
|
import { WidgetTitle, WidgetBody, Widget, Loading } from '@@/Widget';
|
||||||
import { Card } from '@@/Card';
|
import { Card } from '@@/Card';
|
||||||
|
@ -26,6 +27,7 @@ export function HelmApplicationView() {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { params } = useCurrentStateAndParams();
|
const { params } = useCurrentStateAndParams();
|
||||||
const { name, namespace, revision } = params;
|
const { name, namespace, revision } = params;
|
||||||
|
useNamespaceAccessRedirect(namespace, { to: 'kubernetes.applications' });
|
||||||
const helmHistoryQuery = useHelmHistory(environmentId, name, namespace);
|
const helmHistoryQuery = useHelmHistory(environmentId, name, namespace);
|
||||||
const latestRevision = helmHistoryQuery.data?.[0]?.version;
|
const latestRevision = helmHistoryQuery.data?.[0]?.version;
|
||||||
const earlistRevision =
|
const earlistRevision =
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { notifyError, notifySuccess } from '@/portainer/services/notifications';
|
||||||
import { useAuthorizations } from '@/react/hooks/useUser';
|
import { useAuthorizations } from '@/react/hooks/useUser';
|
||||||
import { Annotation } from '@/react/kubernetes/annotations/types';
|
import { Annotation } from '@/react/kubernetes/annotations/types';
|
||||||
import { prepareAnnotations } from '@/react/kubernetes/utils';
|
import { prepareAnnotations } from '@/react/kubernetes/utils';
|
||||||
|
import { useNamespaceAccessRedirect } from '@/react/kubernetes/namespaces/hooks/useNamespaceAccessRedirect';
|
||||||
|
|
||||||
import { Link } from '@@/Link';
|
import { Link } from '@@/Link';
|
||||||
import { PageHeader } from '@@/PageHeader';
|
import { PageHeader } from '@@/PageHeader';
|
||||||
|
@ -43,6 +44,7 @@ import {
|
||||||
export function CreateIngressView() {
|
export function CreateIngressView() {
|
||||||
const environmentId = useEnvironmentId();
|
const environmentId = useEnvironmentId();
|
||||||
const { params } = useCurrentStateAndParams();
|
const { params } = useCurrentStateAndParams();
|
||||||
|
useNamespaceAccessRedirect(params.namespace, { to: 'kubernetes.ingresses' });
|
||||||
const { authorized: isAuthorizedToAddEdit } = useAuthorizations([
|
const { authorized: isAuthorizedToAddEdit } = useAuthorizations([
|
||||||
'K8sIngressesW',
|
'K8sIngressesW',
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { useCurrentStateAndParams } from '@uirouter/react';
|
||||||
import { AlertTriangle, Code, Layers, History } from 'lucide-react';
|
import { AlertTriangle, Code, Layers, History } from 'lucide-react';
|
||||||
|
|
||||||
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
||||||
|
import { useNamespaceAccessRedirect } from '@/react/kubernetes/namespaces/hooks/useNamespaceAccessRedirect';
|
||||||
|
|
||||||
import { PageHeader } from '@@/PageHeader';
|
import { PageHeader } from '@@/PageHeader';
|
||||||
import { findSelectedTabIndex, Tab, WidgetTabs } from '@@/Widget/WidgetTabs';
|
import { findSelectedTabIndex, Tab, WidgetTabs } from '@@/Widget/WidgetTabs';
|
||||||
|
@ -20,6 +21,9 @@ export function NamespaceView() {
|
||||||
const {
|
const {
|
||||||
params: { id: namespace },
|
params: { id: namespace },
|
||||||
} = stateAndParams;
|
} = stateAndParams;
|
||||||
|
useNamespaceAccessRedirect(namespace, {
|
||||||
|
to: 'kubernetes.resourcePools',
|
||||||
|
});
|
||||||
|
|
||||||
const environmentId = useEnvironmentId();
|
const environmentId = useEnvironmentId();
|
||||||
const eventWarningCount = useEventWarningsCount(environmentId, namespace);
|
const eventWarningCount = useEventWarningsCount(environmentId, namespace);
|
||||||
|
|
|
@ -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,
|
||||||
|
]);
|
||||||
|
}
|
Loading…
Reference in New Issue