cant 0.2.0

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,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