funifier-mcp 0.2.25 → 0.2.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor/rules/funifier.mdc +38 -41
- package/.github/copilot-instructions.md +38 -41
- package/AGENTS.md +56 -49
- package/README.md +40 -22
- package/datasource-funifier-docs/.coverage.json +326 -0
- package/datasource-funifier-docs/.validation.json +593 -0
- package/datasource-funifier-docs/knowledge/guides/aggregates.md +182 -70
- package/datasource-funifier-docs/knowledge/guides/database-access.md +174 -88
- package/datasource-funifier-docs/knowledge/guides/java-entities.md +294 -204
- package/datasource-funifier-docs/knowledge/guides/java-libraries.md +202 -226
- package/datasource-funifier-docs/knowledge/guides/java-managers.md +343 -265
- package/datasource-funifier-docs/knowledge/guides/trigger-examples.md +180 -236
- package/datasource-funifier-docs/knowledge/guides/triggers-guide.md +273 -191
- package/datasource-funifier-docs/knowledge/index.md +5 -2
- package/datasource-funifier-docs/knowledge/modules/achievement.md +1126 -28
- package/datasource-funifier-docs/knowledge/modules/action-log.md +469 -62
- package/datasource-funifier-docs/knowledge/modules/action.md +522 -70
- package/datasource-funifier-docs/knowledge/modules/auth.md +718 -69
- package/datasource-funifier-docs/knowledge/modules/avatar.md +483 -18
- package/datasource-funifier-docs/knowledge/modules/backup.md +603 -25
- package/datasource-funifier-docs/knowledge/modules/challenge.md +1048 -220
- package/datasource-funifier-docs/knowledge/modules/compact.md +469 -26
- package/datasource-funifier-docs/knowledge/modules/competition.md +811 -109
- package/datasource-funifier-docs/knowledge/modules/crossword.md +504 -28
- package/datasource-funifier-docs/knowledge/modules/csv-data.md +645 -20
- package/datasource-funifier-docs/knowledge/modules/custom-object.md +701 -36
- package/datasource-funifier-docs/knowledge/modules/database.md +730 -164
- package/datasource-funifier-docs/knowledge/modules/folder.md +1011 -77
- package/datasource-funifier-docs/knowledge/modules/kpi-formulas.md +410 -15
- package/datasource-funifier-docs/knowledge/modules/lastmile.md +568 -29
- package/datasource-funifier-docs/knowledge/modules/leaderboard.md +595 -126
- package/datasource-funifier-docs/knowledge/modules/level.md +536 -54
- package/datasource-funifier-docs/knowledge/modules/lottery.md +809 -76
- package/datasource-funifier-docs/knowledge/modules/marketplace.md +688 -17
- package/datasource-funifier-docs/knowledge/modules/mystery.md +662 -52
- package/datasource-funifier-docs/knowledge/modules/notification.md +564 -26
- package/datasource-funifier-docs/knowledge/modules/patterns.md +519 -814
- package/datasource-funifier-docs/knowledge/modules/player.md +773 -73
- package/datasource-funifier-docs/knowledge/modules/point.md +380 -83
- package/datasource-funifier-docs/knowledge/modules/public.md +508 -178
- package/datasource-funifier-docs/knowledge/modules/question.md +619 -99
- package/datasource-funifier-docs/knowledge/modules/quiz.md +565 -120
- package/datasource-funifier-docs/knowledge/modules/scheduler.md +1092 -39
- package/datasource-funifier-docs/knowledge/modules/security.md +674 -112
- package/datasource-funifier-docs/knowledge/modules/staging.md +742 -19
- package/datasource-funifier-docs/knowledge/modules/story.md +565 -29
- package/datasource-funifier-docs/knowledge/modules/studio-page.md +470 -144
- package/datasource-funifier-docs/knowledge/modules/swap.md +552 -84
- package/datasource-funifier-docs/knowledge/modules/team.md +563 -45
- package/datasource-funifier-docs/knowledge/modules/trigger.md +876 -134
- package/datasource-funifier-docs/knowledge/modules/upload.md +468 -95
- package/datasource-funifier-docs/knowledge/modules/virtual-good.md +510 -63
- package/datasource-funifier-docs/knowledge/modules/webhook.md +375 -28
- package/datasource-funifier-docs/knowledge/modules/websocket.md +459 -26
- package/datasource-funifier-docs/knowledge/modules/widget.md +613 -27
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +42 -1
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/init.test.js +74 -3
- package/dist/cli/init.test.js.map +1 -1
- package/dist/cli/persona.d.ts +3 -0
- package/dist/cli/persona.d.ts.map +1 -0
- package/dist/cli/persona.js +25 -0
- package/dist/cli/persona.js.map +1 -0
- package/dist/core/api-client.d.ts +21 -1
- package/dist/core/api-client.d.ts.map +1 -1
- package/dist/core/api-client.js +154 -1
- package/dist/core/api-client.js.map +1 -1
- package/dist/core/constants.d.ts +14 -0
- package/dist/core/constants.d.ts.map +1 -1
- package/dist/core/constants.js +14 -0
- package/dist/core/constants.js.map +1 -1
- package/dist/core/types/Folder.d.ts +16 -0
- package/dist/core/types/Folder.d.ts.map +1 -0
- package/dist/core/types/Folder.js +3 -0
- package/dist/core/types/Folder.js.map +1 -0
- package/dist/core/types/FolderContent.d.ts +10 -0
- package/dist/core/types/FolderContent.d.ts.map +1 -0
- package/dist/core/types/FolderContent.js +3 -0
- package/dist/core/types/FolderContent.js.map +1 -0
- package/dist/core/types/FolderContentType.d.ts +10 -0
- package/dist/core/types/FolderContentType.d.ts.map +1 -0
- package/dist/core/types/FolderContentType.js +3 -0
- package/dist/core/types/FolderContentType.js.map +1 -0
- package/dist/core/types/FolderLog.d.ts +11 -0
- package/dist/core/types/FolderLog.d.ts.map +1 -0
- package/dist/core/types/FolderLog.js +3 -0
- package/dist/core/types/FolderLog.js.map +1 -0
- package/dist/core/types/index.d.ts +4 -0
- package/dist/core/types/index.d.ts.map +1 -1
- package/dist/core/types/index.js +4 -0
- package/dist/core/types/index.js.map +1 -1
- package/dist/mcp/bundle.js +121 -87
- package/dist/mcp/check-update.d.ts +2 -0
- package/dist/mcp/check-update.d.ts.map +1 -0
- package/dist/mcp/check-update.js +44 -0
- package/dist/mcp/check-update.js.map +1 -0
- package/dist/mcp/index.js +5 -2
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/resources/documentation.d.ts +1 -1
- package/dist/mcp/resources/documentation.d.ts.map +1 -1
- package/dist/mcp/resources/documentation.js +39 -3
- package/dist/mcp/resources/documentation.js.map +1 -1
- package/dist/mcp/tools/_char-guard.js +1 -1
- package/dist/mcp/tools/_char-guard.js.map +1 -1
- package/dist/mcp/tools/_fetch-current.d.ts +1 -1
- package/dist/mcp/tools/_fetch-current.d.ts.map +1 -1
- package/dist/mcp/tools/_fetch-current.js +12 -0
- package/dist/mcp/tools/_fetch-current.js.map +1 -1
- package/dist/mcp/tools/connect.d.ts.map +1 -1
- package/dist/mcp/tools/connect.js +18 -8
- package/dist/mcp/tools/connect.js.map +1 -1
- package/dist/mcp/tools/database.d.ts.map +1 -1
- package/dist/mcp/tools/database.js +59 -47
- package/dist/mcp/tools/database.js.map +1 -1
- package/dist/mcp/tools/database.test.js +2 -2
- package/dist/mcp/tools/database.test.js.map +1 -1
- package/dist/mcp/tools/delete.d.ts.map +1 -1
- package/dist/mcp/tools/delete.js +33 -3
- package/dist/mcp/tools/delete.js.map +1 -1
- package/dist/mcp/tools/execute.d.ts.map +1 -1
- package/dist/mcp/tools/execute.js +20 -9
- package/dist/mcp/tools/execute.js.map +1 -1
- package/dist/mcp/tools/folder.d.ts +4 -0
- package/dist/mcp/tools/folder.d.ts.map +1 -0
- package/dist/mcp/tools/folder.js +68 -0
- package/dist/mcp/tools/folder.js.map +1 -0
- package/dist/mcp/tools/get.d.ts.map +1 -1
- package/dist/mcp/tools/get.js +16 -6
- package/dist/mcp/tools/get.js.map +1 -1
- package/dist/mcp/tools/index.d.ts +1 -1
- package/dist/mcp/tools/index.d.ts.map +1 -1
- package/dist/mcp/tools/index.js +5 -1
- package/dist/mcp/tools/index.js.map +1 -1
- package/dist/mcp/tools/list.d.ts.map +1 -1
- package/dist/mcp/tools/list.js +38 -14
- package/dist/mcp/tools/list.js.map +1 -1
- package/dist/mcp/tools/logs.d.ts.map +1 -1
- package/dist/mcp/tools/logs.js +15 -5
- package/dist/mcp/tools/logs.js.map +1 -1
- package/dist/mcp/tools/save.d.ts.map +1 -1
- package/dist/mcp/tools/save.js +26 -4
- package/dist/mcp/tools/save.js.map +1 -1
- package/dist/mcp/tools/save.test.js +192 -1
- package/dist/mcp/tools/save.test.js.map +1 -1
- package/dist/mcp/tools/search-docs.d.ts +3 -0
- package/dist/mcp/tools/search-docs.d.ts.map +1 -0
- package/dist/mcp/tools/search-docs.js +102 -0
- package/dist/mcp/tools/search-docs.js.map +1 -0
- package/package.json +6 -2
- package/skills/acquire-funifier-knowledge/SKILL.md +132 -0
- package/skills/acquire-funifier-knowledge/assets/templates/CONCERNS.md +25 -0
- package/skills/acquire-funifier-knowledge/assets/templates/CUSTOM_ENDPOINTS.md +24 -0
- package/skills/acquire-funifier-knowledge/assets/templates/CUSTOM_PAGES.md +24 -0
- package/skills/acquire-funifier-knowledge/assets/templates/GAME_MECHANICS.md +35 -0
- package/skills/acquire-funifier-knowledge/assets/templates/INTEGRATIONS.md +35 -0
- package/skills/acquire-funifier-knowledge/assets/templates/LEADERBOARDS.md +24 -0
- package/skills/acquire-funifier-knowledge/assets/templates/OVERVIEW.md +47 -0
- package/skills/acquire-funifier-knowledge/assets/templates/PLAYER_MODEL.md +31 -0
- package/skills/acquire-funifier-knowledge/assets/templates/SCHEDULERS.md +25 -0
- package/skills/acquire-funifier-knowledge/assets/templates/TECHNIQUES_AND_PATTERNS.md +26 -0
- package/skills/acquire-funifier-knowledge/assets/templates/TRIGGERS.md +27 -0
- package/skills/acquire-funifier-knowledge/references/funifier-inventory-checklist.md +81 -0
- package/skills/acquire-funifier-knowledge/references/game-techniques-taxonomy.md +62 -0
- package/skills/acquire-funifier-knowledge/references/mcp-call-patterns.md +118 -0
- package/skills/funifier/SKILL.md +88 -0
- package/skills/funifier/references/configure-security.md +96 -0
- package/skills/{funifier-create-action/SKILL.md → funifier/references/create-action.md} +0 -33
- package/skills/funifier/references/create-aggregate.md +144 -0
- package/skills/funifier/references/create-challenge.md +116 -0
- package/skills/funifier/references/create-competition.md +98 -0
- package/skills/funifier/references/create-crossword.md +574 -0
- package/skills/funifier/references/create-custom-object.md +91 -0
- package/skills/funifier/references/create-custom-page.md +135 -0
- package/skills/funifier/references/create-folder.md +104 -0
- package/skills/funifier/references/create-lastmile.md +643 -0
- package/skills/{funifier-create-leaderboard/SKILL.md → funifier/references/create-leaderboard.md} +0 -33
- package/skills/funifier/references/create-level.md +94 -0
- package/skills/funifier/references/create-lottery.md +913 -0
- package/skills/funifier/references/create-mystery.md +769 -0
- package/skills/funifier/references/create-notification.md +75 -0
- package/skills/{funifier-create-point/SKILL.md → funifier/references/create-point.md} +0 -33
- package/skills/funifier/references/create-quiz.md +98 -0
- package/skills/funifier/references/create-scheduler.md +141 -0
- package/skills/funifier/references/create-story.md +636 -0
- package/skills/funifier/references/create-swap.md +95 -0
- package/skills/{funifier-create-trigger/SKILL.md → funifier/references/create-trigger.md} +0 -33
- package/skills/funifier/references/create-virtual-good.md +96 -0
- package/skills/funifier/references/create-webhook.md +72 -0
- package/skills/funifier/references/create-websocket.md +71 -0
- package/skills/funifier/references/create-widget.md +76 -0
- package/skills/funifier/references/debug.md +87 -0
- package/skills/funifier/references/help.md +81 -0
- package/skills/funifier/references/implement-frontend.md +106 -0
- package/skills/funifier/references/import-csv.md +75 -0
- package/skills/funifier/references/manage-player.md +82 -0
- package/skills/funifier/references/manage-team.md +76 -0
- package/skills/funifier/references/upload-file.md +91 -0
- package/datasource-funifier-docs/.search-index.json +0 -17318
- package/datasource-funifier-docs/.skills-map.json +0 -73
- package/skills/funifier-create-aggregate/SKILL.md +0 -127
- package/skills/funifier-create-challenge/SKILL.md +0 -88
- package/skills/funifier-create-custom-page/SKILL.md +0 -127
- package/skills/funifier-create-level/SKILL.md +0 -87
- package/skills/funifier-create-quiz/SKILL.md +0 -87
- package/skills/funifier-create-scheduler/SKILL.md +0 -127
- package/skills/funifier-create-virtual-good/SKILL.md +0 -87
- package/skills/funifier-debug/SKILL.md +0 -92
- package/skills/funifier-help/SKILL.md +0 -86
- package/skills/funifier-implement-frontend/SKILL.md +0 -90
- package/skills/funifier-index/SKILL.md +0 -58
|
@@ -1,169 +1,731 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `security`
|
|
2
2
|
|
|
3
|
-
**Acesso Studio:**
|
|
4
|
-
**
|
|
3
|
+
**Acesso Studio:** Funifier Studio → Security (cadastro de **Apps** e **Roles**). Não há rota Studio dedicada confirmável no backend.
|
|
4
|
+
**API Endpoint:** `/v3/database/security/{_id}` — gerenciado pelo `DatabaseRest` genérico. **Não existe `SecurityRest`.** Endpoints correlatos: `/v3/auth/token`, `/v3/auth/module`.
|
|
5
|
+
**Coleção MongoDB:** `security`
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
> Engenharia reversa baseada no código de `funifier-service` (`com.funifier.engine.security.*`, `com.funifier.rest.v3.filter.SecurityFilter`, `com.funifier.engine.auth.*`, `com.funifier.rest.v3.rest.DatabaseRest`, `com.funifier.rest.v3.rest.AuthenticationRest`, `com.funifier.rest.v3.TokenUtil`, `com.funifier.rest.v3.AuthBean`). Onde a documentação anterior divergia do código, o código prevalece — as divergências estão sinalizadas ao longo do texto.
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"roles": [
|
|
12
|
-
{ "name": "public", "scope": "read_all", "timeout": "" },
|
|
13
|
-
{ "name": "player", "scope": "read_all, write_all, delete_all, database", "timeout": "" }
|
|
14
|
-
],
|
|
15
|
-
"apps": [
|
|
16
|
-
{ "name": "Nome do App", "app_secret": "GUID_GERADO", "scope": "read_all, write_all, delete_all, database" }
|
|
17
|
-
]
|
|
18
|
-
}
|
|
19
|
-
```
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 1. Visão Geral
|
|
20
12
|
|
|
21
|
-
|
|
13
|
+
A coleção `security` guarda **um documento de configuração de segurança por gamificação**. No Funifier cada gamificação tem seu próprio banco MongoDB, então a coleção `security` daquele banco contém — **por convenção** — um único documento. Essa unicidade **não é imposta pelo código**: a leitura usa `findOne()` sem filtro (`SecurityDaoMongo.find()`) e a escrita é um `save()` por `_id` (ver seção 2.1). Documentos extras podem coexistir e causar comportamento ambíguo.
|
|
22
14
|
|
|
23
|
-
|
|
15
|
+
O documento concentra:
|
|
24
16
|
|
|
25
|
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
17
|
+
- **`apps`** — aplicações servidoras com `app_secret`, `scope` e `whitelist` (autenticação Basic / `client_credentials`).
|
|
18
|
+
- **`roles`** — papéis de jogador com `scope`, `timeout` (validade do token) e `whitelist` (autenticação `password`).
|
|
19
|
+
- **`requirePassword`**, **`createPlayerIfDontExist`** — comportamentos de login de jogador.
|
|
20
|
+
- **`timeZone`** — fuso horário consumido por challenges/bônus/conquistas.
|
|
21
|
+
- **`inlineEditable`** — edição inline no Studio.
|
|
29
22
|
|
|
30
|
-
|
|
31
|
-
| Scope | Descrição |
|
|
32
|
-
|-------|-----------|
|
|
33
|
-
| `read_all` | Leitura em todas as coleções |
|
|
34
|
-
| `write_all` | Escrita em todas as coleções |
|
|
35
|
-
| `delete_all` | Exclusão em todas as coleções |
|
|
36
|
-
| `database` | **Obrigatório** para acessar `/v3/database`. Sem ele, POST retorna 201 mas não persiste |
|
|
23
|
+
O documento é **lido em tempo de request** por:
|
|
37
24
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
25
|
+
1. **`SecurityFilter`** — filtro de servlet que intercepta toda requisição `/v3/**` e decide passar ou retornar `401` (seção 2.2). Lê `roles` (role `public`) e `apps` (`scope`/`whitelist`).
|
|
26
|
+
2. **`AuthBean.getScope()`** — recalcula o scope efetivo para `/v3/database` (seção 4).
|
|
27
|
+
3. **`AuthenticationRest` (`/v3/auth/token`)** — emite tokens Bearer JWT a partir de `apps`/`roles` (seção 2.4).
|
|
28
|
+
4. **Managers de domínio** — `timeZone` (`ChallengeManager`, `LastMileManager`, `BonusManager`, `AchievementManager`), `inlineEditable` (`FrontController.isInline`, `InlineManager`), `createPlayerIfDontExist` (`AuthenticationManager.createIfDontExist`, usado na ingestão de action logs).
|
|
41
29
|
|
|
42
|
-
|
|
43
|
-
A palavra **`database`** deve estar literalmente no scope para que `/v3/database` funcione. Mesmo com `write_all` e `read_all`, sem `database` as operações falham silenciosamente.
|
|
30
|
+
> **Importante (divergência com a documentação anterior):** existe no código um método `AuthenticationManager.authenticate(auth_mode, player, app_secret, password, …)` com os modos `IMPLICIT`, `CREDENTIAL`, `LDAP`, `FACEBOOK`, `GOOGLE`, validação de `websites` e auto-criação de player no login. **Esse método não é chamado por nenhum endpoint** (código órfão — ver seção 2.6 e 7). A documentação anterior descrevia esse fluxo como se fosse o caminho vivo de autenticação de jogador; ele **não** é alcançável.
|
|
44
31
|
|
|
45
|
-
|
|
32
|
+
---
|
|
46
33
|
|
|
47
|
-
|
|
34
|
+
## 2. Arquitetura e Fluxos
|
|
48
35
|
|
|
49
|
-
###
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
36
|
+
### 2.1 Persistência — via `DatabaseRest` genérico (NÃO há full-replace)
|
|
37
|
+
|
|
38
|
+
Não existe `SecurityRest`. A entidade está registrada no enum de roteamento como `Entity.SECURITY("security", Security.class)` e é gravada/lida pela coleção genérica do `DatabaseRest`.
|
|
39
|
+
|
|
40
|
+
`SecurityManager.update()` e `SecurityDaoMongo.update()` (que fazem `c.remove(); c.save(security);` — apagar **toda** a coleção e reinserir) **são código morto no caminho Java**: o único `.update(security)` no projeto é a própria definição/duplicata comentada, e nenhum endpoint o invoca. Ressalva: o `grep` cobre apenas o código Java compilado — um script Groovy (trigger/auth module) que chame `manager.getSecurityManager().update(...)` **dispararia** o `c.remove() + c.save()` destrutivo (apaga toda a coleção `security`). Evite expor essa chamada em scripts.
|
|
41
|
+
|
|
42
|
+
A escrita real é a do `DatabaseRest`:
|
|
53
43
|
|
|
54
|
-
### Token Basic do App
|
|
55
44
|
```
|
|
56
|
-
|
|
45
|
+
[PUT /v3/database/security] → DatabaseRest.update()
|
|
46
|
+
1. valida scope contém "database" (Application.SCOPE_DATABASE) e collection.length > 3
|
|
47
|
+
2. se body não tem _id → gera Guid.newShortGuid() ← cria documento avulso!
|
|
48
|
+
3. TriggerManager.execute(..., "security", EVENT_BEFORE_UPDATE, player)
|
|
49
|
+
4. jongo.getCollection("security").save(o) ← upsert por _id (Jongo)
|
|
50
|
+
5. TriggerManager.execute(..., "security", EVENT_AFTER_UPDATE, player)
|
|
51
|
+
6. AuditManager.log("security", EVENT_UPDATE, authBean, o)
|
|
57
52
|
```
|
|
58
53
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
54
|
+
Consequências reais do `save(o)`:
|
|
55
|
+
|
|
56
|
+
- **É um upsert do documento inteiro pelo `_id`.** Campos omitidos no PUT **somem daquele documento** (é substituição do documento, não merge/patch). A advertência "PUT parcial apaga campos" continua válida — porém **o motivo não é** "apaga a coleção inteira".
|
|
57
|
+
- **NÃO apaga os demais documentos da coleção.** Se você gravar com um `_id` diferente, cria um **segundo** documento `security`. Como `find()` usa `findOne()` sem filtro, qualquer documento pode "vencer".
|
|
58
|
+
- **POST e PUT geram `_id` automático** quando ausente (`Guid.newShortGuid()`), o que produz documentos `security` órfãos.
|
|
59
|
+
|
|
60
|
+
Leitura interna (`SecurityDaoMongo.find()`):
|
|
61
|
+
|
|
62
|
+
```java
|
|
63
|
+
Security s = c.findOne().as(Security.class);
|
|
64
|
+
return s != null ? s : new Security(); // NUNCA retorna null
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
> `find()` **nunca retorna null** — se a coleção estiver vazia retorna `new Security()`, cujos defaults de campo são `requirePassword=false`, `createPlayerIfDontExist=true`, `timeZone=null`. (A documentação anterior afirmava retorno `null` — incorreto.)
|
|
68
|
+
|
|
69
|
+
### 2.2 Pipeline de autorização — `SecurityFilter.doFilter()`
|
|
70
|
+
|
|
71
|
+
Executado a cada requisição HTTP. Tipos de auth reconhecidos: `Basic`, `Account`, `Studio`, `Bearer`.
|
|
72
|
+
|
|
73
|
+
#### Fluxo de autorização — `SecurityFilter.doFilter`
|
|
74
|
+
|
|
75
|
+
```mermaid
|
|
76
|
+
flowchart TD
|
|
77
|
+
A[Request /v3/**] --> B{method == OPTIONS?}
|
|
78
|
+
B -- sim --> PASS[doFilter -> segue]
|
|
79
|
+
B -- nao --> C[apikey = AuthBean.getApiKey]
|
|
80
|
+
C --> D{apikey != null?}
|
|
81
|
+
D -- sim --> E[StatisticManager.newApiRequest]
|
|
82
|
+
E --> F{limite diario ok?}
|
|
83
|
+
F -- nao --> R401A[401 exceeded daily api requests]
|
|
84
|
+
F -- sim --> G
|
|
85
|
+
D -- nao --> G{path em whitelist publica?}
|
|
86
|
+
G -- sim --> PASS
|
|
87
|
+
G -- nao --> H{GET widget/global/system.global?}
|
|
88
|
+
H -- sim --> PASS
|
|
89
|
+
H -- nao --> I{tipo de Authorization}
|
|
90
|
+
I -- Basic publico --> J[scope = role public.scope]
|
|
91
|
+
I -- Basic c/ secret --> K[authBasic; scope=app.scope; whitelist=app.whitelist]
|
|
92
|
+
I -- Basic invalido --> ERR[errorMessage]
|
|
93
|
+
I -- Account --> L[authAccount]
|
|
94
|
+
I -- Studio --> M[authStudio]
|
|
95
|
+
I -- Bearer --> N[authBearer; scope=token.scope; whitelist=token.whitelist]
|
|
96
|
+
I -- nenhum --> ERR
|
|
97
|
+
J --> O{whitelist != null?}
|
|
98
|
+
K --> O
|
|
99
|
+
N --> O
|
|
100
|
+
O -- sim --> P{IP ou URL na whitelist?}
|
|
101
|
+
P -- nao --> R401B[401 Domain or IP is not authorized]
|
|
102
|
+
P -- sim --> Q
|
|
103
|
+
O -- nao --> Q{scope != null?}
|
|
104
|
+
Q -- sim --> S[avalia hierarquia de scope]
|
|
105
|
+
Q -- nao --> PASS
|
|
106
|
+
S --> T{permitido?}
|
|
107
|
+
T -- sim --> PASS
|
|
108
|
+
T -- nao --> ERR
|
|
109
|
+
ERR --> U[newBadRequest + 401 errorMessage]
|
|
62
110
|
```
|
|
63
111
|
|
|
64
|
-
|
|
112
|
+
Sequência exata:
|
|
65
113
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
114
|
+
```
|
|
115
|
+
1. OPTIONS → doFilter (preflight CORS), sem validação
|
|
116
|
+
2. apikey = AuthBean.getApiKey(Authorization)
|
|
117
|
+
3. entity = path sem "/v3/"; corta no primeiro "/"
|
|
118
|
+
entity_full = path sem "/v3/", com "/" trocado por "_"
|
|
119
|
+
4. Estatística: se apikey != null → SystemFactory.getStatisticManager(apikey).newApiRequest(method, entity)
|
|
120
|
+
→ retorno false → 401 "You have exceeded your gamification daily api requests limits"
|
|
121
|
+
5. Paths públicos (passa sem auth) — startsWith de:
|
|
122
|
+
/customer, /v3/pub, /v3/version, /v3/player/password,
|
|
123
|
+
/v3/system/user/password/code, /v3/auth/token, /v3/sso/saml,
|
|
124
|
+
/v3/package/marketplace/aggregate, /v3/package/marketplace/, /v3/package/aggregate,
|
|
125
|
+
/v3/system/template/request/aggregate, /v3/system/template/request_folder/aggregate,
|
|
126
|
+
/v3/system/ai/knowledge, /v3/system/package/aggregate, /v3/system/country/aggregate,
|
|
127
|
+
/v3/system/plan/aggregate, /v3/system/remote/ping, /v3/system/landing, /v3/system/blog,
|
|
128
|
+
/v3/technique, /v3/coredrive, /v3/inline,
|
|
129
|
+
/v3/crm/email/track/open, /v3/crm/email/oauth_callback, /v3/crm/calendar/oauth_callback,
|
|
130
|
+
/v3/crm/doc/oauth_callback, /v3/crm/doc/link, /v3/ad/proxy-image, /v3/account/register
|
|
131
|
+
6. GET /v3/widget | /v3/global | /v3/system/global → passa sem auth
|
|
132
|
+
7. Por tipo de token (Authorization "contém" a palavra):
|
|
133
|
+
- Basic público (só apiKey, sem secret) → authPublic() true → scope = role "public".scope (se a role existir)
|
|
134
|
+
- Basic com secret → authBasic() → se inválido errorMessage; se válido scope=app.scope, whitelist=app.whitelist
|
|
135
|
+
- Account → authAccount() (AccountManager.isAppSecretAllowed)
|
|
136
|
+
- Studio → authStudio() (TokenUtil.getValueStudio != null) — usa lib auth0 JWTVerifier
|
|
137
|
+
- Bearer → authBearer() (TokenUtil.getValue "apiKey" ou "account" != null); scope/whitelist do token
|
|
138
|
+
- nenhum → errorMessage "Need to inform a type of authentication. E.g. Basic, Studio or Bearer."
|
|
139
|
+
8. Se whitelist != null → isRequestWhiteListed() (seção 5); se não → 401 "Domain or IP is not authorized..."
|
|
140
|
+
9. Se scope != null → avalia hierarquia (2.3)
|
|
141
|
+
10. errorMessage != null → newBadRequest + 401 errorMessage
|
|
142
|
+
11. caso contrário → doFilter (segue)
|
|
71
143
|
```
|
|
72
144
|
|
|
73
|
-
|
|
145
|
+
Notas:
|
|
146
|
+
|
|
147
|
+
- **Account** com Authorization não tem token JWT; `AuthBean.getApiKey()` retorna `null` para tipo `Account` na versão estática (linha 45) — daí a estatística pode não ser registrada para Account.
|
|
148
|
+
- O `try/catch` global converte qualquer exceção (inclusive NPE) em `401` com `e.getMessage()`.
|
|
149
|
+
|
|
150
|
+
### 2.3 Avaliação hierárquica de scope (`SecurityFilter`)
|
|
151
|
+
|
|
74
152
|
```
|
|
75
|
-
|
|
76
|
-
|
|
153
|
+
scope = scope.replaceAll(" ", "") // remove TODOS os espaços
|
|
154
|
+
list = scope.split(",")
|
|
155
|
+
m = GET→"read" | POST→"write" | PUT→"write" | (outro)→method.toLowerCase() // DELETE→"delete"
|
|
156
|
+
|
|
157
|
+
allow_all = scope.indexOf(m + "_all") != -1 // ex.: read_all
|
|
158
|
+
allow_full = list.contains(m + "_" + entity_full) // ex.: delete_swap_metodo_123
|
|
159
|
+
// fallback só 2 níveis (apesar do comentário "menos n"):
|
|
160
|
+
allow_full_m1 = list.contains(m + "_" + <entity_full sem último _segmento> + "_all") // delete_swap_metodo_all
|
|
161
|
+
allow_full_m2 = list.contains(m + "_" + <dois segmentos a menos> + "_all") // delete_swap_all
|
|
162
|
+
|
|
163
|
+
PASSA se (allow_all || allow_full || allow_full_m1 || allow_full_m2)
|
|
77
164
|
```
|
|
78
|
-
Retorna: `{ "guid": "69ab3566607db81962b9686e" }`
|
|
79
165
|
|
|
80
|
-
|
|
166
|
+
#### Hierarquia de scope — exemplo `DELETE /v3/swap/metodo/123`
|
|
167
|
+
|
|
168
|
+
```mermaid
|
|
169
|
+
flowchart LR
|
|
170
|
+
A["entity_full = swap_metodo_123<br/>m = delete"] --> B{delete_all?}
|
|
171
|
+
B -- sim --> P[passa]
|
|
172
|
+
B -- nao --> C{delete_swap_metodo_123?}
|
|
173
|
+
C -- sim --> P
|
|
174
|
+
C -- nao --> D{delete_swap_metodo_all?}
|
|
175
|
+
D -- sim --> P
|
|
176
|
+
D -- nao --> E{delete_swap_all?}
|
|
177
|
+
E -- sim --> P
|
|
178
|
+
E -- nao --> X[401 sem permissao]
|
|
81
179
|
```
|
|
82
|
-
PUT https://service2.funifier.com/v3/database/security
|
|
83
|
-
Authorization: Bearer <studio_token>
|
|
84
|
-
Content-Type: application/json
|
|
85
180
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
181
|
+
Exceções (avaliadas só para `POST`, depois da hierarquia):
|
|
182
|
+
|
|
183
|
+
- `POST /v3/action/log` com scope contendo `write_actionlog` → passa mesmo sem `write_all`.
|
|
184
|
+
- `POST /v3/mobile/device` → passa sem verificação de scope.
|
|
185
|
+
|
|
186
|
+
Mensagem de negação: `"You dont have permission to {m} in {entity} endpoint, you need {m}_{entity_full} or {m}_all access to do it"`.
|
|
187
|
+
|
|
188
|
+
> **Camada dupla para `/v3/database`:** o `SecurityFilter` valida `read_all`/`write_all`/`delete_all` (ou os hierárquicos `*_database_all`) usando `entity = "database"`. **Além disso**, o próprio `DatabaseRest` exige que o scope contenha `database`. Logo, um app de servidor típico usa `read_all,write_all,delete_all,database`.
|
|
189
|
+
|
|
190
|
+
### 2.4 Emissão de token — `POST /v3/auth/token`
|
|
191
|
+
|
|
192
|
+
#### Emissão de token Bearer — `/v3/auth/token`
|
|
193
|
+
|
|
194
|
+
```mermaid
|
|
195
|
+
sequenceDiagram
|
|
196
|
+
participant Client
|
|
197
|
+
participant AuthenticationRest
|
|
198
|
+
participant AuthenticationManager
|
|
199
|
+
participant SecurityDaoMongo
|
|
200
|
+
participant TokenUtil
|
|
201
|
+
|
|
202
|
+
Client->>AuthenticationRest: POST /v3/auth/token (grant_type)
|
|
203
|
+
alt client_credentials
|
|
204
|
+
AuthenticationRest->>AuthenticationManager: isAppSecretAllowed(client_secret)
|
|
205
|
+
AuthenticationManager->>SecurityDaoMongo: isAppAllowed (count == 1?)
|
|
206
|
+
SecurityDaoMongo-->>AuthenticationManager: true/false
|
|
207
|
+
AuthenticationRest->>AuthenticationManager: findApplication(secret)
|
|
208
|
+
AuthenticationManager-->>AuthenticationRest: Application{scope, whitelist}
|
|
209
|
+
AuthenticationRest->>TokenUtil: generateToken(apiKey, secret, scope, 7 dias, whitelist)
|
|
210
|
+
TokenUtil-->>AuthenticationRest: JWT HS512+GZIP
|
|
211
|
+
AuthenticationRest-->>Client: {access_token, refresh_token, token_type, expires_in}
|
|
212
|
+
else password
|
|
213
|
+
AuthenticationRest->>AuthenticationManager: authenticateModules(req) (Groovy/PAM)
|
|
214
|
+
AuthenticationManager-->>AuthenticationRest: status 0|1|2
|
|
215
|
+
AuthenticationRest->>AuthenticationManager: authenticatePassword(player, password) (se status 0)
|
|
216
|
+
AuthenticationRest->>AuthenticationManager: findRoleCalculatedToPlayer(player)
|
|
217
|
+
AuthenticationManager-->>AuthenticationRest: Role{scope, timeout, whitelist}
|
|
218
|
+
AuthenticationRest->>TokenUtil: generateToken(apiKey, player, scope, role.timeout||7d)
|
|
219
|
+
AuthenticationRest-->>Client: {access_token, refresh_token, token_type, expires_in}
|
|
220
|
+
else refresh_token
|
|
221
|
+
AuthenticationRest->>TokenUtil: accessTokenWithRefreshToken(refresh)
|
|
222
|
+
AuthenticationRest-->>Client: novo access_token + refresh_token
|
|
223
|
+
end
|
|
91
224
|
```
|
|
92
225
|
|
|
93
|
-
|
|
226
|
+
Detalhes por `grant_type` (existem **dois** handlers `/v3/auth/token`: um `application/x-www-form-urlencoded`, outro `application/json`):
|
|
227
|
+
|
|
228
|
+
| grant_type | Validação | Expiração do access_token | Refresh token |
|
|
229
|
+
| --- | --- | --- | --- |
|
|
230
|
+
| `client_credentials` | `isAppSecretAllowed(secret)` → `findApplication` | **7 dias** (`TimeScale.DAY, 7`) | sim (form) / não (json) |
|
|
231
|
+
| `password` | auth modules (PAM) + `authenticatePassword` | `role.timeout` (ex.: `7d`) ou **7 dias** default | sim (form) / não (json) |
|
|
232
|
+
| `refresh_token` | re-emite a partir do refresh (1 ano) | reconstruída (app 7d / player role.timeout\|7d) | sim |
|
|
233
|
+
|
|
234
|
+
- O `expires_in` retornado é **timestamp Unix em milissegundos** (`expiration.getTime()`), e **não** segundos — diverge do padrão OAuth2.
|
|
235
|
+
- A variante `application/json` (`tokenJson`) **não** anexa `whitelist` ao token (usa overload sem whitelist) e **não** devolve `refresh_token`. A variante `application/x-www-form-urlencoded` anexa whitelist e devolve refresh token.
|
|
236
|
+
- `/v3/auth/token` está na whitelist de paths públicos do `SecurityFilter` (não exige autenticação prévia).
|
|
237
|
+
|
|
238
|
+
> **Atenção ao default de 30 dias:** o valor `60*60*24*30` (30 dias) existe **apenas** dentro do método órfão `authenticate(auth_mode, …)` (seção 2.6), que não é chamado. Os tokens emitidos de verdade por `/v3/auth/token` usam **7 dias** (app e player default) ou `role.timeout`.
|
|
239
|
+
|
|
240
|
+
### 2.5 Cálculo de role do jogador — `findRoleCalculatedToPlayer(player)`
|
|
94
241
|
|
|
95
|
-
### 4. Verificar
|
|
96
242
|
```
|
|
97
|
-
|
|
98
|
-
|
|
243
|
+
1. principal = coleção "principal".findOne({_id: player})
|
|
244
|
+
2. security = SecurityDaoMongo.find() // nunca null
|
|
245
|
+
3. se principal == null OU principal.roles vazio → role = security.getRole("player")
|
|
246
|
+
4. senão se security.getRole(player) != null → role individual por id do player
|
|
247
|
+
5. senão se principal.roles.size() == 1 → role = security.getRole(roles[0])
|
|
248
|
+
6. senão (size > 1) → MERGE:
|
|
249
|
+
- scope = concatenação de todos os scopes das roles, separados por ","
|
|
250
|
+
- timeout = o de MENOR data de expiração (sessão mais curta vence)
|
|
251
|
+
- whitelist: união sem duplicatas; se QUALQUER role tiver whitelist null → whitelist final = null (irrestrito)
|
|
252
|
+
- name = player
|
|
99
253
|
```
|
|
100
254
|
|
|
101
|
-
|
|
255
|
+
Observações de runtime:
|
|
256
|
+
|
|
257
|
+
- Se a role resolvida for `null` (ex.: passo 5 com nome de role inexistente em `security.roles`), o chamador (`TokenUtil`/`AuthenticationRest`) cai no default de scope `read_all,write_actionlog,write_upload` e expiração `+7d` — **mas** acessa `role.whitelist` em seguida, o que provoca **NPE** quando `role == null` no overload com whitelist. Edge case real.
|
|
258
|
+
- `DateUtil.fromKeyword("+" + role.timeout)`: um `timeout` igual a `""` (string vazia) gera `"+"`, que tende a produzir erro/`NPE` na geração do token. **Use `null`/omita o campo, nunca `""`.**
|
|
259
|
+
|
|
260
|
+
### 2.6 Código órfão (presente, mas NÃO acessível)
|
|
261
|
+
|
|
262
|
+
`AuthenticationManager.authenticate(String auth_mode, String player, String app_secret, String password, int expiration, String language, String host, String oauth_access_token)`:
|
|
263
|
+
|
|
264
|
+
- Implementa os modos `IMPLICIT` (valida `websites` via `isWebsiteAllowed`), `PASSWORD`, `CREDENTIAL` (valida `app_secret`), `LDAP` (**bloco inteiro comentado** — o `else if (AUTH_MODE_LDAP…)` casa mas não faz nada → `error` permanece null → "autentica" sem checar nada), `FACEBOOK`/`GOOGLE` (com `// TODO`, sem validação), default de expiração de **30 dias**, e auto-criação de player no login.
|
|
265
|
+
- **Nenhum endpoint chama esse método** (`grep` confirma: somente a versão `authenticate(playerId)` de 1 argumento é usada, em `StudioRest`). Portanto:
|
|
266
|
+
- **`websites` / `isWebsiteAllowed` não têm caminho vivo** (o único caller Java está dentro desse método órfão).
|
|
267
|
+
- **Facebook/Google (`AuthenticationFacebook`/`AuthenticationGoogle`) não são acionáveis** (instanciados só aqui).
|
|
268
|
+
- **LDAP não é acionável** e, mesmo se fosse, o bloco está comentado.
|
|
269
|
+
|
|
270
|
+
`AuthenticationManager.authenticate(String playerId)` (deprecated, 1 argumento) **é** usado por `StudioRest` para gerar um `access_token` opaco (shortGuid) gravado na coleção `authentication` — um mecanismo de sessão legado, distinto do JWT Bearer.
|
|
271
|
+
|
|
272
|
+
---
|
|
102
273
|
|
|
103
|
-
|
|
104
|
-
2. Selecionar a gamificação desejada (botão "Select")
|
|
105
|
-
3. Se gamificação nova, clicar "Começar do Zero" para inicializar
|
|
106
|
-
4. Clicar no nome da gamificação na sidebar (link para `/studio/gamification/me`)
|
|
107
|
-
5. Expandir seção "Mais"
|
|
108
|
-
6. Clicar "Change your gamification security settings"
|
|
109
|
-
7. Na página Security: botão "Apps" expande a seção de Apps, botão "Roles" expande Roles
|
|
274
|
+
## 3. Estrutura dos Objetos
|
|
110
275
|
|
|
111
|
-
|
|
276
|
+
### 3.1 `Security` — documento raiz (`com.funifier.engine.security.Security`)
|
|
277
|
+
|
|
278
|
+
| Campo | Tipo | Padrão | Obrigatório | Descrição |
|
|
279
|
+
| --- | --- | --- | --- | --- |
|
|
280
|
+
| `_id` | String | — | por convenção = apiKey | Mapeado por `@JsonProperty("_id")`. `find()` ignora o `_id` (usa `findOne()`). |
|
|
281
|
+
| `created_at` | Date | — | — | Data de criação (não preenchida pelo `DatabaseRest`). |
|
|
282
|
+
| `updated_at` | Date | — | — | Data de atualização (não preenchida pelo `DatabaseRest`). |
|
|
283
|
+
| `requirePassword` | boolean | `false` | — | `true` → valida BCrypt no login `password`. `false` → senha ignorada. |
|
|
284
|
+
| `createPlayerIfDontExist` | boolean | `true` | — | `true` → cria player automaticamente (ver 5). Default de campo é `true`. |
|
|
285
|
+
| `timeZone` | String | `null` | — | Fuso Java (ex.: `America/Sao_Paulo`). Lido por Challenge/LastMile/Bonus/Achievement managers. |
|
|
286
|
+
| `inlineEditable` | boolean | `false` | — | Habilita edição inline no Studio (`FrontController.isInline`, `InlineManager`). |
|
|
287
|
+
| `appIdFacebook` | String | — | — | Config OAuth Facebook. **Sem consumidor vivo de auth** (ver 2.6/7). |
|
|
288
|
+
| `appSecretFacebook` | String | — | — | idem. |
|
|
289
|
+
| `appIdTwitter` | String | — | — | Config Twitter. **Nunca referenciado no código.** |
|
|
290
|
+
| `appSecretTwitter` | String | — | — | idem. |
|
|
291
|
+
| `appIdGoogle` | String | — | — | Config OAuth Google. **Sem consumidor vivo de auth.** |
|
|
292
|
+
| `appSecretGoogle` | String | — | — | idem. |
|
|
293
|
+
| `apps` | List\<Application\> | `[]` | — | Aplicações registradas (autenticação Basic / client_credentials). |
|
|
294
|
+
| `roles` | List\<Role\> | `[]` | — | Papéis de jogador (autenticação password). |
|
|
295
|
+
| `websites` | List\<String\> | `[]` | — | Origens para IMPLICIT auth. **Sem consumidor vivo** — só usado no método órfão (2.6). |
|
|
296
|
+
| `runInSilentMode` | boolean | `false` | — | **Persistido, sem consumidor em runtime.** |
|
|
297
|
+
| `baselines` | List\<BaseLine\> | `[]` | — | **Persistido, sem consumidor em runtime.** |
|
|
298
|
+
| `homepage` | String | — | — | Página inicial da gamification no Studio (campo `public`; sem getter; uso pelo frontend). |
|
|
299
|
+
| `ldap` | LDAPConfig | — | — | Config LDAP. **Auth LDAP desabilitada/órfã** (2.6). |
|
|
300
|
+
|
|
301
|
+
**Diferença schema × runtime:**
|
|
302
|
+
|
|
303
|
+
- Campos `runInSilentMode`, `baselines`, `websites`, `ldap`, `appId*/appSecret*` (social) e `homepage` são **aceitos no PUT e devolvidos no GET** (round-trip via Jackson). A imprecisão da documentação anterior era dizer que são "removidos silenciosamente" — eles **persistem**; o que falta é **consumidor em runtime** (são inertes para a lógica do servidor).
|
|
304
|
+
- `created_at`/`updated_at` não são preenchidos automaticamente pelo caminho de escrita (`DatabaseRest`), apenas persistidos se vierem no body.
|
|
305
|
+
- `Role` tem `@JsonIgnoreProperties(ignoreUnknown=true)`; `Security` e `Application` **não** têm essa anotação.
|
|
306
|
+
|
|
307
|
+
### 3.2 `Application` — aplicação registrada (`Application.java`)
|
|
308
|
+
|
|
309
|
+
| Campo | Tipo | Padrão | Obrigatório | Descrição |
|
|
310
|
+
| --- | --- | --- | --- | --- |
|
|
311
|
+
| `name` | String | — | — | Nome descritivo. |
|
|
312
|
+
| `app_secret` | String | — | sim | Chave secreta (use um GUID, ex.: `/v3/util/guid/new`). |
|
|
313
|
+
| `scope` | String | — | — | Permissões separadas por vírgula. |
|
|
314
|
+
| `whitelist` | List\<String\> | `null` | — | URLs e IPs autorizados (verifica `Referer`/`Origin` e IP remoto). |
|
|
315
|
+
|
|
316
|
+
**Scopes (constantes literais de `Application.java`):**
|
|
317
|
+
|
|
318
|
+
| Constante | Valor | Significado |
|
|
319
|
+
| --- | --- | --- |
|
|
320
|
+
| `SCOPE_READ_ALL` | `read_all` | Leitura em todas as coleções. |
|
|
321
|
+
| `SCOPE_WRITE_ALL` | `write_all` | Escrita em todas as coleções. |
|
|
322
|
+
| `SCOPE_DELETE_ALL` | `delete_all` | Exclusão em todas as coleções. |
|
|
323
|
+
| `SCOPE_DATABASE` | `database` | **Obrigatório** para qualquer operação em `/v3/database`. |
|
|
324
|
+
| `SCOPE_READ_CRYPTED` | `read_encrypted_field_values` | Ler campos criptografados. |
|
|
325
|
+
| `SCOPE_READ_PLAYER_PASSWORD` | `read_encrypted_player_password` | Ler senha criptografada do player. |
|
|
326
|
+
| `SCOPE_CROSSDOMAIN` | `cross_domain` | Operações cross-account. |
|
|
327
|
+
| `SCOPE_WRITE_ACTIONLOG` | `write_actionlog` | `POST /v3/action/log` sem `write_all`. |
|
|
328
|
+
| `SCOPE_WRITE_UPLOAD` | `write_upload` | Upload sem `write_all`. |
|
|
329
|
+
|
|
330
|
+
> `write_own_actionlog` aparece **comentado** no código (`//public static final String SCOPE_WRITE_OWN_ACTIONLOG`) — não existe. Scopes hierárquicos (`{m}_{entidade}_all`) são suportados pela avaliação da seção 2.3 (até 2 níveis de fallback).
|
|
331
|
+
|
|
332
|
+
### 3.3 `Role` — papel de jogador (`Role.java`, `@JsonIgnoreProperties(ignoreUnknown=true)`)
|
|
333
|
+
|
|
334
|
+
| Campo | Tipo | Padrão | Obrigatório | Descrição |
|
|
335
|
+
| --- | --- | --- | --- | --- |
|
|
336
|
+
| `name` | String | — | — | Identificador (ex.: `public`, `player`, `admin`). |
|
|
337
|
+
| `timeout` | String | — | — | Validade do token (ex.: `7d`, `1h`, `30m`). Omitir → `+7d`. **Nunca `""`** (gera erro em `DateUtil.fromKeyword("+")`). |
|
|
338
|
+
| `scope` | String | — | — | Permissões separadas por vírgula. |
|
|
339
|
+
| `whitelist` | List\<String\> | `null` | — | URLs/IPs. `null` em alguma role no merge → irrestrito (2.5). |
|
|
340
|
+
|
|
341
|
+
Roles com semântica especial no código:
|
|
342
|
+
|
|
343
|
+
- **`public`** — usada pelo `SecurityFilter`/`AuthBean` quando o Basic traz só `apiKey` (sem secret). Se não existir, `scope` fica `null` → **nenhuma verificação de scope** é feita (passa em tudo nessa camada).
|
|
344
|
+
- **`player`** — fallback de `findRoleCalculatedToPlayer` quando o principal não tem roles.
|
|
345
|
+
|
|
346
|
+
### 3.4 `BaseLine` — janela temporal (`BaseLine.java`)
|
|
347
|
+
|
|
348
|
+
| Campo | Tipo | Descrição |
|
|
349
|
+
| --- | --- | --- |
|
|
350
|
+
| `start` | Date | Início. |
|
|
351
|
+
| `end` | Date | Fim. |
|
|
352
|
+
|
|
353
|
+
Campos package-private, sem getters, **sem consumidor em runtime**.
|
|
354
|
+
|
|
355
|
+
### 3.5 `LDAPConfig` — configuração LDAP (`auth/ldap/LDAPConfig.java`)
|
|
356
|
+
|
|
357
|
+
| Campo | Tipo | Descrição |
|
|
358
|
+
| --- | --- | --- |
|
|
359
|
+
| `active` | boolean | Ativar LDAP. |
|
|
360
|
+
| `host` | String | Host do servidor. |
|
|
361
|
+
| `port` | String | Porta (ex.: `389`). |
|
|
362
|
+
| `dc` | String | Domain Component. |
|
|
363
|
+
| `ou` | String | Organizational Unit. |
|
|
364
|
+
|
|
365
|
+
**LDAP está desabilitado/órfão.** O bloco que usaria `LDAPConfig.isActive()` está comentado dentro do método `authenticate()` órfão. Definir `ldap.active=true` não tem efeito em nenhum fluxo acessível.
|
|
366
|
+
|
|
367
|
+
### 3.6 Coleções relacionadas (separadas de `security`)
|
|
368
|
+
|
|
369
|
+
Configuradas/usadas no domínio de autenticação, mas em **coleções próprias** (registradas em `Entity`):
|
|
370
|
+
|
|
371
|
+
- **`auth_module`** (`AuthModule`) — módulos de autenticação **customizados em Groovy** (PAM stacking). Campos: `_id`, `name`, `script`, `relevance` (`required`/`requisite`/`sufficient`/`optional`), `order` (int), `created`, `updated`, `timeout` (Long, s). Gerenciados por `/v3/auth/module`. Consumidos por `/v3/auth/token` (grant `password`). Observação: a relevância `optional` é declarada mas **ignorada** em `authenticateModules` (só `required`/`requisite`/`sufficient` têm efeito).
|
|
372
|
+
- **`sso_saml`** (`SsoSaml`) — configuração SSO SAML 2.0 (SP/IdP, certificados, flags de assinatura). Endpoint público `/v3/sso/saml`.
|
|
373
|
+
- **`two_factor`** (`TwoFactor`) — configuração de 2FA.
|
|
374
|
+
|
|
375
|
+
---
|
|
376
|
+
|
|
377
|
+
## 4. Endpoints
|
|
378
|
+
|
|
379
|
+
> Toda a gestão de `security` usa o `DatabaseRest` genérico (`@Path("v3/database")`). Requer **scope contendo `database`** (verificado dentro do `DatabaseRest`) **e** aprovação prévia do `SecurityFilter` (tipicamente `read_all`/`write_all`/`delete_all`).
|
|
380
|
+
|
|
381
|
+
### `GET /v3/database/security/{_id}`
|
|
382
|
+
|
|
383
|
+
| Aspecto | Detalhe |
|
|
384
|
+
| --- | --- |
|
|
385
|
+
| Finalidade | Ler o documento `security` por `_id`. |
|
|
386
|
+
| Implementação | `DatabaseRest.find()` |
|
|
387
|
+
| Autenticação | Bearer/Studio com scope `database` (+ `read_all`). |
|
|
388
|
+
| Pré-condições | `collection.length > 3`, `id.length > 0`. |
|
|
389
|
+
|
|
390
|
+
**Query params:**
|
|
391
|
+
|
|
392
|
+
| Param | Tipo | Descrição |
|
|
393
|
+
| --- | --- | --- |
|
|
394
|
+
| `strict` | boolean | `true` → saída em BSON strict mode. |
|
|
395
|
+
|
|
396
|
+
**Comportamento real:** `c.findOne("{_id:#}", id).as(HashMap.class)`. Se não autorizado/parâmetros inválidos → retorna `null` com `200 OK`. `{_id}` igual a `me` resolve para o player do token.
|
|
112
397
|
|
|
113
|
-
## Token Basic da Gamificação (público)
|
|
114
|
-
Para acesso público sem App (signup, leitura):
|
|
115
398
|
```
|
|
116
|
-
|
|
399
|
+
GET /v3/database/security/540f14b00ffeeb8c2fe11767?strict=true
|
|
400
|
+
Authorization: Bearer <token com scope database,read_all>
|
|
117
401
|
```
|
|
118
|
-
Nota: dois-pontos no final, sem app_secret. Usa a role `public` se existir.
|
|
119
402
|
|
|
120
|
-
|
|
403
|
+
### `GET /v3/database/security?q={...}`
|
|
404
|
+
|
|
405
|
+
`DatabaseRest.findAll()` — agregação `{$match: {...}}`, paginada (`Range`, `max_results` default 100, `strict`). Útil para inspecionar/contar documentos `security` (detectar duplicatas).
|
|
406
|
+
|
|
407
|
+
### `PUT /v3/database/security`
|
|
408
|
+
|
|
409
|
+
| Aspecto | Detalhe |
|
|
410
|
+
| --- | --- |
|
|
411
|
+
| Finalidade | Criar/atualizar o documento `security`. |
|
|
412
|
+
| Implementação | `DatabaseRest.update()` → `jongo.save(o)` |
|
|
413
|
+
| Full replace ou patch | **Replace do documento inteiro pelo `_id`** (campos omitidos somem). **NÃO** apaga outros documentos. |
|
|
414
|
+
| Side effects | Dispara triggers `BEFORE/AFTER_UPDATE` na coleção `security` + registro no Audit. |
|
|
415
|
+
|
|
416
|
+
> Se o body **não** tiver `_id`, o servidor gera um `Guid.newShortGuid()` → cria documento `security` avulso. Sempre envie `_id`.
|
|
121
417
|
|
|
122
|
-
### Request (JSON body)
|
|
123
418
|
```json
|
|
419
|
+
PUT /v3/database/security
|
|
420
|
+
Authorization: Bearer <token com scope database,write_all>
|
|
421
|
+
Content-Type: application/json
|
|
422
|
+
|
|
124
423
|
{
|
|
125
|
-
"
|
|
126
|
-
"
|
|
127
|
-
"
|
|
128
|
-
"
|
|
424
|
+
"_id": "540f14b00ffeeb8c2fe11767",
|
|
425
|
+
"requirePassword": true,
|
|
426
|
+
"createPlayerIfDontExist": true,
|
|
427
|
+
"timeZone": "America/Sao_Paulo",
|
|
428
|
+
"apps": [
|
|
429
|
+
{ "name": "Backend App", "app_secret": "69ab3566607db81962b9686e", "scope": "read_all,write_all,delete_all,database" }
|
|
430
|
+
],
|
|
431
|
+
"roles": [
|
|
432
|
+
{ "name": "public", "scope": "read_all", "timeout": "7d" },
|
|
433
|
+
{ "name": "player", "scope": "read_all,write_actionlog,write_upload", "timeout": "30d" }
|
|
434
|
+
]
|
|
129
435
|
}
|
|
130
436
|
```
|
|
131
437
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
438
|
+
### `POST /v3/database/security`
|
|
439
|
+
|
|
440
|
+
`DatabaseRest.insert()` — `insert(o)` (não upsert). Gera `_id` se ausente. Dispara triggers `BEFORE/AFTER_CREATE` + Audit. Em coleção singleton, prefira `PUT`.
|
|
441
|
+
|
|
442
|
+
### `DELETE /v3/database/security?q={...}`
|
|
443
|
+
|
|
444
|
+
`DatabaseRest.delete()` — exige `q.length >= 3`. Coleta `_id`s afetados (se total ≤ 20.000), dispara triggers `BEFORE/AFTER_DELETE` + Audit, e remove por `{q}`. Apagar o documento `security` deixa `find()` retornando `new Security()` (defaults).
|
|
445
|
+
|
|
446
|
+
### `POST /v3/auth/token`
|
|
447
|
+
|
|
448
|
+
| Aspecto | Detalhe |
|
|
449
|
+
| --- | --- |
|
|
450
|
+
| Finalidade | Emitir access_token Bearer. |
|
|
451
|
+
| Autenticação | Pública (path liberado no `SecurityFilter`). |
|
|
452
|
+
| Content-Type | `application/x-www-form-urlencoded` **ou** `application/json`. |
|
|
453
|
+
|
|
454
|
+
Grant types e expirações: ver seção 2.4. Exemplo `client_credentials` (form):
|
|
455
|
+
|
|
456
|
+
```
|
|
457
|
+
POST /v3/auth/token
|
|
458
|
+
Content-Type: application/x-www-form-urlencoded
|
|
459
|
+
|
|
460
|
+
client_id=<apiKey>&client_secret=<app_secret>&grant_type=client_credentials
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
Resposta:
|
|
136
464
|
|
|
137
|
-
### Response
|
|
138
465
|
```json
|
|
139
466
|
{
|
|
140
|
-
"access_token": "
|
|
467
|
+
"access_token": "eyJhbGciOiJIUzUxMiIsImNhbGciOiJHWklQIn0...",
|
|
468
|
+
"refresh_token": "eyJhbGciOiJIUzUxMiIs...",
|
|
141
469
|
"token_type": "Bearer",
|
|
142
|
-
"expires_in": "
|
|
470
|
+
"expires_in": "1747862400000"
|
|
143
471
|
}
|
|
144
472
|
```
|
|
145
473
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
|
474
|
+
`expires_in` = timestamp Unix **em milissegundos**.
|
|
475
|
+
|
|
476
|
+
### `/v3/auth/module` (módulos Groovy)
|
|
477
|
+
|
|
478
|
+
| Método | Caminho | Função |
|
|
479
|
+
| --- | --- | --- |
|
|
480
|
+
| `GET` | `/v3/auth/module/{id}` | Buscar módulo por id. |
|
|
481
|
+
| `GET` | `/v3/auth/module` | Listar/filtrar (`q`, `published_min/max`, `orderby`, `reverse`, `max_results`). |
|
|
482
|
+
| `POST` | `/v3/auth/module` | Criar/atualizar — **compila** o script antes; se falhar retorna `status: ERROR`. |
|
|
483
|
+
| `DELETE` | `/v3/auth/module/{id}` | Excluir. |
|
|
484
|
+
|
|
485
|
+
---
|
|
486
|
+
|
|
487
|
+
## 5. Regras de Negócio
|
|
488
|
+
|
|
489
|
+
Regras que **estão no código mas não no schema**:
|
|
490
|
+
|
|
491
|
+
- **Scope `database` + `*_all` (camada dupla).** `/v3/database/*` precisa passar pelo `SecurityFilter` (`read_all`/`write_all`/`delete_all` ou hierárquicos) **e** pelo `DatabaseRest` (`scope` contém `database`). Token sem `database` recebe resposta vazia/`200` no `DatabaseRest`; sem `*_all` recebe `401` no filtro.
|
|
492
|
+
- **Basic público sem role `public`.** Se a role `public` não existir, `scope` fica `null` → o `SecurityFilter` **não** verifica scope → passa em todos os endpoints daquela camada.
|
|
493
|
+
- **`requirePassword` (com nuance).** Em `authenticatePassword(player, password)`: erro de senha só ocorre se `password != null && requirePassword && (user.password == null || !BCrypt.checkpw(password, user.password))`. Logo:
|
|
494
|
+
- `requirePassword=false` → senha ignorada (qualquer login passa).
|
|
495
|
+
- `requirePassword=true` **mas `password` ausente/null** no request → a condição curto-circuita e **passa sem checar senha**.
|
|
496
|
+
- `requirePassword=true` com senha em texto plano (não-BCrypt) → `BCrypt.checkpw` falha → "password incorrect for player".
|
|
497
|
+
- **`createPlayerIfDontExist` (caminho vivo = `createIfDontExist`).** Usado na **ingestão de action logs** (`ActionManager`/`ActionRest`) e em `PlayerRest`/`InGrupo`. Se `true` e o player não existe: verifica `Account.players_allowed`; se o limite foi atingido, **loga no console e NÃO cria** (sem exceção); senão cria o `Player`. (No `authenticatePassword`, o mesmo limite **lança** exceção.)
|
|
498
|
+
- **`isAppAllowed` exige `count == 1`.** `db.security.count({"apps.app_secret": <secret>}) == 1`. Se `0` → falso; se `≥ 2` (secret duplicado entre apps/documentos) → **também falso**. Combine com o risco de documentos `security` duplicados.
|
|
499
|
+
- **Reuso de token (player).** Antes de emitir, o fluxo busca uma `authentication` válida do player (`expires_in >= now`); se existir, **reusa** o mesmo access_token.
|
|
500
|
+
- **Singleton só por convenção.** `find()` faz `findOne()` sem filtro. Vários documentos `security` → resultado não determinístico.
|
|
501
|
+
- **`find()` nunca retorna null** → consumidores sempre recebem ao menos `new Security()` (defaults).
|
|
502
|
+
- **Triggers e Audit em escrita de `security`.** PUT/POST/DELETE em `security` disparam Triggers (Groovy) e gravam Audit como qualquer coleção. Surpreendente para um documento de configuração — uma trigger mal escrita em `security` afeta a configuração de segurança.
|
|
503
|
+
|
|
504
|
+
---
|
|
505
|
+
|
|
506
|
+
## 6. Comportamentos Automáticos
|
|
507
|
+
|
|
508
|
+
| Comportamento | Trigger | Impacto | Persistência |
|
|
509
|
+
| --- | --- | --- | --- |
|
|
510
|
+
| `_id` automático | PUT/POST em `security` sem `_id` | Cria documento com `Guid.newShortGuid()` | Sim (documento avulso) |
|
|
511
|
+
| Triggers `BEFORE/AFTER_CREATE/UPDATE/DELETE` | Qualquer escrita em `security` via `DatabaseRest` | Executa scripts Groovy registrados na coleção `security` | Depende da trigger |
|
|
512
|
+
| Audit log | Qualquer escrita em `security` | Registro em Audit (`EVENT_CREATE/UPDATE/DELETE`) | Sim |
|
|
513
|
+
| Criação automática de player | `createIfDontExist` (ingestão de action log, operações de player) com `createPlayerIfDontExist=true` | Cria `Player` se não existir e dentro de `players_allowed` | Sim |
|
|
514
|
+
| Reuso de access_token | `/v3/auth/token` (password) com sessão válida | Devolve token existente em vez de novo | Não (já existia) |
|
|
515
|
+
| `read_all`/limite diário | Cada request via `StatisticManager.newApiRequest` | Conta requisições; estoura limite → `401` | Em memória/estatística |
|
|
516
|
+
| Índice `apps.app_secret` | `ConnectionPool.createIndexes()` na inicialização | `createIndex({apps.app_secret: 1})` na coleção `security` | Sim (índice Mongo) |
|
|
517
|
+
|
|
518
|
+
#### Efeitos colaterais de uma escrita em `security`
|
|
519
|
+
|
|
520
|
+
```mermaid
|
|
521
|
+
flowchart LR
|
|
522
|
+
A[PUT/POST /v3/database/security] --> B{tem _id?}
|
|
523
|
+
B -- nao --> C[gera shortGuid]
|
|
524
|
+
B -- sim --> D[Trigger BEFORE_*]
|
|
525
|
+
C --> D
|
|
526
|
+
D --> E[jongo.save / insert]
|
|
527
|
+
E --> F[Trigger AFTER_*]
|
|
528
|
+
F --> G[Audit.log]
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
---
|
|
532
|
+
|
|
533
|
+
## 7. Suportado vs NÃO Suportado
|
|
534
|
+
|
|
535
|
+
### ✅ Suportado (caminho vivo no código)
|
|
536
|
+
|
|
537
|
+
- Autorização por `SecurityFilter` em toda req `/v3/**` (Basic, Account, Studio, Bearer).
|
|
538
|
+
- Basic com `app_secret` (validação `isAppAllowed`, `count == 1`).
|
|
539
|
+
- Basic público (`apiKey:` sem secret) → role `public`.
|
|
540
|
+
- Bearer JWT (HS512 + GZIP) com claims `apiKey`, `appSecret`, `player`, `scope`, `account`, `user`, `whitelist`.
|
|
541
|
+
- Studio token (lib auth0 `JWTVerifier`) e Account token.
|
|
542
|
+
- `/v3/auth/token`: `client_credentials`, `password`, `refresh_token`.
|
|
543
|
+
- Scope hierárquico (`{m}_{path}_all`, até 2 níveis de fallback) + exceções `write_actionlog`/`/v3/mobile/device`.
|
|
544
|
+
- Whitelist por IP/URL (App e Role) via `startsWith`.
|
|
545
|
+
- Merge de múltiplas roles e role individual por id de player.
|
|
546
|
+
- `requirePassword` (BCrypt) e `createPlayerIfDontExist` (via `createIfDontExist`).
|
|
547
|
+
- `timeZone` (challenges/bônus/conquistas) e `inlineEditable` (edição inline).
|
|
548
|
+
- Módulos de autenticação customizados Groovy (`/v3/auth/module`, PAM stacking) — relevâncias `required`/`requisite`/`sufficient`.
|
|
549
|
+
- Índice MongoDB em `apps.app_secret` criado na inicialização.
|
|
550
|
+
- SSO SAML (`/v3/sso/saml`, coleção `sso_saml`) e 2FA (coleção `two_factor`) — features próprias.
|
|
551
|
+
|
|
552
|
+
### ❌ NÃO Suportado / inerte / órfão
|
|
553
|
+
|
|
554
|
+
- **`AuthenticationManager.authenticate(auth_mode, …)` (9 args)** — não chamado por nenhum endpoint. Logo, **estão órfãos**:
|
|
555
|
+
- **Auth LDAP** — bloco comentado **e** método órfão; `LDAPConfig.active=true` sem efeito.
|
|
556
|
+
- **Auth Facebook/Google** — `AuthenticationFacebook`/`AuthenticationGoogle` instanciados só no método órfão; o ramo de auth tem `// TODO`.
|
|
557
|
+
- **Validação de `websites` (IMPLICIT)** — `isWebsiteAllowed` só é chamado dentro do método órfão; **não há caminho vivo** que valide `websites`.
|
|
558
|
+
- **Default de expiração de 30 dias** — existe só no método órfão.
|
|
559
|
+
- **Twitter** — `appIdTwitter`/`appSecretTwitter` nunca referenciados em lugar nenhum.
|
|
560
|
+
- **`runInSilentMode`, `baselines`** — persistidos e devolvidos, **sem consumidor em runtime**.
|
|
561
|
+
- **`homepage`** — persistido (uso pelo frontend Studio); sem consumidor server-side de auth.
|
|
562
|
+
- **`SecurityRest` dedicado** — não existe; gestão via `DatabaseRest`.
|
|
563
|
+
- **Full-replace de coleção** — `SecurityDaoMongo.update()`/`SecurityManager.update()` (remove+save) são **código morto no caminho Java** (escrita real = `save()` por `_id`). Só seriam executados se um script Groovy os chamasse explicitamente (ver 2.1).
|
|
564
|
+
- **PATCH/merge parcial** — PUT substitui o documento inteiro pelo `_id` (não faz merge).
|
|
565
|
+
- **Unicidade do documento** — não imposta pelo código.
|
|
566
|
+
- **Rotação/revogação de token** — JWT não é rastreado no banco; sem blacklist; expira só por prazo.
|
|
567
|
+
- **`expires_in` em segundos (OAuth2)** — retorna timestamp Unix em milissegundos.
|
|
568
|
+
- **Relevância `optional` de auth module** — declarada mas ignorada em `authenticateModules`.
|
|
569
|
+
|
|
570
|
+
---
|
|
571
|
+
|
|
572
|
+
## 8. Segurança e Permissões
|
|
573
|
+
|
|
574
|
+
- **Isolamento por gamificação:** cada gamificação tem seu próprio banco; a coleção `security` é escopada via `manager.getJongoConnection()` (por apiKey).
|
|
575
|
+
- **Chave JWT hardcoded e global:** `TokenUtil.SECRET_KEY = "d61de14ad05f48c5283bad0f5924071d87766219"` — fixa, HS512 + GZIP, **compartilhada por todas as gamificações**. A separação entre gamificações depende da validação do claim `apiKey` contra o endpoint no `SecurityFilter`, não da chave de assinatura. Studio usa a mesma chave via auth0 `JWTVerifier`.
|
|
576
|
+
- **Refresh token de 1 ano** sem acesso (`generateRefreshToken`), reidratável em access tokens — janela longa de re-emissão.
|
|
577
|
+
- **Whitelist por `startsWith`, sem CIDR/wildcard:** `isRequestWhiteListed` testa `url.startsWith(ref) || ip.startsWith(ref)`. IP é lido de `X-Forwarded-For`, `Proxy-Client-IP`, `WL-Proxy-Client-IP`, `HTTP_CLIENT_IP`, `HTTP_X_FORWARDED_FOR`, `RemoteAddr` (nessa ordem), **sem sanitização** — spoofável e sujeito a match parcial (ex.: `"10.0.0."` casa `10.0.0.7` e `10.0.0.99`). Se não houver `Referer`/`Origin` e a whitelist estiver setada, `url` é `null` → `NPE` → vira `401`.
|
|
578
|
+
- **Scope sem sanitização forte:** `scope.replaceAll(" ", "")` remove espaços e compara via `indexOf`/`contains`. Scope malformado pode gerar falso negativo (negar acesso), não falso positivo.
|
|
579
|
+
- **Senha do player:** só validada se `requirePassword=true`. Default `false` → qualquer senha aceita. Mesmo com `true`, request sem `password` passa (seção 5).
|
|
580
|
+
- **Triggers em `security`:** escritas disparam Groovy — superfície para lógica não-intencional sobre a própria config de segurança.
|
|
581
|
+
- **`/v3/auth/token` público:** força bruta de `client_secret`/senha não é bloqueada pela camada de filtro (só pelo limite diário de API agregado por gamificação).
|
|
582
|
+
|
|
583
|
+
---
|
|
584
|
+
|
|
585
|
+
## 9. Observabilidade e Troubleshooting
|
|
586
|
+
|
|
587
|
+
### Verificar a configuração atual
|
|
588
|
+
|
|
589
|
+
```
|
|
590
|
+
GET /v3/database/security/<apiKey>?strict=true
|
|
591
|
+
Authorization: Bearer <token com scope database,read_all>
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
### Detectar documentos `security` duplicados (causa de comportamento errático)
|
|
595
|
+
|
|
596
|
+
```
|
|
597
|
+
GET /v3/database/security/count # via DatabaseRest /count?collection=security
|
|
598
|
+
# ou
|
|
599
|
+
POST /v3/database/security/aggregate
|
|
600
|
+
[ { "$group": { "_id": null, "total": { "$sum": 1 } } } ]
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
Espera-se `total = 1`. `≥ 2` explica `isAppAllowed` falhando (exige `count == 1`) e `find()` não determinístico.
|
|
604
|
+
|
|
605
|
+
### Verificar app_secret cadastrado
|
|
606
|
+
|
|
607
|
+
```
|
|
608
|
+
GET /v3/database/security/<apiKey>?strict=true
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
Inspecione `apps[].app_secret`. Lembre: `isAppAllowed` exige **exatamente um** documento com aquele secret.
|
|
612
|
+
|
|
613
|
+
### Testar emissão de token
|
|
614
|
+
|
|
615
|
+
```
|
|
616
|
+
POST /v3/auth/token
|
|
617
|
+
Content-Type: application/x-www-form-urlencoded
|
|
618
|
+
|
|
619
|
+
client_id=<apiKey>&client_secret=<app_secret>&grant_type=client_credentials
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
### Auditoria de mudanças em `security`
|
|
623
|
+
|
|
624
|
+
Escritas geram Audit (`EVENT_CREATE/UPDATE/DELETE`) — consulte a coleção de auditoria para "quem mudou a security e quando".
|
|
625
|
+
|
|
626
|
+
### Erros comuns
|
|
627
|
+
|
|
628
|
+
| Erro | Causa provável |
|
|
629
|
+
| --- | --- |
|
|
630
|
+
| `api_key or app_secret is not valid or app_secret is not enabled in Funifier Studio.` | Basic com secret não cadastrado em `apps`, ou secret duplicado (`count != 1`). |
|
|
631
|
+
| `Need to inform a type of authentication. E.g. Basic, Studio or Bearer.` | Header `Authorization` ausente/prefixo desconhecido. |
|
|
632
|
+
| `Authorization token bearer is not valid.` | Bearer expirado/malformado/assinado com chave errada (sem `apiKey` e sem `account`). |
|
|
633
|
+
| `Studio token is invalid.` | Studio token inválido (`getValueStudio` retornou null). |
|
|
634
|
+
| `Domain or IP is not authorized. Ask the admin to add this in the gamification whitelist.` | App/Role com `whitelist` e origem/IP fora dela (ou ausência de `Referer`/`Origin`). |
|
|
635
|
+
| `You have exceeded your gamification daily api requests limits` | Limite diário da Account atingido. |
|
|
636
|
+
| `You dont have permission to {m} in {entity} endpoint, you need {m}_{entity_full} or {m}_all access to do it` | Scope sem `*_all`/`*_{path}` adequado. |
|
|
637
|
+
| Resposta vazia (`null`/`200`) em `/v3/database/security` | Scope sem `database`, ou `collection.length <= 3`/`id` ausente. |
|
|
638
|
+
| `auth_module_failed` | Auth module Groovy retornou status 2 (falha) com output. |
|
|
639
|
+
| `password incorrect for player` | `requirePassword=true` e BCrypt não confere (ou senha não-BCrypt). |
|
|
640
|
+
| NPE/erro ao gerar token de player | `role.timeout = ""` (use `null`/omita), ou role resolvida `null` no overload com whitelist. |
|
|
641
|
+
|
|
642
|
+
---
|
|
643
|
+
|
|
644
|
+
## 10. Exemplos Práticos
|
|
645
|
+
|
|
646
|
+
### Exemplo mínimo — um app de servidor + role player
|
|
151
647
|
|
|
152
|
-
### ⚠️ Role timeout NUNCA string vazia
|
|
153
648
|
```json
|
|
154
|
-
|
|
155
|
-
|
|
649
|
+
PUT /v3/database/security
|
|
650
|
+
Authorization: Bearer <token com scope database,write_all>
|
|
651
|
+
Content-Type: application/json
|
|
156
652
|
|
|
157
|
-
|
|
158
|
-
|
|
653
|
+
{
|
|
654
|
+
"_id": "540f14b00ffeeb8c2fe11767",
|
|
655
|
+
"createPlayerIfDontExist": true,
|
|
656
|
+
"requirePassword": false,
|
|
657
|
+
"apps": [
|
|
658
|
+
{ "name": "Server App", "app_secret": "a1b2c3d4e5f6a7b8c9d0e1f2", "scope": "read_all,write_all,delete_all,database" }
|
|
659
|
+
],
|
|
660
|
+
"roles": [
|
|
661
|
+
{ "name": "player", "scope": "read_all,write_actionlog,write_upload", "timeout": "7d" }
|
|
662
|
+
]
|
|
663
|
+
}
|
|
664
|
+
```
|
|
159
665
|
|
|
160
|
-
|
|
161
|
-
|
|
666
|
+
### Exemplo avançado — roles + whitelist + timezone + senha obrigatória
|
|
667
|
+
|
|
668
|
+
```json
|
|
669
|
+
{
|
|
670
|
+
"_id": "540f14b00ffeeb8c2fe11767",
|
|
671
|
+
"createPlayerIfDontExist": true,
|
|
672
|
+
"requirePassword": true,
|
|
673
|
+
"timeZone": "America/Sao_Paulo",
|
|
674
|
+
"inlineEditable": false,
|
|
675
|
+
"apps": [
|
|
676
|
+
{
|
|
677
|
+
"name": "Backend Server",
|
|
678
|
+
"app_secret": "a1b2c3d4e5f6a7b8c9d0e1f2",
|
|
679
|
+
"scope": "read_all,write_all,delete_all,database",
|
|
680
|
+
"whitelist": ["10.0.0.", "192.168.1."]
|
|
681
|
+
},
|
|
682
|
+
{
|
|
683
|
+
"name": "Frontend Read Only",
|
|
684
|
+
"app_secret": "f9e8d7c6b5a4f3e2d1c0b9a8",
|
|
685
|
+
"scope": "read_all,write_actionlog",
|
|
686
|
+
"whitelist": ["https://app.empresa.com"]
|
|
687
|
+
}
|
|
688
|
+
],
|
|
689
|
+
"roles": [
|
|
690
|
+
{ "name": "public", "scope": "read_all", "timeout": "1d" },
|
|
691
|
+
{ "name": "player", "scope": "read_all,write_actionlog,write_upload", "timeout": "30d" },
|
|
692
|
+
{ "name": "admin", "scope": "read_all,write_all,delete_all,database", "timeout": "8h" }
|
|
693
|
+
]
|
|
694
|
+
}
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
> A `whitelist` usa `startsWith` (sem CIDR). `"10.0.0."` cobre toda a faixa `10.0.0.x` por prefixo de string — confirme que isso é o pretendido.
|
|
698
|
+
|
|
699
|
+
### Anti-pattern — o que NÃO fazer
|
|
700
|
+
|
|
701
|
+
```json
|
|
702
|
+
{
|
|
703
|
+
"roles": [
|
|
704
|
+
{ "name": "player", "scope": "read_all,write_all", "timeout": "" }
|
|
705
|
+
]
|
|
706
|
+
}
|
|
162
707
|
```
|
|
163
708
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
709
|
+
Problemas:
|
|
710
|
+
|
|
711
|
+
1. **Sem `_id`** → o servidor gera um `Guid.newShortGuid()` e cria um **segundo** documento `security`. Como `find()` faz `findOne()` sem filtro, ele pode passar a "vencer" sobre o real, e `isAppAllowed` (que exige `count == 1`) pode quebrar.
|
|
712
|
+
2. **`timeout: ""`** → `DateUtil.fromKeyword("+")` → erro/NPE na emissão do token do player.
|
|
713
|
+
3. **PUT sem `apps`** (omitindo) → como o PUT substitui o documento inteiro pelo `_id`, **todos os apps somem** daquele documento. Sempre reenvie o documento completo.
|
|
714
|
+
4. **Sem scope `database` em nenhum app/role** → `/v3/database` retorna vazio.
|
|
715
|
+
|
|
716
|
+
---
|
|
717
|
+
|
|
718
|
+
## Checklist de Configuração
|
|
719
|
+
|
|
720
|
+
- [ ] `_id` preenchido com a API Key da gamificação (nunca deixar o servidor gerar).
|
|
721
|
+
- [ ] Exatamente **um** documento na coleção `security` (cheque com `aggregate $group/count`).
|
|
722
|
+
- [ ] Pelo menos um `app` com `app_secret` único (GUID) — secret duplicado quebra `isAppAllowed` (`count == 1`).
|
|
723
|
+
- [ ] Scope dos apps inclui `database` **e** `read_all`/`write_all`/`delete_all` para usar `/v3/database`.
|
|
724
|
+
- [ ] Role `player` com `timeout` não-vazio e diferente de `""` (use `null`/omita ou `7d`).
|
|
725
|
+
- [ ] Role `public` definida se acesso Basic público for necessário (senão Basic público passa sem scope).
|
|
726
|
+
- [ ] `timeZone` preenchido se challenges/bônus por hora/dia forem usados.
|
|
727
|
+
- [ ] `requirePassword` consciente: `false` (default) = sem validação de senha; `true` ainda passa se o request não enviar `password`.
|
|
728
|
+
- [ ] PUT sempre com o documento completo (campos omitidos são apagados daquele `_id`).
|
|
729
|
+
- [ ] `whitelist` com prefixos corretos (lembre: `startsWith`, sem CIDR/wildcard).
|
|
730
|
+
- [ ] Não confiar em LDAP/Facebook/Google/Twitter/`websites`/`baselines`/`runInSilentMode` — inertes ou órfãos no código atual.
|
|
731
|
+
- [ ] Triggers cadastradas na coleção `security` revisadas (escritas disparam Groovy + Audit).
|