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
@@ -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
|