Frontend first draft
This commit is contained in:
165
frontend/pshared/lib/provider/permissions.dart
Normal file
165
frontend/pshared/lib/provider/permissions.dart
Normal file
@@ -0,0 +1,165 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
import 'package:pshared/api/requests/change_role.dart';
|
||||
import 'package:pshared/models/permissions/access.dart';
|
||||
import 'package:pshared/models/permissions/action.dart' as perm;
|
||||
import 'package:pshared/models/permissions/data/permission.dart';
|
||||
import 'package:pshared/models/permissions/data/policy.dart';
|
||||
import 'package:pshared/models/permissions/data/role.dart';
|
||||
import 'package:pshared/models/permissions/descriptions/policy.dart';
|
||||
import 'package:pshared/models/permissions/descriptions/role.dart';
|
||||
import 'package:pshared/models/permissions/effect.dart';
|
||||
import 'package:pshared/models/resources.dart';
|
||||
import 'package:pshared/provider/organizations.dart';
|
||||
import 'package:pshared/provider/resource.dart';
|
||||
import 'package:pshared/service/permissions.dart';
|
||||
|
||||
|
||||
class PermissionsProvider extends ChangeNotifier {
|
||||
Resource<UserAccess> _userAccess = Resource(data: null, isLoading: false, error: null);
|
||||
late OrganizationsProvider _organizations;
|
||||
|
||||
void update(OrganizationsProvider venue) {
|
||||
_organizations = venue;
|
||||
}
|
||||
|
||||
// Generic wrapper to perform service calls and reload state
|
||||
Future<UserAccess?> _performServiceCall(Future Function() operation) async {
|
||||
try {
|
||||
await operation();
|
||||
return await load();
|
||||
} catch (e) {
|
||||
_userAccess = _userAccess.copyWith(
|
||||
error: e is Exception ? e : Exception(e.toString()),
|
||||
isLoading: false,
|
||||
);
|
||||
notifyListeners();
|
||||
return _userAccess.data;
|
||||
}
|
||||
}
|
||||
|
||||
/// Load the [UserAccess] for the current venue.
|
||||
Future<UserAccess?> load() async {
|
||||
_userAccess = _userAccess.copyWith(isLoading: true, error: null);
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
final orgRef = _organizations.current.id;
|
||||
final access = await PermissionsService.load(orgRef);
|
||||
_userAccess = _userAccess.copyWith(data: access, isLoading: false);
|
||||
|
||||
if (canRead(ResourceType.roles)) {
|
||||
final allAccess = await PermissionsService.loadAll(orgRef);
|
||||
_userAccess = _userAccess.copyWith(data: allAccess, isLoading: false);
|
||||
}
|
||||
} catch (e) {
|
||||
_userAccess = _userAccess.copyWith(
|
||||
error: e is Exception ? e : Exception(e.toString()),
|
||||
isLoading: false,
|
||||
);
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
return _userAccess.data;
|
||||
}
|
||||
|
||||
Future<UserAccess?> changeRole(String accountRef, String newRoleDescRef) async {
|
||||
final currentRole = roles.firstWhereOrNull((r) => r.accountRef == accountRef);
|
||||
final currentDesc = currentRole != null
|
||||
? roleDescriptions.firstWhereOrNull((d) => d.storable.id == currentRole.descriptionRef)
|
||||
: null;
|
||||
|
||||
if (currentRole == null || currentDesc == null || currentDesc.storable.id == newRoleDescRef) {
|
||||
return _userAccess.data;
|
||||
}
|
||||
return _performServiceCall(() => PermissionsService.changeRole(
|
||||
_organizations.current.id,
|
||||
ChangeRole(accountRef: accountRef, newRoleDescriptionRef: newRoleDescRef),
|
||||
));
|
||||
}
|
||||
|
||||
Future<UserAccess?> deleteRoleDescription(String descRef) {
|
||||
return _performServiceCall(() => PermissionsService.deleteRoleDescription(descRef));
|
||||
}
|
||||
|
||||
Future<UserAccess?> createPermissions(List<Policy> policies) {
|
||||
return _performServiceCall(() => PermissionsService.createPolicies(policies));
|
||||
}
|
||||
|
||||
Future<UserAccess?> deletePermissions(List<Policy> policies) {
|
||||
return _performServiceCall(() => PermissionsService.deletePolicies(policies));
|
||||
}
|
||||
|
||||
Future<UserAccess?> changePermissions(List<Policy> add, List<Policy> remove) {
|
||||
return _performServiceCall(() => PermissionsService.changePolicies(add, remove));
|
||||
}
|
||||
|
||||
// -- Data getters --
|
||||
Set<ResourceType> extractResourceTypes(Iterable<PolicyDescription> descriptions) => descriptions.expand((policy) => policy.resourceTypes ?? <ResourceType>[]).toSet();
|
||||
|
||||
Set<ResourceType> get resources => Set.unmodifiable(extractResourceTypes(policyDescriptions));
|
||||
|
||||
Set<ResourceType> getRoleResources(String roleDescRef) => Set.unmodifiable(
|
||||
extractResourceTypes(
|
||||
getRolePermissions(roleDescRef)
|
||||
.map((p) => getPolicyDescription(p.policy.descriptionRef))
|
||||
.whereType<PolicyDescription>(),
|
||||
),
|
||||
);
|
||||
|
||||
String? getPolicyDescriptionRef(ResourceType resource) => policyDescriptions.firstWhereOrNull((p) => p.resourceTypes?.contains(resource) ?? false)?.storable.id;
|
||||
|
||||
List<PolicyDescription> get policyDescriptions => List.unmodifiable(_userAccess.data?.descriptions.policies ?? []);
|
||||
List<RoleDescription> get roleDescriptions => List.unmodifiable(_userAccess.data?.descriptions.roles ?? []);
|
||||
List<Permission> get permissions => List.unmodifiable(_userAccess.data?.permissions.permissions ?? []);
|
||||
List<Policy> get policies => List.unmodifiable(_userAccess.data?.permissions.policies ?? []);
|
||||
List<Role> get roles => List.unmodifiable(_userAccess.data?.permissions.roles ?? []);
|
||||
|
||||
Role? getRole(String accountRef) => roles.firstWhereOrNull((r) => r.accountRef == accountRef);
|
||||
RoleDescription? getRoleDescription(String descRef) => roleDescriptions.firstWhereOrNull((d) => d.storable.id == descRef);
|
||||
List<Role> getRoles(String accountRef) => roles.where((r) => r.accountRef == accountRef).toList();
|
||||
List<Policy> getRolePolicies(String roleRef) => policies.where((p) => p.roleDescriptionRef == roleRef).toList();
|
||||
List<Permission> getRolePermissions(String descRef) => permissions.where((p) => p.policy.roleDescriptionRef == descRef).toList();
|
||||
PolicyDescription? getPolicyDescription(String policyRef) => policyDescriptions.firstWhereOrNull((p) => p.storable.id == policyRef);
|
||||
|
||||
// -- Permission checks --
|
||||
bool get isLoading => _userAccess.isLoading;
|
||||
bool get isReady => !_userAccess.isLoading && error == null;
|
||||
Exception? get error => _userAccess.error;
|
||||
|
||||
bool _hasMatchingPermission(
|
||||
PolicyDescription pd,
|
||||
Effect effect,
|
||||
perm.Action? action, {
|
||||
Object? objectRef,
|
||||
}) => permissions.firstWhereOrNull(
|
||||
(p) =>
|
||||
p.policy.descriptionRef == pd.storable.id &&
|
||||
p.policy.effect.effect == effect &&
|
||||
(action == null || p.policy.effect.action == action) &&
|
||||
(p.policy.objectRef == null || p.policy.objectRef == objectRef),
|
||||
) != null;
|
||||
|
||||
bool canAccessResource(
|
||||
ResourceType resource, {
|
||||
perm.Action? action,
|
||||
Object? objectRef,
|
||||
}) {
|
||||
final orgId = _organizations.current.id;
|
||||
final pd = policyDescriptions.firstWhereOrNull(
|
||||
(policy) =>
|
||||
(policy.resourceTypes?.contains(resource) ?? false) &&
|
||||
(policy.organizationRef == null || policy.organizationRef == orgId),
|
||||
);
|
||||
if (pd == null) return false;
|
||||
if (_hasMatchingPermission(pd, Effect.deny, action, objectRef: objectRef)) return false;
|
||||
return _hasMatchingPermission(pd, Effect.allow, action, objectRef: objectRef);
|
||||
}
|
||||
|
||||
bool canRead(ResourceType r, {Object? objectRef}) => canAccessResource(r, action: perm.Action.read, objectRef: objectRef);
|
||||
bool canUpdate(ResourceType r, {Object? objectRef}) => canAccessResource(r, action: perm.Action.update, objectRef: objectRef);
|
||||
bool canDelete(ResourceType r, {Object? objectRef}) => canAccessResource(r, action: perm.Action.delete, objectRef: objectRef);
|
||||
bool canCreate(ResourceType r) => canAccessResource(r, action: perm.Action.create);
|
||||
}
|
||||
Reference in New Issue
Block a user