Skip to content

Commit bff460a

Browse files
committed
fix: remove broken GRANT/REVOKE EXECUTE ON WORKFLOW
Workflows$Workflow has no AllowedModuleRoles field in the Mendix metamodel (confirmed by generated metamodel and BSON dump of Studio Pro output). The GRANT/REVOKE EXECUTE ON WORKFLOW commands silently wrote a phantom field that Studio Pro ignored. Replace with clear error messages explaining that workflow access is controlled through triggering microflows and UserTask targeting.
1 parent c97c85b commit bff460a

File tree

11 files changed

+12
-197
lines changed

11 files changed

+12
-197
lines changed

docs-site/src/appendixes/quick-reference.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,8 +232,6 @@ Nested folders use `/` separator: `'Parent/Child/Grandchild'`. Missing folders a
232232
| Describe workflow | `DESCRIBE WORKFLOW Module.Name;` | Full MDL output |
233233
| Create workflow | `CREATE [OR MODIFY] WORKFLOW Module.Name PARAMETER $Ctx: Module.Entity BEGIN ... END WORKFLOW;` | See activity types below |
234234
| Drop workflow | `DROP WORKFLOW Module.Name;` | |
235-
| Grant workflow access | `GRANT EXECUTE ON WORKFLOW Module.Name TO Mod.Role, ...;` | |
236-
| Revoke workflow access | `REVOKE EXECUTE ON WORKFLOW Module.Name FROM Mod.Role, ...;` | |
237235

238236
**Workflow Activity Types:**
239237
- `USER TASK <name> '<caption>' [PAGE Mod.Page] [TARGETING MICROFLOW Mod.MF] [OUTCOMES '<out>' { } ...];`

docs-site/src/language/grant-revoke.md

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,10 +134,7 @@ REVOKE EXECUTE ON NANOFLOW <Module>.<Name> FROM <Module>.<Role> [, ...];
134134

135135
## Workflow Access
136136

137-
```sql
138-
GRANT EXECUTE ON WORKFLOW <Module>.<Name> TO <Module>.<Role> [, ...];
139-
REVOKE EXECUTE ON WORKFLOW <Module>.<Name> FROM <Module>.<Role> [, ...];
140-
```
137+
> **Not supported.** Mendix workflows do not have document-level `AllowedModuleRoles` (unlike microflows and pages). Workflow access is controlled through the microflow that triggers the workflow and UserTask targeting.
141138
142139
## OData Service Access
143140

docs-site/src/language/workflow-structure.md

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,7 @@ DROP WORKFLOW HR.OnboardEmployee;
6262

6363
## Workflow Access
6464

65-
Grant or revoke execute access to control who can start a workflow:
66-
67-
```sql
68-
GRANT EXECUTE ON WORKFLOW HR.OnboardEmployee TO HR.Manager, HR.Admin;
69-
REVOKE EXECUTE ON WORKFLOW HR.OnboardEmployee FROM HR.Manager;
70-
```
65+
> **Not supported.** Mendix workflows do not have document-level `AllowedModuleRoles` (unlike microflows and pages). Workflow access is controlled through the microflow that triggers the workflow and UserTask targeting.
7166
7267
## See Also
7368

docs-site/src/reference/workflow/README.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,3 @@ Mendix workflows model long-running business processes with user tasks, decision
1717
|-----------|--------|
1818
| Show workflows | `SHOW WORKFLOWS [IN module]` |
1919
| Describe workflow | `DESCRIBE WORKFLOW module.Name` |
20-
| Grant workflow access | `GRANT EXECUTE ON WORKFLOW module.Name TO module.Role, ...` |
21-
| Revoke workflow access | `REVOKE EXECUTE ON WORKFLOW module.Name FROM module.Role, ...` |

