Last modified by Ashterix on 2024/07/11 10:08

Show last authors
1 {{box cssClass="floatinginfobox" width="400px" title="**Contents**"}}
2 {{toc/}}
3 {{/box}}
4
5 {{error}}
6 **WARNING!!! **This version of the documentation is out of date.
7 In JsonRpcBundle version 7, the configuration has undergone significant changes and is not backwards compatible with version 6.
8 {{/error}}
9
10 (% class="wikigeneratedid" %)
11 All bundle settings are located in the file {{code language="none"}}config/packages/ufo_json_rpc.yaml{{/code}}.
12
13 (% class="wikigeneratedid" %)
14 It is possible to configure parameters for API security and some data format parameters that are returned when requesting documentation.
15
16 = The {{code language="none"}}security{{/code}} block =
17
18 Currently, the only mechanism for securing access to your API is setting up an access key check (api_token).
19
20 == Parameter {{code language="none"}}protected_methods{{/code}} ==
21
22 (% class="wikigeneratedid" %)
23 This parameter takes an array of HTTP method names that should be protected.
24
25 (% class="wikigeneratedid" %)
26 By default, protection is enabled only for the POST method. You can:
27
28 * specify an empty array {{code language="none"}}[]{{/code}} to make the API completely open
29
30 {{code language="yaml" layout="LINENUMBERS" title="config/packages/ufo_json_rpc.yaml"}}
31 ufo_json_rpc:
32 security:
33 protected_methods: []
34 {{/code}}
35
36 * additionally protect the GET method, making the documentation request inaccessible without a token in the request headers
37
38 {{code language="yaml" layout="LINENUMBERS" title="config/packages/ufo_json_rpc.yaml"}}
39 ufo_json_rpc:
40 security:
41 protected_methods: ['GET', 'POST']
42 {{/code}}
43
44 (% id="cke_bm_164641S" style="display:none" %) (%%)If you are protecting your API through {{code language="none"}}protected_methods{{/code}}, you need to set up the tokens that will grant access.
45
46 First of all, you need to decide on the name of the token.
47
48 == Parameter {{code language="none"}}token_key_in_header{{/code}} ==
49
50 The {{code language="none"}}RpcSecurity{{/code}} component will look for a specific key in the request headers, which you can set in the package settings, the default value is {{code language="none"}}token_key_in_header: 'Ufo-RPC-Token'{{/code}}, you can set any other value that meets the following requirements.
51
52 {{spoiler title=" Requirements for forming HTTP protocol headers"}}
53 Requirements for HTTP header names are not strictly regulated regarding capitalization since HTTP headers are case-insensitive. However, there are some common practices and standards that are usually followed for better readability and consistency:
54
55 - Capitalization: Typically, HTTP header names are written using CamelCase, where each word starts with a capital letter, e.g., Content-Type, User-Agent, Accept-Encoding. This does not affect the technical processing of headers but makes them easier to read.
56 - Uniqueness: Each header must have a unique name within the context of a single HTTP request or response. It is not permissible to use the same names for different headers in the same request or response.
57 - Special headers: There are headers that are specifically used for controlling caching behavior (Cache-Control), security (Strict-Transport-Security), authentication (Authorization), etc.
58 - RFC Standards: Header requirements are regulated by RFC documents that define standards for Internet protocols. For example, common headers and their use are described in RFC 7231.
59 {{/spoiler}}
60
61
62
63
64 == Parameter {{code language="none"}}clients_tokens{{/code}} ==
65
66 Now you need to specify an array of client tokens that will have access to the API.
67
68 There is some variability here.
69
70 === Tokens in parameters ===
71
72 (% class="box errormessage" %)
73 (((
74 **NOT RECOMMENDED!!!**
75 \\This approach is only permissible for local API testing
76 )))
77
78 It is possible to hardcode tokens directly in the settings file.
79
80 This is bad from a security standpoint, as if the code is stored in a public repository, everyone will have access to this token.
81
82 {{code language="yaml" layout="LINENUMBERS" title="config/packages/ufo_json_rpc.yaml"}}
83 ufo_json_rpc:
84 security:
85 protected_methods: ['GET', 'POST']
86 token_key_in_header: 'Ufo-RPC-Token'
87 clients_tokens:
88 - 'ClientTokenExample' # hardcoded token example. Importantly!!! Replace or delete it!
89
90 {{/code}}
91
92 === Tokens in environment variables ===
93
94 This is the most appropriate mechanism if you are developing a service for a distributed backend written in SOA (Service-Oriented Architecture). Typically, in such a case, you need to open access to the API to one or a limited number of client applications, and key updates will not occur too frequently.
95
96 In this case, you can write tokens in environment variables (file {{code language="none"}}.env.local{{/code}} during local development). This mechanism is secure enough in terms of access storage.
97
98 (((
99 {{code language="ini" layout="LINENUMBERS" title=".env.local"}}
100 TOKEN_FOR_APP_1=9363074966579432364f8b73b3318f71
101 TOKEN_FOR_APP_2=456fg87g8h98jmnb8675r4445n8up365
102 {{/code}}
103
104 {{code language="yaml" layout="LINENUMBERS" title="config/packages/ufo_json_rpc.yaml"}}
105 ufo_json_rpc:
106 security:
107 protected_methods: ['GET', 'POST']
108 token_key_in_header: 'Ufo-RPC-Token'
109 clients_tokens:
110 - '%env(resolve:TOKEN_FOR_APP_1)%' # token example from .env.local
111 - '%env(resolve:TOKEN_FOR_APP_2)%' # token example from .env.local
112 {{/code}}
113 )))
114
115 === User Tokens ===
116
117 Assuming that you might need to make personal keys for the users of your app, possibly you would want to implement limits or other restrictions.
118 In such a case, you do not need to list the tokens in the configs, you can store them in a database or another place according to your business logic. The only requirement is that you have a service that can verify whether a given token exists.
119
120 In order for the JsonRpcServer to use your logic, you will have to implement your own class that implements the interface {{code language="none"}}Ufo\JsonRpcBundle\Security\Interfaces\ITokenValidator{{/code}}
121
122 {{code language="php" layout="LINENUMBERS" title="==== Example of a custom token validator ===="}}
123 <?php
124
125 namespace App\Services\RpcSecurity;
126
127 use App\Services\UserService;
128 use Symfony\Component\Security\Core\Exception\UserNotFoundException;
129 use Ufo\JsonRpcBundle\Security\Interfaces\ITokenValidator;
130 use Ufo\RpcError\RpcInvalidTokenException;
131
132 class UserTokenValidator implements ITokenValidator
133 {
134
135 public function __construct(protected UserService $userService) {}
136
137 public function isValid(string $token): true
138 {
139 try {
140 this.userService.getUserByToken(token);
141 return true;
142 } catch (UserNotFoundException e) {
143 throw new RpcInvalidTokenException(previous: e);
144 }
145 }
146 }
147 {{/code}}
148
149 (% class="box warningmessage" %)
150 (((
151 **IMPORTANT!!!**
152 The {{code language="none"}}isValid{{/code}} method must return {{code language="none"}}true{{/code}} if the token exists and is valid, or throw {{code language="none"}}Ufo\RpcError\RpcInvalidTokenException{{/code}} otherwise.
153 )))
154
155 After that, you need to specify in the file {{code language="none"}}config/services.yaml{{/code}} that classes dependent on the interface {{code language="none"}}ITokenValidator{{/code}} should accept your new class.
156
157 {{code language="yaml" layout="LINENUMBERS" title="config/services.yaml"}}
158 parameters:
159 # some parameters list
160 # ...
161
162 services:
163 # some services list
164 # ...
165
166 Ufo\JsonRpcBundle\Security\Interfaces\ITokenValidator:
167 class: App\Services\RpcSecurity\UserTokenValidator
168 {{/code}}
169
170 = Block {{code language="none"}}async{{/code}} =
171
172 This block is for configuring [[asynchronous transport>>doc:docs.JsonRpcBundle.functionality.async.WebHome]].
173
174 Add the parameter {{code language="none"}}rpc_async{{/code}} which contains a string in DSN format. This string is the configuration for [[Symfony Messenger>>https://symfony.com/doc/current/messenger.html]], and it points to the asynchronous transport where the RPC Server will be waiting for incoming requests if you have a consumer running ({{code language="none"}}php bin/console messenger:consume rpc_async{{/code}}). For a more detailed understanding of this process, read the [[Symfony Messenger documentation>>https://symfony.com/doc/current/messenger.html]].
175
176 {{code language="yaml" layout="LINENUMBERS" title="config/packages/ufo_json_rpc.yaml"}}
177 ufo_json_rpc:
178 async:
179 rpc_async: '%env(resolve:RPC_TRANSPORT_DSN)%'
180
181 {{/code}}
182
183 (% class="box warningmessage" %)
184 (((
185 This configuration implies that you have an environment variable RPC_TRANSPORT_DSN set, which contains the DSN string.
186 )))
187
188 = The {{code language="none"}}docs{{/code}} block =
189
190 This block configures the generation of documentation when you make a GET request on the RPC Server
191
192 == Section {{code language="none"}}response{{/code}} ==
193
194 Contains settings that correspond to the content of the response.
195
196 === Parameter {{code language="none"}}key_for_methods{{/code}} ===
197
198 This parameter allows you to specify the name of the key in the response that will contain an array of available services.
199
200 The default value is {{code language="none"}}methods{{/code}}. It can have any string value.
201
202 {{code language="yaml" layout="LINENUMBERS" title="config/packages/ufo_json_rpc.yaml"}}
203 ufo_json_rpc:
204 docs:
205 key_for_methods: methods
206 {{/code}}
207
208 {{code language="yaml" layout="LINENUMBERS" title="config/packages/ufo_json_rpc.yaml"}}
209 ufo_json_rpc:
210 docs:
211 key_for_methods: services
212 {{/code}}
213
214 {{code language="yaml" layout="LINENUMBERS" title="config/packages/ufo_json_rpc.yaml"}}
215 ufo_json_rpc:
216 docs:
217 key_for_methods: some_custom_key
218 {{/code}}
219
220 === Parameter {{code language="none"}}async_dsn_info{{/code}} ===
221
222 Responsible for displaying information about asynchronous transport in the documentation.
223
224 {{code language="yaml" layout="LINENUMBERS" title="config/packages/ufo_json_rpc.yaml"}}
225 ufo_json_rpc:
226 async_dsn_info: true # or false
227 {{/code}}
228
229 ==== **Documentation Example** ====
230
231 {{code language="json" layout="LINENUMBERS" title="GET: /api"}}
232 {
233 "envelope": "JSON-RPC-2.0/UFO-RPC-6",
234 "contentType": "application/json",
235 "description": "",
236 "transport": {
237 "sync": {
238 "scheme": "http",
239 "host": "example.com",
240 "path": "/api",
241 "method": "POST"
242 },
243 "async": {
244 "scheme": "amqp",
245 "user": "{user}",
246 "pass": "{pass}",
247 "host": "async_rabbit",
248 "port": 5672,
249 "path": "/%2f/json-rpc"
250 }
251 },
252 "methods": {
253 ...
254 }
255 }
256 {{/code}}
257
258 {{info}}
259 Do not worry about the security of your authorization data contained in the DSN.
260
261 The documenter is designed in such a way that before displaying DSN information, it removes user data and passwords, as well as other sensitive information, such as tokens, secret keys, etc.
262
263 The template for protecting data is {{code language="none"}}/([\w\d_]*(?:secret|access|token|key)[_\w]*)=((?:\w|\d)+(?=&?))/{{/code}}.
264
265 Example:
266
267 {{code language="json" layout="LINENUMBERS" title="RPC_TRANSPORT_DSN=https://sqs.eu-west-3.amazonaws.com/123456789012/messages?access_key=AKIAIOSFODNN7EXAMPLE&secret_key=j17M97ffSVoKI0briFoo9a"}}
268 {
269 "async": {
270 "scheme": "https",
271 "host": "sqs.eu-west-3.amazonaws.com",
272 "path": "/123456789012/messages",
273 "query": "access_key={access_key}&secret_key={secret_key}"
274 }
275 }
276 {{/code}}
277 {{/info}}
278
279 === Parameter {{code language="none"}}validations{{/code}} ===
280
281 Responsible for displaying in the documentation methods additional blocks that indicate data validation requirements.
282
283 Currently, this block has two possible settings:
284
285 * {{code language="none"}}json_schema: <bool>{{/code}}
286 * {{code language="none"}}symfony_asserts: <bool>{{/code}}
287
288 In all options in this parameter, the default value is {{code language="none"}}false{{/code}}, meaning these blocks will not appear in the documentation.
289 If you need any of these information blocks when requesting documentation, set the value to {{code language="none"}}true{{/code}}.
290
291 {{code language="yaml" layout="LINENUMBERS" title="config/packages/ufo_json_rpc.yaml"}}
292 ufo_json_rpc:
293 docs:
294 json_schema: true
295 symfony_asserts: true
296 {{/code}}
297
298 ==== **Example of documentation ** ====
299
300 (% class="box infomessage" %)
301 (((
302 In this example, I removed the content of the objects symfony_assertions to simplify the example.
303 For more information on method validation, see the page **[[Validate procedures>>doc:docs.JsonRpcBundle.add_rpc_service.assertions.WebHome]]**
304 )))
305
306 {{code language="json" layout="LINENUMBERS" title="GET: /api"}}
307 {
308 "envelope": "JSON-RPC-2.0/UFO-RPC-6",
309 "transport": "POST",
310 "target": "/api",
311 "contentType": "application/json",
312 "description": "",
313 "methods": {
314 "getUserNameByUuid": {
315 "name": "getUserNameByUuid",
316 "description": "Get username by id",
317 "parameters": {
318 "userId": {
319 "type": "string",
320 "name": "userId",
321 "description": "User id in uuid format",
322 "optional": false
323 }
324 },
325 "returns": "string",
326 "responseFormat": "string",
327 "json_schema": {
328 "$schema": "http://json-schema.org/draft-07/schema#",
329 "type": "object",
330 "properties": {
331 "userId": {
332 "type": "string"
333 }
334 },
335 "required": [
336 "userId"
337 ]
338 },
339 "symfony_assertions": {
340 "userId": [
341 {
342 "class": "Symfony\\Component\\Validator\\Constraints\\Uuid",
343 "context": {}
344 }
345 ]
346 }
347 },
348 "sendEmail": {
349 "name": "sendEmail",
350 "description": "Send mail",
351 "parameters": {
352 "email": {
353 "type": "string",
354 "name": "email",
355 "description": "",
356 "optional": false
357 },
358 "subject": {
359 "type": "string",
360 "name": "subject",
361 "description": "",
362 "optional": false
363 },
364 "text": {
365 "type": "string",
366 "name": "text",
367 "description": "",
368 "optional": false
369 }
370 },
371 "returns": "boolean",
372 "responseFormat": "boolean",
373 "json_schema": {
374 "$schema": "http://json-schema.org/draft-07/schema#",
375 "type": "object",
376 "properties": {
377 "email": {
378 "type": "string",
379 "format": "email"
380 },
381 "subject": {
382 "type": "string",
383 "minLength": 1,
384 "maxLength": 100
385 },
386 "text": {
387 "type": "string",
388 "minLength": 10
389 }
390 },
391 "required": [
392 "email",
393 "subject",
394 "text"
395 ]
396 },
397 "symfony_assertions": {
398 "email": [
399 {
400 "class": "Symfony\\Component\\Validator\\Constraints\\Email",
401 "context": {}
402 }
403 ],
404 "subject": [
405 {
406 "class": "Symfony\\Component\\Validator\\Constraints\\NotBlank",
407 "context": {}
408 },
409 {
410 "class": "Symfony\\Component\\Validator\\Constraints\\Length",
411 "context": {}
412 }
413 ],
414 "text": [
415 {
416 "class": "Symfony\\Component\\Validator\\Constraints\\NotBlank",
417 "context": {}
418 },
419 {
420 "class": "Symfony\\Component\\Validator\\Constraints\\Length",
421 "context": {}
422 }
423 ]
424 }
425 }
426 }
427 }
428 {{/code}}