Skip to content

Vyhodnocení oprávnění

Priorita vyhodnocení

Při každém permission checku probíhá vyhodnocení v tomto přesném pořadí:

┌─────────────────────────────────────────────────────────────┐
│                   CheckService.check()                       │
└──────────────────────────┬──────────────────────────────────┘

              ┌────────────▼────────────┐
              │  1. Je uživatel aktivní? │
              │   users.active = false   │
              └────────────┬────────────┘
                           │ NE → DENY (blokuje vše, i superadmin!)
                           │ ANO ↓
              ┌────────────▼─────────────────┐
              │  2. Je uživatel SuperAdmin?   │
              │   SELECT FROM super_admins    │
              └────────────┬─────────────────┘
                           │ ANO → ALLOW (bypass všeho)
                           │ NE ↓
              ┌────────────▼─────────────────────────────────┐
              │  3. Existuje resource-level override?         │
              │   SELECT FROM user_resource_permissions       │
              │   WHERE email=? AND app=? AND resourceType=?  │
              │   AND resourceId=? AND permission=?           │
              └────────────┬─────────────────────────────────┘
                           │ ANO → výsledek = override.granted (true/false)
                           │ NE ↓
              ┌────────────▼────────────────────────────────────┐
              │  4. Má uživatel roli s daným oprávněním?         │
              │   UserAppRole → RolePermission → Permission      │
              │   Highest access_level ze všech rolí             │
              └────────────┬────────────────────────────────────┘
                           │ ANO → ALLOW (s access_level)
                           │ NE ↓
                        DENY

Krok 1: Aktivita uživatele

Deaktivovaný uživatel je vždy zamítnut — bez výjimky. Ani superadmin nemůže obejít toto pravidlo při permission checku.

typescript
// Pseudokód
const user = await userRepo.findOne({ where: { email } });
if (user && !user.active) {
  return { allowed: false }; // HARD deny
}

Deaktivace

Deaktivace je nejsilnější nástroj. Deaktivovaný uživatel nemá žádná oprávnění, bez výjimky.

Krok 2: SuperAdmin bypass

Superadmin má přístup ke všem oprávněním ve všech aplikacích. Check okamžitě vrátí allowed: true.

typescript
const isSuperAdmin = await superAdminRepo.findOne({ where: { email } });
if (isSuperAdmin) {
  return { allowed: true, accessLevel: 'edit' };
}

Krok 3: Resource-level overrides

Overrides umožňují granulární přístup k specifickým zdrojům (dokumenty, projekty, záznamy).

Struktura

sql
user_resource_permissions:
  email         -- uživatel
  application_id
  resource_type  -- typ zdroje (např. "document", "project")
  resource_id    -- ID konkrétního záznamu
  permission_id  -- které oprávnění
  granted        -- true = povoleno, false = zamítnuto

Příklady

bash
# Uživatel nemá roli s přístupem k dokumentům obecně,
# ale sdílíme s ním konkrétní dokument:
PUT /api/users/uzivatel@firma.cz/apps/crm/resources/document/42/permissions
{
  "permissions": [
    { "permissionId": 5, "granted": true }   # grant
  ]
}

# Naopak — uživatel má roli s přístupem, ale tento projekt mu zakážeme:
PUT /api/users/uzivatel@firma.cz/apps/crm/resources/project/99/permissions
{
  "permissions": [
    { "permissionId": 3, "granted": false }  # explicit deny
  ]
}

Override přepíše role

Override má vždy přednost před tím, co přijde z rolí:

  • granted: true → ALLOW, i když role nic nedává
  • granted: false → DENY, i když role přístup dává

Check s resourceType a resourceId

bash
POST /api/check
{
  "email": "uzivatel@firma.cz",
  "app": "crm",
  "permission": "documents.read",
  "resourceType": "document",
  "resourceId": "42"
}

Pokud resourceType a resourceId nejsou uvedeny, override se nehledá (krok 3 se přeskočí).

Krok 4: Role permissions

Pokud není žádný override, prohledají se role uživatele:

sql
SELECT rp.access_level, p.type
FROM user_app_roles uar
JOIN role_permissions rp ON rp.role_id = uar.role_id
JOIN permissions p ON p.id = rp.permission_id
WHERE uar.email = ?
  AND uar.application_id = ?
  AND p.code = ?

Pokud má uživatel více rolí se stejným oprávněním, vyhraje nejvyšší access_level:

Role "viewer":  access_level = "view"
Role "editor":  access_level = "edit"
→ Výsledek:    access_level = "edit"  (edit > view)

Pořadí: edit > view > allowed > (denied)

Bulk check

bash
POST /api/check/bulk
{
  "email": "uzivatel@firma.cz",
  "app": "crm",
  "permissions": ["documents.read", "documents.edit", "users.manage"]
}

# Odpověď
{
  "data": {
    "documents.read": true,
    "documents.edit": false,
    "users.manage": false
  }
}

Každé oprávnění se vyhodnotí samostatně (paralelně). Cache funguje per-oprávnění.

Effective permissions

Vrátí kompletní seznam oprávnění uživatele v dané aplikaci:

bash
POST /api/check/effective
{
  "email": "uzivatel@firma.cz",
  "app": "crm"
}

# Odpověď
{
  "data": [
    {
      "code": "documents.read",
      "type": "binary",
      "groupName": "Dokumenty",
      "accessLevel": "allowed",
      "granted": true,
      "source": "role"          # "role", "override", "superadmin"
    },
    {
      "code": "documents.edit",
      "type": "tiered",
      "groupName": "Dokumenty",
      "accessLevel": "view",
      "granted": true,
      "source": "role"
    },
    {
      "code": "users.manage",
      "type": "tiered",
      "groupName": "Uživatelé",
      "accessLevel": null,
      "granted": false,
      "source": null
    }
  ]
}

Diagram access level priority

Binary permissions:
  allowed ──────────────────────── ALLOW
  (missing) ────────────────────── DENY

Tiered permissions:
  edit ─────────────────────────── ALLOW (edit)
                                   ALLOW (view) ← implikováno!
  view ─────────────────────────── ALLOW (view)
                                   DENY (edit)
  (missing) ────────────────────── DENY (edit + view)

Atrea User API — interní dokumentace