docs/01-project/MDL_FEATURE_MATRIX.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ Document types that exist in Mendix but have no MDL support:
204204
| **JSON transformations** | Y | Y | Y | Y | Y | N | 20 | Y | N | N | P | N | N | N | N | N | N | JSON structure definitions |
205205
| **Message definitions** | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | Message definition documents |
206206
| **XML schemas** | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | Imported XML schema documents |
207-
| **Workflows** | Y | Y | Y | N | Y | N | N | N | Y | Y | N | N | Y | N | Y | Y | N | SHOW/DESCRIBE/CREATE/DROP/GRANT/REVOKE implemented |
207+
| **Workflows** | Y | Y | Y | N | Y | N | N | N | Y | Y | N | N | Y | N | N | N | N | SHOW/DESCRIBE/CREATE/DROP implemented; GRANT/REVOKE removed (workflows lack AllowedModuleRoles) |
208208
| **Module settings** | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | Module-level configuration |
209209
| **Image collection** | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | Image document collections |
210210
| **Icon collection** | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | Icon/glyph collections |

docs/01-project/MDL_QUICK_REFERENCE.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -269,8 +269,6 @@ Nested folders use `/` separator: `'Parent/Child/Grandchild'`. Missing folders a
269269
| Describe workflow | `DESCRIBE WORKFLOW Module.Name;` | Full MDL output |
270270
| Create workflow | `CREATE [OR MODIFY] WORKFLOW Module.Name PARAMETER $Ctx: Module.Entity BEGIN ... END WORKFLOW;` | See activity types below |
271271
| Drop workflow | `DROP WORKFLOW Module.Name;` | |
272-
| Grant workflow access | `GRANT EXECUTE ON WORKFLOW Module.Name TO Mod.Role, ...;` | |
273-
| Revoke workflow access | `REVOKE EXECUTE ON WORKFLOW Module.Name FROM Mod.Role, ...;` | |
274272

275273
**Workflow Activity Types:**
276274
- `USER TASK <name> '<caption>' [PAGE Mod.Page] [TARGETING MICROFLOW Mod.MF] [OUTCOMES '<out>' { } ...];`

mdl/executor/cmd_misc.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,8 +206,6 @@ Security - Access Control:
206206
REVOKE EXECUTE ON MICROFLOW Module.Name FROM Role [, Role...];
207207
GRANT VIEW ON PAGE Module.Name TO Role [, Role...];
208208
REVOKE VIEW ON PAGE Module.Name FROM Role [, Role...];
209-
GRANT EXECUTE ON WORKFLOW Module.Name TO Role [, Role...];
210-
REVOKE EXECUTE ON WORKFLOW Module.Name FROM Role [, Role...];
211209
GRANT Role ON Module.Entity (CREATE, DELETE, READ *, WRITE *) [WHERE 'xpath'];
212210
REVOKE Role ON Module.Entity;
213211

mdl/executor/cmd_security.go

