Files
Mitgliederverwaltung/lib/Service/PermissionService.php
T
shahondin1624 d48e2b7d9d feat: v0.2.0 — backup system, self-update with Ed25519 signing, column pickers, import fixes
Major features:
- Full backup & restore system (JSON snapshots of all 20 tables + settings)
  - Web UI, REST API, OCC CLI commands, scheduled background job
- Self-update from Gitea releases with Ed25519 signature verification
- Configurable column visibility on all data tables (persisted via localStorage)

Fixes:
- NC admin group fallback for PermissionService (IGroupManager)
- Bundle import inline error correction (editable error rows)

New files: BackupService, BackupSettingsService, BackupController, BackupJob,
SelfUpdateService, 4 OCC commands, ColumnPicker component, Backup.vue,
Ed25519 signing scripts, signature verification tests (18 tests)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 23:11:51 +02:00

260 lines
7.5 KiB
PHP

<?php
declare(strict_types=1);
namespace OCA\Mitgliederverwaltung\Service;
use OCA\Mitgliederverwaltung\Db\MemberMapper;
use OCA\Mitgliederverwaltung\Db\Permission;
use OCA\Mitgliederverwaltung\Db\PermissionMapper;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\DB\Exception;
use OCP\IGroupManager;
/**
* Permission checking logic for all access levels.
*
* Access levels (lowest to highest):
* - none: no access
* - read: view members, search, generate reports (no banking)
* - stufe: read/write for members in assigned Stufe(n) only
* - full: full CRUD on all data
* - admin: full + settings, fee rules, user permissions, import
*
* Part of Issue #36.
*/
class PermissionService {
private PermissionMapper $permissionMapper;
private MemberMapper $memberMapper;
private IGroupManager $groupManager;
public function __construct(
PermissionMapper $permissionMapper,
MemberMapper $memberMapper,
IGroupManager $groupManager
) {
$this->permissionMapper = $permissionMapper;
$this->memberMapper = $memberMapper;
$this->groupManager = $groupManager;
}
/**
* Get permission record for a user. Returns null if no record exists (= no access).
*
* @throws Exception
*/
public function getUserPermission(string $userId): ?Permission {
try {
return $this->permissionMapper->findByUserId($userId);
} catch (DoesNotExistException $e) {
return null;
}
}
/**
* Check if user has any access to the app (level != 'none').
*
* @throws Exception
*/
public function canAccess(string $userId): bool {
if ($this->groupManager->isInGroup($userId, 'admin')) {
return true;
}
$perm = $this->getUserPermission($userId);
return $perm !== null && $perm->getLevel() !== 'none';
}
/**
* Check if user has at least read access.
*
* @throws Exception
*/
public function canRead(string $userId): bool {
if ($this->groupManager->isInGroup($userId, 'admin')) {
return true;
}
$perm = $this->getUserPermission($userId);
if ($perm === null) {
return false;
}
return in_array($perm->getLevel(), ['read', 'stufe', 'full', 'admin'], true);
}
/**
* Check if user can write data for a specific member.
* - 'full' and 'admin' can write any member
* - 'stufe' can only write members in their allowed Stufen
*
* @throws Exception
*/
public function canWrite(string $userId, ?int $memberId = null): bool {
if ($this->groupManager->isInGroup($userId, 'admin')) {
return true;
}
$perm = $this->getUserPermission($userId);
if ($perm === null) {
return false;
}
$level = $perm->getLevel();
if (in_array($level, ['full', 'admin'], true)) {
return true;
}
if ($level === 'stufe' && $memberId !== null) {
try {
$member = $this->memberMapper->findById($memberId);
$stufeId = $member->getStufeId();
if ($stufeId !== null) {
return in_array($stufeId, $perm->getAllowedStufen(), true);
}
} catch (DoesNotExistException $e) {
return false;
}
}
return false;
}
/**
* Check if user can write to members in a specific Stufe.
*
* @throws Exception
*/
public function canWriteStufe(string $userId, int $stufeId): bool {
$perm = $this->getUserPermission($userId);
if ($perm === null) {
return false;
}
$level = $perm->getLevel();
if (in_array($level, ['full', 'admin'], true)) {
return true;
}
if ($level === 'stufe') {
return in_array($stufeId, $perm->getAllowedStufen(), true);
}
return false;
}
/**
* Check if user is admin.
*
* @throws Exception
*/
public function isAdmin(string $userId): bool {
$perm = $this->getUserPermission($userId);
if ($perm !== null && $perm->getLevel() === 'admin') {
return true;
}
// Fallback: Nextcloud system admins are always app admins
return $this->groupManager->isInGroup($userId, 'admin');
}
/**
* Check if user can see banking details.
*
* @throws Exception
*/
public function canSeeBanking(string $userId): bool {
if ($this->groupManager->isInGroup($userId, 'admin')) {
return true;
}
$perm = $this->getUserPermission($userId);
return $perm !== null && $perm->getCanSeeBanking();
}
/**
* Get the list of Stufe IDs visible to a user with Stufenzugriff.
* Returns null for full/admin (= all Stufen visible).
*
* @return int[]|null null means all Stufen visible
* @throws Exception
*/
public function getVisibleStufen(string $userId): ?array {
$perm = $this->getUserPermission($userId);
if ($perm === null) {
return [];
}
$level = $perm->getLevel();
if (in_array($level, ['full', 'admin', 'read'], true)) {
return null; // all visible
}
if ($level === 'stufe') {
return $perm->getAllowedStufen();
}
return [];
}
// ── Admin methods for managing permissions ───────────────────────
/**
* Get all permission records.
*
* @return Permission[]
* @throws Exception
*/
public function getAllPermissions(): array {
return $this->permissionMapper->findAll();
}
/**
* Set permission for a user (create or update).
*
* @throws Exception
*/
public function setPermission(
string $ncUserId,
string $level,
?array $allowedStufen = null,
bool $canSeeBanking = false
): Permission {
$validLevels = ['none', 'read', 'stufe', 'full', 'admin'];
if (!in_array($level, $validLevels, true)) {
throw new ValidationException(
'Ungueltiges Berechtigungslevel: ' . $level . '. Erlaubt: ' . implode(', ', $validLevels)
);
}
try {
$perm = $this->permissionMapper->findByUserId($ncUserId);
$perm->setLevel($level);
$perm->setAllowedStufenJson($allowedStufen !== null ? json_encode($allowedStufen) : null);
$perm->setCanSeeBanking($canSeeBanking);
/** @var Permission $perm */
return $this->permissionMapper->update($perm);
} catch (DoesNotExistException $e) {
$perm = new Permission();
$perm->setNcUserId($ncUserId);
$perm->setLevel($level);
$perm->setAllowedStufenJson($allowedStufen !== null ? json_encode($allowedStufen) : null);
$perm->setCanSeeBanking($canSeeBanking);
/** @var Permission $perm */
return $this->permissionMapper->insert($perm);
}
}
/**
* Remove permission for a user.
*
* @throws Exception
*/
public function removePermission(string $ncUserId): void {
try {
$perm = $this->permissionMapper->findByUserId($ncUserId);
$this->permissionMapper->delete($perm);
} catch (DoesNotExistException $e) {
// Already no permission
}
}
}