Class: Mxrb::Runtime::AccessControl

Inherits:
Object
  • Object
show all
Defined in:
lib/mxrb/runtime/access_control.rb

Overview

Enforces Mendix project, document, entity, and member access rules. Unsupported XPath constructs are never treated as a successful match.

Constant Summary collapse

RIGHTS =

rubocop:disable Metrics/ClassLength

{
  'none' => :none,
  'readonly' => :read,
  'readwrite' => :write
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(project) ⇒ AccessControl

Returns a new instance of AccessControl.



46
47
48
49
50
51
# File 'lib/mxrb/runtime/access_control.rb', line 46

def initialize(project)
  @project = project
  @security = project_security
  @security_level = value(@security, 'SecurityLevel').to_s
  @role_map, @admin_roles = build_role_map
end

Instance Attribute Details

#projectObject (readonly)

Returns the value of attribute project.



44
45
46
# File 'lib/mxrb/runtime/access_control.rb', line 44

def project
  @project
end

#security_levelObject (readonly)

Returns the value of attribute security_level.



44
45
46
# File 'lib/mxrb/runtime/access_control.rb', line 44

def security_level
  @security_level
end

Instance Method Details

#admin?(context) ⇒ Boolean

Returns:

  • (Boolean)


70
71
72
# File 'lib/mxrb/runtime/access_control.rb', line 70

def admin?(context)
  !(normalized_context(context).user_roles & @admin_roles).empty?
end

#authorize!(resource, action:, context:, kind: nil, member: nil, record: nil) ⇒ Object

Raises:



112
113
114
115
116
117
# File 'lib/mxrb/runtime/access_control.rb', line 112

def authorize!(resource, action:, context:, kind: nil, member: nil, record: nil)
  return true if authorized?(resource, action:, context:, kind:, member:, record:)

  name = resource.respond_to?(:name) ? resource.name : resource
  raise AuthorizationError, "not authorized to #{action} #{name}"
end

#authorized?(resource, action:, context:, kind: nil, member: nil, record: nil) ⇒ Boolean

Returns:

  • (Boolean)


100
101
102
103
104
105
106
107
108
109
110
# File 'lib/mxrb/runtime/access_control.rb', line 100

def authorized?(resource, action:, context:, kind: nil, member: nil, record: nil)
  inferred_kind = kind || resource_kind(resource)
  case inferred_kind&.to_sym
  when :microflow then microflow_allowed?(resource, context:)
  when :page then page_allowed?(resource, context:)
  when :entity
    entity_allowed?(resource, action:, context:, member:, record:)
  else
    false
  end
end

#context(user: nil, roles: [], user_roles: nil, module_roles: [], attributes: {}, variables: {}) ⇒ Object



57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/mxrb/runtime/access_control.rb', line 57

def context(user: nil, roles: [], user_roles: nil, module_roles: [],
            attributes: {}, variables: {})
  supplied = SecurityContext.new(
    user:, roles:, user_roles:, module_roles:, attributes:, variables:
  )
  expanded = supplied.user_roles.flat_map { @role_map.fetch(_1, []) }
  SecurityContext.new(
    user: supplied.user, user_roles: supplied.user_roles,
    module_roles: supplied.module_roles + expanded,
    attributes: supplied.attributes, variables: supplied.variables
  )
end

#entity_allowed?(entity, action:, context:, member: nil, record: nil) ⇒ Boolean

Returns:

  • (Boolean)


82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/mxrb/runtime/access_control.rb', line 82

def entity_allowed?(entity, action:, context:, member: nil, record: nil)
  entity = resolve(:entity, entity)
  return false unless entity

  ctx = normalized_context(context)
  return true unless security_enabled?
  return true if admin?(ctx)

  rules = applicable_rules(entity, ctx, record:)
  return false if rules.empty?

  entity_action_allowed?(rules, action, member)
end

#evaluate_xpath(expression, record:, context:) ⇒ Object

Evaluates the deliberately small, safe XPath subset understood by the runtime. nil means that the expression is unsupported.



137
138
139
140
141
142
143
# File 'lib/mxrb/runtime/access_control.rb', line 137

def evaluate_xpath(expression, record:, context:)
  source = expression.to_s.strip
  return true if source.empty?

  source = source[1...-1].strip if source.start_with?('[') && source.end_with?(']')
  evaluate_boolean(source, record, normalized_context(context))
end

#filter_readable(entity, records, context:) ⇒ Object



129
130
131
132
133
# File 'lib/mxrb/runtime/access_control.rb', line 129

def filter_readable(entity, records, context:)
  Array(records).select do |record|
    entity_allowed?(entity, action: :read, context:, record:)
  end
end

#member_allowed?(entity, member, action:, context:, record: nil) ⇒ Boolean

Returns:

  • (Boolean)


96
97
98
# File 'lib/mxrb/runtime/access_control.rb', line 96

def member_allowed?(entity, member, action:, context:, record: nil)
  entity_allowed?(entity, action:, context:, member:, record:)
end

#microflow_allowed?(microflow, context:) ⇒ Boolean

Returns:

  • (Boolean)


74
75
76
# File 'lib/mxrb/runtime/access_control.rb', line 74

def microflow_allowed?(microflow, context:)
  document_allowed?(:microflow, microflow, context:)
end

#page_allowed?(page, context:) ⇒ Boolean

Returns:

  • (Boolean)


78
79
80
# File 'lib/mxrb/runtime/access_control.rb', line 78

def page_allowed?(page, context:)
  document_allowed?(:page, page, context:)
end

#security_enabled?Boolean

Returns:

  • (Boolean)


53
54
55
# File 'lib/mxrb/runtime/access_control.rb', line 53

def security_enabled?
  !@security.nil? && !%w[CheckNothing Off None].include?(@security_level)
end

#xpath_constraints(entity, context:) ⇒ Object

Returns the constraints contributed by roles in the context. An empty string is a valid unconstrained rule and therefore remains in the list.



121
122
123
124
125
126
127
# File 'lib/mxrb/runtime/access_control.rb', line 121

def xpath_constraints(entity, context:)
  entity = resolve(:entity, entity)
  return [] unless entity

  applicable_rules(entity, normalized_context(context), evaluate_xpath: false)
    .map { value(_1, :xpath, 'XPathConstraint').to_s }.uniq.freeze
end