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