cannibal 0.5.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/README.mdown +114 -0
- data/lib/cannibal/actor.rb +24 -0
- data/lib/cannibal/permission_registry.rb +135 -0
- data/lib/cannibal/subject.rb +44 -0
- data/lib/cannibal.rb +3 -0
- metadata +71 -0
data/README.mdown
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
Cannibal
|
2
|
+
========
|
3
|
+
|
4
|
+
A simple permissions system for declaring and querying permissions between Ruby objects.
|
5
|
+
|
6
|
+
Background
|
7
|
+
----------
|
8
|
+
|
9
|
+
Cannibal is based around defining interactions between Actors and Subjects.
|
10
|
+
|
11
|
+
An Actor participates in your system as an agent that "does" things. Cannibal lets you declare what
|
12
|
+
each Actor can do, and Actors are queried to determine whether or not they are permitted to perform
|
13
|
+
a particular action.
|
14
|
+
|
15
|
+
You can turn any class into an Actor by including Cannibal::Actor within it.
|
16
|
+
|
17
|
+
A Subject participates in your system as something that is acted upon. You declare for each Subject
|
18
|
+
the conditions under which each Actor may or may not interact with it.
|
19
|
+
|
20
|
+
You can turn any class into a Subject by including Cannibal::Actor within it.
|
21
|
+
|
22
|
+
If for example you are using Cannibal in a Rails application, an example of an Actor might be a
|
23
|
+
User model. An example of a Subject might be a Task model. You may want all Users in the system to
|
24
|
+
be able to view all of the available Tasks, but you may want to restrict tasks to only be editable
|
25
|
+
by their creators.
|
26
|
+
|
27
|
+
Permissions can be set at various levels, starting at the Class level and getting finer grained
|
28
|
+
right down to the field or method level of your models.
|
29
|
+
|
30
|
+
In addition, you can specify static permissions (no User may modify a Task) or dynamic permissions
|
31
|
+
that are evaluated at query time (ie. a specific user may or may not be able to modify a specific
|
32
|
+
task, based on whatever rules you put forth).
|
33
|
+
|
34
|
+
Sample Scenarios
|
35
|
+
----------------
|
36
|
+
|
37
|
+
Class-level permissions:
|
38
|
+
|
39
|
+
class User
|
40
|
+
include Cannibal::Actor
|
41
|
+
end
|
42
|
+
|
43
|
+
class Thing
|
44
|
+
include Cannibal::Subject
|
45
|
+
allow User, :edit
|
46
|
+
end
|
47
|
+
|
48
|
+
@user = User.new
|
49
|
+
@thing = Thing.new
|
50
|
+
|
51
|
+
puts "Yay!" if @user.can? :edit, @thing
|
52
|
+
|
53
|
+
Actor object-level permissions:
|
54
|
+
|
55
|
+
class User
|
56
|
+
include Cannibal::Actor
|
57
|
+
attr_accessor :role
|
58
|
+
end
|
59
|
+
|
60
|
+
class Thing
|
61
|
+
include Cannibal::Subject
|
62
|
+
permission({
|
63
|
+
:actor => User,
|
64
|
+
:verb => :edit,
|
65
|
+
:actor_proc => Proc.new { |user|
|
66
|
+
if user.role == 'administrator'
|
67
|
+
true
|
68
|
+
else
|
69
|
+
false
|
70
|
+
end
|
71
|
+
}
|
72
|
+
})
|
73
|
+
end
|
74
|
+
|
75
|
+
@user = User.new; @user.role = 'user'
|
76
|
+
@admin = User.new; @user.role = 'administrator'
|
77
|
+
@thing = Thing.new
|
78
|
+
|
79
|
+
puts "Back off!" unless @user.can? :edit, @thing
|
80
|
+
puts "Yay!" if @admin.can? :edit, @thing
|
81
|
+
|
82
|
+
Actor and subject object-to-object level permissions:
|
83
|
+
|
84
|
+
class User
|
85
|
+
include Cannibal::Actor
|
86
|
+
attr_accessor :role
|
87
|
+
end
|
88
|
+
|
89
|
+
class Thing
|
90
|
+
include Cannibal::Subject
|
91
|
+
attr_accessor :owner
|
92
|
+
permission({
|
93
|
+
:actor => User,
|
94
|
+
:verb => :edit,
|
95
|
+
:proc => Proc.new { |user, thing|
|
96
|
+
if user.role == 'administrator' or user == thing.owner
|
97
|
+
true
|
98
|
+
else
|
99
|
+
false
|
100
|
+
end
|
101
|
+
}
|
102
|
+
})
|
103
|
+
end
|
104
|
+
|
105
|
+
@user_a = User.new; @user.role = 'user'
|
106
|
+
@user_b = User.new; @user.role = 'user'
|
107
|
+
@admin = User.new; @user.role = 'administrator'
|
108
|
+
|
109
|
+
@thing = Thing.new; @thing.owner = @user_a
|
110
|
+
|
111
|
+
puts "Back off!" unless @user_b.can? :edit, @thing
|
112
|
+
puts "Yay!" if @user_a.can? :edit, @thing
|
113
|
+
puts "Yay!" if @admin.can? :edit, @thing
|
114
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Cannibal
|
2
|
+
module Actor
|
3
|
+
|
4
|
+
module ClassMethods
|
5
|
+
def can?(verb, subject, attribute=nil)
|
6
|
+
permissions.allowed?(self, subject, verb, attribute)
|
7
|
+
end
|
8
|
+
|
9
|
+
def permissions
|
10
|
+
@@registry = Cannibal::PermissionRegistry.instance
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.included(klass)
|
15
|
+
klass.extend ClassMethods
|
16
|
+
end
|
17
|
+
|
18
|
+
def can?(verb, subject, attribute=nil)
|
19
|
+
self.class.permissions.allowed?(self, subject, verb, attribute)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
module Cannibal
|
4
|
+
class PermissionRegistry
|
5
|
+
include Singleton
|
6
|
+
|
7
|
+
def set(options)
|
8
|
+
actor = options[:actor]
|
9
|
+
verb = options[:verb]
|
10
|
+
subject = options[:subject]
|
11
|
+
|
12
|
+
if actor.is_a? Class
|
13
|
+
actor_class = actor
|
14
|
+
else
|
15
|
+
actor_class = actor.class
|
16
|
+
end
|
17
|
+
|
18
|
+
if subject.is_a? Class
|
19
|
+
subject_class = subject
|
20
|
+
else
|
21
|
+
subject_class = subject.class
|
22
|
+
end
|
23
|
+
|
24
|
+
actor_proc = options[:actor_proc]
|
25
|
+
gproc = options[:proc]
|
26
|
+
|
27
|
+
perm = options[:perm]
|
28
|
+
attributes = options[:attribute]
|
29
|
+
if attributes.nil?
|
30
|
+
# Set class-wide perms if no attributes specified
|
31
|
+
verb_hash(actor_class, subject_class, verb)[:perm] = perm unless perm.nil?
|
32
|
+
verb_hash(actor_class, subject_class, verb)[:actor_proc] = actor_proc unless actor_proc.nil?
|
33
|
+
verb_hash(actor_class, subject_class, verb)[:proc] = gproc unless gproc.nil?
|
34
|
+
else
|
35
|
+
attributes = [ attributes ] unless attributes.is_a? Array
|
36
|
+
attributes.each do |attribute|
|
37
|
+
attribute_hash(actor_class, subject_class, verb)[attribute] = {}
|
38
|
+
attribute_hash(actor_class, subject_class, verb)[attribute][:perm] = perm unless perm.nil?
|
39
|
+
attribute_hash(actor_class, subject_class, verb)[attribute][:actor_proc] = actor_proc unless actor_proc.nil?
|
40
|
+
attribute_hash(actor_class, subject_class, verb)[attribute][:proc] = gproc unless gproc.nil?
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def allowed?(actor, subject, verb, attribute=nil)
|
46
|
+
ok = false
|
47
|
+
|
48
|
+
if actor.is_a? Class
|
49
|
+
actor_class = actor
|
50
|
+
else
|
51
|
+
actor_class = actor.class
|
52
|
+
end
|
53
|
+
if subject.is_a? Class
|
54
|
+
subject_class = subject
|
55
|
+
else
|
56
|
+
subject_class = subject.class
|
57
|
+
end
|
58
|
+
|
59
|
+
ph = verb_hash(actor_class, subject_class, verb)
|
60
|
+
#puts "\n########### PERM HASH FOR #{actor_class} #{subject_class} #{verb} #{attribute}"
|
61
|
+
#puts "#{ph.inspect}\n\n"
|
62
|
+
|
63
|
+
# Check class perms first
|
64
|
+
if ph.has_key? :perm
|
65
|
+
ok = ph[:perm]
|
66
|
+
end
|
67
|
+
|
68
|
+
unless actor.is_a? Class
|
69
|
+
# Allow object perms to override
|
70
|
+
if ph.has_key? :actor_proc
|
71
|
+
ok = ph[:actor_proc].call actor
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
unless subject.is_a? Class
|
76
|
+
# Allow object-to-object perms to override
|
77
|
+
if ph.has_key? :proc
|
78
|
+
ok = ph[:proc].call actor, subject
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
unless attribute.nil?
|
83
|
+
# puts "Evaluating attribute #{attribute}"
|
84
|
+
ah = attribute_hash(actor_class, subject_class, verb)
|
85
|
+
# puts ah.inspect
|
86
|
+
if ah.has_key? attribute
|
87
|
+
# puts "Found key #{attribute}"
|
88
|
+
|
89
|
+
if ah[attribute].has_key? :perm
|
90
|
+
# puts "Setting from perm"
|
91
|
+
ok = ah[attribute][:perm]
|
92
|
+
end
|
93
|
+
|
94
|
+
unless actor.is_a? Class or ah[attribute][:actor_proc].nil?
|
95
|
+
# puts "Setting from actor_proc"
|
96
|
+
ok = ah[attribute][:actor_proc].call actor
|
97
|
+
end
|
98
|
+
|
99
|
+
unless subject.is_a? Class or ah[attribute][:proc].nil?
|
100
|
+
# puts "Setting from proc"
|
101
|
+
ok = ah[attribute][:proc].call actor, subject
|
102
|
+
end
|
103
|
+
end
|
104
|
+
# puts "Found #{ok}"
|
105
|
+
end
|
106
|
+
|
107
|
+
ok
|
108
|
+
end
|
109
|
+
|
110
|
+
def permstore
|
111
|
+
@perms ||= {}
|
112
|
+
end
|
113
|
+
|
114
|
+
def reset
|
115
|
+
@perms = {}
|
116
|
+
end
|
117
|
+
|
118
|
+
def verb_hash(actor, subject, verb)
|
119
|
+
actor_hash = hash_or_init permstore, actor
|
120
|
+
subject_hash = hash_or_init actor_hash, subject
|
121
|
+
hash_or_init subject_hash, verb
|
122
|
+
end
|
123
|
+
|
124
|
+
def attribute_hash(actor, subject, verb)
|
125
|
+
hash_or_init verb_hash(actor, subject, verb), :attributes
|
126
|
+
end
|
127
|
+
|
128
|
+
def hash_or_init(hash, key)
|
129
|
+
unless hash.include? key
|
130
|
+
hash[key] = {}
|
131
|
+
end
|
132
|
+
hash[key]
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Cannibal
|
2
|
+
|
3
|
+
module Subject
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
|
7
|
+
def allow(actor, verb, attribute=nil)
|
8
|
+
permissions.set(
|
9
|
+
:actor => actor,
|
10
|
+
:verb => verb,
|
11
|
+
:subject => self,
|
12
|
+
:attribute => attribute,
|
13
|
+
:perm => true
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
def deny(actor, verb, attribute=nil)
|
18
|
+
permissions.set(
|
19
|
+
:actor => actor,
|
20
|
+
:verb => verb,
|
21
|
+
:subject => self,
|
22
|
+
:attribute => attribute,
|
23
|
+
:perm => false
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
def permission(options)
|
28
|
+
options[:subject] = self
|
29
|
+
permissions.set(options)
|
30
|
+
end
|
31
|
+
|
32
|
+
def permissions
|
33
|
+
@registry ||= Cannibal::PermissionRegistry.instance
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.included(klass)
|
39
|
+
klass.extend ClassMethods
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
data/lib/cannibal.rb
ADDED
metadata
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cannibal
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 11
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 5
|
9
|
+
- 0
|
10
|
+
version: 0.5.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Three Wise Men
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-08-17 00:00:00 -04:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: Use this library in a Ruby application to provide permission declaration and querying capabilities between Ruby objects.
|
23
|
+
email: info @nospam@ threewisemen.ca
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files:
|
29
|
+
- README.mdown
|
30
|
+
files:
|
31
|
+
- lib/cannibal.rb
|
32
|
+
- lib/cannibal/actor.rb
|
33
|
+
- lib/cannibal/permission_registry.rb
|
34
|
+
- lib/cannibal/subject.rb
|
35
|
+
- README.mdown
|
36
|
+
has_rdoc: true
|
37
|
+
homepage:
|
38
|
+
licenses: []
|
39
|
+
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options: []
|
42
|
+
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
none: false
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
hash: 3
|
51
|
+
segments:
|
52
|
+
- 0
|
53
|
+
version: "0"
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 3
|
60
|
+
segments:
|
61
|
+
- 0
|
62
|
+
version: "0"
|
63
|
+
requirements: []
|
64
|
+
|
65
|
+
rubyforge_project:
|
66
|
+
rubygems_version: 1.3.7
|
67
|
+
signing_key:
|
68
|
+
specification_version: 3
|
69
|
+
summary: Permission framework for Ruby objects
|
70
|
+
test_files: []
|
71
|
+
|