@highflame/policy 2.0.7 → 2.0.9
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/_schemas/overwatch/context.json +163 -1
- package/_schemas/overwatch/schema.cedarschema +45 -0
- package/dist/actions.gen.d.ts +0 -1
- package/dist/actions.gen.js +0 -1
- package/dist/annotations.d.ts +0 -1
- package/dist/annotations.js +0 -1
- package/dist/builder.d.ts +0 -1
- package/dist/builder.js +0 -1
- package/dist/context.gen.d.ts +0 -1
- package/dist/context.gen.js +0 -1
- package/dist/engine.d.ts +0 -1
- package/dist/engine.js +0 -1
- package/dist/entities.gen.d.ts +0 -1
- package/dist/entities.gen.js +0 -1
- package/dist/entity-metadata-types.gen.d.ts +0 -1
- package/dist/entity-metadata-types.gen.js +0 -1
- package/dist/errors.d.ts +0 -1
- package/dist/errors.js +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/overwatch-context.gen.d.ts +13 -1
- package/dist/overwatch-context.gen.js +13 -1
- package/dist/overwatch-defaults.gen.d.ts +1 -2
- package/dist/overwatch-defaults.gen.js +346 -2
- package/dist/overwatch-entities.gen.d.ts +0 -1
- package/dist/overwatch-entities.gen.js +0 -1
- package/dist/palisade-context.gen.d.ts +0 -1
- package/dist/palisade-context.gen.js +0 -1
- package/dist/palisade-entities.gen.d.ts +0 -1
- package/dist/palisade-entities.gen.js +0 -1
- package/dist/parser.d.ts +0 -1
- package/dist/parser.js +0 -1
- package/dist/schema.gen.d.ts +0 -1
- package/dist/schema.gen.js +0 -1
- package/dist/schemas.d.ts +0 -1
- package/dist/schemas.js +0 -1
- package/dist/service-schemas.gen.d.ts +0 -1
- package/dist/service-schemas.gen.js +0 -1
- package/dist/types.d.ts +0 -1
- package/dist/types.js +0 -1
- package/package.json +1 -2
- package/dist/actions.gen.d.ts.map +0 -1
- package/dist/actions.gen.js.map +0 -1
- package/dist/annotations.d.ts.map +0 -1
- package/dist/annotations.js.map +0 -1
- package/dist/builder.d.ts.map +0 -1
- package/dist/builder.js.map +0 -1
- package/dist/context.gen.d.ts.map +0 -1
- package/dist/context.gen.js.map +0 -1
- package/dist/engine.d.ts.map +0 -1
- package/dist/engine.js.map +0 -1
- package/dist/engine.test.d.ts +0 -8
- package/dist/engine.test.d.ts.map +0 -1
- package/dist/engine.test.js +0 -190
- package/dist/engine.test.js.map +0 -1
- package/dist/entities.gen.d.ts.map +0 -1
- package/dist/entities.gen.js.map +0 -1
- package/dist/entity-metadata-types.gen.d.ts.map +0 -1
- package/dist/entity-metadata-types.gen.js.map +0 -1
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/overwatch-context.gen.d.ts.map +0 -1
- package/dist/overwatch-context.gen.js.map +0 -1
- package/dist/overwatch-defaults.gen.d.ts.map +0 -1
- package/dist/overwatch-defaults.gen.js.map +0 -1
- package/dist/overwatch-defaults.test.d.ts +0 -8
- package/dist/overwatch-defaults.test.d.ts.map +0 -1
- package/dist/overwatch-defaults.test.js +0 -145
- package/dist/overwatch-defaults.test.js.map +0 -1
- package/dist/overwatch-entities.gen.d.ts.map +0 -1
- package/dist/overwatch-entities.gen.js.map +0 -1
- package/dist/overwatch-rebac.test.d.ts +0 -25
- package/dist/overwatch-rebac.test.d.ts.map +0 -1
- package/dist/overwatch-rebac.test.js +0 -301
- package/dist/overwatch-rebac.test.js.map +0 -1
- package/dist/palisade-context.gen.d.ts.map +0 -1
- package/dist/palisade-context.gen.js.map +0 -1
- package/dist/palisade-entities.gen.d.ts.map +0 -1
- package/dist/palisade-entities.gen.js.map +0 -1
- package/dist/parser.d.ts.map +0 -1
- package/dist/parser.js.map +0 -1
- package/dist/parser.test.d.ts +0 -8
- package/dist/parser.test.d.ts.map +0 -1
- package/dist/parser.test.js +0 -212
- package/dist/parser.test.js.map +0 -1
- package/dist/schema.gen.d.ts.map +0 -1
- package/dist/schema.gen.js.map +0 -1
- package/dist/schemas.d.ts.map +0 -1
- package/dist/schemas.js.map +0 -1
- package/dist/schemas.test.d.ts +0 -8
- package/dist/schemas.test.d.ts.map +0 -1
- package/dist/schemas.test.js +0 -375
- package/dist/schemas.test.js.map +0 -1
- package/dist/service-schemas.gen.d.ts.map +0 -1
- package/dist/service-schemas.gen.js.map +0 -1
- package/dist/studio-ui.test.d.ts +0 -8
- package/dist/studio-ui.test.d.ts.map +0 -1
- package/dist/studio-ui.test.js +0 -687
- package/dist/studio-ui.test.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/src/actions.gen.ts +0 -57
- package/src/annotations.ts +0 -243
- package/src/builder.ts +0 -799
- package/src/context.gen.ts +0 -10
- package/src/engine.test.ts +0 -370
- package/src/engine.ts +0 -497
- package/src/entities.gen.ts +0 -65
- package/src/entity-metadata-types.gen.ts +0 -19
- package/src/errors.ts +0 -195
- package/src/index.ts +0 -62
- package/src/overwatch-context.gen.ts +0 -32
- package/src/overwatch-defaults.gen.ts +0 -907
- package/src/overwatch-defaults.test.ts +0 -176
- package/src/overwatch-entities.gen.ts +0 -41
- package/src/overwatch-rebac.test.ts +0 -346
- package/src/palisade-context.gen.ts +0 -28
- package/src/palisade-entities.gen.ts +0 -49
- package/src/parser.test.ts +0 -251
- package/src/parser.ts +0 -579
- package/src/schema.gen.ts +0 -134
- package/src/schemas.test.ts +0 -445
- package/src/schemas.ts +0 -91
- package/src/service-schemas.gen.ts +0 -608
- package/src/studio-ui.test.ts +0 -813
- package/src/types.ts +0 -66
package/src/context.gen.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
// Code generated by highflame-policy-codegen. DO NOT EDIT.
|
|
2
|
-
// Source: schema/context.yaml
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Context attribute keys for Cedar policy evaluation.
|
|
6
|
-
*/
|
|
7
|
-
export const ContextKey = {
|
|
8
|
-
} as const;
|
|
9
|
-
|
|
10
|
-
export type ContextKey = (typeof ContextKey)[keyof typeof ContextKey];
|
package/src/engine.test.ts
DELETED
|
@@ -1,370 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Engine unit tests
|
|
3
|
-
*
|
|
4
|
-
* Tests the PolicyEngine evaluate() function.
|
|
5
|
-
* These tests are consistent across Go, TypeScript, and Python SDKs.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
9
|
-
import {
|
|
10
|
-
PolicyEngine,
|
|
11
|
-
Decision,
|
|
12
|
-
EntityType,
|
|
13
|
-
ActionType,
|
|
14
|
-
InputValidationError,
|
|
15
|
-
DEFAULT_LIMITS,
|
|
16
|
-
} from './index.js';
|
|
17
|
-
|
|
18
|
-
describe('PolicyEngine', () => {
|
|
19
|
-
let engine: PolicyEngine;
|
|
20
|
-
|
|
21
|
-
const permitAllPolicy = `
|
|
22
|
-
permit(principal, action, resource);
|
|
23
|
-
`;
|
|
24
|
-
|
|
25
|
-
const denyAllPolicy = `
|
|
26
|
-
forbid(principal, action, resource);
|
|
27
|
-
`;
|
|
28
|
-
|
|
29
|
-
// Simple context-based policy without action constraint for testing context evaluation
|
|
30
|
-
const contextBasedPolicy = `
|
|
31
|
-
@id("allow-production")
|
|
32
|
-
permit(
|
|
33
|
-
principal,
|
|
34
|
-
action,
|
|
35
|
-
resource
|
|
36
|
-
)
|
|
37
|
-
when { context.environment == "production" };
|
|
38
|
-
|
|
39
|
-
@id("deny-all")
|
|
40
|
-
forbid(principal, action, resource);
|
|
41
|
-
`;
|
|
42
|
-
|
|
43
|
-
beforeEach(() => {
|
|
44
|
-
engine = new PolicyEngine();
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
describe('basic evaluation', () => {
|
|
48
|
-
it('should allow when permit policy matches', () => {
|
|
49
|
-
engine.loadPolicy(permitAllPolicy);
|
|
50
|
-
|
|
51
|
-
const decision = engine.evaluateSimple(
|
|
52
|
-
EntityType.Scanner,
|
|
53
|
-
'test-scanner',
|
|
54
|
-
ActionType.ScanArtifact,
|
|
55
|
-
EntityType.Artifact,
|
|
56
|
-
'/model.safetensors'
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
expect(decision.effect).toBe('Allow');
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('should deny when forbid policy matches', () => {
|
|
63
|
-
engine.loadPolicy(denyAllPolicy);
|
|
64
|
-
|
|
65
|
-
const decision = engine.evaluateSimple(
|
|
66
|
-
EntityType.Scanner,
|
|
67
|
-
'test-scanner',
|
|
68
|
-
ActionType.ScanArtifact,
|
|
69
|
-
EntityType.Artifact,
|
|
70
|
-
'/model.safetensors'
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
expect(decision.effect).toBe('Deny');
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it('should deny when no policies match (default deny)', () => {
|
|
77
|
-
engine.loadPolicy(''); // No policies
|
|
78
|
-
|
|
79
|
-
const decision = engine.evaluateSimple(
|
|
80
|
-
EntityType.Scanner,
|
|
81
|
-
'test-scanner',
|
|
82
|
-
ActionType.ScanArtifact,
|
|
83
|
-
EntityType.Artifact,
|
|
84
|
-
'/model.safetensors'
|
|
85
|
-
);
|
|
86
|
-
|
|
87
|
-
expect(decision.effect).toBe('Deny');
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
describe('context-based evaluation', () => {
|
|
92
|
-
beforeEach(() => {
|
|
93
|
-
engine.loadPolicy(contextBasedPolicy);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it('should allow when context matches permit condition', () => {
|
|
97
|
-
// Use simple permit policy to test context evaluation in isolation
|
|
98
|
-
const simplePermitPolicy = `
|
|
99
|
-
permit(principal, action, resource)
|
|
100
|
-
when { context.environment == "production" };
|
|
101
|
-
`;
|
|
102
|
-
const testEngine = new PolicyEngine();
|
|
103
|
-
testEngine.loadPolicy(simplePermitPolicy);
|
|
104
|
-
|
|
105
|
-
const decision = testEngine.evaluateSimple(
|
|
106
|
-
EntityType.Scanner,
|
|
107
|
-
'palisade',
|
|
108
|
-
ActionType.ScanArtifact,
|
|
109
|
-
EntityType.Artifact,
|
|
110
|
-
'/model.safetensors',
|
|
111
|
-
{ environment: 'production' }
|
|
112
|
-
);
|
|
113
|
-
|
|
114
|
-
expect(decision.effect).toBe('Allow');
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it('should deny when context does not match permit condition', () => {
|
|
118
|
-
const decision = engine.evaluateSimple(
|
|
119
|
-
EntityType.Scanner,
|
|
120
|
-
'palisade',
|
|
121
|
-
ActionType.ScanArtifact,
|
|
122
|
-
EntityType.Artifact,
|
|
123
|
-
'/model.safetensors',
|
|
124
|
-
{ environment: 'development' }
|
|
125
|
-
);
|
|
126
|
-
|
|
127
|
-
expect(decision.effect).toBe('Deny');
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
it('should deny when context is missing required field', () => {
|
|
131
|
-
const decision = engine.evaluateSimple(
|
|
132
|
-
EntityType.Scanner,
|
|
133
|
-
'palisade',
|
|
134
|
-
ActionType.ScanArtifact,
|
|
135
|
-
EntityType.Artifact,
|
|
136
|
-
'/model.safetensors',
|
|
137
|
-
{} // Missing environment
|
|
138
|
-
);
|
|
139
|
-
|
|
140
|
-
expect(decision.effect).toBe('Deny');
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
describe('input validation', () => {
|
|
145
|
-
beforeEach(() => {
|
|
146
|
-
engine.loadPolicy(permitAllPolicy);
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
it('should accept valid context', () => {
|
|
150
|
-
const decision = engine.evaluateSimple(
|
|
151
|
-
EntityType.Scanner,
|
|
152
|
-
'test',
|
|
153
|
-
ActionType.ScanArtifact,
|
|
154
|
-
EntityType.Artifact,
|
|
155
|
-
'/model.safetensors',
|
|
156
|
-
{
|
|
157
|
-
environment: 'production',
|
|
158
|
-
severity: 'HIGH',
|
|
159
|
-
count: 42,
|
|
160
|
-
enabled: true,
|
|
161
|
-
}
|
|
162
|
-
);
|
|
163
|
-
|
|
164
|
-
expect(decision.effect).toBe('Allow');
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
it('should reject context with too many keys', () => {
|
|
168
|
-
const engine = new PolicyEngine({ limits: { maxContextKeys: 5 } });
|
|
169
|
-
engine.loadPolicy(permitAllPolicy);
|
|
170
|
-
|
|
171
|
-
const bigContext: Record<string, unknown> = {};
|
|
172
|
-
for (let i = 0; i < 10; i++) {
|
|
173
|
-
bigContext[`key${i}`] = 'value';
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
expect(() => {
|
|
177
|
-
engine.evaluateSimple(
|
|
178
|
-
EntityType.Scanner,
|
|
179
|
-
'test',
|
|
180
|
-
ActionType.ScanArtifact,
|
|
181
|
-
EntityType.Artifact,
|
|
182
|
-
'/model.safetensors',
|
|
183
|
-
bigContext
|
|
184
|
-
);
|
|
185
|
-
}).toThrow(InputValidationError);
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
it('should reject context with too long strings', () => {
|
|
189
|
-
const engine = new PolicyEngine({ limits: { maxStringLength: 100 } });
|
|
190
|
-
engine.loadPolicy(permitAllPolicy);
|
|
191
|
-
|
|
192
|
-
const longString = 'x'.repeat(200);
|
|
193
|
-
|
|
194
|
-
expect(() => {
|
|
195
|
-
engine.evaluateSimple(
|
|
196
|
-
EntityType.Scanner,
|
|
197
|
-
'test',
|
|
198
|
-
ActionType.ScanArtifact,
|
|
199
|
-
EntityType.Artifact,
|
|
200
|
-
'/model.safetensors',
|
|
201
|
-
{ value: longString }
|
|
202
|
-
);
|
|
203
|
-
}).toThrow(InputValidationError);
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
it('should reject deeply nested context', () => {
|
|
207
|
-
const engine = new PolicyEngine({ limits: { maxNestingDepth: 3 } });
|
|
208
|
-
engine.loadPolicy(permitAllPolicy);
|
|
209
|
-
|
|
210
|
-
const deepContext = {
|
|
211
|
-
level1: {
|
|
212
|
-
level2: {
|
|
213
|
-
level3: {
|
|
214
|
-
level4: {
|
|
215
|
-
level5: 'too deep',
|
|
216
|
-
},
|
|
217
|
-
},
|
|
218
|
-
},
|
|
219
|
-
},
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
expect(() => {
|
|
223
|
-
engine.evaluateSimple(
|
|
224
|
-
EntityType.Scanner,
|
|
225
|
-
'test',
|
|
226
|
-
ActionType.ScanArtifact,
|
|
227
|
-
EntityType.Artifact,
|
|
228
|
-
'/model.safetensors',
|
|
229
|
-
deepContext
|
|
230
|
-
);
|
|
231
|
-
}).toThrow(InputValidationError);
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
it('should allow skipping validation', () => {
|
|
235
|
-
const engine = new PolicyEngine({
|
|
236
|
-
skipValidation: true,
|
|
237
|
-
limits: { maxContextKeys: 1 },
|
|
238
|
-
});
|
|
239
|
-
engine.loadPolicy(permitAllPolicy);
|
|
240
|
-
|
|
241
|
-
// This would normally fail validation
|
|
242
|
-
const decision = engine.evaluateSimple(
|
|
243
|
-
EntityType.Scanner,
|
|
244
|
-
'test',
|
|
245
|
-
ActionType.ScanArtifact,
|
|
246
|
-
EntityType.Artifact,
|
|
247
|
-
'/model.safetensors',
|
|
248
|
-
{ key1: 'value1', key2: 'value2', key3: 'value3' }
|
|
249
|
-
);
|
|
250
|
-
|
|
251
|
-
expect(decision.effect).toBe('Allow');
|
|
252
|
-
});
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
describe('complex context types', () => {
|
|
256
|
-
beforeEach(() => {
|
|
257
|
-
engine.loadPolicy(permitAllPolicy);
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
it('should handle array context values', () => {
|
|
261
|
-
const decision = engine.evaluateSimple(
|
|
262
|
-
EntityType.Scanner,
|
|
263
|
-
'test',
|
|
264
|
-
ActionType.ScanArtifact,
|
|
265
|
-
EntityType.Artifact,
|
|
266
|
-
'/model.safetensors',
|
|
267
|
-
{ threats: ['malware', 'backdoor', 'injection'] }
|
|
268
|
-
);
|
|
269
|
-
|
|
270
|
-
expect(decision.effect).toBe('Allow');
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
it('should handle nested object context', () => {
|
|
274
|
-
const decision = engine.evaluateSimple(
|
|
275
|
-
EntityType.Scanner,
|
|
276
|
-
'test',
|
|
277
|
-
ActionType.ScanArtifact,
|
|
278
|
-
EntityType.Artifact,
|
|
279
|
-
'/model.safetensors',
|
|
280
|
-
{
|
|
281
|
-
metadata: {
|
|
282
|
-
format: 'safetensors',
|
|
283
|
-
size: 1024,
|
|
284
|
-
},
|
|
285
|
-
}
|
|
286
|
-
);
|
|
287
|
-
|
|
288
|
-
expect(decision.effect).toBe('Allow');
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
it('should handle empty context', () => {
|
|
292
|
-
// Cedar doesn't have null values - this tests that empty context works
|
|
293
|
-
const decision = engine.evaluateSimple(
|
|
294
|
-
EntityType.Scanner,
|
|
295
|
-
'test',
|
|
296
|
-
ActionType.ScanArtifact,
|
|
297
|
-
EntityType.Artifact,
|
|
298
|
-
'/model.safetensors',
|
|
299
|
-
{}
|
|
300
|
-
);
|
|
301
|
-
|
|
302
|
-
expect(decision.effect).toBe('Allow');
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
it('should handle boolean context values', () => {
|
|
306
|
-
const decision = engine.evaluateSimple(
|
|
307
|
-
EntityType.Scanner,
|
|
308
|
-
'test',
|
|
309
|
-
ActionType.ScanArtifact,
|
|
310
|
-
EntityType.Artifact,
|
|
311
|
-
'/model.safetensors',
|
|
312
|
-
{ is_signed: true, is_malicious: false }
|
|
313
|
-
);
|
|
314
|
-
|
|
315
|
-
expect(decision.effect).toBe('Allow');
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
it('should handle integer context values', () => {
|
|
319
|
-
// Cedar uses Long type for numbers (integers only, no floats)
|
|
320
|
-
const decision = engine.evaluateSimple(
|
|
321
|
-
EntityType.Scanner,
|
|
322
|
-
'test',
|
|
323
|
-
ActionType.ScanArtifact,
|
|
324
|
-
EntityType.Artifact,
|
|
325
|
-
'/model.safetensors',
|
|
326
|
-
{ severity_score: 7, count: 100 }
|
|
327
|
-
);
|
|
328
|
-
|
|
329
|
-
expect(decision.effect).toBe('Allow');
|
|
330
|
-
});
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
describe('error handling', () => {
|
|
334
|
-
it('should handle invalid policy syntax gracefully', () => {
|
|
335
|
-
const invalidPolicy = `permit(principal, action, resource`; // Missing closing paren
|
|
336
|
-
|
|
337
|
-
expect(() => {
|
|
338
|
-
engine.loadPolicy(invalidPolicy);
|
|
339
|
-
engine.evaluateSimple(
|
|
340
|
-
EntityType.Scanner,
|
|
341
|
-
'test',
|
|
342
|
-
ActionType.ScanArtifact,
|
|
343
|
-
EntityType.Artifact,
|
|
344
|
-
'/model.safetensors'
|
|
345
|
-
);
|
|
346
|
-
}).not.toThrow();
|
|
347
|
-
|
|
348
|
-
// Should return deny with reason
|
|
349
|
-
engine.loadPolicy(invalidPolicy);
|
|
350
|
-
const decision = engine.evaluateSimple(
|
|
351
|
-
EntityType.Scanner,
|
|
352
|
-
'test',
|
|
353
|
-
ActionType.ScanArtifact,
|
|
354
|
-
EntityType.Artifact,
|
|
355
|
-
'/model.safetensors'
|
|
356
|
-
);
|
|
357
|
-
|
|
358
|
-
expect(decision.effect).toBe('Deny');
|
|
359
|
-
});
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
describe('DEFAULT_LIMITS', () => {
|
|
363
|
-
it('should have consistent default values', () => {
|
|
364
|
-
expect(DEFAULT_LIMITS.maxContextKeys).toBe(100);
|
|
365
|
-
expect(DEFAULT_LIMITS.maxStringLength).toBe(1_000_000);
|
|
366
|
-
expect(DEFAULT_LIMITS.maxNestingDepth).toBe(10);
|
|
367
|
-
expect(DEFAULT_LIMITS.maxContextSizeBytes).toBe(10_000_000);
|
|
368
|
-
});
|
|
369
|
-
});
|
|
370
|
-
});
|