eaco 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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