Lines changed: 3 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -313,36 +313,7 @@ func (e *Executor) showAccessOnPage(name *ast.QualifiedName) error {
313313

314314
// showAccessOnWorkflow handles SHOW ACCESS ON WORKFLOW Module.WF.
315315
func (e *Executor) showAccessOnWorkflow(name *ast.QualifiedName) error {
316-
if name == nil {
317-
return fmt.Errorf("workflow name required")
318-
}
319-
320-
h, err := e.getHierarchy()
321-
if err != nil {
322-
return fmt.Errorf("failed to build hierarchy: %w", err)
323-
}
324-
325-
wfs, err := e.reader.ListWorkflows()
326-
if err != nil {
327-
return fmt.Errorf("failed to list workflows: %w", err)
328-
}
329-
330-
for _, wf := range wfs {
331-
modName := h.GetModuleName(h.FindModuleID(wf.ContainerID))
332-
if modName == name.Module && wf.Name == name.Name {
333-
if len(wf.AllowedModuleRoles) == 0 {
334-
fmt.Fprintf(e.output, "No module roles granted execute access on %s.%s\n", modName, wf.Name)
335-
return nil
336-
}
337-
fmt.Fprintf(e.output, "Allowed module roles for %s.%s:\n", modName, wf.Name)
338-
for _, role := range wf.AllowedModuleRoles {
339-
fmt.Fprintf(e.output, " %s\n", string(role))
340-
}
341-
return nil
342-
}
343-
}
344-
345-
return fmt.Errorf("workflow not found: %s", name)
316+
return fmt.Errorf("SHOW ACCESS ON WORKFLOW is not supported: Mendix workflows do not have document-level AllowedModuleRoles (unlike microflows and pages). Workflow access is controlled through the microflow that triggers the workflow and UserTask targeting")
346317
}
347318

348319
// showSecurityMatrix handles SHOW SECURITY MATRIX [IN module].
@@ -532,35 +503,10 @@ func (e *Executor) showSecurityMatrix(moduleName string) error {
532503
}
533504
fmt.Fprintln(e.output)
534505

535-
// Workflow section
506+
// Workflow section — workflows don't have document-level AllowedModuleRoles
536507
fmt.Fprintln(e.output, "## Workflow Access")
537508
fmt.Fprintln(e.output)
538-
539-
wfs, err := e.reader.ListWorkflows()
540-
if err != nil {
541-
return fmt.Errorf("failed to list workflows: %w", err)
542-
}
543-
544-
wfFound := false
545-
for _, wf := range wfs {
546-
if len(wf.AllowedModuleRoles) == 0 {
547-
continue
548-
}
549-
modID := h.FindModuleID(wf.ContainerID)
550-
modName := h.GetModuleName(modID)
551-
if moduleName != "" && modName != moduleName {
552-
continue
553-
}
554-
wfFound = true
555-
var roleStrs []string
556-
for _, r := range wf.AllowedModuleRoles {
557-
roleStrs = append(roleStrs, string(r))
558-
}
559-
fmt.Fprintf(e.output, " %s.%s: %s\n", modName, wf.Name, strings.Join(roleStrs, ", "))
560-
}
561-
if !wfFound {
562-
fmt.Fprintln(e.output, "(no workflow access rules configured)")
563-
}
509+
fmt.Fprintln(e.output, "(workflow access is controlled through triggering microflows and UserTask targeting, not document-level roles)")
564510
fmt.Fprintln(e.output)
565511

566512
return nil

mdl/executor/cmd_security_write.go

Lines changed: 6 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -753,121 +753,17 @@ func (e *Executor) execRevokePageAccess(s *ast.RevokePageAccessStmt) error {
753753
}
754754

755755
// execGrantWorkflowAccess handles GRANT EXECUTE ON WORKFLOW Module.WF TO roles.
756+
// Mendix workflows do not have a document-level AllowedModuleRoles field (unlike
757+
// microflows and pages), so this operation is not supported.
756758
func (e *Executor) execGrantWorkflowAccess(s *ast.GrantWorkflowAccessStmt) error {
757-
if e.writer == nil {
758-
return fmt.Errorf("not connected to a project in write mode")
759-
}
760-
761-
h, err := e.getHierarchy()
762-
if err != nil {
763-
return fmt.Errorf("failed to build hierarchy: %w", err)
764-
}
765-
766-
// Find the workflow
767-
wfs, err := e.reader.ListWorkflows()
768-
if err != nil {
769-
return fmt.Errorf("failed to list workflows: %w", err)
770-
}
771-
772-
for _, wf := range wfs {
773-
modID := h.FindModuleID(wf.ContainerID)
774-
modName := h.GetModuleName(modID)
775-
if modName != s.Workflow.Module || wf.Name != s.Workflow.Name {
776-
continue
777-
}
778-
779-
// Validate all roles exist
780-
for _, role := range s.Roles {
781-
if err := e.validateModuleRole(role); err != nil {
782-
return err
783-
}
784-
}
785-
786-
// Merge new roles with existing (skip duplicates)
787-
existing := make(map[string]bool)
788-
var merged []string
789-
for _, r := range wf.AllowedModuleRoles {
790-
existing[string(r)] = true
791-
merged = append(merged, string(r))
792-
}
793-
var added []string
794-
for _, role := range s.Roles {
795-
qn := role.Module + "." + role.Name
796-
if !existing[qn] {
797-
merged = append(merged, qn)
798-
added = append(added, qn)
799-
}
800-
}
801-
802-
if err := e.writer.UpdateAllowedRoles(wf.ID, merged); err != nil {
803-
return fmt.Errorf("failed to update workflow access: %w", err)
804-
}
805-
806-
if len(added) == 0 {
807-
fmt.Fprintf(e.output, "All specified roles already have execute access on %s.%s\n", modName, wf.Name)
808-
} else {
809-
fmt.Fprintf(e.output, "Granted execute access on %s.%s to %s\n", modName, wf.Name, strings.Join(added, ", "))
810-
}
811-
return nil
812-
}
813-
814-
return fmt.Errorf("workflow not found: %s.%s", s.Workflow.Module, s.Workflow.Name)
759+
return fmt.Errorf("GRANT EXECUTE ON WORKFLOW is not supported: Mendix workflows do not have document-level AllowedModuleRoles (unlike microflows and pages). Workflow access is controlled through the microflow that triggers the workflow and UserTask targeting")
815760
}
816761

817762
// execRevokeWorkflowAccess handles REVOKE EXECUTE ON WORKFLOW Module.WF FROM roles.
763+
// Mendix workflows do not have a document-level AllowedModuleRoles field (unlike
764+
// microflows and pages), so this operation is not supported.
818765
func (e *Executor) execRevokeWorkflowAccess(s *ast.RevokeWorkflowAccessStmt) error {
819-
if e.writer == nil {
820-
return fmt.Errorf("not connected to a project in write mode")
821-
}
822-
823-
h, err := e.getHierarchy()
824-
if err != nil {
825-
return fmt.Errorf("failed to build hierarchy: %w", err)
826-
}
827-
828-
// Find the workflow
829-
wfs, err := e.reader.ListWorkflows()
830-
if err != nil {
831-
return fmt.Errorf("failed to list workflows: %w", err)
832-
}
833-
834-
for _, wf := range wfs {
835-
modID := h.FindModuleID(wf.ContainerID)
836-
modName := h.GetModuleName(modID)
837-
if modName != s.Workflow.Module || wf.Name != s.Workflow.Name {
838-
continue
839-
}
840-
841-
// Build set of roles to remove
842-
toRemove := make(map[string]bool)
843-
for _, role := range s.Roles {
844-
toRemove[role.Module+"."+role.Name] = true
845-
}
846-
847-
// Filter out removed roles
848-
var remaining []string
849-
var removed []string
850-
for _, r := range wf.AllowedModuleRoles {
851-
if toRemove[string(r)] {
852-
removed = append(removed, string(r))
853-
} else {
854-
remaining = append(remaining, string(r))
855-
}
856-
}
857-
858-
if err := e.writer.UpdateAllowedRoles(wf.ID, remaining); err != nil {
859-
return fmt.Errorf("failed to update workflow access: %w", err)
860-
}
861-
862-
if len(removed) == 0 {
863-
fmt.Fprintf(e.output, "None of the specified roles had execute access on %s.%s\n", modName, wf.Name)
864-
} else {
865-
fmt.Fprintf(e.output, "Revoked execute access on %s.%s from %s\n", modName, wf.Name, strings.Join(removed, ", "))
866-
}
867-
return nil
868-
}
869-
870-
return fmt.Errorf("workflow not found: %s.%s", s.Workflow.Module, s.Workflow.Name)
766+
return fmt.Errorf("REVOKE EXECUTE ON WORKFLOW is not supported: Mendix workflows do not have document-level AllowedModuleRoles (unlike microflows and pages). Workflow access is controlled through the microflow that triggers the workflow and UserTask targeting")
871767
}
872768

873769
// validateModuleRole checks that a module role exists in the project.

sdk/mpr/parser_workflow.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,6 @@ func (r *Reader) parseWorkflow(unitID, containerID string, contents []byte) (*wo
7777
w.DueDate = dueDate
7878
}
7979

80-
// Parse allowed module roles (BY_NAME references)
81-
allowedRoles := extractBsonArray(raw["AllowedModuleRoles"])
82-
for _, r := range allowedRoles {
83-
if name, ok := r.(string); ok {
84-
w.AllowedModuleRoles = append(w.AllowedModuleRoles, model.ID(name))
85-
}
86-
}
87-
8880
// Parse Flow (PART — Workflows$Flow)
8981
if flowRaw := raw["Flow"]; flowRaw != nil {
9082
w.Flow = parseWorkflowFlow(toMap(flowRaw))

0 commit comments

Comments
 (0)