cannabis 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ pkg/*
2
+ *.gem
3
+ .bundle
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source :gemcutter
2
+
3
+ # Specify your gem's dependencies in cannabis.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 John Nunemaker
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.
@@ -0,0 +1,136 @@
1
+ = Cannabis
2
+
3
+ Very simple permissions that I have used on my last several projects
4
+ so I figured it was time to abstract and wrap up into something more
5
+ easily reusable.
6
+
7
+ == Cans
8
+
9
+ Whatever class you want all permissions to run through should include
10
+ Cannabis::Cans.
11
+
12
+ class User
13
+ include MongoMapper::Document
14
+ include Cannabis::Cans
15
+ end
16
+
17
+ This means that an instance of a user automatically gets can methods
18
+ for the default REST actions: can_view?(resource),
19
+ can_create?(resource), can_update?(resource), can_destroy?(resource).
20
+
21
+ == Ables
22
+
23
+ Each of the can methods simply calls the related "able" method
24
+ (viewable, creatable, updatable, destroyable) for the action (view,
25
+ create, update, delete). Cannabis comes with defaults for this methods
26
+ that you can then override as makes sense for your permissions.
27
+
28
+ class Article
29
+ include MongoMapper::Document
30
+ include Cannabis::Ables
31
+ end
32
+
33
+ Including Cannabis::Ables adds the able methods to the class including
34
+ it. In this instance, article now has viewable_by?(user),
35
+ creatable_by?(user), updatable_by?(user) and destroyable_by?(user).
36
+
37
+ Lets say an article can be viewed and created by anyone, but only
38
+ updated or destroyed by the user that created the article. To do that,
39
+ you could leave viewable_by? and creatable_by? alone as they default
40
+ to true and just override the other methods.
41
+
42
+ class Article
43
+ include MongoMapper::Document
44
+ include Cannabis::Ables
45
+ userstamps! # adds creator and updater
46
+
47
+ def updatable_by?(user)
48
+ creator == user
49
+ end
50
+
51
+ def destroyable_by?(user)
52
+ updatable_by?(user)
53
+ end
54
+ end
55
+
56
+ Lets look at some sample code now:
57
+
58
+ john = User.create(:name => 'John')
59
+ steve = User.create(:name =. 'Steve')
60
+
61
+ ruby = Article.new(:title => 'Ruby')
62
+ john.can_create?(ruby) # true
63
+ steve.can_create?(ruby) # true
64
+
65
+ ruby.creator = john
66
+ ruby.save
67
+
68
+ john.can_view?(ruby) # true
69
+ steve.can_view?(ruby) # true
70
+
71
+ john.can_update?(ruby) # true
72
+ steve.can_update?(ruby) # false
73
+
74
+ john.can_destroy?(ruby) # true
75
+ steve.can_destroy?(ruby) # false
76
+
77
+ Now we can implement our permissions for each resource and then always
78
+ check whether a user can or cannot do something. This makes it all
79
+ really easy to test. Next, how would you use this in the controller.
80
+
81
+ == Enforcers
82
+
83
+ class ApplicationController
84
+ include Cannabis::Enforcers
85
+ end
86
+
87
+ Including Cannabis::Enforcers adds an enforce permission method for
88
+ each of the actions defined (by default
89
+ view/create/update/destroy). It is the same thing as doing this for
90
+ each Cannabis action:
91
+
92
+ class ApplicationController
93
+ include Cannabis::Enforcers
94
+
95
+ delegate :can_view?, :to => :current_user
96
+ helper_method :can_view? # so you can use it in your views
97
+ hide_action :can_view?
98
+
99
+ private
100
+ def enforce_view_permission(resource)
101
+ raise Cannabis::Transgression unless can_view?(resource)
102
+ end
103
+ end
104
+
105
+ Which means you can use it like this:
106
+
107
+ class ArticlesController < ApplicationController
108
+ def show
109
+ @article = Article.find!(params[:id])
110
+ enforce_view_permission(@article)
111
+ end
112
+ end
113
+
114
+ If the user can_view? the article, all is well. If not, a
115
+ Cannabis::Transgression is raised which you can decide how to handle
116
+ (show 404, slap them on the wrist, etc.).
117
+
118
+ == Adding Your Own Actions
119
+
120
+ You can add your own actions like this:
121
+
122
+ Cannabis.add(:publish, :publishable)
123
+
124
+ The first parameter is the can method (ie: can_publish?) and the
125
+ second is the able method (ie: publishable_by?).
126
+
127
+ == Review
128
+
129
+ So, lets review: cans go on user model, ables go on everything, you
130
+ override ables in each model where you want to enforce permissions,
131
+ and enforcers go after each time you find or initialize an object in a
132
+ controller. Bing, bang, boom.
133
+
134
+ == Copyright
135
+
136
+ Copyright (c) 2010 John Nunemaker. See LICENSE for details.
@@ -0,0 +1,11 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require "rake/testtask"
5
+ Rake::TestTask.new(:test) do |test|
6
+ test.libs << "lib" << "test"
7
+ test.pattern = "test/**/*_test.rb"
8
+ test.verbose = true
9
+ end
10
+
11
+ task :default => :test
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path("../lib/cannabis/version", __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "cannabis"
6
+ s.version = Cannabis::VERSION
7
+ s.platform = Gem::Platform::RUBY
8
+ s.authors = ["Jakub Kuźma"]
9
+ s.email = ["qoobaa@gmail.com"]
10
+ s.homepage = "http://rubygems.org/gems/cannabis"
11
+ s.summary = "Simple permissions system"
12
+ s.description = "Simple permissions that I have used on my last several projects so I figured it was time to abstract and wrap up into something more easily reusable."
13
+
14
+ s.required_rubygems_version = ">= 1.3.6"
15
+ s.rubyforge_project = "cannabis"
16
+
17
+ s.add_development_dependency "bundler", ">= 1.0.0"
18
+
19
+ s.files = `git ls-files`.split("\n")
20
+ s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
21
+ s.require_path = 'lib'
22
+ end
@@ -0,0 +1,34 @@
1
+ require "cannabis/ables"
2
+ require "cannabis/cans"
3
+ require "cannabis/enforcers"
4
+ require "cannabis/exceptions"
5
+ require "cannabis/version"
6
+
7
+ module Cannabis
8
+ # Default actions to an empty hash.
9
+ @actions = {}
10
+
11
+ # Returns hash of actions that have been added.
12
+ # {:view => :viewable, ...}
13
+ def self.actions
14
+ @actions
15
+ end
16
+
17
+ def self.cans
18
+ actions.keys
19
+ end
20
+
21
+ # Adds an action to actions and the correct methods to can and able modules.
22
+ #
23
+ # @param [Symbol] can?(method) The name of the can?([action]) method.
24
+ # @param [Symbol] resource_method The name of the [resource_method]_by? method.
25
+ def self.add(can, able)
26
+ @actions[can] = able
27
+ Ables.send(:define_method, "#{able}_by?") { |user| true }
28
+ end
29
+ end
30
+
31
+ Cannabis.add(:view, :viewable)
32
+ Cannabis.add(:create, :creatable)
33
+ Cannabis.add(:update, :updatable)
34
+ Cannabis.add(:destroy, :destroyable)
@@ -0,0 +1,6 @@
1
+ module Cannabis
2
+ # Module that holds all the [method]able_by? methods.
3
+ module Ables
4
+
5
+ end
6
+ end
@@ -0,0 +1,10 @@
1
+ module Cannabis
2
+ module Cans
3
+ def can?(can, resource)
4
+ return false if resource.nil?
5
+ able = Cannabis.actions[can]
6
+ raise Exceptions::InvalidAction if able.nil?
7
+ resource.send("#{able}_by?", self)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,19 @@
1
+ module Cannabis
2
+ module Enforcers
3
+ def self.included(controller)
4
+ controller.helper_method :can? if controller.respond_to?(:helper_method)
5
+ controller.hide_action :can? if controller.respond_to?(:hide_action)
6
+ end
7
+
8
+ # delegates the call to current_user
9
+ def can?(can, resource)
10
+ current_user.can?(can, resource)
11
+ end
12
+
13
+ private
14
+
15
+ def authorize!(can, resource)
16
+ raise Exceptions::Transgression unless can?(can, resource)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,9 @@
1
+ module Cannabis
2
+ module Exceptions
3
+ # Exception that gets raised when permissions are broken for whatever reason.
4
+ class Transgression < StandardError; end
5
+
6
+ # Exception that gets raised when wrong action given.
7
+ class InvalidAction < StandardError; end
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module Cannabis
2
+ VERSION = "0.2.0"
3
+ end
@@ -0,0 +1,24 @@
1
+ require "test_helper"
2
+
3
+ class AblesTest < MiniTest::Unit::TestCase
4
+ def setup
5
+ @resource = Resource.new
6
+ @user = MiniTest::Mock.new
7
+ end
8
+
9
+ def test_default_viewable_by_to_true
10
+ assert @resource.viewable_by?(@user)
11
+ end
12
+
13
+ def test_default_creatable_by_to_true
14
+ assert @resource.creatable_by?(@user)
15
+ end
16
+
17
+ def test_default_updatable_by_to_true
18
+ assert @resource.updatable_by?(@user)
19
+ end
20
+
21
+ def test_default_destroyable_by_to_true
22
+ assert @resource.destroyable_by?(@user)
23
+ end
24
+ end
@@ -0,0 +1,29 @@
1
+ require "test_helper"
2
+
3
+ class CannabisTest < MiniTest::Unit::TestCase
4
+ def test_have_view_action_by_default
5
+ assert_equal :viewable, Cannabis.actions[:view]
6
+ end
7
+
8
+ def test_have_create_action_by_default
9
+ assert_equal :creatable, Cannabis.actions[:create]
10
+ end
11
+
12
+ def test_have_update_action_by_default
13
+ assert_equal :updatable, Cannabis.actions[:update]
14
+ end
15
+
16
+ def test_have_destroy_action_by_default
17
+ assert_equal :destroyable, Cannabis.actions[:destroy]
18
+ end
19
+
20
+ def test_be_able_to_add_another_action
21
+ Cannabis.add(:publish, :publishable)
22
+ assert_equal :publishable, Cannabis.actions[:publish]
23
+ end
24
+
25
+ def test_know_cans
26
+ Cannabis.add(:publish, :publishable)
27
+ assert_equal %w(create destroy publish update view), Cannabis.cans.map(&:to_s).sort
28
+ end
29
+ end
@@ -0,0 +1,83 @@
1
+ require "test_helper"
2
+
3
+ class CansTest < MiniTest::Unit::TestCase
4
+ def setup
5
+ @user = User.new
6
+ end
7
+
8
+ # can_view?
9
+ def test_is_true_if_resource_is_viewable_by
10
+ resource = MiniTest::Mock.new
11
+ resource.expect(:viewable_by?, true, [@user])
12
+ assert @user.can?(:view, resource)
13
+ resource.verify
14
+ end
15
+
16
+ def test_is_false_if_resource_is_not_viewable_by
17
+ resource = MiniTest::Mock.new
18
+ resource.expect(:viewable_by?, false, [@user])
19
+ assert ! @user.can?(:view, resource)
20
+ resource.verify
21
+ end
22
+
23
+ def test_is_false_if_resource_is_nil
24
+ assert ! @user.can?(:view, nil)
25
+ end
26
+
27
+ # can_create?
28
+ def test_is_true_if_resource_is_creatable_by
29
+ resource = MiniTest::Mock.new
30
+ resource.expect(:creatable_by?, true, [@user])
31
+ assert @user.can?(:create, resource)
32
+ resource.verify
33
+ end
34
+
35
+ def test_is_false_if_resource_is_not_creatable_by
36
+ resource = MiniTest::Mock.new
37
+ resource.expect(:creatable_by?, false, [@user])
38
+ assert ! @user.can?(:create, resource)
39
+ resource.verify
40
+ end
41
+
42
+ def test_is_false_if_resource_is_nil
43
+ assert ! @user.can?(:create, nil)
44
+ end
45
+
46
+ # can_update?
47
+ def test_is_true_if_resource_is_updatable_by
48
+ resource = MiniTest::Mock.new
49
+ resource.expect(:updatable_by?, true, [@user])
50
+ assert @user.can?(:update, resource)
51
+ resource.verify
52
+ end
53
+
54
+ def test_is_false_if_resource_is_not_updatable_by
55
+ resource = MiniTest::Mock.new
56
+ resource.expect(:updatable_by?, false, [@user])
57
+ assert ! @user.can?(:update, resource)
58
+ resource.verify
59
+ end
60
+
61
+ def test_is_false_if_resource_is_nil
62
+ assert ! @user.can?(:update, nil)
63
+ end
64
+
65
+ # can_destroy?
66
+ def test_is_true_if_resource_is_destroyable_by
67
+ resource = MiniTest::Mock.new
68
+ resource.expect(:destroyable_by?, true, [@user])
69
+ assert @user.can?(:destroy, resource)
70
+ resource.verify
71
+ end
72
+
73
+ def test_is_false_if_resource_is_not_destroyable_by
74
+ resource = MiniTest::Mock.new
75
+ resource.expect(:destroyable_by?, false, [@user])
76
+ assert ! @user.can?(:destroy, resource)
77
+ resource.verify
78
+ end
79
+
80
+ def test_is_false_if_resource_is_nil
81
+ assert ! @user.can?(:destroy, nil)
82
+ end
83
+ end
@@ -0,0 +1,33 @@
1
+ require "test_helper"
2
+
3
+ class EnforcersTest < MiniTest::Unit::TestCase
4
+ def setup
5
+ @article = MiniTest::Mock.new
6
+ @user = MiniTest::Mock.new
7
+ @controller = Controller.new
8
+ @controller.article = @article
9
+ @controller.current_user = @user
10
+ end
11
+
12
+ def test_not_raise_error_if_can
13
+ @user.expect(:can?, true, [:view, @article])
14
+ @controller.show
15
+ @user.verify
16
+ end
17
+
18
+ def test_raises_if_invalid_action_given
19
+ @controller.current_user = User.new
20
+ assert_raises(Cannabis::Exceptions::InvalidAction) { @controller.vaporize }
21
+ end
22
+
23
+ def test_raise_error_if_cannot
24
+ @user.expect(:can?, false, [:view, @article])
25
+ assert_raises(Cannabis::Exceptions::Transgression) { @controller.show }
26
+ @user.verify
27
+ end
28
+
29
+ def test_be_able_to_override_can_xx_method
30
+ @controller.current_user = nil
31
+ assert_raises(Cannabis::Exceptions::Transgression) { @controller.update }
32
+ end
33
+ end
@@ -0,0 +1,33 @@
1
+ require "minitest/autorun"
2
+ require "cannabis"
3
+
4
+ class User
5
+ include Cannabis::Cans
6
+ end
7
+
8
+ class Resource
9
+ include Cannabis::Ables
10
+ end
11
+
12
+ class Controller
13
+ include Cannabis::Enforcers
14
+ attr_accessor :current_user, :article
15
+
16
+ # Overriding example
17
+ def can?(action, resource)
18
+ return false if action == :update and current_user.nil?
19
+ super
20
+ end
21
+
22
+ def show
23
+ authorize!(:view, article)
24
+ end
25
+
26
+ def vaporize
27
+ authorize!(:vaporize, article)
28
+ end
29
+
30
+ def update
31
+ authorize!(:update, article)
32
+ end
33
+ end
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cannabis
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 2
8
+ - 0
9
+ version: 0.2.0
10
+ platform: ruby
11
+ authors:
12
+ - "Jakub Ku\xC5\xBAma"
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-12-02 00:00:00 +01:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: bundler
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 1
30
+ - 0
31
+ - 0
32
+ version: 1.0.0
33
+ type: :development
34
+ version_requirements: *id001
35
+ description: Simple permissions that I have used on my last several projects so I figured it was time to abstract and wrap up into something more easily reusable.
36
+ email:
37
+ - qoobaa@gmail.com
38
+ executables: []
39
+
40
+ extensions: []
41
+
42
+ extra_rdoc_files: []
43
+
44
+ files:
45
+ - .gitignore
46
+ - Gemfile
47
+ - LICENSE
48
+ - README.rdoc
49
+ - Rakefile
50
+ - cannabis.gemspec
51
+ - lib/cannabis.rb
52
+ - lib/cannabis/ables.rb
53
+ - lib/cannabis/cans.rb
54
+ - lib/cannabis/enforcers.rb
55
+ - lib/cannabis/exceptions.rb
56
+ - lib/cannabis/version.rb
57
+ - test/ables_test.rb
58
+ - test/cannabis_test.rb
59
+ - test/cans_test.rb
60
+ - test/enforcers_test.rb
61
+ - test/test_helper.rb
62
+ has_rdoc: true
63
+ homepage: http://rubygems.org/gems/cannabis
64
+ licenses: []
65
+
66
+ post_install_message:
67
+ rdoc_options: []
68
+
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ segments:
77
+ - 0
78
+ version: "0"
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ none: false
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ segments:
85
+ - 1
86
+ - 3
87
+ - 6
88
+ version: 1.3.6
89
+ requirements: []
90
+
91
+ rubyforge_project: cannabis
92
+ rubygems_version: 1.3.7
93
+ signing_key:
94
+ specification_version: 3
95
+ summary: Simple permissions system
96
+ test_files: []
97
+