cannabis 0.2.0

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,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
+