authority 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use --create default@authority
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in authority.gemspec
4
+ gemspec
5
+
6
+ gem 'rspec', '>= 2.8.0'
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Nathan Long
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.
@@ -0,0 +1,135 @@
1
+ # Authority
2
+
3
+ ## SUPER ALPHA VERSION
4
+
5
+ ## Overview
6
+
7
+ Authority gives you a clean and easy way to say, in your Rails app, **who** is allowed to do **what** with your models.
8
+
9
+ It assumes that you already have some kind of user object in your application.
10
+
11
+ The goals of Authority are:
12
+
13
+ - To allow broad, class-level rules. Examples:
14
+ - "Basic users cannot delete **any** Widget."
15
+ - "Only admin users can create Offices."
16
+
17
+ - To allow fine-grained, instance-level rules. Examples:
18
+ - "Management users can only edit schedules in their jurisdiction."
19
+ - "Users can't create playlists more than 20 songs long unless they've paid."
20
+
21
+ - To provide a clear syntax for permissions-based views. Example:
22
+ - `link_to 'Edit Widget', edit_widget_path(@widget) if current_user.can_edit?(@widget)`
23
+
24
+ - To gracefully handle any access violations: display a "you can't do that" screen and log the violation.
25
+
26
+ - To do all of this **without cluttering** either your controllers or your models. This is done by letting Authorizer classes do most of the work. More on that below.
27
+
28
+ ## Installation
29
+
30
+ Add this line to your application's Gemfile:
31
+
32
+ gem 'authority'
33
+
34
+ And then execute:
35
+
36
+ $ bundle
37
+
38
+ Or install it yourself as:
39
+
40
+ $ gem install authority
41
+
42
+ ## How it works
43
+
44
+ In broad terms, the authorization process flows like this:
45
+
46
+ - A request comes to a model, either the class or an instance, saying "can this user do this action to you?"
47
+ - The model passes that question to its Authorizer
48
+ - The Authorizer checks whatever user properties and business rules are relevant to answer that question.
49
+ - The answer is passed back up to the model, then back to the original caller
50
+
51
+ ## Usage
52
+
53
+ ### Users
54
+
55
+ Your user model (whatever you call it) should `include Authority::UserAbilities`. This defines methods like `can_edit?(resource)`, which are just nice shortcuts for `resource.editable_by?(user)`. (TODO: Add this module).
56
+
57
+ ### Models
58
+
59
+ In your models, simply `include Authority::Abilities`. This sets up both class-level and instance-level methods like `creatable_by?(user)`, etc, all of which delegate to the model's corresponding authorizer. For example, the `Rabbit` model would delegate to `RabbitAuthorizer`.
60
+
61
+ ### Controllers
62
+
63
+ #### Basic Usage
64
+
65
+ In your controllers, add this method call:
66
+
67
+ `check_authorization_on ModelName`
68
+
69
+ That sets up a `:before_filter` that calls your class-level methods before each action. For instance, before running the `update` action, it will check whether `ModelName` is `updatable_by?` the current user at a class level. A return value of false means "this user can never update models of this class."
70
+
71
+ If that's all you need, one line does it.
72
+
73
+ #### In-action usage
74
+
75
+ If you need to check some attributes of a model instance to decide if an action is permissible, you can use `check_authorization_for(:action, @model_instance, @user)` (TODO: determine this)
76
+
77
+ ### Authorizers
78
+
79
+ Authorizers should be added under `app/authorizers`, one for each of your models. Each authorizer should correspond to a single model. So if you have `app/models/laser_cannon.rb`, you should have, at minimum:
80
+
81
+ # app/authorizers/laser_cannon_authorizer.rb
82
+ class LaserCannonAuthorizer < Authority::Authorizer
83
+ end
84
+
85
+ These are where your actual authorization logic goes. You do have to specify your own business rules, but Authority comes with the following baked in:
86
+
87
+ - All class-level methods defined on `Authority::Authorizer` return false by default; you must override them in your Authorizers to grant permissions. This whitelisting approach will keep you from accidentally allowing things you didn't intend.
88
+ - All instance-level methods defined on `Authority::Authorizer` call their corresponding class-level method by default.
89
+
90
+ This combination means that, with this code:
91
+
92
+ # app/authorizers/laser_cannon_authorizer.rb
93
+ class LaserCannonAuthorizer < Authority::Authorizer
94
+ end
95
+
96
+ ... you can already do the following:
97
+
98
+ current_user.can_create?(LaserCannon) # false; all inherited class-level permissions are false
99
+ current_user.can_create?(@laser_cannon) # false; instance-level permissions check class-level ones by default
100
+
101
+ If you update your authorizer as follows:
102
+
103
+ # app/authorizers/laser_cannon_authorizer.rb
104
+ class LaserCannonAuthorizer < Authority::Authorizer
105
+
106
+ def self.creatable_by?(user) # class-level permission
107
+ true
108
+ end
109
+
110
+ def deletable_by?(user) # instance_level permission
111
+ user.first_name == 'Larry' && Date.today.friday?
112
+ end
113
+
114
+ end
115
+
116
+ ... you can now do this following:
117
+
118
+ current_user.can_create?(LaserCannon) # true, per class method above
119
+ current_user.can_create?(@laser_cannon) # true; inherited instance method calls class method
120
+ current_user.can_delete?(@laser_cannon) # Only Larry, and only on Fridays
121
+
122
+ ## TODO
123
+
124
+ - Add a module, to be included in whatever user class an app has, which defines all the `can_(verb)` methods.
125
+ - Determine exact syntax for checking rules during a controller action.
126
+ - Add ability to customize default authorization
127
+ - Add customizable logger for authorization violations
128
+
129
+ ## Contributing
130
+
131
+ 1. Fork it
132
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
133
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
134
+ 4. Push to the branch (`git push origin my-new-feature`)
135
+ 5. Create new Pull Request
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/authority/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Nathan Long", "Adam Hunter"]
6
+ gem.email = ["nathanmlong@gmail.com", "adamhunter@me.com"]
7
+ gem.description = %q{Gem for managing authorization on model actions in Rails}
8
+ gem.summary = %q{Authority gives you a clean and easy way to say, in your Rails app, **who** is allowed to do **what** with your models.}
9
+ gem.homepage = "https://github.com/nathanl/authority"
10
+
11
+ gem.add_dependency "rails", ">= 3.0.0"
12
+ gem.add_development_dependency "bundler", ">= 1.0.0"
13
+
14
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
15
+ gem.files = `git ls-files`.split("\n")
16
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ gem.name = "authority"
18
+ gem.require_paths = ["lib"]
19
+ gem.version = Authority::VERSION
20
+ end
@@ -0,0 +1,11 @@
1
+ require 'active_support/concern'
2
+ require 'active_support/core_ext/class/attribute'
3
+ require 'active_support/core_ext/string/inflections'
4
+
5
+ module Authority
6
+ ADJECTIVES = %w[creatable readable updatable deletable]
7
+ end
8
+
9
+ require 'authority/abilities'
10
+ require 'authority/authorizer'
11
+ require 'authority/version'
@@ -0,0 +1,39 @@
1
+ module Authority
2
+ module Abilities
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class_attribute :authorizer_name
7
+
8
+ self.authorizer_name = "#{name}Authorizer"
9
+ end
10
+
11
+ module ClassMethods
12
+
13
+ ADJECTIVES.each do |adjective|
14
+
15
+ # Metaprogram needed methods, allowing for nice backtraces
16
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
17
+ def #{adjective}_by?(actor)
18
+ authorizer.#{adjective}_by?(actor)
19
+ end
20
+ RUBY
21
+ end
22
+
23
+ def authorizer
24
+ @authorizer ||= authorizer_name.constantize
25
+ end
26
+ end
27
+
28
+ ADJECTIVES.each do |adjective|
29
+
30
+ # Metaprogram needed methods, allowing for nice backtraces
31
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
32
+ def #{adjective}_by?(actor)
33
+ self.class.authorizer.new(self).#{adjective}_by?(actor)
34
+ end
35
+ RUBY
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,27 @@
1
+ module Authority
2
+ class Authorizer
3
+
4
+ attr_reader :resource
5
+
6
+ def initialize(resource)
7
+ @resource = resource
8
+ end
9
+
10
+ ADJECTIVES.each do |adjective|
11
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
12
+ def self.#{adjective}_by?(actor)
13
+ false
14
+ end
15
+ RUBY
16
+ end
17
+
18
+ ADJECTIVES.each do |adjective|
19
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
20
+ def #{adjective}_by?(actor)
21
+ false
22
+ end
23
+ RUBY
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,3 @@
1
+ module Authority
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,88 @@
1
+ require 'spec_helper'
2
+ require 'support/ability_model'
3
+ require 'support/actor'
4
+
5
+ describe Authority::Abilities do
6
+
7
+ before :each do
8
+ @actor = Actor.new
9
+ end
10
+
11
+ describe "authorizer" do
12
+
13
+ it "should have a class attribute getter for authorizer_name" do
14
+ AbilityModel.should respond_to(:authorizer_name)
15
+ end
16
+
17
+ it "should have a class attribute setter for authorizer_name" do
18
+ AbilityModel.should respond_to(:authorizer_name=)
19
+ end
20
+
21
+ it "should have a default authorizer_name of '(ClassName)Authorizer'" do
22
+ AbilityModel.authorizer_name.should eq("AbilityModelAuthorizer")
23
+ end
24
+
25
+ it "should constantize the authorizer name as the authorizer" do
26
+ AbilityModel.instance_variable_set(:@authorizer, nil)
27
+ AbilityModel.authorizer_name.should_receive(:constantize)
28
+ AbilityModel.authorizer
29
+ end
30
+
31
+ it "should memoize the authorizer to avoid reconstantizing" do
32
+ AbilityModel.authorizer
33
+ AbilityModel.authorizer_name.should_not_receive(:constantize)
34
+ AbilityModel.authorizer
35
+ end
36
+
37
+ end
38
+
39
+ describe "class methods" do
40
+
41
+ Authority::ADJECTIVES.each do |adjective|
42
+ method_name = "#{adjective}_by?"
43
+
44
+ it "should respond to `#{method_name}`" do
45
+ AbilityModel.should respond_to(method_name)
46
+ end
47
+
48
+ it "should delegate `#{method_name}` to its authorizer class" do
49
+ AbilityModel.authorizer.should_receive(method_name).with(@actor)
50
+ AbilityModel.send(method_name, @actor)
51
+ end
52
+
53
+ end
54
+
55
+ end
56
+
57
+ describe "instance methods" do
58
+
59
+ before :each do
60
+ @ability_model = AbilityModel.new
61
+ @authorizer = AbilityModel.authorizer.new(@ability_model)
62
+ end
63
+
64
+ Authority::ADJECTIVES.each do |adjective|
65
+ method_name = "#{adjective}_by?"
66
+
67
+ it "should respond to `#{method_name}`" do
68
+ @ability_model.should respond_to(method_name)
69
+ end
70
+
71
+ it "should delegate `#{method_name}` to a new authorizer instance" do
72
+ AbilityModel.authorizer.stub(:new).and_return(@authorizer)
73
+ @authorizer.should_receive(method_name).with(@actor)
74
+ @ability_model.send(method_name, @actor)
75
+ end
76
+
77
+ it "should always create a new authorizer instance when checking `#{method_name}`" do
78
+ 2.times do
79
+ @ability_model.class.authorizer.should_receive(:new).with(@ability_model).and_return(@authorizer)
80
+ @ability_model.send(method_name, @actor)
81
+ end
82
+ end
83
+
84
+ end
85
+
86
+ end
87
+
88
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+ require 'support/ability_model'
3
+
4
+ describe Authority::Authorizer do
5
+
6
+ before :each do
7
+ @ability_model = AbilityModel.new
8
+ @authorizer = Authority::Authorizer.new(@ability_model)
9
+ end
10
+
11
+ it "should take a resource instance in its initializer" do
12
+ @authorizer.resource.should eq(@ability_model)
13
+ end
14
+
15
+ describe "class methods" do
16
+
17
+ Authority::ADJECTIVES.each do |adjective|
18
+ method_name = "#{adjective}_by?"
19
+
20
+ it "should respond to `#{method_name}`" do
21
+ Authority::Authorizer.should respond_to(method_name)
22
+ end
23
+
24
+ end
25
+
26
+ end
27
+
28
+ describe "instance methods" do
29
+
30
+ Authority::ADJECTIVES.each do |adjective|
31
+ method_name = "#{adjective}_by?"
32
+
33
+ it "should respond to `#{method_name}`" do
34
+ @authorizer.should respond_to(method_name)
35
+ end
36
+
37
+ end
38
+
39
+ end
40
+
41
+ end
42
+
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+
3
+ describe Authority do
4
+ it "should have a constant of abilities" do
5
+ Authority::ADJECTIVES.should be_an(Array)
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'rspec'
3
+ require 'authority'
4
+
5
+ RSpec.configure do |config|
6
+ config.mock_with :rspec
7
+ end
@@ -0,0 +1,6 @@
1
+ class AbilityModel
2
+ include Authority::Abilities
3
+ end
4
+
5
+ class AbilityModelAuthorizer < Authority::Authorizer
6
+ end
@@ -0,0 +1,17 @@
1
+ class Actor
2
+ def can_create?(resource)
3
+ resource.creatable_by?(self)
4
+ end
5
+
6
+ def can_read?(resource)
7
+ resource.readable_by?(self)
8
+ end
9
+
10
+ def can_update?(resource)
11
+ resource.updatable_by?(self)
12
+ end
13
+
14
+ def can_delete?(resource)
15
+ resource.deletable_by?(self)
16
+ end
17
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: authority
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Nathan Long
9
+ - Adam Hunter
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2012-03-12 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rails
17
+ requirement: &2152357800 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: 3.0.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: *2152357800
26
+ - !ruby/object:Gem::Dependency
27
+ name: bundler
28
+ requirement: &2152357040 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: 1.0.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: *2152357040
37
+ description: Gem for managing authorization on model actions in Rails
38
+ email:
39
+ - nathanmlong@gmail.com
40
+ - adamhunter@me.com
41
+ executables: []
42
+ extensions: []
43
+ extra_rdoc_files: []
44
+ files:
45
+ - .gitignore
46
+ - .rvmrc
47
+ - Gemfile
48
+ - LICENSE
49
+ - README.md
50
+ - Rakefile
51
+ - authority.gemspec
52
+ - lib/authority.rb
53
+ - lib/authority/abilities.rb
54
+ - lib/authority/authorizer.rb
55
+ - lib/authority/version.rb
56
+ - spec/authority/abilities_spec.rb
57
+ - spec/authority/authorizer_spec.rb
58
+ - spec/authority_spec.rb
59
+ - spec/spec_helper.rb
60
+ - spec/support/ability_model.rb
61
+ - spec/support/actor.rb
62
+ homepage: https://github.com/nathanl/authority
63
+ licenses: []
64
+ post_install_message:
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ! '>='
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ! '>='
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ requirements: []
81
+ rubyforge_project:
82
+ rubygems_version: 1.8.16
83
+ signing_key:
84
+ specification_version: 3
85
+ summary: Authority gives you a clean and easy way to say, in your Rails app, **who**
86
+ is allowed to do **what** with your models.
87
+ test_files:
88
+ - spec/authority/abilities_spec.rb
89
+ - spec/authority/authorizer_spec.rb
90
+ - spec/authority_spec.rb
91
+ - spec/spec_helper.rb
92
+ - spec/support/ability_model.rb
93
+ - spec/support/actor.rb