@apollo/federation-internals 2.10.2 → 2.10.4
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/argumentCompositionStrategies.d.ts +7 -0
- package/dist/argumentCompositionStrategies.d.ts.map +1 -1
- package/dist/argumentCompositionStrategies.js +100 -1
- package/dist/argumentCompositionStrategies.js.map +1 -1
- package/dist/definitions.d.ts +1 -0
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +5 -1
- package/dist/definitions.js.map +1 -1
- package/dist/error.d.ts +3 -0
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +6 -0
- package/dist/error.js.map +1 -1
- package/dist/federation.d.ts.map +1 -1
- package/dist/federation.js +28 -0
- package/dist/federation.js.map +1 -1
- package/dist/specs/authenticatedSpec.d.ts +2 -0
- package/dist/specs/authenticatedSpec.d.ts.map +1 -1
- package/dist/specs/authenticatedSpec.js +3 -0
- package/dist/specs/authenticatedSpec.js.map +1 -1
- package/dist/specs/policySpec.d.ts +4 -0
- package/dist/specs/policySpec.d.ts.map +1 -1
- package/dist/specs/policySpec.js +4 -1
- package/dist/specs/policySpec.js.map +1 -1
- package/dist/specs/requiresScopesSpec.d.ts +4 -0
- package/dist/specs/requiresScopesSpec.d.ts.map +1 -1
- package/dist/specs/requiresScopesSpec.js +4 -1
- package/dist/specs/requiresScopesSpec.js.map +1 -1
- package/package.json +1 -1
- package/src/argumentCompositionStrategies.ts +148 -2
- package/src/definitions.ts +4 -0
- package/src/error.ts +21 -0
- package/src/federation.ts +40 -1
- package/src/specs/authenticatedSpec.ts +9 -0
- package/src/specs/policySpec.ts +10 -2
- package/src/specs/requiresScopesSpec.ts +10 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {InputType, NonNullType, Schema, isListType, isNonNullType} from "./definitions"
|
|
2
2
|
import { sameType } from "./types";
|
|
3
3
|
import { valueEquals } from "./values";
|
|
4
4
|
|
|
@@ -19,6 +19,14 @@ function supportFixedTypes(types: (schema: Schema) => InputType[]): TypeSupportV
|
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
function supportAnyNonNullNestedArray(): TypeSupportValidator {
|
|
23
|
+
return (_, type) =>
|
|
24
|
+
isNonNullType(type) && isListType(type.ofType)
|
|
25
|
+
&& isNonNullType(type.ofType.ofType) && isListType(type.ofType.ofType.ofType)
|
|
26
|
+
? { valid: true }
|
|
27
|
+
: { valid: false, supportedMsg: 'non nullable nested list types of any type' }
|
|
28
|
+
}
|
|
29
|
+
|
|
22
30
|
function supportAnyNonNullArray(): TypeSupportValidator {
|
|
23
31
|
return (_, type) => isNonNullType(type) && isListType(type.ofType)
|
|
24
32
|
? { valid: true }
|
|
@@ -54,6 +62,138 @@ function unionValues(values: any[]): any {
|
|
|
54
62
|
}, []);
|
|
55
63
|
}
|
|
56
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Performs conjunction of 2d arrays that represent conditions in Disjunctive Normal Form.
|
|
67
|
+
*
|
|
68
|
+
* Each 2D array is interpreted as follows
|
|
69
|
+
* * Inner array is interpreted as the conjunction (an AND) of the conditions in the array.
|
|
70
|
+
* * Outer array is interpreted as the disjunction (an OR) of the inner arrays.
|
|
71
|
+
*
|
|
72
|
+
* Algorithm
|
|
73
|
+
* * filter out duplicate entries to limit the amount of necessary computations
|
|
74
|
+
* * calculate cartesian product of the arrays to find all possible combinations
|
|
75
|
+
* * simplify combinations by dropping duplicate conditions (i.e. p ^ p = p, p ^ q = q ^ p)
|
|
76
|
+
* * eliminate entries that are subsumed by others (i.e. (p ^ q) subsumes (p ^ q ^ r))
|
|
77
|
+
*/
|
|
78
|
+
export function dnfConjunction<T>(values: T[][][]): T[][] {
|
|
79
|
+
// should never be the case
|
|
80
|
+
if (values.length == 0) {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Copy the 2D arrays, as we'll be modifying them below (due to sorting).
|
|
85
|
+
for (let i = 0; i < values.length; i++) {
|
|
86
|
+
// See the doc string for `convertEmptyToTrue()` to understand why this is
|
|
87
|
+
// necessary.
|
|
88
|
+
values[i] = convertEmptyToTrue(dnfCopy(values[i]));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// we first filter out duplicate values from candidates
|
|
92
|
+
// this avoids exponential computation of exactly the same conditions
|
|
93
|
+
const filtered = filterNestedArrayDuplicates(values);
|
|
94
|
+
|
|
95
|
+
// initialize with first entry
|
|
96
|
+
let result: T[][] = filtered[0];
|
|
97
|
+
// perform cartesian product to find all possible entries
|
|
98
|
+
for (let i = 1; i < filtered.length; i++) {
|
|
99
|
+
const current = filtered[i];
|
|
100
|
+
const accumulator: T[][] = [];
|
|
101
|
+
const seen = new Set<string>;
|
|
102
|
+
|
|
103
|
+
for (const accElement of result) {
|
|
104
|
+
for (const currentElement of current) {
|
|
105
|
+
// filter out elements that are already present in accElement
|
|
106
|
+
const filteredElement = currentElement.filter((e) => !accElement.includes(e));
|
|
107
|
+
const candidate = [...accElement, ...filteredElement].sort();
|
|
108
|
+
const key = JSON.stringify(candidate);
|
|
109
|
+
// only add entries which has not been seen yet
|
|
110
|
+
if (!seen.has(key)) {
|
|
111
|
+
seen.add(key);
|
|
112
|
+
accumulator.push(candidate);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Now we need to deduplicate the results. Given that
|
|
117
|
+
// - outer array implies OR requirements
|
|
118
|
+
// - inner array implies AND requirements
|
|
119
|
+
// We can filter out any inner arrays that fully contain other inner arrays, i.e.
|
|
120
|
+
// A OR B OR (A AND B) OR (A AND B AND C) => A OR B
|
|
121
|
+
result = deduplicateSubsumedValues(accumulator);
|
|
122
|
+
}
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function filterNestedArrayDuplicates<T>(values: T[][][]): T[][][] {
|
|
127
|
+
const filtered: T[][][] = [];
|
|
128
|
+
const seen = new Set<string>;
|
|
129
|
+
values.forEach((value) => {
|
|
130
|
+
value.forEach((inner) => {
|
|
131
|
+
inner.sort();
|
|
132
|
+
})
|
|
133
|
+
value.sort((a, b) => {
|
|
134
|
+
const left = JSON.stringify(a);
|
|
135
|
+
const right = JSON.stringify(b);
|
|
136
|
+
return left > right ? 1 : left < right ? -1 : 0;
|
|
137
|
+
});
|
|
138
|
+
const key = JSON.stringify(value);
|
|
139
|
+
if (!seen.has(key)) {
|
|
140
|
+
seen.add(key);
|
|
141
|
+
filtered.push(value);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
return filtered;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function deduplicateSubsumedValues<T>(values: T[][]): T[][] {
|
|
148
|
+
const result: T[][] = [];
|
|
149
|
+
// we first sort by length as the longer ones might be dropped
|
|
150
|
+
values.sort((first, second) => {
|
|
151
|
+
if (first.length < second.length) {
|
|
152
|
+
return -1;
|
|
153
|
+
} else if (first.length > second.length) {
|
|
154
|
+
return 1;
|
|
155
|
+
} else {
|
|
156
|
+
return 0;
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
for (const candidate of values) {
|
|
161
|
+
const entry = new Set(candidate);
|
|
162
|
+
let redundant = false;
|
|
163
|
+
for (const r of result) {
|
|
164
|
+
if (r.every(e => entry.has(e))) {
|
|
165
|
+
// if `r` is a subset of a `candidate` then it means `candidate` is redundant
|
|
166
|
+
redundant = true;
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (!redundant) {
|
|
172
|
+
result.push(candidate);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return result;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function dnfCopy<T>(value: T[][]): T[][] {
|
|
179
|
+
const newValue = new Array(value.length);
|
|
180
|
+
for (let i = 0; i < value.length; i++) {
|
|
181
|
+
newValue[i] = value[i].slice();
|
|
182
|
+
}
|
|
183
|
+
return newValue;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Normally for DNF, you'd consider [] to be always false and [[]] to be always
|
|
188
|
+
* true, and code that uses some()/every() needs no special-casing to work with
|
|
189
|
+
* these definitions. However, router special-cases [] to also mean true, and so
|
|
190
|
+
* if we're about to do any evaluation on DNFs, we need to do these conversions
|
|
191
|
+
* beforehand.
|
|
192
|
+
*/
|
|
193
|
+
export function convertEmptyToTrue<T>(value: T[][]): T[][] {
|
|
194
|
+
return value.length === 0 ? [[]] : value;
|
|
195
|
+
}
|
|
196
|
+
|
|
57
197
|
export const ARGUMENT_COMPOSITION_STRATEGIES = {
|
|
58
198
|
MAX: {
|
|
59
199
|
name: 'MAX',
|
|
@@ -95,7 +235,8 @@ export const ARGUMENT_COMPOSITION_STRATEGIES = {
|
|
|
95
235
|
schema.booleanType(),
|
|
96
236
|
new NonNullType(schema.booleanType())
|
|
97
237
|
]),
|
|
98
|
-
mergeValues:
|
|
238
|
+
mergeValues:
|
|
239
|
+
mergeNullableValues(
|
|
99
240
|
(values: boolean[]) => values.every((v) => v)
|
|
100
241
|
),
|
|
101
242
|
},
|
|
@@ -113,5 +254,10 @@ export const ARGUMENT_COMPOSITION_STRATEGIES = {
|
|
|
113
254
|
name: 'NULLABLE_UNION',
|
|
114
255
|
isTypeSupported: supportAnyArray(),
|
|
115
256
|
mergeValues: mergeNullableValues(unionValues),
|
|
257
|
+
},
|
|
258
|
+
DNF_CONJUNCTION: {
|
|
259
|
+
name: 'DNF_CONJUNCTION',
|
|
260
|
+
isTypeSupported: supportAnyNonNullNestedArray(),
|
|
261
|
+
mergeValues: dnfConjunction
|
|
116
262
|
}
|
|
117
263
|
}
|
package/src/definitions.ts
CHANGED
|
@@ -3825,3 +3825,7 @@ function copyDirectiveDefinitionInner(
|
|
|
3825
3825
|
export function isFieldDefinition(elem: SchemaElement<any, any>): elem is FieldDefinition<any> {
|
|
3826
3826
|
return elem instanceof FieldDefinition;
|
|
3827
3827
|
}
|
|
3828
|
+
|
|
3829
|
+
export function isElementNamedType(elem: SchemaElement<any, any>): elem is NamedType {
|
|
3830
|
+
return elem instanceof BaseNamedType;
|
|
3831
|
+
}
|
package/src/error.ts
CHANGED
|
@@ -627,6 +627,24 @@ const LIST_SIZE_INVALID_SIZED_FIELD = makeCodeDefinition(
|
|
|
627
627
|
{ addedIn: '2.9.2' },
|
|
628
628
|
);
|
|
629
629
|
|
|
630
|
+
const MAX_VALIDATION_SUBGRAPH_PATHS_EXCEEDED = makeCodeDefinition(
|
|
631
|
+
'MAX_VALIDATION_SUBGRAPH_PATHS_EXCEEDED',
|
|
632
|
+
'The maximum number of validation subgraph paths has been exceeded.',
|
|
633
|
+
{ addedIn: '2.8.0' },
|
|
634
|
+
);
|
|
635
|
+
|
|
636
|
+
const AUTH_REQUIREMENTS_APPLIED_ON_INTERFACE = makeCodeDefinition(
|
|
637
|
+
'AUTH_REQUIREMENTS_APPLIED_ON_INTERFACE',
|
|
638
|
+
'The @authenticated, @requiresScopes and @policy directive cannot be applied on interface, interface fields and interface object',
|
|
639
|
+
{ addedIn: '2.9.4' },
|
|
640
|
+
);
|
|
641
|
+
|
|
642
|
+
const MISSING_TRANSITIVE_AUTH_REQUIREMENTS = makeCodeDefinition(
|
|
643
|
+
'MISSING_TRANSITIVE_AUTH_REQUIREMENTS',
|
|
644
|
+
'Field missing transitive @authenticated, @requiresScopes and/or @policy auth requirements needed to access dependent data.',
|
|
645
|
+
{ addedIn: '2.9.4' },
|
|
646
|
+
)
|
|
647
|
+
|
|
630
648
|
export const ERROR_CATEGORIES = {
|
|
631
649
|
DIRECTIVE_FIELDS_MISSING_EXTERNAL,
|
|
632
650
|
DIRECTIVE_UNSUPPORTED_ON_INTERFACE,
|
|
@@ -727,6 +745,9 @@ export const ERRORS = {
|
|
|
727
745
|
LIST_SIZE_INVALID_ASSUMED_SIZE,
|
|
728
746
|
LIST_SIZE_INVALID_SIZED_FIELD,
|
|
729
747
|
LIST_SIZE_INVALID_SLICING_ARGUMENT,
|
|
748
|
+
MAX_VALIDATION_SUBGRAPH_PATHS_EXCEEDED,
|
|
749
|
+
AUTH_REQUIREMENTS_APPLIED_ON_INTERFACE,
|
|
750
|
+
MISSING_TRANSITIVE_AUTH_REQUIREMENTS,
|
|
730
751
|
};
|
|
731
752
|
|
|
732
753
|
const codeDefByCode = Object.values(ERRORS).reduce((obj: {[code: string]: ErrorCodeDefinition}, codeDef: ErrorCodeDefinition) => { obj[codeDef.code] = codeDef; return obj; }, {});
|
package/src/federation.ts
CHANGED
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
isWrapperType,
|
|
38
38
|
possibleRuntimeTypes,
|
|
39
39
|
isIntType,
|
|
40
|
-
Type,
|
|
40
|
+
Type, isFieldDefinition, isElementNamedType,
|
|
41
41
|
} from "./definitions";
|
|
42
42
|
import { assert, MultiMap, printHumanReadableList, OrderedMap, mapValues, assertUnreachable } from "./utils";
|
|
43
43
|
import { SDLValidationRule } from "graphql/validation/ValidationContext";
|
|
@@ -1840,6 +1840,9 @@ export class FederationBlueprint extends SchemaBlueprint {
|
|
|
1840
1840
|
validateSizedFieldsAreValidLists(application, parent, errorCollector);
|
|
1841
1841
|
}
|
|
1842
1842
|
|
|
1843
|
+
// Validate @authenticated, @requireScopes and @policy usage on interfaces and interface objects
|
|
1844
|
+
validateNoAuthenticationOnInterfaces(metadata, errorCollector);
|
|
1845
|
+
|
|
1843
1846
|
return errorCollector;
|
|
1844
1847
|
}
|
|
1845
1848
|
|
|
@@ -2891,3 +2894,39 @@ function withoutNonExternalLeafFields(selectionSet: SelectionSet): SelectionSet
|
|
|
2891
2894
|
return undefined;
|
|
2892
2895
|
});
|
|
2893
2896
|
}
|
|
2897
|
+
|
|
2898
|
+
function validateNoAuthenticationOnInterfaces(metadata: FederationMetadata, errorCollector: GraphQLError[]) {
|
|
2899
|
+
const authenticatedDirective = metadata.authenticatedDirective();
|
|
2900
|
+
const requiresScopesDirective = metadata.requiresScopesDirective();
|
|
2901
|
+
const policyDirective = metadata.policyDirective();
|
|
2902
|
+
[authenticatedDirective, requiresScopesDirective, policyDirective].forEach((directive) => {
|
|
2903
|
+
for (const application of directive.applications()) {
|
|
2904
|
+
const element: SchemaElement<any, any> = application.parent;
|
|
2905
|
+
if (
|
|
2906
|
+
// Is it applied on interface or interface object types?
|
|
2907
|
+
(isElementNamedType(element) &&
|
|
2908
|
+
(isInterfaceType(element) || isInterfaceObjectType(element))
|
|
2909
|
+
) ||
|
|
2910
|
+
// Is it applied on interface fields?
|
|
2911
|
+
(isFieldDefinition(element) && isInterfaceType(element.parent))
|
|
2912
|
+
) {
|
|
2913
|
+
let kind = '';
|
|
2914
|
+
switch (element.kind) {
|
|
2915
|
+
case 'FieldDefinition':
|
|
2916
|
+
kind = 'field';
|
|
2917
|
+
break;
|
|
2918
|
+
case 'InterfaceType':
|
|
2919
|
+
kind = 'interface';
|
|
2920
|
+
break;
|
|
2921
|
+
case 'ObjectType':
|
|
2922
|
+
kind = 'interface object';
|
|
2923
|
+
break;
|
|
2924
|
+
}
|
|
2925
|
+
errorCollector.push(ERRORS.AUTH_REQUIREMENTS_APPLIED_ON_INTERFACE.err(
|
|
2926
|
+
`Invalid use of @${directive.name} on ${kind} "${element.coordinate}": @${directive.name} cannot be applied on interfaces, interface fields and interface objects`,
|
|
2927
|
+
{nodes: sourceASTs(application, element.parent)},
|
|
2928
|
+
));
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2931
|
+
});
|
|
2932
|
+
}
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
} from "./coreSpec";
|
|
9
9
|
import { createDirectiveSpecification } from "../directiveAndTypeSpecification";
|
|
10
10
|
import { registerKnownFeature } from "../knownCoreFeatures";
|
|
11
|
+
import {DirectiveDefinition, Schema} from "../definitions";
|
|
11
12
|
|
|
12
13
|
export class AuthenticatedSpecDefinition extends FeatureDefinition {
|
|
13
14
|
public static readonly directiveName = "authenticated";
|
|
@@ -23,6 +24,10 @@ export class AuthenticatedSpecDefinition extends FeatureDefinition {
|
|
|
23
24
|
minimumFederationVersion,
|
|
24
25
|
);
|
|
25
26
|
|
|
27
|
+
// WARNING: we cannot declare staticArgumentTransform() as access control merge logic needs to propagate
|
|
28
|
+
// requirements upwards/downwards between types and interfaces. We hijack the merge process by providing
|
|
29
|
+
// implementations/interfaces as "additional sources". This means that we cannot apply staticArgumentTransform()
|
|
30
|
+
// as subgraph index index will be wrong/undefined.
|
|
26
31
|
this.registerDirective(createDirectiveSpecification({
|
|
27
32
|
name: AuthenticatedSpecDefinition.directiveName,
|
|
28
33
|
locations: [
|
|
@@ -37,6 +42,10 @@ export class AuthenticatedSpecDefinition extends FeatureDefinition {
|
|
|
37
42
|
}));
|
|
38
43
|
}
|
|
39
44
|
|
|
45
|
+
authenticatedDirective(schema: Schema): DirectiveDefinition | undefined {
|
|
46
|
+
return this.directive(schema, AuthenticatedSpecDefinition.directiveName);
|
|
47
|
+
}
|
|
48
|
+
|
|
40
49
|
get defaultCorePurpose(): CorePurpose {
|
|
41
50
|
return 'SECURITY';
|
|
42
51
|
}
|
package/src/specs/policySpec.ts
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
FeatureUrl,
|
|
7
7
|
FeatureVersion,
|
|
8
8
|
} from "./coreSpec";
|
|
9
|
-
import { ListType, NonNullType } from "../definitions";
|
|
9
|
+
import {DirectiveDefinition, ListType, NonNullType, Schema} from "../definitions";
|
|
10
10
|
import { createDirectiveSpecification, createScalarTypeSpecification } from "../directiveAndTypeSpecification";
|
|
11
11
|
import { registerKnownFeature } from "../knownCoreFeatures";
|
|
12
12
|
import { ARGUMENT_COMPOSITION_STRATEGIES } from "../argumentCompositionStrategies";
|
|
@@ -31,6 +31,10 @@ export class PolicySpecDefinition extends FeatureDefinition {
|
|
|
31
31
|
|
|
32
32
|
this.registerType(createScalarTypeSpecification({ name: PolicyTypeName.POLICY }));
|
|
33
33
|
|
|
34
|
+
// WARNING: we cannot declare staticArgumentTransform() as access control merge logic needs to propagate
|
|
35
|
+
// requirements upwards/downwards between types and interfaces. We hijack the merge process by providing
|
|
36
|
+
// implementations/interfaces as "additional sources". This means that we cannot apply staticArgumentTransform()
|
|
37
|
+
// as subgraph index index will be wrong/undefined.
|
|
34
38
|
this.registerDirective(createDirectiveSpecification({
|
|
35
39
|
name: PolicySpecDefinition.directiveName,
|
|
36
40
|
args: [{
|
|
@@ -42,7 +46,7 @@ export class PolicySpecDefinition extends FeatureDefinition {
|
|
|
42
46
|
assert(PolicyType, () => `Expected "${policyName}" to be defined`);
|
|
43
47
|
return new NonNullType(new ListType(new NonNullType(new ListType(new NonNullType(PolicyType)))));
|
|
44
48
|
},
|
|
45
|
-
compositionStrategy: ARGUMENT_COMPOSITION_STRATEGIES.
|
|
49
|
+
compositionStrategy: ARGUMENT_COMPOSITION_STRATEGIES.DNF_CONJUNCTION,
|
|
46
50
|
}],
|
|
47
51
|
locations: [
|
|
48
52
|
DirectiveLocation.FIELD_DEFINITION,
|
|
@@ -56,6 +60,10 @@ export class PolicySpecDefinition extends FeatureDefinition {
|
|
|
56
60
|
}));
|
|
57
61
|
}
|
|
58
62
|
|
|
63
|
+
policyDirective(schema: Schema): DirectiveDefinition<{policies: string[][]}> | undefined {
|
|
64
|
+
return this.directive(schema, PolicySpecDefinition.directiveName);
|
|
65
|
+
}
|
|
66
|
+
|
|
59
67
|
get defaultCorePurpose(): CorePurpose {
|
|
60
68
|
return 'SECURITY';
|
|
61
69
|
}
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
FeatureUrl,
|
|
7
7
|
FeatureVersion,
|
|
8
8
|
} from "./coreSpec";
|
|
9
|
-
import { ListType, NonNullType } from "../definitions";
|
|
9
|
+
import {DirectiveDefinition, ListType, NonNullType, Schema} from "../definitions";
|
|
10
10
|
import { createDirectiveSpecification, createScalarTypeSpecification } from "../directiveAndTypeSpecification";
|
|
11
11
|
import { registerKnownFeature } from "../knownCoreFeatures";
|
|
12
12
|
import { ARGUMENT_COMPOSITION_STRATEGIES } from "../argumentCompositionStrategies";
|
|
@@ -32,6 +32,10 @@ export class RequiresScopesSpecDefinition extends FeatureDefinition {
|
|
|
32
32
|
|
|
33
33
|
this.registerType(createScalarTypeSpecification({ name: RequiresScopesTypeName.SCOPE }));
|
|
34
34
|
|
|
35
|
+
// WARNING: we cannot declare staticArgumentTransform() as access control merge logic needs to propagate
|
|
36
|
+
// requirements upwards/downwards between types and interfaces. We hijack the merge process by providing
|
|
37
|
+
// implementations/interfaces as "additional sources". This means that we cannot apply staticArgumentTransform()
|
|
38
|
+
// as subgraph index index will be wrong/undefined.
|
|
35
39
|
this.registerDirective(createDirectiveSpecification({
|
|
36
40
|
name: RequiresScopesSpecDefinition.directiveName,
|
|
37
41
|
args: [{
|
|
@@ -43,7 +47,7 @@ export class RequiresScopesSpecDefinition extends FeatureDefinition {
|
|
|
43
47
|
assert(scopeType, () => `Expected "${scopeName}" to be defined`);
|
|
44
48
|
return new NonNullType(new ListType(new NonNullType(new ListType(new NonNullType(scopeType)))));
|
|
45
49
|
},
|
|
46
|
-
compositionStrategy: ARGUMENT_COMPOSITION_STRATEGIES.
|
|
50
|
+
compositionStrategy: ARGUMENT_COMPOSITION_STRATEGIES.DNF_CONJUNCTION,
|
|
47
51
|
}],
|
|
48
52
|
locations: [
|
|
49
53
|
DirectiveLocation.FIELD_DEFINITION,
|
|
@@ -57,6 +61,10 @@ export class RequiresScopesSpecDefinition extends FeatureDefinition {
|
|
|
57
61
|
}));
|
|
58
62
|
}
|
|
59
63
|
|
|
64
|
+
requiresScopesDirective(schema: Schema): DirectiveDefinition<{scopes: string[][]}> | undefined {
|
|
65
|
+
return this.directive(schema, RequiresScopesSpecDefinition.directiveName);
|
|
66
|
+
}
|
|
67
|
+
|
|
60
68
|
get defaultCorePurpose(): CorePurpose {
|
|
61
69
|
return 'SECURITY';
|
|
62
70
|
}
|