eaco 0.5.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 +7 -0
- data/.gitignore +21 -0
- data/.rspec +2 -0
- data/.travis.yml +18 -0
- data/.yardopts +1 -0
- data/Appraisals +22 -0
- data/Gemfile +4 -0
- data/Guardfile +34 -0
- data/LICENSE.txt +23 -0
- data/README.md +225 -0
- data/Rakefile +26 -0
- data/eaco.gemspec +27 -0
- data/features/active_record.example.yml +8 -0
- data/features/active_record.travis.yml +7 -0
- data/features/rails_integration.feature +10 -0
- data/features/step_definitions/database.rb +7 -0
- data/features/step_definitions/resource_authorization.rb +15 -0
- data/features/support/env.rb +9 -0
- data/gemfiles/rails_3.2.gemfile +9 -0
- data/gemfiles/rails_4.0.gemfile +8 -0
- data/gemfiles/rails_4.1.gemfile +8 -0
- data/gemfiles/rails_4.2.gemfile +8 -0
- data/lib/eaco.rb +93 -0
- data/lib/eaco/acl.rb +206 -0
- data/lib/eaco/actor.rb +86 -0
- data/lib/eaco/adapters.rb +14 -0
- data/lib/eaco/adapters/active_record.rb +70 -0
- data/lib/eaco/adapters/active_record/compatibility.rb +83 -0
- data/lib/eaco/adapters/active_record/compatibility/v32.rb +27 -0
- data/lib/eaco/adapters/active_record/compatibility/v40.rb +59 -0
- data/lib/eaco/adapters/active_record/compatibility/v41.rb +16 -0
- data/lib/eaco/adapters/active_record/compatibility/v42.rb +17 -0
- data/lib/eaco/adapters/active_record/postgres_jsonb.rb +36 -0
- data/lib/eaco/adapters/couchrest_model.rb +37 -0
- data/lib/eaco/adapters/couchrest_model/couchdb_lucene.rb +71 -0
- data/lib/eaco/controller.rb +158 -0
- data/lib/eaco/cucumber.rb +11 -0
- data/lib/eaco/cucumber/active_record.rb +163 -0
- data/lib/eaco/cucumber/active_record/department.rb +19 -0
- data/lib/eaco/cucumber/active_record/document.rb +18 -0
- data/lib/eaco/cucumber/active_record/position.rb +21 -0
- data/lib/eaco/cucumber/active_record/schema.rb +36 -0
- data/lib/eaco/cucumber/active_record/user.rb +24 -0
- data/lib/eaco/cucumber/world.rb +136 -0
- data/lib/eaco/designator.rb +264 -0
- data/lib/eaco/dsl.rb +40 -0
- data/lib/eaco/dsl/acl.rb +163 -0
- data/lib/eaco/dsl/actor.rb +139 -0
- data/lib/eaco/dsl/actor/designators.rb +110 -0
- data/lib/eaco/dsl/base.rb +52 -0
- data/lib/eaco/dsl/resource.rb +129 -0
- data/lib/eaco/dsl/resource/permissions.rb +131 -0
- data/lib/eaco/error.rb +36 -0
- data/lib/eaco/railtie.rb +46 -0
- data/lib/eaco/rake.rb +10 -0
- data/lib/eaco/rake/default_task.rb +164 -0
- data/lib/eaco/resource.rb +234 -0
- data/lib/eaco/version.rb +7 -0
- data/spec/eaco/acl_spec.rb +147 -0
- data/spec/eaco/actor_spec.rb +13 -0
- data/spec/eaco/adapters/active_record/postgres_jsonb_spec.rb +9 -0
- data/spec/eaco/adapters/active_record_spec.rb +13 -0
- data/spec/eaco/adapters/couchrest_model/couchdb_lucene_spec.rb +9 -0
- data/spec/eaco/adapters/couchrest_model_spec.rb +9 -0
- data/spec/eaco/controller_spec.rb +12 -0
- data/spec/eaco/designator_spec.rb +25 -0
- data/spec/eaco/dsl/acl_spec.rb +9 -0
- data/spec/eaco/dsl/actor/designators_spec.rb +7 -0
- data/spec/eaco/dsl/actor_spec.rb +15 -0
- data/spec/eaco/dsl/resource/permissions_spec.rb +7 -0
- data/spec/eaco/dsl/resource_spec.rb +17 -0
- data/spec/eaco/error_spec.rb +9 -0
- data/spec/eaco/resource_spec.rb +31 -0
- data/spec/eaco_spec.rb +49 -0
- data/spec/spec_helper.rb +71 -0
- metadata +296 -0
@@ -0,0 +1,136 @@
|
|
1
|
+
module Eaco
|
2
|
+
module Cucumber
|
3
|
+
|
4
|
+
##
|
5
|
+
# The world in which scenarios are run. This is a story and an example,
|
6
|
+
# real-world data model that can be effectively protected by Eaco.
|
7
|
+
#
|
8
|
+
# But +before { some :art }+
|
9
|
+
#
|
10
|
+
# == AYANAMI REI
|
11
|
+
# __.-"..--,__
|
12
|
+
# __..---" | _| "-_\
|
13
|
+
# __.---" | V|::.-"-._D
|
14
|
+
# _--"".-.._ ,,::::::'"\/""'-:-:/
|
15
|
+
# _.-""::_:_:::::'-8b---" "'
|
16
|
+
# .-/ ::::< |\::::::"\
|
17
|
+
# \/:::/::::'\\ |:::b::\
|
18
|
+
# /|::/:::/::::-::b:%b:\|
|
19
|
+
# \/::::d:|8:::b:"%%%%%\
|
20
|
+
# |\:b:dP:d.:::%%%%%"""-,
|
21
|
+
# \:\.V-/ _\b%P_ / .-._
|
22
|
+
# '|T\ "%j d:::--\.( "-.
|
23
|
+
# ::d< -" d%|:::do%P"-:. "-,
|
24
|
+
# |:I _ /%%%o::o8P "\. "\
|
25
|
+
# \8b d%%%%%%P""-._ _ \::. \
|
26
|
+
# \%%8 _./Y%%P/ .::'-oMMo )
|
27
|
+
# H"'|V | A:::...:odMMMMMM( ./
|
28
|
+
# H /_.--"JMMMMbo:d##########b/
|
29
|
+
# .-'o dMMMMMMMMMMMMMMP""
|
30
|
+
# /" / YMMMMMMMMM|
|
31
|
+
# / . . "MMMMMMMM/
|
32
|
+
# :..::..:::.. MMMMMMM:|
|
33
|
+
# \:/ \::::::::JMMMP":/
|
34
|
+
# :Ao ':__.-'MMMP:::Y
|
35
|
+
# dMM"./:::::::::-.Y
|
36
|
+
# _|b::od8::/:YM::/
|
37
|
+
# I HMMMP::/:/"Y/"
|
38
|
+
# \'""' '':|
|
39
|
+
# | -::::\
|
40
|
+
# | :-._ '::\
|
41
|
+
# |,.| \ _:"o
|
42
|
+
# | d" / " \_:\.
|
43
|
+
# ".Y. \ \::\
|
44
|
+
# \ \ \ MM\:Y
|
45
|
+
# Y \ | MM \:b
|
46
|
+
# >\ Y .MM MM
|
47
|
+
# .IY L_ MP' MP
|
48
|
+
# | \:| JM JP
|
49
|
+
# | :\| MP MM
|
50
|
+
# | ::: JM' JP|
|
51
|
+
# | ':' JP JM |
|
52
|
+
# L : JP MP |
|
53
|
+
# 0 | Y JM |
|
54
|
+
# 0 | JP" |
|
55
|
+
# 0 | JP |
|
56
|
+
# m | JP #
|
57
|
+
# I | JM" Y
|
58
|
+
# l | MP :"
|
59
|
+
# |\ :- :|
|
60
|
+
# | | '.\ :|
|
61
|
+
# | | "| \ :|
|
62
|
+
# \ \ \ :|
|
63
|
+
# | | | \ :|
|
64
|
+
# | | | \ :|
|
65
|
+
# | \ \ | '.
|
66
|
+
# | |:\ | :|
|
67
|
+
# \ |::\..| :\
|
68
|
+
# ". /::::::' :||
|
69
|
+
# :|::/:::| /:\
|
70
|
+
# | \/::|: \' ::|
|
71
|
+
# | :::|| ::|
|
72
|
+
# | ::|| ::|
|
73
|
+
# | ::|| ::|
|
74
|
+
# | ::|| ::|
|
75
|
+
# | ': | .:|
|
76
|
+
# | : | :|
|
77
|
+
# | : | :|
|
78
|
+
# | :|| .:|
|
79
|
+
# | ::\ .:|
|
80
|
+
# | ::: .::|
|
81
|
+
# / ::| :::|
|
82
|
+
# __/ .::| ':|
|
83
|
+
# ...----"" ::/ ::
|
84
|
+
# /m_ AMm '/ .:::
|
85
|
+
# ""MmmMMM#mmMMMMMMM" .:::m
|
86
|
+
# """YMMM""""""P ':mMI
|
87
|
+
# _' _MMMM
|
88
|
+
# _.-" mm mMMMMMMMM"
|
89
|
+
# / MMMMMMM""
|
90
|
+
# mmmmmmMMMM"
|
91
|
+
# ch1x0r
|
92
|
+
#
|
93
|
+
# http://ascii.co.uk/art/anime
|
94
|
+
#
|
95
|
+
# = Scenario
|
96
|
+
#
|
97
|
+
# In this imaginary world we are N E R V, a Top Secret organization that
|
98
|
+
# handles very confidential documents. Some users can read them, some can
|
99
|
+
# edit them, and very few bosses can destroy them.
|
100
|
+
#
|
101
|
+
# The organization employs internal staff and employs consultants. Staff
|
102
|
+
# members have official positions in the organization hierarchy, and they
|
103
|
+
# belong to units within departments. They have the big picture.
|
104
|
+
#
|
105
|
+
# Consultants, on the other hand, come and go, and work on small parts of
|
106
|
+
# the documents, for specific purposes. They do not have the big picture.
|
107
|
+
#
|
108
|
+
# Departments own the documents, not people. Documents are of interest of
|
109
|
+
# departments, sometimes they should be accessed by the whole house, some
|
110
|
+
# other time only few selected users, some times two specific departments
|
111
|
+
# or some units.
|
112
|
+
#
|
113
|
+
# Either way, most of the time, access is granted to who owns a peculiar
|
114
|
+
# authority within the organization and not to a specific person. People
|
115
|
+
# may change, authorities and rules change less often.
|
116
|
+
#
|
117
|
+
# = Mapping Eaco concepts
|
118
|
+
#
|
119
|
+
# The +Document+ is a {Eaco::Resource}
|
120
|
+
#
|
121
|
+
# Each instance of a +Document+ has an {Eaco::ACL} +.acl+ attribute.
|
122
|
+
#
|
123
|
+
# The +:reader+, +:editor+ and +:owner+ are Roles on the Document
|
124
|
+
# resource, and each role is granted a Permission.
|
125
|
+
#
|
126
|
+
# The User is a {Eaco::Actor}.
|
127
|
+
#
|
128
|
+
# Having an user account is the {Eaco::Designator} of type +:user+.
|
129
|
+
# Occupying an official position is the Designator of type +:position+.
|
130
|
+
# Belonging to a department is the Designator of type +:department+
|
131
|
+
#
|
132
|
+
class World
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,264 @@
|
|
1
|
+
module Eaco
|
2
|
+
|
3
|
+
##
|
4
|
+
# A Designator characterizes an Actor.
|
5
|
+
#
|
6
|
+
# Example: an User Actor is uniquely identified by its numerical +id+, as
|
7
|
+
# such we can define an +user+ designator that designs User 42 as +user:42+.
|
8
|
+
#
|
9
|
+
# The same User also could belong to the group +frobber+, uniquely
|
10
|
+
# identified by its name. We can then define a +group+ designator that would
|
11
|
+
# design the same User as +group:frobber+.
|
12
|
+
#
|
13
|
+
# In ACLs designators are given roles, and the intersection between the
|
14
|
+
# designators of an Actor and the ones defined in the ACL gives the role of
|
15
|
+
# the Actor for the Resource that the ACL secures.
|
16
|
+
#
|
17
|
+
# Designators for actors are defined through the DSL, see {DSL::Actor}
|
18
|
+
#
|
19
|
+
# @see ACL
|
20
|
+
# @see Actor
|
21
|
+
#
|
22
|
+
class Designator < String
|
23
|
+
class << self
|
24
|
+
|
25
|
+
##
|
26
|
+
# Instantiate a designator of the given +type+ with the given +value+.
|
27
|
+
#
|
28
|
+
# Example:
|
29
|
+
#
|
30
|
+
# >> Designator.make('user', 42)
|
31
|
+
# => #<Designator(User) value:42>
|
32
|
+
#
|
33
|
+
# @param type [String] the designator type (e.g. +user+)
|
34
|
+
# @param value [String] the designator value. It will be stringified using +.to_s+.
|
35
|
+
#
|
36
|
+
# @return [Designator]
|
37
|
+
#
|
38
|
+
def make(type, value)
|
39
|
+
Eaco::DSL::Actor.find_designator(type).new(value)
|
40
|
+
end
|
41
|
+
|
42
|
+
##
|
43
|
+
# Parses a Designator string representation and instantiates a new
|
44
|
+
# Designator instance from it.
|
45
|
+
#
|
46
|
+
# >> Designator.parse('user:42')
|
47
|
+
# => #<Designator(User) value:42>
|
48
|
+
#
|
49
|
+
# @param string [String] the designator string representation.
|
50
|
+
#
|
51
|
+
# @return [Designator]
|
52
|
+
#
|
53
|
+
def parse(string)
|
54
|
+
return string if string.is_a?(Designator)
|
55
|
+
make(*string.split(':', 2))
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# Resolves one or more designators into the target actors.
|
60
|
+
#
|
61
|
+
# @param designators [Array] designator string representations.
|
62
|
+
#
|
63
|
+
# @return [Array] resolved actors, application-dependant.
|
64
|
+
#
|
65
|
+
def resolve(designators)
|
66
|
+
Array.new(designators||[]).inject([]) {|ret, d| ret.concat parse(d).resolve}
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# Sets up the designator implementation with the given options.
|
71
|
+
# Currently:
|
72
|
+
#
|
73
|
+
# * +:from+ - defines the method to call on the Actor to obtain the unique
|
74
|
+
# IDs for this Designator class.
|
75
|
+
#
|
76
|
+
# Example configuration:
|
77
|
+
#
|
78
|
+
# actor User do
|
79
|
+
# designators do
|
80
|
+
# user from: :id
|
81
|
+
# group from: :group_ids
|
82
|
+
# end
|
83
|
+
# end
|
84
|
+
#
|
85
|
+
# This method is called from the DSL.
|
86
|
+
#
|
87
|
+
# @see DSL::Actor::Designators
|
88
|
+
# @see DSL::Actor::Designators#define_designator
|
89
|
+
#
|
90
|
+
def configure!(options)
|
91
|
+
@method = options.fetch(:from)
|
92
|
+
self
|
93
|
+
|
94
|
+
rescue KeyError
|
95
|
+
raise Malformed, "The designator option :from is required"
|
96
|
+
end
|
97
|
+
|
98
|
+
##
|
99
|
+
# Harvests valid designators for the given Actor.
|
100
|
+
#
|
101
|
+
# It calls the +@method+ defined through the +:from+ option passed when
|
102
|
+
# configuring the designators (see {Designator.configure!}).
|
103
|
+
#
|
104
|
+
# @param actor [Actor]
|
105
|
+
#
|
106
|
+
# @return [Array] an array of Designator objects the Actor owns.
|
107
|
+
#
|
108
|
+
# @see Actor
|
109
|
+
#
|
110
|
+
def harvest(actor)
|
111
|
+
Array.new(actor.send(@method)||[]).map {|value| new(value) }
|
112
|
+
end
|
113
|
+
|
114
|
+
##
|
115
|
+
# Sets this Designator label to the given value.
|
116
|
+
#
|
117
|
+
# Example:
|
118
|
+
#
|
119
|
+
# class User::Designators::Group < Eaco::Designator
|
120
|
+
# label "Active Directory Group"
|
121
|
+
# end
|
122
|
+
#
|
123
|
+
# @param value [String] the designator label
|
124
|
+
#
|
125
|
+
# @return [String] the configured label
|
126
|
+
#
|
127
|
+
def label(value = nil)
|
128
|
+
@label = value if value
|
129
|
+
@label ||= designator_name
|
130
|
+
end
|
131
|
+
|
132
|
+
##
|
133
|
+
# Returns this class' demodulized name
|
134
|
+
#
|
135
|
+
def designator_name
|
136
|
+
self.name.split('::').last
|
137
|
+
end
|
138
|
+
|
139
|
+
##
|
140
|
+
# Returns the designator type.
|
141
|
+
#
|
142
|
+
# The type symbol is derived from the class name, on the other way
|
143
|
+
# around, the {DSL} looks up designator implementation classes from the
|
144
|
+
# designator type symbol.
|
145
|
+
#
|
146
|
+
# Example:
|
147
|
+
#
|
148
|
+
# >> User::Designators::Group.id
|
149
|
+
# => :group
|
150
|
+
#
|
151
|
+
# @return [Symbol]
|
152
|
+
#
|
153
|
+
# @see DSL::Actor::Designators#implementation_for
|
154
|
+
#
|
155
|
+
def id
|
156
|
+
@_id ||= self.designator_name.gsub(/([a-z])?([A-Z])/) do |x|
|
157
|
+
[$1, $2.downcase].compact.join '_'
|
158
|
+
end.intern
|
159
|
+
end
|
160
|
+
alias type id
|
161
|
+
|
162
|
+
##
|
163
|
+
# Searches designator definitions using the given query.
|
164
|
+
#
|
165
|
+
# To be implemented by derived classes. E.g. for a "User" designator
|
166
|
+
# this would return your own User instances that you may want to display
|
167
|
+
# in a typeahead menu, for your Enterprise authorization management
|
168
|
+
# UI... :-)
|
169
|
+
#
|
170
|
+
# @raise [NotImplementedError]
|
171
|
+
#
|
172
|
+
def search(query)
|
173
|
+
raise NotImplementedError
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
##
|
178
|
+
# Initializes the designator with the given value. The string
|
179
|
+
# representation is then calculated by concatenating the type and the
|
180
|
+
# given value.
|
181
|
+
#
|
182
|
+
# An optional instance can be attached, if you use to pass around
|
183
|
+
# designators in your app.
|
184
|
+
#
|
185
|
+
# @param value [String] the unique ID valid in this designator namespace
|
186
|
+
# @param instance [Actor] optional Actor instance
|
187
|
+
#
|
188
|
+
def initialize(value, instance = nil)
|
189
|
+
@value, @instance = value, instance
|
190
|
+
super([ self.class.id, value ].join(':'))
|
191
|
+
end
|
192
|
+
|
193
|
+
# This designator unique ID in the namespace of the designator type.
|
194
|
+
attr_reader :value
|
195
|
+
|
196
|
+
# The instance given to {Designator#initialize}
|
197
|
+
attr_reader :instance
|
198
|
+
|
199
|
+
##
|
200
|
+
# Should return an extended description for this designator. You can then
|
201
|
+
# use this to display it in your application.
|
202
|
+
#
|
203
|
+
# E.g. for an "User" designator this would be the user name, for a "Group"
|
204
|
+
# designator this would be the group name.
|
205
|
+
#
|
206
|
+
# @param style [Symbol] the description style. {#as_json} uses +:full+,
|
207
|
+
# but you are free to define whatever styles you do see fit.
|
208
|
+
#
|
209
|
+
# @return [String] the description
|
210
|
+
#
|
211
|
+
def describe(style = nil)
|
212
|
+
nil
|
213
|
+
end
|
214
|
+
|
215
|
+
##
|
216
|
+
# Translates this designator to concrete Actor instances in your
|
217
|
+
# application. To be implemented by derived classes.
|
218
|
+
#
|
219
|
+
# @raise [NotImplementedError]
|
220
|
+
#
|
221
|
+
def resolve
|
222
|
+
raise NotImplementedError
|
223
|
+
end
|
224
|
+
|
225
|
+
##
|
226
|
+
# Converts this designator to an Hash for +.to_json+ to work.
|
227
|
+
#
|
228
|
+
# @param options [Ignored]
|
229
|
+
#
|
230
|
+
# @return [Hash]
|
231
|
+
#
|
232
|
+
def as_json(options = nil)
|
233
|
+
{ :value => to_s, :label => describe(:full) }
|
234
|
+
end
|
235
|
+
|
236
|
+
##
|
237
|
+
# Pretty prints the designator in your console.
|
238
|
+
#
|
239
|
+
# @return [String]
|
240
|
+
#
|
241
|
+
def inspect
|
242
|
+
%[#<Designator(#{self.class.designator_name}) value:#{value.inspect}>]
|
243
|
+
end
|
244
|
+
|
245
|
+
##
|
246
|
+
# @return [String] the designator's class label.
|
247
|
+
#
|
248
|
+
# @see Designator.label
|
249
|
+
#
|
250
|
+
def label
|
251
|
+
self.class.label
|
252
|
+
end
|
253
|
+
|
254
|
+
##
|
255
|
+
# @return [Symbol] the designator's class type.
|
256
|
+
#
|
257
|
+
# @see Designator.type
|
258
|
+
#
|
259
|
+
def type
|
260
|
+
self.class.type
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
end
|
data/lib/eaco/dsl.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
module Eaco
|
2
|
+
|
3
|
+
##
|
4
|
+
# Eaco DSL entry point.
|
5
|
+
#
|
6
|
+
# @see DSL::Resource
|
7
|
+
# @see DSL::Actor
|
8
|
+
# @see DSL::ACL
|
9
|
+
#
|
10
|
+
module DSL
|
11
|
+
extend self # Oh the irony.
|
12
|
+
|
13
|
+
autoload :Base, 'eaco/dsl/base'
|
14
|
+
|
15
|
+
autoload :ACL, 'eaco/dsl/acl'
|
16
|
+
autoload :Actor, 'eaco/dsl/actor'
|
17
|
+
autoload :Resource, 'eaco/dsl/resource'
|
18
|
+
|
19
|
+
##
|
20
|
+
# Entry point for the {Resource} authorization definition.
|
21
|
+
#
|
22
|
+
# @see DSL::Resource
|
23
|
+
# @see DSL::ACL
|
24
|
+
#
|
25
|
+
def authorize(resource_class, options = {}, &block)
|
26
|
+
DSL::Resource.eval(resource_class, options, &block)
|
27
|
+
DSL::ACL.eval(resource_class, options)
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# Entry point for the {Actor} designators definition.
|
32
|
+
#
|
33
|
+
# @see DSL::Actor
|
34
|
+
#
|
35
|
+
def actor(actor_class, options = {}, &block)
|
36
|
+
DSL::Actor.eval(actor_class, options, &block)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
data/lib/eaco/dsl/acl.rb
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
require 'eaco/dsl/base'
|
2
|
+
|
3
|
+
module Eaco
|
4
|
+
module DSL
|
5
|
+
|
6
|
+
##
|
7
|
+
# Block-less DSL to set up the {ACL} machinery onto an authorized {Resource}.
|
8
|
+
#
|
9
|
+
# * Defines an {ACL} subclass in the Resource namespace
|
10
|
+
# * Defines syntactic sugar on the ACL to easily retrieve {Actor}s with a
|
11
|
+
# specific Role
|
12
|
+
# * Installs {ACL} objects persistance for the supported ORMs
|
13
|
+
# * Installs the authorized collection extraction strategy +.accessible_by+
|
14
|
+
#
|
15
|
+
class ACL < Base
|
16
|
+
|
17
|
+
##
|
18
|
+
# Performs ACL setup on the target Resource class.
|
19
|
+
#
|
20
|
+
# @see #define_acl_subclass
|
21
|
+
# @see #define_role_getters
|
22
|
+
# @see #install_persistance
|
23
|
+
#
|
24
|
+
def initialize(*)
|
25
|
+
super
|
26
|
+
|
27
|
+
define_acl_subclass
|
28
|
+
define_role_getters
|
29
|
+
install_persistance
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
##
|
35
|
+
# Creates the ACL constant on the target, inheriting from {Eaco::ACL}.
|
36
|
+
# Removes if it is already set, so that a reload of the authorization
|
37
|
+
# rules refreshes also these constants.
|
38
|
+
#
|
39
|
+
# The ACL subclass can be retrieved using the +.acl+ singleton method
|
40
|
+
# on the {Resource} class.
|
41
|
+
#
|
42
|
+
# @return [void]
|
43
|
+
#
|
44
|
+
def define_acl_subclass
|
45
|
+
target_eval do
|
46
|
+
remove_const(:ACL) if const_defined?(:ACL)
|
47
|
+
|
48
|
+
Class.new(Eaco::ACL).tap do |acl_class|
|
49
|
+
define_singleton_method(:acl) { acl_class }
|
50
|
+
const_set(:ACL, acl_class)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
# Define getter methods on the ACL for each role, syntactic sugar
|
57
|
+
# for calling {ACL#find_by_role}.
|
58
|
+
#
|
59
|
+
# Example:
|
60
|
+
#
|
61
|
+
# If a +reader+ role is defined, allows doing +resource.acl.readers+
|
62
|
+
# and returns all the designators having the +reader+ role set.
|
63
|
+
#
|
64
|
+
# @return [void]
|
65
|
+
#
|
66
|
+
def define_role_getters
|
67
|
+
roles = self.target.roles
|
68
|
+
|
69
|
+
target.acl.instance_eval do
|
70
|
+
roles.each do |role|
|
71
|
+
define_method(role.to_s.pluralize) { find_by_role(role) }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
##
|
77
|
+
# Sets up the persistance layer for ACLs (+#acl+ and +#acl=+) and the
|
78
|
+
# authorized collection extraction strategy (+.accessible_by+).
|
79
|
+
#
|
80
|
+
# All these APIs can be implemented directly in your models, as
|
81
|
+
# long as the +acl+ accessor accepts and returns the model's ACL
|
82
|
+
# subclass (see {.define_acl_subclass}); and the +.accessible_by+
|
83
|
+
# returns an +Enumerable+ collection.
|
84
|
+
#
|
85
|
+
# See each adapter for the details of the extraction strategies
|
86
|
+
# they provide.
|
87
|
+
#
|
88
|
+
# @return [void]
|
89
|
+
#
|
90
|
+
def install_persistance
|
91
|
+
if adapter
|
92
|
+
target.send(:include, adapter)
|
93
|
+
install_authorized_collection_strategy
|
94
|
+
|
95
|
+
elsif target.respond_to?(:acl) && target.respond_to?(:acl=)
|
96
|
+
raise Malformed, <<-EOF
|
97
|
+
Don't know how to persist ACLs using <#{target}>'s ORM
|
98
|
+
(identified as <#{orm}>). Please define an `acl' instance
|
99
|
+
accessor on <#{target}> that accepts and returns a <#{target.acl}>.
|
100
|
+
EOF
|
101
|
+
end
|
102
|
+
|
103
|
+
unless target.respond_to?(:accessible_by)
|
104
|
+
strategies = adapter ? adapter.strategies.keys : []
|
105
|
+
|
106
|
+
raise Malformed, <<-EOF
|
107
|
+
Don't know how to look up authorized records on <#{target}>'s
|
108
|
+
ORM (identified as <#{orm}>). To authorize <#{target}>
|
109
|
+
|
110
|
+
#{ if strategies.size > 0
|
111
|
+
"either use one of the available strategies: #{strategies.join(', ')} or"
|
112
|
+
end }
|
113
|
+
|
114
|
+
please define your own #{target}.accessible_by method.
|
115
|
+
You may at one point want to move this in a new strategy,
|
116
|
+
and send a pull request :-).
|
117
|
+
EOF
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
##
|
122
|
+
# Looks up the authorized collection strategy within the Adapter,
|
123
|
+
# using the +:using+ option given to the +authorize+ Resource DSL
|
124
|
+
#
|
125
|
+
# @see DSL::Resource
|
126
|
+
#
|
127
|
+
# @return [void]
|
128
|
+
#
|
129
|
+
def install_authorized_collection_strategy
|
130
|
+
if adapter && (strategy = adapter.strategies[ options.fetch(:using, nil) ])
|
131
|
+
target.extend strategy
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
##
|
136
|
+
# Tries to identify which ORM adapter to use for the +target+ class.
|
137
|
+
#
|
138
|
+
# @return [Class] the adapter implementation or nil if not available.
|
139
|
+
#
|
140
|
+
def adapter
|
141
|
+
{ 'ActiveRecord::Base' => Eaco::Adapters::ActiveRecord,
|
142
|
+
'CouchRest::Model::Base' => Eaco::Adapters::CouchrestModel,
|
143
|
+
}.fetch(orm.name, nil)
|
144
|
+
end
|
145
|
+
|
146
|
+
##
|
147
|
+
# Tries to naively identify which ORM the target model is using.
|
148
|
+
#
|
149
|
+
# TODO support more stuff
|
150
|
+
#
|
151
|
+
# @return [Class] the ORM base class.
|
152
|
+
#
|
153
|
+
def orm
|
154
|
+
if target.respond_to?(:base_class)
|
155
|
+
target.base_class.superclass # Active Record
|
156
|
+
else
|
157
|
+
target.superclass # Naive
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
163
|
+
end
|