Class: Checken::Permission

Inherits:
Object
  • Object
show all
Includes:
Concerns::HasParents
Defined in:
lib/checken/permission.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Concerns::HasParents

#parents, #path, #path_with_namespace, #root

Constructor Details

#initialize(group, key) ⇒ Permission

Create a new permission group

Parameters:



40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/checken/permission.rb', line 40

def initialize(group, key)
  if group.nil?
    raise Error, "Group must be provided when creating a permission"
  end

  @group = group
  @schema = group.schema
  @key = key
  @required_object_types = []
  @dependencies = []
  @contexts = []
end

Instance Attribute Details

#contextsArray<Symbol> (readonly)

The name of the contexts that apply to this permission

Returns:

  • (Array<Symbol>)


34
35
36
# File 'lib/checken/permission.rb', line 34

def contexts
  @contexts
end

#dependenciesArray<String> (readonly)

A list of permission paths that this permission depends on

Returns:

  • (Array<String>)


23
24
25
# File 'lib/checken/permission.rb', line 23

def dependencies
  @dependencies
end

#descriptionString

Return a description

Returns:

  • (String)


18
19
20
# File 'lib/checken/permission.rb', line 18

def description
  @description
end

#groupObject (readonly)

Returns the value of attribute group.



12
13
14
# File 'lib/checken/permission.rb', line 12

def group
  @group
end

#keyObject (readonly)

Returns the value of attribute key.



13
14
15
# File 'lib/checken/permission.rb', line 13

def key
  @key
end

#required_object_typesArray<String> (readonly)

An array of object type names (as Strings) that the object passed to this permission must be one of. If empty, any object is permitted.

Returns:

  • (Array<String>)


29
30
31
# File 'lib/checken/permission.rb', line 29

def required_object_types
  @required_object_types
end

Instance Method Details

#add_context(context) ⇒ Symbol, false

Add a new context to this permission

Parameters:

  • context (Symbol)

Returns:

  • (Symbol, false)


198
199
200
201
202
203
204
205
206
# File 'lib/checken/permission.rb', line 198

def add_context(context)
  context = context.to_sym
  if self.contexts.include?(context)
    false
  else
    self.contexts << context
    context
  end
end

#add_dependency(path) ⇒ String, false

Add a new dependency to this permission

Parameters:

  • path (String)

Returns:

  • (String, false)


221
222
223
224
225
226
227
228
229
# File 'lib/checken/permission.rb', line 221

def add_dependency(path)
  path = path.to_s
  if dependencies.include?(path)
    false
  else
    dependencies << path
    path
  end
end

#add_required_object_type(type) ⇒ String, false

Add a new dependency to this permission

Parameters:

  • path (String)

Returns:

  • (String, false)


235
236
237
238
239
240
241
242
243
# File 'lib/checken/permission.rb', line 235

def add_required_object_type(type)
  type = type.to_s
  if required_object_types.include?(type)
    false
  else
    required_object_types << type
    type
  end
end

#add_rule(key, rule = nil, &block) ⇒ Checken::Rule

Add a new rule to this permission

Parameters:

  • key (String)

Returns:



152
153
154
155
156
157
158
159
160
# File 'lib/checken/permission.rb', line 152

def add_rule(key, rule = nil, &block)
  key = key.to_sym
  if rules[key].nil?
    rule ||= Rule.new(key, &block)
    rules[key] = rule
  else
    raise Error, "Rule with key '#{key}' already exists on this permission"
  end
end

#check!(user_proxy, object = nil) ⇒ Object

Check this permission and raises an error if not permitted.

Parameters:

  • user (Object)
  • object (Object) (defaults to: nil)

Returns:

  • true



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/checken/permission.rb', line 67

def check!(user_proxy, object = nil)
  # If we havent' been given a user proxy here, we need to make one. This
  # shouldn't happen very often in production because everything would be
  # encapsulated by the User#can? method.
  unless user_proxy.is_a?(Checken::UserProxy)
    user_proxy = @group.schema.config.user_proxy_class.new(user_proxy)
  end

  # If we're asking about this permission and we aren't in the correct
  # context, it should be denied always.
  unless @contexts.empty?
    unless @contexts.any? { |c| user_proxy.contexts.include?(c) }
      @group.schema.logger.info "`#{self.path}` not granted to #{user_proxy.description} because not in context."
      error = PermissionDeniedError.new('NotInContext', "Permission '#{self.path}' cannot be granted in the #{user_proxy.contexts.join(',')} context(s). Only allowed for #{@contexts.join(', ')}.", self)
      error.user = user_proxy.user
      error.object = object
      raise error
    end
  end

  # Check the user has this permission
  if @group.schema.config.namespace && @group.schema.config.namespace_optional?
    found_permission = (user_proxy.granted_permissions & [self.path, self.path_with_namespace]).any?
  elsif @group.schema.config.namespace
    found_permission = user_proxy.granted_permissions.include?(self.path_with_namespace)
  else
    found_permission = user_proxy.granted_permissions.include?(self.path)
  end

  unless found_permission
    @group.schema.logger.info "`#{self.path}` not granted to #{user_proxy.description}"
    error = PermissionDeniedError.new('PermissionNotGranted', "User has not been granted the '#{self.path}' permission", self)
    error.user = user_proxy.user
    error.object = object
    raise error
  end

  # Check other dependent rules once we've established this
  # user has the base rule. The actual rules won't be checked
  # until we've checked other rules.
  dependencies_as_permissions.each do |dependency_permission|
    @group.schema.logger.info "`#{self.path}` has a dependency of `#{dependency_permission.path}`..."
    dependency_permission.check!(user_proxy, object)
  end

  # Check any included rules too
  if unsatisifed_rule = self.first_unsatisfied_included_rule(user_proxy, object)
    @group.schema.logger.info "`#{self.path} not granted to #{user_proxy.description} because rule `#{unsatisifed_rule.rule.key}` on `#{self.path}` was not satisified."
    error = PermissionDeniedError.new('IncludedRuleNotSatisifed', "Rule #{unsatisifed_rule.rule.key} (on #{self.path}) was not satisified.", self)
    error.rule = unsatisifed_rule
    error.user = user_proxy.user
    error.object = object
    raise error
  end

  # Check rules
  if self.required_object_types.empty? || self.required_object_types.include?(object.class.name)
    if unsatisifed_rule = self.first_unsatisfied_rule(user_proxy, object)
      @group.schema.logger.info "`#{self.path} not granted to #{user_proxy.description} because rule `#{unsatisifed_rule.rule.key}` on `#{self.path}` was not satisified."
      error = PermissionDeniedError.new('RuleNotSatisifed', "Rule #{unsatisifed_rule.rule.key} (on #{self.path}) was not satisified.", self)
      error.rule = unsatisifed_rule
      error.user = user_proxy.user
      error.object = object
      raise error
    else
      @group.schema.logger.info "`#{self.path}` granted to #{user_proxy.description}"
      [self, *dependencies_as_permissions]
    end
  else
    # If one of the permission doesn't have the right object type, raise an error
    raise InvalidObjectError, "The #{object.class.name} object provided to permission check for #{self.path} was not valid. Valid object types are: #{self.required_object_types.join(', ')}"
  end
