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