has_permission 0.2.8
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/VERSION.yml +4 -0
- data/lib/action_controller/has/permission.rb +39 -0
- data/lib/active_record/has/permission.rb +61 -0
- data/lib/has_permission.rb +11 -0
- data/lib/permission/array.rb +15 -0
- data/lib/permission/base.rb +85 -0
- data/lib/permission_exception.rb +2 -0
- data/test/array_test.rb +16 -0
- data/test/has_permission_test.rb +133 -0
- data/test/test_helper.rb +149 -0
- metadata +65 -0
data/VERSION.yml
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
module ActionController
|
2
|
+
module Has
|
3
|
+
module Permission
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(ClassMethods)
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
|
10
|
+
def requires_permission(*args)
|
11
|
+
options = args.extract_options!
|
12
|
+
args.each do |target|
|
13
|
+
self.class_eval <<-_RUBY
|
14
|
+
def #{target}_with_permission
|
15
|
+
klass = #{options[:class] || "controller_name.classify.constantize"}
|
16
|
+
klass.with_permission(current_user).can_#{target}? || #{options[:access_denied] || "access_denied"}
|
17
|
+
#{target}_without_permission
|
18
|
+
end
|
19
|
+
_RUBY
|
20
|
+
alias_method_chain target, "permission"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def permission_method_chain(*args)
|
25
|
+
args.each do |target|
|
26
|
+
self.class_eval <<-_RUBY
|
27
|
+
def #{target}_with_permission
|
28
|
+
#{target}_without_permission.with_permission(current_user)
|
29
|
+
end
|
30
|
+
_RUBY
|
31
|
+
alias_method_chain target, "permission"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Has
|
3
|
+
module Permission
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(ClassMethods)
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
|
10
|
+
def has_permission(options = {})
|
11
|
+
class_eval <<-_DEF
|
12
|
+
def self.permission_namespace
|
13
|
+
"#{options[:namespace] || 'Permission'}"
|
14
|
+
end
|
15
|
+
_DEF
|
16
|
+
extend ActiveRecord::Has::Permission::SingletonMethods
|
17
|
+
include ActiveRecord::Has::Permission::InstanceMethods
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
module SingletonMethods
|
23
|
+
|
24
|
+
def with_permission(user)
|
25
|
+
permission_class.new :user => user, :object => self
|
26
|
+
end
|
27
|
+
|
28
|
+
def permission_class
|
29
|
+
if respond_to?(:base_class)
|
30
|
+
begin
|
31
|
+
[permission_namespace, "#{self.to_s}Permission"].join('::').constantize
|
32
|
+
rescue
|
33
|
+
[permission_namespace ,"#{self.base_class.to_s}Permission"].join('::').constantize
|
34
|
+
end
|
35
|
+
elsif respond_to?(:proxy_reflection)
|
36
|
+
begin
|
37
|
+
[permission_namespace, "#{self.proxy_reflection.class_name}Permsission"].join('::').constantize
|
38
|
+
rescue
|
39
|
+
[permission_namespace, "#{self.proxy_reflection.class_name.constantize.base_class.to_s}Permission"].join('::').constantize
|
40
|
+
end
|
41
|
+
else
|
42
|
+
begin
|
43
|
+
[permission_namespace, "#{self.to_s}Permission"].join('::').constantize
|
44
|
+
rescue
|
45
|
+
[permission_namespace, "#{self.superclass.to_s}Permission"].join('::').constantize
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
module InstanceMethods
|
52
|
+
|
53
|
+
def with_permission(user)
|
54
|
+
self.class.permission_class.new :user => user, :object => self
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'active_record/has/permission'
|
3
|
+
require 'action_controller'
|
4
|
+
require 'action_controller/has/permission'
|
5
|
+
require 'permission/base'
|
6
|
+
require 'permission/array'
|
7
|
+
require 'permission_exception'
|
8
|
+
|
9
|
+
ActiveRecord::Base.send :include, ActiveRecord::Has::Permission
|
10
|
+
ActionController::Base.send :include, ActionController::Has::Permission
|
11
|
+
Array.send :include, Permission::Array
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Permission
|
2
|
+
module Array
|
3
|
+
|
4
|
+
# helper method for adding to an array based on permission i.e. [].push_if("some feature", Feature.with_permission(current_user).can_write?)
|
5
|
+
def push_if(*args)
|
6
|
+
raise ArgumentError.new "wrong number of arguments (#{args.length} for 2)" if args.length < 2
|
7
|
+
if args.pop
|
8
|
+
push(*args)
|
9
|
+
else
|
10
|
+
self
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Permission
|
2
|
+
class Base
|
3
|
+
require 'forwardable'
|
4
|
+
extend Forwardable
|
5
|
+
|
6
|
+
# delegate important object methods so they don't return the values for the proxy - am I missing any??
|
7
|
+
def_delegators :@object, :==, :=~, :class, :enum_for, :eql?, :new, :freeze, :frozen?,
|
8
|
+
:hash, :id, :instance_of?, :is_a?, :kind_of?,
|
9
|
+
:method, :methods, :object_id, :respond_to?, :send,
|
10
|
+
:taint, :tainted?, :to_a, :to_enum, :to_s, :to_yaml, :to_yaml_properties, :to_yaml_style,
|
11
|
+
:type, :untaint, :to_param
|
12
|
+
|
13
|
+
attr_accessor :user, :object
|
14
|
+
|
15
|
+
DEFAULT_FINDERS = /^((find|with).*|first|last|all|children|paginate|scoped|count)$/
|
16
|
+
DEFAULT_SEARCHES = /^search_.*/
|
17
|
+
|
18
|
+
def initialize(params)
|
19
|
+
@user = params[:user]
|
20
|
+
@object = params[:object]
|
21
|
+
end
|
22
|
+
|
23
|
+
def method_missing(method, *args)
|
24
|
+
object.send(method, *args)
|
25
|
+
end
|
26
|
+
|
27
|
+
def update_attribute(name, value)
|
28
|
+
if can_write?(name)
|
29
|
+
object.update_attribute(name, value)
|
30
|
+
else
|
31
|
+
raise PermissionException.new("#{user} does not have permission to access #{name} on #{object}")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def update_attributes(attributes)
|
36
|
+
object.update_attributes(attributes.reject{|key,value| !can_write?(key) })
|
37
|
+
end
|
38
|
+
|
39
|
+
def attribute_names
|
40
|
+
object.attribute_names.reject{|key| !can_read?(key) }
|
41
|
+
end
|
42
|
+
|
43
|
+
def column_names
|
44
|
+
object.column_names.reject{|key| !can_read?(key) }
|
45
|
+
end
|
46
|
+
|
47
|
+
def attributes
|
48
|
+
object.attributes.reject{|key,value| !can_read?(key) }
|
49
|
+
end
|
50
|
+
|
51
|
+
def read_attribute(attr_name)
|
52
|
+
if can_read?(attr_name)
|
53
|
+
object.read_attribute(attr_name)
|
54
|
+
else
|
55
|
+
raise PermissionException.new("#{user} does not have permission to access #{attr_name} on #{object}")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def write_attribute(attr_name, value)
|
60
|
+
if can_write?(attr_name)
|
61
|
+
object.write_attribute(attr_name, value)
|
62
|
+
else
|
63
|
+
raise PermissionException.new("#{user} does not have permission to access #{attr_name} on #{object}")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def can_read?(attr_name)
|
68
|
+
true
|
69
|
+
end
|
70
|
+
|
71
|
+
def can_write?(attr_name)
|
72
|
+
true
|
73
|
+
end
|
74
|
+
|
75
|
+
protected
|
76
|
+
|
77
|
+
def check_roles(user, roles, object)
|
78
|
+
roles.each do |role|
|
79
|
+
return true if user != nil && user.has_role?(user, object)
|
80
|
+
end
|
81
|
+
false
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
data/test/array_test.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class ArrayTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
should "have method push_if" do
|
6
|
+
assert_equal true, [].methods.include?("push_if")
|
7
|
+
end
|
8
|
+
|
9
|
+
should "not push if condition is false" do
|
10
|
+
assert_equal [], [].push_if("test", false)
|
11
|
+
end
|
12
|
+
|
13
|
+
should "push if condition is true" do
|
14
|
+
assert_equal ["test"], [].push_if("test", true)
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class HasPermissionTest < Test::Unit::TestCase
|
4
|
+
context "model instance" do
|
5
|
+
setup do
|
6
|
+
@model = Model.new
|
7
|
+
end
|
8
|
+
|
9
|
+
should "not intercept method" do
|
10
|
+
assert_equal "no permission", @model.some_method
|
11
|
+
end
|
12
|
+
|
13
|
+
should "intercept method" do
|
14
|
+
assert_equal "with permission", @model.with_permission(nil).some_method
|
15
|
+
end
|
16
|
+
|
17
|
+
should "use model id" do
|
18
|
+
assert_equal @model.id, @model.with_permission(nil).id
|
19
|
+
end
|
20
|
+
|
21
|
+
should "use model to_param" do
|
22
|
+
assert_equal @model.to_param, @model.with_permission(nil).to_param
|
23
|
+
end
|
24
|
+
|
25
|
+
should "use model class" do
|
26
|
+
assert_equal @model.class, @model.with_permission(nil).class
|
27
|
+
end
|
28
|
+
|
29
|
+
should "use model ==" do
|
30
|
+
assert @model.with_permission(nil) == @model
|
31
|
+
end
|
32
|
+
|
33
|
+
should "use model eql?" do
|
34
|
+
assert @model.with_permission(nil).eql?(@model)
|
35
|
+
end
|
36
|
+
|
37
|
+
should "throw PermissionException for attribute that does not allow reading" do
|
38
|
+
assert_raise PermissionException do
|
39
|
+
@model.with_permission(nil).read_attribute(:no_access)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
should "allow access for readable attribute" do
|
44
|
+
@model.with_permission(nil).read_attribute(:read_access)
|
45
|
+
end
|
46
|
+
|
47
|
+
should "throw PermissionException for attribute that does not allow writing" do
|
48
|
+
assert_raise PermissionException do
|
49
|
+
@model.with_permission(nil).write_attribute(:no_access, "test")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
should "allow access for writeable attribute" do
|
54
|
+
@model.with_permission(nil).write_attribute(:write_access, "test")
|
55
|
+
end
|
56
|
+
|
57
|
+
should "only allow writeable attribute for update attributes" do
|
58
|
+
@model.expects(:update_attributes).with(:write_access => "test")
|
59
|
+
@model.with_permission(nil).update_attributes(:no_access => "test", :write_access => "test")
|
60
|
+
end
|
61
|
+
|
62
|
+
should "only allow readable attributes for attributes" do
|
63
|
+
assert_equal [:read_access], @model.with_permission(nil).attributes.keys
|
64
|
+
end
|
65
|
+
|
66
|
+
should "only allow readable attributes for attribute_names" do
|
67
|
+
assert_equal [:read_access], @model.with_permission(nil).attribute_names
|
68
|
+
end
|
69
|
+
|
70
|
+
should "only allow readable attributes for column_names" do
|
71
|
+
assert_equal [:read_access], @model.with_permission(nil).column_names
|
72
|
+
end
|
73
|
+
|
74
|
+
should "only allow writeable attribute for update attribute" do
|
75
|
+
assert_raise PermissionException do
|
76
|
+
@model.with_permission(nil).update_attribute(:no_access, "test")
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
context "model class" do
|
83
|
+
should "not intercept method" do
|
84
|
+
assert_equal ["no permission"], Model.all
|
85
|
+
end
|
86
|
+
|
87
|
+
should "intercept method" do
|
88
|
+
assert_equal ["with permission"], Model.with_permission(nil).all
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context "controller permission" do
|
93
|
+
setup do
|
94
|
+
@controller = ModelController.new
|
95
|
+
end
|
96
|
+
|
97
|
+
should "call access denied for index" do
|
98
|
+
@controller.expects(:access_denied).once
|
99
|
+
@controller.index
|
100
|
+
end
|
101
|
+
|
102
|
+
context "with options" do
|
103
|
+
setup do
|
104
|
+
@controller2 = Model2Controller.new
|
105
|
+
end
|
106
|
+
|
107
|
+
should "call no access for index" do
|
108
|
+
@controller2.expects(:no_access).once
|
109
|
+
@controller2.index
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
should "use default namespace setting" do
|
116
|
+
assert_equal Permission::ModelPermission, Model.permission_class
|
117
|
+
end
|
118
|
+
|
119
|
+
should "override default namespace setting" do
|
120
|
+
assert_equal ModelBPermission, ModelB.permission_class
|
121
|
+
end
|
122
|
+
|
123
|
+
should "use class to_s method" do
|
124
|
+
assert_equal Model.to_s, Model.with_permission(nil).to_s
|
125
|
+
end
|
126
|
+
|
127
|
+
should "use class class method" do
|
128
|
+
assert_equal Model.class, Model.with_permission(nil).class
|
129
|
+
end
|
130
|
+
|
131
|
+
# TODO need to test proxy associations somehow
|
132
|
+
|
133
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'shoulda'
|
4
|
+
require 'mocha'
|
5
|
+
|
6
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
7
|
+
require 'has_permission'
|
8
|
+
|
9
|
+
# this doesn't seem like the best way to do this - maybe someone will suggest a better method
|
10
|
+
|
11
|
+
class Model
|
12
|
+
|
13
|
+
include ActiveRecord::Has::Permission
|
14
|
+
|
15
|
+
has_permission
|
16
|
+
|
17
|
+
def read_attribute(attr_name)
|
18
|
+
"test"
|
19
|
+
end
|
20
|
+
|
21
|
+
def write_attribute(attr_name, value)
|
22
|
+
value
|
23
|
+
end
|
24
|
+
|
25
|
+
def column_names
|
26
|
+
[:read_access, :no_access]
|
27
|
+
end
|
28
|
+
|
29
|
+
def attribute_names
|
30
|
+
[:read_access, :no_access]
|
31
|
+
end
|
32
|
+
|
33
|
+
def attributes
|
34
|
+
{:read_access => "test", :no_access => "test"}
|
35
|
+
end
|
36
|
+
|
37
|
+
def some_method
|
38
|
+
"no permission"
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.all
|
42
|
+
["no permission"]
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
class ModelB
|
48
|
+
include ActiveRecord::Has::Permission
|
49
|
+
|
50
|
+
has_permission :namespace => ''
|
51
|
+
end
|
52
|
+
|
53
|
+
class ModelBPermission < Permission::Base
|
54
|
+
def can_index?
|
55
|
+
false
|
56
|
+
end
|
57
|
+
|
58
|
+
def can_show?
|
59
|
+
true
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
module Permission
|
64
|
+
class ModelPermission < Permission::Base
|
65
|
+
|
66
|
+
def can_index?
|
67
|
+
false
|
68
|
+
end
|
69
|
+
|
70
|
+
def can_show?
|
71
|
+
true
|
72
|
+
end
|
73
|
+
|
74
|
+
def can_read?(attr_name)
|
75
|
+
case attr_name
|
76
|
+
when :read_access : true
|
77
|
+
when :no_access : false
|
78
|
+
else true
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def can_write?(attr_name)
|
83
|
+
case attr_name.to_s
|
84
|
+
when "write_access" : true
|
85
|
+
when "no_access" : false
|
86
|
+
else true
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def some_method
|
91
|
+
"with permission"
|
92
|
+
end
|
93
|
+
|
94
|
+
def method_missing(method, *args)
|
95
|
+
if method.to_s.match(DEFAULT_FINDERS)
|
96
|
+
["with permission"]
|
97
|
+
else
|
98
|
+
object.send(method, *args)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
class ModelController
|
105
|
+
include ActionController::Has::Permission
|
106
|
+
|
107
|
+
def controller_name
|
108
|
+
"model"
|
109
|
+
end
|
110
|
+
|
111
|
+
def index
|
112
|
+
"index"
|
113
|
+
end
|
114
|
+
|
115
|
+
def show
|
116
|
+
"show"
|
117
|
+
end
|
118
|
+
|
119
|
+
def current_user
|
120
|
+
nil
|
121
|
+
end
|
122
|
+
|
123
|
+
requires_permission :index, :show
|
124
|
+
end
|
125
|
+
|
126
|
+
class Model2Controller
|
127
|
+
include ActionController::Has::Permission
|
128
|
+
|
129
|
+
def controller_name
|
130
|
+
"model"
|
131
|
+
end
|
132
|
+
|
133
|
+
def index
|
134
|
+
"index"
|
135
|
+
end
|
136
|
+
|
137
|
+
def show
|
138
|
+
"show"
|
139
|
+
end
|
140
|
+
|
141
|
+
def current_user
|
142
|
+
nil
|
143
|
+
end
|
144
|
+
|
145
|
+
requires_permission :index, :show, :access_denied => 'no_access', :class => ModelB
|
146
|
+
end
|
147
|
+
|
148
|
+
class Test::Unit::TestCase
|
149
|
+
end
|
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: has_permission
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.8
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Brian Johnson
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-03-26 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Fine grained access control based on method interception. This is a proxy that allows you to define permissions on your models without breaking MVC by putting user code in your models.
|
17
|
+
email: github@brianjohnson.cc
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- VERSION.yml
|
26
|
+
- lib/action_controller/has/permission.rb
|
27
|
+
- lib/active_record/has/permission.rb
|
28
|
+
- lib/has_permission.rb
|
29
|
+
- lib/permission/array.rb
|
30
|
+
- lib/permission/base.rb
|
31
|
+
- lib/permission_exception.rb
|
32
|
+
- test/array_test.rb
|
33
|
+
- test/has_permission_test.rb
|
34
|
+
- test/test_helper.rb
|
35
|
+
has_rdoc: true
|
36
|
+
homepage: http://github.com/johnsbrn/has_permission
|
37
|
+
licenses: []
|
38
|
+
|
39
|
+
post_install_message:
|
40
|
+
rdoc_options:
|
41
|
+
- --inline-source
|
42
|
+
- --charset=UTF-8
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: "0"
|
50
|
+
version:
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: "0"
|
56
|
+
version:
|
57
|
+
requirements: []
|
58
|
+
|
59
|
+
rubyforge_project:
|
60
|
+
rubygems_version: 1.3.5
|
61
|
+
signing_key:
|
62
|
+
specification_version: 2
|
63
|
+
summary: Access control library
|
64
|
+
test_files: []
|
65
|
+
|