end

#dependencies_as_permissionsArray<Checken::Permission>

Return an array of all dependencies as permissions

Returns:



301
302
303
304
305
# File 'lib/checken/permission.rb', line 301

def dependencies_as_permissions
  @dependencies_as_permissions ||= dependencies.map do |path|
    @group.schema.root_group.find_permissions_from_path(path)
  end.flatten
end

#dsl(&block) ⇒ Object



292
293
294
295
296
# File 'lib/checken/permission.rb', line 292

def dsl(&block)
  dsl = DSL::PermissionDSL.new(self)
  dsl.instance_eval(&block) if block_given?
  dsl
end

#first_unsatisfied_included_rule(user_proxy, object) ⇒ Object



260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
# File 'lib/checken/permission.rb', line 260

def first_unsatisfied_included_rule(user_proxy, object)
  self.included_rules.values.each do |included_rule|

    if included_rule.condition && !included_rule.condition.call(user_proxy.user, object)
      # If the inclusion has a condition, check that and skip this
      # included rule if it's not valid.
      next
    end

    if included_rule.block
      translated_object = included_rule.block.call(object)
    else
      translated_object = object
    end

    rule = @group.all_defined_rules[included_rule.key]
    if rule.nil?
      raise Error, "No defined rule with key #{included_rule.key} is available for #{self.path}"
    end

    unless rule.required_object_types.empty? || rule.required_object_types.include?(translated_object.class.name)
      raise InvalidObjectError, "The #{translated_object.class.name} object provided to included rule (#{rule.key}) for #{self.path} was not valid. Valid object types are: #{rule.required_object_types.join(', ')}"
    end

    rule_execution = RuleExecution.new(rule, user_proxy.user, translated_object)
    unless rule_execution.satisfied?
      return rule_execution
    end
  end
  nil
end

#first_unsatisfied_rule(user_proxy, object) ⇒ Checken::Rule, false

Check all the rules for this permission and ensure they are compliant.

Parameters:

Returns:



250
251
252
253
254
255
256
257
258
# File 'lib/checken/permission.rb', line 250

def first_unsatisfied_rule(user_proxy, object)
  self.rules.values.each do |rule|
    rule_execution = RuleExecution.new(rule, user_proxy.user, object)
    unless rule_execution.satisfied?
      return rule_execution
    end
  end
  nil
end

#include_rule(key_or_existing_rule, options = {}, &block) ⇒ Checken::Rule

Add a new rule to this permission

Parameters:

  • key (String)

Returns:



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/checken/permission.rb', line 173

def include_rule(key_or_existing_rule, options = {}, &block)
  if key_or_existing_rule.is_a?(IncludedRule)
    key = key_or_existing_rule.key
    included_rule = key_or_existing_rule
  else
    key = key_or_existing_rule.to_sym
    included_rule = nil
  end

  if included_rules[key].nil?
    included_rule ||= begin
      new_rule = IncludedRule.new(key, &block)
      new_rule.condition = options[:if]
      new_rule
    end
    included_rules[key] = included_rule
  else
    raise Error, "Rule with key '#{key}' already been included on this permission"
  end
end

#included_rulesHash

Return a hash of all configured included rules

Returns:

  • (Hash)


165
166
167
# File 'lib/checken/permission.rb', line 165

def included_rules
  @included_rules ||= {}
end

#remove_all_contextsInteger

Remove all context from this permission

Returns:

  • (Integer)


211
212
213
214
215
# File 'lib/checken/permission.rb', line 211

def remove_all_contexts
  previous_size = @contexts.size
  @contexts = []
  previous_size
end

#rulesHash

Return a hash of all configured rules

Returns:

  • (Hash)


144
145
146
# File 'lib/checken/permission.rb', line 144

def rules
  @rules ||= {}
end

#update_schemaObject



307
308
309
310
311
312
313
314
315
316
317
318
319
# File 'lib/checken/permission.rb', line 307

def update_schema
  return if path.nil?

  group.schema.update_schema(
    {
      path_with_namespace => {
        type: :permission,
        description: description,
        group: group.path
      }
    }
  )
end