-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #22 from ls1intum/keycloak-roles
Adding Keycloak To Server
- Loading branch information
Showing
43 changed files
with
1,204 additions
and
91 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
30 changes: 30 additions & 0 deletions
30
clients/core/src/PhaseMapping/ExternalRouters/ExternalRoutes.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { ExtendedRouteObject } from '@/interfaces/extended_route_object' | ||
import { Route, Routes } from 'react-router-dom' | ||
import ErrorBoundary from '../../ErrorBoundary' | ||
import { PermissionRestriction } from '../../management/PermissionRestriction' | ||
|
||
interface ExternalRoutesProps { | ||
routes: ExtendedRouteObject[] | ||
} | ||
|
||
export const ExternalRoutes: React.FC<ExternalRoutesProps> = ({ routes }: ExternalRoutesProps) => { | ||
return ( | ||
<> | ||
<Routes> | ||
{routes.map((route, index) => ( | ||
<Route | ||
key={index} | ||
path={route.path} | ||
element={ | ||
<PermissionRestriction requiredPermissions={route.requiredPermissions || []}> | ||
<ErrorBoundary fallback={<div>Route loading failed</div>}> | ||
{route.element} | ||
</ErrorBoundary> | ||
</PermissionRestriction> | ||
} | ||
/> | ||
))} | ||
</Routes> | ||
</> | ||
) | ||
} |
22 changes: 4 additions & 18 deletions
22
clients/core/src/PhaseMapping/ExternalRouters/TemplateRoutes.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
68 changes: 68 additions & 0 deletions
68
clients/core/src/PhaseMapping/ExternalSidebars/ExternalSidebar.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import { SidebarMenuItemProps } from '@/interfaces/sidebar' | ||
import { useAuthStore } from '@/zustand/useAuthStore' | ||
import { InsideSidebarMenuItem } from '../../Sidebar/InsideSidebar/components/InsideSidebarMenuItem' | ||
import { getPermissionString } from '@/interfaces/permission_roles' | ||
import { useCourseStore } from '@/zustand/useCourseStore' | ||
import { useParams } from 'react-router-dom' | ||
|
||
interface ExternalSidebarProps { | ||
rootPath: string | ||
title?: string | ||
sidebarElement: SidebarMenuItemProps | ||
} | ||
|
||
export const ExternalSidebarComponent: React.FC<ExternalSidebarProps> = ({ | ||
title, | ||
rootPath, | ||
sidebarElement, | ||
}: ExternalSidebarProps) => { | ||
// Example of using a custom hook | ||
const { permissions } = useAuthStore() // Example of calling your custom hook | ||
const { courses } = useCourseStore() | ||
const courseId = useParams<{ courseId: string }>().courseId | ||
|
||
const course = courses.find((c) => c.id === courseId) | ||
|
||
let hasComponentPermission = false | ||
if (sidebarElement.requiredPermissions && sidebarElement.requiredPermissions.length > 0) { | ||
hasComponentPermission = sidebarElement.requiredPermissions.some((role) => { | ||
return permissions.includes(getPermissionString(role, course?.name, course?.semester_tag)) | ||
}) | ||
} else { | ||
// no permissions required | ||
hasComponentPermission = true | ||
} | ||
|
||
return ( | ||
<> | ||
{hasComponentPermission && ( | ||
<InsideSidebarMenuItem | ||
title={title || sidebarElement.title} | ||
icon={sidebarElement.icon} | ||
goToPath={rootPath + sidebarElement.goToPath} | ||
subitems={ | ||
sidebarElement.subitems | ||
?.filter((subitem) => { | ||
const hasPermission = subitem.requiredPermissions?.some((role) => { | ||
return permissions.includes( | ||
getPermissionString(role, course?.name, course?.semester_tag), | ||
) | ||
}) | ||
if (subitem.requiredPermissions && !hasPermission) { | ||
return false | ||
} else { | ||
return true | ||
} | ||
}) | ||
.map((subitem) => { | ||
return { | ||
title: subitem.title, | ||
goToPath: rootPath + subitem.goToPath, | ||
} | ||
}) || [] | ||
} | ||
/> | ||
)} | ||
</> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { useAuthStore } from '@/zustand/useAuthStore' | ||
import { Role, getPermissionString } from '@/interfaces/permission_roles' | ||
import { useParams } from 'react-router-dom' | ||
import { useCourseStore } from '@/zustand/useCourseStore' | ||
import UnauthorizedPage from './components/UnauthorizedPage' | ||
|
||
interface PermissionRestrictionProps { | ||
requiredPermissions: Role[] | ||
children: React.ReactNode | ||
} | ||
|
||
// The server will only return data which the user is allowed to see | ||
// This is only needed if the user has to restrict permission further to not show some pages at all (i.e. settings pages) | ||
export const PermissionRestriction = ({ | ||
requiredPermissions, | ||
children, | ||
}: PermissionRestrictionProps): JSX.Element => { | ||
const { permissions } = useAuthStore() | ||
const { courses } = useCourseStore() | ||
const courseId = useParams<{ courseId: string }>().courseId | ||
|
||
// This means something /general | ||
if (!courseId) { | ||
// TODO: refine at later stage | ||
// has at least some prompt permission | ||
return <>{permissions.length > 0 ? children : <UnauthorizedPage />}</> | ||
} | ||
|
||
// in ManagementRoot is verified that this exists | ||
const course = courses.find((c) => c.id === courseId) | ||
|
||
let hasPermission = true | ||
if (requiredPermissions.length > 0) { | ||
hasPermission = requiredPermissions.some((role) => { | ||
return permissions.includes(getPermissionString(role, course?.name, course?.semester_tag)) | ||
}) | ||
} | ||
|
||
return <>{hasPermission ? children : <UnauthorizedPage />}</> | ||
} |
42 changes: 30 additions & 12 deletions
42
clients/core/src/management/components/UnauthorizedPage.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,25 +1,43 @@ | ||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert' | ||
import { Button } from '@/components/ui/button' | ||
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@/components/ui/card' | ||
import { useKeycloak } from '@/keycloak/useKeycloak' | ||
import { AlertTriangle, ArrowLeft } from 'lucide-react' | ||
import { AlertTriangle, ArrowLeft, LogOut } from 'lucide-react' | ||
import { useNavigate } from 'react-router-dom' | ||
|
||
export default function UnauthorizedPage() { | ||
const { logout } = useKeycloak() | ||
const navigate = useNavigate() | ||
|
||
return ( | ||
<div className='min-h-screen flex items-center justify-center bg-background p-4'> | ||
<div className='max-w-md w-full space-y-8'> | ||
<Alert variant='destructive'> | ||
<AlertTriangle className='h-4 w-4' /> | ||
<AlertTitle>Access Denied</AlertTitle> | ||
<AlertDescription>You do not have permission to access this page.</AlertDescription> | ||
</Alert> | ||
<div className='text-center'> | ||
<Button variant='outline' onClick={logout}> | ||
<div className='fixed inset-0 bg-background/80 backdrop-blur-sm z-50 flex items-center justify-center p-4'> | ||
<Card className='max-w-md w-full shadow-lg'> | ||
<CardHeader> | ||
<CardTitle className='text-2xl font-bold text-center text-primary'> | ||
Access Denied | ||
</CardTitle> | ||
</CardHeader> | ||
<CardContent className='space-y-4'> | ||
<Alert | ||
variant='destructive' | ||
className='border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive' | ||
> | ||
<AlertTriangle className='h-4 w-4' /> | ||
<AlertTitle>Unauthorized</AlertTitle> | ||
<AlertDescription>You do not have permission to access this page.</AlertDescription> | ||
</Alert> | ||
</CardContent> | ||
<CardFooter className='flex justify-center space-x-4'> | ||
<Button variant='outline' onClick={() => navigate(-1)} className='w-full sm:w-auto'> | ||
<ArrowLeft className='mr-2 h-4 w-4' /> | ||
Go Back | ||
</Button> | ||
</div> | ||
</div> | ||
<Button variant='destructive' onClick={logout} className='w-full sm:w-auto'> | ||
<LogOut className='mr-2 h-4 w-4' /> | ||
Logout | ||
</Button> | ||
</CardFooter> | ||
</Card> | ||
</div> | ||
) | ||
} |
Oops, something went wrong.