183 lines
7.4 KiB
Dart
183 lines
7.4 KiB
Dart
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;
|
|
bool _isLoaded = false;
|
|
bool _errorHandled = false;
|
|
|
|
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);
|
|
_errorHandled = false;
|
|
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);
|
|
}
|
|
_isLoaded = true;
|
|
} catch (e) {
|
|
_userAccess = _userAccess.copyWith(
|
|
error: e is Exception ? e : Exception(e.toString()),
|
|
isLoading: false,
|
|
);
|
|
}
|
|
|
|
notifyListeners();
|
|
return _userAccess.data;
|
|
}
|
|
|
|
bool get hasUnhandledError => error != null && !_errorHandled;
|
|
|
|
void markErrorHandled() {
|
|
_errorHandled = true;
|
|
}
|
|
|
|
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 && _isLoaded;
|
|
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);
|
|
}
|
|
|
|
void reset() {
|
|
_userAccess = Resource(data: null, isLoading: false, error: null);
|
|
_isLoaded = false;
|
|
notifyListeners();
|
|
}
|
|
|
|
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);
|
|
}
|