Skip to content

Commit

Permalink
Merge pull request #5598 from mjfwebb/cypher-filtering-validate-auth
Browse files Browse the repository at this point in the history
Cypher filtering - Auth validate
  • Loading branch information
mjfwebb authored Sep 27, 2024
2 parents a067e31 + 99c4f47 commit 42c6e07
Show file tree
Hide file tree
Showing 3 changed files with 199 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export class CypherFilter extends Filter {
});

if (this.checkIsNotNull) {
return Cypher.and(Cypher.isNotNull(this.comparisonValue), operation);
return Cypher.and(Cypher.isNotNull(this.comparisonValue), Cypher.isNotNull(this.returnVariable), operation);
}

return operation;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -343,4 +343,128 @@ describe("cypher directive filtering", () => {
],
});
});

test("With authorization on type using @cypher return value, with validate FAIL", async () => {
const Movie = testHelper.createUniqueType("Movie");
const Actor = testHelper.createUniqueType("Actor");

const typeDefs = /* GraphQL */ `
type ${Movie} @node @authorization(validate: [{ where: { node: { custom_field: "$jwt.custom_value" } } }]) {
title: String
custom_field: String
@cypher(
statement: """
MATCH (this)
RETURN this.custom_field AS s
"""
columnName: "s"
)
actors: [${Actor}!]! @relationship(type: "ACTED_IN", direction: IN)
}
type ${Actor} @node {
name: String
movies: [${Movie}!]! @relationship(type: "ACTED_IN", direction: OUT)
}
`;

await testHelper.initNeo4jGraphQL({
typeDefs,
features: {
authorization: {
key: "secret",
},
},
});

const token = createBearerToken("secret", { custom_value: "hello" });

await testHelper.executeCypher(
`
CREATE (m:${Movie} { title: "The Matrix" })
CREATE (m2:${Movie} { title: "The Matrix 2", custom_field: "hello" })
CREATE (m3:${Movie} { title: "The Matrix 3" })
CREATE (a:${Actor} { name: "Keanu Reeves" })
CREATE (a)-[:ACTED_IN]->(m)
`,
{}
);

const query = `
query {
${Movie.plural} {
title
}
}
`;

const gqlResult = await testHelper.executeGraphQLWithToken(query, token);

expect(gqlResult.errors).toHaveLength(1);
expect(gqlResult.errors?.[0]?.message).toBe("Forbidden");
});

test("With authorization on type using @cypher return value, with validate PASS", async () => {
const Movie = testHelper.createUniqueType("Movie");
const Actor = testHelper.createUniqueType("Actor");

const typeDefs = /* GraphQL */ `
type ${Movie} @node @authorization(validate: [{ where: { node: { custom_field: "$jwt.custom_value" } } }]) {
title: String
custom_field: String
@cypher(
statement: """
MATCH (this)
RETURN this.custom_field AS s
"""
columnName: "s"
)
actors: [${Actor}!]! @relationship(type: "ACTED_IN", direction: IN)
}
type ${Actor} @node {
name: String
movies: [${Movie}!]! @relationship(type: "ACTED_IN", direction: OUT)
}
`;

await testHelper.initNeo4jGraphQL({
typeDefs,
features: {
authorization: {
key: "secret",
},
},
});

const token = createBearerToken("secret", { custom_value: "hello" });

await testHelper.executeCypher(
`
CREATE (m2:${Movie} { title: "The Matrix", custom_field: "hello" })
CREATE (a:${Actor} { name: "Keanu Reeves" })
CREATE (a)-[:ACTED_IN]->(m)
`,
{}
);

const query = `
query {
${Movie.plural} {
title
}
}
`;

const gqlResult = await testHelper.executeGraphQLWithToken(query, token);

expect(gqlResult.errors).toBeFalsy();
expect(gqlResult?.data).toEqual({
[Movie.plural]: [
{
title: "The Matrix",
},
],
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ describe("cypher directive filtering - Auth", () => {
RETURN this0 AS var1
}
WITH *
WHERE ($isAuthenticated = true AND ($jwt.custom_value IS NOT NULL AND var1 = $jwt.custom_value))
WHERE ($isAuthenticated = true AND ($jwt.custom_value IS NOT NULL AND var1 IS NOT NULL AND var1 = $jwt.custom_value))
RETURN this { .title } AS this"
`);

Expand Down Expand Up @@ -248,7 +248,7 @@ describe("cypher directive filtering - Auth", () => {
RETURN this1 AS var2
}
WITH *
WHERE ($jwt.custom_value IS NOT NULL AND var2 = $jwt.custom_value)
WHERE ($jwt.custom_value IS NOT NULL AND var2 IS NOT NULL AND var2 = $jwt.custom_value)
RETURN count(this0) > 0 AS var3
}
WITH *
Expand Down Expand Up @@ -321,7 +321,78 @@ describe("cypher directive filtering - Auth", () => {
RETURN this0 AS var1
}
WITH *
WHERE ($isAuthenticated = true AND ($jwt.custom_value IS NOT NULL AND var1 = $jwt.custom_value))
WHERE ($isAuthenticated = true AND ($jwt.custom_value IS NOT NULL AND var1 IS NOT NULL AND var1 = $jwt.custom_value))
RETURN this { .title } AS this"
`);

expect(formatParams(result.params)).toMatchInlineSnapshot(`
"{
\\"isAuthenticated\\": true,
\\"jwt\\": {
\\"roles\\": [],
\\"custom_value\\": \\"hello\\"
}
}"
`);
});

test("With authorization on type using @cypher return value, with validate", async () => {
const typeDefs = /* GraphQL */ `
type Movie @node @authorization(validate: [{ where: { node: { custom_field: "$jwt.custom_value" } } }]) {
title: String
custom_field: String
@cypher(
statement: """
MATCH (this)
RETURN this.custom_field AS s
"""
columnName: "s"
)
actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN)
}
type Actor @node {
name: String
movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT)
}
`;

const token = createBearerToken("secret", { custom_value: "hello" });

const query = `
query {
movies {
title
}
}
`;

const neoSchema: Neo4jGraphQL = new Neo4jGraphQL({
typeDefs,
features: {
authorization: {
key: "secret",
},
},
});

const result = await translateQuery(neoSchema, query, { token });

expect(formatCypher(result.cypher)).toMatchInlineSnapshot(`
"MATCH (this:Movie)
CALL {
WITH this
CALL {
WITH this
WITH this AS this
MATCH (this)
RETURN this.custom_field AS s
}
WITH s AS this0
RETURN this0 AS var1
}
WITH *
WHERE apoc.util.validatePredicate(NOT ($isAuthenticated = true AND ($jwt.custom_value IS NOT NULL AND var1 IS NOT NULL AND var1 = $jwt.custom_value)), \\"@neo4j/graphql/FORBIDDEN\\", [0])
RETURN this { .title } AS this"
`);

Expand Down

0 comments on commit 42c6e07

Please sign in to comment.