eaco 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -3
  3. data/features/authorization_parse_error.feature +157 -0
  4. data/features/enterprise_authorization.feature +159 -0
  5. data/features/rails_integration.feature +1 -1
  6. data/features/role_based_authorization.feature +30 -7
  7. data/features/step_definitions/actor_steps.rb +29 -25
  8. data/features/step_definitions/enterprise_steps.rb +81 -0
  9. data/features/step_definitions/error_steps.rb +49 -0
  10. data/features/step_definitions/fixture_steps.rb +14 -0
  11. data/features/step_definitions/resource_steps.rb +16 -24
  12. data/features/support/env.rb +4 -2
  13. data/lib/eaco.rb +2 -0
  14. data/lib/eaco/actor.rb +4 -3
  15. data/lib/eaco/adapters/active_record.rb +1 -1
  16. data/lib/eaco/adapters/active_record/compatibility.rb +19 -14
  17. data/lib/eaco/adapters/active_record/compatibility/scoped.rb +25 -0
  18. data/lib/eaco/adapters/active_record/compatibility/v40.rb +11 -3
  19. data/lib/eaco/adapters/active_record/compatibility/v41.rb +14 -3
  20. data/lib/eaco/adapters/active_record/compatibility/v42.rb +10 -2
  21. data/lib/eaco/controller.rb +16 -3
  22. data/lib/eaco/coverage.rb +83 -0
  23. data/lib/eaco/cucumber/active_record.rb +13 -18
  24. data/lib/eaco/cucumber/active_record/department.rb +4 -0
  25. data/lib/eaco/cucumber/active_record/position.rb +2 -0
  26. data/lib/eaco/cucumber/active_record/schema.rb +20 -2
  27. data/lib/eaco/cucumber/active_record/user.rb +9 -0
  28. data/lib/eaco/cucumber/active_record/user/designators.rb +4 -1
  29. data/lib/eaco/cucumber/active_record/user/designators/authenticated.rb +54 -0
  30. data/lib/eaco/cucumber/active_record/user/designators/department.rb +58 -0
  31. data/lib/eaco/cucumber/active_record/user/designators/position.rb +53 -0
  32. data/lib/eaco/cucumber/active_record/user/designators/user.rb +4 -0
  33. data/lib/eaco/cucumber/world.rb +115 -5
  34. data/lib/eaco/designator.rb +7 -2
  35. data/lib/eaco/dsl.rb +9 -1
  36. data/lib/eaco/dsl/acl.rb +2 -2
  37. data/lib/eaco/dsl/actor.rb +6 -3
  38. data/lib/eaco/dsl/base.rb +5 -0
  39. data/lib/eaco/error.rb +10 -1
  40. data/lib/eaco/rake.rb +1 -0
  41. data/lib/eaco/rake/default_task.rb +29 -9
  42. data/lib/eaco/rake/utils.rb +38 -0
  43. data/lib/eaco/version.rb +1 -1
  44. data/spec/eaco/acl_spec.rb +34 -0
  45. data/spec/spec_helper.rb +3 -3
  46. metadata +18 -2
