Skip to content

Commit

Permalink
feat: merge from main
Browse files Browse the repository at this point in the history
  • Loading branch information
shahargl committed Nov 7, 2024
1 parent fc6e0d4 commit 84e1ff8
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,21 @@ def _authorize(self, authenticated_entity: AuthenticatedEntity) -> None:
except Exception:
raise HTTPException(status_code=401, detail="Permission check failed")
return allowed

def authorize_resource(
self, resource_type, resource_id, authenticated_entity: AuthenticatedEntity
) -> None:
# use Keycloak's UMA to authorize
try:
permission = UMAPermission(
resource=resource_id,
)
allowed = self.keycloak_uma.permissions_check(
token=authenticated_entity.token, permissions=[permission]
)
if not allowed:
raise HTTPException(status_code=401, detail="Permission check failed")
# secure fallback
except Exception:
raise HTTPException(status_code=401, detail="Permission check failed")
return allowed
20 changes: 14 additions & 6 deletions keep-ui/app/alerts/alerts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import { ViewAlertModal } from "./ViewAlertModal";
import { useRouter, useSearchParams } from "next/navigation";
import AlertChangeStatusModal from "./alert-change-status-modal";
import { useAlertPolling } from "utils/hooks/usePusher";
import NotFound from "@/app/not-found";
import NotAuthorized from "@/app/not-authorized";

