Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
namespace core\oauth2\discovery;
18
 
19
use core\http_client;
20
use GuzzleHttp\Exception\ClientException;
21
use GuzzleHttp\Handler\MockHandler;
22
use GuzzleHttp\HandlerStack;
23
use GuzzleHttp\Middleware;
24
use GuzzleHttp\Psr7\Response;
25
use Psr\Http\Message\ResponseInterface;
26
 
27
/**
28
 * Unit tests for {@see auth_server_config_reader}.
29
 *
30
 * @coversDefaultClass \core\oauth2\discovery\auth_server_config_reader
31
 * @package core
32
 * @copyright 2023 Jake Dallimore <jrhdallimore@gmail.com>
33
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
34
 */
35
class auth_server_config_reader_test extends \advanced_testcase {
36
 
37
    /**
38
     * Test reading the config for an auth server.
39
     *
40
     * @covers ::read_configuration
41
     * @dataProvider config_provider
42
     * @param string $issuerurl the auth server issuer URL.
43
     * @param ResponseInterface $httpresponse a stub HTTP response.
44
     * @param null|string $altwellknownsuffix an alternate value for the well known suffix to use in the reader.
45
     * @param array $expected test expectations.
46
     * @return void
47
     */
48
    public function test_read_configuration(string $issuerurl, ResponseInterface $httpresponse, ?string $altwellknownsuffix = null,
11 efrain 49
            array $expected = []): void {
1 efrain 50
 
51
        $mock = new MockHandler([$httpresponse]);
52
        $handlerstack = HandlerStack::create($mock);
53
        if (!empty($expected['request'])) {
54
            // Request history tracking to allow asserting that request was sent as expected below (to the stub client).
55
            $container = [];
56
            $history = Middleware::history($container);
57
            $handlerstack->push($history);
58
        }
59
 
60
        $args = [
61
            new http_client(['handler' => $handlerstack]),
62
        ];
63
        if (!is_null($altwellknownsuffix)) {
64
            $args[] = $altwellknownsuffix;
65
        }
66
 
67
        if (!empty($expected['exception'])) {
68
            $this->expectException($expected['exception']);
69
        }
70
        $configreader = new auth_server_config_reader(...$args);
71
        $config = $configreader->read_configuration(new \moodle_url($issuerurl));
72
 
73
        if (!empty($expected['request'])) {
74
            // Verify the request goes to the correct URL (i.e. the well known suffix is correctly positioned).
75
            $this->assertEquals($expected['request']['url'], $container[0]['request']->getUri());
76
        }
77
 
78
        $this->assertEquals($expected['metadata'], (array) $config);
79
    }
80
 
81
    /**
82
     * Provider for testing read_configuration().
83
     *
84
     * @return array test data.
85
     */
86
    public function config_provider(): array {
87
        return [
88
            'Valid, good issuer URL, good config' => [
89
                'issuer_url' => 'https://app.example.com',
90
                'http_response' => new Response(
91
                    200,
92
                    ['Content-Type' => 'application/json'],
93
                    json_encode([
94
                        "issuer" => "https://app.example.com",
95
                        "authorization_endpoint" => "https://app.example.com/authorize",
96
                        "token_endpoint" => "https://app.example.com/token",
97
                        "token_endpoint_auth_methods_supported" => [
98
                            "client_secret_basic",
99
                            "private_key_jwt"
100
                        ],
101
                        "token_endpoint_auth_signing_alg_values_supported" => [
102
                            "RS256",
103
                            "ES256"
104
                        ],
105
                        "userinfo_endpoint" => "https://app.example.com/userinfo",
106
                        "jwks_uri" => "https://app.example.com/jwks.json",
107
                        "registration_endpoint" => "https://app.example.com/register",
108
                        "scopes_supported" => [
109
                            "openid",
110
                            "profile",
111
                            "email",
112
                        ],
113
                        "response_types_supported" => [
114
                            "code",
115
                            "code token"
116
                        ],
117
                        "service_documentation" => "http://app.example.com/service_documentation.html",
118
                        "ui_locales_supported" => [
119
                            "en-US",
120
                            "en-GB",
121
                            "fr-FR",
122
                        ]
123
                    ])
124
                ),
125
                'well_known_suffix' => null,
126
                'expected' => [
127
                    'request' => [
128
                        'url' => 'https://app.example.com/.well-known/oauth-authorization-server'
129
                    ],
130
                    'metadata' => [
131
                        "issuer" => "https://app.example.com",
132
                        "authorization_endpoint" => "https://app.example.com/authorize",
133
                        "token_endpoint" => "https://app.example.com/token",
134
                        "token_endpoint_auth_methods_supported" => [
135
                            "client_secret_basic",
136
                            "private_key_jwt"
137
                        ],
138
                        "token_endpoint_auth_signing_alg_values_supported" => [
139
                            "RS256",
140
                            "ES256"
141
                        ],
142
                        "userinfo_endpoint" => "https://app.example.com/userinfo",
143
                        "jwks_uri" => "https://app.example.com/jwks.json",
144
                        "registration_endpoint" => "https://app.example.com/register",
145
                        "scopes_supported" => [
146
                            "openid",
147
                            "profile",
148
                            "email",
149
                        ],
150
                        "response_types_supported" => [
151
                            "code",
152
                            "code token"
153
                        ],
154
                        "service_documentation" => "http://app.example.com/service_documentation.html",
155
                        "ui_locales_supported" => [
156
                            "en-US",
157
                            "en-GB",
158
                            "fr-FR",
159
                        ]
160
                    ]
161
                ]
162
            ],
163
            'Valid, issuer URL with path component confirming well known suffix placement' => [
164
                'issuer_url' => 'https://app.example.com/some/path',
165
                'http_response' => new Response(
166
                    200,
167
                    ['Content-Type' => 'application/json'],
168
                    json_encode([
169
                        "issuer" => "https://app.example.com",
170
                        "authorization_endpoint" => "https://app.example.com/authorize",
171
                        "token_endpoint" => "https://app.example.com/token",
172
                        "token_endpoint_auth_methods_supported" => [
173
                            "client_secret_basic",
174
                            "private_key_jwt"
175
                        ],
176
                        "token_endpoint_auth_signing_alg_values_supported" => [
177
                            "RS256",
178
                            "ES256"
179
                        ],
180
                        "userinfo_endpoint" => "https://app.example.com/userinfo",
181
                        "jwks_uri" => "https://app.example.com/jwks.json",
182
                        "registration_endpoint" => "https://app.example.com/register",
183
                        "scopes_supported" => [
184
                            "openid",
185
                            "profile",
186
                            "email",
187
                        ],
188
                        "response_types_supported" => [
189
                            "code",
190
                            "code token"
191
                        ],
192
                        "service_documentation" => "http://app.example.com/service_documentation.html",
193
                        "ui_locales_supported" => [
194
                            "en-US",
195
                            "en-GB",
196
                            "fr-FR",
197
                        ]
198
                    ])
199
                ),
200
                'well_known_suffix' => null,
201
                'expected' => [
202
                    'request' => [
203
                        'url' => 'https://app.example.com/.well-known/oauth-authorization-server/some/path'
204
                    ],
205
                    'metadata' => [
206
                        "issuer" => "https://app.example.com",
207
                        "authorization_endpoint" => "https://app.example.com/authorize",
208
                        "token_endpoint" => "https://app.example.com/token",
209
                        "token_endpoint_auth_methods_supported" => [
210
                            "client_secret_basic",
211
                            "private_key_jwt"
212
                        ],
213
                        "token_endpoint_auth_signing_alg_values_supported" => [
214
                            "RS256",
215
                            "ES256"
216
                        ],
217
                        "userinfo_endpoint" => "https://app.example.com/userinfo",
218
                        "jwks_uri" => "https://app.example.com/jwks.json",
219
                        "registration_endpoint" => "https://app.example.com/register",
220
                        "scopes_supported" => [
221
                            "openid",
222
                            "profile",
223
                            "email",
224
                        ],
225
                        "response_types_supported" => [
226
                            "code",
227
                            "code token"
228
                        ],
229
                        "service_documentation" => "http://app.example.com/service_documentation.html",
230
                        "ui_locales_supported" => [
231
                            "en-US",
232
                            "en-GB",
233
                            "fr-FR",
234
                        ]
235
                    ]
236
                ]
237
            ],
238
            'Valid, single trailing / path only' => [
239
                'issuer_url' => 'https://app.example.com/',
240
                'http_response' => new Response(
241
                    200,
242
                    ['Content-Type' => 'application/json'],
243
                    json_encode([
244
                        "issuer" => "https://app.example.com",
245
                        "authorization_endpoint" => "https://app.example.com/authorize",
246
                        "token_endpoint" => "https://app.example.com/token",
247
                        "token_endpoint_auth_methods_supported" => [
248
                            "client_secret_basic",
249
                            "private_key_jwt"
250
                        ],
251
                        "token_endpoint_auth_signing_alg_values_supported" => [
252
                            "RS256",
253
                            "ES256"
254
                        ],
255
                        "userinfo_endpoint" => "https://app.example.com/userinfo",
256
                        "jwks_uri" => "https://app.example.com/jwks.json",
257
                        "registration_endpoint" => "https://app.example.com/register",
258
                        "scopes_supported" => [
259
                            "openid",
260
                            "profile",
261
                            "email",
262
                        ],
263
                        "response_types_supported" => [
264
                            "code",
265
                            "code token"
266
                        ],
267
                        "service_documentation" => "http://app.example.com/service_documentation.html",
268
                        "ui_locales_supported" => [
269
                            "en-US",
270
                            "en-GB",
271
                            "fr-FR",
272
                        ]
273
                    ])
274
                ),
275
                'well_known_suffix' => null,
276
                'expected' => [
277
                    'request' => [
278
                        'url' => 'https://app.example.com/.well-known/oauth-authorization-server'
279
                    ],
280
                    'metadata' => [
281
                        "issuer" => "https://app.example.com",
282
                        "authorization_endpoint" => "https://app.example.com/authorize",
283
                        "token_endpoint" => "https://app.example.com/token",
284
                        "token_endpoint_auth_methods_supported" => [
285
                            "client_secret_basic",
286
                            "private_key_jwt"
287
                        ],
288
                        "token_endpoint_auth_signing_alg_values_supported" => [
289
                            "RS256",
290
                            "ES256"
291
                        ],
292
                        "userinfo_endpoint" => "https://app.example.com/userinfo",
293
                        "jwks_uri" => "https://app.example.com/jwks.json",
294
                        "registration_endpoint" => "https://app.example.com/register",
295
                        "scopes_supported" => [
296
                            "openid",
297
                            "profile",
298
                            "email",
299
                        ],
300
                        "response_types_supported" => [
301
                            "code",
302
                            "code token"
303
                        ],
304
                        "service_documentation" => "http://app.example.com/service_documentation.html",
305
                        "ui_locales_supported" => [
306
                            "en-US",
307
                            "en-GB",
308
                            "fr-FR",
309
                        ]
310
                    ]
311
                ]
312
            ],
313
            'Invalid, non HTTPS issuer URL' => [
314
                'issuer_url' => 'http://app.example.com',
315
                'http_response' => new Response(
316
                    200,
317
                    ['Content-Type' => 'application/json'],
318
                    json_encode([
319
                        "issuer" => "https://app.example.com",
320
                        "authorization_endpoint" => "https://app.example.com/authorize",
321
                        "token_endpoint" => "https://app.example.com/token",
322
                        "token_endpoint_auth_methods_supported" => [
323
                            "client_secret_basic",
324
                            "private_key_jwt"
325
                        ],
326
                        "token_endpoint_auth_signing_alg_values_supported" => [
327
                            "RS256",
328
                            "ES256"
329
                        ],
330
                        "userinfo_endpoint" => "https://app.example.com/userinfo",
331
                        "jwks_uri" => "https://app.example.com/jwks.json",
332
                        "registration_endpoint" => "https://app.example.com/register",
333
                        "scopes_supported" => [
334
                            "openid",
335
                            "profile",
336
                            "email",
337
                        ],
338
                        "response_types_supported" => [
339
                            "code",
340
                            "code token"
341
                        ],
342
                        "service_documentation" => "http://app.example.com/service_documentation.html",
343
                        "ui_locales_supported" => [
344
                            "en-US",
345
                            "en-GB",
346
                            "fr-FR",
347
                        ]
348
                    ])
349
                ),
350
                'well_known_suffix' => null,
351
                'expected' => [
352
                    'exception' => \moodle_exception::class
353
                ]
354
            ],
355
            'Invalid, query string in issuer URL' => [
356
                'issuer_url' => 'https://app.example.com?test=cat',
357
                'http_response' => new Response(
358
                    200,
359
                    ['Content-Type' => 'application/json'],
360
                    json_encode([
361
                        "issuer" => "https://app.example.com",
362
                        "authorization_endpoint" => "https://app.example.com/authorize",
363
                        "token_endpoint" => "https://app.example.com/token",
364
                        "token_endpoint_auth_methods_supported" => [
365
                            "client_secret_basic",
366
                            "private_key_jwt"
367
                        ],
368
                        "token_endpoint_auth_signing_alg_values_supported" => [
369
                            "RS256",
370
                            "ES256"
371
                        ],
372
                        "userinfo_endpoint" => "https://app.example.com/userinfo",
373
                        "jwks_uri" => "https://app.example.com/jwks.json",
374
                        "registration_endpoint" => "https://app.example.com/register",
375
                        "scopes_supported" => [
376
                            "openid",
377
                            "profile",
378
                            "email",
379
                        ],
380
                        "response_types_supported" => [
381
                            "code",
382
                            "code token"
383
                        ],
384
                        "service_documentation" => "http://app.example.com/service_documentation.html",
385
                        "ui_locales_supported" => [
386
                            "en-US",
387
                            "en-GB",
388
                            "fr-FR",
389
                        ]
390
                    ])
391
                ),
392
                'well_known_suffix' => null,
393
                'expected' => [
394
                    'exception' => \moodle_exception::class
395
                ]
396
            ],
397
            'Invalid, fragment in issuer URL' => [
398
                'issuer_url' => 'https://app.example.com/#cat',
399
                'http_response' => new Response(
400
                    200,
401
                    ['Content-Type' => 'application/json'],
402
                    json_encode([
403
                        "issuer" => "https://app.example.com",
404
                        "authorization_endpoint" => "https://app.example.com/authorize",
405
                        "token_endpoint" => "https://app.example.com/token",
406
                        "token_endpoint_auth_methods_supported" => [
407
                            "client_secret_basic",
408
                            "private_key_jwt"
409
                        ],
410
                        "token_endpoint_auth_signing_alg_values_supported" => [
411
                            "RS256",
412
                            "ES256"
413
                        ],
414
                        "userinfo_endpoint" => "https://app.example.com/userinfo",
415
                        "jwks_uri" => "https://app.example.com/jwks.json",
416
                        "registration_endpoint" => "https://app.example.com/register",
417
                        "scopes_supported" => [
418
                            "openid",
419
                            "profile",
420
                            "email",
421
                        ],
422
                        "response_types_supported" => [
423
                            "code",
424
                            "code token"
425
                        ],
426
                        "service_documentation" => "http://app.example.com/service_documentation.html",
427
                        "ui_locales_supported" => [
428
                            "en-US",
429
                            "en-GB",
430
                            "fr-FR",
431
                        ]
432
                    ])
433
                ),
434
                'well_known_suffix' => null,
435
                'expected' => [
436
                    'exception' => \moodle_exception::class
437
                ]
438
            ],
439
            'Valid, port in issuer URL' => [
440
                'issuer_url' => 'https://app.example.com:8080/some/path',
441
                'http_response' => new Response(
442
                    200,
443
                    ['Content-Type' => 'application/json'],
444
                    json_encode([
445
                        "issuer" => "https://app.example.com",
446
                        "authorization_endpoint" => "https://app.example.com/authorize",
447
                        "token_endpoint" => "https://app.example.com/token",
448
                        "token_endpoint_auth_methods_supported" => [
449
                            "client_secret_basic",
450
                            "private_key_jwt"
451
                        ],
452
                        "token_endpoint_auth_signing_alg_values_supported" => [
453
                            "RS256",
454
                            "ES256"
455
                        ],
456
                        "userinfo_endpoint" => "https://app.example.com/userinfo",
457
                        "jwks_uri" => "https://app.example.com/jwks.json",
458
                        "registration_endpoint" => "https://app.example.com/register",
459
                        "scopes_supported" => [
460
                            "openid",
461
                            "profile",
462
                            "email",
463
                        ],
464
                        "response_types_supported" => [
465
                            "code",
466
                            "code token"
467
                        ],
468
                        "service_documentation" => "http://app.example.com/service_documentation.html",
469
                        "ui_locales_supported" => [
470
                            "en-US",
471
                            "en-GB",
472
                            "fr-FR",
473
                        ]
474
                    ])
475
                ),
476
                'well_known_suffix' => null,
477
                'expected' => [
478
                    'request' => [
479
                        'url' => 'https://app.example.com:8080/.well-known/oauth-authorization-server/some/path'
480
                    ],
481
                    'metadata' => [
482
                        "issuer" => "https://app.example.com",
483
                        "authorization_endpoint" => "https://app.example.com/authorize",
484
                        "token_endpoint" => "https://app.example.com/token",
485
                        "token_endpoint_auth_methods_supported" => [
486
                            "client_secret_basic",
487
                            "private_key_jwt"
488
                        ],
489
                        "token_endpoint_auth_signing_alg_values_supported" => [
490
                            "RS256",
491
                            "ES256"
492
                        ],
493
                        "userinfo_endpoint" => "https://app.example.com/userinfo",
494
                        "jwks_uri" => "https://app.example.com/jwks.json",
495
                        "registration_endpoint" => "https://app.example.com/register",
496
                        "scopes_supported" => [
497
                            "openid",
498
                            "profile",
499
                            "email",
500
                        ],
501
                        "response_types_supported" => [
502
                            "code",
503
                            "code token"
504
                        ],
505
                        "service_documentation" => "http://app.example.com/service_documentation.html",
506
                        "ui_locales_supported" => [
507
                            "en-US",
508
                            "en-GB",
509
                            "fr-FR",
510
                        ]
511
                    ]
512
                ]
513
            ],
514
            'Valid, alternate well known suffix, no path' => [
515
                'issuer_url' => 'https://app.example.com',
516
                'http_response' => new Response(
517
                    200,
518
                    ['Content-Type' => 'application/json'],
519
                    json_encode([
520
                        "issuer" => "https://app.example.com",
521
                        "authorization_endpoint" => "https://app.example.com/authorize",
522
                        "token_endpoint" => "https://app.example.com/token",
523
                        "token_endpoint_auth_methods_supported" => [
524
                            "client_secret_basic",
525
                            "private_key_jwt"
526
                        ],
527
                        "token_endpoint_auth_signing_alg_values_supported" => [
528
                            "RS256",
529
                            "ES256"
530
                        ],
531
                        "userinfo_endpoint" => "https://app.example.com/userinfo",
532
                        "jwks_uri" => "https://app.example.com/jwks.json",
533
                        "registration_endpoint" => "https://app.example.com/register",
534
                        "scopes_supported" => [
535
                            "openid",
536
                            "profile",
537
                            "email",
538
                        ],
539
                        "response_types_supported" => [
540
                            "code",
541
                            "code token"
542
                        ],
543
                        "service_documentation" => "http://app.example.com/service_documentation.html",
544
                        "ui_locales_supported" => [
545
                            "en-US",
546
                            "en-GB",
547
                            "fr-FR",
548
                        ]
549
                    ])
550
                ),
551
                'well_known_suffix' => 'openid-configuration', // An application using the openid well known, which is valid.
552
                'expected' => [
553
                    'request' => [
554
                        'url' => 'https://app.example.com/.well-known/openid-configuration'
555
                    ],
556
                    'metadata' => [
557
                        "issuer" => "https://app.example.com",
558
                        "authorization_endpoint" => "https://app.example.com/authorize",
559
                        "token_endpoint" => "https://app.example.com/token",
560
                        "token_endpoint_auth_methods_supported" => [
561
                            "client_secret_basic",
562
                            "private_key_jwt"
563
                        ],
564
                        "token_endpoint_auth_signing_alg_values_supported" => [
565
                            "RS256",
566
                            "ES256"
567
                        ],
568
                        "userinfo_endpoint" => "https://app.example.com/userinfo",
569
                        "jwks_uri" => "https://app.example.com/jwks.json",
570
                        "registration_endpoint" => "https://app.example.com/register",
571
                        "scopes_supported" => [
572
                            "openid",
573
                            "profile",
574
                            "email",
575
                        ],
576
                        "response_types_supported" => [
577
                            "code",
578
                            "code token"
579
                        ],
580
                        "service_documentation" => "http://app.example.com/service_documentation.html",
581
                        "ui_locales_supported" => [
582
                            "en-US",
583
                            "en-GB",
584
                            "fr-FR",
585
                        ]
586
                    ]
587
                ]
588
            ],
589
            'Valid, alternate well known suffix, with path' => [
590
                'issuer_url' => 'https://app.example.com/some/path/',
591
                'http_response' => new Response(
592
                    200,
593
                    ['Content-Type' => 'application/json'],
594
                    json_encode([
595
                        "issuer" => "https://app.example.com",
596
                        "authorization_endpoint" => "https://app.example.com/authorize",
597
                        "token_endpoint" => "https://app.example.com/token",
598
                        "token_endpoint_auth_methods_supported" => [
599
                            "client_secret_basic",
600
                            "private_key_jwt"
601
                        ],
602
                        "token_endpoint_auth_signing_alg_values_supported" => [
603
                            "RS256",
604
                            "ES256"
605
                        ],
606
                        "userinfo_endpoint" => "https://app.example.com/userinfo",
607
                        "jwks_uri" => "https://app.example.com/jwks.json",
608
                        "registration_endpoint" => "https://app.example.com/register",
609
                        "scopes_supported" => [
610
                            "openid",
611
                            "profile",
612
                            "email",
613
                        ],
614
                        "response_types_supported" => [
615
                            "code",
616
                            "code token"
617
                        ],
618
                        "service_documentation" => "http://app.example.com/service_documentation.html",
619
                        "ui_locales_supported" => [
620
                            "en-US",
621
                            "en-GB",
622
                            "fr-FR",
623
                        ]
624
                    ])
625
                ),
626
                'well_known_suffix' => 'openid-configuration', // An application using the openid well known, which is valid.
627
                'expected' => [
628
                    'request' => [
629
                        'url' => 'https://app.example.com/.well-known/openid-configuration/some/path/'
630
                    ],
631
                    'metadata' => [
632
                        "issuer" => "https://app.example.com",
633
                        "authorization_endpoint" => "https://app.example.com/authorize",
634
                        "token_endpoint" => "https://app.example.com/token",
635
                        "token_endpoint_auth_methods_supported" => [
636
                            "client_secret_basic",
637
                            "private_key_jwt"
638
                        ],
639
                        "token_endpoint_auth_signing_alg_values_supported" => [
640
                            "RS256",
641
                            "ES256"
642
                        ],
643
                        "userinfo_endpoint" => "https://app.example.com/userinfo",
644
                        "jwks_uri" => "https://app.example.com/jwks.json",
645
                        "registration_endpoint" => "https://app.example.com/register",
646
                        "scopes_supported" => [
647
                            "openid",
648
                            "profile",
649
                            "email",
650
                        ],
651
                        "response_types_supported" => [
652
                            "code",
653
                            "code token"
654
                        ],
655
                        "service_documentation" => "http://app.example.com/service_documentation.html",
656
                        "ui_locales_supported" => [
657
                            "en-US",
658
                            "en-GB",
659
                            "fr-FR",
660
                        ]
661
                    ]
662
                ]
663
            ],
664
            'Invalid, bad response' => [
665
                'issuer_url' => 'https://app.example.com',
666
                'http_response' => new Response(404),
667
                'well_known_suffix' => null,
668
                'expected' => [
669
                    'exception' => ClientException::class
670
                ]
671
            ]
672
        ];
673
    }
674
}