Skip to content
This repository was archived by the owner on Sep 3, 2021. It is now read-only.

Commit ca76f9f

Browse files
authored
Merge pull request #146 from moxious/master
WIP - query escaping and safety
2 parents ee27fe2 + 2df7ab5 commit ca76f9f

File tree

5 files changed

+485
-381
lines changed

5 files changed

+485
-381
lines changed

src/index.js

+68-35
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ import {
1717
getFieldValueType,
1818
extractTypeMapFromTypeDefs,
1919
addDirectiveDeclarations,
20-
printTypeMap
20+
printTypeMap,
21+
safeLabel,
22+
safeVar
2123
} from './utils';
2224
import { buildCypherSelection } from './selections';
2325
import {
@@ -132,14 +134,16 @@ export function cypherQuery(
132134

133135
query = `WITH apoc.cypher.runFirstColumn("${
134136
cypherQueryArg.value.value
135-
}", ${argString}, True) AS x UNWIND x AS ${variableName}
136-
RETURN ${variableName} {${subQuery}} AS ${variableName}${orderByValue} ${outerSkipLimit}`;
137+
}", ${argString}, True) AS x UNWIND x AS ${safeVar(variableName)}
138+
RETURN ${safeVar(variableName)} {${subQuery}} AS ${safeVar(
139+
variableName
140+
)}${orderByValue} ${outerSkipLimit}`;
137141
} else {
138142
// No @cypher directive on QueryType
139143

140144
// FIXME: support IN for multiple values -> WHERE
141145
const idWherePredicate =
142-
typeof _id !== 'undefined' ? `ID(${variableName})=${_id}` : '';
146+
typeof _id !== 'undefined' ? `ID(${safeVar(variableName)})=${_id}` : '';
143147
const nullFieldPredicates = Object.keys(nullParams).map(
144148
key => `${variableName}.${key} IS NULL`
145149
);
@@ -149,9 +153,12 @@ export function cypherQuery(
149153
const predicate = predicateClauses ? `WHERE ${predicateClauses} ` : '';
150154

151155
query =
152-
`MATCH (${variableName}:${typeName} ${argString}) ${predicate}` +
153-
`RETURN ${variableName} {${subQuery}} AS ${variableName}${orderByValue} ${outerSkipLimit}`;
154-
156+
`MATCH (${safeVar(variableName)}:${safeLabel(
157+
typeName
158+
)} ${argString}) ${predicate}` +
159+
`RETURN ${safeVar(variableName)} {${subQuery}} AS ${safeVar(
160+
variableName
161+
)}${orderByValue} ${outerSkipLimit}`;
155162
}
156163

157164
return [query, { ...nonNullParams, ...subParams }];
@@ -227,10 +234,12 @@ export function cypherMutation(
227234
cypherQueryArg.value.value
228235
}", ${argString}) YIELD value
229236
WITH apoc.map.values(value, [keys(value)[0]])[0] AS ${variableName}
230-
RETURN ${variableName} {${subQuery}} AS ${variableName}${orderByValue} ${outerSkipLimit}`;
237+
RETURN ${safeVar(variableName)} {${subQuery}} AS ${safeVar(
238+
variableName
239+
)}${orderByValue} ${outerSkipLimit}`;
231240
} else if (isCreateMutation(resolveInfo)) {
232-
query = `CREATE (${variableName}:${typeName}) `;
233-
query += `SET ${variableName} = $params `;
241+
query = `CREATE (${safeVar(variableName)}:${safeLabel(typeName)}) `;
242+
query += `SET ${safeVar(variableName)} = $params `;
234243
//query += `RETURN ${variable}`;
235244

236245
const [subQuery, subParams] = buildCypherSelection({
@@ -247,15 +256,17 @@ export function cypherMutation(
247256
resolveInfo.fieldName
248257
].astNode.arguments;
249258

250-
const firstIdArg = args.find(e => getFieldValueType(e) === "ID");
259+
const firstIdArg = args.find(e => getFieldValueType(e) === 'ID');
251260
if (firstIdArg) {
252261
const firstIdArgFieldName = firstIdArg.name.value;
253262
if (params.params[firstIdArgFieldName] === undefined) {
254263
query += `SET ${variableName}.${firstIdArgFieldName} = apoc.create.uuid() `;
255264
}
256265
}
257266

258-
query += `RETURN ${variableName} {${subQuery}} AS ${variableName}`;
267+
query += `RETURN ${safeVar(variableName)} {${subQuery}} AS ${safeVar(
268+
variableName
269+
)}`;
259270
} else if (isAddMutation(resolveInfo)) {
260271
let mutationMeta, relationshipNameArg, fromTypeArg, toTypeArg;
261272

@@ -332,25 +343,35 @@ export function cypherMutation(
332343
paramIndex: 1,
333344
rootVariableNames: {
334345
from: `${fromVar}`,
335-
to: `${toVar}`,
346+
to: `${toVar}`
336347
},
337348
variableName: schemaType.name === fromType ? `${toVar}` : `${fromVar}`
338349
});
339350
params = { ...params, ...subParams };
340351
query = `
341-
MATCH (${fromVar}:${fromType} {${fromParam}: $from.${fromParam}})
342-
MATCH (${toVar}:${toType} {${toParam}: $to.${toParam}})
343-
CREATE (${fromVar})-[${lowercased}_relation:${relationshipName}${
352+
MATCH (${safeVar(fromVar)}:${safeLabel(
353+
fromType
354+
)} {${fromParam}: $from.${fromParam}})
355+
MATCH (${safeVar(toVar)}:${safeLabel(
356+
toType
357+
)} {${toParam}: $to.${toParam}})
358+
CREATE (${safeVar(fromVar)})-[${safeVar(
359+
lowercased + '_relation'
360+
)}:${safeLabel(relationshipName)}${
344361
relationPropertyArguments ? ` {${relationPropertyArguments}}` : ''
345-
}]->(${toVar})
346-
RETURN ${lowercased}_relation { ${subQuery} } AS ${schemaType};
362+
}]->(${safeVar(toVar)})
363+
RETURN ${safeVar(lowercased + '_relation')} { ${subQuery} } AS ${safeVar(
364+
schemaType
365+
)};
347366
`;
348367
} else if (isUpdateMutation(resolveInfo)) {
349368
const idParam = resolveInfo.schema.getMutationType().getFields()[
350369
resolveInfo.fieldName
351370
].astNode.arguments[0].name.value;
352371

353-
query = `MATCH (${variableName}:${typeName} {${idParam}: $params.${
372+
query = `MATCH (${safeVar(variableName)}:${safeLabel(
373+
typeName
374+
)} {${idParam}: $params.${
354375
resolveInfo.schema.getMutationType().getFields()[resolveInfo.fieldName]
355376
.astNode.arguments[0].name.value
356377
}}) `;
@@ -366,7 +387,9 @@ export function cypherMutation(
366387
});
367388
params = { ...params, ...subParams };
368389

369-
query += `RETURN ${variableName} {${subQuery}} AS ${variableName}`;
390+
query += `RETURN ${safeVar(variableName)} {${subQuery}} AS ${safeVar(
391+
variableName
392+
)}`;
370393
} else if (isDeleteMutation(resolveInfo)) {
371394
const idParam = resolveInfo.schema.getMutationType().getFields()[
372395
resolveInfo.fieldName
@@ -384,14 +407,17 @@ export function cypherMutation(
384407

385408
// Cannot execute a map projection on a deleted node in Neo4j
386409
// so the projection is executed and aliased before the delete
387-
query = `MATCH (${variableName}:${typeName} {${idParam}: $${
410+
query = `MATCH (${safeVar(variableName)}:${safeLabel(
411+
typeName
412+
)} {${idParam}: $${
388413
resolveInfo.schema.getMutationType().getFields()[resolveInfo.fieldName]
389414
.astNode.arguments[0].name.value
390415
}})
391-
WITH ${variableName} AS ${variableName +
392-
'_toDelete'}, ${variableName} {${subQuery}} AS ${variableName}
393-
DETACH DELETE ${variableName + '_toDelete'}
394-
RETURN ${variableName}`;
416+
WITH ${safeVar(variableName)} AS ${safeVar(
417+
variableName + '_toDelete'
418+
)}, ${safeVar(variableName)} {${subQuery}} AS ${safeVar(variableName)}
419+
DETACH DELETE ${safeVar(variableName + '_toDelete')}
420+
RETURN ${safeVar(variableName)}`;
395421
} else if (isRemoveMutation(resolveInfo)) {
396422
let mutationMeta, relationshipNameArg, fromTypeArg, toTypeArg;
397423

@@ -460,7 +486,7 @@ RETURN ${variableName}`;
460486
from: `_${fromVar}`,
461487
to: `_${toVar}`
462488
},
463-
variableName: schemaType.name === fromType ? `_${toVar}` : `_${fromVar}`,
489+
variableName: schemaType.name === fromType ? `_${toVar}` : `_${fromVar}`
464490
});
465491
params = { ...params, ...subParams };
466492

@@ -470,13 +496,20 @@ RETURN ${variableName}`;
470496
// object construction into a WITH statement above the DELETE, then return it
471497
// the delete
472498
query = `
473-
MATCH (${fromVar}:${fromType} {${fromParam}: $from.${fromParam}})
474-
MATCH (${toVar}:${toType} {${toParam}: $to.${toParam}})
475-
OPTIONAL MATCH (${fromVar})-[${fromVar +
476-
toVar}:${relationshipName}]->(${toVar})
477-
DELETE ${fromVar + toVar}
478-
WITH COUNT(*) AS scope, ${fromVar} AS _${fromVar}, ${toVar} AS _${toVar}
479-
RETURN {${subQuery}} AS ${schemaType};
499+
MATCH (${safeVar(fromVar)}:${safeLabel(
500+
fromType
501+
)} {${fromParam}: $from.${fromParam}})
502+
MATCH (${safeVar(toVar)}:${safeLabel(
503+
toType
504+
)} {${toParam}: $to.${toParam}})
505+
OPTIONAL MATCH (${safeVar(fromVar)})-[${safeVar(
506+
fromVar + toVar
507+
)}:${safeLabel(relationshipName)}]->(${safeVar(toVar)})
508+
DELETE ${safeVar(fromVar + toVar)}
509+
WITH COUNT(*) AS scope, ${safeVar(fromVar)} AS ${safeVar(
510+
'_' + fromVar
511+
)}, ${safeVar(toVar)} AS ${safeVar('_' + toVar)}
512+
RETURN {${subQuery}} AS ${safeVar(schemaType)};
480513
`;
481514
} else {
482515
// throw error - don't know how to handle this type of mutation
@@ -527,9 +560,9 @@ export const makeAugmentedSchema = ({
527560
});
528561
};
529562

530-
export const augmentTypeDefs = (typeDefs) => {
563+
export const augmentTypeDefs = typeDefs => {
531564
const typeMap = extractTypeMapFromTypeDefs(typeDefs);
532565
// overwrites any provided declarations of system directives
533566
const augmented = addDirectiveDeclarations(typeMap);
534567
return printTypeMap(augmented);
535-
}
568+
};

0 commit comments

Comments
 (0)