Jogosultságok projektenkénti kezelése
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 6s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 6s
This commit is contained in:
@@ -1171,7 +1171,8 @@ class MapSurveyController extends GetxController implements StyleEditable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void startContactTool() {
|
void startContactTool() {
|
||||||
if (!PermissionService.to.canContacts) {
|
if (!PermissionService.to
|
||||||
|
.canContacts(projectId: ProjectService.to.activeProject.value?.uuid)) {
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
'Nincs jogosultság', 'Kapcsolatok kezeléséhez jogosultság szükséges.',
|
'Nincs jogosultság', 'Kapcsolatok kezeléséhez jogosultság szükséges.',
|
||||||
snackPosition: SnackPosition.BOTTOM);
|
snackPosition: SnackPosition.BOTTOM);
|
||||||
@@ -1248,7 +1249,8 @@ class MapSurveyController extends GetxController implements StyleEditable {
|
|||||||
|
|
||||||
Future<void> saveContactAtPoint(LatLng point) async {
|
Future<void> saveContactAtPoint(LatLng point) async {
|
||||||
activeEditTool.value = MapEditTool.none;
|
activeEditTool.value = MapEditTool.none;
|
||||||
if (!PermissionService.to.canContacts) return;
|
if (!PermissionService.to.canContacts(
|
||||||
|
projectId: ProjectService.to.activeProject.value?.uuid)) return;
|
||||||
|
|
||||||
final projectId = ProjectService.to.activeProject.value?.uuid;
|
final projectId = ProjectService.to.activeProject.value?.uuid;
|
||||||
if (projectId == null) return;
|
if (projectId == null) return;
|
||||||
@@ -1275,7 +1277,8 @@ class MapSurveyController extends GetxController implements StyleEditable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadContactMarkers() async {
|
Future<void> _loadContactMarkers() async {
|
||||||
if (!PermissionService.to.canContacts) {
|
if (!PermissionService.to
|
||||||
|
.canContacts(projectId: ProjectService.to.activeProject.value?.uuid)) {
|
||||||
contactMarkers.clear();
|
contactMarkers.clear();
|
||||||
contactsWithLocation.clear();
|
contactsWithLocation.clear();
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,17 +1,14 @@
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||||
|
|
||||||
/// Oldal-/funkció-szintű jogosultságok kezelése.
|
/// Oldal-/funkció-szintű jogosultságok kezelése — PROJEKTENKÉNT.
|
||||||
///
|
///
|
||||||
/// A jogosultságokat egy Supabase `app_permissions` tábla tárolja
|
/// A jogosultságokat a Supabase `app_permissions` tábla tárolja
|
||||||
/// (user_id + area), és a felhasználó a saját sorait olvashatja (RLS).
|
/// (user_id + area + project_id), ahol a project_id NULL értéke
|
||||||
/// A service induláskor és bejelentkezéskor betölti a jelenlegi
|
/// GLOBÁLIS jogot jelent (minden projektre érvényes — pl. admin).
|
||||||
/// felhasználó jogosultságait egy halmazba, amit a UI reaktívan figyel.
|
/// A tényleges védelmet a Supabase-oldali RLS adja
|
||||||
///
|
/// (`has_permission(area, project_id)`), ez a service csak a UI-t
|
||||||
/// Bővíthető: új védett terület = új 'area' string (pl. 'admin'),
|
/// vezérli (menüpont elrejtése, üzenet).
|
||||||
/// a UI a [can] getterrel kérdez rá — kódmódosítás nélkül. A tényleges
|
|
||||||
/// védelmet a Supabase-oldali RLS adja (a `contacts`/admin táblákon),
|
|
||||||
/// ez a service csak a UI-t vezérli (menüpont elrejtése, üzenet).
|
|
||||||
class PermissionService extends GetxService {
|
class PermissionService extends GetxService {
|
||||||
static PermissionService get to => Get.find();
|
static PermissionService get to => Get.find();
|
||||||
|
|
||||||
@@ -20,18 +17,31 @@ class PermissionService extends GetxService {
|
|||||||
|
|
||||||
SupabaseClient get _client => Supabase.instance.client;
|
SupabaseClient get _client => Supabase.instance.client;
|
||||||
|
|
||||||
/// A jelenlegi felhasználó engedélyezett területei.
|
/// projectId → engedélyezett területek. A `null` kulcs a GLOBÁLIS
|
||||||
final _areas = <String>{}.obs;
|
/// (minden projektre érvényes) jogokat tárolja.
|
||||||
|
final _areasByProject = <String?, Set<String>>{}.obs;
|
||||||
final isLoaded = false.obs;
|
final isLoaded = false.obs;
|
||||||
|
|
||||||
bool can(String area) => _areas.contains(area);
|
/// [projectId] nélkül CSAK a globális jogokat nézi — projekt-specifikus
|
||||||
bool get canContacts => can(areaContacts);
|
/// ellenőrzéshez mindig add meg az aktív projektet.
|
||||||
|
bool can(String area, {String? projectId}) {
|
||||||
|
if (_areasByProject[null]?.contains(area) ?? false) return true;
|
||||||
|
if (projectId != null) {
|
||||||
|
return _areasByProject[projectId]?.contains(area) ?? false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool canContacts({String? projectId}) =>
|
||||||
|
can(areaContacts, projectId: projectId);
|
||||||
|
|
||||||
|
/// Az admin jogot szándékosan globálisnak tartjuk — ha ezt is
|
||||||
|
/// projektenként szeretnéd, ugyanígy paraméterezhető.
|
||||||
bool get canAdmin => can(areaAdmin);
|
bool get canAdmin => can(areaAdmin);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
// Induláskor és minden auth-változáskor újratöltjük.
|
|
||||||
reload();
|
reload();
|
||||||
_client.auth.onAuthStateChange.listen((_) => reload());
|
_client.auth.onAuthStateChange.listen((_) => reload());
|
||||||
}
|
}
|
||||||
@@ -39,23 +49,28 @@ class PermissionService extends GetxService {
|
|||||||
Future<void> reload() async {
|
Future<void> reload() async {
|
||||||
final user = _client.auth.currentUser;
|
final user = _client.auth.currentUser;
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
_areas.clear();
|
_areasByProject.clear();
|
||||||
isLoaded.value = true;
|
isLoaded.value = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
final rows = await _client
|
final rows = await _client
|
||||||
.from('terepi_seged_app_permissions')
|
.from('terepi_seged_app_permissions')
|
||||||
.select('area')
|
.select('area, project_id')
|
||||||
.eq('user_id', user.id);
|
.eq('user_id', user.id);
|
||||||
_areas
|
|
||||||
|
final map = <String?, Set<String>>{};
|
||||||
|
for (final r in rows) {
|
||||||
|
final pid = r['project_id'] as String?;
|
||||||
|
map.putIfAbsent(pid, () => {}).add(r['area'] as String);
|
||||||
|
}
|
||||||
|
_areasByProject
|
||||||
..clear()
|
..clear()
|
||||||
..addAll(rows.map((r) => r['area'] as String));
|
..addAll(map);
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
// Hálózati hiba: nem adunk jogot (fail-closed), de nem is dobunk.
|
_areasByProject.clear();
|
||||||
_areas.clear();
|
|
||||||
} finally {
|
} finally {
|
||||||
_areas.refresh();
|
_areasByProject.refresh();
|
||||||
isLoaded.value = true;
|
isLoaded.value = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,8 +109,9 @@ class AppDrawer extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
Obx(() {
|
Obx(() {
|
||||||
final signedIn = AuthService.to.isSignedIn;
|
final signedIn = AuthService.to.isSignedIn;
|
||||||
|
final projectId = ProjectService.to.activeProject.value?.uuid;
|
||||||
final allowed = !Get.isRegistered<PermissionService>() ||
|
final allowed = !Get.isRegistered<PermissionService>() ||
|
||||||
PermissionService.to.canContacts;
|
PermissionService.to.canContacts(projectId: projectId);
|
||||||
if (!signedIn || !allowed) return const SizedBox.shrink();
|
if (!signedIn || !allowed) return const SizedBox.shrink();
|
||||||
return ListTile(
|
return ListTile(
|
||||||
leading: const Icon(Icons.phone_outlined),
|
leading: const Icon(Icons.phone_outlined),
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:get/get.dart';
|
|||||||
import 'package:terepi_seged/enums/map_edit_tool.dart';
|
import 'package:terepi_seged/enums/map_edit_tool.dart';
|
||||||
import 'package:terepi_seged/pages/map_survey/presentations/controllers/map_survey_controller.dart';
|
import 'package:terepi_seged/pages/map_survey/presentations/controllers/map_survey_controller.dart';
|
||||||
import 'package:terepi_seged/services/permission_service.dart';
|
import 'package:terepi_seged/services/permission_service.dart';
|
||||||
|
import 'package:terepi_seged/services/project_service.dart';
|
||||||
|
|
||||||
import 'map_toolbar_action.dart';
|
import 'map_toolbar_action.dart';
|
||||||
import 'map_toolbar_divider.dart';
|
import 'map_toolbar_divider.dart';
|
||||||
@@ -66,7 +67,8 @@ class MapEditCompactToolbar extends StatelessWidget {
|
|||||||
selected: activeTool == MapEditTool.polygon,
|
selected: activeTool == MapEditTool.polygon,
|
||||||
onTap: controller.startPolygonTool,
|
onTap: controller.startPolygonTool,
|
||||||
),
|
),
|
||||||
if (PermissionService.to.canContacts)
|
if (PermissionService.to.canContacts(
|
||||||
|
projectId: ProjectService.to.activeProject.value?.uuid))
|
||||||
ToolbarAction(
|
ToolbarAction(
|
||||||
icon: Icons.person_pin_circle_outlined,
|
icon: Icons.person_pin_circle_outlined,
|
||||||
label: 'Kapcsolat',
|
label: 'Kapcsolat',
|
||||||
|
|||||||
Reference in New Issue
Block a user