> For AI agents: the complete documentation index is available at https://nhanbe.id.vn/llms.txt, the full documentation bundle is available at https://nhanbe.id.vn/llms-full.txt.

## Nội dung

1. Giải thích nguyên nhân user bị thiếu quyền khi thao tác trên AEM (mở trang, sửa content, upload asset, replicate/publish, ...).
2. Hiểu rõ ý nghĩa của wildcard và restriction (`""`, `*`, `/*`, `*/jcr:content*`, ...) trong `rep:glob`.
3. Cung cấp quy trình từng bước để troubleshooting lỗi phân quyền nhanh chóng.

# 1. Cơ chế phân quyền JCR & Oak

Trên AEM Author, dữ liệu như **Page, Asset, Component, Configuration và User** được lưu trữ dưới dạng **Node/Property trong JCR Repository**, được quản lý bởi Apache Jackrabbit Oak.

Cơ chế kiểm soát truy cập của JCR/Oak dựa trên **Access Control List (ACL)**. Mỗi node có thể có node con `rep:policy` (type `rep:ACL`, orderable) chứa các **Access Control Entry (ACE)** (node type `rep:GrantACE` / `rep:DenyACE`). Mỗi ACE gồm `rep:principalName`, `rep:privileges` và optional node con `rep:restrictions` (chứa các restriction như `rep:glob`, `rep:ntNames`, ...).
![ACL restriction](/others/permissions-101/acl-restriction.png)

Có thể hình dung quá trình authorization như sau:
![Authorization flow](/others/permissions-101/authorization-flow.png)

## Các thuật ngữ cốt lõi

### Principal

**Principal** là identity được Oak sử dụng để đánh giá quyền truy cập, thường là **User hoặc Group**.

Ví dụ:

```text
content-authors
dam-users
administrators
user123
```

Một user có thể là member của nhiều group. Khi Oak đánh giá quyền, các ACE áp dụng cho user và các group mà user thuộc về đều có thể tham gia vào quá trình authorization evaluation.

### Privilege

**Privilege** mô tả một hoặc một nhóm thao tác mà Principal có thể thực hiện trên một JCR item.

Ví dụ:

```text
jcr:read
jcr:modifyProperties
jcr:addChildNodes
jcr:removeNode
jcr:removeChildNodes
jcr:write
crx:replicate
```

Privilege có thể là:

- **Aggregate privilege**: bao gồm nhiều privilege khác.
- **Non-aggregate privilege**: privilege nguyên thủy.

Ví dụ:

```text
jcr:write
├── jcr:modifyProperties
├── jcr:addChildNodes
├── jcr:removeNode
└── jcr:removeChildNodes
```

Do đó, `jcr:write` không phải là một thao tác cụ thể mà là một aggregate privilege bao gồm nhiều quyền thao tác khác. Lưu ý: `jcr:write` **KHÔNG bao gồm** `jcr:nodeTypeManagement` — muốn tạo node với node type mới (vd tạo page/component mới) phải cấp thêm privilege này.

### ACE — Access Control Entry

**ACE** là một entry trong ACL, xác định một Principal được **Allow hoặc Deny** một hoặc nhiều Privilege tại một JCR path, có thể kèm theo Restriction.

Có thể biểu diễn một ACE như:

```text
ACE
├── Principal
├── Privilege(s)
├── Allow / Deny
└── Restriction(s)
```

Ví dụ:

```text
Principal   = content-authors
Path        = /content/my-site
Privilege   = jcr:read
Effect      = ALLOW
Restriction = rep:glob = "public/*"
```

Có thể hiểu:

> `content-authors` được phép đọc các item phù hợp với restriction `public/*` trong phạm vi của ACE này.

***

### Restriction

**Restriction** là một **điều kiện giới hạn phạm vi áp dụng của ACE**.

Restriction không phải là một Privilege và cũng không phải là Allow/Deny. Nó bổ sung điều kiện để xác định ACE áp dụng cho những item nào hoặc trong điều kiện nào.

Một restriction thường gặp trong AEM là:

```text
rep:glob
```

Ví dụ:

```text
rep:glob = "*/jcr:content*"
```

Có thể hiểu ở mức khái niệm:

> ACE chỉ áp dụng cho các item/path phù hợp với pattern được chỉ định bởi `rep:glob`.

Restriction được lưu dưới dạng property trong node con `rep:restrictions` của ACE. Ngoài `rep:glob`, Oak còn hỗ trợ `rep:ntNames`, `rep:prefixes`, `rep:itemNames`, `rep:globs` (multi-value, Oak 1.44+), `rep:subtrees` (Oak 1.44+).

