rights system
This commit is contained in:
@@ -76,6 +76,35 @@ class PermissionController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PUT /api/admin/permissions/bulk
|
||||||
|
* Bulk-update permissions for multiple groups in one request.
|
||||||
|
* Body: { updates: [{ group: string, permissions: string[] }] }
|
||||||
|
*/
|
||||||
|
async setBulkPermissions(req: Request, res: Response): Promise<void> {
|
||||||
|
try {
|
||||||
|
const { updates } = req.body;
|
||||||
|
|
||||||
|
if (!Array.isArray(updates)) {
|
||||||
|
res.status(400).json({ success: false, message: 'updates must be an array' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const u of updates) {
|
||||||
|
if (typeof u.group !== 'string' || !Array.isArray(u.permissions)) {
|
||||||
|
res.status(400).json({ success: false, message: 'Each update must have group (string) and permissions (array)' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await permissionService.setMultipleGroupPermissions(updates, req.user!.id);
|
||||||
|
res.json({ success: true, message: 'Berechtigungen aktualisiert' });
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Failed to set bulk permissions', { error });
|
||||||
|
res.status(500).json({ success: false, message: 'Fehler beim Speichern der Berechtigungen' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /api/admin/permissions/groups
|
* GET /api/admin/permissions/groups
|
||||||
* Returns all known Authentik groups from the permission table.
|
* Returns all known Authentik groups from the permission table.
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ router.get('/admin/matrix', authenticate, requirePermission('admin:view'), permi
|
|||||||
router.get('/admin/groups', authenticate, requirePermission('admin:view'), permissionController.getGroups.bind(permissionController));
|
router.get('/admin/groups', authenticate, requirePermission('admin:view'), permissionController.getGroups.bind(permissionController));
|
||||||
router.get('/admin/unknown-groups', authenticate, requirePermission('admin:view'), permissionController.getUnknownGroups.bind(permissionController));
|
router.get('/admin/unknown-groups', authenticate, requirePermission('admin:view'), permissionController.getUnknownGroups.bind(permissionController));
|
||||||
router.put('/admin/group/:groupName', authenticate, requirePermission('admin:write'), permissionController.setGroupPermissions.bind(permissionController));
|
router.put('/admin/group/:groupName', authenticate, requirePermission('admin:write'), permissionController.setGroupPermissions.bind(permissionController));
|
||||||
|
router.put('/admin/bulk', authenticate, requirePermission('admin:write'), permissionController.setBulkPermissions.bind(permissionController));
|
||||||
router.put('/admin/maintenance/:featureGroupId', authenticate, requirePermission('admin:write'), permissionController.setMaintenanceFlag.bind(permissionController));
|
router.put('/admin/maintenance/:featureGroupId', authenticate, requirePermission('admin:write'), permissionController.setMaintenanceFlag.bind(permissionController));
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -148,21 +148,100 @@ class PermissionService {
|
|||||||
const client = await pool.connect();
|
const client = await pool.connect();
|
||||||
try {
|
try {
|
||||||
await client.query('BEGIN');
|
await client.query('BEGIN');
|
||||||
|
|
||||||
|
// Validate permission IDs exist (filter out stale/invalid ones)
|
||||||
|
let validPermIds = permIds;
|
||||||
|
if (permIds.length > 0) {
|
||||||
|
const validResult = await client.query(
|
||||||
|
'SELECT id FROM permissions WHERE id = ANY($1)',
|
||||||
|
[permIds]
|
||||||
|
);
|
||||||
|
const validSet = new Set(validResult.rows.map((r: any) => r.id));
|
||||||
|
validPermIds = permIds.filter(p => validSet.has(p));
|
||||||
|
}
|
||||||
|
|
||||||
// Remove all existing permissions for this group
|
// Remove all existing permissions for this group
|
||||||
await client.query('DELETE FROM group_permissions WHERE authentik_group = $1', [group]);
|
await client.query('DELETE FROM group_permissions WHERE authentik_group = $1', [group]);
|
||||||
|
|
||||||
// Insert new permissions
|
// Insert new permissions
|
||||||
for (const permId of permIds) {
|
if (validPermIds.length > 0) {
|
||||||
|
const values = validPermIds.map((_p, i) =>
|
||||||
|
`($1, $${i + 2}, $${validPermIds.length + 2})`
|
||||||
|
).join(', ');
|
||||||
await client.query(
|
await client.query(
|
||||||
'INSERT INTO group_permissions (authentik_group, permission_id, granted_by) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING',
|
`INSERT INTO group_permissions (authentik_group, permission_id, granted_by)
|
||||||
[group, permId, grantedBy]
|
VALUES ${values}
|
||||||
|
ON CONFLICT DO NOTHING`,
|
||||||
|
[group, ...validPermIds, grantedBy]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await client.query('COMMIT');
|
await client.query('COMMIT');
|
||||||
|
|
||||||
// Reload cache
|
// Reload cache
|
||||||
await this.loadCache();
|
await this.loadCache();
|
||||||
|
|
||||||
logger.info('Group permissions updated', { group, permissionCount: permIds.length, grantedBy });
|
logger.info('Group permissions updated', { group, permissionCount: validPermIds.length, grantedBy });
|
||||||
|
} catch (error) {
|
||||||
|
await client.query('ROLLBACK');
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
client.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bulk-update permissions for multiple groups in a single transaction.
|
||||||
|
* Reloads cache once at the end.
|
||||||
|
*/
|
||||||
|
async setMultipleGroupPermissions(
|
||||||
|
updates: { group: string; permissions: string[] }[],
|
||||||
|
grantedBy: string,
|
||||||
|
): Promise<void> {
|
||||||
|
const client = await pool.connect();
|
||||||
|
try {
|
||||||
|
await client.query('BEGIN');
|
||||||
|
|
||||||
|
// Collect all referenced permission IDs to validate in one query
|
||||||
|
const allPermIds = new Set<string>();
|
||||||
|
for (const u of updates) {
|
||||||
|
for (const p of u.permissions) allPermIds.add(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
let validSet = new Set<string>();
|
||||||
|
if (allPermIds.size > 0) {
|
||||||
|
const validResult = await client.query(
|
||||||
|
'SELECT id FROM permissions WHERE id = ANY($1)',
|
||||||
|
[Array.from(allPermIds)]
|
||||||
|
);
|
||||||
|
validSet = new Set(validResult.rows.map((r: any) => r.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const { group, permissions } of updates) {
|
||||||
|
const validPermIds = permissions.filter(p => validSet.has(p));
|
||||||
|
|
||||||
|
await client.query('DELETE FROM group_permissions WHERE authentik_group = $1', [group]);
|
||||||
|
|
||||||
|
if (validPermIds.length > 0) {
|
||||||
|
const values = validPermIds.map((_p, i) =>
|
||||||
|
`($1, $${i + 2}, $${validPermIds.length + 2})`
|
||||||
|
).join(', ');
|
||||||
|
await client.query(
|
||||||
|
`INSERT INTO group_permissions (authentik_group, permission_id, granted_by)
|
||||||
|
VALUES ${values}
|
||||||
|
ON CONFLICT DO NOTHING`,
|
||||||
|
[group, ...validPermIds, grantedBy]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await client.query('COMMIT');
|
||||||
|
await this.loadCache();
|
||||||
|
|
||||||
|
logger.info('Bulk group permissions updated', {
|
||||||
|
groupCount: updates.length,
|
||||||
|
grantedBy,
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await client.query('ROLLBACK');
|
await client.query('ROLLBACK');
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@@ -188,10 +188,10 @@ function PermissionMatrixTab() {
|
|||||||
onError: () => showError('Fehler beim Aktualisieren des Wartungsmodus'),
|
onError: () => showError('Fehler beim Aktualisieren des Wartungsmodus'),
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── Permission save (saves full group permissions) ──
|
// ── Permission save (bulk — single request for all affected groups) ──
|
||||||
const permissionMutation = useMutation({
|
const permissionMutation = useMutation({
|
||||||
mutationFn: (updates: { group: string; permissions: string[] }[]) =>
|
mutationFn: (updates: { group: string; permissions: string[] }[]) =>
|
||||||
Promise.all(updates.map(u => permissionsApi.setGroupPermissions(u.group, u.permissions))),
|
permissionsApi.setBulkPermissions(updates),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['admin-permission-matrix'] });
|
queryClient.invalidateQueries({ queryKey: ['admin-permission-matrix'] });
|
||||||
queryClient.invalidateQueries({ queryKey: ['my-permissions'] });
|
queryClient.invalidateQueries({ queryKey: ['my-permissions'] });
|
||||||
@@ -576,8 +576,17 @@ function PermissionMatrixTab() {
|
|||||||
</TableCell>
|
</TableCell>
|
||||||
{nonAdminGroups.map(g => {
|
{nonAdminGroups.map(g => {
|
||||||
const isGranted = (grants[g] || []).includes(perm.id);
|
const isGranted = (grants[g] || []).includes(perm.id);
|
||||||
|
// Check if this perm is required by another granted perm (dependency lock)
|
||||||
|
const groupGrants = grants[g] || [];
|
||||||
|
const dependents = REVERSE_DEPS[perm.id] || [];
|
||||||
|
const isRequiredByOther = isGranted && dependents.some(d => groupGrants.includes(d));
|
||||||
return (
|
return (
|
||||||
<TableCell key={g} align="center" sx={{ minWidth: 120 }}>
|
<TableCell key={g} align="center" sx={{ minWidth: 120 }}>
|
||||||
|
<Tooltip
|
||||||
|
title={isRequiredByOther ? 'Wird von anderen Berechtigungen benötigt' : ''}
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={isGranted}
|
checked={isGranted}
|
||||||
onChange={() =>
|
onChange={() =>
|
||||||
@@ -585,7 +594,10 @@ function PermissionMatrixTab() {
|
|||||||
}
|
}
|
||||||
disabled={permissionMutation.isPending}
|
disabled={permissionMutation.isPending}
|
||||||
size="small"
|
size="small"
|
||||||
|
sx={isRequiredByOther ? { color: 'warning.main', '&.Mui-checked': { color: 'warning.main' } } : undefined}
|
||||||
/>
|
/>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ export const permissionsApi = {
|
|||||||
await api.put(`/api/permissions/admin/group/${encodeURIComponent(group)}`, { permissions });
|
await api.put(`/api/permissions/admin/group/${encodeURIComponent(group)}`, { permissions });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setBulkPermissions: async (updates: { group: string; permissions: string[] }[]): Promise<void> => {
|
||||||
|
await api.put('/api/permissions/admin/bulk', { updates });
|
||||||
|
},
|
||||||
|
|
||||||
setMaintenanceFlag: async (featureGroup: string, active: boolean): Promise<void> => {
|
setMaintenanceFlag: async (featureGroup: string, active: boolean): Promise<void> => {
|
||||||
await api.put(`/api/permissions/admin/maintenance/${encodeURIComponent(featureGroup)}`, { active });
|
await api.put(`/api/permissions/admin/maintenance/${encodeURIComponent(featureGroup)}`, { active });
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user