aclatraz 0.0.1 → 0.1.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.
- data/CHANGELOG.rdoc +21 -0
- data/README.rdoc +221 -57
- data/Rakefile +9 -2
- data/TODO.rdoc +6 -0
- data/VERSION +1 -1
- data/aclatraz.gemspec +88 -0
- data/examples/dinner.rb +71 -0
- data/lib/aclatraz.rb +34 -12
- data/lib/aclatraz/acl.rb +82 -7
- data/lib/aclatraz/guard.rb +139 -57
- data/lib/aclatraz/helpers.rb +14 -6
- data/lib/aclatraz/store.rb +3 -7
- data/lib/aclatraz/store/redis.rb +11 -11
- data/lib/aclatraz/suspect.rb +157 -57
- data/spec/aclatraz/acl_spec.rb +8 -3
- data/spec/aclatraz/guard_spec.rb +178 -121
- data/spec/aclatraz/stores_spec.rb +1 -26
- data/spec/aclatraz/suspect_spec.rb +25 -25
- data/spec/aclatraz_spec.rb +16 -2
- data/spec/alcatraz_bm.rb +54 -0
- data/spec/spec_helper.rb +7 -0
- metadata +12 -5
data/CHANGELOG.rdoc
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
== Version 0.1.0
|
2
|
+
|
3
|
+
* Inherited access control lists
|
4
|
+
* Optimized Aclatraz::Guard & Aclatraz::ACL
|
5
|
+
* Added aliases: #access_control for #suspects and #authorize! for #guard! in Aclatraz::Guard
|
6
|
+
* Added #init to Aclatraz
|
7
|
+
* Added Aclatraz::Suspect::Roles
|
8
|
+
* Added #roles proxy instead of #assign_role!, #delete_role!, #has_role? in Aclatraz::Suspect
|
9
|
+
* Defining permissions in #guard! block
|
10
|
+
|
11
|
+
* README documentation
|
12
|
+
* Code documentation
|
13
|
+
|
14
|
+
== Version 0.0.1 (proof of concept)
|
15
|
+
|
16
|
+
* Redis store
|
17
|
+
* Flat access control lists
|
18
|
+
* Semantic permissions in suspect
|
19
|
+
|
20
|
+
* Basic documentation
|
21
|
+
* Benchmarks
|
data/README.rdoc
CHANGED
@@ -5,92 +5,256 @@ powered by fast key value stores like Redis or TokyoCabinet.
|
|
5
5
|
|
6
6
|
== Installation
|
7
7
|
|
8
|
-
You can install ACLatraz via rubygems:
|
8
|
+
You can simple install ACLatraz via rubygems:
|
9
9
|
|
10
|
-
sudo gem install
|
10
|
+
sudo gem install aclatraz
|
11
11
|
|
12
|
-
==
|
12
|
+
== Configuration
|
13
13
|
|
14
|
-
|
15
|
-
|
14
|
+
Before you'll start play with access controll in your apps you have to
|
15
|
+
correctly configure datastore for permissions (at this moment ACLatraz
|
16
|
+
is only supporting Redis database as storage). Redis datastore configuration
|
17
|
+
looks very simple:
|
16
18
|
|
17
|
-
Aclatraz.
|
19
|
+
Aclatraz.init :redis, "redis://localhost:6379/0"
|
18
20
|
|
19
|
-
|
21
|
+
Remember that using Redis, you should specify database dedicated only for ACLatraz.
|
22
|
+
|
23
|
+
== Suspects
|
24
|
+
|
25
|
+
Suspects are objects which we can assign specific permissions. There is only
|
26
|
+
one condition which object have to meet to be suspect - it must have the #id
|
27
|
+
method which returns an unique identifier after which we will be able to
|
28
|
+
reference this object. To enable suspect behaviour you have to include the
|
29
|
+
+Aclatraz::Suspect+ module to specified class, eg:
|
20
30
|
|
21
31
|
class Account < ActiveRecord::Base
|
22
32
|
include Aclatraz::Suspect
|
23
33
|
end
|
24
34
|
|
25
|
-
Now
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
+
Now your suspect have few methods which will helps you manage it permissions.
|
36
|
+
|
37
|
+
=== Managing roles
|
38
|
+
|
39
|
+
ACLatraz distinguishes between three types of roles:
|
40
|
+
|
41
|
+
*global* ::
|
42
|
+
simple roles, which are most commonly assigned to many users. We can say that
|
43
|
+
they are kind of groups. Global roles are eg. _guest_, _admin_, _customer_.
|
44
|
+
*class-related* ::
|
45
|
+
roles that affects management of a particular class. Example of this kind
|
46
|
+
of role can be *manager* of +Pages+, *admin* of +Products+, etc.
|
47
|
+
*object-related* ::
|
48
|
+
roles that affects management of an object. For example *author* of
|
49
|
+
_specified page_, *owner* of _specified product_, etc.
|
50
|
+
|
51
|
+
Now there is two ways for managing roles. You can use +#roles+ or semanticaly
|
52
|
+
look like +#is+ and +#is_not+ proxies.
|
53
|
+
|
54
|
+
==== Assigning
|
55
|
+
|
56
|
+
To add given role you can use +#assign+ method from +#roles+ proxy or use
|
57
|
+
semantic shortcuts. Semantic shortcut have to ends with *!*, and can have
|
58
|
+
optional suffixes: <em>_on</em>, <em>_of</em>, <em>_at</em>, <em>_for</em>,
|
59
|
+
<em>_in</em>, <em>_by</em>. Take a look at the following examples to get
|
60
|
+
everything to be clear:
|
61
|
+
|
62
|
+
@account.roles.assign(:admin) # or ...
|
63
|
+
@account.is.admin!
|
64
|
+
|
65
|
+
...will assign *global* _admin_ role to the account.
|
66
|
+
|
67
|
+
@account.roles.assign(:responsible, Foo) # or...
|
68
|
+
@account.is.responsible_for!(Foo)
|
69
|
+
|
70
|
+
...will assign to the account _responsible_ role related with Foo class.
|
71
|
+
|
72
|
+
@account.roles.assign(:author, Page.find(15)) # or...
|
73
|
+
@account.is.author_of!(Page.find(15))
|
74
|
+
|
75
|
+
...will assign to the account _author_ role related with given page object.
|
76
|
+
|
77
|
+
==== Checking
|
78
|
+
|
79
|
+
Using #roles proxy you can call +#has?+ method on it, eg:
|
80
|
+
|
81
|
+
@account.roles.has?(:admin) # => true
|
82
|
+
@account.roles.has?(:responsible, Foo) # => true
|
83
|
+
@account.roles.has?(:author, Page.find(15) # => true
|
84
|
+
|
85
|
+
With semantic shortcuts your method name have to ends with *?* and can contain
|
86
|
+
any of suffixes listed above, eg:
|
87
|
+
|
88
|
+
@account.is.admin? # => true
|
89
|
+
@account.is.responsible_for?(Foo) # => true
|
90
|
+
@account.is.author_of(Page.find(15)) # => true
|
91
|
+
|
92
|
+
Few more examples with semantic negation:
|
93
|
+
|
94
|
+
@account.is_not.admin? # => false
|
95
|
+
@account.is_not.responsible_for(Foo) # => false
|
96
|
+
|
97
|
+
==== Deleting
|
98
|
+
|
99
|
+
To unassign given role from object use +#delete+ method from +#roles+, eg:
|
100
|
+
|
101
|
+
@account.roles.delete(:admin)
|
102
|
+
@account.roles.delete(:responsible, Foo)
|
103
|
+
|
104
|
+
Another way is to use semantic negation, where method name have to ends with *!*
|
105
|
+
and can contain one of allowed suffixes, eg:
|
106
|
+
|
107
|
+
@account.is_not.admin!
|
108
|
+
@account.is_not.author_of(Page.find(15))
|
35
109
|
|
36
|
-
|
110
|
+
== Guards
|
111
|
+
|
112
|
+
To enable access control in your for your objects you have to include to it the
|
113
|
+
+Aclatraz::Guard+ module. This module provides methods for defining and checking
|
114
|
+
permissions of an suspected object. Take a look for this basic example:
|
37
115
|
|
38
116
|
class Foo
|
39
117
|
include Aclatraz::Guard
|
118
|
+
|
119
|
+
suspects :account do
|
120
|
+
deny all # notice that it's a method, not symbol
|
121
|
+
allow :admin
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
The +#suspects+ block is passing one argument - suspected object. When there is
|
126
|
+
symbol given, like in example above, then will treat #account instance method
|
127
|
+
result as suspected object. When you will use string, eg:
|
128
|
+
|
129
|
+
suspects "account" do # or suspects "@account" do ...
|
130
|
+
|
131
|
+
... it will treat @account instance variable as suspect. You can also specify
|
132
|
+
suspected object directly, eg:
|
133
|
+
|
134
|
+
account = Account.find(1)
|
135
|
+
suspects account do # ...
|
136
|
+
|
137
|
+
=== Allowing and denying access
|
138
|
+
|
139
|
+
As you probably noticed, there is two methods responsible for access control,
|
140
|
+
namely +#allow+ and +#deny+. As its argument you can pass simple name of role
|
141
|
+
or permission statement, eg:
|
142
|
+
|
143
|
+
allow :admin
|
144
|
+
deny :guest
|
145
|
+
allow :responsible_for => Foo
|
146
|
+
allow :author_of => "@page"
|
147
|
+
|
148
|
+
Like you see, you can easy specify access for each kind of role. The object-related
|
149
|
+
permissions behaviour is similar to +#suspects+ method. When given related object
|
150
|
+
is string then applies permissions for an instance variable, when symbol then
|
151
|
+
applies it for instance method, otherwise directly for given object.
|
152
|
+
|
153
|
+
=== Actions
|
154
|
+
|
155
|
+
In your access control block you can specify separate action with its own
|
156
|
+
permissions, eg:
|
157
|
+
|
158
|
+
suspects :account do
|
159
|
+
deny all
|
160
|
+
allow :admin
|
161
|
+
|
162
|
+
action :manage do
|
163
|
+
allow :responsible_for => Foo
|
164
|
+
end
|
40
165
|
|
41
|
-
|
42
|
-
allow
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
166
|
+
action :delete do
|
167
|
+
allow :author_of => "@page"
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
Obviously all actions inherits all permissions from main block.
|
172
|
+
|
173
|
+
=== Authorizing
|
174
|
+
|
175
|
+
The +Aclatraz::Guards+ module provides +#guard!+ method for checking permissions.
|
176
|
+
When suspected object don't have any of allowed permissions or have any of defnied
|
177
|
+
then it will raise the +Aclatraz::AccessDenied+ error. Here's an comprehensive example:
|
178
|
+
|
179
|
+
class Foo
|
180
|
+
suspects "@account" do
|
181
|
+
deny all
|
182
|
+
allow :admin
|
183
|
+
action :foo
|
184
|
+
allow :foo
|
47
185
|
end
|
48
|
-
|
49
|
-
|
50
|
-
deny :
|
51
|
-
allow :owner_of => "@project"
|
186
|
+
action :bar
|
187
|
+
allow :bar
|
188
|
+
deny :admin
|
52
189
|
end
|
53
190
|
end
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
# instance variable will be treated as suspect. When Symbol given, then
|
58
|
-
# system will take value from given instance method. Otherwise given object
|
59
|
-
# will be treated as suspect if possible.
|
60
|
-
#
|
61
|
-
# Similar situation is with permission arguments. To #allow and #deny
|
62
|
-
# methods you can pass symbol or hash. Symbol given represents
|
63
|
-
# single role, the hash represents ownership of assigned object or class.
|
64
|
-
# Assigned object declaration behaves the same as suspects' one.
|
65
|
-
|
66
|
-
def account
|
67
|
-
@account ||= Account.find(ENV['MYAPP_ACCOUNT'])
|
191
|
+
|
192
|
+
def initialize(account)
|
193
|
+
@account = account
|
68
194
|
end
|
69
195
|
|
70
|
-
def
|
196
|
+
def simple
|
71
197
|
guard!
|
72
|
-
#
|
198
|
+
# only for accounts with :admin role...
|
73
199
|
end
|
74
200
|
|
75
|
-
def
|
76
|
-
guard!(:
|
77
|
-
#
|
201
|
+
def foo
|
202
|
+
guard!(:foo)
|
203
|
+
# only for accounts with :admin or :foo role...
|
78
204
|
end
|
79
205
|
|
80
|
-
def
|
81
|
-
|
82
|
-
|
83
|
-
# ... only root or or @product owner will see this
|
206
|
+
def bar
|
207
|
+
guard!(:bar)
|
208
|
+
# only for accounts with :bar role...
|
84
209
|
end
|
85
210
|
|
86
|
-
def
|
87
|
-
|
88
|
-
|
89
|
-
return @product
|
90
|
-
end
|
91
|
-
return nil
|
211
|
+
def foobar
|
212
|
+
guard!(:foo, :bar)
|
213
|
+
# only for accounts with :foo or :bar, and mandatory without :admin role...
|
92
214
|
end
|
93
215
|
end
|
216
|
+
|
217
|
+
There is also a very nice feature. You can define additional permissions directly
|
218
|
+
in +#guard!+ block, eg:
|
219
|
+
|
220
|
+
def foo
|
221
|
+
guard! do
|
222
|
+
allow :foo
|
223
|
+
deny :bar
|
224
|
+
end
|
225
|
+
# ...
|
226
|
+
end
|
227
|
+
|
228
|
+
=== Inheritance
|
229
|
+
|
230
|
+
ACLatraz access control supports inheritance. It means that when you define
|
231
|
+
your ACL in parent class it will be applied also for all child classes. Obviously
|
232
|
+
in each child class you can freely modify permissions. Here's an example:
|
233
|
+
|
234
|
+
class Foo
|
235
|
+
suspects :account do
|
236
|
+
deny all
|
237
|
+
allow :admin
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
class Bar < Foo
|
242
|
+
suspects do
|
243
|
+
# notice, that in child class don't have to again specify suspect...
|
244
|
+
allow :foobar
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
class Spam < Foo
|
249
|
+
suspects :egg do
|
250
|
+
# but of course you can specify different suspect than in parent class...
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
=== Aliases
|
255
|
+
|
256
|
+
If you prefer you can use aliases: +#access_control+ instead of +#suspects+ and
|
257
|
+
+#authorize!+ instead of +#guard!+.
|
94
258
|
|
95
259
|
== Note on Patches/Pull Requests
|
96
260
|
|
data/Rakefile
CHANGED
@@ -6,14 +6,16 @@ begin
|
|
6
6
|
Jeweler::Tasks.new do |gem|
|
7
7
|
gem.name = "aclatraz"
|
8
8
|
gem.summary = %Q{Flexible access control that doesn't sucks!}
|
9
|
-
gem.description =
|
9
|
+
gem.description = <<-DESCR
|
10
|
+
Extremaly fast and flexible access control mechanism inspired by *nix ACLs,
|
11
|
+
powered by fast key value stores like Redis or TokyoCabinet.
|
12
|
+
DESCR
|
10
13
|
gem.email = "kriss.kowalik@gmail.com"
|
11
14
|
gem.homepage = "http://github.com/nu7hatch/aclatraz"
|
12
15
|
gem.authors = ["Kriss 'nu7hatch' Kowalik"]
|
13
16
|
gem.add_development_dependency "rspec", ">= 1.2.9"
|
14
17
|
gem.add_development_dependency "mocha", ">= 0.9"
|
15
18
|
gem.add_development_dependency "redis", "~> 2.0"
|
16
|
-
#gem.add_development_dependency "tokyocabinet", ">= XX"
|
17
19
|
gem.add_dependency "dictionary", "~> 1.0"
|
18
20
|
end
|
19
21
|
Jeweler::GemcutterTasks.new
|
@@ -44,3 +46,8 @@ Rake::RDocTask.new do |rdoc|
|
|
44
46
|
rdoc.rdoc_files.include('README*')
|
45
47
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
46
48
|
end
|
49
|
+
|
50
|
+
task :benchmark do
|
51
|
+
require 'benchmark'
|
52
|
+
require File.dirname(__FILE__)+"/spec/alcatraz_bm"
|
53
|
+
end
|
data/TODO.rdoc
ADDED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0
|
1
|
+
0.1.0
|
data/aclatraz.gemspec
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{aclatraz}
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Kriss 'nu7hatch' Kowalik"]
|
12
|
+
s.date = %q{2010-09-16}
|
13
|
+
s.description = %q{ Extremaly fast and flexible access control mechanism inspired by *nix ACLs,
|
14
|
+
powered by fast key value stores like Redis or TokyoCabinet.
|
15
|
+
}
|
16
|
+
s.email = %q{kriss.kowalik@gmail.com}
|
17
|
+
s.extra_rdoc_files = [
|
18
|
+
"LICENSE",
|
19
|
+
"README.rdoc"
|
20
|
+
]
|
21
|
+
s.files = [
|
22
|
+
".document",
|
23
|
+
".gitignore",
|
24
|
+
"CHANGELOG.rdoc",
|
25
|
+
"LICENSE",
|
26
|
+
"README.rdoc",
|
27
|
+
"Rakefile",
|
28
|
+
"TODO.rdoc",
|
29
|
+
"VERSION",
|
30
|
+
"aclatraz.gemspec",
|
31
|
+
"examples/dinner.rb",
|
32
|
+
"lib/aclatraz.rb",
|
33
|
+
"lib/aclatraz/acl.rb",
|
34
|
+
"lib/aclatraz/guard.rb",
|
35
|
+
"lib/aclatraz/helpers.rb",
|
36
|
+
"lib/aclatraz/store.rb",
|
37
|
+
"lib/aclatraz/store/redis.rb",
|
38
|
+
"lib/aclatraz/suspect.rb",
|
39
|
+
"spec/aclatraz/acl_spec.rb",
|
40
|
+
"spec/aclatraz/guard_spec.rb",
|
41
|
+
"spec/aclatraz/helpers_spec.rb",
|
42
|
+
"spec/aclatraz/stores_spec.rb",
|
43
|
+
"spec/aclatraz/suspect_spec.rb",
|
44
|
+
"spec/aclatraz_spec.rb",
|
45
|
+
"spec/alcatraz_bm.rb",
|
46
|
+
"spec/spec.opts",
|
47
|
+
"spec/spec_helper.rb"
|
48
|
+
]
|
49
|
+
s.homepage = %q{http://github.com/nu7hatch/aclatraz}
|
50
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
51
|
+
s.require_paths = ["lib"]
|
52
|
+
s.rubygems_version = %q{1.3.7}
|
53
|
+
s.summary = %q{Flexible access control that doesn't sucks!}
|
54
|
+
s.test_files = [
|
55
|
+
"spec/alcatraz_bm.rb",
|
56
|
+
"spec/spec_helper.rb",
|
57
|
+
"spec/aclatraz/guard_spec.rb",
|
58
|
+
"spec/aclatraz/helpers_spec.rb",
|
59
|
+
"spec/aclatraz/acl_spec.rb",
|
60
|
+
"spec/aclatraz/stores_spec.rb",
|
61
|
+
"spec/aclatraz/suspect_spec.rb",
|
62
|
+
"spec/aclatraz_spec.rb",
|
63
|
+
"examples/dinner.rb"
|
64
|
+
]
|
65
|
+
|
66
|
+
if s.respond_to? :specification_version then
|
67
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
68
|
+
s.specification_version = 3
|
69
|
+
|
70
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
71
|
+
s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
|
72
|
+
s.add_development_dependency(%q<mocha>, [">= 0.9"])
|
73
|
+
s.add_development_dependency(%q<redis>, ["~> 2.0"])
|
74
|
+
s.add_runtime_dependency(%q<dictionary>, ["~> 1.0"])
|
75
|
+
else
|
76
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
77
|
+
s.add_dependency(%q<mocha>, [">= 0.9"])
|
78
|
+
s.add_dependency(%q<redis>, ["~> 2.0"])
|
79
|
+
s.add_dependency(%q<dictionary>, ["~> 1.0"])
|
80
|
+
end
|
81
|
+
else
|
82
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
83
|
+
s.add_dependency(%q<mocha>, [">= 0.9"])
|
84
|
+
s.add_dependency(%q<redis>, ["~> 2.0"])
|
85
|
+
s.add_dependency(%q<dictionary>, ["~> 1.0"])
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|