-
Notifications
You must be signed in to change notification settings - Fork 49
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Escape entity details queries (#793)
* Prevent injection attacks from graph file * Fix notification message when exactly 1 not found * Update changelog * Fix isNotEmptyIfString * Fix isValidRdfEdgeIdIfSparql
- Loading branch information
Showing
20 changed files
with
849 additions
and
45 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
67 changes: 67 additions & 0 deletions
67
packages/graph-explorer/src/connector/gremlin/edgeDetails.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import { createRandomEdge, createRandomVertex } from "@/utils/testing"; | ||
import { edgeDetails } from "./edgeDetails"; | ||
import { Edge } from "@/core"; | ||
|
||
describe("edgeDetails", () => { | ||
it("should return the correct edge details", async () => { | ||
const edge = createRandomEdge(createRandomVertex(), createRandomVertex()); | ||
edge.__isFragment = false; | ||
const response = createGremlinResponseFromEdge(edge); | ||
const mockFetch = vi | ||
.fn() | ||
.mockImplementation(() => Promise.resolve(response)); | ||
|
||
const result = await edgeDetails(mockFetch, { | ||
edgeId: edge.id, | ||
}); | ||
|
||
expect(result.edge).toEqual(edge); | ||
}); | ||
}); | ||
|
||
function createGremlinResponseFromEdge(edge: Edge) { | ||
return { | ||
result: { | ||
data: { | ||
"@type": "g:List", | ||
"@value": [ | ||
{ | ||
"@type": "g:Edge", | ||
"@value": { | ||
id: edge.id, | ||
label: edge.type, | ||
inV: edge.target, | ||
outV: edge.source, | ||
inVLabel: edge.targetType, | ||
outVLabel: edge.sourceType, | ||
properties: createProperties(edge.attributes), | ||
}, | ||
}, | ||
], | ||
}, | ||
}, | ||
}; | ||
} | ||
|
||
function createProperties(attributes: Edge["attributes"]) { | ||
const mapped = Object.entries(attributes).map(([key, value]) => ({ | ||
"@type": "g:EdgeProperty", | ||
"@value": { | ||
key, | ||
value: | ||
typeof value === "string" | ||
? value | ||
: { | ||
"@type": "g:Int64", | ||
"@value": value, | ||
}, | ||
}, | ||
})); | ||
|
||
const result = {} as Record<string, any>; | ||
mapped.forEach(prop => { | ||
result[prop["@value"].key] = prop; | ||
}); | ||
|
||
return result; | ||
} |
67 changes: 67 additions & 0 deletions
67
packages/graph-explorer/src/connector/gremlin/vertexDetails.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import { createRandomVertex } from "@/utils/testing"; | ||
import { vertexDetails } from "./vertexDetails"; | ||
import { Vertex } from "@/core"; | ||
|
||
describe("vertexDetails", () => { | ||
it("should return the correct vertex details", async () => { | ||
const vertex = createRandomVertex(); | ||
|
||
// Align with the gremlin mapper | ||
vertex.types = [vertex.type]; | ||
vertex.__isFragment = false; | ||
|
||
const response = createGremlinResponseFromVertex(vertex); | ||
const mockFetch = vi | ||
.fn() | ||
.mockImplementation(() => Promise.resolve(response)); | ||
|
||
const result = await vertexDetails(mockFetch, { | ||
vertexId: vertex.id, | ||
}); | ||
|
||
expect(result.vertex).toEqual(vertex); | ||
}); | ||
}); | ||
|
||
function createGremlinResponseFromVertex(vertex: Vertex) { | ||
return { | ||
result: { | ||
data: { | ||
"@type": "g:List", | ||
"@value": [ | ||
{ | ||
"@type": "g:Vertex", | ||
"@value": { | ||
id: vertex.id, | ||
label: vertex.type, | ||
properties: createProperties(vertex.attributes), | ||
}, | ||
}, | ||
], | ||
}, | ||
}, | ||
}; | ||
} | ||
|
||
function createProperties(attributes: Vertex["attributes"]) { | ||
const mapped = Object.entries(attributes).map(([key, value]) => ({ | ||
"@type": "g:VertexProperty", | ||
"@value": { | ||
label: key, | ||
value: | ||
typeof value === "string" | ||
? value | ||
: { | ||
"@type": "g:Int64", | ||
"@value": value, | ||
}, | ||
}, | ||
})); | ||
|
||
const result = {} as Record<string, any>; | ||
mapped.forEach(prop => { | ||
result[prop["@value"].label] = [prop]; | ||
}); | ||
|
||
return result; | ||
} |
36 changes: 36 additions & 0 deletions
36
packages/graph-explorer/src/connector/openCypher/edgeDetails.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { createRandomEdge, createRandomVertex } from "@/utils/testing"; | ||
import { edgeDetails } from "./edgeDetails"; | ||
import { Edge } from "@/core"; | ||
|
||
describe("edgeDetails", () => { | ||
it("should return the edge details", async () => { | ||
const edge = createRandomEdge(createRandomVertex(), createRandomVertex()); | ||
|
||
const response = createResponseFromEdge(edge); | ||
const mockFetch = vi | ||
.fn() | ||
.mockImplementation(() => Promise.resolve(response)); | ||
|
||
const result = await edgeDetails(mockFetch, { edgeId: edge.id }); | ||
|
||
expect(result.edge).toEqual(edge); | ||
}); | ||
}); | ||
|
||
function createResponseFromEdge(edge: Edge) { | ||
return { | ||
results: [ | ||
{ | ||
edge: { | ||
"~id": edge.id, | ||
"~type": edge.type, | ||
"~start": edge.source, | ||
"~end": edge.target, | ||
"~properties": edge.attributes, | ||
}, | ||
sourceLabels: [edge.sourceType], | ||
targetLabels: [edge.targetType], | ||
}, | ||
], | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
33 changes: 33 additions & 0 deletions
33
packages/graph-explorer/src/connector/openCypher/vertexDetails.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { createRandomVertex } from "@/utils/testing"; | ||
import { vertexDetails } from "./vertexDetails"; | ||
import { Vertex } from "@/core"; | ||
|
||
describe("vertexDetails", () => { | ||
it("should return the vertex details", async () => { | ||
const vertex = createRandomVertex(); | ||
vertex.types = [vertex.type]; | ||
|
||
const response = createResponseFromVertex(vertex); | ||
const mockFetch = vi | ||
.fn() | ||
.mockImplementation(() => Promise.resolve(response)); | ||
|
||
const result = await vertexDetails(mockFetch, { vertexId: vertex.id }); | ||
|
||
expect(result.vertex).toEqual(vertex); | ||
}); | ||
}); | ||
|
||
function createResponseFromVertex(vertex: Vertex) { | ||
return { | ||
results: [ | ||
{ | ||
vertex: { | ||
"~id": vertex.id, | ||
"~labels": [vertex.type], | ||
"~properties": vertex.attributes, | ||
}, | ||
}, | ||
], | ||
}; | ||
} |
20 changes: 20 additions & 0 deletions
20
packages/graph-explorer/src/connector/sparql/createRdfEdgeId.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { createEdgeId, VertexId } from "@/core"; | ||
|
||
/** | ||
* Combines the triple that makes up an edge into a single string. | ||
* | ||
* The format is: | ||
* {source}-[{predicate}]->{target} | ||
* | ||
* @param source The source resource URI | ||
* @param predicate The predicate URI | ||
* @param target The target resource URI | ||
* @returns A string that represents the relationship between the source and target | ||
*/ | ||
export function createRdfEdgeId( | ||
source: string | VertexId, | ||
predicate: string, | ||
target: string | VertexId | ||
) { | ||
return createEdgeId(`${source}-[${predicate}]->${target}`); | ||
} |
108 changes: 108 additions & 0 deletions
108
packages/graph-explorer/src/connector/sparql/edgeDetails.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
import { Edge, EdgeId } from "@/core"; | ||
import { | ||
createRandomEdgeForRdf, | ||
createRandomVertexForRdf, | ||
} from "@/utils/testing"; | ||
import { edgeDetails } from "./edgeDetails"; | ||
import { createRandomUrlString } from "@shared/utils/testing"; | ||
|
||
describe("edgeDetails", () => { | ||
it("should return the edge details", async () => { | ||
const edge = createRandomEdgeForRdf( | ||
createRandomVertexForRdf(), | ||
createRandomVertexForRdf() | ||
); | ||
const response = createResponseFromEdge(edge); | ||
const mockFetch = vi | ||
.fn() | ||
.mockImplementation(() => Promise.resolve(response)); | ||
const result = await edgeDetails(mockFetch, { edgeId: edge.id }); | ||
expect(result.edge).toEqual(edge); | ||
}); | ||
|
||
it("should throw an error when the edge ID is not in the RDF edge ID format", async () => { | ||
const edge = createRandomEdgeForRdf( | ||
createRandomVertexForRdf(), | ||
createRandomVertexForRdf() | ||
); | ||
// Missing the brackets | ||
edge.id = `${edge.source}-${edge.type}->${edge.target}` as EdgeId; | ||
const response = createResponseFromEdge(edge); | ||
const mockFetch = vi | ||
.fn() | ||
.mockImplementation(() => Promise.resolve(response)); | ||
|
||
await expect(edgeDetails(mockFetch, { edgeId: edge.id })).rejects.toThrow( | ||
"Invalid RDF edge ID" | ||
); | ||
}); | ||
|
||
it("should throw an error when the source vertex ID doesn't match the response", async () => { | ||
const edge = createRandomEdgeForRdf( | ||
createRandomVertexForRdf(), | ||
createRandomVertexForRdf() | ||
); | ||
edge.id = | ||
`${createRandomUrlString()}-[${edge.type}]->${edge.target}` as EdgeId; | ||
const response = createResponseFromEdge(edge); | ||
const mockFetch = vi | ||
.fn() | ||
.mockImplementation(() => Promise.resolve(response)); | ||
|
||
await expect(edgeDetails(mockFetch, { edgeId: edge.id })).rejects.toThrow( | ||
"Edge type not found in bindings" | ||
); | ||
}); | ||
|
||
it("should throw an error when the target vertex ID doesn't match the response", async () => { | ||
const edge = createRandomEdgeForRdf( | ||
createRandomVertexForRdf(), | ||
createRandomVertexForRdf() | ||
); | ||
edge.id = | ||
`${edge.source}-[${edge.type}]->${createRandomUrlString()}` as EdgeId; | ||
const response = createResponseFromEdge(edge); | ||
const mockFetch = vi | ||
.fn() | ||
.mockImplementation(() => Promise.resolve(response)); | ||
|
||
await expect(edgeDetails(mockFetch, { edgeId: edge.id })).rejects.toThrow( | ||
"Edge type not found in bindings" | ||
); | ||
}); | ||
}); | ||
|
||
function createResponseFromEdge(edge: Edge) { | ||
const source = edge.source; | ||
const target = edge.target; | ||
|
||
return { | ||
head: { | ||
vars: ["resource", "type"], | ||
}, | ||
results: { | ||
bindings: [ | ||
{ | ||
resource: { | ||
type: "uri", | ||
value: source, | ||
}, | ||
type: { | ||
type: "uri", | ||
value: edge.sourceType, | ||
}, | ||
}, | ||
{ | ||
resource: { | ||
type: "uri", | ||
value: target, | ||
}, | ||
type: { | ||
type: "uri", | ||
value: edge.targetType, | ||
}, | ||
}, | ||
], | ||
}, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.