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