cleanroom-1.0.0/0000755000175000017500000000000012531644662013050 5ustar globusglobuscleanroom-1.0.0/metadata.yml0000644000175000017500000000574512531644662015366 0ustar globusglobus--- !ruby/object:Gem::Specification name: cleanroom version: !ruby/object:Gem::Version version: 1.0.0 platform: ruby authors: - Seth Vargo autorequire: bindir: bin cert_chain: [] date: 2014-08-12 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: rspec requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '3.0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '3.0' - !ruby/object:Gem::Dependency name: bundler requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: rake requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' description: Ruby is an excellent programming language for creating and managing custom DSLs, but how can you securely evaluate a DSL while explicitly controlling the methods exposed to the user? Our good friends instance_eval and instance_exec are great, but they expose all methods - public, protected, and private - to the user. Even worse, they expose the ability to accidentally or intentionally alter the behavior of the system! The cleanroom pattern is a safer, more convenient, Ruby-like approach for limiting the information exposed by a DSL while giving users the ability to write awesome code! email: sethvargo@gmail.com executables: [] extensions: [] extra_rdoc_files: [] files: - ".gitignore" - ".travis.yml" - CHANGELOG.md - Gemfile - LICENSE - README.md - Rakefile - cleanroom.gemspec - lib/cleanroom.rb - lib/cleanroom/errors.rb - lib/cleanroom/rspec.rb - lib/cleanroom/version.rb - spec/functional/cleanroom_spec.rb - spec/spec_helper.rb - spec/unit/cleanroom_spec.rb - spec/unit/rspec_spec.rb homepage: https://github.com/sethvargo/cleanroom licenses: - Apache 2.0 metadata: {} post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 1.9.3 required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: rubygems_version: 2.3.0 signing_key: specification_version: 4 summary: "(More) safely evaluate Ruby DSLs with cleanroom" test_files: - spec/functional/cleanroom_spec.rb - spec/spec_helper.rb - spec/unit/cleanroom_spec.rb - spec/unit/rspec_spec.rb has_rdoc: cleanroom-1.0.0/spec/0000755000175000017500000000000012531644662014002 5ustar globusglobuscleanroom-1.0.0/spec/unit/0000755000175000017500000000000012531644662014761 5ustar globusglobuscleanroom-1.0.0/spec/unit/rspec_spec.rb0000644000175000017500000000404712531644662017441 0ustar globusglobusrequire 'spec_helper' require 'cleanroom/rspec' describe 'RSpec matchers' do let(:klass) do Class.new do include Cleanroom def method_1; end expose :method_1 def method_2; end end end let(:instance) { klass.new } describe '#be_an_exposed_method_on' do context 'when given a class' do it 'is true when the method is exposed' do expect(:method_1).to be_an_exposed_method_on(klass) end it 'is false when the method exists, but is not exposed' do expect(:method_2).to_not be_an_exposed_method_on(klass) end it 'is false when the method is not exposed' do expect(:method_3).to_not be_an_exposed_method_on(klass) end end context 'when given an instance' do it 'is true when the method is exposed' do expect(:method_1).to be_an_exposed_method_on(instance) end it 'is false when the method exists, but is not exposed' do expect(:method_2).to_not be_an_exposed_method_on(instance) end it 'is false when the method is not exposed' do expect(:method_3).to_not be_an_exposed_method_on(instance) end end end describe '#have_exposed_method' do context 'when given a class' do it 'is true when the method is exposed' do expect(klass).to have_exposed_method(:method_1) end it 'is false when the method exists, but is not exposed' do expect(klass).to_not have_exposed_method(:method_2) end it 'is false when the method is not exposed' do expect(klass).to_not have_exposed_method(:method_3) end end context 'when given an instance' do it 'is true when the method is exposed' do expect(instance).to have_exposed_method(:method_1) end it 'is false when the method exists, but is not exposed' do expect(instance).to_not have_exposed_method(:method_2) end it 'is false when the method is not exposed' do expect(instance).to_not have_exposed_method(:method_3) end end end end cleanroom-1.0.0/spec/unit/cleanroom_spec.rb0000644000175000017500000001426612531644662020310 0ustar globusglobusrequire 'spec_helper' describe Cleanroom do let(:klass) do Class.new do include Cleanroom def exposed_method @called = true end expose :exposed_method def unexposed_method; end end end let(:instance) { klass.new } describe '.included' do let(:klass) { Class.new { include Cleanroom } } it 'extends the ClassMethods' do expect(klass).to be_a(Cleanroom::ClassMethods) end it 'includes the InstanceMethods' do expect(instance).to be_a(Cleanroom::InstanceMethods) end end describe '.extended' do let(:klass) { Class.new { extend Cleanroom } } it 'extends the ClassMethods' do expect(klass).to be_a(Cleanroom::ClassMethods) end it 'includes the InstanceMethods' do expect(instance).to be_a(Cleanroom::InstanceMethods) end end describe '.evaluate_file' do let(:path) { '/path/to/file' } let(:contents) { 'contents' } before do allow(File).to receive(:expand_path) .with(path) .and_return(path) allow(IO).to receive(:read) .with(path) .and_return(contents) allow(klass).to receive(:evaluate) end it 'gets the absolute path to the file' do expect(File).to receive(:expand_path).with(path).once klass.evaluate_file(instance, path) end it 'reads the contents to a string' do expect(IO).to receive(:read).with(path).once klass.evaluate_file(instance, path) end it 'evaluates the contents' do expect(klass).to receive(:evaluate).with(instance, contents, path, 1).once klass.evaluate_file(instance, path) end end describe '.evaluate' do let(:cleanroom) { double('Cleanroom.cleanroom') } let(:cleanroom_instance) { double('Cleanroom.cleanroom_instance') } let(:string) { '"hello"' } before do allow(cleanroom).to receive(:new) .with(instance) .and_return(cleanroom_instance) allow(cleanroom_instance).to receive(:instance_eval) allow(klass).to receive(:cleanroom) .and_return(cleanroom) end it 'creates a new cleanroom object' do expect(cleanroom).to receive(:new).with(instance).once klass.evaluate(instance, string) end it 'evaluates against the new cleanroom object' do expect(cleanroom_instance).to receive(:instance_eval).with(string).once klass.evaluate(instance, string) end end describe '.expose' do let(:klass) do Class.new do include Cleanroom def public_method; end protected def protected_method; end private def private_method; end end end it 'exposes the method when it is public' do expect { klass.expose(:public_method) }.to_not raise_error expect(klass.exposed_methods).to include(:public_method) end it 'raises an exception if the method is not defined' do expect { klass.expose(:no_method) }.to raise_error(NameError) end it 'raises an exception if the method is protected' do expect { klass.expose(:protected_method) }.to raise_error(NameError) end it 'raises an exception if the method is private' do expect { klass.expose(:private_method) }.to raise_error(NameError) end end describe '.exposed_methods' do it 'returns a hash' do expect(klass.exposed_methods).to be_a(Hash) end end describe '.cleanroom' do let(:klass) do Class.new do include Cleanroom def method_1 @method_1 = true end expose :method_1 def method_2 @method_2 = true end expose :method_2 end end it 'creates a new anonymous class each time' do a, b = klass.send(:cleanroom), klass.send(:cleanroom) expect(a).to_not be(b) end it 'creates a method for each exposed one on the proxy object' do cleanroom = klass.send(:cleanroom) expect(cleanroom).to be_public_method_defined(:method_1) expect(cleanroom).to be_public_method_defined(:method_2) end it 'calls the proxied method' do cleanroom = klass.send(:cleanroom).new(instance) cleanroom.method_1 cleanroom.method_2 expect(instance.instance_variable_get(:@method_1)).to be(true) expect(instance.instance_variable_get(:@method_2)).to be(true) end it 'prevents calls to the instance directly' do cleanroom = klass.send(:cleanroom).new(instance) expect { cleanroom.__instance__ }.to raise_error(Cleanroom::InaccessibleError) expect { cleanroom.send(:__instance__) }.to raise_error(Cleanroom::InaccessibleError) end end describe '#evaluate_file' do let(:path) { '/path/to/file' } before do allow(klass).to receive(:evaluate_file) .with(instance, path) end it 'delegates to the class method' do expect(klass).to receive(:evaluate_file).with(instance, path) instance.evaluate_file(path) end it 'returns self' do expect(instance.evaluate_file(path)).to be(instance) end end describe '#evaluate' do let(:string) { '"hello"' } before do allow(klass).to receive(:evaluate) .with(instance, string) end it 'delegates to the class method' do expect(klass).to receive(:evaluate).with(instance, string) instance.evaluate(string) end it 'returns self' do expect(instance.evaluate(string)).to be(instance) end end context 'when evaluating a DSL subclass' do let(:parent) do Class.new do include Cleanroom def parent_method; end expose :parent_method end end let(:child) do Class.new(parent) do def child_method; end expose :child_method end end let(:instance) { child.new } it 'inherits the parent DSL methods' do expect { instance.evaluate("parent_method") }.to_not raise_error end it 'allows for custom DSL methods' do expect { instance.evaluate("child_method") }.to_not raise_error end it 'does not change the parent DSL' do expect { parent.new.evaluate("child_method") }.to raise_error(NameError) end end end cleanroom-1.0.0/spec/spec_helper.rb0000644000175000017500000000126112531644662016620 0ustar globusglobusrequire 'rspec' require 'cleanroom' RSpec.configure do |config| config.filter_run(focus: true) config.run_all_when_everything_filtered = true # Force the expect syntax config.expect_with :rspec do |c| c.syntax = :expect end # Create and clear tmp_path on each run config.before(:each) do FileUtils.rm_rf(tmp_path) FileUtils.mkdir_p(tmp_path) end # Run specs in a random order config.order = 'random' end # # The path on disk to the temporary directory. # # @param [String, Array] paths # the extra path parameters to join # # @return [String] # def tmp_path(*paths) root = File.expand_path('../..', __FILE__) File.join('tmp', *paths) end cleanroom-1.0.0/spec/functional/0000755000175000017500000000000012531644662016144 5ustar globusglobuscleanroom-1.0.0/spec/functional/cleanroom_spec.rb0000644000175000017500000000405612531644662021467 0ustar globusglobusrequire 'spec_helper' describe Cleanroom do let(:klass) do Class.new do NULL = Object.new.freeze unless defined?(NULL) include Cleanroom def method_1(val = NULL) if val.equal?(NULL) @method_1 else @method_1 = val end end expose :method_1 def method_2(val = NULL) if val.equal?(NULL) @method_2 else @method_2 = val end end expose :method_2 def method_3 @method_3 = true end end end let(:instance) { klass.new } describe '#evaluate_file' do let(:path) { tmp_path('file.rb') } before do File.open(path, 'w') do |f| f.write <<-EOH.gsub(/^ {10}/, '') method_1 'hello' method_2 false EOH end end it 'evaluates the file' do instance.evaluate_file(path) expect(instance.method_1).to eq('hello') expect(instance.method_2).to be(false) end end describe '#evaluate' do let(:contents) do <<-EOH.gsub(/^ {8}/, '') method_1 'hello' method_2 false EOH end it 'evaluates the file' do instance.evaluate(contents) expect(instance.method_1).to eq('hello') expect(instance.method_2).to be(false) end end describe 'security' do it 'restricts access to __instance__' do expect { instance.evaluate("__instance__") }.to raise_error(Cleanroom::InaccessibleError) end it 'restricts access to __instance__ using :send' do expect { instance.evaluate("send(:__instance__)") }.to raise_error(Cleanroom::InaccessibleError) end it 'restricts access to defining new methods' do expect { instance.evaluate <<-EOH.gsub(/^ {12}/, '') self.class.class_eval do def new_method __instance__.method_3 end end EOH }.to raise_error(Cleanroom::InaccessibleError) expect(instance.instance_variables).to_not include(:@method_3) end end end cleanroom-1.0.0/lib/0000755000175000017500000000000012531644662013616 5ustar globusglobuscleanroom-1.0.0/lib/cleanroom/0000755000175000017500000000000012531644662015575 5ustar globusglobuscleanroom-1.0.0/lib/cleanroom/version.rb0000644000175000017500000000131112531644662017603 0ustar globusglobus# # Copyright 2014 Seth Vargo # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # module Cleanroom # # The version of the Cleanroom gem. # # @return [String] # VERSION = '1.0.0' end cleanroom-1.0.0/lib/cleanroom/rspec.rb0000644000175000017500000000314512531644662017241 0ustar globusglobus# # Copyright 2014 Seth Vargo # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require_relative '../cleanroom' unless defined?(RSpec) require 'rspec' end # # Assert a given method is exposed on a class. # # @example Checking against an instance # expect(:method_1).to be_an_exposed_method_on(instance) # # @example Checking against a class # expect(:method_1).to be_an_exposed_method_on(klass) # RSpec::Matchers.define :be_an_exposed_method_on do |object| match do |name| if object.is_a?(Class) object.exposed_methods.key?(name.to_sym) else object.class.exposed_methods.key?(name.to_sym) end end end # # Assert a given class or instance has an exposed method. # # @example Checking against an instance # expect(instance).to have_exposed_method(:method_1) # # @example Checking against a class # expect(klass).to have_exposed_method(:method_1) # RSpec::Matchers.define :have_exposed_method do |name| match do |object| if object.is_a?(Class) object.exposed_methods.key?(name.to_sym) else object.class.exposed_methods.key?(name.to_sym) end end end cleanroom-1.0.0/lib/cleanroom/errors.rb0000644000175000017500000000204212531644662017434 0ustar globusglobus# # Copyright 2014 Seth Vargo # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # module Cleanroom class Error < StandardError; end class InaccessibleError < Error def initialize(name, instance) @name, @instance = name, instance end def to_s <<-EOH.gsub(/\r?\n/, ' ') Undefined local variable or method `#{@name}' for #{@instance}. It may have been removed for the purposes of evaluating the DSL or for added security. If you feel you have reached this message in error, please open an issue. EOH end end end cleanroom-1.0.0/lib/cleanroom.rb0000644000175000017500000001132512531644662016124 0ustar globusglobus# # Copyright 2014 Seth Vargo # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require_relative 'cleanroom/errors' require_relative 'cleanroom/version' module Cleanroom # # Callback for when this module is included. # # @param [Class] base # def self.included(base) base.send(:extend, ClassMethods) base.send(:include, InstanceMethods) end # # Callback for when this module is included. # # @param [Class] base # def self.extended(base) base.send(:extend, ClassMethods) base.send(:include, InstanceMethods) end # # Class methods # module ClassMethods # # Evaluate the file in the context of the cleanroom. # # @param [Class] instance # the instance of the class to evaluate against # @param [String] filepath # the path of the file to evaluate # def evaluate_file(instance, filepath) absolute_path = File.expand_path(filepath) file_contents = IO.read(absolute_path) evaluate(instance, file_contents, absolute_path, 1) end # # Evaluate the string or block in the context of the cleanroom. # # @param [Class] instance # the instance of the class to evaluate against # @param [Array] args # the args to +instance_eval+ # @param [Proc] block # the block to +instance_eval+ # def evaluate(instance, *args, &block) cleanroom.new(instance).instance_eval(*args, &block) end # # Expose the given method to the DSL. # # @param [Symbol] name # def expose(name) unless public_method_defined?(name) raise NameError, "undefined method `#{name}' for class `#{self.name}'" end exposed_methods[name] = true end # # The list of exposed methods. # # @return [Hash] # def exposed_methods @exposed_methods ||= from_superclass(:exposed_methods, {}).dup end private # # The cleanroom instance for this class. This method is intentionally # NOT cached! # # @return [Class] # def cleanroom exposed = exposed_methods.keys parent = self.name || 'Anonymous' Class.new(Object) do class << self def class_eval raise Cleanroom::InaccessibleError.new(:class_eval, self) end def instance_eval raise Cleanroom::InaccessibleError.new(:instance_eval, self) end end define_method(:initialize) do |instance| define_singleton_method(:__instance__) do unless caller[0].include?(__FILE__) raise Cleanroom::InaccessibleError.new(:__instance__, self) end instance end end exposed.each do |exposed_method| define_method(exposed_method) do |*args, &block| __instance__.public_send(exposed_method, *args, &block) end end define_method(:class_eval) do raise Cleanroom::InaccessibleError.new(:class_eval, self) end define_method(:inspect) do "#<#{parent} (Cleanroom)>" end alias_method :to_s, :inspect end end # # Get the value from the superclass, if it responds, otherwise return # +default+. Since class instance variables are **not** inherited upon # subclassing, this is a required check to ensure subclasses inherit # exposed DSL methods. # # @param [Symbol] m # the name of the method to find # @param [Object] default # the default value to return if not found # def from_superclass(m, default = nil) return default if superclass == Cleanroom superclass.respond_to?(m) ? superclass.send(m) : default end end # # Instance Mehtods # module InstanceMethods # # Evaluate the file against the current instance. # # @param (see Cleanroom.evaluate_file) # @return [self] # def evaluate_file(filepath) self.class.evaluate_file(self, filepath) self end # # Evaluate the contents against the current instance. # # @param (see Cleanroom.evaluate_file) # @return [self] # def evaluate(*args, &block) self.class.evaluate(self, *args, &block) self end end end cleanroom-1.0.0/cleanroom.gemspec0000644000175000017500000000312512531644662016375 0ustar globusglobus# coding: utf-8 lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'cleanroom' Gem::Specification.new do |spec| spec.name = 'cleanroom' spec.version = Cleanroom::VERSION spec.author = 'Seth Vargo' spec.email = 'sethvargo@gmail.com' spec.summary = '(More) safely evaluate Ruby DSLs with cleanroom' spec.description = <<-EOH.gsub(/^ {4}/, '').gsub(/\r?\n/, ' ').strip Ruby is an excellent programming language for creating and managing custom DSLs, but how can you securely evaluate a DSL while explicitly controlling the methods exposed to the user? Our good friends instance_eval and instance_exec are great, but they expose all methods - public, protected, and private - to the user. Even worse, they expose the ability to accidentally or intentionally alter the behavior of the system! The cleanroom pattern is a safer, more convenient, Ruby-like approach for limiting the information exposed by a DSL while giving users the ability to write awesome code! EOH spec.homepage = 'https://github.com/sethvargo/cleanroom' spec.license = 'Apache 2.0' spec.required_ruby_version = '>= 1.9.3' spec.files = `git ls-files -z`.split("\x0") spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) spec.require_paths = ['lib'] spec.add_development_dependency 'rspec', '~> 3.0' spec.add_development_dependency 'bundler' spec.add_development_dependency 'rake' end cleanroom-1.0.0/Rakefile0000644000175000017500000000063712531644662014523 0ustar globusglobusrequire 'bundler/gem_tasks' require 'rspec/core/rake_task' [:unit, :functional].each do |type| RSpec::Core::RakeTask.new(type) do |t| t.pattern = "spec/#{type}/**/*_spec.rb" t.rspec_opts = [].tap do |a| a.push('--color') a.push('--format progress') end.join(' ') end end namespace :travis do desc 'Run tests on Travis' task ci: %w(unit functional) end task default: %w(travis:ci) cleanroom-1.0.0/README.md0000644000175000017500000001417712531644662014341 0ustar globusglobusRuby Cleanroom ============== [![Gem Version](http://img.shields.io/gem/v/cleanroom.svg)][gem] [![Build Status](http://img.shields.io/travis/sethvargo/cleanroom.svg)][travis] [![Dependency Status](http://img.shields.io/gemnasium/sethvargo/cleanroom.svg)][gemnasium] [![Code Climate](http://img.shields.io/codeclimate/github/sethvargo/cleanroom.svg)][codeclimate] [![Gittip](http://img.shields.io/gittip/sethvargo.svg)][gittip] [gem]: https://rubygems.org/gems/cleanroom [travis]: http://travis-ci.org/sethvargo/chef-suguar [gemnasium]: https://gemnasium.com/sethvargo/cleanroom [codeclimate]: https://codeclimate.com/github/sethvargo/cleanroom [gittip]: https://www.gittip.com/sethvargo Ruby is an excellent programming language for creating and managing custom DSLs, but how can you securely evaluate a DSL while explicitly controlling the methods exposed to the user? Our good friends `instance_eval` and `instance_exec` are great, but they expose all methods - public, protected, and private - to the user. Even worse, they expose the ability to accidentally or intentionally alter the behavior of the system! The cleanroom pattern is a safer, more convenient, Ruby-like approach for limiting the information exposed by a DSL while giving users the ability to write awesome code! The cleanroom pattern is a unique way for more safely evaluating Ruby DSLs without adding additional overhead. Installation ------------ Add this line to your application's Gemfile: ```ruby gem 'cleanroom' ``` And then execute: $ bundle Or install it yourself as: $ gem install cleanroom Usage ----- ### Setup In order to use the cleanroom, you must first load the cleanroom gem: ```ruby require 'cleanroom' ``` Next, for any file you wish to be evaluated as a DSL, include the module: ```ruby class MyDSL include Cleanroom end ``` ### Writing DSLs For each public method you with to expose as a DSL method, call `expose` after the method definition in your class: ```ruby class MyDSL include Cleanroom def my_method # ... end expose :my_method def public_method # ... end private def private_method # ... end end ``` In this example, `MyDSL` exposes two public API methods: - `my_method` - `public_method` which would be accessible via: ```ruby instance = MyDSL.new instance.my_method instance.public_method ``` MyDSL also exposes one DSL method: - `my_method` which would be accessible in a DSL file: ```ruby my_method ``` The use of the `expose` method has the added advantage of clearly identifying which methods are available as part of the DSL. The method `private_method` is never accessible in the DSL or as part of the public API. ### Evaluating DSLs The cleanroom also includes the ability to more safely evaluate DSL files. Given an instance of a class, you can call `evaluate` or `evaluate_file` to read a DSL. ```ruby instance = MyDSL.new # Using a Ruby block instance.evaluate do my_method end # Using a String instance.evaluate "my_method" # Given a file at /file instance.evaluate_file('/file') ``` These same methods are available on the class as well, but require you pass in the instance: ```ruby instance = MyDSL.new # Using a Ruby block MyDSL.evaluate(instance) do my_method end # Using a String MyDSL.evaluate(instance) "my_method" # Given a file at /file MyDSL.evaluate_file(instance, '/file') ``` For both of these examples, _the given instance is modified_, meaning `instance` holds the values after the evaluation took place. "Security" ---------- The cleanroom gem tries to prevent unauthorized variable access and attempts to alter the behavior of the system. First, the underlying instance object is never stored in an instance variable. Due to the nature of `instance_eval`, it would be trivial for a malicious user to directly access methods on the delegate class. ```ruby # Some DSL file @instance #=> nil ``` Second, access to the underlying `instance` in the cleanroom is restricted to `self` by inspecting the `caller` attempts to access `__instance__` from outside of a method in the cleanroom will result in an error. ```ruby # Some DSL file __instance__ #=> Cleanroom::InaccessibleError send(:__instance__) #=> Cleanroom::InaccessibleError ``` Third, the ability to create new methods on the cleanroom is also disabled: ```ruby # Some DSL file self.class.class_eval { } #=> Cleanroom::InaccessibleError self.class.instance_eval { } #=> Cleanroom::InaccessibleError ``` Fourth, when delegating to the underlying instance object, `public_send` (as opposed to `send` or `__send__`) is used. Even if an attacker could somehow bypass the previous safeguards, they would be unable to call non-public methods on the delegate object. If you find a security hole in the cleanroom implementation, please email me at the contact info found in my [GitHub profile](https://github.com/sethvargo). **Do not open an issue!** Testing ------- If you are using cleanroom in your DSLs, you will likely want to test a particular DSL method is exposed. Cleanroom packages some RSpec matchers for your convienence: ```ruby # spec/spec_helper.rb require 'rspec' require 'cleanroom/rspec' ``` This will define the following matchers: ```ruby # Check against an instance expect(:my_method).to be_an_exposed_method_on(instance) # Check against a class expect(:my_method).to be_an_exposed_method_on(klass) # Check against an instance expect(instance).to have_exposed_method(:my_method) # Check against a class expect(klass).to have_exposed_method(:my_method) ``` Contributing ------------ 1. Fork the project 1. Write tests 1. Run tests 1. Create a new Pull Request License ------- ```text Copyright 2014 Seth Vargo Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ``` cleanroom-1.0.0/LICENSE0000644000175000017500000002514212531644662014061 0ustar globusglobus Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. cleanroom-1.0.0/Gemfile0000644000175000017500000000004612531644662014343 0ustar globusglobussource 'https://rubygems.org' gemspec cleanroom-1.0.0/CHANGELOG.md0000644000175000017500000000016612531644662014664 0ustar globusglobusCleanroom CHANGELOG =================== v1.0.0 (August, 12, 2014) ------------------------- - Initial public release cleanroom-1.0.0/.travis.yml0000644000175000017500000000017512531644662015164 0ustar globusglobusrvm: - 1.9.3 - 2.0.0 - 2.1 bundler_args: --jobs 7 branches: only: - master script: bundle exec rake travis:ci cleanroom-1.0.0/.gitignore0000644000175000017500000000016612531644662015043 0ustar globusglobus/.bundle/ /.yardoc /Gemfile.lock /_yardoc/ /coverage/ /doc/ /pkg/ /spec/reports/ /tmp/ *.bundle *.so *.o *.a mkmf.log