kindergarten 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
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