Skip to content
Open
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
2 changes: 0 additions & 2 deletions docs-site/src/appendixes/quick-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,8 +232,6 @@ Nested folders use `/` separator: `'Parent/Child/Grandchild'`. Missing folders a
| Describe workflow | `DESCRIBE WORKFLOW Module.Name;` | Full MDL output |
| Create workflow | `CREATE [OR MODIFY] WORKFLOW Module.Name PARAMETER $Ctx: Module.Entity BEGIN ... END WORKFLOW;` | See activity types below |
| Drop workflow | `DROP WORKFLOW Module.Name;` | |
| Grant workflow access | `GRANT EXECUTE ON WORKFLOW Module.Name TO Mod.Role, ...;` | |
| Revoke workflow access | `REVOKE EXECUTE ON WORKFLOW Module.Name FROM Mod.Role, ...;` | |

**Workflow Activity Types:**
- `USER TASK <name> '<caption>' [PAGE Mod.Page] [TARGETING MICROFLOW Mod.MF] [OUTCOMES '<out>' { } ...];`
Expand Down
5 changes: 1 addition & 4 deletions docs-site/src/language/grant-revoke.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,7 @@ REVOKE EXECUTE ON NANOFLOW <Module>.<Name> FROM <Module>.<Role> [, ...];

## Workflow Access

```sql
GRANT EXECUTE ON WORKFLOW <Module>.<Name> TO <Module>.<Role> [, ...];
REVOKE EXECUTE ON WORKFLOW <Module>.<Name> FROM <Module>.<Role> [, ...];
```
> **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.

## OData Service Access

Expand Down
7 changes: 1 addition & 6 deletions docs-site/src/language/workflow-structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,7 @@ DROP WORKFLOW HR.OnboardEmployee;

## Workflow Access

Grant or revoke execute access to control who can start a workflow:

```sql
GRANT EXECUTE ON WORKFLOW HR.OnboardEmployee TO HR.Manager, HR.Admin;
REVOKE EXECUTE ON WORKFLOW HR.OnboardEmployee FROM HR.Manager;
```
> **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.

## See Also

Expand Down
2 changes: 1 addition & 1 deletion docs-site/src/language/workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,4 @@ END WORKFLOW;
- [Workflow Structure](./workflow-structure.md) -- full CREATE WORKFLOW syntax
- [Activity Types](./workflow-activities.md) -- all workflow activity types
- [Workflow vs Microflow](./workflow-vs-microflow.md) -- choosing between the two
- [GRANT / REVOKE](./grant-revoke.md) -- granting execute access on workflows
- [GRANT / REVOKE](./grant-revoke.md) -- workflow access is controlled through triggering microflows and UserTask targeting, not document-level roles
2 changes: 0 additions & 2 deletions docs-site/src/reference/workflow/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,3 @@ Mendix workflows model long-running business processes with user tasks, decision
|-----------|--------|
| Show workflows | `SHOW WORKFLOWS [IN module]` |
| Describe workflow | `DESCRIBE WORKFLOW module.Name` |
| Grant workflow access | `GRANT EXECUTE ON WORKFLOW module.Name TO module.Role, ...` |
| Revoke workflow access | `REVOKE EXECUTE ON WORKFLOW module.Name FROM module.Role, ...` |
2 changes: 1 addition & 1 deletion docs/01-project/MDL_FEATURE_MATRIX.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ Document types that exist in Mendix but have no MDL support:
| **JSON transformations** | Y | Y | Y | Y | Y | N | 20 | Y | N | N | P | N | N | N | N | N | N | JSON structure definitions |
| **Message definitions** | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | Message definition documents |
| **XML schemas** | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | Imported XML schema documents |
| **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 |
| **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) |
| **Module settings** | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | Module-level configuration |
| **Image collection** | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | Image document collections |
| **Icon collection** | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | Icon/glyph collections |
Expand Down
2 changes: 0 additions & 2 deletions docs/01-project/MDL_QUICK_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,8 +269,6 @@ Nested folders use `/` separator: `'Parent/Child/Grandchild'`. Missing folders a
| Describe workflow | `DESCRIBE WORKFLOW Module.Name;` | Full MDL output |
| Create workflow | `CREATE [OR MODIFY] WORKFLOW Module.Name PARAMETER $Ctx: Module.Entity BEGIN ... END WORKFLOW;` | See activity types below |
| Drop workflow | `DROP WORKFLOW Module.Name;` | |
| Grant workflow access | `GRANT EXECUTE ON WORKFLOW Module.Name TO Mod.Role, ...;` | |
| Revoke workflow access | `REVOKE EXECUTE ON WORKFLOW Module.Name FROM Mod.Role, ...;` | |