@@ -0,0 +1,81 @@
1
+ When(/I am "(.+?)"$/) do |user_name|
2
+ model = find_model('User')
3
+
4
+ @current_user = model.where(name: user_name).first!
5
+ end
6
+
7
+ Then(/I can (\w+) the Documents? "(.+?)" being a (\w+)$/) do |permission, document_names, role|
8
+ model = find_model('Document')
9
+ names = document_names.split(/,\s*/)
10
+ documents = model.where(name: names)
11
+
12
+ documents.each do |document|
13
+ expect(@current_user.can?(permission, document)).to eq(true)
14
+ expect(document.role_of(@current_user)).to eq(role.intern)
15
+ end
16
+ end
17
+
18
+ Then(/I can not (\w+) the Documents? "(.+?)" *(?:being a (\w+))?$/) do |permission, document_names, role|
19
+ model = find_model('Document')
20
+ names = document_names.split(/,\s*/)
21
+ documents = model.where(name: names)
22
+
23
+ documents.each do |document|
24
+ expect(@current_user.cannot?(permission, document)).to eq(true)
25
+ expect(document.role_of(@current_user)).to eq(role ? role.intern : nil)
26
+ end
27
+ end
28
+
29
+ When(/I ask for Documents I can access, I get/) do |table|
30
+ model = find_model('Document')
31
+ names = table.raw.flatten
32
+
33
+ expect(model.accessible_by(@current_user).map(&:name)).to match_array(names)
34
+ end
35
+
36
+ When(/I make a Designator with "(\w+)" and "(.+?)"/) do |type, value|
37
+ @designator = Eaco::Designator.make(type, value)
38
+ end
39
+
40
+ When(/I parse the Designator "(.+?)"/) do |text|
41
+ @designator = Eaco::Designator.parse(text)
42
+ end
43
+
44
+ Then(/it should describe itself as "(.+?)"/) do |description|
45
+ expect(@designator.describe).to eq(description)
46
+ end
47
+
48
+ Then(/it should have a label of "(.+?)"/) do |label|
49
+ expect(@designator.label).to eq(label)
50
+ end
51
+
52
+ Then(/it should resolve itself to/) do |table|
53
+ names = table.raw.flatten
54
+
55
+ expect(@designator.resolve.map(&:name)).to match_array(names)
56
+ end
57
+
58
+ When(/I have the following designators/) do |table|
59
+ @designators = table.raw.flatten
60
+ end
61
+
62
+ Then(/they should resolve to/) do |table|
63
+ resolved = Eaco::Designator.resolve(@designators)
64
+ names = table.raw.flatten
65
+
66
+ expect(resolved.map(&:name)).to match_array(names)
67
+ end
68
+
69
+ When(/I ask the Document the list of roles and labels/) do
70
+ model = find_model('Document')
71
+
72
+ @roles_labels = model.roles_with_labels
73
+ end
74
+
75
+ Then(/I should get the following roles and labels/) do |table|
76
+ expected = table.raw.map do |role, label|
77
+ [role.to_sym, label]
78
+ end
79
+
80
+ expect(@roles_labels.to_a).to match(expected)
81
+ end
@@ -0,0 +1,49 @@
1
+ When(/I have a wrong authorization definition (?:on model (.+?))? *such as/) do |model_name, definition|
2
+ @model_name = model_name
3
+ @definition = definition
4
+ end
5
+
6
+ Then(/I should receive a DSL error (.+?) saying/) do |error_class, error_contents|
7
+ error_class = error_class.constantize
8
+
9
+ model = find_model(@model_name) if @model_name
10
+
11
+ error_contents += '.+(feature)' unless error_contents.include?('\(feature\)')
12
+
13
+ expect { eval_dsl(@definition, model) }.to \
14
+ raise_error(error_class).
15
+ with_message(/#{error_contents}/m)
16
+ end
17
+
18
+ When(/I am using ActiveRecord (.+?)$/) do |version|
19
+ version = version.sub(/\D/, '') # FIXME
20
+
21
+ allow_any_instance_of(Eaco::Adapters::ActiveRecord::Compatibility).to \
22
+ receive(:active_record_version).and_return(version)
23
+ end
24
+
25
+ When(/I parse the invalid Designator "(.+?)"/) do |text|
26
+ @designator_text = text
27
+ end
28
+
29
+ Then(/I should receive a Designator error (.+?) saying/) do |error_class, error_contents|
30
+ error_class = error_class.constantize
31
+
32
+ expect { Eaco::Designator.parse(@designator_text) }.to \
33
+ raise_error(error_class).
34
+ with_message(/#{error_contents}/)
35
+ end
36
+
37
+ When(/I grant (\w+) access to (\w+) "(.+?)" as an invalid role (\w+) in quality of (\w+)/) do |actor_name, resource_model, resource_name, role_name, designator|
38
+ @actor = fetch_actor(actor_name)
39
+ @resource = fetch_resource(resource_model, resource_name)
40
+ @role_name, @designator = role_name, designator
41
+ end
42
+
43
+ Then(/I should receive a Resource error (.+?) saying/) do |error_class, error_contents|
44
+ error_class = error_class.constantize
45
+
46
+ expect { @resource.grant @role_name, @designator, @actor }.to \
47
+ raise_error(error_class).
48
+ with_message(/#{error_contents}/)
49
+ end
@@ -0,0 +1,14 @@
1
+ Given(/I have the following (\w+) records/) do |model_name, table|
2
+ model = find_model(model_name)
3
+
4
+ table.hashes.each do |attributes|
5
+ instance = model.new
6
+
7
+ if attributes.key?('acl')
8
+ attributes['acl'] = MultiJson.load(attributes['acl'])
9
+ end
10
+
11
+ instance.attributes = attributes
12
+ instance.save!
13
+ end
14
+ end
@@ -1,40 +1,32 @@
1
1
  When(/I have a (\w+) resource defined as/) do |model_name, resource_definition|
2
- @resource_model = find_model(model_name)
3
-
4
- eval_dsl resource_definition, @resource_model
2
+ authorize_model model_name, resource_definition
5
3
  end
6
4
 
7
- When(/I have a confidential \w+ named "([\w\s]+)"/) do |name|
8
- @resources ||= {}
9
- @resources[name] = @resource_model.new(name: name)
5
+ When(/I have a confidential (\w+) named "([\w\s]+)"/) do |model_name, resource_name|
6
+ register_resource model_name, resource_name
10
7
  end
11
8
 
12
- Then(/I should be able to set an ACL on it/) do
13
- instance = @resource_model.new
9
+ Then(/I should be able to set an ACL on the (\w+)/) do |model_name|
10
+ resource_model = find_model(model_name)
11
+ instance = resource_model.new
14
12
 
15
13
  instance.acl = {"foo" => :bar}
16
14
  instance.save!
17
- instance = @resource_model.find(instance.id)
18
15
 
19
- unless instance.acl == {"foo" => :bar}
20
- raise %[Expecting {"foo"=> :bar} as an ACL but found #{instance.acl.inspect}]
21
- end
16
+ instance = resource_model.find(instance.id)
22
17
 
23
- unless instance.acl.kind_of?(@resource_model.acl)
24
- raise "Expecting #{instance.acl.class} to be a #{@resource_model.acl}"
25
- end
18
+ expect(instance.acl).to eq({"foo" => :bar})
19
+ expect(instance.acl).to be_a(resource_model.acl)
26
20
  end
27
21
 
28
- Then(/(\w+) can see only "(.*?)" in the (\w+) authorized list/) do |actor_name, resource_names, model_name|
29
- actor = @actors[actor_name]
22
+ Then(/(\w+) can see (?:only)? *"(.*?)" in the (\w+) authorized list/) do |actor_name, resource_names, resource_model|
23
+ actor = fetch_actor(actor_name)
30
24
 
31
- resource_names = resource_names.split(',')
32
- resources = resource_names.map {|name| @resources.fetch(name)}
25
+ resource_names = resource_names.split(/,\s*/)
26
+ resources = resource_names.map {|name| fetch_resource(resource_model, name)}
33
27
 
34
- model = find_model(model_name)
35
- accessible = model.accessible_by(actor).to_a
28
+ resource_model = find_model(resource_model)
29
+ accessible = resource_model.accessible_by(actor)
36
30
 
37
- unless (accessible & resources) == resources
38
- raise "Expected to have access to #{resources} but found only #{accessible}"
39
- end
31
+ expect(accessible).to match_array(resources)
40
32
  end
@@ -1,12 +1,14 @@
1
1
  require 'bundler/setup'
2
2
  require 'byebug'
3
3
 
4
- require 'coveralls'
5
- Coveralls.wear!
4
+ require 'eaco/coverage'
5
+ Eaco::Coverage.start!
6
6
 
7
7
  require 'eaco'
8
8
  require 'eaco/cucumber'
9
9
 
10
+ require 'cucumber/rspec/doubles'
11
+
10
12
  ##
11
13
  # Create a whole new world.
12
14
  # @see {World}
data/lib/eaco.rb CHANGED
@@ -2,7 +2,9 @@ require 'eaco/error'
2
2
  require 'eaco/version'
3
3
 
4
4
  if defined? Rails
5
+ # :nocov:
5
6
  require 'eaco/railtie'
7
+ # :nocov:
6
8
  end
7
9
 
8
10
  require 'pathname'
data/lib/eaco/actor.rb CHANGED
@@ -4,12 +4,13 @@ module Eaco
4
4
  # An Actor is an entity whose access to Resources is discretionary,
5
5
  # depending on the Role this actor has in the ACL.
6
6
  #
7
- # The role of this +Actor+ is calculated from the +Designator+ that
8
- # the actor instance has, and the +ACL+ instance attached to the
9
- # +Resource+.
7
+ # The role of this +Actor+ is calculated from the {Designator} that
8
+ # the actor instance has, and the {ACL} instance attached to the
9
+ # {Resource}.
10
10
  #
11
11
  # @see ACL
12
12
  # @see Resource
13
+ # @see Resource.role_of
13
14
  # @see DSL::Actor
14
15
  #
15
16
  module Actor
@@ -38,7 +38,7 @@ module Eaco
38
38
  end
39
39
 
40
40
  unless column.type == :json || column.type == :jsonb
41
- raise Malformed, "The `acl` column on #{base} must be of the json type."
41
+ raise Malformed, "The `acl` column on #{base} must be of the jsonb type."
42
42
  end
43
43
  end
44
44
 
@@ -11,7 +11,11 @@ module Eaco
11
11
  autoload :V41, 'eaco/adapters/active_record/compatibility/v41.rb'
12
12
  autoload :V42, 'eaco/adapters/active_record/compatibility/v42.rb'
13
13
 
14
+ autoload :Scoped, 'eaco/adapters/active_record/compatibility/scoped.rb'
15
+
14
16
  ##
17
+ # Memoizes the given +model+ for later {#check!} calls.
18
+ #
15
19
  # @param model [ActiveRecord::Base] the model to check
16
20
  #
17
21
  def initialize(model)
@@ -19,21 +23,16 @@ module Eaco
19
23
  end
20
24
 
21
25
  ##
22
- # Checks whether this model is compatible.
23
- #
24
- # Looks up the {#support_module} and, if found, includes it in the
25
- # target model.
26
+ # Checks whether the target model is compatible.
27
+ # Looks up the {#support_module} and includes it.
26
28
  #
27
29
  # @see #support_module
28
30
  #
29
- # @return [nil]
31
+ # @return [void]
30
32
  #
31
33
  def check!
32
- mod = support_module
33
- return unless mod
34
- base.instance_eval { include mod }
35
-
36
- nil
34
+ layer = support_module
35
+ target.instance_eval { include layer }
37
36
  end
38
37
 
39
38
  private
@@ -41,7 +40,7 @@ module Eaco
41
40
  ##
42
41
  # @return [ActiveRecord::Base] associated with the model
43
42
  #
44
- def base
43
+ def target
45
44
  @model.base_class.superclass
46
45
  end
47
46
 
@@ -51,7 +50,7 @@ module Eaco
51
50
  # Example: "42" for 4.2
52
51
  #
53
52
  def active_record_version
54
- ver = base.parent::VERSION
53
+ ver = target.parent.const_get(:VERSION)
55
54
  [ver.const_get(:MAJOR), ver.const_get(:MINOR)].join
56
55
  end
57
56
 
@@ -59,12 +58,18 @@ module Eaco
59
58
  # Tries to look up the support module for the {#active_record_version}
60
59
  # in the {Compatibility} namespace.
61
60
  #
62
- # @return [Module] the support module or nil if not required.
61
+ # @return [Module] the support module
62
+ #
63
+ # @raise [Eaco::Error] if not found.
63
64
  #
64
65
  # @see check!
65
66
  #
66
67
  def support_module
67
- return unless self.class.const_defined?(support_module_name)
68
+ unless self.class.const_defined?(support_module_name)
69
+ raise Eaco::Error, <<-EOF
70
+ Unsupported Active Record version: #{active_record_version}
71
+ EOF
72
+ end
68
73
 
69
74
  self.class.const_get support_module_name
70
75
  end
@@ -0,0 +1,25 @@
1
+ module Eaco
2
+ module Adapters
3
+ module ActiveRecord
4
+ class Compatibility
5
+
6
+ ##
7
+ # Aliases `scoped` as `all` for ActiveRecord >= 4.1.
8
+ #
9
+ # TODO maybe is against Rails' practices to revive a
10
+ # dead API? It may be, comments accepted.
11
+ #
12
+ module Scoped
13
+
14
+ ##
15
+ # Just returns +ActiveRecord::Base.all+.
16
+ #
17
+ def scoped
18
+ all
19
+ end
20
+ end
21
+
22
+ end
23
+ end
24
+ end
25
+ end
@@ -13,6 +13,9 @@ module Eaco
13
13
  # and makes jsonb mimick itself as a json - for the rest of the AR
14
14
  # machinery to work intact.
15
15
  #
16
+ # @param base [Class] the +ActiveRecord+ model to mangle
17
+ # @return [void]
18
+ #
16
19
  def self.included(base)
17
20
  adapter = base.connection
18
21
 
@@ -31,17 +34,22 @@ module Eaco
31
34
  #
32
35
  module Column
33
36
  ##
34
- # Makes +sql_type+ return +json+ for +jsonb+ columns
37
+ # Makes +sql_type+ return +json+ for +jsonb+ columns. This is
38
+ # an hack to let the casting machinery in AR 4.0 keep working
39
+ # with the unsupported +jsonb+ type.
40
+ #
41
+ # @return [String] the SQL type.
35
42
  #
36
43
  def sql_type
37
44
  orig_type = super
38
- orig_type == 'jsonb' ? 'json' : type
45
+ orig_type == 'jsonb' ? 'json' : orig_type
39
46
  end
40
47
 
41
48
  ##
42
49
  # Makes +simplified_type+ return +json+ for +jsonb+ columns
43
50
  #
44
- # @return [Symbol]
51
+ # @param field_type [String] the database field type
52
+ # @return [Symbol] the simplified type
45
53
  #
46
54
  def simplified_type(field_type)
47
55
  if field_type == 'jsonb'
@@ -4,11 +4,22 @@ module Eaco
4
4
  class Compatibility
5
5
 
6
6
  ##
7
- # Rails 4.1 JSONB support module.
7
+ # Rails 4.1 support module.
8
8
  #
9
- # Magically, the 4.0 hacks work on 4.1. YAY!
9
+ # Magically, the 4.0 hacks work on 4.1. But on 4.1 we need the
10
+ # +.scoped+ API so we revive it through the {Scoped} module.
10
11
  #
11
- V41 = V40
12
+ # @see V40
13
+ # @see Scoped
14
+ #
15
+ module V41
16
+ extend ActiveSupport::Concern
17
+
18
+ included do
19
+ include V40
20
+ extend Scoped
21
+ end
22
+ end
12
23
 
13
24
  end
14
25
  end
@@ -4,11 +4,19 @@ module Eaco
4
4
  class Compatibility
5
5
 
6
6
  ##
7
- # Rails 4.2 JSONB support module.
7
+ # Rails 4.2 support module.
8
8
  #
9
- # Magically, this module is empty. YAY!
9
+ # JSONB works correctly, but we need +.scoped+ so we revive it through
10
+ # the {Scoped} support module.
11
+ #
12
+ # @see Scoped
10
13
  #
11
14
  module V42
15
+ extend ActiveSupport::Concern
16
+
17
+ included do
18
+ extend Scoped
19
+ end
12
20
  end
13
21
 
14
22
  end