2. Налаштування бандлу

Версія 19.5 додана 2024/05/16 11:21 автором Ashterix

Всі налаштування бандла знаходяться в файлі config/packages/ufo_json_rpc.yaml.

Є можливість налаштувати параметри захисту API та деякі параметри формату даних, що віддається при запиті документації.

Блок security

Наразі єдиним механізмом захисту доступу до вашого API є встановлення перевірки ключа доступу (api_token).

Параметр protected_methods

Цей параметр приймає масив назв http методів, які мають бути захищені.

За замовченням ввімкнут захист лише для методу POST. Ви можете:

  • вказати пустий масив [] щоб зробити API повністю відкритим
config/packages/ufo_json_rpc.yaml
1
2
3
ufo_json_rpc:
   security:
       protected_methods: []
  • вказати додатково захист для методу GET, що зробить запит документації недоступним без токену в заголовках запиту
config/packages/ufo_json_rpc.yaml
1
2
3
ufo_json_rpc:
   security:
       protected_methods: ['GET', 'POST']

Якщо ви захищаєте ваш API через protected_methods, вам необхідно налаштувати токени, по яким буде відкритий доступ.

Перш за все, треба визначитися з назвою токену.

Параметр token_key_in_header

Компонент RpcSecurity буде шукати в заголовках запиту специфічний ключ, який ви можете встановити в налаштуваннях пакету, значення за замовченням token_key_in_header: 'Ufo-RPC-Token', ви можете встановити будь-яке інше значення яке відповідає наступним вимогам.


Вимоги до формування заголовків протоколу HTTP

Параметр clients_tokens

Тепер слід вказати масив клієнтськіх токенів, які будуть мати доступ до API.

Тут є певна варіативність.

Токени в параметрах

НЕ РЕКОМЕНДОВАНО!!!

Цей підхід допускається лише для локального тестування API

Є можливість прописати токени хардкодом прямо в файлі налаштувань.

Це погано з позиції безпеки, якщо код зберігається в публічному репозиторію, то до цього токену буде мати доступ кожен.

config/packages/ufo_json_rpc.yaml
1
2
3
4
5
6
7
ufo_json_rpc:
   security:
       protected_methods: ['GET', 'POST']
       token_key_in_header: 'Ufo-RPC-Token'
       clients_tokens:
            - 'ClientTokenExample'  # hardcoded token example. Importantly!!! Replace or delete it!
            

Токени в змінних оточення

Це найбільш доцільний механізм у разі, якщо ви розробляєте сервіс для розподіленого бекенду, що написаний на SOA (Сервіс-Орієнтована Архітектура). Зазвичай, в такому випадку, вам треба відкрити доступ до апі одному або обмеженій кількості клієнтських додатків і оновлення ключів не буде відбуватися занадто часто.

В такому випадку можна прописати токени в змінних оточення (файл .env.local під час локальної розробки). Цей механізм достатньо безпечний з боку збереження доступів.

.env.local
1
2
TOKEN_FOR_APP_1=9363074966579432364f8b73b3318f71
TOKEN_FOR_APP_2=456fg87g8h98jmnb8675r4445n8up365
config/packages/ufo_json_rpc.yaml
1
2
3
4
5
6
7
ufo_json_rpc:
   security:
       protected_methods: ['GET', 'POST']
       token_key_in_header: 'Ufo-RPC-Token'
       clients_tokens:
            - '%env(resolve:TOKEN_FOR_APP_1)%'   # token example from .env.local
            - '%env(resolve:TOKEN_FOR_APP_2)%'   # token example from .env.local

Токени для користувача

Припускаю, що у вас може виникнути потреба зробити персональні ключі для користувачів вашого додатку, можливо ви захочете впровадити ліміти або інші обмеження.
В такому випадку вам не потрібно вказувати перелік токенів в конфігах, ви можете зберігати їх в базі даних або іншому місці згідно вашій бізнес-логіки. Єдина вимога, у вас має бути сервіс, який вміє перевіряти чи існує наданий токен. 

