acts_as_wrapped_class 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,5 @@
1
+ == 1.0.0 / 2007-10-22
2
+
3
+ * Release of the initial version of the gem
4
+ * BUG: missing constants
5
+
data/Manifest.txt ADDED
@@ -0,0 +1,7 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ lib/acts_as_wrapped_class.rb
6
+ lib/wrapper_base.rb
7
+ test/test_acts_as_wrapped_class.rb
data/README.txt ADDED
@@ -0,0 +1,63 @@
1
+ ActsAsWrappedClass
2
+ by David Stevenson
3
+ http://elctech.com/blog
4
+
5
+ == DESCRIPTION:
6
+
7
+ ActsAsWrappedClass is designed to automatically generate a wrapper for an object that you don't want to be allowed to access certain methods in. This is useful in cases where you want to sandbox what users' code can and can't do, by providing them access to the wrapper classes rather than the original classes.
8
+
9
+ == FEATURES/PROBLEMS:
10
+
11
+ * Wrappers do not dispatch const_missing yet, so constants are not accessible yet.
12
+
13
+ == SYNOPSIS:
14
+
15
+ class Something
16
+ acts_as_wrapped_class :methods => [:safe_method]
17
+ # SomethingWrapper is now defined
18
+
19
+ def safe_method # allowed to access this method through SomethingWrapper
20
+ Something.new
21
+ end
22
+
23
+ def unsafe_method # not allowed to access this method through SomethingWrapper
24
+ end
25
+ end
26
+
27
+ s = Something.new
28
+ wrapper = s.to_wrapper
29
+ wrapper.safe_method # returns a new SomethingWrapper
30
+ wrapper.unsafe_method # raises an exception
31
+
32
+ == REQUIREMENTS:
33
+
34
+ * none
35
+
36
+ == INSTALL:
37
+
38
+ * sudo gem install acts_as_wrapped_class
39
+
40
+ == LICENSE:
41
+
42
+ (The MIT License)
43
+
44
+ Copyright (c) 2007 David Stevenson
45
+
46
+ Permission is hereby granted, free of charge, to any person obtaining
47
+ a copy of this software and associated documentation files (the
48
+ 'Software'), to deal in the Software without restriction, including
49
+ without limitation the rights to use, copy, modify, merge, publish,
50
+ distribute, sublicense, and/or sell copies of the Software, and to
51
+ permit persons to whom the Software is furnished to do so, subject to
52
+ the following conditions:
53
+
54
+ The above copyright notice and this permission notice shall be
55
+ included in all copies or substantial portions of the Software.
56
+
57
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
58
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
59
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
60
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
61
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
62
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
63
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+ require './lib/acts_as_wrapped_class.rb'
6
+
7
+ Hoe.new('acts_as_wrapped_class', ActsAsWrappedClass::VERSION) do |p|
8
+ p.rubyforge_name = 'acts_as_wrapped_class'
9
+ p.author = 'David Stevenson'
10
+ p.email = 'ds@elctech.com'
11
+ p.summary = 'automatically generate wrapper classes which restrict access to methods and constants in the wrapped class'
12
+ p.description = p.paragraphs_of('README.txt', 2..5).join("\n\n")
13
+ p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1]
14
+ p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
15
+ end
16
+
17
+ # vim: syntax=Ruby
@@ -0,0 +1,144 @@
1
+ require "erb"
2
+
3
+ module ActsAsWrappedClass
4
+ VERSION = "1.0.0"
5
+ WRAPPED_CLASSES = []
6
+
7
+ module InstanceMethods
8
+ def to_wrapper
9
+ eval("#{self.class.name}Wrapper").new(self)
10
+ end
11
+ end
12
+
13
+ module ClassMethods
14
+ def wrapped_class?
15
+ true
16
+ end
17
+ end
18
+
19
+ class WrapperFinder
20
+ SANDBOX_BASE_IMPORTS = ["Object", "Module", "Class", "Kernel", "Main", "Array", "Bignum", "Binding", "Comparable", "Cont", "Data", "Dir", "Enumerable", "Exception", "FalseClass", "FConst", "File", "FileTest", "Fixnum", "Float", "GC", "Hash", "Integer", "IO", "Marshal", "Math", "Match", "Method", "NilClass", "Numeric", "ObSpace", "Precision", "Proc", "Process", "ProcStatus", "ProcUID", "ProcGID", "ProcID_Syscall", "Range", "Regexp", "Stat", "String", "Struct", "Symbol", "Thread", "ThGroup", "Time", "Tms", "TrueClass", "UnboundMethod", "StandardError", "SystemExit", "Interrupt", "Signal", "Fatal", "ArgError", "EOFError", "IndexError", "RangeError", "RegexpError", "IOError", "RuntimeError", "SecurityError", "SystemCallError", "SysStackError", "ThreadError", "TypeError", "ZeroDivError", "NotImpError", "NoMemError", "NoMethodError", "FloatDomainError", "ScriptError", "NameError", "NameErrorMesg", "SyntaxError", "LoadError", "LocalJumpError", "Errno", "BoxedClass"]
21
+ @@special_handlers = {Array => Proc.new{ |object| object.collect{|val| find_wrapper_for(val)} },
22
+ Hash => Proc.new{ |object| object.inject({}){|h, key_val| h[find_wrapper_for(key_val[0])] = find_wrapper_for(key_val[1]); h } }}
23
+
24
+ # Returns a wrapper for an instance of an object, if one exsits, or the original object if it's a core datatype.
25
+ # * This will first attempt to find a special handler for the type of object being wrapped and invoke it's block
26
+ # * Then it will look for a wrapper classes that fits the object's type (Something looks for SomethingWrapper)
27
+ # * Finally, it checks a list of "safe" classes that don't need wrapping
28
+ # If no match is found, an exception is raised
29
+ def self.find_wrapper_for(object)
30
+ wrapper_name = "#{object.class.name}Wrapper"
31
+ return nil if nil
32
+
33
+ @@special_handlers.each do |key, value|
34
+ return value.call(object) if object.is_a?(key)
35
+ end
36
+
37
+ return object if object.kind_of?(WrapperBase)
38
+ return eval(wrapper_name).new(object) if eval("defined?(#{wrapper_name})")
39
+ return object if SANDBOX_BASE_IMPORTS.include?(object.class.name)
40
+
41
+ raise "Can't find wrapper for class: #{object.class.name}"
42
+ end
43
+
44
+ # Add a special handler for how to wrap certain types of classes.
45
+ # For example, if you wanted to wrap Arrays by wrapping each of their elements
46
+ def self.add_special_handler(klass, prc)
47
+ raise "1st arg must be a Class" unless klass.is_a?(Class)
48
+ raise "2st arg must be a Proc" unless prc.is_a?(Proc)
49
+ @@special_handlers[klass] = prc
50
+ end
51
+ end
52
+
53
+ def wrapped_class?
54
+ false
55
+ end
56
+
57
+ # Mark a class as wrapped, creating a wrapper class which allows access to certain methods specified by EITHER the :methods safe list of the :except_methods blacklist.
58
+ # You cannot use both :methods and :except_methods at once.
59
+ # * options[:methods] contains a list of method names (symbols) to allow access to
60
+ # * options[:except_methods] contains a list of method names (symbols) to not allow access to
61
+ # * options[:constants] contains a list of constant names (symbols) to allow access to
62
+ # * options[:except_constants] contains a list of constant names (symbols) to not allow access to
63
+ def acts_as_wrapped_class(options = {})
64
+ raise "Can't specify methods to allow and to deny." if options[:methods] && options[:except_methods]
65
+ raise "Can't specify constants to allow and to deny." if options[:constants] && options[:except_constants]
66
+ options[:methods] ||= :all
67
+ options[:constants] ||= :all
68
+
69
+ WRAPPED_CLASSES << self
70
+
71
+ if options[:methods] == :all
72
+ options.delete(:methods)
73
+ options[:except_methods] = []
74
+ end
75
+
76
+ if options[:constants] == :all
77
+ options.delete(:constants)
78
+ options[:except_constants] = []
79
+ end
80
+
81
+ meths = options[:methods] || options[:except_methods]
82
+ consts = options[:constants] || options[:except_constants]
83
+
84
+ allowed_method_missing = options[:methods] ? options[:methods].include?(:method_missing) : !options[:except_methods].include?(:method_missing)
85
+ allowed_const_missing = options[:constants] ? options[:constants].include?(:cont_missing) : !options[:except_constants].include?(:const_missing)
86
+
87
+ self.send(:include, ActsAsWrappedClass::InstanceMethods)
88
+ self.send(:extend, ActsAsWrappedClass::ClassMethods)
89
+
90
+ if allowed_method_missing
91
+ method_defs_erb = <<-EOF
92
+ def method_missing(meth, *args);
93
+ <% if options[:except_methods] %>
94
+ raise NameError.new("Method `"+meth.to_s+"' now allowed") if [<%= meths.collect {|m| ":\#{m}"}.join(", ") %>].include?(meth)
95
+ <% else %>
96
+ raise NameError.new("Method `"+meth.to_s+"' now allowed") unless [<%= meths.collect {|m| ":\#{m}"}.join(", ") %>].include?(meth) || !@wrapped_object.class.method_defined?(meth)
97
+ <% end %>
98
+ ActsAsWrappedClass::WrapperFinder.find_wrapper_for(@wrapped_object.send(meth, *args));
99
+ end
100
+ EOF
101
+ else
102
+ method_defs_erb = <<-EOF
103
+ def method_missing(meth, *args);
104
+ raise NameError.new("Method `"+meth.to_s+"' now allowed") <%= options[:methods] ? "unless" : "if"%> [<%=meths.collect {|m| ":\#{m}"}.join(", ")%>].include?(meth)
105
+ ActsAsWrappedClass::WrapperFinder.find_wrapper_for(@wrapped_object.method(meth).call(*args));
106
+ end
107
+ EOF
108
+ end
109
+
110
+ # if allowed_const_missing
111
+ # const_defs_erb = <<-EOF
112
+ # def const_missing(const, *args);
113
+ # <% if options[:except_constants] %>
114
+ # raise NameError.new("Constant `"+const.to_s+"' now allowed") if [<%= consts.collect {|c| ":\#{c}"}.join(", ") %>].include?(const)
115
+ # <% else %>
116
+ # raise NameError.new("Method `"+meth.to_s+"' now allowed") unless [<%= meths.collect {|c| ":\#{c}"}.join(", ") %>].include?(const) || !@wrapped_object.class.const_defined?(const)
117
+ # <% end %>
118
+ # @wrapped_object.const_get(const)
119
+ # end
120
+ # EOF
121
+ # else
122
+ # const_defs_erb = <<-EOF
123
+ # def const_missing(const, *args);
124
+ # raise NameError.new("Constant `"+const.to_s+"' now allowed") <%= options[:constants] ? "unless" : "if"%> [<%=consts.collect {|m| ":\#{m}"}.join(", ")%>].include?(const)
125
+ # @wrapped_object.const_get(const)
126
+ # end
127
+ # EOF
128
+ # end
129
+
130
+ method_defs = ERB.new(method_defs_erb).result(binding)
131
+
132
+ wrapper_class_code = <<-EOF
133
+ class #{self.name}Wrapper < WrapperBase
134
+ #{method_defs}
135
+ end
136
+ EOF
137
+
138
+ eval wrapper_class_code, TOPLEVEL_BINDING
139
+ end
140
+ end
141
+
142
+ Object.send(:include, ActsAsWrappedClass)
143
+
144
+ require File.join(File.dirname(__FILE__), "wrapper_base")
@@ -0,0 +1,36 @@
1
+ class WrapperBase
2
+ eval((public_instance_methods - ["__id__","__send__", "is_a?", "kind_of?", "hash", "class", "inspect"]).collect{|meth| "undef "+meth}.join("; "))
3
+
4
+ # Create a wrapper, passing in an object to wrap
5
+ def initialize(wrapped_object)
6
+ @wrapped_object = wrapped_object
7
+ end
8
+
9
+ def hash
10
+ @wrapped_object.hash
11
+ end
12
+
13
+ def ==(other)
14
+ return false if self.class != other.class
15
+ @wrapped_object == other._wrapped_object
16
+ end
17
+
18
+ def <=>(other)
19
+ raise "Can't compare objects of different types" if self.class != other.class
20
+ @wrapped_object <=> other._wrapped_object
21
+ end
22
+
23
+ def ===(other)
24
+ return false if self.class != other.class
25
+ @wrapped_object === other._wrapped_object
26
+ end
27
+
28
+ # Provide access to the wrapped object
29
+ def _wrapped_object
30
+ @wrapped_object
31
+ end
32
+
33
+ def self.wrapper_class?
34
+ true
35
+ end
36
+ end
@@ -0,0 +1,128 @@
1
+ require 'test/unit'
2
+ require File.dirname(__FILE__) + "/../lib/acts_as_wrapped_class"
3
+
4
+ class NotWrappedClass
5
+ def method1
6
+ 6.383
7
+ end
8
+ end
9
+
10
+ class SampleClass #SampleClassWrapper
11
+ acts_as_wrapped_class :methods => [:get_other_class, :other_method], :constants => [:API]
12
+
13
+ API = 3.1415
14
+
15
+ def get_other_class
16
+ OtherClass.new(rand)
17
+ end
18
+
19
+ def other_method
20
+ 5.5
21
+ end
22
+
23
+ def unsafe_method
24
+ 666
25
+ end
26
+ end
27
+
28
+ class OtherClass
29
+ SAMPLE_CLASS = SampleClass.new
30
+
31
+ acts_as_wrapped_class :methods => :all, :constants => :all
32
+
33
+ attr_reader :value
34
+
35
+ def initialize(val)
36
+ @value = val
37
+ end
38
+
39
+ def get_sample_classes
40
+ [SampleClass.new] * 10
41
+ end
42
+
43
+ def get_hash
44
+ {:a => SampleClass.new, OtherClass.new(11) => "hello", :array => [SampleClass.new, SampleClass.new, 10]}
45
+ end
46
+
47
+ def another_method
48
+ 6.6
49
+ end
50
+
51
+ def hash
52
+ @value
53
+ end
54
+
55
+ def ==(other)
56
+ other.value == value
57
+ end
58
+ end
59
+
60
+ class ActsAsWrappedCodeTest < Test::Unit::TestCase
61
+ # Replace this with your real tests.
62
+ def test_wrappers_exist
63
+ assert defined?(OtherClassWrapper)
64
+ assert defined?(SampleClassWrapper)
65
+ end
66
+
67
+ def test_unwrappers_to_wrapper
68
+ assert SampleClass.public_instance_methods.include?("to_wrapper")
69
+ assert OtherClass.public_instance_methods.include?("to_wrapper")
70
+ end
71
+
72
+ def test_awrappers_clean
73
+ assert_contents_same ["method_missing"] + allowed_methods, SampleClassWrapper.public_instance_methods
74
+ assert_contents_same ["method_missing"] + allowed_methods, OtherClassWrapper.public_instance_methods
75
+ end
76
+
77
+ def test_wrappers_method_missing_clean
78
+ wrap = SampleClass.new.to_wrapper
79
+ wrap.other_method
80
+ assert_raise(NameError) { wrap.unsafe_method }
81
+ assert_raise(NameError) { wrap.method_missing :unsafe_method }
82
+ end
83
+
84
+ def test_class1_wrappers
85
+ wrap = SampleClass.new.to_wrapper
86
+ assert wrap.is_a?(SampleClassWrapper)
87
+ assert wrap.other_method.is_a?(Float)
88
+ assert_equal 5.5, wrap.other_method
89
+ assert wrap.get_other_class.is_a?(OtherClassWrapper)
90
+ end
91
+
92
+ def test_hash_and_equals
93
+ wrap1 = OtherClass.new(11).to_wrapper
94
+ wrap2 = OtherClass.new(11).to_wrapper
95
+ assert_equal wrap1.hash, wrap2.hash
96
+ assert_equal wrap1, wrap2
97
+ end
98
+
99
+
100
+ def test_class2_wrappers
101
+ wrap = OtherClass.new(10.0).to_wrapper
102
+ assert wrap.is_a?(OtherClassWrapper)
103
+ assert_equal 10.0, wrap.value
104
+ assert_equal 6.6, wrap.another_method
105
+ array = wrap.get_sample_classes
106
+ array.each do |a|
107
+ assert a.is_a?(SampleClassWrapper)
108
+ end
109
+ hash = wrap.get_hash
110
+ assert hash[:a].is_a?(SampleClassWrapper)
111
+ assert_contents_same hash[:array].collect{|v| v.class.name}, ["SampleClassWrapper", "SampleClassWrapper", "Fixnum"]
112
+ end
113
+
114
+ def test_wrapped_class?
115
+ assert SampleClass.wrapped_class?
116
+ assert OtherClass.wrapped_class?
117
+ assert !NotWrappedClass.wrapped_class?
118
+ end
119
+
120
+ def assert_contents_same(array1, array2)
121
+ assert_equal array1.length, array2.length, "#{array1.inspect} != #{array2.inspect}"
122
+ array1.each { |a| assert array2.include?(a), "#{array2.inspect} does not contain #{a.inspect}" }
123
+ end
124
+
125
+ def allowed_methods
126
+ ["__id__", "__send__", "is_a?", "kind_of?", "class", "hash", "inspect", "==", "<=>", "===", "_wrapped_object"]
127
+ end
128
+ end
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.4
3
+ specification_version: 1
4
+ name: acts_as_wrapped_class
5
+ version: !ruby/object:Gem::Version
6
+ version: 1.0.0
7
+ date: 2007-10-22 00:00:00 -07:00
8
+ summary: automatically generate wrapper classes which restrict access to methods and constants in the wrapped class
9
+ require_paths:
10
+ - lib
11
+ email: ds@elctech.com
12
+ homepage: " by David Stevenson "
13
+ rubyforge_project: acts_as_wrapped_class
14
+ description: "== FEATURES/PROBLEMS: * Wrappers do not dispatch const_missing yet, so constants are not accessible yet. == SYNOPSIS: class Something acts_as_wrapped_class :methods => [:safe_method] # SomethingWrapper is now defined def safe_method # allowed to access this method through SomethingWrapper Something.new end def unsafe_method # not allowed to access this method through SomethingWrapper end end"
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - David Stevenson
31
+ files:
32
+ - History.txt
33
+ - Manifest.txt
34
+ - README.txt
35
+ - Rakefile
36
+ - lib/acts_as_wrapped_class.rb
37
+ - lib/wrapper_base.rb
38
+ - test/test_acts_as_wrapped_class.rb
39
+ test_files:
40
+ - test/test_acts_as_wrapped_class.rb
41
+ rdoc_options:
42
+ - --main
43
+ - README.txt
44
+ extra_rdoc_files:
45
+ - History.txt
46
+ - Manifest.txt
47
+ - README.txt
48
+ executables: []
49
+
50
+ extensions: []
51
+
52
+ requirements: []
53
+
54
+ dependencies:
55
+ - !ruby/object:Gem::Dependency
56
+ name: hoe
57
+ version_requirement:
58
+ version_requirements: !ruby/object:Gem::Version::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: 1.3.0
63
+ version: