auth0-deploy-cli 8.31.0 → 8.32.0

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.
Files changed (73) hide show
  1. package/CHANGELOG.md +40 -419
  2. package/lib/args.d.ts +4 -0
  3. package/lib/args.js +18 -0
  4. package/lib/commands/import.js +40 -3
  5. package/lib/context/directory/handlers/clients.js +14 -0
  6. package/lib/context/directory/handlers/connections.js +1 -0
  7. package/lib/context/directory/handlers/guardianFactors.js +8 -6
  8. package/lib/context/directory/handlers/tenant.d.ts +1 -6
  9. package/lib/context/directory/handlers/tenant.js +0 -1
  10. package/lib/context/index.js +5 -0
  11. package/lib/context/yaml/handlers/clients.js +12 -0
  12. package/lib/context/yaml/handlers/connections.js +1 -17
  13. package/lib/context/yaml/handlers/guardianFactors.js +3 -1
  14. package/lib/context/yaml/handlers/tenant.js +1 -1
  15. package/lib/sessionDurationsToMinutes.d.ts +9 -7
  16. package/lib/sessionDurationsToMinutes.js +11 -10
  17. package/lib/tools/auth0/handlers/actions.d.ts +1 -0
  18. package/lib/tools/auth0/handlers/actions.js +36 -0
  19. package/lib/tools/auth0/handlers/attackProtection.js +15 -0
  20. package/lib/tools/auth0/handlers/branding.js +7 -0
  21. package/lib/tools/auth0/handlers/clientGrants.js +1 -0
  22. package/lib/tools/auth0/handlers/clients.d.ts +52 -2
  23. package/lib/tools/auth0/handlers/clients.js +175 -22
  24. package/lib/tools/auth0/handlers/connections.d.ts +1 -0
  25. package/lib/tools/auth0/handlers/connections.js +42 -5
  26. package/lib/tools/auth0/handlers/databases.d.ts +91 -0
  27. package/lib/tools/auth0/handlers/databases.js +162 -2
  28. package/lib/tools/auth0/handlers/default.d.ts +4 -0
  29. package/lib/tools/auth0/handlers/default.js +82 -4
  30. package/lib/tools/auth0/handlers/emailProvider.js +8 -0
  31. package/lib/tools/auth0/handlers/emailTemplates.js +7 -0
  32. package/lib/tools/auth0/handlers/flowVaultConnections.js +6 -1
  33. package/lib/tools/auth0/handlers/flows.js +13 -4
  34. package/lib/tools/auth0/handlers/forms.js +10 -1
  35. package/lib/tools/auth0/handlers/guardianFactors.js +7 -1
  36. package/lib/tools/auth0/handlers/guardianPhoneFactorMessageTypes.js +8 -1
  37. package/lib/tools/auth0/handlers/guardianPhoneFactorSelectedProvider.js +8 -1
  38. package/lib/tools/auth0/handlers/guardianPolicies.js +7 -0
  39. package/lib/tools/auth0/handlers/hooks.d.ts +1 -0
  40. package/lib/tools/auth0/handlers/hooks.js +10 -0
  41. package/lib/tools/auth0/handlers/organizations.d.ts +11 -1
  42. package/lib/tools/auth0/handlers/organizations.js +48 -52
  43. package/lib/tools/auth0/handlers/pages.js +7 -0
  44. package/lib/tools/auth0/handlers/phoneProvider.js +7 -0
  45. package/lib/tools/auth0/handlers/prompts.js +7 -0
  46. package/lib/tools/auth0/handlers/resourceServers.d.ts +1 -2
  47. package/lib/tools/auth0/handlers/resourceServers.js +9 -25
  48. package/lib/tools/auth0/handlers/roles.js +7 -0
  49. package/lib/tools/auth0/handlers/rules.d.ts +1 -0
  50. package/lib/tools/auth0/handlers/rules.js +9 -0
  51. package/lib/tools/auth0/handlers/rulesConfigs.d.ts +1 -0
  52. package/lib/tools/auth0/handlers/rulesConfigs.js +3 -0
  53. package/lib/tools/auth0/handlers/scimHandler.js +8 -1
  54. package/lib/tools/auth0/handlers/selfServiceProfiles.d.ts +1 -1
  55. package/lib/tools/auth0/handlers/selfServiceProfiles.js +10 -17
  56. package/lib/tools/auth0/handlers/tenant.d.ts +13 -1
  57. package/lib/tools/auth0/handlers/tenant.js +43 -4
  58. package/lib/tools/auth0/handlers/themes.js +11 -2
  59. package/lib/tools/auth0/handlers/triggers.js +6 -0
  60. package/lib/tools/auth0/handlers/userAttributeProfiles.js +16 -10
  61. package/lib/tools/auth0/index.d.ts +3 -0
  62. package/lib/tools/auth0/index.js +204 -0
  63. package/lib/tools/calculateChanges.d.ts +2 -2
  64. package/lib/tools/calculateChanges.js +21 -6
  65. package/lib/tools/calculateDryRunChanges.d.ts +71 -0
  66. package/lib/tools/calculateDryRunChanges.js +492 -0
  67. package/lib/tools/deploy.js +25 -1
  68. package/lib/tools/utils.d.ts +5 -0
  69. package/lib/tools/utils.js +64 -3
  70. package/lib/types.d.ts +16 -0
  71. package/lib/utils.d.ts +7 -0
  72. package/lib/utils.js +33 -1
  73. package/package.json +7 -4