Для того, щоб JsonRpcServer міг використовувати вашу логіку, доведеться реалізувати власний клас, що реалізує інтерфейс Ufo\JsonRpcBundle\Security\Interfaces\ITokenValidator

Приклад власного валідатора токенів

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

namespace App\Services\RpcSecurity;

use App\Services\UserService;
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
use Ufo\JsonRpcBundle\Security\Interfaces\ITokenValidator;
use Ufo\RpcError\RpcInvalidTokenException;

class UserTokenValidator implements ITokenValidator
{

   public function __construct(protected UserService $userService) {}

   public function isValid(string $token): true
   {
       try {
           $this->userService->getUserByToken($token);
           return true;
        } catch (UserNotFoundException $e) {
           throw new RpcInvalidTokenException(previous: $e);
        }
    }
}

ВАЖЛИВО!!!
Метод isValid має повертати true якщо токен існує і валідний, або викидати Ufo\RpcError\RpcInvalidTokenException в іншому разі.

Після цього вам потрібно в файлі config/services.yaml прописати що класи, що мають залежність від інтерфейса ITokenValidator мають приймати ваш новий клас.

config/services.yaml
1
2
3
4
5
6
7
8
9
10
parameters:
  # some parameters list
  # ...

services:
  # some services list
  # ...

   Ufo\JsonRpcBundle\Security\Interfaces\ITokenValidator:
       class: App\Services\RpcSecurity\UserTokenValidator

Блок async

Цей блок для налаштування асинхронного транспорту.

Додайте параметр rpc_async який містить рядок у форматі DSN. Цей рядок є конфігурацією Symfony Messenger, він вказує на асинхронний транспорт по якому RPC Server буде очікувати вхідні запити якщо у вас запущений консюмер (php bin/console messenger:consume rpc_async). Для більш детального розуміння цього процесу читайте документацію Symfony Messenge.

config/packages/ufo_json_rpc.yaml
1
2
3
4
ufo_json_rpc:
   async:
       rpc_async: '%env(resolve:RPC_TRANSPORT_DSN)%'

Блок docs

Це блок який налаштовує генерацію документації коли ви робите GET запит на RPC Server 

Секція response

Містить налаштування, що відповідають за вміст відповіді.

Параметр key_for_methods

Цей параметр дозволяє вказати назву ключа у відповіді в якій буде віддаватися масив доступних сервісів.

Значення за замовченням methods. Може мати будь-яке значення типу рядок.

config/packages/ufo_json_rpc.yaml
1
2
3
ufo_json_rpc:
   docs:
       key_for_methods: methods
config/packages/ufo_json_rpc.yaml
1
2
3
ufo_json_rpc:
   docs:
       key_for_methods: services
config/packages/ufo_json_rpc.yaml
1
2
3
ufo_json_rpc:
   docs:
       key_for_methods: some_custom_key

Параметр async_dsn_info

Відповідає за відображення в документації інформації про асинхронний транспорт.

config/packages/ufo_json_rpc.yaml
1
2
ufo_json_rpc:
   async_dsn_info: true # or false

Приклад документації 

GET: /api
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
   "envelope": "JSON-RPC-2.0/UFO-RPC-6",
   "contentType": "application/json",
   "description": "",
   "transport": {
       "sync": {
           "scheme": "http",
           "host": "example.com",
           "path": "/api",
           "method": "POST"
        },
       "async": {
           "scheme": "amqp",
           "user": "{user}",
           "pass": "{pass}",
           "host": "async_rabbit",
           "port": 5672,
           "path": "/%2f/json-rpc"
        }
    },
   "methods": {
       ...
    }
}

Не переймайтеся щодо безпеки ваших авторизаційних даних. що містяться в DSN.

Документатор побудований таким чином, що перед виводом інформації про DSN він видаляє дані про користувача і його пароль, а також інші секретні дані, як то токени, секретні ключі, тощо.

Шаблон, по якому відбувається захист /([\w\d_]*(?:secret|access|token|key)[_\w]*)=((?:\w|\d)+(?=&?))/.

Приклад: 