**Workflow Activity Types:**
- `USER TASK <name> '<caption>' [PAGE Mod.Page] [TARGETING MICROFLOW Mod.MF] [OUTCOMES '<out>' { } ...];`
Expand Down
3 changes: 0 additions & 3 deletions mdl/executor/cmd_misc.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,6 @@ Security - Access Control:
REVOKE EXECUTE ON MICROFLOW Module.Name FROM Role [, Role...];
GRANT VIEW ON PAGE Module.Name TO Role [, Role...];
REVOKE VIEW ON PAGE Module.Name FROM Role [, Role...];
GRANT EXECUTE ON WORKFLOW Module.Name TO Role [, Role...];
REVOKE EXECUTE ON WORKFLOW Module.Name FROM Role [, Role...];
GRANT Role ON Module.Entity (CREATE, DELETE, READ *, WRITE *) [WHERE 'xpath'];
REVOKE Role ON Module.Entity;

Expand All @@ -224,7 +222,6 @@ Security - Queries:
SHOW DEMO USERS;
SHOW ACCESS ON MICROFLOW Module.Name;
SHOW ACCESS ON PAGE Module.Name;
SHOW ACCESS ON WORKFLOW Module.Name;
SHOW ACCESS ON Module.Entity;
SHOW SECURITY MATRIX [IN Module];
DESCRIBE MODULE ROLE Module.Role;
Expand Down
82 changes: 3 additions & 79 deletions mdl/executor/cmd_security.go
Original file line number Diff line number Diff line change
Expand Up @@ -313,36 +313,7 @@ func (e *Executor) showAccessOnPage(name *ast.QualifiedName) error {

// showAccessOnWorkflow handles SHOW ACCESS ON WORKFLOW Module.WF.
func (e *Executor) showAccessOnWorkflow(name *ast.QualifiedName) error {
if name == nil {
return fmt.Errorf("workflow name required")
}

h, err := e.getHierarchy()
if err != nil {
return fmt.Errorf("failed to build hierarchy: %w", err)
}

wfs, err := e.reader.ListWorkflows()
if err != nil {
return fmt.Errorf("failed to list workflows: %w", err)
}

for _, wf := range wfs {
modName := h.GetModuleName(h.FindModuleID(wf.ContainerID))
if modName == name.Module && wf.Name == name.Name {
if len(wf.AllowedModuleRoles) == 0 {
fmt.Fprintf(e.output, "No module roles granted execute access on %s.%s\n", modName, wf.Name)
return nil
}
fmt.Fprintf(e.output, "Allowed module roles for %s.%s:\n", modName, wf.Name)
for _, role := range wf.AllowedModuleRoles {
fmt.Fprintf(e.output, " %s\n", string(role))
}
return nil
}
}

return fmt.Errorf("workflow not found: %s", name)
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")
}

// showSecurityMatrix handles SHOW SECURITY MATRIX [IN module].
Expand Down Expand Up @@ -536,35 +507,10 @@ func (e *Executor) showSecurityMatrix(moduleName string) error {
}
fmt.Fprintln(e.output)

// Workflow section
// Workflow section — workflows don't have document-level AllowedModuleRoles
fmt.Fprintln(e.output, "## Workflow Access")
fmt.Fprintln(e.output)

wfs, err := e.reader.ListWorkflows()
if err != nil {
return fmt.Errorf("failed to list workflows: %w", err)
}

wfFound := false
for _, wf := range wfs {
if len(wf.AllowedModuleRoles) == 0 {
continue
}
modID := h.FindModuleID(wf.ContainerID)
modName := h.GetModuleName(modID)
if moduleName != "" && modName != moduleName {
continue
}
wfFound = true
var roleStrs []string
for _, r := range wf.AllowedModuleRoles {
roleStrs = append(roleStrs, string(r))
}
fmt.Fprintf(e.output, " %s.%s: %s\n", modName, wf.Name, strings.Join(roleStrs, ", "))
}
if !wfFound {
fmt.Fprintln(e.output, "(no workflow access rules configured)")
}
fmt.Fprintln(e.output, "(workflow access is controlled through triggering microflows and UserTask targeting, not document-level roles)")
fmt.Fprintln(e.output)

return nil
Expand Down Expand Up @@ -683,28 +629,6 @@ func (e *Executor) showSecurityMatrixJSON(moduleName string) error {
})
}

// Workflows
wfs, _ := e.reader.ListWorkflows()
for _, wf := range wfs {
if len(wf.AllowedModuleRoles) == 0 {
continue
}
modID := h.FindModuleID(wf.ContainerID)
modName := h.GetModuleName(modID)
if moduleName != "" && modName != moduleName {
continue
}
var roleStrs []string
for _, r := range wf.AllowedModuleRoles {
roleStrs = append(roleStrs, string(r))
}
tr.Rows = append(tr.Rows, []any{
"Workflow",
modName + "." + wf.Name,
strings.Join(roleStrs, ", "),
"X",
})
}

return e.writeResult(tr)
}
Expand Down
116 changes: 6 additions & 110 deletions mdl/executor/cmd_security_write.go
Original file line number Diff line number Diff line change
Expand Up @@ -753,121 +753,17 @@ func (e *Executor) execRevokePageAccess(s *ast.RevokePageAccessStmt) error {
}