***

### Allow / Deny

**Allow / Deny** xác định ACE đang **cho phép hoặc từ chối** các Privilege được khai báo.

Ví dụ:

```text
ALLOW
  Principal: content-authors
  Privilege: jcr:read

DENY
  Principal: content-authors
  Privilege: jcr:modifyProperties
```

`Allow/Deny` là **effect của ACE**, không phải Action.

### Permission

**Permission** nên được hiểu là **quyết định authorization cuối cùng** của Oak Security: một Principal có được phép thực hiện một thao tác tương ứng với required privilege trên một JCR item/path hay không.

Ví dụ:

```text
Principal:
    content-authors

Target:
    /content/my-site/page/jcr:content

Required privilege:
    jcr:modifyProperties

             │
             ▼

     Oak Security Evaluation
             │
             ▼

       Permission Decision
             │
        ┌────┴────┐
        ▼         ▼
     GRANTED    DENIED
```

Do đó, có thể ghi nhớ:

```text
Principal
    +
JCR Path / Item
    +
Required Privilege
    +
Applicable ACEs
       ├── Allow / Deny
       └── Restrictions
    ↓
Oak Security Evaluation
    ↓
Permission Decision
```

> \[!NOTE]
> **Permission không phải là một thành phần đơn lẻ của ACE.**
> ACE chứa thông tin về Principal, Privilege, Allow/Deny và Restriction. **Permission là kết quả của quá trình Oak Security đánh giá các ACE áp dụng cho một Principal và một JCR item/path để quyết định thao tác có được phép hay không.**

### Tóm tắt

| Khái niệm           | Trả lời câu hỏi                                    |
| ------------------- | -------------------------------------------------- |
| **Principal**       | **Ai?**                                            |
| **JCR Path / Item** | **Ở đâu / trên resource nào?**                     |
| **Privilege**       | **Được phép làm gì?**                              |
| **Allow / Deny**    | **Cho phép hay từ chối?**                          |
| **Restriction**     | **Trong điều kiện/phạm vi nào?**                   |
| **ACE**             | **Một rule authorization cụ thể**                  |
| **ACL**             | **Tập hợp các ACE**                                |
| **Permission**      | **Quyết định cuối cùng: được phép hay bị từ chối** |

Có thể nhớ ngắn gọn:

> **ACE là rule, ACL là tập hợp các rule, còn Permission là kết quả sau khi Oak đánh giá các rule đó.**

### Quy tắc đánh giá ACL (Oak Access Control Evaluation)

Khi user thao tác trên một JCR item, Oak thu thập các ACE khớp **principal set** (user + tất cả group của user) dọc theo cây từ target đi lên, sắp xếp chúng theo thứ tự ưu tiên, rồi lấy **ACE khớp đầu tiên** trong thứ tự đó làm kết quả:

```mermaid
flowchart TD
    A["User thực hiện thao tác trên JCR item"] --> B["Xác định privilege cần thiết<br/>(vd: jcr:modifyProperties)"]
    B --> C["Thu thập ACE khớp principal set<br/>(user + các group của user)"]
    C --> D["Sắp xếp theo thứ tự ưu tiên:<br/>1. ACE của user<br/>2. ACE ở node gần target nhất<br/>3. ACE ghi sau trong ACL"]
    D --> E{"ACE khớp đầu tiên<br/>(principal + privilege + restriction)?"}
    E -->|"ALLOW"| G["GRANTED"]
    E -->|"DENY"| H["DENIED"]
    E -->|"Không có ACE nào khớp"| H
```

Thứ tự ưu tiên (theo tài liệu chính thức Oak — _Default Permission Evaluation_):

