mirror of
https://codeberg.org/shroff/phylum.git
synced 2026-01-04 10:39:47 -06:00
[client] Perform local permission check
This commit is contained in:
@@ -19,3 +19,7 @@ class DestinationCycleException {
|
||||
|
||||
const DestinationCycleException(this.resource);
|
||||
}
|
||||
|
||||
class PermissionException {
|
||||
const PermissionException();
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import 'package:phylum/libphylum/responses/responses.dart';
|
||||
import 'package:phylum/libphylum/util/uuid.dart';
|
||||
import 'package:phylum/util/permissions.dart';
|
||||
|
||||
const performLocalNameCheck = true;
|
||||
const performLocalChecks = true;
|
||||
const _remoteResourcesBoxName = 'remote_resources';
|
||||
|
||||
typedef CopyMoveFn = Future<String> Function({
|
||||
@@ -85,14 +85,19 @@ class ResourceRepository extends Repository<PhylumAccount, Resource> {
|
||||
Future<String> mkdir({
|
||||
required String parent,
|
||||
required String name,
|
||||
required NameConflictResolution conflictResolution,
|
||||
required Permission userPermission,
|
||||
String? id,
|
||||
required NameConflictResolution conflictResolution,
|
||||
bool ensurePermission = true,
|
||||
}) async {
|
||||
id ??= generateUuid();
|
||||
final id = generateUuid();
|
||||
|
||||
String localName = name;
|
||||
String? deletedId;
|
||||
if (performLocalNameCheck) {
|
||||
if (performLocalChecks) {
|
||||
if (ensurePermission && userPermission & permissionWrite == 0) {
|
||||
throw PermissionException();
|
||||
}
|
||||
|
||||
var existing = await _account.db.getResourcesByName(parent: parent, name: name);
|
||||
if (existing.isNotEmpty) {
|
||||
switch (conflictResolution) {
|
||||
@@ -132,12 +137,17 @@ class ResourceRepository extends Repository<PhylumAccount, Resource> {
|
||||
required Permission userPermission,
|
||||
XFile? file,
|
||||
String? path,
|
||||
NameConflictResolution conflictResolution = nameConflictError,
|
||||
required NameConflictResolution conflictResolution,
|
||||
bool ensurePermission = true,
|
||||
}) async {
|
||||
String id = generateUuid();
|
||||
String localName = name;
|
||||
String? deletedId;
|
||||
if (performLocalNameCheck) {
|
||||
if (performLocalChecks) {
|
||||
if (ensurePermission && userPermission & permissionWrite == 0) {
|
||||
throw PermissionException();
|
||||
}
|
||||
|
||||
var existing = await _account.db.getResourcesByName(parent: parent, name: name);
|
||||
if (existing.isNotEmpty) {
|
||||
switch (conflictResolution) {
|
||||
@@ -183,10 +193,16 @@ class ResourceRepository extends Repository<PhylumAccount, Resource> {
|
||||
required String name,
|
||||
required String parent,
|
||||
required NameConflictResolution conflictResolution,
|
||||
bool ensurePermission = true,
|
||||
}) async {
|
||||
String localName = name;
|
||||
String? deletedId;
|
||||
if (performLocalNameCheck) {
|
||||
if (performLocalChecks) {
|
||||
final parentPermission = await _account.db.getResource(parent).then((p) => p?.userPermission ?? 0);
|
||||
if (ensurePermission && parentPermission & permissionWrite == 0) {
|
||||
throw PermissionException();
|
||||
}
|
||||
|
||||
final parents = await _account.db.parents(parent).get();
|
||||
for (final p in parents) {
|
||||
if (p.id == resource.id) {
|
||||
@@ -231,6 +247,7 @@ class ResourceRepository extends Repository<PhylumAccount, Resource> {
|
||||
required String parent,
|
||||
required String name,
|
||||
required NameConflictResolution conflictResolution,
|
||||
bool ensurePermission = true,
|
||||
}) async {
|
||||
if (parent == resource.parent && name == resource.name) {
|
||||
name = '${basenameWithoutExtension(name)} - copy${extension(name)}';
|
||||
@@ -239,7 +256,12 @@ class ResourceRepository extends Repository<PhylumAccount, Resource> {
|
||||
String id = generateUuid();
|
||||
String? deletedId;
|
||||
String localName = name;
|
||||
if (performLocalNameCheck) {
|
||||
if (performLocalChecks) {
|
||||
final parentPermission = await _account.db.getResource(parent).then((p) => p?.userPermission ?? 0);
|
||||
if (ensurePermission && parentPermission & permissionWrite == 0) {
|
||||
throw PermissionException();
|
||||
}
|
||||
|
||||
var existing = await _account.db.getResourcesByName(parent: parent, name: name);
|
||||
if (existing.isNotEmpty) {
|
||||
switch (conflictResolution) {
|
||||
|
||||
@@ -90,11 +90,12 @@ class ExplorerActions extends StatelessWidget {
|
||||
context,
|
||||
name,
|
||||
overwriteFn: (r) => nameConflictDelete,
|
||||
(name, conflictResolution) => account.resourceRepository.move(
|
||||
(name, conflictResolution, ensurePermission) => account.resourceRepository.move(
|
||||
resource: r,
|
||||
name: name,
|
||||
parent: r.parent!,
|
||||
conflictResolution: conflictResolution,
|
||||
ensurePermission: ensurePermission,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -104,12 +104,13 @@ class _ExplorerViewState extends State<ExplorerView> {
|
||||
context,
|
||||
name,
|
||||
overwriteFn: (r) => r.dir ? nameConflictDelete : nameConflictOverwrite,
|
||||
(name, conflictResolution) => account.resourceRepository.upload(
|
||||
(name, conflictResolution, ensurePermission) => account.resourceRepository.upload(
|
||||
parent: dest,
|
||||
name: name,
|
||||
path: path,
|
||||
conflictResolution: conflictResolution,
|
||||
userPermission: userPermission,
|
||||
ensurePermission: ensurePermission,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -140,11 +140,12 @@ Future<void> processPasteItems(
|
||||
context,
|
||||
r.name,
|
||||
overwriteFn: (r) => nameConflictDelete,
|
||||
(name, conflictResolution) => copyMoveFn(
|
||||
(name, conflictResolution, ensurePermission) => copyMoveFn(
|
||||
resource: r,
|
||||
name: name,
|
||||
parent: folderId,
|
||||
conflictResolution: conflictResolution,
|
||||
ensurePermission: ensurePermission,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -64,11 +64,12 @@ class _ResourceDragTargetState extends State<ResourceDragTarget> {
|
||||
context,
|
||||
r.name,
|
||||
overwriteFn: (r) => nameConflictDelete,
|
||||
(name, conflictResolution) => account.resourceRepository.move(
|
||||
(name, conflictResolution, ensurePermission) => account.resourceRepository.move(
|
||||
resource: r,
|
||||
name: name,
|
||||
parent: widget.resourceId,
|
||||
conflictResolution: conflictResolution,
|
||||
ensurePermission: ensurePermission,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -63,11 +63,12 @@ void handleOption(BuildContext context, Iterable<Resource> resources, MenuOption
|
||||
context,
|
||||
name,
|
||||
overwriteFn: (r) => nameConflictDelete,
|
||||
(name, conflictResolution) => account.resourceRepository.move(
|
||||
(name, conflictResolution, ensurePermission) => account.resourceRepository.move(
|
||||
resource: r,
|
||||
name: name,
|
||||
parent: r.parent!,
|
||||
conflictResolution: conflictResolution,
|
||||
ensurePermission: ensurePermission,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -85,11 +86,12 @@ void handleOption(BuildContext context, Iterable<Resource> resources, MenuOption
|
||||
context,
|
||||
r.name,
|
||||
overwriteFn: (r) => nameConflictDelete,
|
||||
(name, conflictResolution) => account.resourceRepository.move(
|
||||
(name, conflictResolution, ensurePermission) => account.resourceRepository.move(
|
||||
resource: r,
|
||||
name: name,
|
||||
parent: dest,
|
||||
conflictResolution: conflictResolution,
|
||||
ensurePermission: ensurePermission,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -107,12 +109,12 @@ void handleOption(BuildContext context, Iterable<Resource> resources, MenuOption
|
||||
context,
|
||||
r.name,
|
||||
overwriteFn: (r) => nameConflictDelete,
|
||||
(name, conflictResolution) => account.resourceRepository.copy(
|
||||
resource: r,
|
||||
name: name,
|
||||
parent: dest,
|
||||
conflictResolution: conflictResolution,
|
||||
),
|
||||
(name, conflictResolution, ensurePermission) => account.resourceRepository.copy(
|
||||
resource: r,
|
||||
name: name,
|
||||
parent: dest,
|
||||
conflictResolution: conflictResolution,
|
||||
ensurePermission: ensurePermission),
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -38,11 +38,12 @@ Future<void> createDirectory(BuildContext context, Resource parent, {String? pre
|
||||
context,
|
||||
name,
|
||||
overwriteFn: (r) => nameConflictDelete,
|
||||
(name, conflictResolution) => account.resourceRepository.mkdir(
|
||||
(name, conflictResolution, ensurePermission) => account.resourceRepository.mkdir(
|
||||
parent: parent.id,
|
||||
name: name,
|
||||
conflictResolution: conflictResolution,
|
||||
userPermission: parent.userPermission,
|
||||
conflictResolution: conflictResolution,
|
||||
ensurePermission: ensurePermission,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -78,12 +79,13 @@ Future<void> uploadXFiles(BuildContext context, String folderId, Iterable<XFile>
|
||||
context,
|
||||
p.basename(file.name),
|
||||
overwriteFn: (r) => r.dir ? nameConflictDelete : nameConflictOverwrite,
|
||||
(name, conflictResolution) => account.resourceRepository.upload(
|
||||
(name, conflictResolution, ensurePermission) => account.resourceRepository.upload(
|
||||
parent: folderId,
|
||||
name: name,
|
||||
file: file,
|
||||
conflictResolution: conflictResolution,
|
||||
userPermission: userPermission,
|
||||
conflictResolution: conflictResolution,
|
||||
ensurePermission: ensurePermission,
|
||||
),
|
||||
);
|
||||
if (id == null && context.mounted) {
|
||||
@@ -135,11 +137,12 @@ Future<void> uploadPath(BuildContext context, String folderId, Iterable<String>
|
||||
context,
|
||||
p.basename(path),
|
||||
overwriteFn: (r) => nameConflictDelete,
|
||||
(name, conflictResolution) => account.resourceRepository.mkdir(
|
||||
(name, conflictResolution, ensurePermission) => account.resourceRepository.mkdir(
|
||||
parent: parent,
|
||||
name: name,
|
||||
conflictResolution: conflictResolution,
|
||||
userPermission: userPermission,
|
||||
conflictResolution: conflictResolution,
|
||||
ensurePermission: ensurePermission,
|
||||
),
|
||||
);
|
||||
if (id == null) {
|
||||
@@ -163,12 +166,13 @@ Future<void> uploadPath(BuildContext context, String folderId, Iterable<String>
|
||||
context,
|
||||
p.basename(path),
|
||||
overwriteFn: (r) => r.dir ? nameConflictDelete : nameConflictOverwrite,
|
||||
(name, conflictResolution) => account.resourceRepository.upload(
|
||||
(name, conflictResolution, ensurePermission) => account.resourceRepository.upload(
|
||||
parent: parent,
|
||||
name: name,
|
||||
path: path,
|
||||
conflictResolution: conflictResolution,
|
||||
userPermission: userPermission,
|
||||
conflictResolution: conflictResolution,
|
||||
ensurePermission: ensurePermission,
|
||||
),
|
||||
);
|
||||
if (id == null && context.mounted) {
|
||||
@@ -180,81 +184,108 @@ Future<void> uploadPath(BuildContext context, String folderId, Iterable<String>
|
||||
}
|
||||
}
|
||||
|
||||
// #TODO: handle permission errors as well
|
||||
Future<String?> handleLocalErrors(
|
||||
BuildContext context,
|
||||
String name,
|
||||
Future<String> Function(String name, NameConflictResolution conflictResolution) performAction, {
|
||||
Future<String> Function(String, NameConflictResolution, bool) performAction, {
|
||||
NameConflictResolution? Function(Resource)? overwriteFn,
|
||||
NameConflictResolution conflictResolution = nameConflictError,
|
||||
bool ensurePermission = true,
|
||||
}) async {
|
||||
NameConflictResolution? conflictResolution = nameConflictError;
|
||||
while (conflictResolution != null) {
|
||||
try {
|
||||
return await performAction(name, conflictResolution);
|
||||
} on NameConflictException catch (e) {
|
||||
if (!context.mounted) return null;
|
||||
final overwrite = overwriteFn?.call(e.resource);
|
||||
conflictResolution = await showDialog<NameConflictResolution>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Name Conflict'),
|
||||
content: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 360),
|
||||
child: Text(
|
||||
'There is already another item with the name \'$name\' in this folder.',
|
||||
softWrap: true,
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
if (overwrite != null)
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(foregroundColor: Theme.of(context).colorScheme.error),
|
||||
child: const Text('Overwrite'),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(overwrite);
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: const Text('Change Name'),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(nameConflictError);
|
||||
},
|
||||
),
|
||||
ElevatedButton(
|
||||
autofocus: true,
|
||||
child: const Text('Auto Rename'),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(nameConflictRename);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (conflictResolution == nameConflictError) {
|
||||
if (!context.mounted) return null;
|
||||
final newName = await showInputDialog(
|
||||
try {
|
||||
return await performAction(name, conflictResolution, ensurePermission);
|
||||
} on PermissionException {
|
||||
if (!context.mounted) return null;
|
||||
final skip = await showAlertDialog(
|
||||
context,
|
||||
title: 'Change Name',
|
||||
labelText: 'New Name',
|
||||
preset: name,
|
||||
validate: validateName,
|
||||
);
|
||||
if (!context.mounted) return null;
|
||||
if (newName == null) {
|
||||
return null;
|
||||
}
|
||||
name = newName;
|
||||
}
|
||||
} on DestinationCycleException catch (e) {
|
||||
if (context.mounted) {
|
||||
await showAlertDialog(context, message: 'Cannot move ${e.resource.name} into itself');
|
||||
}
|
||||
title: 'Permissions Error',
|
||||
message: 'You do not have permissions to write to this folder.',
|
||||
positiveText: 'OK',
|
||||
negativeText: 'Try Anyway',
|
||||
) ??
|
||||
true;
|
||||
if (!skip && context.mounted) {
|
||||
return handleLocalErrors(
|
||||
context,
|
||||
name,
|
||||
performAction,
|
||||
overwriteFn: overwriteFn,
|
||||
conflictResolution: conflictResolution,
|
||||
ensurePermission: false,
|
||||
);
|
||||
}
|
||||
} on NameConflictException catch (e) {
|
||||
if (!context.mounted) return null;
|
||||
final overwrite = overwriteFn?.call(e.resource);
|
||||
final newResolution = await showDialog<NameConflictResolution>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Name Conflict'),
|
||||
content: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 360),
|
||||
child: Text(
|
||||
'There is already another item with the name \'$name\' in this folder.',
|
||||
softWrap: true,
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
if (overwrite != null)
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(foregroundColor: Theme.of(context).colorScheme.error),
|
||||
child: const Text('Overwrite'),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(overwrite);
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: const Text('Change Name'),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(nameConflictError);
|
||||
},
|
||||
),
|
||||
ElevatedButton(
|
||||
autofocus: true,
|
||||
child: const Text('Auto Rename'),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(nameConflictRename);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (newResolution == null) {
|
||||
return null;
|
||||
} catch (e) {
|
||||
if (context.mounted) {
|
||||
await showAlertDialog(context, title: 'Unable to create $name', message: e.toString());
|
||||
}
|
||||
if (newResolution == nameConflictError) {
|
||||
if (!context.mounted) return null;
|
||||
final newName = await showInputDialog(
|
||||
context,
|
||||
title: 'Change Name',
|
||||
labelText: 'New Name',
|
||||
preset: name,
|
||||
validate: validateName,
|
||||
);
|
||||
if (newName == null) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
name = newName;
|
||||
}
|
||||
if (!context.mounted) return null;
|
||||
return handleLocalErrors(
|
||||
context,
|
||||
name,
|
||||
performAction,
|
||||
overwriteFn: overwriteFn,
|
||||
conflictResolution: newResolution,
|
||||
ensurePermission: ensurePermission,
|
||||
);
|
||||
} on DestinationCycleException catch (e) {
|
||||
if (context.mounted) {
|
||||
await showAlertDialog(context, message: 'Cannot move ${e.resource.name} into itself');
|
||||
}
|
||||
} catch (e) {
|
||||
if (context.mounted) {
|
||||
await showAlertDialog(context, title: 'Unable to create $name', message: e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user