Skip to content

Audit stavu oprávnění

Oprávnění: audit současného stavu a cílový model

Section titled “Oprávnění: audit současného stavu a cílový model”

Zdroje: audit zdrojového kódu + snapshot produkční databáze (chk_sp_roles, chk_sp_permissions) k 30. 6. 2026. Stav: analytický dokument, kód zatím nebyl měněn. Část I mapuje současný stav, Část II navrhuje cílový model (Cesta B — role podle oddělení + samostatná datová osa trhů CZ/SK).

Tři klíčové principy cílového modelu:

  1. Oprávnění = akce (resource.view/create/update/delete/approve), ne „vidím modul“.
  2. Trh je samostatná osa — stejná role může platit pro všechny trhy, jen CZ, nebo jen SK.
  3. Jediný bypass je superadminadmin se stává běžnou technickou rolí.

  • Balíček: spatie/laravel-permission ^6. Žádný Filament panel ani Shield — Filament je použit jen jako UI knihovna (tables/forms).
  • Model: jediný App\User s traitem HasRoles. Na chk_users není sloupec role ani is_admin.
  • Guardy: web (session) a api (token). Žádný samostatný guard pro admin/moderátora.
  • Tabulky (prefix chk_): chk_sp_roles, chk_sp_permissions, chk_model_has_roles, chk_model_has_permissions, chk_role_has_permissions.
  • Gates: Gate::before → role admin projde vším. viewHorizon (chybná logika, viz §6).
  • Policies: žádné. app/Policies/ obsahuje jen .gitkeep.
  • Ochrana rout: admin. přes view_admin + vnořené role:… skupiny; moderator. bez auth middlewaru (jen podepsané URL).

Legacy systém Entrust byl migrován na Spatie (app/Console/Commands/MigratePermissionPackage.php); modely App\Modules\Role, App\Modules\Permission a App\UserMigration už neexistují — živé jsou pouze Spatie tabulky. V DB však přežívají legacy Entrust tabulky a mrtvé seedery.

Aktuální data: 176 oprávnění, 13 rolí (15 řádků — 2 duplicity, viz §6).

Roztříštěnost mechanismů je jádro problému, který má přechod vyřešit:

#MechanismusKdeCo dělá
1Gate::before super-adminAuthServiceProvider::boot()role adminkaždý gate/can() vrací true
2Kontrola oprávnění přes can()User::canView(), canViewSection(), blade @if($loggedUser->canView(...))oprávnění pojmenovaná podle modulů/stránek, používaná hlavně pro menu
3Route middleware role:routes/admin.phpněkolik skupin rout omezených názvem role
4Authorize::using('…')routes/admin.php (quizzConfig.download)jediné místo, kde routu chrání oprávnění
5Natvrdo hasRole()/hasAnyRole()kontrolery, Livewire, Bladebusiness pravidla navázaná přímo na názvy rolí

1.2 Oprávnění jsou dnes „stránky“, ne „akce nad modelem“

Section titled “1.2 Oprávnění jsou dnes „stránky“, ne „akce nad modelem“”

