kindergarten 0.0.5

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 ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ spec/support/db/test.db
19
+ spec/support/log/test.log
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color -f d
data/.travis.yml ADDED
@@ -0,0 +1,12 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 1.9.2
5
+ - jruby-18mode
6
+ - jruby-19mode
7
+ - rbx-18mode
8
+ - rbx-19mode
9
+ - ruby-head
10
+ - jruby-head
11
+ - 1.8.7
12
+ - ree
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in kindergarten.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Hartog C. de Mik
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,89 @@
1
+ # Kindergarten
2
+
3
+ [![Build Status](https://secure.travis-ci.org/coffeeaddict/kindergarten.png)](http://travis-ci.org/coffeeaddict/kindergarten)
4
+
5
+ [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/coffeeaddict/kindergarten)
6
+
7
+ A way to achieve modularity and modular security with a sandbox on steroids.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ gem 'kindergarten'
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install kindergarten
22
+
23
+ ## Usage
24
+
25
+ ```ruby
26
+ # define a child
27
+ child = User.find(2)
28
+
29
+ # define a module (perimeter) for the child to play in
30
+ class MyPlayModule < Kindergarten::Perimeter
31
+ # use can-can rules to govern the perimeter
32
+ govern do |child|
33
+ can :watch, Television
34
+ cannot :watch, CableTV
35
+
36
+ can :eat, Candy do |candy|
37
+ child.quotum.allows(candy)
38
+ end
39
+ end
40
+
41
+ # define methods for the sandbox
42
+ sandbox :watch_tv, :eat
43
+
44
+ def watch_tv(tv)
45
+ guard(:watch, tv)
46
+ child.watch(tv)
47
+
48
+ sleep(:four)
49
+ end
50
+
51
+ def eat(candy)
52
+ guard(:eat, candy)
53
+ child.eat(candy)
54
+ end
55
+
56
+ def sleep(len) # not_accessible_from_outside
57
+ child.sleep(len)
58
+ end
59
+ end
60
+
61
+ # load the child and the module into a sandbox
62
+ sandbox = Kindergarten.sandbox(child)
63
+ sandbox.load_module(MyPlayPerimeter)
64
+
65
+ # you can now call the sandboxed methods on the sandbox
66
+ sandbox.watch_tv(CableTV.new) # fails with Kindergarten::AccessDenied
67
+ 30.times do
68
+ sandbox.eat(Liquorice.new) # fails after a while
69
+ end
70
+
71
+ sandbox.sleep(:long) # fails with NoMethodError
72
+
73
+ sandbox.allowed?(:watch, Television)
74
+ # => true
75
+ ```
76
+
77
+ You are not restricted to only one perimeter/module - that would be most
78
+ boring...
79
+
80
+ Infact, the above is the essence of things - but there is much much more fun
81
+ hidden inside the Kindergarten. More will follow on the Wiki
82
+
83
+ ## Contributing
84
+
85
+ 1. Fork it
86
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
87
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
88
+ 4. Push to the branch (`git push origin my-new-feature`)
89
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task :default => :spec
data/TODO.md ADDED
@@ -0,0 +1,6 @@
1
+ # Kindergarten TODO
2
+
3
+ ## Travis
4
+
5
+ * Change specs to use ruby-1.8 hashes so Travis can run 1.8.x
6
+ * Switch to mysql for testing jrubies
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/kindergarten/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Hartog C. de Mik"]
6
+ gem.email = ["hartog@organisedminds.com"]
7
+ gem.description = %q{A kindergarten with a perimeter, a governess and a sandbox}
8
+ gem.summary = %q{Provide a kindergarten for your code to play in}
9
+ gem.homepage = ""
10
+
11
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
12
+ gem.files = `git ls-files`.split("\n")
13
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
14
+ gem.name = "kindergarten"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Kindergarten::VERSION
17
+
18
+ gem.add_dependency('cancan', ['~> 1.6.8'])
19
+ gem.add_dependency('activesupport', ['~> 3.2'])
20
+
21
+ gem.add_development_dependency('rake')
22
+ gem.add_development_dependency('rspec', ['~> 2.10'])
23
+ gem.add_development_dependency('activerecord', ['~> 3.2'])
24
+ gem.add_development_dependency('sqlite3')
25
+ end
@@ -0,0 +1,17 @@
1
+ require 'cancan'
2
+ require 'active_support/core_ext'
3
+
4
+ require "kindergarten/version"
5
+ require "kindergarten/sandbox"
6
+ require "kindergarten/exceptions"
7
+ require "kindergarten/governesses"
8
+ require "kindergarten/perimeter"
9
+
10
+ module Kindergarten
11
+ class << self
12
+ def sandbox(child)
13
+ Kindergarten::Sandbox.new(child)
14
+ end
15
+ end
16
+ end
17
+
@@ -0,0 +1,19 @@
1
+ module Kindergarten
2
+ # Signals unallowed access
3
+ class AccessDenied < CanCan::AccessDenied
4
+ def initialize(action, target, opts)
5
+ message = opts.delete(:message)
6
+ if message.blank?
7
+ name = target.is_a?(Class) ? target.name : target.class.name
8
+ message = "You are not allowed to #{action} that #{name.downcase}"
9
+ end
10
+
11
+ super(message, action, target)
12
+ end
13
+ end
14
+
15
+ class Perimeter
16
+ # Signals bad sandbox method implementation
17
+ class Unguarded < SecurityError; end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ module Kindergarten
2
+ # Hash with only allowed keys
3
+ class ScrubbedHash < Hash; end
4
+
5
+ # Hash with only allowed keys and untainted values
6
+ class RinsedHash < Hash; end
7
+
8
+ module Governesses
9
+ class << self
10
+ attr_accessor :forbidden_keys
11
+ end
12
+
13
+ self.forbidden_keys = []
14
+ end
15
+ end
16
+
17
+ require "kindergarten/governesses/head_governess"
18
+ require "kindergarten/governesses/strict_governess"
19
+ require "kindergarten/governesses/easy_governess"
@@ -0,0 +1,11 @@
1
+ module Kindergarten
2
+ # A very easy governess, lets everything happen unguarded. Perhaps not such
3
+ # a good idea to be using this...
4
+ #
5
+ class EasyGoverness < HeadGoverness
6
+ def initialize(child)
7
+ super
8
+ @unguarded = true
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,144 @@
1
+ module Kindergarten
2
+ # The Governess keeps an eye on the child in the sandbox and makes sure
3
+ # she plays nicely and within the bounds of legality
4
+ #
5
+ class HeadGoverness
6
+ include CanCan::Ability
7
+
8
+ def initialize(child)
9
+ @child = child
10
+ @unguarded = false
11
+ @rules = []
12
+ end
13
+
14
+ # The governess is empty when no rules have been defined
15
+ def empty?
16
+ @rules.empty?
17
+ end
18
+
19
+ # Perform a sandbox method within the care of the governess.
20
+ #
21
+ # The HeadGoverness does nothing with it.
22
+ #
23
+ # @param [Symbol] method The name of the method that will be executed (for
24
+ # logging, record-keeping, raising, etc.)
25
+ # @return The result of the block
26
+ #
27
+ def governed(method, &block)
28
+ raise "You must specify a block" unless block_given?
29
+
30
+ return yield
31
+ end
32
+
33
+ # Check to see if the child can do something, increments @guard_count
34
+ #
35
+ # @param action Action to take
36
+ # @param target On given target
37
+ # @param opts [Hash] options
38
+ # @option opts [String] :message The message on access denied
39
+ #
40
+ # @raise [Kindergarten::AccessDenied] when the kindergarten is guarded and
41
+ # the action is not allowed
42
+ #
43
+ # @return The given target to allow
44
+ # def project(id)
45
+ # project = Project.find(id)
46
+ # guard(:view, project)
47
+ # end
48
+ #
49
+ def guard(action, target, opts={})
50
+ if guarded? && cannot?(action, target)
51
+ raise Kindergarten::AccessDenied.new(action, target, opts)
52
+ end
53
+
54
+ # to allow
55
+ # def project(id)
56
+ # project = Project.find(id)
57
+ # guard(:view, project)
58
+ # end
59
+ #
60
+ return target
61
+ end
62
+
63
+ # When a block is given, set the Governess to unguarded during the
64
+ # execution of the block
65
+ #
66
+ def unguarded(&block)
67
+ if block_given?
68
+ before = @unguarded
69
+
70
+ @unguarded = true
71
+ yield
72
+ @unguarded = before
73
+ end
74
+ end
75
+
76
+ def guarded?
77
+ !unguarded?
78
+ end
79
+
80
+ def unguarded?
81
+ !!@unguarded
82
+ end
83
+
84
+ # Scrub a hash of any key that is not specified
85
+ #
86
+ # @param [Hash] attributes An attributes-hash to scrub
87
+ # @param [Symbol] list A list of allowed attributes
88
+ #
89
+ # @return [ScrubbedHash] a hash with only allowed keys
90
+ def scrub(attributes, *list)
91
+ list.map!(&:to_sym)
92
+
93
+ forbidden = Kindergarten::Governesses.forbidden_keys
94
+
95
+ Kindergarten::ScrubbedHash[
96
+ attributes.symbolize_keys!.delete_if do |key,value|
97
+ forbidden.include?(key) || !list.include?(key)
98
+ end
99
+ ]
100
+ end
101
+
102
+ # Scrub a hash of any key that is not specified
103
+ #
104
+ # @param [Hash] attributes An attributes-hash to scrub
105
+ # @param [Hash] untaint_opts Specify a Regexp for each key. The value from
106
+ # the attributes will be matched against the regexp and replaced with
107
+ # the first result.
108
+ #
109
+ # specify :pass instead of a Regexp to let the value pass without
110
+ # matching (usefull for non strings, etc.)
111
+ #
112
+ # @return [RinsedHash] a hash with only allowed keys and untainted values
113
+ #
114
+ # @example Untaint
115
+ # rinse(attributes, :name => /^([a-Z0-9]+)/, :description => /([\w\s-]+)/mg)
116
+ #
117
+ # @example Pass on a Date attribute
118
+ # rinse(attributes, :name => /([\w\s-]+)/, :date => :pass)
119
+ #
120
+ # @example Bad practice
121
+ # # beware of the dot-star!
122
+ # rinse(attributes, :name => /(.*)/)
123
+ #
124
+ def rinse(attributes, untaint_opts)
125
+ untaint_opts.symbolize_keys!
126
+
127
+ scrubbed = scrub(attributes, *untaint_opts.keys)
128
+
129
+ scrubbed.each do |key, value|
130
+ untaint = untaint_opts[key]
131
+ next if untaint == :pass
132
+
133
+ match = value.match(untaint)
134
+ if match.nil?
135
+ scrubbed.delete key
136
+ else
137
+ scrubbed[key] = match[1]
138
+ end
139
+ end
140
+
141
+ return Kindergarten::RinsedHash[scrubbed]
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,48 @@
1
+ module Kindergarten
2
+ # A very strict governess, forces all the sandbox methods to use the guard
3
+ # methods.
4
+ #
5
+ # @note
6
+ # Does not specify how to rollback when the guard method was not called,
7
+ # You're on your own there...
8
+ #
9
+ class StrictGoverness < HeadGoverness
10
+ # Check how often the perimeter guarded something
11
+ attr_reader :guard_count
12
+
13
+ def initialize(child)
14
+ super
15
+ @guard_count = 0
16
+ end
17
+
18
+ # Force the use of guard inside sandbox methods
19
+ #
20
+ # @raise Kindergarten::Perimeter::Unguarded when the guard count did not
21
+ # increment during the block execution
22
+ #
23
+ def governed(method, &block)
24
+ before = self.guard_count
25
+ res = yield
26
+
27
+ if @unguarded != true && self.guard_count == before
28
+ raise Kindergarten::Perimeter::Unguarded.new(
29
+ "#{method} was executed without propper guarding"
30
+ )
31
+ end
32
+
33
+ return res
34
+ end
35
+
36
+ # guard something and increment the guard count
37
+ def guard(*args)
38
+ @guard_count += 1
39
+ super
40
+ end
41
+
42
+ # allow something unguarded and increment the guard count
43
+ def unguarded(&block)
44
+ @guard_count += 1
45
+ super
46
+ end
47
+ end
48
+ end