Skip to content

Commit f79208a

Browse files
committed
Provide real privilege context with available transition checks
This allows transitions to depend on membership, sensitivity, etc.
1 parent e0974fa commit f79208a

File tree

4 files changed

+22
-6
lines changed

4 files changed

+22
-6
lines changed

src/components/project/workflow/migrations/step-history-to-workflow-events.migration.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ export class StepHistoryToWorkflowEventsMigration extends BaseMigration {
7575
moduleRef: this.moduleRef,
7676
migrationPrevStep: prev.value,
7777
},
78+
project,
7879
// We don't know who did it, so we can't confirm this was an official
7980
// transition instead of a bypass.
8081
// Guess that it was if a transition exists.

src/components/project/workflow/project-workflow.service.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export class ProjectWorkflowService extends WorkflowService(ProjectWorkflow) {
5050
return await this.resolveAvailable(
5151
project.step.value!,
5252
{ project, moduleRef: this.moduleRef },
53+
project,
5354
session,
5455
);
5556
}

src/components/workflow/workflow.granter.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Query } from 'cypher-query-builder';
22
import { inspect, InspectOptionsStylized } from 'util';
3-
import { ID, isIdLike, Many } from '~/common';
3+
import { ID, Many } from '~/common';
44
import { ResourceGranter } from '../authorization';
55
import { action } from '../authorization/policy/builder/perm-granter';
66
import { PropsGranterFn } from '../authorization/policy/builder/resource-granter';
@@ -108,13 +108,11 @@ export function WorkflowEventGranter<W extends Workflow>(workflow: W) {
108108
// These should be treated as false without error.
109109
return false;
110110
}
111-
const transitionKey = object.transition;
111+
const transitionKey = Reflect.get(object, TransitionKey);
112112
if (!transitionKey) {
113113
return false;
114114
}
115-
return this.allowedTransitionKeys.has(
116-
isIdLike(transitionKey) ? transitionKey : transitionKey.key,
117-
);
115+
return this.allowedTransitionKeys.has(transitionKey);
118116
}
119117

120118
asCypherCondition(query: Query) {
@@ -191,3 +189,15 @@ export function WorkflowEventGranter<W extends Workflow>(workflow: W) {
191189

192190
return WorkflowEventGranterClass;
193191
}
192+
193+
const TransitionKey = Symbol('TransitionKey');
194+
195+
export const withTransitionKey = <T extends object>(
196+
obj: T,
197+
transitionKey: ID,
198+
) =>
199+
Object.defineProperty(obj, TransitionKey, {
200+
value: transitionKey,
201+
enumerable: false,
202+
configurable: true,
203+
});

src/components/workflow/workflow.service.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { ID, Session, UnauthorizedException } from '~/common';
44
import { Privileges } from '../authorization';
55
import { Workflow } from './define-workflow';
66
import { ExecuteTransitionInput as ExecuteTransitionInputFn } from './dto';
7+
import { withTransitionKey } from './workflow.granter';
78

89
type ExecuteTransitionInput = ReturnType<
910
typeof ExecuteTransitionInputFn
@@ -29,6 +30,7 @@ export const WorkflowService = <W extends Workflow>(workflow: W) => {
2930
protected async resolveAvailable(
3031
currentState: W['state'],
3132
dynamicContext: W['context'],
33+
privilegeContext: object,
3234
session: Session,
3335
) {
3436
let available = this.workflow.transitions;
@@ -43,7 +45,9 @@ export const WorkflowService = <W extends Workflow>(workflow: W) => {
4345
available = available.filter((t) =>
4446
// I don't have a good way to type this right now.
4547
// Context usage is still fuzzy when conditions need different shapes.
46-
p.forContext({ transition: t.key } as any).can('create'),
48+
p
49+
.forContext(withTransitionKey(privilegeContext, t.key) as any)
50+
.can('create'),
4751
);
4852

4953
// Resolve conditions & filter as needed

0 commit comments

Comments
 (0)