cant 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ pkg/*
2
+ coverage/
3
+ .yardoc
4
+ doc/
5
+
6
+ *.gem
7
+ .bundle
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source "http://rubygems.org"
2
+
3
+ # dependencies in cant.gemspec
4
+ gemspec
5
+
6
+ group :development do
7
+ gem 'rspec'
8
+ gem 'wrong'
9
+ gem 'guard-rspec'
10
+ gem 'growl'
11
+ end
12
+
13
+
data/Gemfile.lock ADDED
@@ -0,0 +1,69 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ cant (0.1.0)
5
+
6
+ GEM
7
+ remote: http://rubygems.org/
8
+ specs:
9
+ ParseTree (3.0.6)
10
+ RubyInline (>= 3.7.0)
11
+ sexp_processor (>= 3.0.0)
12
+ RubyInline (3.8.6)
13
+ ZenTest (~> 4.3)
14
+ ZenTest (4.4.2)
15
+ configuration (1.2.0)
16
+ diff-lcs (1.1.2)
17
+ file-tail (1.0.5)
18
+ spruz (>= 0.1.0)
19
+ growl (1.0.3)
20
+ guard (0.2.2)
21
+ open_gem (~> 1.4.2)
22
+ thor (~> 0.14.3)
23
+ guard-rspec (0.1.9)
24
+ guard (>= 0.2.2)
25
+ launchy (0.3.7)
26
+ configuration (>= 0.0.5)
27
+ rake (>= 0.8.1)
28
+ open_gem (1.4.2)
29
+ launchy (~> 0.3.5)
30
+ predicated (0.2.2)
31
+ rake (0.8.7)
32
+ rspec (2.3.0)
33
+ rspec-core (~> 2.3.0)
34
+ rspec-expectations (~> 2.3.0)
35
+ rspec-mocks (~> 2.3.0)
36
+ rspec-core (2.3.0)
37
+ rspec-expectations (2.3.0)
38
+ diff-lcs (~> 1.1.2)
39
+ rspec-mocks (2.3.0)
40
+ ruby2ruby (1.2.5)
41
+ ruby_parser (~> 2.0)
42
+ sexp_processor (~> 3.0)
43
+ ruby_parser (2.0.5)
44
+ sexp_processor (~> 3.0)
45
+ sexp_processor (3.0.5)
46
+ sourcify (0.4.0)
47
+ ruby2ruby (>= 1.2.5)
48
+ sexp_processor (>= 3.0.5)
49
+ spruz (0.2.2)
50
+ thor (0.14.6)
51
+ wrong (0.5.0)
52
+ ParseTree (~> 3.0)
53
+ diff-lcs (~> 1.1.2)
54
+ file-tail (~> 1.0)
55
+ predicated (>= 0.2.2)
56
+ ruby2ruby (~> 1.2)
57
+ ruby_parser (~> 2.0.4)
58
+ sexp_processor (~> 3.0)
59
+ sourcify (>= 0.3.0)
60
+
61
+ PLATFORMS
62
+ ruby
63
+
64
+ DEPENDENCIES
65
+ cant!
66
+ growl
67
+ guard-rspec
68
+ rspec
69
+ wrong
data/Guardfile ADDED
@@ -0,0 +1,8 @@
1
+ # A sample Guardfile
2
+ # More info at http://github.com/guard/guard#readme
3
+
4
+ guard 'rspec', :version => 2 do
5
+ watch('^spec/(.*)_spec.rb')
6
+ watch('^lib/(.*)\.rb') { |m| "spec/#{m[1]}_spec.rb" }
7
+ watch('^spec/spec_helper.rb') { "spec" }
8
+ end
data/LICENSE.mit ADDED
@@ -0,0 +1,22 @@
1
+ (The MIT License)
2
+
3
+ Copyright (c) 2010 Thierry Henrio
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 NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,204 @@
1
+ Intent
2
+ ======
3
+
4
+ Lightweight authorization library, that let you choose where and how you write your rules
5
+
6
+ Cant is agnostic : you can use it in any existing or future framework, provided names don't clash
7
+
8
+ Cant is small and even simple : it is 120 lines of code, has no dependencies but for testing
9
+
10
+ Cant can be configured at class level, and support basic configuration inheritance
11
+
12
+
13
+ What does it look like ?
14
+ ========================
15
+
16
+ Examples
17
+ --------
18
+
19
+ * in an existing model
20
+
21
+ class User
22
+ include Cant::Embeddable
23
+
24
+ cant do |action, code|
25
+ (action == :post and Code === code) if drunken?
26
+ end
27
+ end
28
+
29
+ deny {bob.cant? :post, kata}
30
+
31
+ * in a separate model
32
+
33
+ class Permission
34
+ include Cant::Embeddable
35
+
36
+ cant do |user, action, resource|
37
+ (action == :post and Code === resource) if user.drunken?
38
+ end
39
+ end
40
+
41
+ assert {permission.cant? jackie, :post, kata}
42
+
43
+ * in a rails controller
44
+
45
+ class ApplicationController < ActionController::Base
46
+ include Cant::Embeddable
47
+ alias_method :authorize_user!, :die_if_cant!
48
+ helper_method :cant?
49
+
50
+ rescue_from Cant::AccessDenied do |error|
51
+ flash[:error] = error.message
52
+ redirect_to request.referer
53
+ end
54
+ end
55
+
56
+ class KatasController < ApplicationController
57
+ before_filter :authenticate_user!, :authorize_user!
58
+
59
+ cant do |request|
60
+ current_user.drunken? if request.method == 'POST'
61
+ end
62
+ end
63
+
64
+ * in a rack middleware ... I have not experimented yet ...
65
+
66
+ breaking authorization into small independent pieces is valuable, and you do not need a library for that, though using Cant::Editable and Cant::Questionable mixins can help you do that
67
+
68
+
69
+
70
+ Concepts
71
+ ========
72
+
73
+ Cant is simply put a reduce (or [fold](http://learnyousomeerlang.com/higher-order-functions)) on a list of Rules.
74
+
75
+ * __Rule__
76
+
77
+ a tuple of functions {__predicate__, __die__}.
78
+
79
+ * __rules__
80
+
81
+ a list of __Rule__s
82
+
83
+ * __fold__
84
+
85
+ function defining how __rules__ are traversed, and how each predicate is evaluated, and what is the result
86
+
87
+ Cant provide two fold functions, returning both first __Rule__ that predicates, or nil
88
+
89
+ * __predicate__
90
+
91
+ default rationale is : true means cant
92
+
93
+ * __die__
94
+
95
+ a function, that can raise or return any result suitable to your need
96
+
97
+ * __cant?__
98
+
99
+ calls fold function to operate on __rules__
100
+
101
+ * __die\_if\_cant!__
102
+
103
+ calls fold function to operate on __rules__, and calls die function on result
104
+
105
+ How do I define rules ?
106
+ =======================
107
+
108
+ First, choose where you want to define your list, and what information a Rule will need
109
+
110
+ The point of cant is to define a lists of similar rules together, so that they will require similar informations
111
+
112
+ A list of rules can be embedded in an existing class using Cant::Embeddable mixin
113
+
114
+ * define rule and functions at class level
115
+ * evaluate predicates at instance level
116
+
117
+ Then there is one list of rules for any instance of this class, and instance evaluation leads to terser rules
118
+
119
+ Note that a list of rules can be shared by many inquirers, either explicitly or by using class instance variable inheritance feature
120
+
121
+ Default Values
122
+ --------------
123
+
124
+ __Cant__ module has "reasonable" default values for __fold__, __die__, __rules__
125
+
126
+ The Cant::Editable module gather methods to configure a Cant engine (fold, die, rules), and defaults to Cant module values
127
+
128
+ Inheritance
129
+ -----------
130
+
131
+ Cant support _basic_ inheritance functionality for configuration with the Cant::Embeddable module ...
132
+ Ouch what that means ?
133
+
134
+ Given Admin inherits from User
135
+ And User.die {"I'm not dead!"}
136
+ Then assert {Admin.die.call == "I'm not dead!"} is true
137
+
138
+ Well, have a look at read documentation and source code embeddable.rb if you are having trouble with this functionality
139
+
140
+ What is the arity of a __predicate__ function ?
141
+ -----------------------------------------------
142
+
143
+ You are free to pick one that suit your needs
144
+
145
+ There are a couple of things to drive your choice :
146
+
147
+ * params of __cant?__ are passed to __predicate__
148
+
149
+ * params of __die\_if\_cant!__ are passed to __predicate__ and __die__
150
+
151
+ * number and order of params of in a rule list should be the same
152
+
153
+ * the context of predicate evaluation (that is defined in fold function)
154
+ the receiver methods will be available in function
155
+
156
+ * a container can be a handy parameter (Hash)
157
+ _env_, _params_
158
+
159
+ * a block is a proc, and can use default values for params
160
+
161
+ So pick your own semantic, or grow an existing one
162
+
163
+ How do I verify authorization ?
164
+ ===============================
165
+
166
+ Cant very meaning is : you can unless you cant, and you define what you cant
167
+
168
+ Defining _not_ or _negative_ ability require some thinking, and I believe we can do it :)
169
+
170
+ Use __cant?__ method to check
171
+
172
+ Use __die\_if\_cant!__ method to check and run __die__ code
173
+
174
+ When you check, provide the list of parameters you specified in your list of rules
175
+
176
+ Inspired from
177
+ =============
178
+
179
+ * [cancan](https://github.com/ryanb/cancan), as I started with it and saw that it did not functioned in presence of mongoid as of < 1.5 ... so I planned to do something no dependent of a model implementation
180
+
181
+ * [learn you some erlang?](http://learnyousomeerlang.com/higher-order-functions#maps-filters-folds), for the fold illustrations
182
+
183
+ * [Howard](http://rubyquiz.com/quiz67.html) and [Nunemaker](http://railstips.org/blog/archives/2006/11/18/class-and-instance-variables-in-ruby/) for inheritable class instance variables
184
+
185
+
186
+ Does it work ?
187
+ ==============
188
+
189
+ specs are green on mri : [1.9.2-p0, 1.8.7-p302]
190
+
191
+ C0 coverage is acceptable under ruby-1.9.2-p0 (100% is acceptable for this kind of code)
192
+
193
+ There is few lines of code as concept is simple : fold a list of functions...
194
+ Though, we can make it better and lesser, cant we ?
195
+
196
+
197
+ Help|Contribute
198
+ ===============
199
+
200
+ Fill an item in tracker
201
+
202
+ Add a page on wiki
203
+
204
+ Add a spec, open a pull request on topic branch, commit granted on first accepted patch|pull
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ task :clean do
5
+ FileUtils.rm_rf 'pkg', :verbose => true
6
+ end
data/cant.gemspec ADDED
@@ -0,0 +1,52 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "cant/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "cant"
7
+ s.version = Cant::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["thierry.henrio"]
10
+ s.email = ["thierry.henrio@gmail.com"]
11
+ s.homepage = "https://github.com/thierryhenrio"
12
+ s.summary = %q{Tiny authorization library, let you craft your own rules}
13
+ s.description = %q{
14
+ include Cant
15
+ ------------
16
+ class User; include Cant::Embeddable; end
17
+
18
+ class AuthorizationMiddleware; include Cant::Embeddable; end
19
+
20
+ declare rules
21
+ -------------
22
+ User.cant do |action=:update, post|
23
+ not post.user == self if Post === resource and action == :update
24
+ end
25
+
26
+ AuthorizationMiddleware.cant do |env|
27
+ not env['user'] == env['post'].user if env.path =~ /^\posts/ and env.method == 'PUT'
28
+ end
29
+
30
+ verify
31
+ ------
32
+ user.cant? :update, post
33
+ user.die_if_cant! :update, post
34
+
35
+ control
36
+ -------
37
+ rescue_from Cant::AccessDenied do |error|
38
+ flash[:error] = error.message
39
+ redirect_to request.referer
40
+ end
41
+ }
42
+
43
+ s.rubyforge_project = "cant"
44
+
45
+ s.files = `git ls-files`.split("\n")
46
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
47
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
48
+ s.require_paths = ["lib"]
49
+
50
+ s.add_development_dependency 'rspec'
51
+ s.add_development_dependency 'wrong'
52
+ end
@@ -0,0 +1,39 @@
1
+ require 'cant/engine'
2
+ module Cant
3
+ module Embeddable
4
+ # class instance variable can be helpful there
5
+ #
6
+ # http://rubyquiz.com/quiz67.html
7
+ # http://rubykoans.com/
8
+ #
9
+ # and there is code available, though I did not fully parsed it with enlightenment yet :)
10
+ # https://github.com/ahoward/fattr
11
+ #
12
+ # see also
13
+ # http://www.ruby-forum.com/topic/197051
14
+ # http://railstips.org/blog/archives/2006/11/18/class-and-instance-variables-in-ruby/
15
+ #
16
+ class << self
17
+ def included(base)
18
+ base.extend Cant::Editable
19
+
20
+ # XXX this sucks if class defines its own inherited callback and includes Cant::Embeddable
21
+ # double evil : order of inherited and extend statement has an effect : latter overwrite earlier !!!
22
+ class << base
23
+ def inherited(subclass)
24
+ subclass.rules.concat(rules)
25
+ [:die, :fold].each do |attr|
26
+ subclass.send(attr, &(send(attr)))
27
+ end
28
+ super
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ include Cant::Questionable
35
+ def cantfiguration
36
+ self.class
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,175 @@
1
+ module Cant
2
+ module Editable
3
+ # list of Rules
4
+ # returns the list of rules for this device
5
+ def rules
6
+ unless @rules
7
+ @rules = []
8
+ @rules.concat(Cant.rules) unless self == Cant
9
+ end
10
+ @rules
11
+ end
12
+ # add a Rule, as a pair of functions {predicate, die}
13
+ #
14
+ # block form sets predicate function.
15
+ # block can have argument, (as any proc)
16
+ #
17
+ # eg :
18
+ # rooms = [:kitchen]
19
+ # cant {|context| rooms.include?(context[:room]) and context[:user]}
20
+ # cant {current_user.admin?}
21
+ #
22
+ # returns a rule which die function can be configured
23
+ #
24
+ # cant do |controller|
25
+ # controller.request.path =~ /admin/ unless controller.current_user.admin?
26
+ # end.die do |controller|
27
+ # raise AccessDenied.new(controller.request)
28
+ # end
29
+ #
30
+ # options form looks for the predicate and die functions as values in options,
31
+ # under symbols :predicate and :die
32
+ #
33
+ # cant :predicate => proc {|controller| not controller.current_user},
34
+ # :die => proc {|controller| controller.redirect '/users/sign_in'}
35
+ def cant(options={}, &block)
36
+ rule = if block.nil?
37
+ Rule.new(options[:predicate], options[:die] || self.die)
38
+ else
39
+ Rule.new(block, self.die)
40
+ end
41
+ rules << rule
42
+ rule
43
+ end
44
+
45
+ # set default die function for this device
46
+ #
47
+ # example :
48
+ # die do |request|
49
+ # raise AccessDenied, "Cant process #{request}"
50
+ # end
51
+ def die(&block)
52
+ @die = block unless block.nil?
53
+ @die || Cant.die
54
+ end
55
+
56
+ # define fold function
57
+ # fold function has arity 2..n
58
+ # - rules : the rules to traverse
59
+ # - receiver : a Questionable asking for cant?
60
+ # - *args : the arguments for each rule to pass to predicate functions
61
+ #
62
+ # returns a rule if strategy evaluates it cant do
63
+ # nil either
64
+ #
65
+ # eg :
66
+ # fold {true} #=> always cant
67
+ # fold {|rules, _receiver, context| rules.reverse.find {|rule| rule.predicate?(context)}}
68
+ def fold(&block)
69
+ @fold = block unless block.nil?
70
+ @fold || Cant.fold
71
+ end
72
+ end
73
+
74
+ # module level configuration
75
+ # Cant.fold
76
+ # Cant.die
77
+ class << self
78
+ include Editable
79
+ end
80
+ # module level default values
81
+ fold {|rules, receiver, *args| Folds.first_rule_that_predicates_in_receiver(rules, receiver, *args)}
82
+ die {|*args| raise AccessDenied, "Cant you do that #{args}, can you ??"}
83
+
84
+ # questionable interface
85
+ module Questionable
86
+ attr_writer :cantfiguration
87
+ def cantfiguration
88
+ @cantfiguration ||= Object.new.extend(Editable)
89
+ end
90
+ # return strategy fold for rules with context (a rule or nil)
91
+ def cant?(*args)
92
+ cantfiguration.fold.call(cantfiguration.rules, self, *args)
93
+ end
94
+ # return evaled die function of strategy fold
95
+ def die_if_cant!(*args)
96
+ rule = cant?(*args)
97
+ rule.die!(*args) if rule
98
+ end
99
+ end
100
+
101
+ # standalone engine
102
+ class Engine
103
+ include Editable
104
+ include Questionable
105
+ def cantfiguration
106
+ self
107
+ end
108
+ end
109
+
110
+ # a Rule is a pair of functions :
111
+ # - predicate(*args), that return true if predicate is met (hint of predicate?)
112
+ # - die(*args), that cant raise if convenient
113
+ #
114
+ # this class could have been:
115
+ # -spared
116
+ # -an Array, with an optional syntactic sugar
117
+ class Rule
118
+ # a new rule with a predicate and response function
119
+ def initialize(predicate=nil, die=Cant.die)
120
+ @predicate=predicate
121
+ @die = die
122
+ end
123
+ # set or return predicate function using block
124
+ #
125
+ # return true means rule can die
126
+ #
127
+ # example :
128
+ # predicate do |request|
129
+ # not current_user.admin? if request.path =~ /^\/admin/
130
+ # end
131
+ def predicate(&block)
132
+ @predicate = block unless block.nil?
133
+ @predicate
134
+ end
135
+ # evaluates predicate function with args
136
+ def predicate?(*args)
137
+ predicate.call(*args)
138
+ end
139
+ # set die function using block
140
+ #
141
+ # example :
142
+ # die do |request|
143
+ # raise AccessDenied, "Cant process #{request}"
144
+ # end
145
+ def die(&block)
146
+ @die = block unless block.nil?
147
+ @die
148
+ end
149
+ # call die function with args
150
+ #
151
+ # *args - variable list of arguments
152
+ def die!(*args)
153
+ die.call(*args)
154
+ end
155
+ end
156
+
157
+ module Folds
158
+ class << self
159
+ # first rule that predicates to true, all args are carried to closure
160
+ # this strategy does not use the receiver argument, and evaluate each predicate with the binding of its creation
161
+ def first_rule_that_predicates(rules, _receiver, *args)
162
+ rules.find {|rule| rule.predicate?(*args)}
163
+ end
164
+ # strategy that evals block in receiver context
165
+ # closure is rebound to receiver (acting as a function)
166
+ def first_rule_that_predicates_in_receiver(rules, receiver, *args)
167
+ rules.find do |rule|
168
+ receiver.instance_exec(*args, &(rule.predicate))
169
+ end
170
+ end
171
+ end
172
+ end
173
+
174
+ class AccessDenied < RuntimeError; end
175
+ end
@@ -0,0 +1,3 @@
1
+ module Cant
2
+ VERSION = "0.2.0"
3
+ end
data/lib/cant.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'cant/engine'
2
+ require 'cant/embeddable'
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+ require 'cant/embeddable'
3
+
4
+ describe Cant::Embeddable do
5
+ class Foo
6
+ def foo?; true; end
7
+ include Cant::Embeddable
8
+ cant {|x| 9<x if foo?}
9
+ end
10
+ class Bar < Foo
11
+ def foo?; false; end
12
+ def bar?; true; end
13
+ cant {|x| x>20 if bar?}
14
+ die {true}
15
+ end
16
+ describe 'including class' do
17
+ it 'can configured, and instance be queried' do
18
+ foo = Foo.new
19
+ deny {foo.cant?(9)}
20
+ assert {rescuing{foo.die_if_cant!(10)}.is_a? Cant::AccessDenied}
21
+ end
22
+ end
23
+ describe 'subclass' do
24
+ # this does not work !!!
25
+ #
26
+ # it 'has base class callback called' do
27
+ # assert {bar.imposterized?}
28
+ # end
29
+ it 'has superclass and self rules' do
30
+ assert {Bar.rules.length == 2}
31
+ end
32
+ it 'subclass can use it' do
33
+ bar = Bar.new
34
+ bar.cant?(10)
35
+ assert {bar.cant?(21)}
36
+ end
37
+ # XXX this might be considered as a bug
38
+ it 'does not gain superclass rules when modified after class loading' do
39
+ Foo.rules << :whop
40
+ deny {Bar.rules.include? :whoop}
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,148 @@
1
+ require 'spec_helper'
2
+ require 'cant'
3
+ require 'stunts/stunt'
4
+
5
+ describe Cant do
6
+ it 'has fold default value to first_rule_that_predicates_in_receiver' do
7
+ Cant.cant {|x| include? x}
8
+ a = [1]
9
+ assert {Cant.fold.call(Cant.rules, a, 1)}
10
+ end
11
+ it 'has a raising die function' do
12
+ e = rescuing {Cant.die.call(:do, :that)}
13
+ assert {e.is_a? Cant::AccessDenied}
14
+ assert {e.message =~ /^Cant you do that.*\?$/}
15
+ end
16
+ it 'rules is enumerable' do
17
+ assert {Cant.rules.respond_to? :each}
18
+ end
19
+ after do
20
+ Cant.rules.clear
21
+ end
22
+ end
23
+
24
+
25
+ describe Cant::Editable do
26
+ let(:editable) {Object.new.extend Cant::Editable}
27
+ describe "#cant" do
28
+ it 'returns a Cant::Rule' do
29
+ rule = editable.cant {true}
30
+ assert {rule.is_a? Cant::Rule}
31
+ end
32
+ it 'accepts an option argument, that can provide both predicate and response functions' do
33
+ predicate, die = proc {:predicate}, proc {:die}
34
+ rule = editable.cant :predicate => predicate, :die => die
35
+ assert {rule.predicate == predicate}
36
+ end
37
+ end
38
+
39
+ describe '#fold' do
40
+ context 'with a block' do
41
+ it 'sets the fold proc' do
42
+ editable.fold {:onoes}
43
+ assert {editable.fold.call == :onoes}
44
+ end
45
+ end
46
+ end
47
+
48
+ describe "#die" do
49
+ before do
50
+ # XXX preserving default value for class instance variable
51
+ @proc=Cant.instance_variable_get(:@die)
52
+ Cant.die{2}
53
+ end
54
+ it 'provide default die function for this engine rules' do
55
+ editable.die{:im_not_dead}
56
+ assert {editable.cant.die.call == :im_not_dead}
57
+ end
58
+ it "returns top level response function as a fall case" do
59
+ assert {editable.cant.die.call == 2}
60
+ end
61
+ after do
62
+ Cant.instance_variable_set(:@die, @proc)
63
+ end
64
+ end
65
+
66
+ describe "rules" do
67
+ it 'cant does not creep in module rules' do
68
+ editable.cant {true}
69
+ deny {Cant.rules.include? editable.rules.first}
70
+ end
71
+ it 'has module rules first' do
72
+ Cant.rules << :first
73
+ assert {editable.rules == [:first]}
74
+ end
75
+ after do
76
+ Cant.rules.clear
77
+ end
78
+ end
79
+ end
80
+
81
+ describe Cant::Questionable do
82
+ include Cant::Questionable
83
+ # query
84
+ describe '#cant?' do
85
+ let(:admin) {Stunt.new(:admin => true)}
86
+ let(:user) {Stunt.new(:admin => false)}
87
+
88
+ before do
89
+ cantfiguration.cant{|context| context[:url] =~ /admin/ and context[:user].admin?}
90
+ end
91
+ it 'authorize admin on /admin/users' do
92
+ assert {cant?(:url => '/admin/users', :user => admin)}
93
+ end
94
+ it 'deny user on /admin/users' do
95
+ deny {cant?(:url => '/admin/users', :user => user)}
96
+ end
97
+ end
98
+
99
+ # query or die
100
+ describe '#die_if_cant!' do
101
+ it "return die function evaluation" do
102
+ cantfiguration.cant{true}.die{1}
103
+ assert {die_if_cant! == 1}
104
+ end
105
+ end
106
+
107
+ it 'cant be given a cantfiguration' do
108
+ self.cantfiguration = 1
109
+ assert{cantfiguration == 1}
110
+ end
111
+ end
112
+
113
+ describe Cant::Rule do
114
+ let(:rule) {Cant::Rule.new}
115
+ describe "#die, #die!" do
116
+ it 'die! return call of die block' do
117
+ rule.die {1}
118
+ assert {rule.die! == 1}
119
+ end
120
+ end
121
+ end
122
+
123
+ describe Cant::Folds do
124
+ describe "#first_rule_that_predicates" do
125
+ it 'carries all tailing args to closure (there is a first unused one)' do
126
+ rule = Cant::Rule.new(proc {|x,y| x+y==10})
127
+ deny {Cant::Folds.first_rule_that_predicates([rule], nil, 2, 7)}
128
+ assert {Cant::Folds.first_rule_that_predicates([rule], nil, 2, 8) == rule}
129
+ end
130
+ end
131
+ describe "#first_rule_that_predicates_in_receiver" do
132
+ let(:receiver) {Stunt.new(:admin => true)}
133
+ it 'carry args to function evaled in receiver' do
134
+ rule = Cant::Rule.new(lambda {|x,y| admin? if (x+y == 2)})
135
+ assert {Cant::Folds.first_rule_that_predicates_in_receiver([rule], receiver, 1, 1)}
136
+ assert {Cant::Folds.first_rule_that_predicates_in_receiver([rule], receiver, 1, 1)}
137
+ end
138
+ end
139
+ end
140
+
141
+ describe Cant::Engine do
142
+ let(:engine) {Cant::Engine.new}
143
+ it 'can be configured and queried' do
144
+ engine.cant{|x,y,z| x+y != z}.die{'bad arith'}
145
+ deny {engine.cant?(1,2,3)}
146
+ assert {engine.die_if_cant!(1,2,4) == 'bad arith'}
147
+ end
148
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+ require 'models/user'
3
+
4
+ describe User do
5
+ before do
6
+ User.cant do |env|
7
+ env[:path] =~ /^\/admin/ unless admin?
8
+ end
9
+ end
10
+
11
+ context "without being an admin" do
12
+ let(:user) {User.new}
13
+ it 'can not access /admin ' do
14
+ assert {user.cant?(:path => '/admin')}
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,8 @@
1
+ require 'cant/embeddable'
2
+
3
+ class User
4
+ attr_accessor :admin
5
+ alias_method :admin?, :admin
6
+
7
+ include Cant::Embeddable
8
+ end
@@ -0,0 +1,6 @@
1
+ require 'rspec'
2
+ require 'wrong/adapters/rspec'
3
+ RSpec.configure do |configuration|
4
+ end
5
+
6
+ (require 'simplecov'; SimpleCov.start) if ENV['COVERAGE']
@@ -0,0 +1,11 @@
1
+ class Stunt
2
+ def initialize(properties={})
3
+ properties.each { |k,v|
4
+ (class << self; self end).module_eval {
5
+ attr_accessor k
6
+ alias_method "#{k}?".to_sym, k if [TrueClass, FalseClass].include?(v.class)
7
+ }
8
+ self.send("#{k}=", v)
9
+ }
10
+ end
11
+ end
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cant
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 2
8
+ - 0
9
+ version: 0.2.0
10
+ platform: ruby
11
+ authors:
12
+ - thierry.henrio
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-01-03 00:00:00 +01:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rspec
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :development
32
+ version_requirements: *id001
33
+ - !ruby/object:Gem::Dependency
34
+ name: wrong
35
+ prerelease: false
36
+ requirement: &id002 !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 0
43
+ version: "0"
44
+ type: :development
45
+ version_requirements: *id002
46
+ description: "\n include Cant\n ------------\n class User; include Cant::Embeddable; end\n \n class AuthorizationMiddleware; include Cant::Embeddable; end\n \n declare rules\n -------------\n User.cant do |action=:update, post|\n not post.user == self if Post === resource and action == :update\n end\n \n AuthorizationMiddleware.cant do |env|\n not env['user'] == env['post'].user if env.path =~ /^\\posts/ and env.method == 'PUT'\n end\n \n verify\n ------\n user.cant? :update, post\n user.die_if_cant! :update, post\n\n control\n -------\n rescue_from Cant::AccessDenied do |error|\n flash[:error] = error.message\n redirect_to request.referer\n end\n "
47
+ email:
48
+ - thierry.henrio@gmail.com
49
+ executables: []
50
+
51
+ extensions: []
52
+
53
+ extra_rdoc_files: []
54
+
55
+ files:
56
+ - .gitignore
57
+ - .rspec
58
+ - Gemfile
59
+ - Gemfile.lock
60
+ - Guardfile
61
+ - LICENSE.mit
62
+ - README.markdown
63
+ - Rakefile
64
+ - cant.gemspec
65
+ - lib/cant.rb
66
+ - lib/cant/embeddable.rb
67
+ - lib/cant/engine.rb
68
+ - lib/cant/version.rb
69
+ - spec/cant/embeddable_spec.rb
70
+ - spec/cant/engine_spec.rb
71
+ - spec/integration_spec.rb
72
+ - spec/models/user.rb
73
+ - spec/spec_helper.rb
74
+ - spec/stunts/stunt.rb
75
+ has_rdoc: true
76
+ homepage: https://github.com/thierryhenrio
77
+ licenses: []
78
+
79
+ post_install_message:
80
+ rdoc_options: []
81
+
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ segments:
90
+ - 0
91
+ version: "0"
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ none: false
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ segments:
98
+ - 0
99
+ version: "0"
100
+ requirements: []
101
+
102
+ rubyforge_project: cant
103
+ rubygems_version: 1.3.7
104
+ signing_key:
105
+ specification_version: 3
106
+ summary: Tiny authorization library, let you craft your own rules
107
+ test_files:
108
+ - spec/cant/embeddable_spec.rb
109
+ - spec/cant/engine_spec.rb
110
+ - spec/integration_spec.rb
111
+ - spec/models/user.rb
112
+ - spec/spec_helper.rb
113
+ - spec/stunts/stunt.rb