allowance 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.documentup.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "twitter": "hmans",
3
+ "travis": true
4
+ }
data/README.md CHANGED
@@ -1,73 +1,252 @@
1
1
  # Allowance
2
2
 
3
- Allowance is a general-use permission management library that can be used
4
- in any framework or application.
3
+ **Allowance is a general-use permission management library for Ruby.
4
+ It's decidedly simple, highly flexible, and has out-of-the-box support
5
+ for ActiveModel-compliant classes.**
6
+
7
+ It was inspired by Ryan Bates' fantastic Rails authorization plugin [cancan](https://github.com/ryanb/cancan) and, unlike most other gems
8
+ of its kind, is not bound to a specific framework.
5
9
 
6
10
  A simple Example:
7
11
 
8
12
  ``` ruby
9
13
  p = Allowance.define do |can|
10
- can.sing!
11
- can.play!
14
+ # Allow logging in
15
+ can.login!
16
+
17
+ # Allow creating new Article instances
18
+ can.create! Article
19
+
20
+ # Allow the user to edit Article instances that belong to him
21
+ can.edit! Article, :author_id => current_user.id
22
+
23
+ # Allow viewing all Article instances that are published or the user's
24
+ can.read! Article, ['published = ? OR author_id = ?', true, current_user.id]
12
25
  end
26
+ ```
27
+
28
+ Allowance parses these permission definitions and stores them in the object the
29
+ `Allowance.define` call returns. It is now up to you to query that object where
30
+ necessary. Some examples:
13
31
 
14
- p.sing? # true
15
- p.play? # true
16
- p.dance? # false
32
+ ``` ruby
33
+ p.login? # true
34
+ p.create? Article # true
35
+ p.read? @article # true or false, depending on state of @article
17
36
  ```
18
37
 
19
- You can specify permissions on objects (and their classes), too:
38
+ You can use the same object to provide you with correctly scoped models, too:
20
39
 