1. **Mặc định DENY**: Nếu không có ACE nào khớp trên toàn bộ đường đi từ target lên tới root → từ chối. _(AEM docs: "If no permissions are defined for a page, then all actions are denied.")_
2. **ACE cụ thể hơn thắng (Most specific path wins)**: Oak đánh giá từ node đang bị tác động, đi ngược lên các node cha cho tới khi gặp ACE khớp đầu tiên — ACE ở node gần target nhất được xét trước ACE kế thừa từ node cha. Hệ quả quan trọng: **Deny ở node cha KHÔNG chặn được Allow ở node con cụ thể hơn**. Ví dụ: `deny jcr:read` tại `/content` + `allow jcr:read` tại `/content/public` → subtree `/content/public` vẫn đọc được.
3. **ACE của User thắng ACE của Group**: ACE gán trực tiếp cho user luôn được đánh giá trước ACE của các group mà user thuộc về, **bất kể thứ tự trong danh sách hay độ sâu của node** (_"user principals always take precedence over group principals"_). Ví dụ: user có `allow` tại `/home/user-a` trong khi group `everyone` có `deny` tại chính path đó → user vẫn có quyền.
4. **Trong cùng principal type + cùng path: ACE ghi SAU thắng (entry order)**: Các ACE trong một ACL được đánh giá theo thứ tự **ngược** với thứ tự được ghi vào danh sách — ACE ghi sau cùng có độ ưu tiên cao nhất, và ACE khớp đầu tiên trong thứ tự đánh giá quyết định kết quả. **KHÔNG có quy tắc "Deny thắng Allow"** trong Oak: một Deny chỉ thắng khi nó được ghi sau Allow cạnh tranh (hoặc là ACE của user).
5. **Restriction là một phần của điều kiện "khớp" ACE** (`rep:glob`, `rep:ntNames`, ...) — nếu restriction không khớp item đang xét, ACE đó bị bỏ qua và Oak tiếp tục tìm ACE khác; restriction không phải một bước đánh giá riêng sau khi đã quyết định allow/deny.
6. **Administrators không bị ràng buộc bởi ACL**: các principal admin (vd `admin`) luôn có toàn quyền trên toàn bộ repository, kể cả khi tồn tại ACE deny-everyone trên node.

> \[!WARNING]
> **Yếu tố quyết định là thứ tự đánh giá ACE, không phải Allow hay Deny.** Quan niệm "Deny luôn thắng Allow" là quy tắc của Jackrabbit 2.x / CRX 2 — **Oak đã bỏ quy tắc này**. Khi troubleshooting, hãy xem thứ tự thực tế của các ACE trong `rep:policy` (CRXDE → tab Access Control) hoặc trong file cấu hình Repoinit / Netcentric ACL Tool, đừng chỉ nhìn "có allow hay có deny".

***

## 2. Wildcard & Restriction Patterns (`rep:glob`)

`rep:glob` là restriction dùng để **giới hạn phạm vi tác dụng của ACE** xuống một tập item con bên dưới path được gán mà không cần tạo thêm ACE thủ công tại từng node con.
Các ví dụ dưới đây lấy ACE gán tại `/content/mysite`:

| Ký hiệu   | Ý nghĩa                                                           |
| --------- | ----------------------------------------------------------------- |
| **Trống** | Không có quyền(Deny) - user/group không có quyền này trên path đó |
| ✅         | Allow                                                             |

## 3. Tại sao User bị thiếu quyền? (Root Causes & Hidden Dependencies)

Dev và Support Engineer rất hay gặp tình trạng: _User được gán Allow trên path trang nhưng vẫn bị lỗi 403, không sửa được trang, hoặc bấm Replicate/Publish không có tác dụng._

Dưới đây là các nguyên nhân cốt lõi về **Dependency của AEM Architecture**:

```mermaid
flowchart LR
    subgraph "Page Authoring Dependencies"
        A["/content/mysite/en"] -->|Content Page| B["/conf/mysite"]
        A -->|Editable Template| C["/apps & /libs"]
        A -->|Assets & Images| D["/content/dam/mysite"]
        A -->|Tags| E["/content/cq:tags"]
        A -->|XF / CF| F["/content/experience-fragments"]
    end
```

### Nguyên nhân 1: Thiếu Read permission trên Path cha (Hierarchy Scope)

Để mở được path `/content/mysite/en/page-a`, Oak yêu cầu user phải có `jcr:read` trên node đó và trên các node tổ tiên (để traverse cây JCR). Nếu user bị `deny jcr:read` ở `/content` hoặc `/content/mysite` (và không có `allow` cụ thể hơn ở dưới), user sẽ không thể đi xuyên cây để tới trang con. (Nhớ quy tắc most-specific-wins: một `allow jcr:read` tại node con cụ thể hơn sẽ khôi phục quyền đọc ở chính node con đó.)

### Nguyên nhân 2: Thiếu Read trên các Path phụ thuộc (Hidden Dependencies)

Trang AEM không đứng một mình. Để render và chỉnh sửa 1 trang trên Author Instance, User bắt buộc phải có `jcr:read` trên:

