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
@@ -16,6 +16,8 @@ module Eaco
|
|
16
16
|
#
|
17
17
|
class User < Eaco::Designator
|
18
18
|
##
|
19
|
+
# This {Designator} description
|
20
|
+
#
|
19
21
|
# @return [String] the {User}'s name.
|
20
22
|
#
|
21
23
|
def describe(*)
|
@@ -23,6 +25,8 @@ module Eaco
|
|
23
25
|
end
|
24
26
|
|
25
27
|
##
|
28
|
+
# {User}s matching this designator.
|
29
|
+
#
|
26
30
|
# @return [Array] this very {User} wrapped in an +Array+.
|
27
31
|
#
|
28
32
|
def resolve
|
data/lib/eaco/cucumber/world.rb
CHANGED
@@ -132,7 +132,7 @@ module Eaco
|
|
132
132
|
class World
|
133
133
|
|
134
134
|
##
|
135
|
-
#
|
135
|
+
# Sets up the World:
|
136
136
|
#
|
137
137
|
# * Connect to ActiveRecord
|
138
138
|
#
|
@@ -141,7 +141,117 @@ module Eaco
|
|
141
141
|
end
|
142
142
|
|
143
143
|
##
|
144
|
-
#
|
144
|
+
# Authorizes model with the given {DSL}
|
145
|
+
#
|
146
|
+
# @param name [String] the model name
|
147
|
+
# @param definition [String] the {DSL} code
|
148
|
+
# @see {#find_model}
|
149
|
+
#
|
150
|
+
# @return [void]
|
151
|
+
#
|
152
|
+
def authorize_model(name, definition)
|
153
|
+
model = find_model(name)
|
154
|
+
|
155
|
+
eval_dsl definition, model
|
156
|
+
end
|
157
|
+
|
158
|
+
##
|
159
|
+
# Registers and persists an {Actor} instance with the given +name+.
|
160
|
+
#
|
161
|
+
# @param model [String] the {Actor} model name
|
162
|
+
# @param name [String] the {Actor} instance name
|
163
|
+
# @param options [Boolean] the only supported one is +admin+, and
|
164
|
+
# specifies whether this {Actor} an admin
|
165
|
+
#
|
166
|
+
# @return [Actor] the newly created {Actor} instance.
|
167
|
+
#
|
168
|
+
def register_actor(model, name, options = {})
|
169
|
+
actor_model = find_model(model)
|
170
|
+
|
171
|
+
actors[name] = actor_model.new.tap do |actor|
|
172
|
+
actor.name = name
|
173
|
+
actor.admin = options.fetch(:admin, false)
|
174
|
+
actor.save!
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
##
|
179
|
+
# Fetches an {Actor} instance by name.
|
180
|
+
#
|
181
|
+
# @param name [String] the actor name
|
182
|
+
# @return [Actor] the registered actor name
|
183
|
+
# @raise [RuntimeError] if the actor is not found in the registry
|
184
|
+
#
|
185
|
+
def fetch_actor(name)
|
186
|
+
actors.fetch(name)
|
187
|
+
rescue KeyError
|
188
|
+
# :nocov:
|
189
|
+
raise "Actor '#{name}' not found in registry"
|
190
|
+
# :nocov:
|
191
|
+
end
|
192
|
+
|
193
|
+
##
|
194
|
+
# Registers and persists {Resource} instance with the given name.
|
195
|
+
#
|
196
|
+
# @param model [String] the {Resource} model name
|
197
|
+
# @param name [String] the {Resource} name
|
198
|
+
#
|
199
|
+
# @return [Resource] the newly instantiated {Resource}
|
200
|
+
#
|
201
|
+
def register_resource(model, name)
|
202
|
+
resource_model = find_model(model)
|
203
|
+
|
204
|
+
resource = resource_model.new.tap do |resource|
|
205
|
+
resource.name = name
|
206
|
+
resource.save!
|
207
|
+
end
|
208
|
+
|
209
|
+
resources[model] ||= {}
|
210
|
+
resources[model][name] = resource
|
211
|
+
end
|
212
|
+
|
213
|
+
##
|
214
|
+
# Fetches a {Resource} instance by name.
|
215
|
+
#
|
216
|
+
# @param model [String] the {Resource} model name
|
217
|
+
# @param name [String] the {Resource} name
|
218
|
+
#
|
219
|
+
def fetch_resource(model, name)
|
220
|
+
resources.fetch(model).fetch(name)
|
221
|
+
rescue KeyError
|
222
|
+
# :nocov:
|
223
|
+
raise "Resource #{model} '#{resource}' not found in registry"
|
224
|
+
# :nocov:
|
225
|
+
end
|
226
|
+
|
227
|
+
##
|
228
|
+
# All registered {Actor} instances.
|
229
|
+
#
|
230
|
+
# @return [Hash] actors keyed by name
|
231
|
+
#
|
232
|
+
def actors
|
233
|
+
@actors ||= {}
|
234
|
+
end
|
235
|
+
|
236
|
+
##
|
237
|
+
# All registered {Resource} instances.
|
238
|
+
#
|
239
|
+
# @return [Hash] resources keyed by model name with +Hash+es
|
240
|
+
# as values keyed by resource name.
|
241
|
+
#
|
242
|
+
def resources
|
243
|
+
@resources ||= {}
|
244
|
+
end
|
245
|
+
|
246
|
+
##
|
247
|
+
# Returns a model in the {ActiveRecord} namespace.
|
248
|
+
#
|
249
|
+
# Example:
|
250
|
+
#
|
251
|
+
# World.find_model('Document')
|
252
|
+
#
|
253
|
+
# @param model_name [String] the model name
|
254
|
+
# @return [Class]
|
145
255
|
#
|
146
256
|
def find_model(model_name)
|
147
257
|
Eaco::Cucumber::ActiveRecord.const_get(model_name)
|
@@ -152,13 +262,13 @@ module Eaco
|
|
152
262
|
# +$MODEL+ string with the given model name.
|
153
263
|
#
|
154
264
|
# @param code [String] the DSL code to eval
|
155
|
-
# @param model [Class] the model name to substitute
|
265
|
+
# @param model [Class] the model name to substitute (optional)
|
156
266
|
#
|
157
267
|
# @return [void]
|
158
268
|
#
|
159
|
-
def eval_dsl(code, model)
|
269
|
+
def eval_dsl(code, model = nil)
|
160
270
|
# Sub in place to print final code when running cucumber
|
161
|
-
code.sub! '$MODEL', model.name
|
271
|
+
code.sub! '$MODEL', model.name if model
|
162
272
|
Eaco.eval! code, '(feature)'
|
163
273
|
end
|
164
274
|
end
|
data/lib/eaco/designator.rb
CHANGED
@@ -63,7 +63,7 @@ module Eaco
|
|
63
63
|
# @return [Array] resolved actors, application-dependant.
|
64
64
|
#
|
65
65
|
def resolve(designators)
|
66
|
-
Array.new(designators||[]).inject(
|
66
|
+
Array.new(designators||[]).inject(Set.new) {|ret, d| ret.merge parse(d).resolve}
|
67
67
|
end
|
68
68
|
|
69
69
|
##
|
@@ -108,7 +108,9 @@ module Eaco
|
|
108
108
|
# @see Actor
|
109
109
|
#
|
110
110
|
def harvest(actor)
|
111
|
-
|
111
|
+
list = actor.send(@method)
|
112
|
+
list = list.to_a if list.respond_to?(:to_a)
|
113
|
+
Array.new([list]).flatten.map! {|value| new(value) }
|
112
114
|
end
|
113
115
|
|
114
116
|
##
|
@@ -167,6 +169,9 @@ module Eaco
|
|
167
169
|
# in a typeahead menu, for your Enterprise authorization management
|
168
170
|
# UI... :-)
|
169
171
|
#
|
172
|
+
# @param query [String] the query to search against
|
173
|
+
# @return [Enumerable] application {Actor}s collection
|
174
|
+
#
|
170
175
|
# @raise [NotImplementedError]
|
171
176
|
#
|
172
177
|
def search(query)
|
data/lib/eaco/dsl.rb
CHANGED
@@ -19,6 +19,10 @@ module Eaco
|
|
19
19
|
##
|
20
20
|
# Entry point for the {Resource} authorization definition.
|
21
21
|
#
|
22
|
+
# @param resource_class [Class] the application resource class
|
23
|
+
# @param options [Hash] options passed to {DSL::Resource} and
|
24
|
+
# and {DSL::ACL}.
|
25
|
+
#
|
22
26
|
# @see DSL::Resource
|
23
27
|
# @see DSL::ACL
|
24
28
|
#
|
@@ -28,7 +32,11 @@ module Eaco
|
|
28
32
|
end
|
29
33
|
|
30
34
|
##
|
31
|
-
# Entry point for
|
35
|
+
# Entry point for an {Actor} definition.
|
36
|
+
#
|
37
|
+
# @param actor_class [Class] the application actor class
|
38
|
+
# @param options [Hash] currently unused
|
39
|
+
# @param block [Proc] the DSL code to eval
|
32
40
|
#
|
33
41
|
# @see DSL::Actor
|
34
42
|
#
|
data/lib/eaco/dsl/acl.rb
CHANGED
@@ -50,7 +50,7 @@ module Eaco
|
|
50
50
|
#
|
51
51
|
def define_acl_subclass
|
52
52
|
target_eval do
|
53
|
-
remove_const(:ACL) if
|
53
|
+
remove_const(:ACL) if constants.include?(:ACL)
|
54
54
|
|
55
55
|
Class.new(Eaco::ACL).tap do |acl_class|
|
56
56
|
define_singleton_method(:acl) { acl_class }
|
@@ -97,7 +97,7 @@ module Eaco
|
|
97
97
|
target.send(:include, adapter)
|
98
98
|
install_authorized_collection_strategy
|
99
99
|
|
100
|
-
elsif target.
|
100
|
+
elsif (target.instance_methods & [:acl, :acl=]).size != 2
|
101
101
|
raise Malformed, <<-EOF
|
102
102
|
Don't know how to persist ACLs using <#{target}>'s ORM
|
103
103
|
(identified as <#{orm}>). Please define an `acl' instance
|
data/lib/eaco/dsl/actor.rb
CHANGED
@@ -21,7 +21,7 @@ module Eaco
|
|
21
21
|
autoload :Designators, 'eaco/dsl/actor/designators'
|
22
22
|
|
23
23
|
##
|
24
|
-
#
|
24
|
+
# Makes an application model a valid {Eaco::Actor}.
|
25
25
|
#
|
26
26
|
# @see Eaco::Actor
|
27
27
|
#
|
@@ -79,7 +79,7 @@ module Eaco
|
|
79
79
|
|
80
80
|
##
|
81
81
|
# Defines the boolean logic that determines whether an {Actor} is an
|
82
|
-
# admin. Usually you'll have an +admin
|
82
|
+
# admin. Usually you'll have an +admin?+ method on your model, that you
|
83
83
|
# can call from here. Or, feel free to just return +false+ to disable
|
84
84
|
# this functionality.
|
85
85
|
#
|
@@ -91,9 +91,12 @@ module Eaco
|
|
91
91
|
# end
|
92
92
|
# end
|
93
93
|
#
|
94
|
+
# @param block [Proc]
|
95
|
+
# @return [void]
|
96
|
+
#
|
94
97
|
def admin(&block)
|
95
98
|
target_eval do
|
96
|
-
@
|
99
|
+
@_admin_logic = block
|
97
100
|
end
|
98
101
|
end
|
99
102
|
|
data/lib/eaco/dsl/base.rb
CHANGED
@@ -32,6 +32,8 @@ module Eaco
|
|
32
32
|
attr_reader :options
|
33
33
|
|
34
34
|
##
|
35
|
+
# Sets up the DSL instance target class and the options.
|
36
|
+
#
|
35
37
|
# @param target [Class]
|
36
38
|
# @param options [Hash]
|
37
39
|
#
|
@@ -43,6 +45,9 @@ module Eaco
|
|
43
45
|
##
|
44
46
|
# Evaluates the given block in the context of the target class
|
45
47
|
#
|
48
|
+
# @param block [Proc]
|
49
|
+
# @return [void]
|
50
|
+
#
|
46
51
|
def target_eval(&block)
|
47
52
|
target.instance_eval(&block)
|
48
53
|
end
|
data/lib/eaco/error.rb
CHANGED
@@ -5,7 +5,16 @@ module Eaco
|
|
5
5
|
#
|
6
6
|
class Error < StandardError
|
7
7
|
# As we make use of heredoc for long error messages, squeeze subsequent
|
8
|
-
# spaces and remove newlines.
|
8
|
+
# spaces and remove newlines. If the message looks like an internal error
|
9
|
+
# though, newlines are preserved.
|
10
|
+
#
|
11
|
+
# Example:
|
12
|
+
#
|
13
|
+
# raise Eaco::Error, <<-EOF
|
14
|
+
# Some fancy message
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# @param message [String]
|
9
18
|
#
|
10
19
|
def initialize(message)
|
11
20
|
unless message =~ %r{EACO.+Error}
|
data/lib/eaco/rake.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'eaco/coverage'
|
2
|
+
|
1
3
|
module Eaco
|
2
4
|
module Rake
|
3
5
|
|
@@ -30,12 +32,14 @@ module Eaco
|
|
30
32
|
task :default do
|
31
33
|
run_specs
|
32
34
|
run_cucumber
|
35
|
+
output_coverage
|
33
36
|
end
|
34
37
|
|
35
38
|
elsif running_in_travis?
|
36
39
|
task :default do
|
37
40
|
run_specs
|
38
41
|
run_cucumber
|
42
|
+
report_coverage
|
39
43
|
generate_documentation
|
40
44
|
end
|
41
45
|
|
@@ -114,6 +118,25 @@ module Eaco
|
|
114
118
|
::Rake::Task[task].invoke
|
115
119
|
end
|
116
120
|
|
121
|
+
##
|
122
|
+
# Reports coverage data
|
123
|
+
#
|
124
|
+
# @return [void]
|
125
|
+
#
|
126
|
+
def report_coverage
|
127
|
+
Eaco::Coverage.report!
|
128
|
+
end
|
129
|
+
|
130
|
+
##
|
131
|
+
# Formats code coverage results and prints a summary
|
132
|
+
#
|
133
|
+
# @return [void]
|
134
|
+
#
|
135
|
+
def output_coverage
|
136
|
+
summary = Eaco::Coverage.format!
|
137
|
+
croak summary
|
138
|
+
end
|
139
|
+
|
117
140
|
##
|
118
141
|
# Fancily logs the given +msg+ to +$stderr+.
|
119
142
|
#
|
@@ -142,8 +165,8 @@ module Eaco
|
|
142
165
|
# @return [String]
|
143
166
|
#
|
144
167
|
def with_appraisal(msg)
|
145
|
-
if
|
146
|
-
msg = "%s \033[1;31m[%s]" % [msg,
|
168
|
+
if gemfile
|
169
|
+
msg = "%s \033[1;31m[%s]" % [msg, gemfile]
|
147
170
|
end
|
148
171
|
|
149
172
|
return msg
|
@@ -166,14 +189,11 @@ module Eaco
|
|
166
189
|
end
|
167
190
|
|
168
191
|
##
|
169
|
-
# @
|
192
|
+
# @see {Rake::Utils.gemfile}
|
193
|
+
# @private
|
170
194
|
#
|
171
|
-
def
|
172
|
-
|
173
|
-
|
174
|
-
gemfile = ENV['BUNDLE_GEMFILE']
|
175
|
-
|
176
|
-
File.basename(gemfile, '.*') if gemfile
|
195
|
+
def gemfile
|
196
|
+
Rake::Utils.gemfile
|
177
197
|
end
|
178
198
|
|
179
199
|
##
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Eaco
|
2
|
+
module Rake
|
3
|
+
|
4
|
+
##
|
5
|
+
# Assorted utilities.
|
6
|
+
#
|
7
|
+
module Utils
|
8
|
+
extend self
|
9
|
+
|
10
|
+
##
|
11
|
+
# Captures the stdout emitted by the given +block+
|
12
|
+
#
|
13
|
+
# @param block [Proc]
|
14
|
+
# @return [String] the captured output
|
15
|
+
#
|
16
|
+
def capture_stdout(&block)
|
17
|
+
stdout, string = $stdout, StringIO.new
|
18
|
+
$stdout = string
|
19
|
+
|
20
|
+
yield
|
21
|
+
|
22
|
+
string.tap(&:rewind).read
|
23
|
+
ensure
|
24
|
+
$stdout = stdout
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# @return [String] the current gemfile name
|
29
|
+
#
|
30
|
+
def gemfile
|
31
|
+
gemfile = ENV['BUNDLE_GEMFILE']
|
32
|
+
|
33
|
+
File.basename(gemfile, '.*') if gemfile
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
data/lib/eaco/version.rb
CHANGED
data/spec/eaco/acl_spec.rb
CHANGED
@@ -31,6 +31,24 @@ RSpec.describe Eaco::ACL do
|
|
31
31
|
it { expect(subject).to be_a(described_class) }
|
32
32
|
it { expect(subject).to include({designator => :reader}) }
|
33
33
|
end
|
34
|
+
|
35
|
+
context 'when looking up designators' do
|
36
|
+
after { acl.add(:reader, :foo, 1) }
|
37
|
+
|
38
|
+
let(:acl) { described_class.new }
|
39
|
+
|
40
|
+
it { expect(Eaco::Designator).to receive(:make).with(:foo, 1) }
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'when giving rubbish' do
|
44
|
+
subject { acl.add(:reader, 'rubbish') }
|
45
|
+
|
46
|
+
let(:acl) { described_class.new }
|
47
|
+
|
48
|
+
it { expect { subject }.to \
|
49
|
+
raise_error(Eaco::Error).
|
50
|
+
with_message(/Cannot infer designator from "rubbish"/) }
|
51
|
+
end
|
34
52
|
end
|
35
53
|
|
36
54
|
describe '#del' do
|
@@ -144,4 +162,20 @@ RSpec.describe Eaco::ACL do
|
|
144
162
|
end
|
145
163
|
end
|
146
164
|
|
165
|
+
describe '#inspect' do
|
166
|
+
let(:acl) { described_class.new('foo' => :bar) }
|
167
|
+
|
168
|
+
subject { acl.inspect }
|
169
|
+
|
170
|
+
it { expect(subject).to eq('#<Eaco::ACL: {"foo"=>:bar}>') }
|
171
|
+
end
|
172
|
+
|
173
|
+
describe '#pretty_inspect' do
|
174
|
+
let(:acl) { described_class.new('foo' => :bar) }
|
175
|
+
|
176
|
+
subject { acl.pretty_inspect }
|
177
|
+
|
178
|
+
it { expect(subject).to eq("Eaco::ACL\n{\"foo\"=>:bar}\n") }
|
179
|
+
end
|
180
|
+
|
147
181
|
end
|