Oprávnění jsou v drtivé většině názvy modulů/sekcí převzaté z config/module.php (např. pub, result, overview, quizzConfig, newLeague). Menšina má tvar resource.action (player.create, bankTransaction.destroy, generalFeedback.submitReply). Odpovídají na otázku „může uživatel vidět tuhle admin stránku?“, ne „může uživatel provést akci X nad modelem Y?“. Proto skutečná pravidla na úrovni modelu spadají zpět na hasRole() (mechanismus #5).


Než začneme mapovat kdo co smí: vynucování oprávnění na úrovni rout se dnes prakticky neděje.

  • ModuleMiddleware je no-op. Spočítá Auth::user()->canView($module), ale tělo if je prázdné a následuje bezpodmínečný return $next($request). Závěrečný abort(403) je nedosažitelný:

    if (Auth::user()->canView($module)) {
    }
    return $next($request); // vždy
    abort(403); // mrtvý kód

    Každá routa ve velké skupině ['middleware' => ['module']] v routes/admin.php (tedy většina admin panelu) je proto chráněná jen view_admin (= „je přihlášen, má ≥1 roli, ověřený e-mail“) — ne oprávněním modulu. Oprávnění dnes řídí pouze viditelnost v menu.

  • view_admin (CanViewAdmin) kontroluje jen: autentizovaný + alespoň jedna role + (u účtů vzniklých po 1. 11. 2025) ověřený e-mail. Vstup do admin domény tedy dává jakákoli role.

  • Skutečná autorizace na úrovni rout existuje jen v několika skupinách:

    • role:admin
    • role:admin|kvizAdmin
    • role:moderator|admin|kvizAdmin
    • Authorize::using('quizzConfig.download') ← jediná routa řízená oprávněním.
  • Subdoména moderator. nemá auth middlewareroutes/moderator.php spoléhá výhradně na podepsané tokeny v URL.

  • Žádné Policies. AuthServiceProvider::$policies stále obsahuje stub App\Model => App\Policies\ModelPolicy.

Důsledek: přechod na permission-based autorizaci není jen přejmenování kontrol — vyžaduje skutečné vynucování (oprava ModuleMiddleware a/nebo Policies), protože dnes platí „jakákoli admin role se dostane na jakoukoli stránku a Gate::before dělá z admin všemocného“.


Počty uživatelů a přímo přiřazených oprávnění ze snapshotu produkční DB.

RoleUživatelůPřímých opráv.Účel (dle přiřazených oprávnění)
Superadmin admin34∞ (Gate::before)Obchází všechny kontroly. Plný přístup.
Systémový admin systemadmin370Technická správa systému, skoro vše kromě financí.
Kvíz administrátor kvizAdmin2640Provozní správa kvízů, ligy, obsah, marketing.
Manažer provozu manager2413Uživatelé, výsledky, moderátoři, statistiky.
Business manažer business-manager119Obchod: konfigurace kvízů, ligy, CMS, reklama.
Finance / účetní finance314Platby, mzdy, bankovní transakce.
Tvůrce témat creator417Tvorba témat a otázek.
Super tvůrce superCreator307Tvorba + doporučování témat.
Auditor auditor1214Kontrola/korektura témat.
Super auditor superAuditor2317Audit + správa kvíz konfigurace.
Moderátor moderator8032Pouze ligové výsledky (přes admin panel).
PR manažer pr-manazer131Poznámky moderátorů.
Správce rolí spravce-roli63Uživatelé + statistiky (správa rolí).

admin má „∞“, protože práva získává přes Gate::before, ne přiřazením.

Role moderator je největší skupina, ale má jen 2 oprávnění — skutečné pravomoci moderátora plynou z hasRole('moderator') kontrol a ze subdomény, ne z oprávnění.


4. Kompletní matice — současný stav (modul × role)

Section titled “4. Kompletní matice — současný stav (modul × role)”

Legenda sloupců: Sadm Superadmin · Sys Systémový admin · Kvíz Kvíz administrátor · Prov Manažer provozu · Bus Business manažer · Fin Finance / účetní · Tvůr Tvůrce témat · STvů Super tvůrce · Aud Auditor · SAud Super auditor · Mod Moderátor · PR PR manažer · Role Správce rolí.

✓ = role má oprávnění · = nemá. admin má vše přes bypass.

Modul / oprávněníSadmSysKvízProvBusFinTvůrSTvůAudSAudModPRRole
Uživatelé user·········
Hráči player···········
Týmy team··········
Zobrazení uživatelů show_users···········
Modul / oprávněníSadmSysKvízProvBusFinTvůrSTvůAudSAudModPRRole
Kalendář akcí eventCalendar············
Rezervace reservations············
Kvízy (instance) quizz··········
Výsledky result·········
Poznámky moderátorů resultNote······
Náměty poznámek resultNoteIdeas···········
Hodnocení poznámek resultNoteRating···········
Stahování download··········
Moderátoři (modul) moderator·········
Vouchery voucher············
Zákaznický podnik customerPub·········
Modul / oprávněníSadmSysKvízProvBusFinTvůrSTvůAudSAudModPRRole
Podniky pub··········
Statistiky podniků pub_statistics·········
Šablony kvízů quizzBlueprints············
Speciál special············
Konfigurace kvízů (staré) quizzConfigOld···········
Konfigurace kvízů quizzConfig·········
Stažení konfigurace quizzConfig.download·········
Modul / oprávněníSadmSysKvízProvBusFinTvůrSTvůAudSAudModPRRole
Nová liga newLeague···········
Liga (modul) league··········
Extraliga extraleague··········
Ceny / odměny price·········
Typ ligy leagueType·········
Výherci ligy leagueWinner············
Zobrazení ligových výsledků show_league_results··········
Výpočet ligy calculate_league··········
Registrace do ligy leagueRegistration············
Modul / oprávněníSadmSysKvízProvBusFinTvůrSTvůAudSAudModPRRole
Témata topic······
Vytvoření tématu topic.create···········
Přehled témat overview········
Detail tématu overview.show········
Otázky questions············
Audit otázek questionsAudit············
Kategorie category···········
Podkategorie subcategory···········
Skupiny témat topicGroup···········
Přiřazení skupiny topicGroup.assign·······
Stavy témat topicStatus···········
Uživatelská témata usertopics··········
Audit audit·········
Korektura correct·········
Moje korektury myCorrections········
Doporučená témata recommended········
Vytvoření doporučeného recommended.create···········
Přiřazení doporučeného recommended.assign········
Přiřazení assign·········
Vracející se témata returning_topics···········
Vrácení témat returning_of_topics·········
Statistiky témat topic_statistics········
Statistiky témat (2) statistics_topics·········
Modul / oprávněníSadmSysKvízProvBusFinTvůrSTvůAudSAudModPRRole
Platby payment··········
Transakce generalPayment············
Bankovní transakce bankTransaction···········
Transakce (modul) transaction···········
Platby za kvíz quizzPayment···········
Typ platby paymentType············
Typ platby za téma topicPaymentType············
Mzdy wage··········
Výplaty remittance··········
Cashflow cashflow···········
Bloky block···········
Podmínky bonusu bonusCondition···········
Interní bonus internBonus···········
Externí bonus externBonus···········
Vzorec lovce hunterFormula············
Vzorce odměn moderátorů moderatorRewardFormula············
Odměny rewards············
EET kód eetCode············
Modul / oprávněníSadmSysKvízProvBusFinTvůrSTvůAudSAudModPRRole
Účetní reporty accountingReport············
Faktura (modul) invoice···········
Faktury invoices············
Společnosti company···········
Modul / oprávněníSadmSysKvízProvBusFinTvůrSTvůAudSAudModPRRole
Kandidáti na moderátory moderatorCandidate··········
Připomínky generalFeedback··········
Odpověď na připomínku generalFeedback.submitReply············
Firemní poptávky companyInquiry··········
Žádosti o změnu changeRequest············
Komentáře comments············
Modul / oprávněníSadmSysKvízProvBusFinTvůrSTvůAudSAudModPRRole
Blog blog··········
Firemní kvízy company_quiz··········
Kontakty contacts··········
Novinky news·········
Nastavení homepage homepage_settings············
Modul / oprávněníSadmSysKvízProvBusFinTvůrSTvůAudSAudModPRRole
Produkty product············
Objednávky order············
Doprava shipping············
Objednávka párty kvízu partyQuizzOrder··········
Stažení párty kvízu partyQuizzDownload··········
Promo kódy promoCode··········
Modul / oprávněníSadmSysKvízProvBusFinTvůrSTvůAudSAudModPRRole
Engagement engagement···········
Reklama advertisement·········
Video v aplikaci app_video···········
Modul / oprávněníSadmSysKvízProvBusFinTvůrSTvůAudSAudModPRRole
Promo promo·········
Soubory file·········
Složky folder·········
Partneři partner·········
Filtry filter·········
Atributy attribute··········
Jazyky language··········
Města city·········
Regiony region·········
Skupiny regionů regionGroup············
Země country··········
Materiály material··········
Info info··········
Obecné nastavení general_settings············
Modul / oprávněníSadmSysKvízProvBusFinTvůrSTvůAudSAudModPRRole
Systémová oznámení systemNotification············
Snippety snippet············
Plakáty poster············
Nastavení systému system············
Moduly (RBAC) module···········
Role (RBAC) role···········
Oprávnění (RBAC) permission···········
Oznámení notification··········
Alerty alert···········
Statistiky statistics········
Dokončení migrace migrationCompletition············
Migrace rezervací migrateReservations············
Kvízy→Šablony quizzesToBlueprints············
Ostatní / nezařazené specials············

5. Kde se kontrolují role (inventura hasRole)

Section titled “5. Kde se kontrolují role (inventura hasRole)”

Tato volací místa je potřeba převést na kontroly oprávnění. Každý řádek navrhuje název oprávnění (konsolidovaný seznam viz §9).

UmístěníSoučasná kontrolaZáměrNavržené oprávnění
AuthServiceProvider:31hasRole('admin') (Gate::before)Super-admin bypass(zůstává jako role → superadmin)
User::canImpersonate():507hasAnyRole('admin','kvizAdmin')Spuštění impersonaceuser.impersonate
User::canBeImpersonated():512logika rolíCíl impersonace(pravidlo cíle, ponechat)
HorizonServiceProvider:27hasRole('admin')Zobrazení Horizon dashboardusystem.horizon.view
Console/EnsureQaUser:55hasRole('admin')Guard pro QA seed(konzole, nízká priorita)
Livewire/ChangeRequest/CreateEventRequest:34hasRole('admin')Zobrazení akce „vytvořit událost“changeRequest.createEvent
Http/ViewComposers/ModeratorWorklistComposer:17hasRole('moderator')Sestavení worklistu moderátoramoderator.worklist.view
Livewire/QuestionsTable:200isMine() || hasAnyRole('admin','superAuditor')Smazání libovolné otázkyquestion.delete.any
Livewire/Topic/TopicForm:418hasRole(['auditor','superAuditor','admin'])Auditorský flow editacetopic.audit
Livewire/Topic/TopicForm:441hasRole(['superAuditor','admin'])Force-edit schváleného tématutopic.forceEdit
Livewire/Topic/TopicsTable:348! hasRole(['superAuditor','admin'])Omezení editace v přehledutopic.manage
Livewire/Topic/TopicsTable:361hasAnyRole(['superAuditor','admin'])Správa stavů témattopic.manage
Livewire/Actions/AdminConfirmResults:32hasAnyRole('admin','kvizAdmin')Potvrzení výsledků akceresult.confirm
Livewire/User/UsersForm:93hasRole(['admin','spravce-roli'])syncRoles() při uloženíuser.assignRoles
Livewire/User/UsersForm:163hasRole('admin') || hasRole('spravce-roli')Zobrazení selectu rolíuser.assignRoles
Modules/Topic/UserTopic:136hasRole('superAuditor')Přeřazení auditoratopic.assign.auditor
Modules/Topic/UserTopic:179hasRole('superCorrector') *Přeřazení korektoratopic.assign.corrector
Http/Controllers/Admin/UserController:76hasRole(['creator','superCreator'])Dashboard autoratopic.author.dashboard
Http/Controllers/Admin/TopicController:210autor NEBO hasRole('admin')Editace tématutopic.edit.any (+ vlastnictví)
Http/Controllers/Admin/TopicController:222hasRole(['auditor','superAuditor','admin'])Audit tématutopic.audit
Http/Controllers/Admin/TopicController:230,300hasRole(['superAuditor','admin'])Schválení / správatopic.approve
Http/Controllers/Admin/TopicController:273korektor NEBO hasRole('superAuditor')Editace korekturytopic.correct.any (+ vlastnictví)
Http/Controllers/Admin/TopicController:783hasRole(['superAuditor','auditor','admin','kvizAdmin'])Stažení/viditelnost tématutopic.download
Rules/MustHaveAccountingDetails:19hasRole('moderator')Validace: moderátor potřebuje účetní údaje(doménové pravidlo, ponechat)
Blade partials/sidebar:11, admin/layout/sidebar:35hasRole('moderator')Položky sidebaru moderátoramoderator.worklist.view

* superCorrector se v kódu kontroluje, ale v DB jako role neexistuje — ta větev je mrtvá. Při přechodu je potřeba ji buď odstranit, nebo roli založit.

5.1 Route middleware podle rolí (routes/admin.php)

Section titled “5.1 Route middleware podle rolí (routes/admin.php)”
ŘádekMiddlewareCílový stav
56role:adminoprávnění pokrývající stránky té skupiny
61role:admin|kvizAdminnapř. result.confirm / provozní oprávnění
69role:moderator|admin|kvizAdmin (prefix moderator)moderator.access
92Authorize::using('quizzConfig.download')✅ už řízeno oprávněním (vzor k následování)

🔴 KRITICKÉ — ModuleMiddleware nevynucuje oprávnění

Section titled “🔴 KRITICKÉ — ModuleMiddleware nevynucuje oprávnění”

if (Auth::user()->canView($module)) {} s prázdným tělem a abort(403) až za return (mrtvý kód). CRUD routy jsou přístupné každému přihlášenému s rolí; omezení je jen vizuální v menu. Detail viz §2.

  • creator — dva řádky v chk_sp_roles (id 3 a 13; uživatelů 41 + 0).
  • superCreator — dva řádky v chk_sp_roles (id 4 a 14; uživatelů 30 + 0).

Oprávnění jsou rozdělená mezi oba řádky. Nutná deduplikace před jakoukoli migrací mapování role → oprávnění.

🟠 VYSOKÉ — duplicitní a osiřelá oprávnění

Section titled “🟠 VYSOKÉ — duplicitní a osiřelá oprávnění”

Duplicity (blog, invoices, reservations, regionGroup, specials, topic.create, migrationCompletition …) i oprávnění bez role (dostupná jen adminovi přes bypass).

🟠 VYSOKÉ — subdoména moderator. bez auth middlewaru

Section titled “🟠 VYSOKÉ — subdoména moderator. bez auth middlewaru”

routes/moderator.php spoléhá výhradně na podepsané tokeny v URL.

🟡 STŘEDNÍ — chybný viewHorizon gate, bypass přes admin, zbytky Entrust

Section titled “🟡 STŘEDNÍ — chybný viewHorizon gate, bypass přes admin, zbytky Entrust”
  • viewHorizon porovnává e-mail s booleanem.
  • admin obchází vše a navíc má redundantně přiřazená práva.
  • V DB přežívají legacy Entrust tabulky a mrtvé seedery.
  • routes/admin.php:553 Route::post('togglePerm', …) — metoda togglePerm na kontroleru neexistuje.
  • ModuleController (resource module) odkazuje na App\Modules\Permission, třídu, která už neexistuje → fatal error při zavolání. Legacy obrazovka „Modul ↔ Oprávnění“ je nefunkční.
  • Role superCorrector se kontroluje v UserTopic:179, ale nikdy nebyla založena.
  • Žádné Policies; $policies obsahuje jen framework stub.

7. Kontroly založené na oprávněních (vzory k následování)

Section titled “7. Kontroly založené na oprávněních (vzory k následování)”

Tato místa už používají oprávnění a ukazují cílový vzor:

  • User::canView($module)$this->can($module) — viditelnost menu/sekcí v sidebar.blade.php, partials/sidebar.blade.php, dashboard/modules.blade.php.
  • User::canViewSection($section) — sekce je vidět, pokud je povolen libovolný podřízený modul (config('module.sections')).
  • User::canViewStatistics()canView('statistics').
  • Blade: @if($loggedUser->canView('league.edit')), canView('overview.edit'), canView('statistics.'.$name).
  • Authorize::using('quizzConfig.download') jako route middleware.
  • BusinessManagerRoleSeedernejnovější kód a dobrá šablona: definuje roli čistě jako balík oprávnění (quizzConfig, newLeague, blog, …) plus quizzConfig.download. Tímto směrem je potřeba zobecnit zbytek.

8. Tři osy oprávnění + datová osa trhů

Section titled “8. Tři osy oprávnění + datová osa trhů”
OsaVýznam
WHO — identitaUživatel. Patří do jednoho/více oddělení a má přiřazené trhy.
WHAT — role → oprávněníRole = balík akcí resource.view/create/update/delete/approve. Vynuceno přes Policies.
WHERE — trh (CZ/SK/…)Samostatná osa. user_markets + sloupec market_id na datech + global scope = datová izolace.

Cesta B (zvolená): role zůstávají globální (definují co), a kde řeší datová osa. Klíčové komponenty:

  • Číselník trhů (tabulka markets: CZ, SK, …, rozšiřitelné).
  • Přiřazení trhů uživateli (pivot user_markets; „vše“ = všechny trhy / flag all_markets).
  • Sloupec market_id na scope-citlivých entitách (topic, quizzConfig, pub, league, …).
  • Global query scope (trait Marketable) — dotazy automaticky vrací jen řádky z trhů uživatele → CZ uživatel fyzicky neuvidí SK data ani v míchaných seznamech.

Policy kombinuje obě osy:

public function audit(User $user, Topic $topic): bool
{
return $user->can('topic.audit') // WHAT — akce
&& $user->hasMarketAccess($topic->market_id); // WHERE — trh
}

Tím je splněn požadavek: stejná role (např. produkt-manazer) může být uživateli přiřazena pro všechny trhy, jen CZ, nebo jen SK — beze změny role samotné.


Každé oddělení má standardní roli (běžná práce ve svém trhu: čtení + tvorba + úpravy) a super roli (vedoucí: navíc mazání, schvalování, publikace, export; typicky napříč trhy).

OdděleníStandardní roleSuper roleOdpovědnost
Obchodobchodnikobchod-manazerFiremní poptávky a kvízy, konfigurace a prodej kvízů, ligy a ceny, e-shop, společnosti.
Produkt / Obsahtvurce-obsahuprodukt-manazerTvorba témat a otázek; audit, korektura a publikace; doporučená témata; šablony kvízů.
Provozprovozniprovoz-manazerAkce a rezervace, výsledky, podniky, moderátoři a kandidáti, ligy (provoz), vouchery.
Marketingmarketermarketing-manazerBlog a novinky, reklama, promo, partneři, CMS/homepage, materiály.
Financeucetnifinance-manazerPlatby, mzdy a výplaty, bankovní transakce, fakturace a účetní reporty, číselníky.
Systém / ITadminsuperadminRBAC, číselníky lokací, systémová nastavení, statistiky. Superadmin = jediný bypass.

Externí: moderator je průřezová role mimo oddělení — výsledky a formuláře přes podepsané URL.

Rozdíl mezi standardní a super rolí je hloubka oprávnění (akce), ne jiný modul; rozsah trhů řeší osa WHERE.


10. Cílová matice schopností (domény × cílové role)

Section titled “10. Cílová matice schopností (domény × cílové role)”

Legenda hodnot: ● plná správa (vč. mazání / schvalování / publikace) · ◐ úprava (čtení + tvorba + editace) · ○ pouze čtení · · bez přístupu.

Legenda sloupců: Obch Obchodník · OMgr Obchod-manažer · Tvůr Tvůrce obsahu · PMgr Produkt-manažer · Prov Provozní · PvMgr Provoz-manažer · Mktg Marketér · MMgr Marketing-manažer · Účet Účetní · FMgr Finance-manažer · Adm Admin (technický) · SAdm Superadmin · Mod Moderátor.

Doména / oprávněníObchOMgrTvůrPMgrProvPvMgrMktgMMgrÚčetFMgrAdmSAdmMod
Firemní poptávky companyInquiry, changeRequest··········
Firemní kvízy company_quiz·········
Konfigurace kvízů quizzConfig (+ download)········
Ligy – prodej a ceny newLeague, price, leagueType·········
E-shop product, order, shipping·········
Kontakty a společnosti contacts, company········
Doména / oprávněníObchOMgrTvůrPMgrProvPvMgrMktgMMgrÚčetFMgrAdmSAdmMod
Témata topic, overview, usertopics, category, topicGroup·········
Otázky questions··········
Audit a korektura audit, correct, myCorrections···········
Doporučená témata recommended··········
Šablony kvízů quizzBlueprints·········
Statistiky obsahu topic_statistics·········
Doména / oprávněníObchOMgrTvůrPMgrProvPvMgrMktgMMgrÚčetFMgrAdmSAdmMod
Uživatelé / hráči / týmy user, player, team········
Kalendář a rezervace eventCalendar, reservations··········
Kvízy a výsledky quizz, result, resultNote, download········
Podniky pub, pub_statistics, customerPub·········
Moderátoři a kandidáti moderator, moderatorCandidate·········
Odměny moderátorů moderatorRewardFormula, rewards·········
Připomínky generalFeedback··········
Ligy – provoz show_league_results, calculate_league·········
Vouchery voucher·········
Doména / oprávněníObchOMgrTvůrPMgrProvPvMgrMktgMMgrÚčetFMgrAdmSAdmMod
Blog a novinky blog, news··········
Reklama advertisement, engagement, app_video··········
Promo a partneři promo, partner·········
Soubory a materiály folder, file, material·········
Homepage / CMS homepage_settings, contacts·········
Doména / oprávněníObchOMgrTvůrPMgrProvPvMgrMktgMMgrÚčetFMgrAdmSAdmMod
Platby payment, generalPayment, quizzPayment··········
Bankovní transakce bankTransaction··········
Mzdy a výplaty wage, remittance··········
Fakturace a reporty invoice(s), accountingReport·········
Účetní číselníky cashflow, block, eetCode, hunterFormula··········
Doména / oprávněníObchOMgrTvůrPMgrProvPvMgrMktgMMgrÚčetFMgrAdmSAdmMod
RBAC role, permission, module···········
Lokace / trhy country, region, city, language·········
Atributy a filtry attribute, filter··········
Systém a oznámení system, systemNotification, snippet, poster, alert···········
Statistiky a insighty statistics, statistics_topics······

Hodnoty jsou návrh k revizi. Projděte buňky a upravte hloubku tam, kde to proces vyžaduje jinak. Z odsouhlasené verze vznikne seeder rolí.


11. Lokace / trhy — jak se překrývají s rolemi

Section titled “11. Lokace / trhy — jak se překrývají s rolemi”

Trh je nezávislá osa. Tentýž profil oprávnění (role) se aplikuje na různý rozsah dat podle přiřazených trhů uživatele:

UživatelRole (WHAT)Trhy (WHERE)Výsledný efekt
Jana Novákováprodukt-manazerCZ, SKSpravuje a schvaluje obsah pro oba trhy.
Petr Svobodaprodukt-manazerCZStejná role, ale vidí a edituje jen český obsah.
Mária Horváthováprodukt-manazerSKStejná role, jen slovenský obsah.
Tomáš DvořákobchodnikSKObchod jen pro SK trh.

Proč to řeší rozjezd SK: nového slovenského pracovníka přidáte jako obchodnik / provozni / … s trhem SK — vidí a spravuje výhradně SK data, bez nutnosti zakládat nové „SK role“. CZ tým funguje beze změny, vedoucí (super role) dostanou oba trhy. Datová izolace je vynucena na úrovni dotazů, ne jen menu.


12. Nová oprávnění potřebná pro přechod

Section titled “12. Nová oprávnění potřebná pro přechod”

Oprávnění, která je potřeba domodelovat, aby šly nahradit natvrdo psané hasRole() kontroly (§5). Názvosloví navazuje na existující konvenci resource.action (player.create, bankTransaction.destroy).

Témata a otázky (dnes nejsilnější vazba na role)

Section titled “Témata a otázky (dnes nejsilnější vazba na role)”
OprávněníNahrazujePoznámka
topic.createcreator s právem tvorby tématuž existuje (deduplikovat)
topic.edit.anyTopicController:210 admin větevkombinovat s pravidlem vlastnictví
topic.auditauditor/superAuditor/admin kontrolyTopicForm:418, TopicController:222
topic.correct.anyTopicController:273 superAuditor větev+ vlastnictví
topic.approvesuperAuditor/adminTopicController:230,300
topic.forceEditforce-edit schváleného tématuTopicForm:441
topic.managespráva přehledu tématTopicsTable:348,361
topic.returnflow returning_of_topicsnormalizace existujícího oprávnění
topic.downloadTopicController:783 multi-role
topic.assign.auditorUserTopic:136 superAuditor
topic.assign.correctorUserTopic:179 superCorrector (mrtvá role)
topic.author.dashboardUserController:76 creator/superCreator
question.delete.anyQuestionsTable:200 admin/superAuditormazání vlastní otázky zůstává na vlastnictví
OprávněníNahrazuje
result.confirmAdminConfirmResults:32, skupina role:admin|kvizAdmin
quizzEvent.createCreateEventRequest:34 (dnes jen admin)
quizzEvent.editdnes jen na úrovni stránky eventCalendar
quizzBlueprint.managednes jen na úrovni stránky quizzBlueprints
OprávněníNahrazuje
user.assignRolesUsersForm:93,163, role spravce-roli
user.impersonateUser::canImpersonate() admin/kvizAdmin
user.viewFinancednes implicitně přes user + admin gate (cashflow/účetní podmoduly)
role.managednes jen admin/systemadmin; potřeba pro reálné UI správy rolí
permission.managepřiřazování oprávnění rolím (nahrazuje mrtvý ModuleController)
OprávněníNahrazuje
moderator.accessskupina rout role:moderator|admin|kvizAdmin
moderator.worklist.viewModeratorWorklistComposer:17, sidebar hasRole('moderator')
OprávněníPoznámka
bankTransaction.*už existují — role finance je čistý balík oprávnění, dobrá šablona
payment.manage, wage.manage, remittance.managenormalizace hrubých oprávnění payment/wage/remittance do tvaru akcí
OprávněníNahrazuje
system.horizon.viewHorizonServiceProvider:27 (+ oprava viewHorizon)
system.settings.managednes stránka general_settings + admin gate

Policies jsou chybějící vynucovací vrstva. Dnes je autorizace směs can() pro menu a rozesetých hasRole(); neexistuje jediné místo, které odpoví na otázku „může uživatel provést akci X nad touto konkrétní instancí?“. Policy nám to dá a hlavně umožní kombinovat oprávnění (schopnost nezávislá na roli), vlastnictví (patří řádek uživateli) a trh (§8) — přesně to, co dnes hasRole místa dělají ručně a nekonzistentně.

  • Každá ability volá oprávnění, ne roli. Např. return $user->can('result.confirm'); — nikdy $user->hasRole('admin'). Role zůstávají jen jako balíky oprávnění (§12), takže policy vrstva je plně permission-based.

  • Bypass zůstává jediný. Po přejmenování na superadmin platí Gate::before jen pro něj → superadmin má plný přístup bez kódu v každé policy. admin se stává běžnou technickou rolí s explicitními právy.

  • Vlastnictví je zakódováno v policy, kombinované s oprávněním:

    public function update(User $user, Topic $topic): bool
    {
    return $user->can('topic.edit.any')
    || ($user->can('topic.edit.own') && $topic->author_id === $user->id);
    }
  • Registrace: Laravel auto-discovery hledá App\Policies\{Model}Policy. Modely jsou rozdělené mezi app/Models a app/Modules — pro namespace Modules auto-discovery nefunguje, ty je nutné explicitně namapovat v AuthServiceProvider::$policies (a nahradit tím současný framework stub).

  • Vynucovací body: kontrolery přes $this->authorize('update', $topic), Livewire přes Gate::authorize(...), Blade přes @can('update', $topic). Tím se nahradí inline hasRole() i rozbitý ModuleMiddleware.

AbilityMapuje se na oprávnění (vzor)
viewAny{resource}.viewAny (často existující oprávnění modulu, např. result)
view{resource}.view (+ vlastnictví u rolí „jen své“)
create{resource}.create
update{resource}.edit.any / {resource}.edit.own + vlastnictví
delete{resource}.delete.any / .own + vlastnictví
restore / forceDelete{resource}.manage

Plus vlastní abilities pro workflow slovesa, která dnes žijí v hasRole.

13.3 Tier 1 — jádro domény (stavět první)

Section titled “13.3 Tier 1 — jádro domény (stavět první)”

Tyto policies nesou skutečnou logiku vlastnictví a absorbují místa z §5.

PolicySignál vlastnictvíVlastní abilities (→ oprávnění)
TopicPolicy (app/Modules/Topic)author_id, auditor_id, corrector_idaudittopic.audit, correcttopic.correct.any, approvetopic.approve, forceEdittopic.forceEdit, returntopic.return, downloadtopic.download, assignAuditortopic.assign.auditor, assignCorrectortopic.assign.corrector
QuestionPolicy (app/Models/Question)isMine() + stav (Draft/ReturnedToAuthor)delete→ vlastní (dle stavu) nebo question.delete.any
QuizzEventPolicy (app/Models/QuizzEvent)moderator_id + morph moderable (moderators())createquizzEvent.create, enterResults→ vlastní akce + moderátor, download→ vlastní akce, confirmResultsresult.confirm
ResultPolicy (app/Modules/Result)přes eventmoderator_idconfirmresult.confirm, update→ moderátor vlastní akce nebo result.edit
TeamReservationPolicy (app/Models/TeamReservation)team_id → hráči týmu; moderátor akceview/update/cancel pro vlastnící tým nebo moderátora akce
UserPolicy (app/User)sám sebe vs. ostatníassignRolesuser.assignRoles, impersonateuser.impersonate, viewFinanceuser.viewFinance, update sebe vs. user.edit.any

Menší objem, vysoká citlivost; dnes chráněno jen rolí finance + admin gate.

PolicyPoznámka
NewInvoicePolicy (app/Modules/NewInvoice)vlastnictví user_id; download pro vlastníka nebo invoices
GeneralPaymentPolicy (app/Modules/GeneralPayment)vlastnictví recipient_id; změny → payment.manage
BankTransactionPolicy (app/Modules/BankTransaction)oprávnění bankTransaction.* už existují — obalit je do policy
CashflowRecordPolicy (app/Models/CashflowRecord)user.viewFinance + vlastnictví

Mnoho „nastavovacích“ / lookup modelů (City, Region, Country, Language, Category, Material, Partner, Filter, Attribute, Snippet, Promo, LeagueType, Price, …) se spravuje přes ResourceController a je chráněno jen oprávněním modulu. Možnosti:

  • Preferované: jedna generická ResourcePolicy (nebo základní třída), jejíž abilities odvodí oprávnění z názvu modulu, explicitně namapovaná v $policies. Chování zůstane stejné jako dnes, jen projde vynucovací vrstvou.
  • Přeskočit vlastní policy u read-only číselníků, kde stačí oprávnění modulu s pevným mapováním viewAny/create/update/delete.
  • Validaci tokenů/rout (např. podepsané odkazy moderátorského dotazníku {id}/{token}) — zůstává. Doplní se ale auth middleware (§6).
  • Doménová validační pravidla jako MustHaveAccountingDetails (datové pravidlo moderátora, ne rozhodnutí o přístupu).
  • Viditelnost sekcí/menu (canViewSection) — zůstává řízená oprávněními přes config/module.php; policies řídí akce, sidebar řídí navigaci.

Každá policy potřebuje Pest feature testy: pro každou ability ověřit (a) držitel oprávnění projde, (b) vlastník projde tam, kde to dává smysl, (c) nevlastník bez oprávnění je odmítnut, (d) superadmin projde přes Gate::before, (e) uživatel bez přístupu k trhu je odmítnut i s oprávněním. Pro sestavení aktérů použít factories + assignRole / givePermissionTo.


14. Aktualizace balíčku spatie/laravel-permission

Section titled “14. Aktualizace balíčku spatie/laravel-permission”

Současný stav: composer.json má constraint ^6.0, nainstalováno je 6.25.0. Nejnovější vydaná verze je 8.3.0 (3. 7. 2026) — jsme dvě major verze pozadu.

Proč to řešit teď: cílový model (§9–§13) přidá seedery rolí, Policies a nová oprávnění. Je výrazně levnější upgradovat předtím, než na starou verzi navrstvíme novou logiku, než potom.

Požadavekv7 / v8ProjektStav
PHP≥ 8.3>=8.4 (běží 8.5.7)
Laravel≥ 12^13.0
Podpora Laravel 13od verze 7.2.1

Pozn.: v7.0.0 původně vyžadovala PHP ^8.4, ve verzích 7.1.0 / 7.3.0 byl požadavek snížen zpět na 8.3.

Všechny breaking changes z oficiálního upgrade guide prověřené proti tomuto projektu:

Změna (v6 → v7)Dopad na projekt
Event třídy dostaly suffix Event (PermissionAttachedPermissionAttachedEvent)Žádný — projekt nemá listenery na Spatie eventy (a events_enabled je vypnuté)
Command třídy dostaly suffix Command (CacheResetCacheResetCommand)Žádný — projekt na ně nikde neodkazuje
ServiceProvider přešel na PackageServiceProviderŽádný — provider není rozšířený ani publikovaný
Zrušena podpora LumenŽádný
Odstraněno PermissionRegistrar::clearClassPermissions()Žádný — nepoužíváme
Přidané návratové typy v traitech HasRoles / HasPermissionsŽádnýApp\User používá HasRoles bez přepsaných metod
Z kontraktu Wildcard odstraněn __construct()Žádný — wildcard oprávnění nepoužíváme
Vlastní modely Role / PermissionŽádnýconfig/permission.php ukazuje přímo na Spatie modely
Změna (v7 → v8)Dopad na projekt
Kontrakty Role / Permission: findByName() a findOrCreate() přijímají BackedEnum|stringŽádný — neimplementujeme tyto kontrakty ve vlastních modelech

Závěr: upgrade nevyžaduje žádnou změnu aplikačního kódu. Práce leží v konfiguraci a v databázovém schématu.

Náš config má 132 řádků, aktuální vendor config 206. Chybí 8 klíčů:

display_role_in_exception, enable_wildcard_permission, events_enabled, register_octane_reset_listener, register_permission_check_method, team_resolver, teams, use_passport_client_credentials.

Soubor pochází z éry v5 a nikdy nebyl po upgradu na v6 re-publikován. Aplikace funguje, protože PermissionServiceProvider::register() volá mergeConfigFrom() — chybějící klíče nejvyšší úrovně se doplní z vendor defaultů. Konfigurace ale není explicitní a při upgradu se hůř kontroluje, co se skutečně mění.

Při re-publikaci je nutné znovu aplikovat naše úpravy — zejména table_names s prefixy sp_ (sp_roles, sp_permissions).

B. Chybějící unique index — přímá příčina duplicit z §6

Section titled “B. Chybějící unique index — přímá příčina duplicit z §6”

Migrační stub balíčku vytváří na obou tabulkách:

$table->unique(['name', 'guard_name']);

Naše tabulky chk_sp_roles a chk_sp_permissions mají pouze PRIMARY KEY (id) — unique index chybí. Tabulky nevznikly z migrace balíčku, ale z převodu z Entrustu, který tuto podmínku zahodil. To je přesně důvod, proč v DB existují dva řádky creator a dva superCreator a duplicitní oprávnění.

Deduplikace (Fáze 0) a doplnění indexu jsou tedy jedna pracovní položka — a musí proběhnout v tomto pořadí, jinak migrace přidávající index selže.

Pivotní tabulky (chk_model_has_roles, chk_model_has_permissions, chk_role_has_permissions) stubu odpovídají včetně primárních klíčů a foreign keys. ✅

Drobné odchylky s nízkou prioritou: id je int unsigned (stub používá bigIncrements) a tabulky mají charset utf8mb3 místo utf8mb4.

V database/migrations/ není create_permission_tables — schéma RBAC tabulek žije jen v database/schema/mysql-schema.sql (squashed dump). Krok 2 oficiálního postupu („porovnejte stuby s migracemi, které jste spustili“) tedy nemá co porovnávat; srovnání je nutné dělat proti schema dumpu, jak je uvedeno výše.

Zařadit jako Fázi 0a, tedy před úklid dat. Všechny příkazy běží v kontejneru fpm (viz CLAUDE.md).

  1. Baseline — ověřit, že testovací sada je zelená před jakoukoli změnou.
  2. Upgrade na v7:
    Terminal window
    docker-compose exec fpm composer require spatie/laravel-permission:^7.0 -W
    docker-compose exec fpm php artisan config:clear
    docker-compose exec fpm php artisan permission:cache-reset
  3. Re-publikovat config a znovu aplikovat naše úpravy (table_names!):
    Terminal window
    cp config/permission.php config/permission.php.bak
    docker-compose exec fpm php artisan vendor:publish \
    --provider="Spatie\Permission\PermissionServiceProvider" --tag="permission-config"
    Poté ručně vrátit sp_ prefixy a porovnat s .bak. (Po přechodu na PackageServiceProvider v v7 ověřit, že název tagu zůstal permission-config.)
  4. Spustit testy. Vzhledem k §14.2 se nečekají žádné pády — pokud nějaké nastanou, jde o nezdokumentovanou závislost, kterou je potřeba prozkoumat.
  5. Upgrade na v8:
    Terminal window
    docker-compose exec fpm composer require spatie/laravel-permission:^8.0 -W
    Jediná breaking change v8 se nás netýká (§14.2), proto je tento krok samostatný a rychlý — držet ho odděleně od v7 kvůli čistému rollbacku.
  6. Doplnit unique index — až po deduplikaci dat ve Fázi 0:
    $table->unique(['name', 'guard_name']);
    na sp_roles i sp_permissions.
  7. Aktualizovat composer.json na "spatie/laravel-permission": "^8.0".
  • Nízké. Nulová plocha dotyku v aplikačním kódu, obě major verze jsou podle autorů „modernizace bez velkých architektonických změn“.
  • Hlavní riziko není v balíčku, ale v re-publikaci configu — pokud se ztratí table_names s prefixem sp_, balíček začne hledat tabulky chk_roles / chk_permissions, které neexistují. Proto záloha a diff proti .bak.
  • Migrace přidávající unique index selže, pokud duplicity nejsou vyčištěné. Držet pořadí kroků.

Upgradovat spatie/laravel-permission z 6.25.0 na 8.x před úklidem dat. Nevyžaduje změny aplikačního kódu; hlavní práce je re-publikace configu.

  1. Sloučit duplicitní role creator / superCreator a duplicitní oprávnění; teprve poté přidat unikátní index (name, guard_name), který v našem schématu oproti migračnímu stubu balíčku chybí (§14.3 B) — a který duplicitám do budoucna zabrání.
  2. Smazat legacy Entrust tabulky a mrtvé seedery/příkazy.
  3. Odstranit mrtvou routu togglePerm a nefunkční ModuleController / obrazovku „Modul ↔ Oprávnění“.
  4. Opravit viewHorizon gate.
  5. Zrušit mrtvou větev superCorrector, nebo roli založit.
  6. Přejmenovat bypass na superadmin, admin převést na běžnou roli s explicitními právy.
  1. Migrace markets, user_markets, sloupce market_id na scope-citlivé entity (s backfillem na CZ).
  2. Trait Marketable + global scope; helper User::hasMarketAccess().

Fáze 2 — role oddělení + skutečné vynucování

Section titled “Fáze 2 — role oddělení + skutečné vynucování”
  1. Seeder cílových rolí (standardní + super) dle matice ze §10; migrace uživatelů ze starých rolí. Role se stávají balíky oprávnění, à la BusinessManagerRoleSeeder.
  2. Zavést Policies dle §13 (Tier 1 první), kombinující WHAT + WHERE. Napojit přes $this->authorize() / @can a registrovat modely z app/Modules explicitně.
  3. Postupně převádět kontroly z §5 z hasRole(...) na can(...).
  4. Opravit ModuleMiddleware (reálný abort(403)) — až nakonec, pod testy, protože dnes propouští vše a zapnutí začne požadavky odmítat.
  5. Doplnit auth/podpis i jako middleware na subdoménu moderator..
  6. Postavit UI pro správu rolí a oprávnění (náhrada mrtvého ModuleController), chráněné role.manage / permission.manage.

Akční oprávnění {modul}.view|create|update|delete|approve tam, kde to proces vyžaduje (finance, uživatelé, témata).


OblastSoubor
Super-admin bypass / registrace policiesapp/Providers/AuthServiceProvider.php
Policies (dnes prázdné)app/Policies/ (jen .gitkeep)
Cílové modely Tier 1app/Modules/Topic.php, app/Models/Question.php, app/Models/QuizzEvent.php, app/Modules/Result.php, app/Models/TeamReservation.php, app/User.php
Helpery oprávnění na uživateliapp/User.php (canView, canViewSection, canImpersonate)
Konfigurace modul → oprávněníconfig/module.php
Konfigurace Spatieconfig/permission.php
Vstupní gate do adminuapp/Http/Middleware/CanViewAdmin.php
Rozbitý gate modulůapp/Http/Middleware/ModuleMiddleware.php
Skupiny route middlewaruroutes/admin.php
Routy moderátora (bez auth middlewaru)routes/moderator.php
Seedery rolí/oprávněnídatabase/seeders/UserRoleCompanySeeder.php, database/seeders/BusinessManagerRoleSeeder.php
Migrace Entrust → Spatieapp/Console/Commands/MigratePermissionPackage.php
Mrtvé UI role–oprávněníapp/Http/Controllers/Admin/ModuleController.php, app/Module.php
Horizon gateapp/Providers/HorizonServiceProvider.php