Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,26 @@ export class AuthorizationFilters extends QueryASTNode {
return;
}

public getValidationPredicate(
context: QueryASTContext,
when: ValidateWhen = "BEFORE"
): Cypher.Predicate | undefined {
const validationPredicate = Cypher.or(
...this.getValidations(when).flatMap((validationRule) => validationRule.getPredicate(context))
);
return validationPredicate;
}

public getSubqueries(context: QueryASTContext): Cypher.Clause[] {
return [...this.validations, ...this.filters].flatMap((c) => c.getSubqueries(context));
}

public getSubqueriesBefore(context: QueryASTContext): Cypher.Clause[] {
return [...this.validations.filter((v) => v.when === "BEFORE"), ...this.filters].flatMap((c) =>
c.getSubqueries(context)
);
}

public getSelection(context: QueryASTContext): Array<Cypher.Match | Cypher.With> {
return [...this.validations, ...this.filters].flatMap((c) => c.getSelection(context));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,17 @@ import type { AuthorizationFilters } from "../filters/authorization-filters/Auth
import type { InputField } from "../input-fields/InputField";
import type { SelectionPattern } from "../selection/SelectionPattern/SelectionPattern";
import { MutationOperation, type OperationTranspileResult } from "./operations";
import { checkEntityAuthentication } from "../../../authorization/check-authentication";
import { ParamInputField } from "../input-fields/ParamInputField";
import { isConcreteEntity } from "../../utils/is-concrete-entity";

export class ConnectOperation extends MutationOperation {
public readonly target: ConcreteEntityAdapter;
public readonly relationship: RelationshipAdapter;

private selectionPattern: SelectionPattern;
protected readonly authFilters: AuthorizationFilters[] = [];
protected readonly sourceAuthFilters: AuthorizationFilters[] = [];

public readonly inputFields: Map<string, InputField> = new Map();
private filters: Filter[] = [];
Expand Down Expand Up @@ -74,6 +78,9 @@ export class ConnectOperation extends MutationOperation {
public addAuthFilters(...filter: AuthorizationFilters[]) {
this.authFilters.push(...filter);
}
public addSourceAuthFilters(...filter: AuthorizationFilters[]) {
this.sourceAuthFilters.push(...filter);
}

/**
* Get and set field methods are utilities to remove duplicate fields between separate inputs
Expand Down Expand Up @@ -115,6 +122,29 @@ export class ConnectOperation extends MutationOperation {
const { nestedContext } = this.selectionPattern.apply(context);
this.nestedContext = nestedContext;

checkEntityAuthentication({
context: nestedContext.neo4jGraphQLContext,
entity: this.target.entity,
targetOperations: ["CREATE_RELATIONSHIP"],
});
if (isConcreteEntity(this.relationship.source)) {
checkEntityAuthentication({
context: nestedContext.neo4jGraphQLContext,
entity: this.relationship.source.entity,
targetOperations: ["CREATE_RELATIONSHIP"],
});
}
this.inputFields.forEach((field) => {
if (field.attachedTo === "node" && field instanceof ParamInputField) {
checkEntityAuthentication({
context: nestedContext.neo4jGraphQLContext,
entity: this.target.entity,
targetOperations: ["CREATE_RELATIONSHIP"],
field: field.name,
});
}
});

const matchPattern = new Cypher.Pattern(nestedContext.target, {
labels: getEntityLabels(this.target, context.neo4jGraphQLContext),
});
Expand Down Expand Up @@ -157,17 +187,36 @@ export class ConnectOperation extends MutationOperation {
return input.getSubqueries(connectContext);
});

const authClausesBefore = this.getAuthorizationClauses(nestedContext);
const sourceAuthClausesBefore = this.getSourceAuthorizationClausesBefore(context);
const bothAuthClausesBefore: Cypher.Clause[] = [];
if (authClausesBefore.length === 0 && sourceAuthClausesBefore.length > 0) {
bothAuthClausesBefore.push(new Cypher.With("*"), ...sourceAuthClausesBefore);
} else {
bothAuthClausesBefore.push(Cypher.utils.concat(...authClausesBefore, ...sourceAuthClausesBefore));
}

const clauses = Cypher.utils.concat(
matchClause,
...this.getAuthorizationClauses(nestedContext), // THESE ARE "BEFORE" AUTH
...bothAuthClausesBefore, // THESE ARE "BEFORE" AUTH
...mutationSubqueries,
connectClause,
...this.getAuthorizationClausesAfter(nestedContext) // THESE ARE "AFTER" AUTH
connectClause
// ...this.getAuthorizationClausesAfter(context) // THESE ARE "AFTER" AUTH
);

const authClausesAfter = this.getAuthorizationClausesAfter(nestedContext);
const sourceAuthClausesAfter = this.getSourceAuthorizationClausesAfter(context);

const callClause = new Cypher.Call(clauses, [context.target]);
const authClauses: Cypher.Clause[] = [];
if (authClausesAfter.length > 0 || sourceAuthClausesAfter.length > 0) {
authClauses.push(Cypher.utils.concat(...authClausesAfter, ...sourceAuthClausesAfter));
}

return { projectionExpr: context.returnVariable, clauses: [callClause] };
return {
projectionExpr: context.returnVariable,
clauses: [callClause, ...authClauses],
};
}

private getAuthorizationClauses(context: QueryASTContext): Cypher.Clause[] {
Expand Down Expand Up @@ -201,6 +250,36 @@ export class ConnectOperation extends MutationOperation {
return [];
}

private getSourceAuthorizationClausesAfter(context: QueryASTContext): Cypher.Clause[] {
const validationsAfter: Cypher.VoidProcedure[] = [];
for (const authFilter of this.sourceAuthFilters) {
const validationAfter = authFilter.getValidation(context, "AFTER");
if (validationAfter) {
validationsAfter.push(validationAfter);
}
}

if (validationsAfter.length > 0) {
return [new Cypher.With("*"), ...validationsAfter];
}
return [];
}

private getSourceAuthorizationClausesBefore(context: QueryASTContext): Cypher.Clause[] {
const validationsAfter: Cypher.VoidProcedure[] = [];
for (const authFilter of this.sourceAuthFilters) {
const validationAfter = authFilter.getValidation(context, "BEFORE");
if (validationAfter) {
validationsAfter.push(validationAfter);
}
}

if (validationsAfter.length > 0) {
return [new Cypher.With("*"), ...validationsAfter];
}
return [];
}

private transpileAuthClauses(context: QueryASTContext): {
selections: (Cypher.With | Cypher.Match)[];
subqueries: Cypher.Clause[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* limitations under the License.
*/

import Cypher from "@neo4j/cypher-builder";
import Cypher, { With } from "@neo4j/cypher-builder";
import type { ConcreteEntityAdapter } from "../../../../schema-model/entity/model-adapters/ConcreteEntityAdapter";
import type { RelationshipAdapter } from "../../../../schema-model/relationship/model-adapters/RelationshipAdapter";
import { filterTruthy } from "../../../../utils/utils";
Expand All @@ -31,13 +31,17 @@ import type { InputField } from "../input-fields/InputField";
import type { SelectionPattern } from "../selection/SelectionPattern/SelectionPattern";
import type { ReadOperation } from "./ReadOperation";
import { MutationOperation, type OperationTranspileResult } from "./operations";
import { checkEntityAuthentication } from "../../../authorization/check-authentication";
import { ParamInputField } from "../input-fields/ParamInputField";
import { isConcreteEntity } from "../../utils/is-concrete-entity";

export class DisconnectOperation extends MutationOperation {
public readonly target: ConcreteEntityAdapter;
public readonly relationship: RelationshipAdapter;

private selectionPattern: SelectionPattern;
protected readonly authFilters: AuthorizationFilters[] = [];
protected readonly sourceAuthFilters: AuthorizationFilters[] = [];

public readonly inputFields: Map<string, InputField> = new Map();
private filters: Filter[] = [];
Expand Down Expand Up @@ -75,6 +79,9 @@ export class DisconnectOperation extends MutationOperation {
public addAuthFilters(...filter: AuthorizationFilters[]) {
this.authFilters.push(...filter);
}
public addSourceAuthFilters(...filter: AuthorizationFilters[]) {
this.sourceAuthFilters.push(...filter);
}

/**
* Get and set field methods are utilities to remove duplicate fields between separate inputs
Expand Down Expand Up @@ -116,6 +123,29 @@ export class DisconnectOperation extends MutationOperation {
const { nestedContext, pattern: matchPattern } = this.selectionPattern.apply(context);
this.nestedContext = nestedContext;

checkEntityAuthentication({
context: nestedContext.neo4jGraphQLContext,
entity: this.target.entity,
targetOperations: ["DELETE_RELATIONSHIP"],
});
if (isConcreteEntity(this.relationship.source)) {
checkEntityAuthentication({
context: nestedContext.neo4jGraphQLContext,
entity: this.relationship.source.entity,
targetOperations: ["DELETE_RELATIONSHIP"],
});
}
this.inputFields.forEach((field) => {
if (field.attachedTo === "node" && field instanceof ParamInputField) {
checkEntityAuthentication({
context: nestedContext.neo4jGraphQLContext,
entity: this.target.entity,
targetOperations: ["DELETE_RELATIONSHIP"],
field: field.name,
});
}
});

const allFilters = [...this.authFilters, ...this.filters];

const filterSubqueries = wrapSubqueriesInCypherCalls(nestedContext, allFilters, [nestedContext.target]);
Expand All @@ -124,13 +154,13 @@ export class DisconnectOperation extends MutationOperation {
if (filterSubqueries.length > 0) {
const predicate = Cypher.and(...allFilters.map((f) => f.getPredicate(nestedContext)));
matchClause = Cypher.utils.concat(
new Cypher.Match(matchPattern),
new Cypher.OptionalMatch(matchPattern),
...filterSubqueries,
new Cypher.With("*").where(predicate)
);
} else {
const predicate = Cypher.and(...allFilters.map((f) => f.getPredicate(nestedContext)));
matchClause = new Cypher.Match(matchPattern).where(predicate);
matchClause = new Cypher.OptionalMatch(matchPattern).where(predicate);
}

const relVar = new Cypher.Relationship();
Expand All @@ -145,15 +175,40 @@ export class DisconnectOperation extends MutationOperation {

const deleteClause = new Cypher.With(nestedContext.relationship!).delete(nestedContext.relationship!);

const authClausesBefore = this.getAuthorizationClauses(nestedContext);
const sourceAuthClausesBefore = this.getSourceAuthorizationClausesBefore(context);

const bothAuthClausesBefore: Cypher.Clause[] = [];
if (authClausesBefore.length === 0 && sourceAuthClausesBefore.length > 0) {
bothAuthClausesBefore.push(new Cypher.With("*"), ...sourceAuthClausesBefore);
} else {
bothAuthClausesBefore.push(Cypher.utils.concat(...authClausesBefore, ...sourceAuthClausesBefore));
}

const clauses = Cypher.utils.concat(
matchClause,
...this.getAuthorizationClauses(nestedContext), // THESE ARE "BEFORE" AUTH
...bothAuthClausesBefore,
...mutationSubqueries,
deleteClause,
...this.getAuthorizationClausesAfter(nestedContext) // THESE ARE "AFTER" AUTH
deleteClause
// ...this.getAuthorizationClausesAfter(nestedContext) // THESE ARE "AFTER" AUTH
);

return { projectionExpr: context.returnVariable, clauses: [clauses] };
const authClausesAfter = this.getAuthorizationClausesAfter(nestedContext);
const sourceAuthClausesAfter = this.getSourceAuthorizationClausesAfter(context);

const callClause = new Cypher.Call(clauses, [context.target]);
const authClauses: Cypher.Clause[] = [];
if (authClausesAfter.length > 0 || sourceAuthClausesAfter.length > 0) {
authClauses.push(Cypher.utils.concat(...authClausesAfter, ...sourceAuthClausesAfter));
}
console.log("authClauses", authClauses);

return {
projectionExpr: context.returnVariable,
clauses: [callClause, ...authClauses],
};

// return { projectionExpr: context.returnVariable, clauses: [clauses] };
}

private getAuthorizationClauses(context: QueryASTContext): Cypher.Clause[] {
Expand Down Expand Up @@ -187,6 +242,35 @@ export class DisconnectOperation extends MutationOperation {
return [];
}

private getSourceAuthorizationClausesAfter(context: QueryASTContext): Cypher.Clause[] {
const validationsAfter: Cypher.VoidProcedure[] = [];
for (const authFilter of this.sourceAuthFilters) {
const validationAfter = authFilter.getValidation(context, "AFTER");
if (validationAfter) {
validationsAfter.push(validationAfter);
}
}

if (validationsAfter.length > 0) {
return [new Cypher.With("*"), ...validationsAfter];
}
return [];
}
private getSourceAuthorizationClausesBefore(context: QueryASTContext): Cypher.Clause[] {
const validationsAfter: Cypher.VoidProcedure[] = [];
for (const authFilter of this.sourceAuthFilters) {
const validationAfter = authFilter.getValidation(context, "BEFORE");
if (validationAfter) {
validationsAfter.push(validationAfter);
}
}

if (validationsAfter.length > 0) {
return [new Cypher.With("*"), ...validationsAfter];
}
return [];
}

private transpileAuthClauses(context: QueryASTContext): {
selections: (Cypher.With | Cypher.Match)[];
subqueries: Cypher.Clause[];
Expand Down
Loading