@@ -4,9 +4,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.schema = void 0;
7
+ const auth0_1 = require("auth0");
7
8
  const lodash_1 = require("lodash");
8
9
  const client_1 = require("../client");
9
10
  const default_1 = __importDefault(require("./default"));
11
+ const calculateChanges_1 = require("../../calculateChanges");
10
12
  const connectionProfiles_1 = require("./connectionProfiles");
11
13
  const userAttributeProfiles_1 = require("./userAttributeProfiles");
12
14
  const logger_1 = __importDefault(require("../../../logger"));
@@ -31,12 +33,57 @@ const multiResourceRefreshTokenPoliciesSchema = {
31
33
  required: ['audience', 'scope'],
32
34
  },
33
35
  };
36
+ const myOrganizationConfigurationSchema = {
37
+ type: ['object', 'null'],
38
+ description: 'Configuration related to the My Organization API for the client.',
39
+ properties: {
40
+ connection_profile_id: {
41
+ type: 'string',
42
+ description: 'The name of the connection profile to use for this application',
43
+ },
44
+ user_attribute_profile_id: {
45
+ type: 'string',
46
+ description: 'The name of the user attribute profile to use for this application',
47
+ },
48
+ allowed_strategies: {
49
+ type: 'array',
50
+ description: 'The allowed connection strategies for the My Organization configuration',
51
+ items: {
52
+ type: 'string',
53
+ enum: Object.values(auth0_1.Management.ClientMyOrganizationConfigurationAllowedStrategiesEnum),
54
+ },
55
+ uniqueItems: true,
56
+ },
57
+ connection_deletion_behavior: {
58
+ type: 'string',
59
+ enum: Object.values(auth0_1.Management.ClientMyOrganizationDeletionBehaviorEnum),
60
+ description: 'The deletion behavior for My Organization connections created by this client',
61
+ },
62
+ },
63
+ required: ['allowed_strategies', 'connection_deletion_behavior'],
64
+ };
34
65
  exports.schema = {
35
66
  type: 'array',
36
67
  items: {
37
68
  type: 'object',
38
69
  properties: {
39
70
  name: { type: 'string', minLength: 1, pattern: '[^<>]+' },
71
+ external_client_id: {
72
+ type: 'string',
73
+ description: 'Alternative identifier for the client. For CIMD clients, this is the HTTPS URL to the OAuth 2.0 Client ID Metadata Document. Client name should match the client_name value in the metadata document.',
74
+ },
75
+ external_metadata_type: {
76
+ type: 'string',
77
+ description: 'Type of external client registration metadata.',
78
+ },
79
+ external_metadata_created_by: {
80
+ type: 'string',
81
+ description: 'Indicates who created the external metadata client: admin for Management API, client for dynamic registration.',
82
+ },
83
+ jwks_uri: {
84
+ type: 'string',
85
+ description: 'JSON Web Key Set endpoint associated with the client.',
86
+ },
40
87
  mobile: {
41
88
  type: 'object',
42
89
  properties: {
@@ -177,6 +224,7 @@ exports.schema = {
177
224
  enum: ['email', 'organization_name'],
178
225
  },
179
226
  },
227
+ my_organization_configuration: myOrganizationConfigurationSchema,
180
228
  async_approval_notification_channels: {
181
229
  type: ['array', 'null'],
182
230
  description: 'An ordered array of notification channels enabled for CIBA (Client-Initiated Backchannel Authentication) requests. Channels are evaluated in the order specified.',
@@ -259,7 +307,7 @@ exports.schema = {
259
307
  description: 'List of enabled token exchange profile types for this client',
260
308
  items: {
261
309
  type: 'string',
262
- enum: ['custom_authentication'],
310
+ enum: ['custom_authentication', 'on_behalf_of_token_exchange'],
263
311
  },
264
312
  },
265
313
  },
@@ -376,6 +424,7 @@ class ClientHandler extends default_1.default {
376
424
  id: 'client_id',
377
425
  identifiers: ['client_id', 'name'],
378
426
  objectFields: ['client_metadata'],
427
+ stripCreateFields: ['external_metadata_type', 'external_metadata_created_by', 'jwks_uri'],
379
428
  stripUpdateFields: [
380
429
  // Fields not allowed during updates
381
430
  'callback_url_template',
@@ -384,18 +433,60 @@ class ClientHandler extends default_1.default {
384
433
  'tenant',
385
434
  'jwt_configuration.secret_encoded',
386
435
  'resource_server_identifier',
436
+ 'external_metadata_type',
437
+ 'external_metadata_created_by',
438
+ 'jwks_uri',
387
439
  ],
440
+ functions: {
441
+ create: (client) => this.createClient(client),
442
+ update: (clientId, client) => this.updateClient(clientId, client),
443
+ },
388
444
  });
389
445
  }
390
446
  objString(item) {
391
447
  return super.objString({ name: item.name, client_id: item.client_id });
392
448
  }
449
+ async calcChanges(assets) {
450
+ const clients = assets[this.type];
451
+ if (!clients) {
452
+ return { del: [], create: [], conflicts: [], update: [] };
453
+ }
454
+ const existing = await this.getType();
455
+ const allowDelete = !!this.config('AUTH0_ALLOW_DELETE');
456
+ const cimdClients = clients.filter((c) => this.isCimdClient(c));
457
+ const existingCimd = (existing || []).filter((c) => this.isCimdClient(c));
458
+ const regularClients = clients.filter((c) => !this.isCimdClient(c));
459
+ const existingRegular = (existing || []).filter((c) => !this.isCimdClient(c));
460
+ const regularChanges = (0, calculateChanges_1.calculateChanges)({
461
+ handler: this,
462
+ assets: regularClients,
463
+ existing: existingRegular,
464
+ identifiers: ['client_id', 'name'],
465
+ allowDelete,
466
+ });
467
+ // CIMD clients:
468
+ // If external_client_id changes (and no client_id match), old client is deleted
469
+ // and new registration is created with the new external_client_id.
470
+ const cimdChanges = (0, calculateChanges_1.calculateChanges)({
471
+ handler: this,
472
+ assets: cimdClients,
473
+ existing: existingCimd,
474
+ identifiers: ['client_id', 'external_client_id'],
475
+ allowDelete,
476
+ });
477
+ return {
478
+ del: [...regularChanges.del, ...cimdChanges.del],
479
+ update: [...regularChanges.update, ...cimdChanges.update],
480
+ create: [...regularChanges.create, ...cimdChanges.create],
481
+ conflicts: [...regularChanges.conflicts, ...cimdChanges.conflicts],
482
+ };
483
+ }
393
484
  async processChanges(assets) {
394
485
  const { clients } = assets;
395
486
  // Do nothing if not set
396
487
  if (!clients)
397
488
  return;
398
- assets.clients = await this.sanitizeMapExpressConfiguration(this.client, clients);
489
+ assets.clients = await this.sanitizeMapClientReferences(this.client, clients);
399
490
  const excludedClients = (assets.exclude && assets.exclude.clients) || [];
400
491
  const { del, update, create, conflicts } = await this.calcChanges(assets);
401
492
  // Always filter out the client we are using to access Auth0 Management API
@@ -453,37 +544,99 @@ class ClientHandler extends default_1.default {
453
544
  this.existing = createClientSanitizer(clients).sanitizeCrossOriginAuth(false).get();
454
545
  return this.existing;
455
546
  }
456
- // convert names back to IDs for express configuration
457
- async sanitizeMapExpressConfiguration(auth0Client, clientList) {
458
- // if no clients have express configuration, return early
459
- if (!clientList.some((p) => p.express_configuration)) {
547
+ isCimdClient(client) {
548
+ return !!client.external_client_id;
549
+ }
550
+ async createClient(client) {
551
+ if (!this.isCimdClient(client)) {
552
+ return this.client.clients.create(client);
553
+ }
554
+ const externalClientId = client.external_client_id;
555
+ const registration = await this.client.clients.registerCimdClient({
556
+ external_client_id: externalClientId,
557
+ });
558
+ return registration;
559
+ }
560
+ getCIMDEditableFields(client) {
561
+ // Only a subset of fields are editable for CIMD clients.
562
+ return (0, lodash_1.pick)(client, [
563
+ 'description',
564
+ 'app_type',
565
+ 'allowed_origins',
566
+ 'web_origins',
567
+ 'grant_types',
568
+ 'oidc_conformant',
569
+ 'organization_discovery_methods',
570
+ 'client_metadata',
571
+ 'default_organization',
572
+ 'require_proof_of_possession',
573
+ 'token_quota',
574
+ 'skip_non_verifiable_callback_uri_confirmation_prompt',
575
+ 'jwt_configuration',
576
+ 'refresh_token',
577
+ ]);
578
+ }
579
+ async updateClient(clientId, client) {
580
+ // For non-CIMD clients
581
+ if (!this.isCimdClient(client)) {
582
+ return this.client.clients.update(clientId, client);
583
+ }
584
+ const updatePayload = this.getCIMDEditableFields(client);
585
+ const registrationClientId = client.client_id || clientId;
586
+ if (registrationClientId && Object.keys(updatePayload).length > 0) {
587
+ return this.client.clients.update(registrationClientId, updatePayload);
588
+ }
589
+ return client;
590
+ }
591
+ // convert names back to IDs for client reference-based configuration
592
+ async sanitizeMapClientReferences(auth0Client, clientList) {
593
+ const hasExpressConfiguration = clientList.some((client) => client.express_configuration);
594
+ const hasMyOrganizationConfiguration = clientList.some((client) => client.my_organization_configuration);
595
+ if (!hasExpressConfiguration && !hasMyOrganizationConfiguration) {
460
596
  return clientList;
461
597
  }
462
598
  const clientData = await this.getType();
463
599
  const connectionProfiles = await (0, connectionProfiles_1.getConnectionProfile)(auth0Client);
464
600
  const userAttributeProfiles = await (0, userAttributeProfiles_1.getUserAttributeProfiles)(auth0Client);
465
601
  return clientList.map((client) => {
466
- if (!client.express_configuration)
602
+ if (client.express_configuration) {
603
+ const userAttributeProfileName = client.express_configuration.user_attribute_profile_id;
604
+ if (userAttributeProfileName) {
605
+ const userAttributeProfile = userAttributeProfiles?.find((uap) => uap.name === userAttributeProfileName);
606
+ if (userAttributeProfile?.id) {
607
+ client.express_configuration.user_attribute_profile_id = userAttributeProfile.id;
608
+ }
609
+ }
610
+ const connectionProfileName = client.express_configuration.connection_profile_id;
611
+ if (connectionProfileName) {
612
+ const connectionProfile = connectionProfiles?.find((cp) => cp.name === connectionProfileName);
613
+ if (connectionProfile?.id) {
614
+ client.express_configuration.connection_profile_id = connectionProfile.id;
615
+ }
616
+ }
617
+ const oktaOinClientName = client.express_configuration.okta_oin_client_id;
618
+ if (oktaOinClientName) {
619
+ const oktaOinClient = clientData?.find((c) => c.name === oktaOinClientName);
620
+ if (oktaOinClient?.client_id) {
621
+ client.express_configuration.okta_oin_client_id = oktaOinClient.client_id;
622
+ }
623
+ }
624
+ }
625
+ if (!client.my_organization_configuration) {
467
626
  return client;
468
- const userAttributeProfileName = client.express_configuration?.user_attribute_profile_id;
469
- if (userAttributeProfileName) {
470
- const userAttributeProfile = userAttributeProfiles?.find((uap) => uap.name === userAttributeProfileName);
627
+ }
628
+ const myOrganizationUserAttributeProfileName = client.my_organization_configuration.user_attribute_profile_id;
629
+ if (myOrganizationUserAttributeProfileName) {
630
+ const userAttributeProfile = userAttributeProfiles?.find((uap) => uap.name === myOrganizationUserAttributeProfileName);
471
631
  if (userAttributeProfile?.id) {
472
- client.express_configuration.user_attribute_profile_id = userAttributeProfile.id;
632
+ client.my_organization_configuration.user_attribute_profile_id = userAttributeProfile.id;
473
633
  }
474
634
  }
475
- const connectionProfileName = client.express_configuration.connection_profile_id;
476
- if (connectionProfileName) {
477
- const connectionProfile = connectionProfiles?.find((cp) => cp.name === connectionProfileName);
635
+ const myOrganizationConnectionProfileName = client.my_organization_configuration.connection_profile_id;
636
+ if (myOrganizationConnectionProfileName) {
637
+ const connectionProfile = connectionProfiles?.find((cp) => cp.name === myOrganizationConnectionProfileName);
478
638
  if (connectionProfile?.id) {
479
- client.express_configuration.connection_profile_id = connectionProfile.id;
480
- }
481
- }
482
- const oktaOinClientName = client.express_configuration.okta_oin_client_id;
483
- if (oktaOinClientName) {
484
- const oktaOinClient = clientData?.find((c) => c.name === oktaOinClientName);
485
- if (oktaOinClient?.client_id) {
486
- client.express_configuration.okta_oin_client_id = oktaOinClient.client_id;
639
+ client.my_organization_configuration.connection_profile_id = connectionProfile.id;
487
640
  }
488
641
  }
489
642
  return client;
@@ -195,6 +195,7 @@ export default class ConnectionsHandler extends DefaultAPIHandler {
195
195
  processConnectionDirectoryProvisioning(changes: CalculatedChanges): Promise<void>;
196
196
  getType(): Promise<Asset[] | null>;
197
197
  calcChanges(assets: Assets): Promise<CalculatedChanges>;
198
+ dryRunChanges(assets: Assets): Promise<CalculatedChanges>;
198
199
  processChanges(assets: Assets): Promise<void>;
199
200
  }
200
201
  export {};
@@ -543,11 +543,14 @@ class ConnectionsHandler extends default_1.default {
543
543
  });
544
544
  // Prepare an id map. We'll use this map later to get the `strategy` and SCIM enable status of the connections.
545
545
  await this.scimHandler.createIdMap(existingConnections);
546
- const formatted = connections.map((connection) => ({
547
- ...connection,
548
- ...this.getFormattedOptions(connection, clients),
549
- enabled_clients: (0, utils_1.getEnabledClients)(assets, connection, existingConnections, clients),
550
- }));
546
+ const formatted = connections.map((connection) => {
547
+ const enabledClients = (0, utils_1.getEnabledClients)(assets, connection, existingConnections, clients);
548
+ return {
549
+ ...connection,
550
+ ...this.getFormattedOptions(connection, clients),
551
+ ...(enabledClients !== undefined && { enabled_clients: enabledClients }),
552
+ };
553
+ });
551
554
  const proposedChanges = await super.calcChanges({ ...assets, connections: formatted });
552
555
  const proposedChangesWithExcludedProperties = (0, exports.addExcludedConnectionPropertiesToChanges)({
553
556
  proposedChanges,
@@ -556,6 +559,40 @@ class ConnectionsHandler extends default_1.default {
556
559
  });
557
560
  return proposedChangesWithExcludedProperties;
558
561
  }
562
+ async dryRunChanges(assets) {
563
+ const { connections } = assets;
564
+ if (!connections) {
565
+ return {
566
+ del: [],
567
+ create: [],
568
+ update: [],
569
+ conflicts: [],
570
+ };
571
+ }
572
+ const clients = await (0, client_1.paginate)(this.client.clients.list, {
573
+ paginate: true,
574
+ include_totals: true,
575
+ });
576
+ const existingConnections = await (0, client_1.paginate)(this.client.connections.list, {
577
+ checkpoint: true,
578
+ include_totals: true,
579
+ });
580
+ await this.scimHandler.createIdMap(existingConnections);
581
+ const formatted = connections.map((connection) => {
582
+ const enabledClients = (0, utils_1.getEnabledClients)(assets, connection, existingConnections, clients);
583
+ return {
584
+ ...connection,
585
+ ...this.getFormattedOptions(connection, clients),
586
+ ...(enabledClients !== undefined && { enabled_clients: enabledClients }),
587
+ };
588
+ });
589
+ const proposedChanges = await super.dryRunChanges({ ...assets, connections: formatted });
590
+ return (0, exports.addExcludedConnectionPropertiesToChanges)({
591
+ proposedChanges,
592
+ existingConnections,
593
+ config: this.config,
594
+ });
595
+ }
559
596
  // Run after clients are updated so we can convert all the enabled_clients names to id's
560
597
  async processChanges(assets) {
561
598
  const { connections } = assets;
@@ -37,6 +37,11 @@ export declare const schema: {
37
37
  };
38
38
  api_behavior: {
39
39
  type: string;
40
+ enum: string[];
41
+ };
42
+ signup_behavior: {
43
+ type: string;
44
+ enum: string[];
40
45
  };
41
46
  };
42
47
  };
@@ -190,6 +195,90 @@ export declare const schema: {
190
195
  };
191
196
  };
192
197
  };
198
+ password_options: {
199
+ type: string;
200
+ properties: {
201
+ complexity: {
202
+ type: string;
203
+ properties: {
204
+ min_length: {
205
+ type: string;
206
+ minimum: number;
207
+ maximum: number;
208
+ };
209
+ character_types: {
210
+ type: string;
211
+ items: {
212
+ type: string;
213
+ enum: string[];
214
+ };
215
+ };
216
+ character_type_rule: {
217
+ type: string;
218
+ enum: string[];
219
+ };
220
+ identical_characters: {
221
+ type: string;
222
+ enum: string[];
223
+ };
224
+ sequential_characters: {
225
+ type: string;
226
+ enum: string[];
227
+ };
228
+ max_length_exceeded: {
229
+ type: string;
230
+ enum: string[];
231
+ };
232
+ };
233
+ };
234
+ profile_data: {
235
+ type: string;
236
+ properties: {
237
+ active: {
238
+ type: string;
239
+ };
240
+ blocked_fields: {
241
+ type: string;
242
+ items: {
243
+ type: string;
244
+ };
245
+ maxItems: number;
246
+ };
247
+ };
248
+ };
249
+ history: {
250
+ type: string;
251
+ properties: {
252
+ active: {
253
+ type: string;
254
+ };
255
+ size: {
256
+ type: string;
257
+ minimum: number;
258
+ maximum: number;
259
+ };
260
+ };
261
+ };
262
+ dictionary: {
263
+ type: string;
264
+ properties: {
265
+ active: {
266
+ type: string;
267
+ };
268
+ default: {
269
+ type: string;
270
+ enum: string[];
271
+ };
272
+ custom: {
273
+ type: string;
274
+ items: {
275
+ type: string;
276
+ };
277
+ };
278
+ };
279
+ };
280
+ };
281
+ };
193
282
  };
194
283
  };
195
284
  };
@@ -202,10 +291,12 @@ export default class DatabaseHandler extends DefaultAPIHandler {
202
291
  objString(db: any): string;
203
292
  getFormattedOptions(options: any, actions?: Action[]): any;
204
293
  validate(assets: Assets): Promise<void>;
294
+ private validatePasswordOptions;
205
295
  private validatePasswordlessSettings;
206
296
  private validateEmailUniqueConstraints;
207
297
  getClientFN(fn: 'create' | 'delete' | 'getAll' | 'update'): Function;
208
298
  getType(): Promise<Connection[]>;
209
299
  calcChanges(assets: Assets): Promise<CalculatedChanges>;
300
+ dryRunChanges(assets: Assets): Promise<CalculatedChanges>;
210
301
  processChanges(assets: Assets): Promise<void>;
211
302
  }