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.
- checksums.yaml +4 -4
- data/README.md +3 -3
- data/features/authorization_parse_error.feature +157 -0
- data/features/enterprise_authorization.feature +159 -0
- data/features/rails_integration.feature +1 -1
- data/features/role_based_authorization.feature +30 -7
- data/features/step_definitions/actor_steps.rb +29 -25
- data/features/step_definitions/enterprise_steps.rb +81 -0
- data/features/step_definitions/error_steps.rb +49 -0
- data/features/step_definitions/fixture_steps.rb +14 -0
- data/features/step_definitions/resource_steps.rb +16 -24
- data/features/support/env.rb +4 -2
- data/lib/eaco.rb +2 -0
- data/lib/eaco/actor.rb +4 -3
- data/lib/eaco/adapters/active_record.rb +1 -1
- data/lib/eaco/adapters/active_record/compatibility.rb +19 -14
- data/lib/eaco/adapters/active_record/compatibility/scoped.rb +25 -0
- data/lib/eaco/adapters/active_record/compatibility/v40.rb +11 -3
- data/lib/eaco/adapters/active_record/compatibility/v41.rb +14 -3
- data/lib/eaco/adapters/active_record/compatibility/v42.rb +10 -2
- data/lib/eaco/controller.rb +16 -3
- data/lib/eaco/coverage.rb +83 -0
- data/lib/eaco/cucumber/active_record.rb +13 -18
- data/lib/eaco/cucumber/active_record/department.rb +4 -0
- data/lib/eaco/cucumber/active_record/position.rb +2 -0
- data/lib/eaco/cucumber/active_record/schema.rb +20 -2
- data/lib/eaco/cucumber/active_record/user.rb +9 -0
- data/lib/eaco/cucumber/active_record/user/designators.rb +4 -1
- data/lib/eaco/cucumber/active_record/user/designators/authenticated.rb +54 -0
- data/lib/eaco/cucumber/active_record/user/designators/department.rb +58 -0
- data/lib/eaco/cucumber/active_record/user/designators/position.rb +53 -0
- data/lib/eaco/cucumber/active_record/user/designators/user.rb +4 -0
- data/lib/eaco/cucumber/world.rb +115 -5
- data/lib/eaco/designator.rb +7 -2
- data/lib/eaco/dsl.rb +9 -1
- data/lib/eaco/dsl/acl.rb +2 -2
- data/lib/eaco/dsl/actor.rb +6 -3
- data/lib/eaco/dsl/base.rb +5 -0
- data/lib/eaco/error.rb +10 -1
- data/lib/eaco/rake.rb +1 -0
- data/lib/eaco/rake/default_task.rb +29 -9
- data/lib/eaco/rake/utils.rb +38 -0
- data/lib/eaco/version.rb +1 -1
- data/spec/eaco/acl_spec.rb +34 -0
- data/spec/spec_helper.rb +3 -3
- 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
|
-
|
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 |
|
8
|
-
|
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
|
13
|
-
|
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
|
-
|
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
|
-
|
24
|
-
|
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,
|
29
|
-
actor =
|
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|
|
25
|
+
resource_names = resource_names.split(/,\s*/)
|
26
|
+
resources = resource_names.map {|name| fetch_resource(resource_model, name)}
|
33
27
|
|
34
|
-
|
35
|
-
accessible =
|
28
|
+
resource_model = find_model(resource_model)
|
29
|
+
accessible = resource_model.accessible_by(actor)
|
36
30
|
|
37
|
-
|
38
|
-
raise "Expected to have access to #{resources} but found only #{accessible}"
|
39
|
-
end
|
31
|
+
expect(accessible).to match_array(resources)
|
40
32
|
end
|
data/features/support/env.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
require 'bundler/setup'
|
2
2
|
require 'byebug'
|
3
3
|
|
4
|
-
require '
|
5
|
-
|
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
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
|
8
|
-
# the actor instance has, and the
|
9
|
-
#
|
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
|
@@ -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
|
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 [
|
31
|
+
# @return [void]
|
30
32
|
#
|
31
33
|
def check!
|
32
|
-
|
33
|
-
|
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
|
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 =
|
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
|
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
|
-
|
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' :
|
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
|
-
# @
|
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
|
7
|
+
# Rails 4.1 support module.
|
8
8
|
#
|
9
|
-
# Magically, the 4.0 hacks work on 4.1.
|
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
|
-
|
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
|
7
|
+
# Rails 4.2 support module.
|
8
8
|
#
|
9
|
-
#
|
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
|