1. **`/conf/...`**: Nơi chứa **Editable Templates**, Content Policies, Page Component Configurations. Nếu thiếu -> Trang báo lỗi "Error loading page" hoặc trắng trang.
2. **`/apps/...` & `/libs/...`**: Nơi chứa Client Libraries (JS/CSS), Component Dialogs, Granite UI icons. Nếu thiếu -> Dialog không mở được, UI bị hỏng.
3. **`/content/dam/...`**: Nơi chứa Hình ảnh, Video, Content Fragments được nhúng vào trang. Nếu thiếu -> Không thấy asset trong Asset Finder hoặc không chọn được Image.
4. **`/content/cq:tags/...`**: Nơi chứa taxonomy tags. Thiếu quyền -> Không chọn được Tag trong Page Properties.

### Nguyên nhân 3: Nhầm lẫn giữa Node Privileges (Modify vs Add/Remove Child)

- **Edit Property (VD: Sửa Title, Description)**: Cần Privilege `jcr:modifyProperties`.
- **Thêm Component mới vào Layout Container**: Cần `jcr:addChildNodes` + `jcr:nodeTypeManagement` (để tạo node component mới bên dưới `jcr:content/root/...` với node type riêng — `jcr:write` không bao gồm `jcr:nodeTypeManagement`).
- **Xóa / Di chuyển Component**: Cần `jcr:removeChildNodes` (trên node cha) hoặc `jcr:removeNode` (trên chính node bị xóa); di chuyển thêm `jcr:addChildNodes` + `jcr:nodeTypeManagement` ở đích.

### Nguyên nhân 4: Tại sao không Replicate / Publish được?

Để một Author bấm nút **Quick Publish** hoặc **Manage Publication** thành công:

1. User cần privilege `crx:replicate` trên path trang `/content/mysite/...`.
2. User cần `crx:replicate` trên cả **Assets phụ thuộc** `/content/dam/...` nếu chọn "Publish with referenced assets".
3. Việc ghi vào queue `/var/replication/...` do **replication agent** (chạy bằng service user / credentials riêng) đảm nhiệm — **user tác giả KHÔNG cần quyền trên `/var`**.
4. Nếu site dùng workflow publish (**Manage Publication** với tùy chọn "Request for Publish"), user cần thuộc nhóm được phép khởi tạo workflow (vd `workflow-users`) để bắt đầu workflow.

***

## 4. Ma trận Thao tác Authoring vs JCR Path & Privilege

Bảng tra cứu nhanh dành cho Support Engineers & Developers khi cấp quyền cho Group:

| Thao tác trên AEM Author UI                | JCR Path liên quan                                                           | Privileges tối thiểu cần gán                                                                              |
| ------------------------------------------ | ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| **Mở Sites Console & Xem danh sách trang** | `/content/mysite`                                                            | `jcr:read`                                                                                                |
| **Xem / Mở trang trong Page Editor**       | `/content/mysite`<br/>`/conf/mysite`<br/>`/apps`, `/libs`, `/etc.clientlibs` | `jcr:read`                                                                                                |
| **Sửa Page Properties (Title, Meta, ...)** | `/content/mysite`                                                            | `jcr:read`, `jcr:modifyProperties`                                                                        |
| **Thêm / Xóa Component vào trang**         | `/content/mysite`                                                            | `jcr:read`, `jcr:modifyProperties`, `jcr:addChildNodes`, `jcr:removeChildNodes`, `jcr:nodeTypeManagement` |
| **Tạo trang mới (Create Page)**            | `/content/mysite`                                                            | `jcr:read`, `jcr:addChildNodes`, `jcr:modifyProperties`, `jcr:nodeTypeManagement`                         |
| **Xóa trang (Delete Page)**                | `/content/mysite`                                                            | `jcr:removeChildNodes`, `jcr:removeNode`                                                                  |
| **Replicate / Publish trang**              | `/content/mysite`<br/>`/content/dam/mysite` _(nếu publish kèm Asset)_        | `crx:replicate`                                                                                           |
| **Upload & Quản lý Assets / DAM**          | `/content/dam/mysite`                                                        | `jcr:read`, `rep:write` (= `jcr:write` + `jcr:nodeTypeManagement`)                                        |
| **Publish Asset trong DAM**                | `/content/dam/mysite`                                                        | `crx:replicate`                                                                                           |
| **Sửa Editable Templates**                 | `/conf/mysite/settings/wcm/templates`                                        | `jcr:read`, `rep:write`                                                                                   |
| **Gán Tags trong Page Properties**         | `/content/cq:tags`                                                           | `jcr:read`                                                                                                |

***

