rights system
This commit is contained in:
@@ -144,20 +144,30 @@ class PermissionService {
|
||||
// ── Admin methods ──
|
||||
|
||||
async getMatrix(): Promise<MatrixData> {
|
||||
const [fgResult, pResult, gpResult] = await Promise.all([
|
||||
const [fgResult, pResult, gpResult, userGroupsResult] = await Promise.all([
|
||||
pool.query('SELECT id, label, sort_order, maintenance FROM feature_groups ORDER BY sort_order'),
|
||||
pool.query('SELECT id, feature_group_id, label, description, sort_order FROM permissions ORDER BY feature_group_id, sort_order'),
|
||||
pool.query('SELECT authentik_group, permission_id FROM group_permissions'),
|
||||
// Also include all dashboard_ groups from users table
|
||||
pool.query(`SELECT DISTINCT g AS group_name FROM users, unnest(authentik_groups) AS g WHERE g LIKE 'dashboard_%' AND g != 'dashboard_admin'`),
|
||||
]);
|
||||
|
||||
const grants: Record<string, string[]> = {};
|
||||
const groupSet = new Set<string>();
|
||||
|
||||
// Add groups from group_permissions
|
||||
for (const row of gpResult.rows) {
|
||||
groupSet.add(row.authentik_group);
|
||||
if (!grants[row.authentik_group]) grants[row.authentik_group] = [];
|
||||
grants[row.authentik_group].push(row.permission_id);
|
||||
}
|
||||
|
||||
// Also add groups from users table (they may have no permissions yet)
|
||||
for (const row of userGroupsResult.rows) {
|
||||
groupSet.add(row.group_name);
|
||||
if (!grants[row.group_name]) grants[row.group_name] = [];
|
||||
}
|
||||
|
||||
const maintenance: Record<string, boolean> = {};
|
||||
for (const row of fgResult.rows) {
|
||||
maintenance[row.id] = row.maintenance;
|
||||
@@ -180,13 +190,14 @@ class PermissionService {
|
||||
}
|
||||
|
||||
async getUnknownGroups(): Promise<string[]> {
|
||||
// Groups from users table that are not yet in the permission matrix
|
||||
// Groups from users table that have zero permissions assigned
|
||||
// (they appear in the matrix but admin should be notified)
|
||||
const result = await pool.query(`
|
||||
SELECT DISTINCT g AS group_name
|
||||
FROM users, unnest(authentik_groups) AS g
|
||||
WHERE g LIKE 'dashboard_%'
|
||||
AND g NOT IN (SELECT DISTINCT authentik_group FROM group_permissions)
|
||||
AND g != 'dashboard_admin'
|
||||
AND g NOT IN (SELECT DISTINCT authentik_group FROM group_permissions)
|
||||
ORDER BY group_name
|
||||
`);
|
||||
return result.rows.map((r: any) => r.group_name);
|
||||
|
||||
@@ -10,6 +10,11 @@ import {
|
||||
Chip,
|
||||
CircularProgress,
|
||||
Collapse,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
FormControlLabel,
|
||||
IconButton,
|
||||
Switch,
|
||||
@@ -109,6 +114,7 @@ function PermissionMatrixTab() {
|
||||
|
||||
const [expandedGroups, setExpandedGroups] = useState<Record<string, boolean>>({});
|
||||
const [showDepEditor, setShowDepEditor] = useState(false);
|
||||
const [deleteGroupConfirm, setDeleteGroupConfirm] = useState<string | null>(null);
|
||||
|
||||
const toggleGroup = (groupId: string) => {
|
||||
setExpandedGroups(prev => ({ ...prev, [groupId]: !prev[groupId] }));
|
||||
@@ -137,16 +143,6 @@ function PermissionMatrixTab() {
|
||||
onError: () => showError('Fehler beim Speichern der Berechtigungen'),
|
||||
});
|
||||
|
||||
const addGroupMutation = useMutation({
|
||||
mutationFn: (groupName: string) => permissionsApi.setGroupPermissions(groupName, []),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['admin-permission-matrix'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['admin-unknown-groups'] });
|
||||
showSuccess('Gruppe hinzugefügt');
|
||||
},
|
||||
onError: () => showError('Fehler beim Hinzufügen der Gruppe'),
|
||||
});
|
||||
|
||||
const deleteGroupMutation = useMutation({
|
||||
mutationFn: (groupName: string) => permissionsApi.deleteGroup(groupName),
|
||||
onSuccess: () => {
|
||||
@@ -290,18 +286,10 @@ function PermissionMatrixTab() {
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
||||
{/* Unknown Groups Alert */}
|
||||
{unknownGroups && unknownGroups.length > 0 && (
|
||||
<Alert severity="warning" sx={{ alignItems: 'center' }}>
|
||||
<Typography variant="body2" sx={{ mb: 1 }}>
|
||||
Folgende Gruppen wurden in der Benutzertabelle gefunden, sind aber noch nicht in der Berechtigungsmatrix:
|
||||
<Alert severity="info" sx={{ alignItems: 'center' }}>
|
||||
<Typography variant="body2">
|
||||
Folgende Gruppen haben noch keine Berechtigungen zugewiesen: <strong>{unknownGroups.join(', ')}</strong>
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
|
||||
{unknownGroups.map(g => (
|
||||
<Button key={g} variant="outlined" size="small" startIcon={<AddIcon />}
|
||||
onClick={() => addGroupMutation.mutate(g)} disabled={addGroupMutation.isPending}>
|
||||
{g} hinzufügen
|
||||
</Button>
|
||||
))}
|
||||
</Box>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
@@ -339,7 +327,7 @@ function PermissionMatrixTab() {
|
||||
<DependencyEditor
|
||||
groupHierarchy={groupHierarchy}
|
||||
permissionDeps={permissionDeps}
|
||||
allGroups={[...nonAdminGroups, ...groups.filter(g => g === 'dashboard_admin')]}
|
||||
allGroups={nonAdminGroups}
|
||||
allPermissions={permissions}
|
||||
onSave={(config) => depConfigMutation.mutate(config)}
|
||||
isSaving={depConfigMutation.isPending}
|
||||
@@ -376,11 +364,8 @@ function PermissionMatrixTab() {
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 0.5 }}>
|
||||
{g.replace('dashboard_', '')}
|
||||
<Tooltip title={`Gruppe "${g}" entfernen`} placement="top">
|
||||
<IconButton size="small" onClick={() => {
|
||||
if (window.confirm(`Gruppe "${g}" und alle zugehörigen Berechtigungen wirklich entfernen?`)) {
|
||||
deleteGroupMutation.mutate(g);
|
||||
}
|
||||
}} sx={{ opacity: 0.4, '&:hover': { opacity: 1, color: 'error.main' } }}>
|
||||
<IconButton size="small" onClick={() => setDeleteGroupConfirm(g)}
|
||||
sx={{ opacity: 0.4, '&:hover': { opacity: 1, color: 'error.main' } }}>
|
||||
<DeleteIcon sx={{ fontSize: 14 }} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
@@ -471,6 +456,33 @@ function PermissionMatrixTab() {
|
||||
</TableContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Delete Group Confirmation Dialog */}
|
||||
<Dialog open={!!deleteGroupConfirm} onClose={() => setDeleteGroupConfirm(null)}>
|
||||
<DialogTitle>Gruppe entfernen</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
Soll die Gruppe "{deleteGroupConfirm}" und alle zugehörigen Berechtigungen
|
||||
wirklich entfernt werden? Diese Aktion kann nicht rückgängig gemacht werden.
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setDeleteGroupConfirm(null)}>Abbrechen</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="error"
|
||||
onClick={() => {
|
||||
if (deleteGroupConfirm) {
|
||||
deleteGroupMutation.mutate(deleteGroupConfirm);
|
||||
setDeleteGroupConfirm(null);
|
||||
}
|
||||
}}
|
||||
disabled={deleteGroupMutation.isPending}
|
||||
>
|
||||
{deleteGroupMutation.isPending ? <CircularProgress size={20} /> : 'Entfernen'}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -487,8 +499,28 @@ interface DependencyEditorProps {
|
||||
}
|
||||
|
||||
function DependencyEditor({ groupHierarchy, permissionDeps, allGroups, allPermissions, onSave, isSaving }: DependencyEditorProps) {
|
||||
const [editHierarchy, setEditHierarchy] = useState<Record<string, string[]>>(() => ({ ...groupHierarchy }));
|
||||
const [editDeps, setEditDeps] = useState<Record<string, string[]>>(() => ({ ...permissionDeps }));
|
||||
const groupSet = useMemo(() => new Set(allGroups), [allGroups]);
|
||||
const permIdSet = useMemo(() => new Set(allPermissions.map(p => p.id)), [allPermissions]);
|
||||
|
||||
// Filter saved config to only include groups/permissions that actually exist
|
||||
const [editHierarchy, setEditHierarchy] = useState<Record<string, string[]>>(() => {
|
||||
const filtered: Record<string, string[]> = {};
|
||||
for (const [g, inheritors] of Object.entries(groupHierarchy)) {
|
||||
if (groupSet.has(g)) {
|
||||
filtered[g] = inheritors.filter(i => groupSet.has(i));
|
||||
}
|
||||
}
|
||||
return filtered;
|
||||
});
|
||||
const [editDeps, setEditDeps] = useState<Record<string, string[]>>(() => {
|
||||
const filtered: Record<string, string[]> = {};
|
||||
for (const [p, deps] of Object.entries(permissionDeps)) {
|
||||
if (permIdSet.has(p)) {
|
||||
filtered[p] = deps.filter(d => permIdSet.has(d));
|
||||
}
|
||||
}
|
||||
return filtered;
|
||||
});
|
||||
|
||||
const nonAdminGroups = allGroups.filter(g => g !== 'dashboard_admin');
|
||||
const permOptions = allPermissions.map(p => p.id);
|
||||
|
||||
Reference in New Issue
Block a user