authority 0.0.1

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.
@@ -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