Skip to content
Merged
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
4 changes: 2 additions & 2 deletions Gemfile.sqlite-vec.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
mxrb (0.1.0)
mxrb (0.1.1)
base64 (~> 0.2)
bigdecimal (~> 3.1)
bson (~> 5.2)
Expand Down Expand Up @@ -126,7 +126,7 @@ CHECKSUMS
json (2.21.1) sha256=13a43df75d95641443f5702dff350f237164a9d811ff0f2c2800d4d980220583
language_server-protocol (3.17.0.6) sha256=5ef2c0c138f8267e1bc631d3328347d354f96724b0af22f2c79516120443b7f0
lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
mxrb (0.1.0)
mxrb (0.1.1)
onnxruntime (0.11.5-arm64-darwin) sha256=9f2f45b3ac16999c466ac352562195e03a011cc930b8750c0b1e43a49c1ca5d9
onnxruntime (0.11.5-x86_64-darwin) sha256=19c18e90eb101f69a3232dfdd1742e77d69e2df623b106b670221629d4b4cc15
onnxruntime (0.11.5-x86_64-linux) sha256=13f20de606a8fb32ba404bca9e1fc47f0eab4605c909252dbcf6db4fb106c9e6
Expand Down
3 changes: 2 additions & 1 deletion lib/mxrb/compiler/gallery_bundle_compiler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,14 @@ def primitives(values)
end
end

def primitive(type, value) # rubocop:disable Metrics/CyclomaticComplexity
def primitive(type, value) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
compiled = case type
when 'Boolean' then value['PrimitiveValue'] == 'true'
when 'Integer' then value['PrimitiveValue'].to_i
when 'Enumeration' then value['PrimitiveValue'].to_s
when 'TextTemplate' then raw(expression(translated_text(value['TextTemplate'])))
when 'Widgets' then [] if array(value['Widgets']).empty?
when 'Object' then [] if array(value['Objects']).empty?
end
compiled.nil? ? :undefined : compiled
end
Expand Down
5 changes: 2 additions & 3 deletions lib/mxrb/compiler/page_bundle_compiler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1810,9 +1810,8 @@ def widget_imports
(@generic_widgets || {}).sort.each do |name, path|
imports.concat([
"import * as #{name}WidgetModule from \"../widgets/#{path}.mjs\";",
"const #{name} = Object.getOwnPropertyDescriptor(#{name}WidgetModule, " \
"#{JSON.generate(name)})?.value || " \
"Object.getOwnPropertyDescriptor(#{name}WidgetModule, \"default\")?.value;"
"const #{name} = #{name}WidgetModule[#{JSON.generate(name)}] || " \
"#{name}WidgetModule.default;"
])
widgets << name
end
Expand Down
23 changes: 18 additions & 5 deletions lib/mxrb/compiler/web_operation_compiler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,8 @@ def microflow_action_operation(page_name, widget, action, role_sets)
{
'operationId' => self.class.operation_id(page_name, widget['Name']),
'operationType' => 'callMicroflow', 'parameters' => parameters,
'constants' => { 'MicroflowName' => name }, 'allowedUserRoleSets' => role_sets
'constants' => { 'MicroflowName' => name },
'allowedUserRoleSets' => microflow_operation_role_sets(role_sets, name)
}
end

Expand Down Expand Up @@ -276,7 +277,7 @@ def operation(page_name, widget, role_sets, page_document)
'operationId' => self.class.operation_id(page_name, widget['Name']),
'operationType' => 'retrieveByMicroflow', 'parameters' => microflow_parameters(source.microflow_name) || {},
'constants' => data_source_constants(page_name, widget, source.microflow_name),
'allowedUserRoleSets' => role_sets
'allowedUserRoleSets' => microflow_operation_role_sets(role_sets, source.microflow_name)
}
end

Expand Down Expand Up @@ -340,7 +341,8 @@ def call_microflow_operation(page_name, widget, role_sets, name)
{
'operationId' => self.class.operation_id(page_name, widget['Name']),
'operationType' => 'callMicroflow', 'parameters' => parameters,
'constants' => { 'MicroflowName' => name }, 'allowedUserRoleSets' => role_sets
'constants' => { 'MicroflowName' => name },
'allowedUserRoleSets' => microflow_operation_role_sets(role_sets, name)
}
end