## 5. Quy trình Troubleshooting dành cho Support Engineer & Dev

Khi nhận được Ticket báo lỗi _"User X không thể làm Y (mở trang/sửa trang/publish)"_, hãy thực hiện 5 bước sau:

```mermaid
flowchart TD
    S1["1. Kiểm tra danh sách Group của User"] --> S2["2. Kiểm tra Path Scope & Hierarchy"]
    S2 --> S3["3. Soi các ACE xung đột (Deny vs Allow, thứ tự, rep:glob)"]
    S3 --> S4["4. Kiểm tra Path phụ thuộc /conf, /dam, /tags"]
    S4 --> S5["5. Test thực tế qua Impersonate / ACL Checker"]
```

### Bước 1: Kiểm tra User thuộc những Group nào?

- Vào User Admin (`/libs/granite/security/content/admin.html` hoặc `/security/users.html`).
- Tra cứu User, xem danh sách `Groups` mà User tham gia. Lưu ý các Group kế thừa (Group lồng Group).

### Bước 2: Kiểm tra Phân quyền tại Path chính

- Kiểm tra xem Group của User có được cấp `jcr:read` từ root `/content` xuống target path hay chưa (nhớ quy tắc most-specific-wins: deny ở cha có thể bị allow ở con cụ thể hơn ghi đè).
- Thao tác Create/Edit/Publish đã có đúng Privilege tương ứng (xem Ma trận ở Section 4) chưa?

### Bước 3: Soi các ACE xung đột (Deny vs Allow, thứ tự, rep:glob)

- Liệt kê **toàn bộ ACE** áp dụng cho user và các group của user trên path đang lỗi (CRXDE → tab Access Control, hoặc đọc trực tiếp node `rep:policy`).
- Nhớ 3 quy tắc đánh giá: (1) ACE của **user thắng ACE của group**; (2) ACE **cụ thể hơn** (node gần target) thắng ACE kế thừa từ cha; (3) cùng principal + cùng path thì **ACE ghi sau thắng** — **không có quy tắc "Deny thắng Allow"**. Một Deny "có vẻ" không có tác dụng thường do bị Allow ghi sau (hoặc Allow của user) thắng.
- Kiểm tra `rep:glob`: glob được nối vào path của node chứa ACE. Ví dụ `rep:glob = ""` chỉ ảnh hưởng đúng node đó (không phải con cháu); `rep:glob = "*/jcr:content*"` chỉ phủ các node `jcr:content` ở mọi cấp — nếu Allow chỉ phủ pattern này thì user sửa được nội dung trong trang nhưng **không** tạo/đổi tên/xóa được `cq:Page` (đúng thiết kế, không phải lỗi phân quyền).

### Bước 4: Kiểm tra Path phụ thuộc (Dependencies)

- Nếu User mở trang bị lỗi 403 / 500 hoặc không thấy Template: Kiểm tra quyền `jcr:read` trên `/conf/<site-name>`.
- Nếu không thấy Asset: Kiểm tra quyền `jcr:read` trên `/content/dam/<site-name>`.

### Bước 5: Verify bằng công cụ

1. **User Impersonation**: Trong AEM Author, admin có thể Impersonate thành User đó để tái hiện lỗi trên giao diện UI.
2. **CRXDE Lite Access Control Tab**: Chọn node -> Tab `Access Control` ở góc dưới phải để xem danh sách ACE thực tế đang có hiệu lực (kể cả thứ tự ACE).
3. **Repository initialiser (Repoinit) / Netcentric ACL Tool**: Đảm bảo file cấu hình ACL trong codebase (Repoinit / YAML) khớp với môi trường đang chạy. Lưu ý thứ tự câu lệnh `allow`/`deny` trong file quyết định thứ tự ghi ACE — do đó quyết định ACE nào thắng khi xung đột.

***

## Tài liệu tham khảo thêm

- [ACL and Permission Deep-Dive](https://nhanbe.id.vn/others/acl-and-permissions.md) - Tài liệu nâng cao về Repoinit, Netcentric ACL Tool, Service Users và Programmatic ACLs trong Java/Groovy.
- [Oak Restrictions (rep:glob) — Official Documentation](https://jackrabbit.apache.org/oak/docs/security/authorization/restriction.html)
- [Oak Default Permission Evaluation — Official Documentation](https://jackrabbit.apache.org/oak/docs/security/permission/evaluation.html)
- [AEM Security & Oak Access Control Official Documentation](https://experienceleague.adobe.com/docs/experience-manager-65/administering/security/security.html)
