Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Authorization support for Apollo Federation #3661

Merged
Merged
5 changes: 5 additions & 0 deletions .changeset/spotty-snakes-fry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@neo4j/graphql": minor
---

The evaluation of authorization rules is now supported when using the Neo4j GraphQL Library as a Federation Subgraph.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ module.exports = {
prefer: "type-imports",
},
],
"@typescript-eslint/no-unused-vars": ["error", { "ignoreRestSiblings": true }],
},
settings: {
"import/resolver": {
Expand Down
20 changes: 13 additions & 7 deletions packages/graphql/src/translate/translate-resolve-reference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ export function translateResolveReference({

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

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

const {
Expand All @@ -61,6 +60,16 @@ export function translateResolveReference({
cypherFieldAliasMap: {},
});

let projAuth: Cypher.Clause | undefined;

const predicates: Cypher.Predicate[] = [];

predicates.push(...projection.predicates);

if (predicates.length) {
projAuth = new Cypher.With("*").where(Cypher.and(...predicates));
}

const projectionSubqueries = Cypher.concat(...projection.subqueries, ...projection.subqueriesBeforeSort);

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

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

const projectionClause: Cypher.Clause = returnClause; // TODO avoid reassign
let connectionPreClauses: Cypher.Clause | undefined;

const preComputedWhereFields =
preComputedWhereFieldSubqueries && !preComputedWhereFieldSubqueries.empty
? Cypher.concat(preComputedWhereFieldSubqueries, topLevelWhereClause)
: undefined;
: topLevelWhereClause;

const readQuery = Cypher.concat(
topLevelMatch,
preComputedWhereFields,
connectionPreClauses,
projAuth,
projectionSubqueries,
projectionClause
returnClause
);

return readQuery.build(undefined, context.cypherParams ? { cypherParams: context.cypherParams } : {});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { SubgraphServer } from "../setup/subgraph-server";
import { Neo4j } from "../setup/neo4j";
import { schema as inventory } from "./subgraphs/inventory";
import { schema as users } from "./subgraphs/users";
import { productsRequest, routerRequest } from "./utils/client";
import { graphqlRequest } from "./utils/client";
import { stripIgnoredCharacters } from "graphql";

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

let neo4j: Neo4j;

let productsUrl: string;
let gatewayUrl: string;

beforeAll(async () => {
const products = gql`
extend schema
Expand Down Expand Up @@ -122,8 +125,8 @@ describe("Tests copied from https://github.com/apollographql/apollo-federation-s
neo4j = new Neo4j();
await neo4j.init();

inventoryServer = new SubgraphServer(inventory, 4010);
usersServer = new SubgraphServer(users, 4012);
inventoryServer = new SubgraphServer(inventory);
usersServer = new SubgraphServer(users);

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

const productsSchema = await productsSubgraph.getSchema();

productsServer = new SubgraphServer(productsSchema, 4011);
productsServer = new SubgraphServer(productsSchema);

const [inventoryUrl, productsUrl, usersUrl] = await Promise.all([
inventoryServer.start(),
productsServer.start(),
usersServer.start(),
]);
productsUrl = await productsServer.start();
const [inventoryUrl, usersUrl] = await Promise.all([inventoryServer.start(), usersServer.start()]);

gatewayServer = new GatewayServer(
[
{ name: "inventory", url: inventoryUrl },
{ name: "products", url: productsUrl },
{ name: "users", url: usersUrl },
],
4013
);
gatewayServer = new GatewayServer([
{ name: "inventory", url: inventoryUrl },
{ name: "products", url: productsUrl },
{ name: "users", url: usersUrl },
]);

await gatewayServer.start();
gatewayUrl = await gatewayServer.start();

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

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

describe("@inaccessible", () => {
it("should return @inaccessible directives in _service sdl", async () => {
const response = await productsRequest({
const response = await graphqlRequest(productsUrl, {
query: "query { _service { sdl } }",
});

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

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

describe("@key single", () => {
test("applies single field @key on User", async () => {
const serviceSDLQuery = await productsRequest({
const serviceSDLQuery = await graphqlRequest(productsUrl, {
query: "query { _service { sdl } }",
});

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

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

describe("@key multiple", () => {
test("applies multiple field @key on DeprecatedProduct", async () => {
const serviceSDLQuery = await productsRequest({
const serviceSDLQuery = await graphqlRequest(productsUrl, {
query: "query { _service { sdl } }",
});

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

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

describe("@key composite", () => {
test("applies composite object @key on ProductResearch", async () => {
const serviceSDLQuery = await productsRequest({
const serviceSDLQuery = await graphqlRequest(productsUrl, {
query: "query { _service { sdl } }",
});

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

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

describe("repeatable @key", () => {
test("applies repeatable @key directive on Product", async () => {
const serviceSDLQuery = await productsRequest({
const serviceSDLQuery = await graphqlRequest(productsUrl, {
query: "query { _service { sdl } }",
});

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

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

test("@link", async () => {
const response = await productsRequest({
const response = await graphqlRequest(productsUrl, {
query: "query { _service { sdl } }",
});

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

describe("@override", () => {
it("should return @override directives in _service sdl", async () => {
const response = await productsRequest({
const response = await graphqlRequest(productsUrl, {
query: "query { _service { sdl } }",
});

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

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

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

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

describe("@shareable", () => {
it("should return @shareable directives in _service sdl", async () => {
const response = await productsRequest({
const response = await graphqlRequest(productsUrl, {
query: "query { _service { sdl } }",
});

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

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

test("@tag", async () => {
const response = await productsRequest({
const response = await graphqlRequest(productsUrl, {
query: "query { _service { sdl } }",
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,28 +35,6 @@ export async function graphqlRequest(
return resp.text();
}

export function productsRequest(
req: {
query: string;
variables?: { [key: string]: any };
operationName?: string;
},
headers?: { [key: string]: any }
) {
return graphqlRequest(PRODUCTS_URL, req, headers);
}

export function routerRequest(
req: {
query: string;
variables?: { [key: string]: any };
operationName?: string;
},
headers?: { [key: string]: any }
) {
return graphqlRequest(ROUTER_URL, req, headers);
}

export async function healthcheckAll(libraryName: string): Promise<boolean> {
const routerUp = await healthcheckRouter();
if (!routerUp) {
Expand Down
Loading