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.
- data/.gitignore +3 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.rdoc +136 -0
- data/Rakefile +11 -0
- data/cannabis.gemspec +22 -0
- data/lib/cannabis.rb +34 -0
- data/lib/cannabis/ables.rb +6 -0
- data/lib/cannabis/cans.rb +10 -0
- data/lib/cannabis/enforcers.rb +19 -0
- data/lib/cannabis/exceptions.rb +9 -0
- data/lib/cannabis/version.rb +3 -0
- data/test/ables_test.rb +24 -0
- data/test/cannabis_test.rb +29 -0
- data/test/cans_test.rb +83 -0
- data/test/enforcers_test.rb +33 -0
- data/test/test_helper.rb +33 -0
- metadata +97 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
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.
|
data/README.rdoc
ADDED
@@ -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.
|
data/Rakefile
ADDED
data/cannabis.gemspec
ADDED
@@ -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
|
data/lib/cannabis.rb
ADDED
@@ -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,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
|
data/test/ables_test.rb
ADDED
@@ -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
|
data/test/cans_test.rb
ADDED
@@ -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
|
data/test/test_helper.rb
ADDED
@@ -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
|
+
|