mirror of
https://codeberg.org/shroff/phylum.git
synced 2026-05-05 11:50:04 -05:00
[client] Fix permission view
This commit is contained in:
@@ -3,7 +3,6 @@ import 'dart:convert';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:offtheline/offtheline.dart';
|
||||
import 'package:phylum/libphylum/db/db.dart';
|
||||
import 'package:phylum/util/permissions.dart';
|
||||
|
||||
class UpdatePermissionsChange extends LocalChange<Resource> {
|
||||
@override
|
||||
@@ -25,21 +24,29 @@ class UpdatePermissionsChange extends LocalChange<Resource> {
|
||||
@override
|
||||
Resource? apply(Resource? data) {
|
||||
if (data == null) return null;
|
||||
var p = data.grants.parseGrantsMap();
|
||||
if (p.containsKey(username) && p[username] == permission) {
|
||||
var p = data.grants == null ? const <String, Map>{} : (jsonDecode(data.grants!) as Map).cast<String, Map>();
|
||||
if ((p[username]?['p'] ?? 0) == permission) {
|
||||
return data;
|
||||
}
|
||||
if (permission == 0) {
|
||||
p.remove(username);
|
||||
} else if (p.isEmpty) {
|
||||
// Empty means const map, so we can't add to it
|
||||
p = {username: permission};
|
||||
p = {
|
||||
username: <String, int>{
|
||||
'p': permission,
|
||||
't': DateTime.now().millisecondsSinceEpoch,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
p[username] = permission;
|
||||
p[username] = <String, int>{
|
||||
'p': permission,
|
||||
't': DateTime.now().millisecondsSinceEpoch,
|
||||
};
|
||||
}
|
||||
final serialized = p.isEmpty ? null : json.encode(p);
|
||||
return data.copyWith(
|
||||
permissions: Value(serialized),
|
||||
grants: Value(serialized),
|
||||
modified: Value(timestamp),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1639,7 +1639,7 @@ abstract class _$AppDatabase extends GeneratedDatabase {
|
||||
|
||||
Selectable<ParentsResult> parents(String id) {
|
||||
return customSelect(
|
||||
'WITH RECURSIVE parents (id, parent, name, permissions, grants) AS (SELECT id, parent, name, permissions, grants FROM resources WHERE id = ?1 UNION ALL SELECT r.id, r.parent, r.name, r.permissions, r.grants FROM resources AS r JOIN parents AS p ON r.id = p.parent) SELECT id, name, permissions, grants FROM parents',
|
||||
'WITH RECURSIVE parents (id, parent, name, permissions) AS (SELECT id, parent, name, permissions FROM resources WHERE id = ?1 UNION ALL SELECT r.id, r.parent, r.name, r.permissions FROM resources AS r JOIN parents AS p ON r.id = p.parent) SELECT id, name, permissions FROM parents',
|
||||
variables: [
|
||||
Variable<String>(id)
|
||||
],
|
||||
@@ -1649,7 +1649,23 @@ abstract class _$AppDatabase extends GeneratedDatabase {
|
||||
id: row.read<String>('id'),
|
||||
name: row.read<String>('name'),
|
||||
permissions: row.readNullable<String>('permissions'),
|
||||
));
|
||||
}
|
||||
|
||||
Selectable<PermissionsResult> permissions(String id) {
|
||||
return customSelect(
|
||||
'SELECT r.name, r.permissions, r.grants, p.permissions AS inherited_permissions FROM resources AS r JOIN resources AS p ON r.parent = p.id WHERE r.id = ?1',
|
||||
variables: [
|
||||
Variable<String>(id)
|
||||
],
|
||||
readsFrom: {
|
||||
resources,
|
||||
}).map((QueryRow row) => PermissionsResult(
|
||||
name: row.read<String>('name'),
|
||||
permissions: row.readNullable<String>('permissions'),
|
||||
grants: row.readNullable<String>('grants'),
|
||||
inheritedPermissions:
|
||||
row.readNullable<String>('inherited_permissions'),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -3063,12 +3079,23 @@ class ParentsResult {
|
||||
final String id;
|
||||
final String name;
|
||||
final String? permissions;
|
||||
final String? grants;
|
||||
ParentsResult({
|
||||
required this.id,
|
||||
required this.name,
|
||||
this.permissions,
|
||||
});
|
||||
}
|
||||
|
||||
class PermissionsResult {
|
||||
final String name;
|
||||
final String? permissions;
|
||||
final String? grants;
|
||||
final String? inheritedPermissions;
|
||||
PermissionsResult({
|
||||
required this.name,
|
||||
this.permissions,
|
||||
this.grants,
|
||||
this.inheritedPermissions,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,10 @@ extension ResourceHelpers on AppDatabase {
|
||||
return parents(id).watch();
|
||||
}
|
||||
|
||||
Stream<PermissionsResult?> watchPermissions(String id) {
|
||||
return permissions(id).watchSingleOrNull();
|
||||
}
|
||||
|
||||
Future<void> markResourceAccess(String id) {
|
||||
return recents.insertOnConflictUpdate(Recent(resourceId: id, accessed: DateTime.now()));
|
||||
}
|
||||
|
||||
@@ -15,13 +15,18 @@ CREATE TABLE IF NOT EXISTS resources(
|
||||
last_refresh DATETIME
|
||||
);
|
||||
|
||||
parents: WITH RECURSIVE parents(id, parent, name, permissions, grants) AS (
|
||||
SELECT id, parent, name, permissions, grants
|
||||
parents: WITH RECURSIVE parents(id, parent, name, permissions) AS (
|
||||
SELECT id, parent, name, permissions
|
||||
FROM resources
|
||||
WHERE id = :id
|
||||
UNION ALL
|
||||
SELECT r.id, r.parent, r.name, r.permissions, r.grants
|
||||
SELECT r.id, r.parent, r.name, r.permissions
|
||||
FROM resources r
|
||||
JOIN parents p
|
||||
ON r.id = p.parent
|
||||
) SELECT id, name, permissions, grants from parents;
|
||||
) SELECT id, name, permissions from parents;
|
||||
|
||||
permissions: SELECT r.name, r.permissions, r.grants, p.permissions as inherited_permissions
|
||||
FROM resources r
|
||||
JOIN resources p ON r.parent = p.id
|
||||
WhERE r.id = :id;
|
||||
@@ -2,7 +2,6 @@ import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:phylum/libphylum/actions/action_resource_share.dart';
|
||||
import 'package:phylum/libphylum/db/db.dart';
|
||||
import 'package:phylum/libphylum/db/resource_helpers.dart';
|
||||
import 'package:phylum/libphylum/phylum_account.dart';
|
||||
import 'package:phylum/util/dialogs.dart';
|
||||
@@ -19,20 +18,23 @@ class ResourcePermissionsView extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _ResourcePermissionsViewState extends State<ResourcePermissionsView> {
|
||||
late String resourceName;
|
||||
Map<String, Permission>? inheritedPermissions;
|
||||
String? resourceName;
|
||||
Map<String, Permission>? permissions;
|
||||
Map<String, Permission>? grants;
|
||||
Permission? userPermission;
|
||||
|
||||
StreamSubscription? sub;
|
||||
|
||||
StreamSubscription<List<ParentsResult>>? sub;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final account = context.read<PhylumAccount>();
|
||||
sub = account.db.watchParents(widget.resourceId).listen((data) {
|
||||
sub = account.db.watchPermissions(widget.resourceId).listen((data) {
|
||||
setState(() {
|
||||
resourceName = data.first.name;
|
||||
inheritedPermissions = data.length > 1 ? data[1].permissions.parsePermissionMap() : const {};
|
||||
permissions = data.first.grants.parseGrantsMap();
|
||||
resourceName = data?.name;
|
||||
userPermission = data?.permissions.parsePermissionMap()[account.userName] ?? 0;
|
||||
permissions = data?.inheritedPermissions.parsePermissionMap();
|
||||
grants = data?.grants.parseGrantsMap();
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -45,14 +47,17 @@ class _ResourcePermissionsViewState extends State<ResourcePermissionsView> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final grants = this.grants;
|
||||
final permissions = this.permissions;
|
||||
final inheritedPermissions = this.inheritedPermissions;
|
||||
if (permissions == null || inheritedPermissions == null) return const CircularProgressIndicator();
|
||||
if (grants == null || permissions == null) return const CircularProgressIndicator();
|
||||
|
||||
final account = context.read<PhylumAccount>();
|
||||
final repository = account.userRepository;
|
||||
final userPermissions = permissions[account.userName] ?? 0;
|
||||
final entries = permissions.entries.toList(growable: false)
|
||||
final grantEntries = grants.entries.toList(growable: false)
|
||||
..sort(((a, b) {
|
||||
return a.value == b.value ? a.key.compareTo(b.key) : a.value.compareTo(b.value);
|
||||
}));
|
||||
final permissionEntries = permissions.entries.toList(growable: false)
|
||||
..sort(((a, b) {
|
||||
return a.value == b.value ? a.key.compareTo(b.key) : a.value.compareTo(b.value);
|
||||
}));
|
||||
@@ -60,16 +65,15 @@ class _ResourcePermissionsViewState extends State<ResourcePermissionsView> {
|
||||
return DropdownButtonHideUnderline(
|
||||
child: Column(
|
||||
children: [
|
||||
if (userPermissions & permissionShare != 0)
|
||||
if (userPermission! & permissionShare != 0)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: ElevatedButton.icon(
|
||||
label: Text('Add User'),
|
||||
icon: Icon(Icons.person_add),
|
||||
onPressed: () async {
|
||||
final users = account.userRepository.users.entries
|
||||
.where((e) => !inheritedPermissions.containsKey(e.key) && !permissions.containsKey(e.key))
|
||||
.map((e) => e.value);
|
||||
final users =
|
||||
account.userRepository.users.entries.where((e) => !grants.containsKey(e.key)).map((e) => e.value);
|
||||
final user = await showOptionsDialogBuilder(
|
||||
context, users.toList(growable: false), (user) => ListTile(title: Text(user.display)),
|
||||
filterList: (u, q) {
|
||||
@@ -86,105 +90,115 @@ class _ResourcePermissionsViewState extends State<ResourcePermissionsView> {
|
||||
permissionSetReadWrite,
|
||||
permissionSetReadWriteShare,
|
||||
],
|
||||
(p) => ListTile(leading: Icon(_getIcon(p)), title: Text(_getText(p))),
|
||||
(p) => ListTile(leading: Icon(p.icon), title: Text(p.text)),
|
||||
);
|
||||
if (permission == null || !context.mounted) return;
|
||||
account.addAction(ResourceShareAction(
|
||||
resourceId: widget.resourceId,
|
||||
resourceName: resourceName,
|
||||
resourceName: resourceName!,
|
||||
username: user.username,
|
||||
permission: permission,
|
||||
));
|
||||
},
|
||||
),
|
||||
),
|
||||
for (final e in entries)
|
||||
for (final e in grantEntries)
|
||||
ListTile(
|
||||
visualDensity: VisualDensity.adaptivePlatformDensity,
|
||||
title: Text(repository.getUserDisplayName(e.key)),
|
||||
trailing: DropdownButton<Permission>(
|
||||
padding: EdgeInsets.symmetric(horizontal: 6, vertical: 6),
|
||||
items: _createPermissionsItems(theme, e.value, inheritedPermissions[e.key] ?? 0),
|
||||
items: _createPermissionsItems(theme, e.value),
|
||||
value: e.value,
|
||||
onChanged: (userPermissions & permissionShare) == 0
|
||||
onChanged: (userPermission! & permissionShare) == 0
|
||||
? null
|
||||
: (value) {
|
||||
if (value == null || value == e.value) return;
|
||||
if (value == inheritedPermissions[e.key]) {
|
||||
if (value == permissions[e.key]) {
|
||||
value = 0;
|
||||
}
|
||||
account.addAction(ResourceShareAction(
|
||||
resourceId: widget.resourceId,
|
||||
resourceName: resourceName,
|
||||
resourceName: resourceName!,
|
||||
username: e.key,
|
||||
permission: value,
|
||||
));
|
||||
},
|
||||
),
|
||||
// trailing: _getPermissionIcon(e.value),
|
||||
),
|
||||
ListTile(
|
||||
visualDensity: VisualDensity.compact,
|
||||
dense: true,
|
||||
title: const Text('Inherited Permissions', style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
),
|
||||
for (final e in permissionEntries)
|
||||
ListTile(
|
||||
visualDensity: VisualDensity.adaptivePlatformDensity,
|
||||
leading: Icon(e.value.icon),
|
||||
title: Text(repository.getUserDisplayName(e.key)),
|
||||
subtitle: Text(e.value.text),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<DropdownMenuItem<Permission>> _createPermissionsItems(ThemeData theme, Permission p, Permission inherited) {
|
||||
List<DropdownMenuItem<Permission>> _createPermissionsItems(ThemeData theme, Permission p) {
|
||||
return [
|
||||
_buildDropdownMenuItem(permissionSetNone, inherited, theme),
|
||||
_buildDropdownMenuItem(permissionSetReadOnly, inherited, theme),
|
||||
_buildDropdownMenuItem(permissionSetReadWrite, inherited, theme),
|
||||
_buildDropdownMenuItem(permissionSetReadWriteShare, inherited, theme),
|
||||
if (p == permissionSetAll) _buildDropdownMenuItem(permissionSetAll, inherited, theme),
|
||||
_buildDropdownMenuItem(permissionSetNone, theme),
|
||||
_buildDropdownMenuItem(permissionSetReadOnly, theme),
|
||||
_buildDropdownMenuItem(permissionSetReadWrite, theme),
|
||||
_buildDropdownMenuItem(permissionSetReadWriteShare, theme),
|
||||
if (p == permissionSetAll) _buildDropdownMenuItem(permissionSetAll, theme),
|
||||
if (p != permissionSetNone &&
|
||||
p != permissionSetReadOnly &&
|
||||
p != permissionSetReadWrite &&
|
||||
p != permissionSetReadWriteShare &&
|
||||
p != permissionSetAll)
|
||||
_buildDropdownMenuItem(p, inherited, theme),
|
||||
_buildDropdownMenuItem(p, theme),
|
||||
];
|
||||
}
|
||||
|
||||
DropdownMenuItem<Permission> _buildDropdownMenuItem(Permission p, Permission inherited, ThemeData theme) {
|
||||
bool enabled = (p | inherited) == p || (p | inherited) != inherited;
|
||||
|
||||
final iconColor = enabled ? theme.listTileTheme.iconColor : theme.disabledColor;
|
||||
final textColor = enabled ? theme.listTileTheme.tileColor : theme.disabledColor;
|
||||
DropdownMenuItem<Permission> _buildDropdownMenuItem(Permission p, ThemeData theme) {
|
||||
final iconColor = theme.listTileTheme.iconColor;
|
||||
final textColor = theme.listTileTheme.tileColor;
|
||||
|
||||
return DropdownMenuItem(
|
||||
value: p,
|
||||
enabled: enabled,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8.0),
|
||||
child: Icon(_getIcon(p), color: iconColor),
|
||||
child: Icon(p.icon, color: iconColor),
|
||||
),
|
||||
Text(
|
||||
_getText(p),
|
||||
p.text,
|
||||
style: TextStyle(color: textColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
IconData _getIcon(Permission p) {
|
||||
if (p == 0) return Icons.block;
|
||||
if (p == -1) return Icons.shield_outlined;
|
||||
if (p == permissionSetReadOnly) return Icons.visibility;
|
||||
if (p == permissionSetReadWrite) return Icons.edit;
|
||||
if (p == permissionSetReadWriteShare) return Icons.person_add;
|
||||
extension on Permission {
|
||||
IconData get icon {
|
||||
if (this == 0) return Icons.block;
|
||||
if (this == -1) return Icons.shield_outlined;
|
||||
if (this == permissionSetReadOnly) return Icons.visibility;
|
||||
if (this == permissionSetReadWrite) return Icons.edit;
|
||||
if (this == permissionSetReadWriteShare) return Icons.person_add;
|
||||
return Icons.settings;
|
||||
}
|
||||
|
||||
String _getText(Permission p) {
|
||||
if (p == 0) return 'None';
|
||||
if (p == -1) return 'Admin';
|
||||
if (p == permissionSetReadOnly) return 'Viewer';
|
||||
if (p == permissionSetReadWrite) return 'Collaborator';
|
||||
if (p == permissionSetReadWriteShare) return 'Editor';
|
||||
String get text {
|
||||
if (this == 0) return 'None';
|
||||
if (this == -1) return 'Admin';
|
||||
if (this == permissionSetReadOnly) return 'Viewer';
|
||||
if (this == permissionSetReadWrite) return 'Collaborator';
|
||||
if (this == permissionSetReadWriteShare) return 'Editor';
|
||||
return 'Custom';
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user