RPC_TRANSPORT_DSN=https://sqs.eu-west-3.amazonaws.com/123456789012/messages?access_key=AKIAIOSFODNN7EXAMPLE&secret_key=j17M97ffSVoKI0briFoo9a
1
2
3
4
5
6
7
8
{
   "async": {
       "scheme": "https",
       "host": "sqs.eu-west-3.amazonaws.com",
       "path": "/123456789012/messages",
       "query": "access_key={access_key}&secret_key={secret_key}"
    }
}

Параметр validations

Відповідає за відображення в документації методів додаткових блоків, що вказують на вимоги до валідації даних.

Наразі цей блок має два можливих налаштування:

  • json_schema: <bool>
  • symfony_asserts: <bool>

У всіх опцій в цьому параметрі значення за замовченням false, тобто ці блоки не будуть відображатися в документації.
Якщо ви потребуєте якийсь з цих блоків інформації при запиті документації, то встановіть значення в true.

config/packages/ufo_json_rpc.yaml
1
2
3
4
ufo_json_rpc:
   docs:
       json_schema: true
       symfony_asserts: true 

Приклад документації 

В цьому прикладі я видалив вміст обʼєктів symfony_assertions для спрощення прикладу.
Детальніше про валідацію методів дивись сторінку Валідація процедур

GET: /api
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
{
   "envelope": "JSON-RPC-2.0/UFO-RPC-6",
   "contentType": "application/json",
   "description": "",
   "transport": {
       "sync": {
           "scheme": "https",
           "host": "example.com",
           "path": "/api",
           "method": "POST"
        }
    },
   "methods": {
       "getUserNameByUuid": {
           "name": "getUserNameByUuid",
           "description": "Get username by id",
           "parameters": {
               "userId": {
                   "type": "string",
                   "name": "userId",
                   "description": "User id in uuid format",
                   "optional": false
                }
            },
           "returns": "string",
           "responseFormat": "string",
           "json_schema": {
               "$schema": "http://json-schema.org/draft-07/schema#",
               "type": "object",
               "properties": {
                   "userId": {
                       "type": "string"
                    }
                },
               "required": [
                   "userId"
                ]
            },
           "symfony_assertions": {
               "userId": [
                    {
                       "class": "Symfony\\Component\\Validator\\Constraints\\Uuid",
                       "context": {}
                    }
                ]
            }
        },
       "sendEmail": {
           "name": "sendEmail",
           "description": "Send mail",
           "parameters": {
               "email": {
                   "type": "string",
                   "name": "email",
                   "description": "",
                   "optional": false
                },
               "subject": {
                   "type": "string",
                   "name": "subject",
                   "description": "",
                   "optional": false
                },
               "text": {
                   "type": "string",
                   "name": "text",
                   "description": "",
                   "optional": false
                }
            },
           "returns": "boolean",
           "responseFormat": "boolean",
           "json_schema": {
               "$schema": "http://json-schema.org/draft-07/schema#",
               "type": "object",
               "properties": {
                   "email": {
                       "type": "string",
                       "format": "email"
                    },
                   "subject": {
                       "type": "string",
                       "minLength": 1,
                       "maxLength": 100
                    },
                   "text": {
                       "type": "string",
                       "minLength": 10
                    }
                },
               "required": [
                   "email",
                   "subject",
                   "text"
                ]
            },
           "symfony_assertions": {
               "email": [
                    {
                       "class": "Symfony\\Component\\Validator\\Constraints\\Email",
                       "context": {}
                    }
                ],
               "subject": [
                    {
                       "class": "Symfony\\Component\\Validator\\Constraints\\NotBlank",
                       "context": {}
                    },
                    {
                       "class": "Symfony\\Component\\Validator\\Constraints\\Length",
                       "context": {}
                    }
                ],
               "text": [
                    {
                       "class": "Symfony\\Component\\Validator\\Constraints\\NotBlank",
                       "context": {}
                    },
                    {
                       "class": "Symfony\\Component\\Validator\\Constraints\\Length",
                       "context": {}
                    }
                ]
            }
        }
    }
}