Expand All @@ -352,7 +354,7 @@ def retrieve_by_microflow_operation(page_name, widget, role_sets, name)
'operationId' => self.class.operation_id(page_name, widget['Name']),
'operationType' => 'retrieveByMicroflow', 'parameters' => parameters,
'constants' => data_source_constants(page_name, widget, name),
'allowedUserRoleSets' => role_sets
'allowedUserRoleSets' => microflow_operation_role_sets(role_sets, name)
}
end

Expand Down Expand Up @@ -455,7 +457,9 @@ def nanoflow_action_operations(qualified_name, document)
nested(document, 'Microflows$ActionActivity').filter_map do |activity|
action = activity['Action'] || {}
operation = nanoflow_server_operation(qualified_name, activity, action, entities)
operation&.merge('allowedUserRoleSets' => [])
name = operation&.dig('constants', 'MicroflowName')
role_sets = name ? microflow_operation_role_sets([], name) : []
operation&.merge('allowedUserRoleSets' => role_sets)
end
end

Expand Down Expand Up @@ -537,6 +541,15 @@ def allowed_user_role_sets(unit) # rubocop:disable Metrics/AbcSize
end
end # rubocop:enable Metrics/AbcSize

def microflow_operation_role_sets(container_role_sets, qualified_name)
flow = qualified_unit('Microflows$Microflow', qualified_name)
flow_role_sets = flow ? allowed_user_role_sets(flow) : []
return container_role_sets if flow_role_sets.empty?
return flow_role_sets if container_role_sets.empty?

container_role_sets & flow_role_sets
end

def constants(page_name, widget, constraint, entity)
{
'PageName' => page_name, 'WidgetName' => "#{page_name}.#{widget['Name']}",
Expand Down
2 changes: 1 addition & 1 deletion lib/mxrb/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module Mxrb
VERSION = "0.1.0"
VERSION = "0.1.1"
end
2 changes: 1 addition & 1 deletion mxrb.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Gem::Specification.new do |s|
s.name = "mxrb"
s.version = "0.1.0"
s.version = "0.1.1"
s.summary = "Pure-Ruby read/write engine for Mendix .mpr projects — no mxcli required"
s.description = "mxrb reads and writes Mendix .mpr files (SQLite3) directly, providing a Ruby DSL to define entities, pages, microflows and modules without any dependency on the official mxcli tooling."
s.authors = ["Lucas Moura"]
Expand Down
3 changes: 2 additions & 1 deletion script/frontend_browser_acceptance
Original file line number Diff line number Diff line change
Expand Up @@ -530,7 +530,8 @@ module MxrbFrontendBrowserAcceptance
Array(page['styles']).each { @browser.wait_for_style(_1) }
snapshot = @browser.snapshot
failure_markers = ['Could not render widget',
'An error occurred, please contact your system administrator.']
'An error occurred, please contact your system administrator.',
'Executing runtime operation failed for security reasons']
render_errors = Array(snapshot['elements']).filter_map do |element|
text = element['text'].to_s
text if failure_markers.any? { text.include?(_1) }
Expand Down
16 changes: 10 additions & 6 deletions spec/frontend_browser_acceptance_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,16 @@ def run(browser, root, baseline: nil, update_baseline: false)

it 'rejects the generic Mendix runtime error dialog' do
Dir.mktmpdir do |root|
broken = snapshot.merge('elements' => [{
'tag' => 'p', 'text' => 'An error occurred, please contact your system administrator.'
}])

expect(run(fake_browser(snapshot: broken), root).fetch(:error))
.to include('visible widget render failure')
messages = [
'An error occurred, please contact your system administrator.',
'Executing runtime operation failed for security reasons: operation-id'
]

messages.each do |message|
broken = snapshot.merge('elements' => [{ 'tag' => 'p', 'text' => message }])
expect(run(fake_browser(snapshot: broken), root).fetch(:error))
.to include('visible widget render failure')
end
end
end
end
Expand Down
2 changes: 2 additions & 0 deletions spec/gallery_bundle_compiler_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ def source(*units, widget_schema: schema)
expect(compiler).not_to be_supported
expect(compiler.send(:property_values, nil)).to eq({})
expect(compiler.send(:primitive, 'Widgets', 'Widgets' => [2, {}])).to eq(:undefined)
expect(compiler.send(:primitive, 'Object', 'Objects' => [2])).to eq([])
expect(compiler.send(:primitive, 'Object', 'Objects' => [2, {}])).to eq(:undefined)
expect(compiler.send(:translated_text, nil)).to eq('')
end

