From 1a154b04b0d9e81c219abf36cba71be3712ad27e Mon Sep 17 00:00:00 2001 From: Daniel Starns Date: Mon, 21 Jun 2021 14:39:57 +0100 Subject: [PATCH 1/3] config: remove codeowners --- .github/CODEOWNERS | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index 301c5f3494..0000000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1 +0,0 @@ -* @danstarns @darrellwarde From 9377a6ed6b9c48239a455d006a3f7f4fea361c4a Mon Sep 17 00:00:00 2001 From: Daniel Starns Date: Fri, 16 Jul 2021 13:31:11 +0100 Subject: [PATCH 2/3] fix: add cypher WITH stmt in correct place to fix #326 --- packages/graphql/src/schema/resolvers/cypher.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/graphql/src/schema/resolvers/cypher.ts b/packages/graphql/src/schema/resolvers/cypher.ts index cbffe77118..b58a2d7302 100644 --- a/packages/graphql/src/schema/resolvers/cypher.ts +++ b/packages/graphql/src/schema/resolvers/cypher.ts @@ -86,6 +86,7 @@ export default function cypherResolver({ cypherStrs.push(` WITH apoc.cypher.runFirstColumn("${statement}", ${apocParamsStr}, ${expectMultipleValues}) as x UNWIND x as this + WITH this `); } else { cypherStrs.push(` From d6c6d46c58753cbce2b9e3c5750fa3c37912e443 Mon Sep 17 00:00:00 2001 From: Daniel Starns Date: Fri, 16 Jul 2021 13:31:50 +0100 Subject: [PATCH 3/3] test: add cases for auth under custom cypher projection to fix #326 --- .../tests/integration/issues/326.int.test.ts | 177 ++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 packages/graphql/tests/integration/issues/326.int.test.ts diff --git a/packages/graphql/tests/integration/issues/326.int.test.ts b/packages/graphql/tests/integration/issues/326.int.test.ts new file mode 100644 index 0000000000..434431c6da --- /dev/null +++ b/packages/graphql/tests/integration/issues/326.int.test.ts @@ -0,0 +1,177 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Driver } from "neo4j-driver"; +import { graphql } from "graphql"; +import { generate } from "randomstring"; +import neo4j from "../neo4j"; +import { Neo4jGraphQL } from "../../../src/classes"; +import { IncomingMessage } from "http"; +import jsonwebtoken from "jsonwebtoken"; +import { Socket } from "net"; + +describe("326", () => { + let driver: Driver; + + beforeAll(async () => { + driver = await neo4j(); + }); + + afterAll(async () => { + await driver.close(); + }); + + test("should throw forbidden when user does not have correct allow on projection field(using Query)", async () => { + const session = driver.session(); + + const id = generate({ + charset: "alphabetic", + }); + + const typeDefs = ` + type Query { + getSelf: [User]! + @cypher( + statement: """ + MATCH (user:User { id: \\"${id}\\" }) + RETURN user + """ + ) + } + + type User { + id: ID + email: String! @auth(rules: [{ operations: [READ], allow: { id: "$jwt.sub" } }]) + } + `; + + const secret = "secret"; + + const token = jsonwebtoken.sign( + { + roles: [], + sub: "invalid", + }, + secret + ); + + const neoSchema = new Neo4jGraphQL({ typeDefs, config: { jwt: { secret } } }); + + const query = ` + { + getSelf { + email + } + } + `; + + try { + await session.run( + ` + CREATE (:User {id: $id, email: randomUUID()}) + `, + { id } + ); + + const socket = new Socket({ readable: true }); + const req = new IncomingMessage(socket); + req.headers.authorization = `Bearer ${token}`; + + const gqlResult = await graphql({ + schema: neoSchema.schema, + source: query, + variableValues: { id }, + contextValue: { driver, req }, + }); + + expect((gqlResult.errors as any[])[0].message).toEqual("Forbidden"); + } finally { + await session.close(); + } + }); + + test("should throw forbidden when user does not have correct allow on projection field(using Mutation)", async () => { + const session = driver.session(); + + const id = generate({ + charset: "alphabetic", + }); + + const typeDefs = ` + type Mutation { + getSelf: [User]! + @cypher( + statement: """ + MATCH (user:User { id: \\"${id}\\" }) + RETURN user + """ + ) + } + + type User { + id: ID + email: String! @auth(rules: [{ operations: [READ], allow: { id: "$jwt.sub" } }]) + } + `; + + const secret = "secret"; + + const token = jsonwebtoken.sign( + { + roles: [], + sub: "invalid", + }, + secret + ); + + const neoSchema = new Neo4jGraphQL({ typeDefs, config: { jwt: { secret } } }); + + const query = ` + mutation { + getSelf { + email + } + } + `; + + try { + await session.run( + ` + CREATE (:User {id: $id, email: randomUUID()}) + `, + { id } + ); + + const socket = new Socket({ readable: true }); + const req = new IncomingMessage(socket); + req.headers.authorization = `Bearer ${token}`; + + const gqlResult = await graphql({ + schema: neoSchema.schema, + source: query, + variableValues: { id }, + contextValue: { driver, req }, + }); + + expect((gqlResult.errors as any[])[0].message).toEqual("Forbidden"); + } finally { + await session.close(); + } + }); +});