maybee 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
File without changes
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2012 Matthias Grosser
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,211 @@
1
+ # maybee
2
+
3
+ Simple Model-Based Authorization for Rails.
4
+
5
+ ## Install
6
+ ```
7
+ gem install maybee
8
+ ```
9
+
10
+ ## Usage
11
+
12
+ In maybee, subjects may do the things that objects allow them to do.
13
+
14
+ To have an authorization subject, which will in most cases be your user model:
15
+
16
+ ```ruby
17
+ class User < ActiveRecord::Base
18
+ acts_as_authorization_subject
19
+ end
20
+ ```
21
+ To have models that act as authorization objects, i.e. something that users may or may not use:
22
+
23
+ ```ruby
24
+ class Car < ActiveRecord::Base
25
+ acts_as_authorization_object
26
+ end
27
+ ```
28
+
29
+ ### Defining access rules
30
+
31
+ Access rules are defined inside models using a simple DSL and may be named as you like. A named rule like
32
+
33
+ ```ruby
34
+ class Car < ActiveRecord::Base
35
+ acts_as_authorization_object
36
+
37
+ allows :to => :drive
38
+ end
39
+ ```
40
+ will have model instances respond `true` if asked
41
+
42
+ ```ruby
43
+ car.allow?(:drive, user)
44
+ => true
45
+ ```
46
+
47
+ Usually, you will want to restrict access based on some internal state of the model (the authorization object) or the user (the subject). This can be accomplished using the options `:if`, `:unless`, `:if_subject` and `:unless_subject`:
48
+
49
+ ```ruby
50
+ allows :to => :drive, :if => :license_plate_valid?, :if_subject => :has_drivers_license?
51
+ ```
52
+
53
+ With this declaration, the car would allow any (ruby) object to drive, if the car has a valid license plate and the ruby object responds to `#has_drivers_license?` with a true value.
54
+
55
+ In order to limit the access to instances of a certain class, you can include the desired subject class(es) in the rule declaration:
56
+
57
+ ```ruby
58
+ class User < ActiveRecord::Base
59
+ acts_as_authorization_subject
60
+ end
61
+
62
+ # only some users are actual drivers
63
+ class Driver < User
64
+ def drunk?
65
+ 0 == self.drinks
66
+ end
67
+ end
68
+
69
+ class Car < ActiveRecord::Base
70
+ acts_as_authorization_object
71
+
72
+ allows :drivers, :to => :drive, :unless_subject => :drunk?
73
+ end
74
+ ```
75
+
76
+ If you do not care for the subject class, you may also write
77
+
78
+ ```ruby
79
+ allows_to :drive, :if => ...
80
+ ```
81
+
82
+ which is the same as `allows :to => ...`
83
+
84
+ Multiple access rights may be given in the same declaration:
85
+
86
+ ```ruby
87
+ allows :drivers, :to => [:start, :drive], :if => ...
88
+ ```
89
+
90
+
91
+ ### Passing blocks
92
+
93
+ It is also possible to pass a proc to any of the conditional options:
94
+
95
+ ```ruby
96
+ allows :drivers, :to => :start, :if => lambda { |driver| gasoline_level > 0 }
97
+ ```
98
+
99
+ Blocks passed to `:if` and `:unless` are evaluated inside the authorization object, while `:if_subject` and `:unless_subject` get evaluated inside the authorization subject (the user).
100
+
101
+
102
+ ### Dealing with nil
103
+
104
+ In most cases, you will want to restrict authorizations to authorized subjects only. So maybee will refuse any access by default if the subject is `nil`. For the special case, where an access should also be granted if the subject is nil, use the `:allow_nil` option:
105
+
106
+ ```ruby
107
+ class Image
108
+ allows :users, :to => :view, :if => :publicly_accessible?, :allow_nil => true
109
+ allows :users, :to => :view, :if_subject => lambda { |image| self.company_id == image.company_id }
110
+
111
+ def publicly_accessible?
112
+ # implementation, or a simple attribute
113
+ end
114
+ end
115
+ ```
116
+
117
+ This would allow anyone (including `nil`) to view an image if it is publicly accessible, and allow users to view the images belonging to the same company as the user.
118
+
119
+ ### Authorizing create, update and destroy on the model level (in an MVC way)
120
+
121
+ There are three special accesses which limit creation, updating and destruction of records. By default, if a model is an authorization object, it will prevent new records from being created, and existing ones from being updated or destroyed. As models by default do not know about the application's `current_user`, maybee defines an `attr_accessor` on auth objects where the `authorization_subject` can be set by the controller.
122
+
123
+ In the simplest form, the access to create, update and destroy would be granted regardless of the `authorization_subject`. This would be the default behaviour of ActiveRecord, where besides validations there is no restriction on these operations:
124
+
125
+ ```ruby
126
+ allows_to :create, :update, :destroy, :allow_nil => true
127
+ ```
128
+
129
+ Say you have models for users and roles, and you want normal users not to be able to assign roles, but only admins:
130
+
131
+ ```ruby
132
+ class User < ActiveRecord::Base
133
+ acts_as_authorization_subject
134
+
135
+ has_many :user_roles, :dependent => :destroy
136
+ has_many :roles, :through => :user_roles
137
+ end
138
+
139
+
140
+ class Role < ActiveRecord::Base
141
+ end
142
+
143
+ class UserRole < ActiveRecord::Base
144
+ belongs_to :user
145
+ belongs_to :role
146
+
147
+ acts_as_authorization_object
148
+
149
+ allows :users, :to => [:create, :update, :destroy], :if_subject => :admin?
150
+ end
151
+ ```
152
+
153
+ Just adding a role to a user is no longer possible this way, as the association object `UserRole` requires an admin subject to be set in order to be created.
154
+
155
+ ```ruby
156
+ user_role = user.user_roles.build
157
+ user_role.save
158
+ => false
159
+ user_role.authorization_subject = current_user # current_user is an admin
160
+ user_role.save
161
+ => true
162
+ ```
163
+
164
+ ### Enforcing rules
165
+
166
+ The idea behind maybee was to do things in an explicit way, so it doesn't do any magic in the background, but it provides your implementation with methods to determine whether an access is authorized or not.
167
+
168
+ In a classic Rails application, the controller is responsible for restricting access to objects. However, more complex rules of what is allowed and what is not should not go into the controller code, but be placed inside the model itself. This is where maybee can be used.
169
+
170
+ ```ruby
171
+
172
+ class ImagesController < ApplicationController
173
+ before_filter :find_image
174
+
175
+ def show
176
+ render
177
+ end
178
+
179
+ private
180
+
181
+ def find_image
182
+ @image = Image.find_by_id(params[:id]) or return(not_found)
183
+ @image.allow?(:view, current_user) or return(forbidden)
184
+ end
185
+ end
186
+ ```
187
+
188
+ Instead of `allow?` you can always write `user.may?`
189
+
190
+ ```ruby
191
+ current_user.may?(:view, @image)
192
+ ```
193
+
194
+ ### User feedback
195
+
196
+ Usually, you want to give some feedback to the user when she attempted an access which was denied. Maybee provides two authorization query methods which set an error on the record every time an access was denied.
197
+
198
+ ```ruby
199
+ @image.authorize?(:destroy, user)
200
+ ```
201
+ sets an error on the image instance when the user is not allowed to destroy it.
202
+
203
+ ```ruby
204
+ current_user.authorized_to?(:destroy, @image)
205
+ ```
206
+
207
+ is equivalent.
208
+
209
+ ### Default authorization subject
210
+
211
+ For more generic implementations the subject argument to `authorize?` and `allow?` can be left out. It will then default to the value of the `authorization_subject` accessor, which should be set before, for example in a `before_filter`.
data/Rakefile ADDED
@@ -0,0 +1,22 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rdoc/task'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Run all tests'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.test_files = FileList['test/cases/*_test.rb']
12
+ t.verbose = true
13
+ end
14
+
15
+ desc 'Generate documentation'
16
+ Rake::RDocTask.new(:rdoc) do |rdoc|
17
+ rdoc.rdoc_dir = 'rdoc'
18
+ rdoc.title = 'Maybee'
19
+ rdoc.options << '--line-numbers' << '--inline-source'
20
+ rdoc.rdoc_files.include('README')
21
+ rdoc.rdoc_files.include('lib/**/*.rb')
22
+ end
@@ -0,0 +1,12 @@
1
+ de:
2
+
3
+ activerecord:
4
+ errors:
5
+ messages:
6
+ not_authorized: "Keine Berechtigung zum %{access} dieses Objekts vorhanden"
7
+
8
+ authorizations:
9
+ 'active_record/base':
10
+ create: Anlegen
11
+ update: Speichern
12
+ destroy: Löschen
@@ -0,0 +1,12 @@
1
+ en:
2
+
3
+ activerecord:
4
+ errors:
5
+ messages:
6
+ not_authorized: "You are not authorized to %{access} this object"
7
+
8
+ authorizations:
9
+ 'active_record/base':
10
+ create: create
11
+ update: update
12
+ destroy: destroy
@@ -0,0 +1,36 @@
1
+ module Maybee
2
+
3
+ class Authorization
4
+ attr_reader :access, :subject_classes, :conditionals, :allow_nil
5
+
6
+ def initialize(access, subject_classes, options)
7
+ raise ArgumentError, "Access name must be symbol" unless access.is_a?(Symbol)
8
+ @access = access
9
+ #raise ArgumentError, "Subject classes must be an array" unless subject_classes.is_a?(Array)
10
+ @subject_classes = subject_classes.empty? ? nil : subject_classes
11
+ options.assert_valid_keys(:if, :unless, :if_subject, :unless_subject, :allow_nil)
12
+ @allow_nil = options.delete(:allow_nil)
13
+ @conditionals = options.empty? ? nil : options
14
+ end
15
+
16
+ def granted?(object, subject)
17
+ return false if !@allow_nil && @subject_classes && @subject_classes.none? { |klass| subject.is_a?(klass) }
18
+ return true unless @conditionals
19
+ return true if @conditionals.all? do |clause, cond|
20
+ if :if_subject == clause || :unless_subject == clause
21
+ if @allow_nil && subject.nil?
22
+ next(true)
23
+ else
24
+ receiver, argument = subject, object
25
+ end
26
+ else
27
+ receiver, argument = object, subject
28
+ end
29
+ result = cond.is_a?(Proc) ? receiver.instance_exec(argument, &cond) : receiver.send(cond)
30
+ (:if_subject == clause || :if == clause) ? result : !result
31
+ end
32
+ false
33
+ end
34
+ end
35
+
36
+ end
@@ -0,0 +1,60 @@
1
+ module Maybee
2
+
3
+ module AuthorizationObject
4
+
5
+ def self.included(base)
6
+ base.extend ClassMethods
7
+ base.class_attribute :authorizations
8
+ base.authorizations = {}
9
+ base.class_eval do
10
+ attr_accessor :authorization_subject
11
+ before_create { authorize?(:create) }
12
+ before_update { authorize?(:update) }
13
+ before_destroy { authorize?(:destroy) }
14
+ end
15
+ end
16
+
17
+ module ClassMethods
18
+ def allows(*args)
19
+ options = args.extract_options!
20
+ accesses = Array(options.delete(:to))
21
+ raise ArgumentError, "No accesses given" if accesses.empty?
22
+ exclusive = options.delete(:exclusive)
23
+ subject_classes = args.map { |name| name.is_a?(Symbol) ? name.to_s.classify.constantize : name.constantize }
24
+ additional_authorizations = accesses.inject({}) { |hsh, access| hsh[access] = [Authorization.new(access, subject_classes, options)]; hsh }
25
+ self.authorizations = authorizations.merge(additional_authorizations) { |access, previous_auths, new_auths| exclusive ? new_auths : previous_auths + new_auths }
26
+ end
27
+
28
+ def allows_to(*accesses)
29
+ options = accesses.extract_options!
30
+ allows options.merge(:to => accesses)
31
+ end
32
+ end
33
+
34
+ def allow?(access, subject = authorization_subject)
35
+ authorizations = self.class.authorizations[access] or return(false)
36
+ authorizations.any? { |authorization| authorization.granted?(self, subject) }
37
+ end
38
+
39
+ def authorize?(access, subject = authorization_subject)
40
+ errors.clear
41
+ return true if allow?(access, subject)
42
+ defaults = ([ActiveRecord::Base] + self.class.lookup_ancestors).map do |klass|
43
+ :"#{self.class.i18n_scope}.authorizations.#{klass.model_name.i18n_key}.#{access}"
44
+ end
45
+ key = defaults.shift
46
+ errors.add(:base, :not_authorized, :access => I18n.translate(key, :default => defaults))
47
+ false
48
+ end
49
+
50
+ #def with_authorization_to(access, object = self)
51
+ # if authorization_user && authorization_user.may?(access, object)
52
+ # yield
53
+ # else
54
+ # false
55
+ # end
56
+ #end
57
+
58
+ end
59
+
60
+ end
@@ -0,0 +1,15 @@
1
+ module Maybee
2
+
3
+ module AuthorizationSubject
4
+
5
+ def may?(access, object)
6
+ object.allow?(access, self)
7
+ end
8
+
9
+ def authorized_to?(access, object)
10
+ object.authorize?(access, self)
11
+ end
12
+
13
+ end
14
+
15
+ end
@@ -0,0 +1,3 @@
1
+ files = Dir[File.join(File.dirname(__FILE__), '../locales/*.yml')]
2
+ I18n.load_path.concat(files)
3
+
@@ -0,0 +1,3 @@
1
+ module Maybee
2
+ VERSION = '0.0.1'
3
+ end
data/lib/maybee.rb ADDED
@@ -0,0 +1,37 @@
1
+ require 'active_record'
2
+ require 'active_support'
3
+ require 'i18n'
4
+
5
+ require 'maybee/version'
6
+ require 'maybee/authorization'
7
+ require 'maybee/authorization_object'
8
+ require 'maybee/authorization_subject'
9
+ require 'maybee/i18n'
10
+
11
+ module Maybee
12
+
13
+ def self.included(base) # :nodoc:
14
+ base.extend ClassMethods
15
+ end
16
+
17
+ module ClassMethods
18
+
19
+ #
20
+ #
21
+ #
22
+ def acts_as_authorization_object
23
+ include AuthorizationObject
24
+ end
25
+
26
+ #
27
+ #
28
+ #
29
+ def acts_as_authorization_subject
30
+ include AuthorizationSubject
31
+ end
32
+ end
33
+
34
+ end
35
+
36
+ ActiveRecord::Base.class_eval { include Maybee }
37
+
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: maybee
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Matthias Grosser
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-06-18 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: i18n
16
+ requirement: &2151917620 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '0.5'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *2151917620
25
+ - !ruby/object:Gem::Dependency
26
+ name: activerecord
27
+ requirement: &2151916900 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: 3.2.3
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *2151916900
36
+ - !ruby/object:Gem::Dependency
37
+ name: activesupport
38
+ requirement: &2151916160 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: 3.2.3
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *2151916160
47
+ - !ruby/object:Gem::Dependency
48
+ name: sqlite3
49
+ requirement: &2151915520 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *2151915520
58
+ - !ruby/object:Gem::Dependency
59
+ name: simplecov
60
+ requirement: &2151914880 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *2151914880
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: &2151914120 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: 0.8.7
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *2151914120
80
+ description: A simple, yet flexible approach to model-based authorization
81
+ email: mtgrosser@gmx.net
82
+ executables: []
83
+ extensions: []
84
+ extra_rdoc_files: []
85
+ files:
86
+ - lib/maybee/authorization.rb
87
+ - lib/maybee/authorization_object.rb
88
+ - lib/maybee/authorization_subject.rb
89
+ - lib/maybee/i18n.rb
90
+ - lib/maybee/version.rb
91
+ - lib/maybee.rb
92
+ - lib/locales/de.yml
93
+ - lib/locales/en.yml
94
+ - MIT-LICENSE
95
+ - README.md
96
+ - CHANGELOG
97
+ - Rakefile
98
+ homepage: http://rubygems.org/gems/maybee
99
+ licenses: []
100
+ post_install_message:
101
+ rdoc_options: []
102
+ require_paths:
103
+ - lib
104
+ required_ruby_version: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ required_rubygems_version: !ruby/object:Gem::Requirement
111
+ none: false
112
+ requirements:
113
+ - - ! '>='
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ requirements: []
117
+ rubyforge_project:
118
+ rubygems_version: 1.8.11
119
+ signing_key:
120
+ specification_version: 3
121
+ summary: Simple Model-Based Authorization for Rails
122
+ test_files: []