aclatraz 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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 ACLatraz
10
+ sudo gem install aclatraz
11
11
 
12
- == Basic usage
12
+ == Configuration
13
13
 
14
- First of all you have to setup store for ACL data. ACLatraz for now uses only
15
- Redis database for storage. Store can be set like below:
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.store :redis, "redis://localhost:6379/0"
19
+ Aclatraz.init :redis, "redis://localhost:6379/0"
18
20
 
19
- Then you have to define your suspects:
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 you suspect have few methods which will helps you with managing permissions:
26
-
27
- @account = Account.create
28
- @account.has_role?(:foo) # => false
29
- @account.is.foo? # syntactic sugar for #has_role?
30
- @account.assign_role!(foo) # or @account.is.foo!
31
- @account.is.foo? # => true
32
- @account.delete_role!(foo) # or @account.is_not.foo!
33
- @account.is.foo? # => false
34
- @account.is_not.foo? # => true
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
- Last step is create you access control list and set guards:
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
- suspect :account do
42
- allow all
43
-
44
- on :manage do
45
- allow :root
46
- allow :manager
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
- on :delete do
50
- deny :manager
51
- allow :owner_of => "@project"
186
+ action :bar
187
+ allow :bar
188
+ deny :admin
52
189
  end
53
190
  end
54
-
55
- # The #suspect method allows to pass String, Symbol or Object as suspect.
56
- # When String given (eg. "account" or "@account") then value of given
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 index
196
+ def simple
71
197
  guard!
72
- # ... everybody are allowed to see this
198
+ # only for accounts with :admin role...
73
199
  end
74
200
 
75
- def create
76
- guard!(:manage)
77
- # ... only managers and root will see this
201
+ def foo
202
+ guard!(:foo)
203
+ # only for accounts with :admin or :foo role...
78
204
  end
79
205
 
80
- def delete(id)
81
- @product = Product.find(id)
82
- guard!(:manage, :delete)
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 product(id)
87
- @product.find(id)
88
- account.is.owner_of?(@product) do
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 = %Q{Extremaly fast and flexible access control mechanism inspired by *nix ACLs, powered by fast key value stores like Redis or TokyoCabinet.}
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
@@ -0,0 +1,6 @@
1
+ == TODO
2
+
3
+ * TokyoCabinet store
4
+ * Memcached store
5
+ * Think that the main roles should have an impact on object/class ownerships
6
+ * More Examples
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.1
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
+