d48e2b7d9d
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>
260 lines
7.5 KiB
PHP
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
|
|
}
|
|
}
|
|
}
|