Skip to content

Commit ce84c47

Browse files
authored
Authorization support for Apollo Federation (#3661)
* Add initial Federation authorization * Add E2E tests * Update Federation E2E tests to work without port * Test type level authorization * Add changeset * Update TCK tests * Skip Federation tests for GraphQL 15
1 parent 39b4ebf commit ce84c47

File tree

15 files changed

+645
-123
lines changed

15 files changed

+645
-123
lines changed

.changeset/spotty-snakes-fry.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@neo4j/graphql": minor
3+
---
4+
5+
The evaluation of authorization rules is now supported when using the Neo4j GraphQL Library as a Federation Subgraph.

.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ module.exports = {
6161
prefer: "type-imports",
6262
},
6363
],
64+
"@typescript-eslint/no-unused-vars": ["error", { "ignoreRestSiblings": true }],
6465
},
6566
settings: {
6667
"import/resolver": {

packages/graphql/src/translate/translate-resolve-reference.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ export function translateResolveReference({
3838

3939
const matchNode = new Cypher.NamedNode(varName, { labels: node.getLabels(context) });
4040

41-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
4241
const { __typename, ...where } = reference;
4342

4443
const {
@@ -61,6 +60,16 @@ export function translateResolveReference({
6160
cypherFieldAliasMap: {},
6261
});
6362

63+
let projAuth: Cypher.Clause | undefined;
64+
65+
const predicates: Cypher.Predicate[] = [];
66+
67+
predicates.push(...projection.predicates);
68+
69+
if (predicates.length) {
70+
projAuth = new Cypher.With("*").where(Cypher.and(...predicates));
71+
}
72+
6473
const projectionSubqueries = Cypher.concat(...projection.subqueries, ...projection.subqueriesBeforeSort);
6574

6675
const projectionExpression = new Cypher.RawCypher((env) => {
@@ -69,20 +78,17 @@ export function translateResolveReference({
6978

7079
const returnClause = new Cypher.Return([projectionExpression, varName]);
7180

72-
const projectionClause: Cypher.Clause = returnClause; // TODO avoid reassign
73-
let connectionPreClauses: Cypher.Clause | undefined;
74-
7581
const preComputedWhereFields =
7682
preComputedWhereFieldSubqueries && !preComputedWhereFieldSubqueries.empty
7783
? Cypher.concat(preComputedWhereFieldSubqueries, topLevelWhereClause)
78-
: undefined;
84+
: topLevelWhereClause;
7985

8086
const readQuery = Cypher.concat(
8187
topLevelMatch,
8288
preComputedWhereFields,
83-
connectionPreClauses,
89+
projAuth,
8490
projectionSubqueries,
85-
projectionClause
91+
returnClause
8692
);
8793

8894
return readQuery.build(undefined, context.cypherParams ? { cypherParams: context.cypherParams } : {});

packages/graphql/tests/e2e/federation/apollo-federation-subgraph-compatibility/apollo-federation-subgraph-compatibility.e2e.test.ts

Lines changed: 35 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import { SubgraphServer } from "../setup/subgraph-server";
2525
import { Neo4j } from "../setup/neo4j";
2626
import { schema as inventory } from "./subgraphs/inventory";
2727
import { schema as users } from "./subgraphs/users";
28-
import { productsRequest, routerRequest } from "./utils/client";
28+
import { graphqlRequest } from "./utils/client";
2929
import { stripIgnoredCharacters } from "graphql";
3030

3131
describe("Tests copied from https://github.com/apollographql/apollo-federation-subgraph-compatibility", () => {
@@ -37,6 +37,9 @@ describe("Tests copied from https://github.com/apollographql/apollo-federation-s
3737

3838
let neo4j: Neo4j;
3939

40+
let productsUrl: string;
41+
let gatewayUrl: string;
42+
4043
beforeAll(async () => {
4144
const products = gql`
4245
extend schema
@@ -122,8 +125,8 @@ describe("Tests copied from https://github.com/apollographql/apollo-federation-s
122125
neo4j = new Neo4j();
123126
await neo4j.init();
124127

125-
inventoryServer = new SubgraphServer(inventory, 4010);
126-
usersServer = new SubgraphServer(users, 4012);
128+
inventoryServer = new SubgraphServer(inventory);
129+
usersServer = new SubgraphServer(users);
127130

128131
const productsSubgraph = new TestSubgraph({
129132
typeDefs: products,
@@ -142,24 +145,18 @@ describe("Tests copied from https://github.com/apollographql/apollo-federation-s
142145

143146
const productsSchema = await productsSubgraph.getSchema();
144147

145-
productsServer = new SubgraphServer(productsSchema, 4011);
148+
productsServer = new SubgraphServer(productsSchema);
146149

147-
const [inventoryUrl, productsUrl, usersUrl] = await Promise.all([
148-
inventoryServer.start(),
149-
productsServer.start(),
150-
usersServer.start(),
151-
]);
150+
productsUrl = await productsServer.start();
151+
const [inventoryUrl, usersUrl] = await Promise.all([inventoryServer.start(), usersServer.start()]);
152152

153-
gatewayServer = new GatewayServer(
154-
[
155-
{ name: "inventory", url: inventoryUrl },
156-
{ name: "products", url: productsUrl },
157-
{ name: "users", url: usersUrl },
158-
],
159-
4013
160-
);
153+
gatewayServer = new GatewayServer([
154+
{ name: "inventory", url: inventoryUrl },
155+
{ name: "products", url: productsUrl },
156+
{ name: "users", url: usersUrl },
157+
]);
161158

162-
await gatewayServer.start();
159+
gatewayUrl = await gatewayServer.start();
163160

164161
await neo4j.executeWrite(
165162
`
@@ -203,7 +200,8 @@ describe("Tests copied from https://github.com/apollographql/apollo-federation-s
203200
});
204201

205202
test("ftv1", async () => {
206-
const resp = await productsRequest(
203+
const resp = await graphqlRequest(
204+
productsUrl,
207205
{
208206
query: `query { __typename }`,
209207
},
@@ -222,7 +220,7 @@ describe("Tests copied from https://github.com/apollographql/apollo-federation-s
222220

223221
describe("@inaccessible", () => {
224222
it("should return @inaccessible directives in _service sdl", async () => {
225-
const response = await productsRequest({
223+
const response = await graphqlRequest(productsUrl, {
226224
query: "query { _service { sdl } }",
227225
});
228226

@@ -233,7 +231,7 @@ describe("Tests copied from https://github.com/apollographql/apollo-federation-s
233231
});
234232

235233
it("should be able to query @inaccessible fields via the products schema directly", async () => {
236-
const resp = await productsRequest({
234+
const resp = await graphqlRequest(productsUrl, {
237235
query: `
238236
query GetProduct($id: ID!) {
239237
product(id: $id) {
@@ -261,7 +259,7 @@ describe("Tests copied from https://github.com/apollographql/apollo-federation-s
261259

262260
describe("@key single", () => {
263261
test("applies single field @key on User", async () => {
264-
const serviceSDLQuery = await productsRequest({
262+
const serviceSDLQuery = await graphqlRequest(productsUrl, {
265263
query: "query { _service { sdl } }",
266264
});
267265

@@ -273,7 +271,7 @@ describe("Tests copied from https://github.com/apollographql/apollo-federation-s
273271
});
274272

275273
test("resolves single field @key on User", async () => {
276-
const resp = await productsRequest({
274+
const resp = await graphqlRequest(productsUrl, {
277275
query: `#graphql
278276
query ($representations: [_Any!]!) {
279277
_entities(representations: $representations) {
@@ -302,7 +300,7 @@ describe("Tests copied from https://github.com/apollographql/apollo-federation-s
302300

303301
describe("@key multiple", () => {
304302
test("applies multiple field @key on DeprecatedProduct", async () => {
305-
const serviceSDLQuery = await productsRequest({
303+
const serviceSDLQuery = await graphqlRequest(productsUrl, {
306304
query: "query { _service { sdl } }",
307305
});
308306

@@ -312,7 +310,7 @@ describe("Tests copied from https://github.com/apollographql/apollo-federation-s
312310
});
313311

314312
test("resolves multiple field @key on DeprecatedProduct", async () => {
315-
const resp = await productsRequest({
313+
const resp = await graphqlRequest(productsUrl, {
316314
query: `#graphql
317315
query ($representations: [_Any!]!) {
318316
_entities(representations: $representations) {
@@ -348,7 +346,7 @@ describe("Tests copied from https://github.com/apollographql/apollo-federation-s
348346

349347
describe("@key composite", () => {
350348
test("applies composite object @key on ProductResearch", async () => {
351-
const serviceSDLQuery = await productsRequest({
349+
const serviceSDLQuery = await graphqlRequest(productsUrl, {
352350
query: "query { _service { sdl } }",
353351
});
354352

@@ -358,7 +356,7 @@ describe("Tests copied from https://github.com/apollographql/apollo-federation-s
358356
});
359357

360358
test("resolves composite object @key on ProductResearch", async () => {
361-
const resp = await productsRequest({
359+
const resp = await graphqlRequest(productsUrl, {
362360
query: `#graphql
363361
query ($representations: [_Any!]!) {
364362
_entities(representations: $representations) {
@@ -396,7 +394,7 @@ describe("Tests copied from https://github.com/apollographql/apollo-federation-s
396394

397395
describe("repeatable @key", () => {
398396
test("applies repeatable @key directive on Product", async () => {
399-
const serviceSDLQuery = await productsRequest({
397+
const serviceSDLQuery = await graphqlRequest(productsUrl, {
400398
query: "query { _service { sdl } }",
401399
});
402400

@@ -415,7 +413,7 @@ describe("Tests copied from https://github.com/apollographql/apollo-federation-s
415413
});
416414

417415
test("resolves multiple @key directives on Product", async () => {
418-
const entitiesQuery = await productsRequest({
416+
const entitiesQuery = await graphqlRequest(productsUrl, {
419417
query: `#graphql
420418
query ($representations: [_Any!]!) {
421419
_entities(representations: $representations) {
@@ -466,7 +464,7 @@ describe("Tests copied from https://github.com/apollographql/apollo-federation-s
466464
});
467465

468466
test("@link", async () => {
469-
const response = await productsRequest({
467+
const response = await graphqlRequest(productsUrl, {
470468
query: "query { _service { sdl } }",
471469
});
472470

@@ -544,7 +542,7 @@ describe("Tests copied from https://github.com/apollographql/apollo-federation-s
544542

545543
describe("@override", () => {
546544
it("should return @override directives in _service sdl", async () => {
547-
const response = await productsRequest({
545+
const response = await graphqlRequest(productsUrl, {
548546
query: "query { _service { sdl } }",
549547
});
550548

@@ -553,7 +551,7 @@ describe("Tests copied from https://github.com/apollographql/apollo-federation-s
553551
});
554552

555553
it("should return overridden user name", async () => {
556-
const resp = await routerRequest({
554+
const resp = await graphqlRequest(gatewayUrl, {
557555
query: `
558556
query GetProduct($id: ID!) {
559557
product(id: $id) {
@@ -580,7 +578,7 @@ describe("Tests copied from https://github.com/apollographql/apollo-federation-s
580578
});
581579

582580
test("@provides", async () => {
583-
const resp = await productsRequest({
581+
const resp = await graphqlRequest(productsUrl, {
584582
query: `#graphql
585583
query ($id: ID!) {
586584
product(id: $id) {
@@ -607,7 +605,7 @@ describe("Tests copied from https://github.com/apollographql/apollo-federation-s
607605
});
608606

609607
test("@requires", async () => {
610-
const resp = await routerRequest({
608+
const resp = await graphqlRequest(gatewayUrl, {
611609
query: `#graphql
612610
query ($id: ID!) {
613611
product(id: $id) { createdBy { averageProductsCreatedPerYear email } }
@@ -630,7 +628,7 @@ describe("Tests copied from https://github.com/apollographql/apollo-federation-s
630628

631629
describe("@shareable", () => {
632630
it("should return @shareable directives in _service sdl", async () => {
633-
const response = await productsRequest({
631+
const response = await graphqlRequest(productsUrl, {
634632
query: "query { _service { sdl } }",
635633
});
636634

@@ -639,7 +637,7 @@ describe("Tests copied from https://github.com/apollographql/apollo-federation-s
639637
});
640638

641639
it("should be able to resolve @shareable ProductDimension types", async () => {
642-
const resp = await routerRequest({
640+
const resp = await graphqlRequest(gatewayUrl, {
643641
query: `
644642
query GetProduct($id: ID!) {
645643
product(id: $id) {
@@ -668,7 +666,7 @@ describe("Tests copied from https://github.com/apollographql/apollo-federation-s
668666
});
669667

670668
test("@tag", async () => {
671-
const response = await productsRequest({
669+
const response = await graphqlRequest(productsUrl, {
672670
query: "query { _service { sdl } }",
673671
});
674672

packages/graphql/tests/e2e/federation/apollo-federation-subgraph-compatibility/utils/client.ts

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -35,28 +35,6 @@ export async function graphqlRequest(
3535
return resp.text();
3636
}
3737

38-
export function productsRequest(
39-
req: {
40-
query: string;
41-
variables?: { [key: string]: any };
42-
operationName?: string;
43-
},
44-
headers?: { [key: string]: any }
45-
) {
46-
return graphqlRequest(PRODUCTS_URL, req, headers);
47-
}
48-
49-
export function routerRequest(
50-
req: {
51-
query: string;
52-
variables?: { [key: string]: any };
53-
operationName?: string;
54-
},
55-
headers?: { [key: string]: any }
56-
) {
57-
return graphqlRequest(ROUTER_URL, req, headers);
58-
}
59-
6038
export async function healthcheckAll(libraryName: string): Promise<boolean> {
6139
const routerUp = await healthcheckRouter();
6240
if (!routerUp) {

0 commit comments

Comments
 (0)