// execGrantWorkflowAccess handles GRANT EXECUTE ON WORKFLOW Module.WF TO roles.
// Mendix workflows do not have a document-level AllowedModuleRoles field (unlike
// microflows and pages), so this operation is not supported.
func (e *Executor) execGrantWorkflowAccess(s *ast.GrantWorkflowAccessStmt) error {
if e.writer == nil {
return fmt.Errorf("not connected to a project in write mode")
}

h, err := e.getHierarchy()
if err != nil {
return fmt.Errorf("failed to build hierarchy: %w", err)
}

// Find the workflow
wfs, err := e.reader.ListWorkflows()
if err != nil {
return fmt.Errorf("failed to list workflows: %w", err)
}

for _, wf := range wfs {
modID := h.FindModuleID(wf.ContainerID)
modName := h.GetModuleName(modID)
if modName != s.Workflow.Module || wf.Name != s.Workflow.Name {
continue
}

// Validate all roles exist
for _, role := range s.Roles {
if err := e.validateModuleRole(role); err != nil {
return err
}
}

// Merge new roles with existing (skip duplicates)
existing := make(map[string]bool)
var merged []string
for _, r := range wf.AllowedModuleRoles {
existing[string(r)] = true
merged = append(merged, string(r))
}
var added []string
for _, role := range s.Roles {
qn := role.Module + "." + role.Name
if !existing[qn] {
merged = append(merged, qn)
added = append(added, qn)
}
}

if err := e.writer.UpdateAllowedRoles(wf.ID, merged); err != nil {
return fmt.Errorf("failed to update workflow access: %w", err)
}

if len(added) == 0 {
fmt.Fprintf(e.output, "All specified roles already have execute access on %s.%s\n", modName, wf.Name)
} else {
fmt.Fprintf(e.output, "Granted execute access on %s.%s to %s\n", modName, wf.Name, strings.Join(added, ", "))
}
return nil
}

return fmt.Errorf("workflow not found: %s.%s", s.Workflow.Module, s.Workflow.Name)
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")
}

// execRevokeWorkflowAccess handles REVOKE EXECUTE ON WORKFLOW Module.WF FROM roles.
// Mendix workflows do not have a document-level AllowedModuleRoles field (unlike
// microflows and pages), so this operation is not supported.
func (e *Executor) execRevokeWorkflowAccess(s *ast.RevokeWorkflowAccessStmt) error {
if e.writer == nil {
return fmt.Errorf("not connected to a project in write mode")
}

h, err := e.getHierarchy()
if err != nil {
return fmt.Errorf("failed to build hierarchy: %w", err)
}

// Find the workflow
wfs, err := e.reader.ListWorkflows()
if err != nil {
return fmt.Errorf("failed to list workflows: %w", err)
}

for _, wf := range wfs {
modID := h.FindModuleID(wf.ContainerID)
modName := h.GetModuleName(modID)
if modName != s.Workflow.Module || wf.Name != s.Workflow.Name {
continue
}

// Build set of roles to remove
toRemove := make(map[string]bool)
for _, role := range s.Roles {
toRemove[role.Module+"."+role.Name] = true
}

// Filter out removed roles
var remaining []string
var removed []string
for _, r := range wf.AllowedModuleRoles {
if toRemove[string(r)] {
removed = append(removed, string(r))
} else {
remaining = append(remaining, string(r))
}
}

if err := e.writer.UpdateAllowedRoles(wf.ID, remaining); err != nil {
return fmt.Errorf("failed to update workflow access: %w", err)
}

if len(removed) == 0 {
fmt.Fprintf(e.output, "None of the specified roles had execute access on %s.%s\n", modName, wf.Name)
} else {
fmt.Fprintf(e.output, "Revoked execute access on %s.%s from %s\n", modName, wf.Name, strings.Join(removed, ", "))
}
return nil
}

return fmt.Errorf("workflow not found: %s.%s", s.Workflow.Module, s.Workflow.Name)
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")
}

// validateModuleRole checks that a module role exists in the project.
Expand Down
8 changes: 0 additions & 8 deletions sdk/mpr/parser_workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,6 @@ func (r *Reader) parseWorkflow(unitID, containerID string, contents []byte) (*wo
w.DueDate = dueDate
}

// Parse allowed module roles (BY_NAME references)
allowedRoles := extractBsonArray(raw["AllowedModuleRoles"])
for _, r := range allowedRoles {
if name, ok := r.(string); ok {
w.AllowedModuleRoles = append(w.AllowedModuleRoles, model.ID(name))
}
}

// Parse Flow (PART — Workflows$Flow)
if flowRaw := raw["Flow"]; flowRaw != nil {
w.Flow = parseWorkflowFlow(toMap(flowRaw))
Expand Down
3 changes: 0 additions & 3 deletions sdk/workflows/workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@ type Workflow struct {

// Flow contains the workflow activities
Flow *Flow `json:"flow,omitempty"`

// Allowed module roles for execution
AllowedModuleRoles []model.ID `json:"allowedModuleRoles,omitempty"`
}

// GetName returns the workflow's name.
Expand Down
Loading