Expand Down
4 changes: 3 additions & 1 deletion spec/page_bundle_compiler_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -872,8 +872,10 @@
imports = compiler.send(:widget_imports)
expect(imports).to include(
'RadioButtonGroup', 'GroupBox', 'FileManager', 'MicroflowObjectProperty',
'ListView', 'NanoflowObjectProperty', 'CustomWidgetModule'
'ListView', 'NanoflowObjectProperty', 'CustomWidgetModule',
'const Custom = CustomWidgetModule["Custom"] || CustomWidgetModule.default;'
)
expect(imports).not_to include('Object.getOwnPropertyDescriptor')
end

it 'executes custom image and generic action-property callbacks' do
Expand Down
69 changes: 69 additions & 0 deletions spec/web_operation_compiler_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,75 @@ def widget(source: true)
.to eq([['Administrator']])
end

it 'authorizes layout microflow data sources from the microflow module roles' do
data_view = {
'$Type' => 'Forms$DataView', 'Name' => 'currentUser', 'DataSource' => {
'$Type' => 'Forms$MicroflowSource',
'MicroflowSettings' => { 'Microflow' => 'Atlas_Core.DS_Account_CurrentUser' }
}
}
layout = unit(module_name: 'Atlas_Core', document: {
'$Type' => 'Forms$Layout', 'Name' => 'Atlas_TopBar', 'Widgets' => [data_view]
})
flow = unit(module_name: 'Atlas_Core', document: {
'$Type' => 'Microflows$Microflow', 'Name' => 'DS_Account_CurrentUser',
'AllowedModuleRoles' => [1, 'Atlas_Core.User']
})
security = {
'UserRoles' => [2,
{ 'Name' => 'Administrator',
'ModuleRoles' => [1, 'Atlas_Core.User'] },
{ 'Name' => 'Unrelated', 'ModuleRoles' => [1, 'Demo.User'] }]
}
source = instance_double(Mxrb::Compiler::SourceModel)
allow(source).to receive(:units_of) do |type|
{ 'Microflows$Microflow' => [flow] }.fetch(type, [])
end
allow(source).to receive(:documents).with('Security$ProjectSecurity').and_return([security])

expect(described_class.new(source).send(:page_operations, layout)).to contain_exactly(
include(
'operationType' => 'retrieveByMicroflow',
'allowedUserRoleSets' => [['Administrator']]
)
)
end

it 'limits page and nanoflow microflow operations to the microflow roles' do
flow = unit(module_name: 'Demo', document: {
'$Type' => 'Microflows$Microflow', 'Name' => 'SecureAction',
'AllowedModuleRoles' => [1, 'Demo.Executor']
})
security = {
'UserRoles' => [2,
{ 'Name' => 'Administrator', 'ModuleRoles' => [1, 'Demo.Executor'] },
{ 'Name' => 'Viewer', 'ModuleRoles' => [1, 'Demo.Viewer'] }]
}
source = instance_double(Mxrb::Compiler::SourceModel)
allow(source).to receive(:units_of).with('Microflows$Microflow').and_return([flow])
allow(source).to receive(:documents).with('Security$ProjectSecurity').and_return([security])
compiler = described_class.new(source)

expect(compiler.send(
:microflow_operation_role_sets, [['Administrator']], 'Demo.Missing'
)).to eq([['Administrator']])

expect(compiler.send(
:call_microflow_operation, 'Demo.Home', { 'Name' => 'run' },
[['Administrator'], ['Viewer']], 'Demo.SecureAction'
)).to include('allowedUserRoleSets' => [['Administrator']])

nanoflow = {
'ObjectCollection' => { 'Objects' => [2, {
'$Type' => 'Microflows$ActionActivity', '$ID' => SecureRandom.uuid,
'Action' => { '$Type' => 'Microflows$MicroflowCallAction',
'MicroflowCall' => { 'Microflow' => 'Demo.SecureAction' } }
}] }
}
expect(compiler.send(:nanoflow_action_operations, 'Demo.ClientAction', nanoflow))
.to contain_exactly(include('allowedUserRoleSets' => [['Administrator']]))
end

it 'covers defensive menu, data-action, association, and nanoflow operation paths' do # rubocop:disable Metrics/BlockLength
expect(described_class.menu_operation_id('$ID' => SecureRandom.uuid)).to be_a(String)
expect(described_class.menu_operation_id(
Expand Down
Loading