21
40
  ``` ruby
22
- p = Allowance.define do |can|
23
- # Everyone can view posts that have been published
24
- can.view! Post, :published => true
41
+ p.scoped_model(:view, Article).all
42
+ # -> Article.where(['published = ? OR author_id = ?', true, current_user.id]).all
43
+ ```
44
+
45
+
46
+
47
+ ## Installation
48
+
49
+ ### Requirements
50
+
51
+ Allowance should work fine with Ruby 1.8.7, 1.9.2, 1.9.3 and respective JRuby versions. Please consult Allowance's [Travis status page](http://travis-ci.org/hmans/allowance) for details.
52
+
53
+ [![Build Status](https://secure.travis-ci.org/hmans/allowance.png?branch=master)](http://travis-ci.org/hmans/allowance)
54
+
55
+ ### Installing through Bundler
56
+
57
+ Well, you've done this before, haven't you? Just add the `allowance` gem to your project's Gemfile:
58
+
59
+ ``` ruby
60
+ gem 'allowance'
61
+ ```
62
+
63
+ ### Installing without Bundler
64
+
65
+ Install using RubyGems:
66
+
67
+ ```
68
+ gem install allowance
69
+ ```
70
+
71
+ Then require it in your code:
72
+
73
+ ```
74
+ require 'rubygems'
75
+ require 'allowance'
76
+ ```
77
+
78
+
79
+ ## Usage
80
+
81
+ ### Defining permissions
82
+
83
+ Use `Allowance.define` to create a new permissions object, then use its `allow!` or `can!`
84
+ methods to add permissions:
85
+
86
+ ``` ruby
87
+ p = Allowance.define
88
+ p.allow! :sing
89
+ ```
25
90
 
26
- # Post owners can delete their own posts
27
- can.delete! Post, :user_id => current_user.id
91
+ Instead of using `allow!` or `can!`, you can just name the permission directly:
28
92
 
29
- # Admin users can view and delete all posts
30
- if current_user.admin?
31
- can.view! Post
32
- can.delete! Post
93
+ ``` ruby
94
+ p.sing!
95
+ ```
96
+
97
+ You can also specify permissions as a block:
98
+
99
+ ``` ruby
100
+ p = Allowance.define do |allow|
101
+ allow.sing!
102
+ end
103
+ ```
104
+
105
+ ### Querying permissions
106
+
107
+ Similar to how you define permissions, you can use the `allowed?` or `can?` methods, or
108
+ query permissions directly by name. The following two lines are equivalent:
109
+
110
+ ``` ruby
111
+ p.allowed? :sing
112
+ p.can? :sing
113
+ p.sing?
114
+ ```
115
+
116
+ ### One-dimensional permissions
117
+
118
+ Allowance lets you define simple, one-dimenstional permissions like this:
119
+
120
+ ``` ruby
121
+ p = Allowance.define do |allow|
122
+ allow.sing!
123
+ allow.play!
124
+ allow.dance! if current_user.can_dance?
125
+ end
126
+ ```
127
+
128
+ ### Two-dimensional permissions
129
+
130
+ Most of the time, you will be using two-dimensional permissions, consisting of a
131
+ _verb_ and an _object_, with the object typically being some kind of model class.
132
+ For example:
133
+
134
+ ``` ruby
135
+ p = Allowance.define do |allow|
136
+ allow.view! Article
137
+
138
+ if current_user.is_admin?
139
+ allow.edit! Article
33
140
  end
34
141
  end
35
142
  ```
36
143
 
37
- Instead of condition hashes, you can specify lambdas. This is great for model
38
- classes that are ActiveModel based (eg. ActiveRecord, Mongoid etc.):
144
+ When querying for permissions, just pass an object as an additional parameter:
39
145
 
40
146
  ``` ruby
41
- p = Allowance.define do |can|
42
- # Everyone can view posts that have been published
43
- can.view! Post, lambda { |posts| posts.visible_posts }
147
+ p.edit? Article
148
+ ```
149
+
150
+ When you pass a class instance (instead of a class), Allowance will check for
151
+ the permission defined for its class, so the following will work, too:
152
+
153
+ ``` ruby
154
+ p.edit? @article
155
+ ```
156
+
157
+ Allowance will even allow you to define permissions in specific objects -- this is not recommended, though, since permission objects defined through Allowance only exist in memory; if you need to control permissions on individual model objects, you'll be better off with another authorization library, ideally one that stores its permissions in your datastore.
158
+
159
+
160
+ ### Defining model scopes
161
+
162
+ For classes implementing ActiveModel (eg. ActiveRecord, Mongoid and others), Allowance allows you to restrict certain permissions to specific scopes. For example, if, in a web application, a user should only be able to see articles that have been published, but admin users can see everything, you can define the permissions like this:
163
+
164
+ ``` ruby
165
+ p = Allowance.define do |allow|
166
+ allow.view! Article, :published => true
167
+
168
+ if current_user.is_admin?
169
+ allow.view! Article
170
+ end
44
171
  end
45
172
  ```
46
173
 
47
- More documentation coming up soon.
174
+ You can see here that when a specific permission (for a _verb_ and _object_ combination) is defined more than once, the last definition will overwrite all those that came before it.
48
175
 
49
- ## Installation
176
+ In the example above, we defined the scope as a so-called "where conditions hash", ie. a hash passed to the model class' `.where` method. Since the hash notation assumes a logical AND and isn't all that flexible overall, you can also use the same string/array syntax you know from ActiveModel:
177
+
178
+ ``` ruby
179
+ p.view! Article, ['published = ? OR user_id = ?', true, current_user.id]
180
+ ```
181
+
182
+ Lastly, you're encouraged to re-use scopes defined on the model class. Just define your scope using a lambda:
50
183
 
51
- Add this line to your application's Gemfile:
184
+ ``` ruby
185
+ p.view! Article, lambda { |r| r.viewable_by(current_user) }
186
+ ```
52
187
 
53
- gem 'allowance'
188
+ Not only can use check permissions against those scopes, but you can also use the `#scoped_model` method to retreive a correctly scoped model according to its previous scope definition. For example:
54
189
 
55
- And then execute:
190
+ ``` ruby
191
+ @articles = p.scoped_model(:view, Article).order('created_at DESC').all
192
+ ```
56
193
 
57
- $ bundle
194
+ ### Defining contextual permissions
58
195
 
59
- Or install it yourself as:
196
+ Since permissions are just Ruby code, you can use all your favorite language
197
+ constructs when defining permissions. For example, in a web application
198
+ providing a `current_user` method, you can do the following:
60
199
 
61
- $ gem install allowance
200
+ ``` ruby
201
+ permissions = Allowance.define do |allow|
202
+ allow.read! Article
62
203
 
63
- ## Usage
204
+ if current_user.is_admin?
205
+ allow.destroy! Article
206
+ end
207
+ end
208
+ ```
209
+
210
+ You can then query this permission like you'd expect:
64
211
 
65
- TODO: Write usage instructions here
212
+ ``` ruby
213
+ @article = Article.find(params[:id])
214
+ if permissions.destroy? @article
215
+ @article.destroy
216
+ else
217
+ raise "You're not allowed to do this"
218
+ end
219
+ ```
66
220
 
67
221
  ## Contributing
68
222
 
69
- 1. Fork it
70
- 2. Create your feature branch (`git checkout -b my-new-feature`)
71
- 3. Commit your changes (`git commit -am 'Add some feature'`)
72
- 4. Push to the branch (`git push origin my-new-feature`)
73
- 5. Create new Pull Request
223
+ I'm looking forward to seeing your Pull Requests. However, please be aware that,
224
+ like with pretty much all other projects I'm maintaining, I'm trying to keep it
225
+ as small as possible, so I'm going to be somewhat picky about which pull
226
+ requests to accept. If you're adding new features, I recommed you check back
227
+ with me first in order to avoid disappointment.
228
+
229
+ ## License
230
+
231
+ Copyright (c) 2012 Hendrik Mans <hendrik@mans.de>
232
+
233
+ MIT License
234
+
235
+ Permission is hereby granted, free of charge, to any person obtaining
236
+ a copy of this software and associated documentation files (the
237
+ "Software"), to deal in the Software without restriction, including
238
+ without limitation the rights to use, copy, modify, merge, publish,
239
+ distribute, sublicense, and/or sell copies of the Software, and to
240
+ permit persons to whom the Software is furnished to do so, subject to
241
+ the following conditions:
242
+
243
+ The above copyright notice and this permission notice shall be
244
+ included in all copies or substantial portions of the Software.
245
+
246
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
247
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
248
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
249
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
250
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
251
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
252
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -20,6 +20,8 @@ module Allowance
20
20
  false
21
21
  end
22
22
 
23
+ alias_method :can?, :allowed?
24
+
23
25
  def allow!(verbs, objects = nil, scope = true, &blk)
24
26
  expand_permissions(verbs).each do |verb|
25
27
  [objects].flatten.each do |object|
@@ -28,6 +30,8 @@ module Allowance
28
30
  end
29
31
  end
30
32
 
33
+ alias_method :can!, :allow!
34
+
31
35
  def method_missing(name, *args, &blk)
32
36
  if name.to_s =~ /(.+)!$/
33
37
  allow!($1.to_sym, *args, &blk)
@@ -40,12 +44,10 @@ module Allowance
40
44
 
41
45
  def scoped_model(verb, model)
42
46
  if p = @permissions[[verb, model]]
43
- if p.is_a?(Hash)
44
- model.where(p)
45
- elsif p.is_a?(Proc)
46
- p.call(model)
47
- else
48
- model
47
+ case p
48
+ when Hash, String, Array then model.where(p)
49
+ when Proc then p.call(model)
50
+ else model
49
51
  end
50
52
  else
51
53
  model.where(false)
@@ -1,3 +1,3 @@
1
1
  module Allowance
2
- VERSION = "0.0.4"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -5,6 +5,14 @@ module Allowance
5
5
  SomeClass = Class.new
6
6
  SomeOtherClass = Class.new
7
7
 
8
+ it "should allow permissions to be specified and queried through its instance methods" do
9
+ subject.can! :sing
10
+ subject.allow! :dance
11
+
12
+ insist subject.can? :dance
13
+ insist subject.allowed? :sing
14
+ end
15
+
8
16
  it "should allow simple permissions to be specified" do
9
17
  subject.moo!
10
18
 
@@ -73,7 +81,7 @@ module Allowance
73
81
  subject.scoped_model(:view, model).should == scoped_model
74
82
  end
75
83
 
76
- it "should allow scopes to be defined through where conditions" do
84
+ it "should allow scopes to be defined through a conditions hash" do
77
85
  model = mock
78
86
  model.should_receive(:where).with(:awesome => true).and_return(scoped_model = mock)
79
87
 
@@ -81,6 +89,24 @@ module Allowance
81
89
 
82
90
  subject.scoped_model(:view, model).should == scoped_model
83
91
  end
92
+
93
+ it "should allow scopes to be defined through a conditions string" do
94
+ model = mock
95
+ model.should_receive(:where).with("awesome = true").and_return(scoped_model = mock)
96
+
97
+ subject.view! model, "awesome = true"
98
+
99
+ subject.scoped_model(:view, model).should == scoped_model
100
+ end
101
+
102
+ it "should allow scopes to be defined through a conditions array" do
103
+ model = mock
104
+ model.should_receive(:where).with(["awesome = ?", true]).and_return(scoped_model = mock)
105
+
106
+ subject.view! model, ["awesome = ?", true]
107
+
108
+ subject.scoped_model(:view, model).should == scoped_model
109
+ end
84
110
  end
85
111
  end
86
112
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: allowance
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-05-28 00:00:00.000000000 Z
12
+ date: 2012-05-29 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
16
- requirement: &70168031557120 !ruby/object:Gem::Requirement
16
+ requirement: &70231867448560 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *70168031557120
24
+ version_requirements: *70231867448560
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rspec
27
- requirement: &70168031554220 !ruby/object:Gem::Requirement
27
+ requirement: &70231867447800 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *70168031554220
35
+ version_requirements: *70231867447800
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: watchr
38
- requirement: &70168031553600 !ruby/object:Gem::Requirement
38
+ requirement: &70231867444600 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,7 +43,7 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *70168031553600
46
+ version_requirements: *70231867444600
47
47
  description: A generic, but decidedly awesome authorization control layer.
48
48
  email:
49
49
  - hendrik@mans.de
@@ -51,12 +51,12 @@ executables: []
51
51
  extensions: []
52
52
  extra_rdoc_files: []
53
53
  files:
54
+ - .documentup.json
54
55
  - .gitignore
55
56
  - .rspec
56
57
  - .travis.yml
57
58
  - .watchr
58
59
  - Gemfile
59
- - LICENSE
60
60
  - README.md
61
61
  - Rakefile
62
62
  - allowance.gemspec
data/LICENSE DELETED
@@ -1,22 +0,0 @@
1
- Copyright (c) 2012 Hendrik Mans
2
-
3
- MIT License
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining
6
- a copy of this software and associated documentation files (the
7
- "Software"), to deal in the Software without restriction, including
8
- without limitation the rights to use, copy, modify, merge, publish,
9
- distribute, sublicense, and/or sell copies of the Software, and to
10
- permit persons to whom the Software is furnished to do so, subject to
11
- the following conditions:
12
-
13
- The above copyright notice and this permission notice shall be
14
- included in all copies or substantial portions of the Software.
15
-
16
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.