const defaultPresets: Preset[] = [
{
Expand All @@ -25,7 +27,7 @@ const defaultPresets: Preset[] = [
is_noisy: false,
alerts_count: 0,
should_do_noise_now: false,
tags: []
tags: [],
},
{
id: "dismissed",
Expand All @@ -35,7 +37,7 @@ const defaultPresets: Preset[] = [
is_noisy: false,
alerts_count: 0,
should_do_noise_now: false,
tags: []
tags: [],
},
{
id: "groups",
Expand All @@ -45,7 +47,7 @@ const defaultPresets: Preset[] = [
is_noisy: false,
alerts_count: 0,
should_do_noise_now: false,
tags: []
tags: [],
},
{
id: "without-incident",
Expand All @@ -55,7 +57,7 @@ const defaultPresets: Preset[] = [
is_noisy: false,
alerts_count: 0,
should_do_noise_now: false,
tags: []
tags: [],
},
];

Expand Down Expand Up @@ -97,11 +99,13 @@ export default function Alerts({ presetName }: AlertsProps) {
const selectedPreset = presets.find(
(preset) => preset.name.toLowerCase() === decodeURIComponent(presetName)
);

const { data: pollAlerts } = useAlertPolling();
const {
data: alerts = [],
isLoading: isAsyncLoading,
mutate: mutateAlerts,
error: alertsError,
} = usePresetAlerts(selectedPreset ? selectedPreset.name : "");
useEffect(() => {
const fingerprint = searchParams?.get("alertPayloadFingerprint");
Expand All @@ -119,8 +123,12 @@ export default function Alerts({ presetName }: AlertsProps) {
}
}, [mutateAlerts, pollAlerts]);

if (selectedPreset === undefined) {
return null;
if (!selectedPreset) {
return <NotFound />;
}

if (alertsError) {
return <NotAuthorized />;
}

return (
Expand Down
36 changes: 36 additions & 0 deletions keep-ui/app/not-authorized.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"use client";

import { Link } from "@/components/ui";
import { Title, Button, Subtitle } from "@tremor/react";
import Image from "next/image";
import { useRouter } from "next/navigation";

export default function NotAuthorized() {
const router = useRouter();
return (
<div className="flex flex-col items-center justify-center h-full">
<Title>403 Not Authorized</Title>
<Subtitle>
You do not have permission to access this page. If you believe this is
an error, please contact us on{" "}
<Link
href="https://slack.keephq.dev/"
target="_blank"
rel="noopener noreferrer"
>
Slack
</Link>
</Subtitle>
<Image src="/keep.svg" alt="Keep" width={150} height={150} />
<Button
onClick={() => {
router.back();
}}
color="orange"
variant="secondary"
>
Go back
</Button>
</div>
);
}
2 changes: 1 addition & 1 deletion keep-ui/app/not-found.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export default function NotFound() {
const router = useRouter();
return (
<div className="flex flex-col items-center justify-center h-full">
<Title>Page not found</Title>
<Title>404 Page not found</Title>
<Subtitle>
If you believe this is an error, please contact us on{" "}
<Link
Expand Down
100 changes: 57 additions & 43 deletions keep-ui/components/navbar/AlertsLinks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,36 @@ import Modal from "@/components/ui/Modal";
import CreatableMultiSelect from "@/components/ui/CreatableMultiSelect";
import { useLocalStorage } from "utils/hooks/useLocalStorage";
import { ActionMeta, MultiValue } from "react-select";
import {MdFlashOff} from "react-icons/md";
import { MdFlashOff } from "react-icons/md";

type AlertsLinksProps = {
session: Session | null;
};

type PresetConfig = {
path: string;
icon: any;
label: string;
};

const PRESET_CONFIGS: Record<string, PresetConfig> = {
feed: {
path: "/alerts/feed",
icon: AiOutlineSwap,
label: "Feed",
},
"without-incident": {
path: "/alerts/without-incident",
icon: MdFlashOff,
label: "Without Incident",
},
dismissed: {
path: "/alerts/dismissed",
icon: SilencedDoorbellNotification,
label: "Dismissed",
},
};

export const AlertsLinks = ({ session }: AlertsLinksProps) => {
const [isTagModalOpen, setIsTagModalOpen] = useState(false);
const [selectedTags, setSelectedTags] = useState<string[]>([]);
Expand All @@ -34,8 +58,10 @@ export const AlertsLinks = ({ session }: AlertsLinksProps) => {
});

const [staticPresets, setStaticPresets] = useState(staticPresetsOrderFromLS);

const [storedTags, setStoredTags] = useLocalStorage<string[]>("selectedTags", []);
const [storedTags, setStoredTags] = useLocalStorage<string[]>(
"selectedTags",
[]
);

useEffect(() => {
if (
Expand All @@ -52,14 +78,6 @@ export const AlertsLinks = ({ session }: AlertsLinksProps) => {
}
}, []);

const mainPreset = staticPresets.find((preset) => preset.name === "feed");
const dismissedPreset = staticPresets.find(
(preset) => preset.name === "dismissed"
);
const withoutIncidentPreset = staticPresets.find(
(preset) => preset.name === "without-incident"
);

const handleTagSelect = (
newValue: MultiValue<{ value: string; label: string }>,
actionMeta: ActionMeta<{ value: string; label: string }>
Expand All @@ -78,6 +96,26 @@ export const AlertsLinks = ({ session }: AlertsLinksProps) => {
setIsTagModalOpen(true);
};

const renderPresetLink = (presetName: string) => {
const preset = staticPresets.find((p) => p.name === presetName);
const config = PRESET_CONFIGS[presetName];

if (!preset || !config) return null;

return (
<li key={presetName}>
<LinkWithIcon
href={config.path}
icon={config.icon}
count={preset.alerts_count}
testId={`menu-alerts-${presetName}`}
>
<Subtitle>{config.label}</Subtitle>
</LinkWithIcon>
</li>
);
};

return (
<>
<Disclosure as="div" className="space-y-1" defaultOpen>
Expand All @@ -93,7 +131,8 @@ export const AlertsLinks = ({ session }: AlertsLinksProps) => {
"absolute left-full ml-2 cursor-pointer text-gray-400 transition-opacity",
{
"opacity-100 text-orange-500": selectedTags.length > 0,
"opacity-0 group-hover:opacity-100 group-hover:text-orange-500": selectedTags.length === 0
"opacity-0 group-hover:opacity-100 group-hover:text-orange-500":
selectedTags.length === 0,
}
)}
size={16}
Expand All @@ -114,42 +153,15 @@ export const AlertsLinks = ({ session }: AlertsLinksProps) => {
as="ul"
className="space-y-2 overflow-auto min-w-[max-content] p-2 pr-4"
>
<li>
<LinkWithIcon
href="/alerts/feed"
icon={AiOutlineSwap}
count={mainPreset?.alerts_count}
testId="menu-alerts-feed"
>
<Subtitle>Feed</Subtitle>
</LinkWithIcon>
</li>
<li>
<LinkWithIcon
href="/alerts/without-incident"
icon={MdFlashOff}
count={withoutIncidentPreset?.alerts_count}
testId="menu-alerts-without-incident"
>
<Subtitle>Without Incident</Subtitle>
</LinkWithIcon>
</li>
{renderPresetLink("feed")}
{renderPresetLink("without-incident")}
{session && (
<CustomPresetAlertLinks
session={session}
selectedTags={selectedTags}
/>
)}
<li>
<LinkWithIcon
href="/alerts/dismissed"
icon={SilencedDoorbellNotification}
count={dismissedPreset?.alerts_count}
testId="menu-alerts-dismissed"
>
<Subtitle>Dismissed</Subtitle>
</LinkWithIcon>
</li>
{renderPresetLink("dismissed")}
</Disclosure.Panel>
</>
)}
Expand All @@ -161,7 +173,9 @@ export const AlertsLinks = ({ session }: AlertsLinksProps) => {
>
<div className="space-y-2">
<Subtitle>Select tags to watch</Subtitle>
<Callout title="" color="orange">Customize your presets list by watching specific tags.</Callout>
<Callout title="" color="orange">
Customize your presets list by watching specific tags.
</Callout>
<CreatableMultiSelect
value={tempSelectedTags.map((tag) => ({
value: tag,
Expand Down
2 changes: 2 additions & 0 deletions keep-ui/utils/hooks/useAlerts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export const useAlerts = () => {
data: alertsFromEndpoint = [],
mutate,
isLoading,
error,
} = useAllAlerts(presetName, options);

useEffect(() => {
Expand All @@ -85,6 +86,7 @@ export const useAlerts = () => {
data: Array.from(alertsMap.values()),
mutate: mutate,
isLoading: isLoading,
error: error,
};
};

Expand Down
15 changes: 14 additions & 1 deletion keep/api/routes/preset.py
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ def update_preset(

@router.get(
"/{preset_name}/alerts",
description="Get a preset for tenant",
description="Get the alerts of a preset",
)
def get_preset_alerts(
request: Request,
Expand Down Expand Up @@ -429,6 +429,19 @@ def get_preset_alerts(
preset_dto = PresetDto(**preset.to_dict())
else:
preset_dto = PresetDto(**preset.dict())

# get all preset ids that the user has access to
identity_manager = IdentityManagerFactory.get_identity_manager(
authenticated_entity.tenant_id
)
# Note: if no limitations (allowed_preset_ids is []), then all presets are allowed
allowed_preset_ids = identity_manager.get_user_permission_on_resource_type(
resource_type="preset",
authenticated_entity=authenticated_entity,
)
if allowed_preset_ids and str(preset_dto.id) not in allowed_preset_ids:
raise HTTPException(403, "Not authorized to access this preset")

search_engine = SearchEngine(tenant_id=tenant_id)
preset_alerts = search_engine.search_alerts(preset_dto.query)
logger.info("Got preset alerts", extra={"preset_name": preset_name})
Expand Down

0 comments on commit 84e1ff8

Please sign in to comment.