@opentdf/sdk 0.4.0-beta.4 → 0.4.0-beta.41
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/dist/cjs/src/access/access-fetch.js +2 -1
- package/dist/cjs/src/access/access-rpc.js +11 -5
- package/dist/cjs/src/access/constants.js +6 -0
- package/dist/cjs/src/access.js +39 -4
- package/dist/cjs/src/auth/oidc-clientcredentials-provider.js +4 -2
- package/dist/cjs/src/auth/oidc-externaljwt-provider.js +5 -3
- package/dist/cjs/src/auth/oidc-refreshtoken-provider.js +19 -3
- package/dist/cjs/src/auth/oidc.js +9 -8
- package/dist/cjs/src/auth/providers.js +7 -1
- package/dist/cjs/src/index.js +4 -2
- package/dist/cjs/src/nanoclients.js +4 -4
- package/dist/cjs/src/nanotdf/Client.js +10 -6
- package/dist/cjs/src/opentdf.js +103 -13
- package/dist/cjs/src/platform/authorization/v2/authorization_pb.js +112 -0
- package/dist/cjs/src/platform/buf/validate/validate_pb.js +114 -170
- package/dist/cjs/src/platform/common/common_pb.js +16 -5
- package/dist/cjs/src/platform/entity/entity_pb.js +51 -0
- package/dist/cjs/src/platform/entityresolution/entity_resolution_pb.js +1 -1
- package/dist/cjs/src/platform/entityresolution/v2/entity_resolution_pb.js +49 -0
- package/dist/cjs/src/platform/google/api/annotations_pb.js +1 -1
- package/dist/cjs/src/platform/google/api/http_pb.js +3 -3
- package/dist/cjs/src/platform/kas/kas_pb.js +2 -2
- package/dist/cjs/src/platform/policy/attributes/attributes_pb.js +12 -2
- package/dist/cjs/src/platform/policy/kasregistry/key_access_server_registry_pb.js +57 -4
- package/dist/cjs/src/platform/policy/keymanagement/key_management_pb.js +2 -2
- package/dist/cjs/src/platform/policy/namespaces/namespaces_pb.js +31 -4
- package/dist/cjs/src/platform/policy/objects_pb.js +116 -42
- package/dist/cjs/src/platform/policy/obligations/obligations_pb.js +159 -0
- package/dist/cjs/src/platform/policy/registeredresources/registered_resources_pb.js +20 -15
- package/dist/cjs/src/platform/policy/resourcemapping/resource_mapping_pb.js +2 -3
- package/dist/cjs/src/platform/policy/selectors_pb.js +1 -1
- package/dist/cjs/src/platform/policy/subjectmapping/subject_mapping_pb.js +2 -3
- package/dist/cjs/src/platform/policy/unsafe/unsafe_pb.js +2 -4
- package/dist/cjs/src/platform.js +20 -3
- package/dist/cjs/src/policy/api.js +27 -7
- package/dist/cjs/src/policy/granter.js +75 -48
- package/dist/cjs/src/seekable.js +32 -1
- package/dist/cjs/src/utils.js +85 -3
- package/dist/cjs/tdf3/src/assertions.js +39 -2
- package/dist/cjs/tdf3/src/client/DecoratedReadableStream.js +8 -1
- package/dist/cjs/tdf3/src/client/builders.js +13 -1
- package/dist/cjs/tdf3/src/client/index.js +213 -54
- package/dist/cjs/tdf3/src/client/validation.js +3 -3
- package/dist/cjs/tdf3/src/tdf.js +42 -9
- package/dist/cjs/tdf3/src/utils/unwrap.js +2 -2
- package/dist/types/src/access/access-fetch.d.ts +1 -0
- package/dist/types/src/access/access-fetch.d.ts.map +1 -1
- package/dist/types/src/access/access-rpc.d.ts +2 -1
- package/dist/types/src/access/access-rpc.d.ts.map +1 -1
- package/dist/types/src/access/constants.d.ts +3 -0
- package/dist/types/src/access/constants.d.ts.map +1 -0
- package/dist/types/src/access.d.ts +30 -1
- package/dist/types/src/access.d.ts.map +1 -1
- package/dist/types/src/auth/oidc-clientcredentials-provider.d.ts +1 -1
- package/dist/types/src/auth/oidc-clientcredentials-provider.d.ts.map +1 -1
- package/dist/types/src/auth/oidc-externaljwt-provider.d.ts +1 -1
- package/dist/types/src/auth/oidc-externaljwt-provider.d.ts.map +1 -1
- package/dist/types/src/auth/oidc-refreshtoken-provider.d.ts +15 -1
- package/dist/types/src/auth/oidc-refreshtoken-provider.d.ts.map +1 -1
- package/dist/types/src/auth/oidc.d.ts +4 -0
- package/dist/types/src/auth/oidc.d.ts.map +1 -1
- package/dist/types/src/auth/providers.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/nanotdf/Client.d.ts +8 -1
- package/dist/types/src/nanotdf/Client.d.ts.map +1 -1
- package/dist/types/src/opentdf.d.ts +137 -6
- package/dist/types/src/opentdf.d.ts.map +1 -1
- package/dist/types/src/platform/authorization/v2/authorization_pb.d.ts +439 -0
- package/dist/types/src/platform/authorization/v2/authorization_pb.d.ts.map +1 -0
- package/dist/types/src/platform/buf/validate/validate_pb.d.ts +495 -370
- package/dist/types/src/platform/buf/validate/validate_pb.d.ts.map +1 -1
- package/dist/types/src/platform/common/common_pb.d.ts +36 -0
- package/dist/types/src/platform/common/common_pb.d.ts.map +1 -1
- package/dist/types/src/platform/entity/entity_pb.d.ts +130 -0
- package/dist/types/src/platform/entity/entity_pb.d.ts.map +1 -0
- package/dist/types/src/platform/entityresolution/entity_resolution_pb.d.ts +4 -0
- package/dist/types/src/platform/entityresolution/entity_resolution_pb.d.ts.map +1 -1
- package/dist/types/src/platform/entityresolution/v2/entity_resolution_pb.d.ts +136 -0
- package/dist/types/src/platform/entityresolution/v2/entity_resolution_pb.d.ts.map +1 -0
- package/dist/types/src/platform/google/api/http_pb.d.ts.map +1 -1
- package/dist/types/src/platform/kas/kas_pb.d.ts +5 -0
- package/dist/types/src/platform/kas/kas_pb.d.ts.map +1 -1
- package/dist/types/src/platform/policy/attributes/attributes_pb.d.ts +44 -13
- package/dist/types/src/platform/policy/attributes/attributes_pb.d.ts.map +1 -1
- package/dist/types/src/platform/policy/kasregistry/key_access_server_registry_pb.d.ts +329 -24
- package/dist/types/src/platform/policy/kasregistry/key_access_server_registry_pb.d.ts.map +1 -1
- package/dist/types/src/platform/policy/keymanagement/key_management_pb.d.ts +20 -1
- package/dist/types/src/platform/policy/keymanagement/key_management_pb.d.ts.map +1 -1
- package/dist/types/src/platform/policy/namespaces/namespaces_pb.d.ts +143 -5
- package/dist/types/src/platform/policy/namespaces/namespaces_pb.d.ts.map +1 -1
- package/dist/types/src/platform/policy/objects_pb.d.ts +382 -33
- package/dist/types/src/platform/policy/objects_pb.d.ts.map +1 -1
- package/dist/types/src/platform/policy/obligations/obligations_pb.d.ts +670 -0
- package/dist/types/src/platform/policy/obligations/obligations_pb.d.ts.map +1 -0
- package/dist/types/src/platform/policy/registeredresources/registered_resources_pb.d.ts +67 -0
- package/dist/types/src/platform/policy/registeredresources/registered_resources_pb.d.ts.map +1 -1
- package/dist/types/src/platform/policy/resourcemapping/resource_mapping_pb.d.ts.map +1 -1
- package/dist/types/src/platform/policy/selectors_pb.d.ts +18 -0
- package/dist/types/src/platform/policy/selectors_pb.d.ts.map +1 -1
- package/dist/types/src/platform/policy/subjectmapping/subject_mapping_pb.d.ts.map +1 -1
- package/dist/types/src/platform/policy/unsafe/unsafe_pb.d.ts +18 -4
- package/dist/types/src/platform/policy/unsafe/unsafe_pb.d.ts.map +1 -1
- package/dist/types/src/platform.d.ts +21 -0
- package/dist/types/src/platform.d.ts.map +1 -1
- package/dist/types/src/policy/api.d.ts +2 -0
- package/dist/types/src/policy/api.d.ts.map +1 -1
- package/dist/types/src/policy/granter.d.ts +11 -6
- package/dist/types/src/policy/granter.d.ts.map +1 -1
- package/dist/types/src/seekable.d.ts +31 -0
- package/dist/types/src/seekable.d.ts.map +1 -1
- package/dist/types/src/utils.d.ts +61 -2
- package/dist/types/src/utils.d.ts.map +1 -1
- package/dist/types/tdf3/src/assertions.d.ts +4 -0
- package/dist/types/tdf3/src/assertions.d.ts.map +1 -1
- package/dist/types/tdf3/src/client/DecoratedReadableStream.d.ts +6 -0
- package/dist/types/tdf3/src/client/DecoratedReadableStream.d.ts.map +1 -1
- package/dist/types/tdf3/src/client/builders.d.ts +14 -0
- package/dist/types/tdf3/src/client/builders.d.ts.map +1 -1
- package/dist/types/tdf3/src/client/index.d.ts +25 -4
- package/dist/types/tdf3/src/client/index.d.ts.map +1 -1
- package/dist/types/tdf3/src/client/validation.d.ts +3 -3
- package/dist/types/tdf3/src/client/validation.d.ts.map +1 -1
- package/dist/types/tdf3/src/tdf.d.ts +3 -1
- package/dist/types/tdf3/src/tdf.d.ts.map +1 -1
- package/dist/types/tdf3/src/utils/unwrap.d.ts.map +1 -1
- package/dist/web/src/access/access-fetch.js +2 -1
- package/dist/web/src/access/access-rpc.js +11 -5
- package/dist/web/src/access/constants.js +3 -0
- package/dist/web/src/access.js +37 -3
- package/dist/web/src/auth/oidc-clientcredentials-provider.js +4 -2
- package/dist/web/src/auth/oidc-externaljwt-provider.js +5 -3
- package/dist/web/src/auth/oidc-refreshtoken-provider.js +19 -3
- package/dist/web/src/auth/oidc.js +9 -8
- package/dist/web/src/auth/providers.js +7 -1
- package/dist/web/src/index.js +2 -1
- package/dist/web/src/nanoclients.js +4 -4
- package/dist/web/src/nanotdf/Client.js +11 -7
- package/dist/web/src/opentdf.js +103 -13
- package/dist/web/src/platform/authorization/v2/authorization_pb.js +109 -0
- package/dist/web/src/platform/buf/validate/validate_pb.js +113 -169
- package/dist/web/src/platform/common/common_pb.js +15 -4
- package/dist/web/src/platform/entity/entity_pb.js +48 -0
- package/dist/web/src/platform/entityresolution/entity_resolution_pb.js +1 -1
- package/dist/web/src/platform/entityresolution/v2/entity_resolution_pb.js +46 -0
- package/dist/web/src/platform/google/api/annotations_pb.js +1 -1
- package/dist/web/src/platform/google/api/http_pb.js +3 -3
- package/dist/web/src/platform/kas/kas_pb.js +2 -2
- package/dist/web/src/platform/policy/attributes/attributes_pb.js +12 -2
- package/dist/web/src/platform/policy/kasregistry/key_access_server_registry_pb.js +55 -3
- package/dist/web/src/platform/policy/keymanagement/key_management_pb.js +2 -2
- package/dist/web/src/platform/policy/namespaces/namespaces_pb.js +30 -3
- package/dist/web/src/platform/policy/objects_pb.js +114 -41
- package/dist/web/src/platform/policy/obligations/obligations_pb.js +156 -0
- package/dist/web/src/platform/policy/registeredresources/registered_resources_pb.js +19 -14
- package/dist/web/src/platform/policy/resourcemapping/resource_mapping_pb.js +2 -3
- package/dist/web/src/platform/policy/selectors_pb.js +1 -1
- package/dist/web/src/platform/policy/subjectmapping/subject_mapping_pb.js +2 -3
- package/dist/web/src/platform/policy/unsafe/unsafe_pb.js +2 -4
- package/dist/web/src/platform.js +20 -3
- package/dist/web/src/policy/api.js +26 -7
- package/dist/web/src/policy/granter.js +75 -48
- package/dist/web/src/seekable.js +32 -1
- package/dist/web/src/utils.js +84 -3
- package/dist/web/tdf3/src/assertions.js +38 -2
- package/dist/web/tdf3/src/client/DecoratedReadableStream.js +8 -1
- package/dist/web/tdf3/src/client/builders.js +13 -1
- package/dist/web/tdf3/src/client/index.js +215 -57
- package/dist/web/tdf3/src/client/validation.js +3 -3
- package/dist/web/tdf3/src/tdf.js +42 -9
- package/dist/web/tdf3/src/utils/unwrap.js +2 -2
- package/package.json +7 -5
- package/src/access/access-fetch.ts +1 -0
- package/src/access/access-rpc.ts +13 -4
- package/src/access/constants.ts +2 -0
- package/src/access.ts +54 -2
- package/src/auth/oidc-clientcredentials-provider.ts +4 -0
- package/src/auth/oidc-externaljwt-provider.ts +5 -1
- package/src/auth/oidc-refreshtoken-provider.ts +19 -1
- package/src/auth/oidc.ts +12 -7
- package/src/auth/providers.ts +6 -0
- package/src/index.ts +1 -0
- package/src/nanoclients.ts +3 -3
- package/src/nanotdf/Client.ts +28 -6
- package/src/opentdf.ts +206 -73
- package/src/platform/authorization/v2/authorization_pb.ts +503 -0
- package/src/platform/buf/validate/validate_pb.ts +529 -401
- package/src/platform/common/common_pb.ts +48 -3
- package/src/platform/entity/entity_pb.ts +154 -0
- package/src/platform/entityresolution/entity_resolution_pb.ts +4 -0
- package/src/platform/entityresolution/v2/entity_resolution_pb.ts +170 -0
- package/src/platform/google/api/annotations_pb.ts +1 -1
- package/src/platform/google/api/http_pb.ts +2 -2
- package/src/platform/kas/kas_pb.ts +6 -1
- package/src/platform/policy/attributes/attributes_pb.ts +46 -16
- package/src/platform/policy/kasregistry/key_access_server_registry_pb.ts +371 -27
- package/src/platform/policy/keymanagement/key_management_pb.ts +24 -2
- package/src/platform/policy/namespaces/namespaces_pb.ts +163 -7
- package/src/platform/policy/objects_pb.ts +474 -59
- package/src/platform/policy/obligations/obligations_pb.ts +788 -0
- package/src/platform/policy/registeredresources/registered_resources_pb.ts +80 -13
- package/src/platform/policy/resourcemapping/resource_mapping_pb.ts +1 -2
- package/src/platform/policy/selectors_pb.ts +18 -0
- package/src/platform/policy/subjectmapping/subject_mapping_pb.ts +1 -2
- package/src/platform/policy/unsafe/unsafe_pb.ts +21 -6
- package/src/platform.ts +29 -5
- package/src/policy/api.ts +37 -6
- package/src/policy/granter.ts +82 -56
- package/src/seekable.ts +31 -0
- package/src/utils.ts +88 -2
- package/tdf3/src/assertions.ts +52 -1
- package/tdf3/src/client/DecoratedReadableStream.ts +9 -0
- package/tdf3/src/client/builders.ts +16 -0
- package/tdf3/src/client/index.ts +309 -73
- package/tdf3/src/client/validation.ts +2 -2
- package/tdf3/src/tdf.ts +53 -9
- package/tdf3/src/utils/unwrap.ts +2 -1
package/src/policy/granter.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { ConfigurationError } from '../errors.js';
|
|
2
2
|
import { Attribute, AttributeRuleType, KeyAccessServer, Value } from './attributes.js';
|
|
3
|
+
import { SimpleKasPublicKey } from '../platform/policy/objects_pb.js';
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
type KeyHolder = KeyAccessServer | (SimpleKasPublicKey & { kasUri: string });
|
|
6
|
+
|
|
7
|
+
type KeySplitStep = {
|
|
8
|
+
kas: KeyHolder;
|
|
9
|
+
kid?: string;
|
|
10
|
+
sid: string;
|
|
7
11
|
};
|
|
8
12
|
|
|
9
13
|
type AttributeClause = {
|
|
@@ -13,17 +17,17 @@ type AttributeClause = {
|
|
|
13
17
|
|
|
14
18
|
type AndClause = {
|
|
15
19
|
op: 'allOf';
|
|
16
|
-
|
|
20
|
+
granters: KeyHolder[];
|
|
17
21
|
};
|
|
18
22
|
|
|
19
23
|
type HeirarchyClause = {
|
|
20
24
|
op: 'hierarchy';
|
|
21
|
-
|
|
25
|
+
granters: KeyHolder[];
|
|
22
26
|
};
|
|
23
27
|
|
|
24
28
|
type OrClause = {
|
|
25
29
|
op: 'anyOf';
|
|
26
|
-
|
|
30
|
+
granters: KeyHolder[];
|
|
27
31
|
};
|
|
28
32
|
|
|
29
33
|
type BooleanClause = AndClause | OrClause | HeirarchyClause;
|
|
@@ -49,53 +53,51 @@ export function booleanOperatorFor(rule?: AttributeRuleType): BooleanOperator {
|
|
|
49
53
|
}
|
|
50
54
|
}
|
|
51
55
|
|
|
56
|
+
type ValueFQN = string;
|
|
52
57
|
export function plan(dataAttrs: Value[]): KeySplitStep[] {
|
|
53
58
|
// KASes by value
|
|
54
|
-
const
|
|
55
|
-
// KAS detail by KAS url
|
|
56
|
-
const kasInfo: Record<string, KeyAccessServer> = {};
|
|
57
|
-
// Attribute definitions in use
|
|
58
|
-
const prefixes: Set<string> = new Set();
|
|
59
|
+
const granters: Record<ValueFQN, Set<KeyHolder>> = Object.create(null);
|
|
59
60
|
// Values grouped by normalized attribute prefix
|
|
60
|
-
const allClauses: Record<string, AttributeClause> =
|
|
61
|
-
// Values by normalized FQN
|
|
62
|
-
const allValues: Record<string, Value> = {};
|
|
61
|
+
const allClauses: Record<string, AttributeClause> = Object.create(null);
|
|
63
62
|
|
|
64
|
-
const addGrants = (
|
|
63
|
+
const addGrants = (valueFQN: string, gs?: KeyHolder[]): boolean => {
|
|
64
|
+
if (!(valueFQN in granters)) {
|
|
65
|
+
granters[valueFQN] = new Set();
|
|
66
|
+
}
|
|
65
67
|
if (!gs?.length) {
|
|
66
|
-
if (!(val in grants)) {
|
|
67
|
-
grants[val] = new Set();
|
|
68
|
-
}
|
|
69
68
|
return false;
|
|
70
69
|
}
|
|
71
70
|
for (const g of gs) {
|
|
72
|
-
|
|
73
|
-
grants[val].add(g.uri);
|
|
74
|
-
} else {
|
|
75
|
-
grants[val] = new Set([g.uri]);
|
|
76
|
-
}
|
|
77
|
-
kasInfo[g.uri] = g;
|
|
71
|
+
granters[valueFQN].add(g);
|
|
78
72
|
}
|
|
79
73
|
return true;
|
|
80
74
|
};
|
|
81
75
|
|
|
82
76
|
for (const v of dataAttrs) {
|
|
83
|
-
const { attribute, fqn } = v;
|
|
77
|
+
const { attribute, fqn, kasKeys } = v;
|
|
84
78
|
if (!attribute) {
|
|
85
79
|
throw new ConfigurationError(`attribute not defined for [${fqn}]`);
|
|
86
80
|
}
|
|
87
81
|
const valFqn = fqn.toLowerCase();
|
|
88
82
|
const attrFqn = attribute.fqn.toLowerCase();
|
|
89
|
-
if (!
|
|
90
|
-
prefixes.add(attrFqn);
|
|
83
|
+
if (!(attrFqn in allClauses)) {
|
|
91
84
|
allClauses[attrFqn] = {
|
|
92
85
|
def: attribute,
|
|
93
86
|
values: [],
|
|
94
87
|
};
|
|
95
88
|
}
|
|
96
89
|
allClauses[attrFqn].values.push(valFqn);
|
|
97
|
-
|
|
98
|
-
|
|
90
|
+
const validKasKeys = kasKeys
|
|
91
|
+
.map((kasKey) => {
|
|
92
|
+
if (!kasKey.publicKey) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
return Object.assign({ kasUri: kasKey.kasUri }, kasKey.publicKey);
|
|
96
|
+
})
|
|
97
|
+
.filter((kasKey) => kasKey !== null);
|
|
98
|
+
if (validKasKeys.length) {
|
|
99
|
+
addGrants(valFqn, validKasKeys);
|
|
100
|
+
} else if (!addGrants(valFqn, v.grants)) {
|
|
99
101
|
if (!addGrants(valFqn, attribute.grants)) {
|
|
100
102
|
addGrants(valFqn, attribute.namespace?.grants);
|
|
101
103
|
}
|
|
@@ -103,64 +105,80 @@ export function plan(dataAttrs: Value[]): KeySplitStep[] {
|
|
|
103
105
|
}
|
|
104
106
|
const kcs: ComplexBooleanClause[] = [];
|
|
105
107
|
for (const attrClause of Object.values(allClauses)) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
// Create wrapper clauses for each value, [(ANY_OF Value-1), (ANY_OF Value-2)]
|
|
109
|
+
const individualValueClauses: BooleanClause[] = [];
|
|
110
|
+
for (const attrValue of attrClause.values) {
|
|
111
|
+
const grantersForAttr = granters[attrValue] || new Set();
|
|
112
|
+
if (grantersForAttr.size) {
|
|
113
|
+
individualValueClauses.push({
|
|
111
114
|
op: 'anyOf',
|
|
112
|
-
|
|
115
|
+
granters: Array.from(grantersForAttr.values()),
|
|
113
116
|
});
|
|
114
117
|
}
|
|
115
118
|
}
|
|
119
|
+
// Use proper boolean operation with wrapped values.
|
|
116
120
|
const op = booleanOperatorFor(attrClause.def?.rule);
|
|
117
121
|
kcs.push({
|
|
118
122
|
op,
|
|
119
|
-
children:
|
|
123
|
+
children: individualValueClauses,
|
|
120
124
|
});
|
|
121
125
|
}
|
|
122
|
-
return simplify(kcs
|
|
126
|
+
return simplify(kcs);
|
|
123
127
|
}
|
|
124
128
|
|
|
125
|
-
function simplify(
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
129
|
+
function simplify(clauses: ComplexBooleanClause[]): KeySplitStep[] {
|
|
130
|
+
const conjunction: Record<string, KeyHolder[]> = {};
|
|
131
|
+
function keyFor(granters: KeyHolder[]): string {
|
|
132
|
+
const keyParts = granters
|
|
133
|
+
.map((keyHolder) => {
|
|
134
|
+
const keyParts: string[] = [];
|
|
135
|
+
if ('kid' in keyHolder) {
|
|
136
|
+
keyParts.push(keyHolder.kasUri);
|
|
137
|
+
keyParts.push(keyHolder.kid);
|
|
138
|
+
} else {
|
|
139
|
+
keyParts.push(keyHolder.uri);
|
|
140
|
+
keyParts.push('');
|
|
141
|
+
}
|
|
142
|
+
return [keyHolder, keyParts.join('/')] as [KeyHolder, string];
|
|
143
|
+
})
|
|
144
|
+
.sort(([, sortKeyA], [, sortKeyB]) => {
|
|
145
|
+
return sortKeyA.localeCompare(sortKeyB);
|
|
146
|
+
})
|
|
147
|
+
.map(([, sortKey]) => {
|
|
148
|
+
return sortKey;
|
|
149
|
+
});
|
|
150
|
+
return keyParts.join(':');
|
|
133
151
|
}
|
|
134
152
|
for (const { op, children } of clauses) {
|
|
135
153
|
if (!children) {
|
|
136
154
|
continue;
|
|
137
155
|
}
|
|
138
156
|
if (op === 'anyOf') {
|
|
139
|
-
const
|
|
157
|
+
const granters: KeyHolder[] = [];
|
|
140
158
|
for (const bc of children) {
|
|
141
159
|
if (bc.op != 'anyOf') {
|
|
142
160
|
throw new Error('internal: autoconfigure inversion in disjunction');
|
|
143
161
|
}
|
|
144
|
-
if (!bc.
|
|
162
|
+
if (!bc.granters.length) {
|
|
145
163
|
continue;
|
|
146
164
|
}
|
|
147
|
-
|
|
165
|
+
granters.push(...bc.granters);
|
|
148
166
|
}
|
|
149
|
-
if (!
|
|
167
|
+
if (!granters.length) {
|
|
150
168
|
continue;
|
|
151
169
|
}
|
|
152
|
-
const k = keyFor(
|
|
153
|
-
conjunction[k] =
|
|
170
|
+
const k = keyFor(granters);
|
|
171
|
+
conjunction[k] = granters;
|
|
154
172
|
} else {
|
|
155
173
|
for (const bc of children) {
|
|
156
174
|
if (bc.op != 'anyOf') {
|
|
157
|
-
throw new Error('
|
|
175
|
+
throw new Error('internal: autoconfigure inversion in conjunction');
|
|
158
176
|
}
|
|
159
|
-
if (!bc.
|
|
177
|
+
if (!bc.granters.length) {
|
|
160
178
|
continue;
|
|
161
179
|
}
|
|
162
|
-
const k = keyFor(bc.
|
|
163
|
-
conjunction[k] = bc.
|
|
180
|
+
const k = keyFor(bc.granters);
|
|
181
|
+
conjunction[k] = bc.granters;
|
|
164
182
|
}
|
|
165
183
|
}
|
|
166
184
|
}
|
|
@@ -173,7 +191,15 @@ function simplify(
|
|
|
173
191
|
i += 1;
|
|
174
192
|
const sid = '' + i;
|
|
175
193
|
for (const kas of conjunction[k]) {
|
|
176
|
-
|
|
194
|
+
if ('kid' in kas) {
|
|
195
|
+
t.push({ sid, kas: kas, kid: kas.kid });
|
|
196
|
+
} else if (kas.publicKey && kas.publicKey.publicKey.case === 'cached') {
|
|
197
|
+
kas.publicKey.publicKey.value.keys.forEach((key) => {
|
|
198
|
+
t.push({ sid, kas: kas, kid: key.kid });
|
|
199
|
+
});
|
|
200
|
+
} else {
|
|
201
|
+
t.push({ sid, kas: kas });
|
|
202
|
+
}
|
|
177
203
|
}
|
|
178
204
|
}
|
|
179
205
|
return t;
|
package/src/seekable.ts
CHANGED
|
@@ -33,12 +33,30 @@ export const fromBrowserFile = (fileRef: Blob): Chunker => {
|
|
|
33
33
|
};
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Creates a seekable object from a buffer.
|
|
38
|
+
* @param source A Uint8Array to read from.
|
|
39
|
+
* If byteStart and byteEnd are not provided, reads the entire array.
|
|
40
|
+
* If byteStart is provided, reads from that index to the end of the array.
|
|
41
|
+
* If byteEnd is provided, reads from byteStart to byteEnd (exclusive).
|
|
42
|
+
* If both byteStart and byteEnd are provided, reads from byteStart to byteEnd (exclusive).
|
|
43
|
+
* @returns A promise that resolves to a Uint8Array containing the requested data.
|
|
44
|
+
*/
|
|
36
45
|
export const fromBuffer = (source: Uint8Array): Chunker => {
|
|
37
46
|
return (byteStart?: number, byteEnd?: number) => {
|
|
38
47
|
return Promise.resolve(source.slice(byteStart, byteEnd));
|
|
39
48
|
};
|
|
40
49
|
};
|
|
41
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Creates a seekable object from a string.
|
|
53
|
+
* @param source A string to read from.
|
|
54
|
+
* If byteStart and byteEnd are not provided, reads the entire string.
|
|
55
|
+
* If byteStart is provided, reads from that index to the end of the string.
|
|
56
|
+
* If byteEnd is provided, reads from byteStart to byteEnd (exclusive).
|
|
57
|
+
* If both byteStart and byteEnd are provided, reads from byteStart to byteEnd (exclusive).
|
|
58
|
+
* @returns A promise that resolves to a Uint8Array containing the requested data.
|
|
59
|
+
*/
|
|
42
60
|
export const fromString = (source: string): Chunker => {
|
|
43
61
|
return fromBuffer(new TextEncoder().encode(source));
|
|
44
62
|
};
|
|
@@ -110,6 +128,12 @@ export const fromUrl = async (location: string): Promise<Chunker> => {
|
|
|
110
128
|
};
|
|
111
129
|
};
|
|
112
130
|
|
|
131
|
+
/**
|
|
132
|
+
* Creates a seekable object from a source.
|
|
133
|
+
* @param source A Source object containing the type and location of the data.
|
|
134
|
+
* @returns A promise that resolves to a Chunker function.
|
|
135
|
+
* @throws ConfigurationError if the source type is not supported or the location is invalid.
|
|
136
|
+
*/
|
|
113
137
|
export const fromSource = async ({ type, location }: Source): Promise<Chunker> => {
|
|
114
138
|
switch (type) {
|
|
115
139
|
case 'buffer':
|
|
@@ -139,6 +163,13 @@ export const fromSource = async ({ type, location }: Source): Promise<Chunker> =
|
|
|
139
163
|
}
|
|
140
164
|
};
|
|
141
165
|
|
|
166
|
+
/**
|
|
167
|
+
* Converts a Source object to a ReadableStream.
|
|
168
|
+
* @param source A Source object containing the type and location of the data.
|
|
169
|
+
* Converts the source to a ReadableStream of Uint8Array.
|
|
170
|
+
* This is useful for streaming data from various sources like files, remote URLs, or chunkers.
|
|
171
|
+
* @returns A ReadableStream of Uint8Array.
|
|
172
|
+
*/
|
|
142
173
|
export async function sourceToStream(source: Source): Promise<ReadableStream<Uint8Array>> {
|
|
143
174
|
switch (source.type) {
|
|
144
175
|
case 'stream':
|
package/src/utils.ts
CHANGED
|
@@ -3,8 +3,11 @@ import { exportSPKI, importX509 } from 'jose';
|
|
|
3
3
|
import { base64 } from './encodings/index.js';
|
|
4
4
|
import { pemCertToCrypto, pemPublicToCrypto } from './nanotdf-crypto/pemPublicToCrypto.js';
|
|
5
5
|
import { ConfigurationError } from './errors.js';
|
|
6
|
+
import { RewrapResponse } from './platform/kas/kas_pb.js';
|
|
6
7
|
import { ConnectError } from '@connectrpc/connect';
|
|
7
8
|
|
|
9
|
+
const REQUIRED_OBLIGATIONS_METADATA_KEY = 'X-Required-Obligations';
|
|
10
|
+
|
|
8
11
|
/**
|
|
9
12
|
* Check to see if the given URL is 'secure'. This assumes:
|
|
10
13
|
*
|
|
@@ -35,6 +38,12 @@ export function validateSecureUrl(url: string): boolean {
|
|
|
35
38
|
return true;
|
|
36
39
|
}
|
|
37
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Pads a URL with a trailing slash if it does not already have one.
|
|
43
|
+
* This is useful for ensuring that URLs are in a consistent format.
|
|
44
|
+
* @param u The URL to pad.
|
|
45
|
+
* @returns The padded URL.
|
|
46
|
+
*/
|
|
38
47
|
export function padSlashToUrl(u: string): string {
|
|
39
48
|
if (u.endsWith('/')) {
|
|
40
49
|
return u;
|
|
@@ -42,10 +51,21 @@ export function padSlashToUrl(u: string): string {
|
|
|
42
51
|
return `${u}/`;
|
|
43
52
|
}
|
|
44
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Checks if the current environment is a browser.
|
|
56
|
+
* This is useful for determining if certain APIs or features are available.
|
|
57
|
+
* @returns true if running in a browser, false otherwise.
|
|
58
|
+
*/
|
|
45
59
|
export function isBrowser() {
|
|
46
60
|
return typeof window !== 'undefined'; // eslint-disable-line
|
|
47
61
|
}
|
|
48
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Removes trailing characters from a string.
|
|
65
|
+
* @param str The string to trim.
|
|
66
|
+
* @param suffix The suffix to remove (default is a single space).
|
|
67
|
+
* @returns The trimmed string.
|
|
68
|
+
*/
|
|
49
69
|
export const rstrip = (str: string, suffix = ' '): string => {
|
|
50
70
|
while (str && suffix && str.endsWith(suffix)) {
|
|
51
71
|
str = str.slice(0, -suffix.length);
|
|
@@ -92,6 +112,15 @@ export const estimateSkewFromHeaders = (headers: AnyHeaders, dateNowBefore?: num
|
|
|
92
112
|
return Math.round((deltaBefore + deltaAfter) / 2);
|
|
93
113
|
};
|
|
94
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Adds new lines to a string every 64 characters.
|
|
117
|
+
* @param str A string to add new lines to.
|
|
118
|
+
* This function takes a string and adds new lines every 64 characters.
|
|
119
|
+
* If the string is empty or undefined, it returns the original string.
|
|
120
|
+
* This is useful for formatting long strings, such as public keys or certificates,
|
|
121
|
+
* to ensure they are properly formatted for PEM encoding.
|
|
122
|
+
* @returns The formatted string with new lines added.
|
|
123
|
+
*/
|
|
95
124
|
export function addNewLines(str: string): string {
|
|
96
125
|
if (!str) {
|
|
97
126
|
return str;
|
|
@@ -105,6 +134,11 @@ export function addNewLines(str: string): string {
|
|
|
105
134
|
return finalString;
|
|
106
135
|
}
|
|
107
136
|
|
|
137
|
+
/**
|
|
138
|
+
* Creates a PEM-encoded string from a public key.
|
|
139
|
+
* @param publicKey The public key to convert.
|
|
140
|
+
* @returns A promise that resolves to a PEM-encoded string.
|
|
141
|
+
*/
|
|
108
142
|
export async function cryptoPublicToPem(publicKey: CryptoKey): Promise<string> {
|
|
109
143
|
if (publicKey.type !== 'public') {
|
|
110
144
|
throw new ConfigurationError('incorrect key type');
|
|
@@ -116,6 +150,11 @@ export async function cryptoPublicToPem(publicKey: CryptoKey): Promise<string> {
|
|
|
116
150
|
return `-----BEGIN PUBLIC KEY-----\r\n${pem}-----END PUBLIC KEY-----`;
|
|
117
151
|
}
|
|
118
152
|
|
|
153
|
+
/**
|
|
154
|
+
* Converts a PEM-encoded public key to a CryptoKey.
|
|
155
|
+
* @param pem The PEM-encoded public key.
|
|
156
|
+
* @returns A promise that resolves to a CryptoKey.
|
|
157
|
+
*/
|
|
119
158
|
export async function pemToCryptoPublicKey(pem: string): Promise<CryptoKey> {
|
|
120
159
|
if (/-----BEGIN PUBLIC KEY-----/.test(pem)) {
|
|
121
160
|
return pemPublicToCrypto(pem);
|
|
@@ -128,6 +167,15 @@ export async function pemToCryptoPublicKey(pem: string): Promise<CryptoKey> {
|
|
|
128
167
|
throw new TypeError(`unsupported pem type [${pem}]`);
|
|
129
168
|
}
|
|
130
169
|
|
|
170
|
+
/**
|
|
171
|
+
* Extracts the PEM-encoded public key from a key string.
|
|
172
|
+
* @param keyString A string containing a public key or certificate.
|
|
173
|
+
* This function extracts the PEM-encoded public key from a given key string.
|
|
174
|
+
* If the key string contains a certificate, it imports the certificate and exports
|
|
175
|
+
* the public key in PEM format. If the key string is already in PEM format, it returns
|
|
176
|
+
* the key string as is.
|
|
177
|
+
* @returns A promise that resolves to a PEM-encoded public key.
|
|
178
|
+
*/
|
|
131
179
|
export async function extractPemFromKeyString(keyString: string): Promise<string> {
|
|
132
180
|
let pem: string = keyString;
|
|
133
181
|
|
|
@@ -143,6 +191,12 @@ export async function extractPemFromKeyString(keyString: string): Promise<string
|
|
|
143
191
|
|
|
144
192
|
/**
|
|
145
193
|
* Extracts the error message from an RPC catch error.
|
|
194
|
+
* @param error An error object, typically from a network request.
|
|
195
|
+
* This function extracts the error message from a ConnectError or a generic Error.
|
|
196
|
+
* If the error is a ConnectError or a standard Error, it returns the message.
|
|
197
|
+
* If the error is of an unknown type, it returns a default message indicating
|
|
198
|
+
* that an unknown network error occurred.
|
|
199
|
+
* @returns The extracted error message.
|
|
146
200
|
*/
|
|
147
201
|
export function extractRpcErrorMessage(error: unknown): string {
|
|
148
202
|
if (error instanceof ConnectError || error instanceof Error) {
|
|
@@ -153,8 +207,11 @@ export function extractRpcErrorMessage(error: unknown): string {
|
|
|
153
207
|
|
|
154
208
|
/**
|
|
155
209
|
* Converts a KAS endpoint URL to a platform URL.
|
|
156
|
-
*
|
|
157
|
-
*
|
|
210
|
+
* @param endpoint The KAS endpoint URL to extract the platform URL from.
|
|
211
|
+
* This function extracts the base URL from a KAS endpoint URL.
|
|
212
|
+
* It removes any trailing slashes and specific path segments related to rewrap or kas.
|
|
213
|
+
* This is useful for obtaining the base URL for further API requests.
|
|
214
|
+
* @returns The base URL of the platform.
|
|
158
215
|
*/
|
|
159
216
|
export function getPlatformUrlFromKasEndpoint(endpoint: string): string {
|
|
160
217
|
let result = endpoint || '';
|
|
@@ -169,3 +226,32 @@ export function getPlatformUrlFromKasEndpoint(endpoint: string): string {
|
|
|
169
226
|
}
|
|
170
227
|
return result;
|
|
171
228
|
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Retrieves the fully qualified Obligations (values) that must be fulfilled from a rewrap response.
|
|
232
|
+
*/
|
|
233
|
+
export function getRequiredObligationFQNs(response: RewrapResponse) {
|
|
234
|
+
const requiredObligations = new Set<string>();
|
|
235
|
+
|
|
236
|
+
// Loop through response key access object results, checking proto values/types for a metadata key
|
|
237
|
+
// that matches the expected KAS-provided fulfillable obligations list.
|
|
238
|
+
for (const resp of response.responses) {
|
|
239
|
+
for (const result of resp.results) {
|
|
240
|
+
if (!result.metadata.hasOwnProperty(REQUIRED_OBLIGATIONS_METADATA_KEY)) {
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
const value = result.metadata[REQUIRED_OBLIGATIONS_METADATA_KEY];
|
|
244
|
+
if (value?.kind.case !== 'listValue') {
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
const obligations = value.kind.value.values;
|
|
248
|
+
for (const obligation of obligations) {
|
|
249
|
+
if (obligation.kind.case === 'stringValue') {
|
|
250
|
+
requiredObligations.add(obligation.kind.value.toLowerCase());
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return [...requiredObligations.values()];
|
|
257
|
+
}
|
package/tdf3/src/assertions.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { canonicalizeEx } from 'json-canonicalize';
|
|
|
2
2
|
import { SignJWT, jwtVerify } from 'jose';
|
|
3
3
|
import { base64, hex } from '../../src/encodings/index.js';
|
|
4
4
|
import { ConfigurationError, IntegrityError, InvalidFileError } from '../../src/errors.js';
|
|
5
|
+
import { tdfSpecVersion, version as sdkVersion } from '../../src/version.js';
|
|
5
6
|
|
|
6
7
|
export type AssertionKeyAlg = 'ES256' | 'RS256' | 'HS256';
|
|
7
8
|
export type AssertionType = 'handling' | 'other';
|
|
@@ -43,7 +44,9 @@ export type AssertionPayload = {
|
|
|
43
44
|
* @returns the hexadecimal string representation of the hash
|
|
44
45
|
*/
|
|
45
46
|
export async function hash(a: Assertion): Promise<string> {
|
|
46
|
-
const result = canonicalizeEx(a, {
|
|
47
|
+
const result = canonicalizeEx(a, {
|
|
48
|
+
exclude: ['binding', 'hash', 'sign', 'verify', 'signingKey'],
|
|
49
|
+
});
|
|
47
50
|
|
|
48
51
|
const hash = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(result));
|
|
49
52
|
return hex.encodeArrayBuffer(hash);
|
|
@@ -227,6 +230,54 @@ export type AssertionVerificationKeys = {
|
|
|
227
230
|
Keys: Record<string, AssertionKey>;
|
|
228
231
|
};
|
|
229
232
|
|
|
233
|
+
/**
|
|
234
|
+
* Metadata structure for system information.
|
|
235
|
+
*/
|
|
236
|
+
type SystemMetadata = {
|
|
237
|
+
tdf_spec_version: string;
|
|
238
|
+
creation_date: string;
|
|
239
|
+
sdk_version: string;
|
|
240
|
+
browser_user_agent?: string;
|
|
241
|
+
// platform is often the same as os in browser, but kept for consistency with original Go struct
|
|
242
|
+
platform?: string;
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Returns a default assertion configuration populated with system metadata.
|
|
247
|
+
*/
|
|
248
|
+
export function getSystemMetadataAssertionConfig(): AssertionConfig {
|
|
249
|
+
let platformIdentifier = 'unknown';
|
|
250
|
+
if (typeof navigator !== 'undefined') {
|
|
251
|
+
if (typeof navigator.userAgent === 'string') {
|
|
252
|
+
platformIdentifier = navigator.userAgent;
|
|
253
|
+
} else if (typeof navigator.platform === 'string') {
|
|
254
|
+
platformIdentifier = navigator.platform; // Deprecated, but used as a fallback
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const metadata: SystemMetadata = {
|
|
259
|
+
tdf_spec_version: tdfSpecVersion,
|
|
260
|
+
creation_date: new Date().toISOString(),
|
|
261
|
+
sdk_version: `JS-${sdkVersion}`, // Prefixed to distinguish from Go SDK version
|
|
262
|
+
browser_user_agent: typeof navigator !== 'undefined' ? navigator.userAgent : 'unknown',
|
|
263
|
+
platform: platformIdentifier,
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
const metadataJSON = JSON.stringify(metadata);
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
id: 'system-metadata', // Consistent ID for this type of assertion
|
|
270
|
+
type: 'other', // General type for metadata assertions
|
|
271
|
+
scope: 'tdo', // Metadata typically applies to the TDF Data Object as a whole
|
|
272
|
+
appliesToState: 'unencrypted', // Metadata itself is not encrypted by this assertion's scope
|
|
273
|
+
statement: {
|
|
274
|
+
format: 'json',
|
|
275
|
+
schema: 'system-metadata-v1', // A schema name for this metadata
|
|
276
|
+
value: metadataJSON,
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
230
281
|
function concatenateUint8Arrays(array1: Uint8Array, array2: Uint8Array): Uint8Array {
|
|
231
282
|
const combinedLength = array1.length + array2.length;
|
|
232
283
|
const combinedArray = new Uint8Array(combinedLength);
|
|
@@ -21,6 +21,7 @@ export class DecoratedReadableStream {
|
|
|
21
21
|
metadata?: Metadata;
|
|
22
22
|
manifest: Manifest;
|
|
23
23
|
fileStreamServiceWorker?: string;
|
|
24
|
+
requiredObligations?: string[];
|
|
24
25
|
|
|
25
26
|
constructor(
|
|
26
27
|
underlyingSource: UnderlyingSource & {
|
|
@@ -60,6 +61,14 @@ export class DecoratedReadableStream {
|
|
|
60
61
|
async toString(): Promise<string> {
|
|
61
62
|
return new Response(this.stream).text();
|
|
62
63
|
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The fully qualified obligations required to be fulfilled on stream contents
|
|
67
|
+
* are set as decoration during the decrypt flow.
|
|
68
|
+
*/
|
|
69
|
+
obligations(): string[] {
|
|
70
|
+
return this.requiredObligations ?? [];
|
|
71
|
+
}
|
|
63
72
|
}
|
|
64
73
|
|
|
65
74
|
export function isDecoratedReadableStream(s: unknown): s is DecoratedReadableStream {
|
|
@@ -31,6 +31,7 @@ export type EncryptStreamMiddleware = (
|
|
|
31
31
|
|
|
32
32
|
export type SplitStep = {
|
|
33
33
|
kas: string;
|
|
34
|
+
kid?: string;
|
|
34
35
|
sid?: string;
|
|
35
36
|
};
|
|
36
37
|
|
|
@@ -50,6 +51,7 @@ export type EncryptParams = {
|
|
|
50
51
|
splitPlan?: SplitStep[];
|
|
51
52
|
streamMiddleware?: EncryptStreamMiddleware;
|
|
52
53
|
assertionConfigs?: AssertionConfig[];
|
|
54
|
+
systemMetadataAssertion?: boolean;
|
|
53
55
|
defaultKASEndpoint?: string;
|
|
54
56
|
|
|
55
57
|
// Preferred wrapping key algorithm. Used when KID resolution is not available.
|
|
@@ -499,6 +501,19 @@ class EncryptParamsBuilder {
|
|
|
499
501
|
this._params.assertionConfigs = assertionConfigs;
|
|
500
502
|
return this;
|
|
501
503
|
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Specifies whether a default system metadata assertion should be automatically
|
|
507
|
+
* included during the encryption process.
|
|
508
|
+
*
|
|
509
|
+
* @param {boolean} systemMetadataAssertion - True to include the system metadata assertion, false otherwise.
|
|
510
|
+
* @returns {EncryptParamsBuilder} The current instance of the EncryptParamsBuilder for method chaining.
|
|
511
|
+
* @see {@link getSystemMetadataAssertionConfig}
|
|
512
|
+
*/
|
|
513
|
+
withSystemMetadataAssertion(systemMetadataAssertion: boolean): EncryptParamsBuilder {
|
|
514
|
+
this._params.systemMetadataAssertion = systemMetadataAssertion;
|
|
515
|
+
return this;
|
|
516
|
+
}
|
|
502
517
|
}
|
|
503
518
|
|
|
504
519
|
export type DecryptKeyMiddleware = (key: Binary) => Promise<Binary>;
|
|
@@ -523,6 +538,7 @@ export type DecryptParams = {
|
|
|
523
538
|
concurrencyLimit?: number;
|
|
524
539
|
noVerifyAssertions?: boolean;
|
|
525
540
|
wrappingKeyAlgorithm?: KasPublicKeyAlgorithm;
|
|
541
|
+
fulfillableObligationFQNs?: string[];
|
|
526
542
|
};
|
|
527
543
|
|
|
528
544
|
/**
|