kissifer-rack-ticket-office 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 kissifer
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,7 @@
1
+ = rack-ticket-office
2
+
3
+ Description goes here.
4
+
5
+ == Copyright
6
+
7
+ Copyright (c) 2009 kissifer. See LICENSE for details.
@@ -0,0 +1,52 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "rack-ticket-office"
8
+ gem.summary = %Q{Rack middleware to allow/deny access to remote user by http method, path and query params.}
9
+ gem.email = "tierneydrchris@gmail.com"
10
+ gem.homepage = "http://github.com/kissifer/rack-ticket-office"
11
+ gem.authors = ["kissifer"]
12
+ gem.add_dependency('hash-persistent', '>= 0.3.2')
13
+ gem.add_dependency('sinatra-options-route')
14
+ gem.add_dependency('sinatra-response-header-helpers')
15
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
+ end
17
+
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
20
+ end
21
+
22
+ require 'spec/rake/spectask'
23
+ Spec::Rake::SpecTask.new(:spec) do |spec|
24
+ spec.libs << 'lib' << 'spec'
25
+ spec.spec_files = FileList['spec/**/*_spec.rb']
26
+ spec.spec_opts = ['--options spec/spec.opts']
27
+ end
28
+
29
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
30
+ spec.libs << 'lib' << 'spec'
31
+ spec.pattern = 'spec/**/*_spec.rb'
32
+ spec.rcov = true
33
+ end
34
+
35
+
36
+ task :default => :spec
37
+
38
+ require 'rake/rdoctask'
39
+ Rake::RDocTask.new do |rdoc|
40
+ if File.exist?('VERSION.yml')
41
+ config = YAML.load(File.read('VERSION.yml'))
42
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
43
+ else
44
+ version = ""
45
+ end
46
+
47
+ rdoc.rdoc_dir = 'rdoc'
48
+ rdoc.title = "rack-ticket-office #{version}"
49
+ rdoc.rdoc_files.include('README*')
50
+ rdoc.rdoc_files.include('lib/**/*.rb')
51
+ end
52
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,3 @@
1
+ require 'rubygems'
2
+ require File.join(File.dirname(__FILE__), 'ticket_collection')
3
+
@@ -0,0 +1,45 @@
1
+ require 'rubygems'
2
+ require File.join(File.dirname(__FILE__), 'ticket')
3
+
4
+ require 'sinatra/base'
5
+ require 'sinatra/options-route'
6
+ require 'sinatra/response-header-helpers'
7
+
8
+ module Rack
9
+ module TicketOffice
10
+ class Booth < Sinatra::Base
11
+ register Sinatra::OptionsRoute
12
+ helpers Sinatra::ResponseHeaderHelpers
13
+
14
+ attr_writer :store, :counter
15
+
16
+ def template(&block)
17
+ @template = block
18
+ end
19
+
20
+ options_route "/?" do
21
+ allow :post, :options
22
+ halt
23
+ end
24
+
25
+ [:get, :put, :delete].each do |method|
26
+ send(method, "/") do
27
+ allow :post, :options
28
+ halt 405
29
+ end
30
+ end
31
+
32
+ post "/" do
33
+ halt 500 unless request.env['REMOTE_USER']
34
+ new_id = @counter.next
35
+ ticket_initialiser = @template.call(new_id)
36
+ ticket = @store.create(ticket_initialiser)
37
+ ticket.key = new_id.to_s
38
+ ticket.user = request.env['REMOTE_USER']
39
+ ticket.save
40
+ ""
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,32 @@
1
+ class Hash
2
+ def assert_valid_keys(*valid_keys)
3
+ # Lifted from activesupport
4
+ unknown_keys = keys - [valid_keys].flatten
5
+ raise(ArgumentError, "Unknown key(s): #{unknown_keys.join(", ")}") unless unknown_keys.empty?
6
+ end
7
+
8
+ def symbolize_keys!
9
+ # Lifted from merb
10
+ each do |key, value|
11
+ sym = key.respond_to?(:to_sym) ? key.to_sym : key
12
+ self[sym] = value
13
+ delete(key) unless key == sym
14
+ end
15
+ self
16
+ end
17
+ end
18
+
19
+ class Array
20
+ def assert_valid_elements(*valid_elements)
21
+ # Lifted from activesupport
22
+ unknown_elements = self - [valid_elements].flatten
23
+ raise(ArgumentError, "Unknown elements(s): #{unknown_elements.join(", ")}") unless unknown_elements.empty?
24
+ end
25
+
26
+ def symbolize_elements!
27
+ collect! do |element|
28
+ element.respond_to?(:to_sym) ? element.to_sym : element
29
+ end
30
+ self
31
+ end
32
+ end
@@ -0,0 +1,38 @@
1
+ require 'rubygems'
2
+ require 'hash-persistent/resource'
3
+ require 'hash-persistent/counter'
4
+ require File.expand_path(File.dirname(__FILE__) + '/url_filter')
5
+
6
+ module Rack
7
+ module TicketOffice
8
+ class Ticket
9
+ include HashPersistent::Resource
10
+
11
+ attr_reader :filters
12
+ attr_accessor :user
13
+
14
+ def initialize(filter_initializers)
15
+ begin
16
+ @filters = filter_initializers.collect do |filter_initializer|
17
+ UrlFilter.new(filter_initializer)
18
+ end
19
+ rescue
20
+ raise ArgumentError
21
+ end
22
+
23
+ raise ArgumentError if @filters.empty?
24
+ end
25
+
26
+ def match_any?(env)
27
+ @filters.any? do |filter|
28
+ filter.match(env)
29
+ end
30
+ end
31
+
32
+ def ==(other)
33
+ return (self.class == other.class) &&
34
+ (self.filters == other.filters)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'hash-persistent/collection'
3
+
4
+ module Rack
5
+ module TicketOffice
6
+ class TicketCollection
7
+ include HashPersistent::Collection
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,64 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/monkey_patches')
2
+ require 'uri'
3
+
4
+ module Rack
5
+ module TicketOffice
6
+ class UrlFilter
7
+ attr_reader :request_methods, :path_info, :required_queries, :allowed_queries
8
+
9
+ def initialize(request_spec)
10
+ begin
11
+ request_spec.assert_valid_keys(:request_methods, :path, :required_queries, :allowed_queries)
12
+ request_spec[:request_methods].assert_valid_elements(:get, :post, :put, :delete, :head, :options, :all)
13
+ "Fred".match(request_spec[:path])
14
+ rescue
15
+ raise ArgumentError
16
+ end
17
+
18
+ @request_methods = request_spec[:request_methods]
19
+ @request_methods = [:get, :post, :put, :delete, :head, :options] if request_spec[:request_methods].include?(:all)
20
+ @path_info = request_spec[:path]
21
+ @required_queries = request_spec[:required_queries] || {}
22
+ @allowed_queries = request_spec[:allowed_queries] || []
23
+ end
24
+
25
+ def match(env)
26
+ req = Rack::Request.new(env)
27
+ return false unless @request_methods.include?(req.request_method.downcase.to_sym)
28
+
29
+ path_match = req.path_info.match(@path_info)
30
+ return false unless path_match and path_match[0].eql?(req.path_info)
31
+
32
+ @required_queries.each do |key, value|
33
+ return false unless req.GET[key] == value
34
+ end
35
+
36
+ req.GET.each_key do |key|
37
+ return false unless @required_queries.has_key?(key) or @allowed_queries.include?(key)
38
+ end
39
+
40
+ true
41
+ end
42
+
43
+ def marshal_dump
44
+ [ @request_methods, @path_info, @required_queries, @allowed_queries ]
45
+ end
46
+
47
+ def marshal_load(variables)
48
+ @request_methods = variables[0]
49
+ @path_info = variables[1]
50
+ @required_queries = variables[2]
51
+ @allowed_queries = variables[3]
52
+ end
53
+
54
+ def ==(other)
55
+ return (self.class == other.class) &&
56
+ (self.request_methods == other.request_methods) &&
57
+ (self.path_info == other.path_info) &&
58
+ (self.required_queries == other.required_queries) &&
59
+ (self.allowed_queries == other.allowed_queries)
60
+ true
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,69 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{rack-ticket-office}
5
+ s.version = "0.0.1"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["kissifer"]
9
+ s.date = %q{2009-06-09}
10
+ s.email = %q{tierneydrchris@gmail.com}
11
+ s.extra_rdoc_files = [
12
+ "LICENSE",
13
+ "README.rdoc"
14
+ ]
15
+ s.files = [
16
+ ".document",
17
+ ".gitignore",
18
+ "LICENSE",
19
+ "README.rdoc",
20
+ "Rakefile",
21
+ "VERSION",
22
+ "lib/rack/ticket-office/barrier.rb",
23
+ "lib/rack/ticket-office/booth.rb",
24
+ "lib/rack/ticket-office/monkey_patches.rb",
25
+ "lib/rack/ticket-office/ticket.rb",
26
+ "lib/rack/ticket-office/ticket_collection.rb",
27
+ "lib/rack/ticket-office/url_filter.rb",
28
+ "rack-ticket-office.gemspec",
29
+ "spec/barrier_spec.rb",
30
+ "spec/booth_spec.rb",
31
+ "spec/spec.opts",
32
+ "spec/spec_helper.rb",
33
+ "spec/ticket_collection_spec.rb",
34
+ "spec/ticket_spec.rb",
35
+ "spec/url_filter_spec.rb"
36
+ ]
37
+ s.homepage = %q{http://github.com/kissifer/rack-ticket-office}
38
+ s.rdoc_options = ["--charset=UTF-8"]
39
+ s.require_paths = ["lib"]
40
+ s.rubygems_version = %q{1.3.4}
41
+ s.summary = %q{Rack middleware to allow/deny access to remote user by http method, path and query params.}
42
+ s.test_files = [
43
+ "spec/barrier_spec.rb",
44
+ "spec/booth_spec.rb",
45
+ "spec/spec_helper.rb",
46
+ "spec/ticket_collection_spec.rb",
47
+ "spec/ticket_spec.rb",
48
+ "spec/url_filter_spec.rb"
49
+ ]
50
+
51
+ if s.respond_to? :specification_version then
52
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
53
+ s.specification_version = 3
54
+
55
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
56
+ s.add_runtime_dependency(%q<hash-persistent>, [">= 0.3.2"])
57
+ s.add_runtime_dependency(%q<sinatra-options-route>, [">= 0"])
58
+ s.add_runtime_dependency(%q<sinatra-response-header-helpers>, [">= 0"])
59
+ else
60
+ s.add_dependency(%q<hash-persistent>, [">= 0.3.2"])
61
+ s.add_dependency(%q<sinatra-options-route>, [">= 0"])
62
+ s.add_dependency(%q<sinatra-response-header-helpers>, [">= 0"])
63
+ end
64
+ else
65
+ s.add_dependency(%q<hash-persistent>, [">= 0.3.2"])
66
+ s.add_dependency(%q<sinatra-options-route>, [">= 0"])
67
+ s.add_dependency(%q<sinatra-response-header-helpers>, [">= 0"])
68
+ end
69
+ end
@@ -0,0 +1,4 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "Rack::TicketOffice::Barrier" do
4
+ end
@@ -0,0 +1,111 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+ require 'spec/interop/test'
3
+ require 'rack/test'
4
+ require 'hash-persistent/resource'
5
+ require 'hash-persistent/counter'
6
+
7
+ describe "Rack::TicketOffice::Booth" do
8
+ include Rack::Test::Methods
9
+
10
+ before(:each) do
11
+ def app
12
+ Rack::TicketOffice::Booth.new
13
+ end
14
+ end
15
+
16
+ context "at root URL /" do
17
+ it "it should return valid methods in reponse to an OPTIONS method request" do
18
+ options_request_env = Rack::MockRequest.env_for("/", {:method => "OPTIONS"})
19
+ request("/", options_request_env)
20
+
21
+ last_response.should be_ok
22
+ methods = last_response['Allow'].split(", ")
23
+
24
+ methods.should include('OPTIONS')
25
+ methods.should include('POST')
26
+ methods.should_not include('GET')
27
+ methods.should_not include('PUT')
28
+ methods.should_not include('DELETE')
29
+ methods.should_not include('HEAD')
30
+ end
31
+
32
+ it "it should (only) return valid methods in reponse to an invalid request method" do
33
+ statuses = []
34
+ all_methods = []
35
+
36
+ get "/"
37
+ statuses << last_response.status
38
+ all_methods << last_response['Allow'].split(", ")
39
+
40
+ put "/"
41
+ statuses << last_response.status
42
+ all_methods << last_response['Allow'].split(", ")
43
+
44
+ delete "/"
45
+ statuses << last_response.status
46
+ all_methods << last_response['Allow'].split(", ")
47
+
48
+ head "/"
49
+ statuses << last_response.status
50
+ all_methods << last_response['Allow'].split(", ")
51
+
52
+ statuses.uniq!.should == [405]
53
+
54
+ all_methods.each do |methods|
55
+ methods.should include('OPTIONS')
56
+ methods.should include('POST')
57
+ methods.should_not include('GET')
58
+ methods.should_not include('PUT')
59
+ methods.should_not include('DELETE')
60
+ methods.should_not include('HEAD')
61
+ end
62
+ end
63
+
64
+ context "in response to POST" do
65
+ before(:each) do
66
+ @store = HashPersistent::Store.new({}, "") do |the_store|
67
+ the_store.managed_class = Rack::TicketOffice::Ticket
68
+ end
69
+ @counter = HashPersistent::Counter.new(HashPersistent::Store.new({}, ""), "counter")
70
+ @template = lambda{ |new_id| [{:request_methods => [:all], :path => "/some/path/#{new_id}"}]}
71
+
72
+ def app
73
+ Rack::TicketOffice::Booth.new do |booth|
74
+ booth.store = @store
75
+ booth.counter = @counter
76
+ booth.template &@template
77
+ end
78
+ end
79
+ end
80
+
81
+
82
+ it "should signal server error if REMOTE_USER is not defined in the env" do
83
+ post "/"
84
+ last_response.status.should == 500
85
+ end
86
+
87
+ context "(configuration)" do
88
+ it "should do the right thing when poorly configured"
89
+ end
90
+
91
+ context "when supplied REMOTE_USER in the env" do
92
+ it "should return ok" do
93
+ post "/", {}, {'REMOTE_USER' => "fred"}
94
+ last_response.should be_ok
95
+ end
96
+
97
+ it "should ask for a new ticket id, create a ticket using the supplied template, and save the ticket" do
98
+ key = 5
99
+ expected_ticket_initialiser = @template.call(key)
100
+ expected_ticket = Rack::TicketOffice::Ticket.new(expected_ticket_initialiser)
101
+ expected_ticket.user = "fred"
102
+ expected_ticket.key = key
103
+
104
+ @counter.should_receive(:next).and_return(key)
105
+ post "/", {}, {'REMOTE_USER' => "fred"}
106
+ @store.find(key.to_s).should == expected_ticket
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,2 @@
1
+ --colour
2
+ --format nested
@@ -0,0 +1,26 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+ require 'rack/mock'
4
+ require "json/ext"
5
+ require 'hash-persistent'
6
+
7
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
8
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
9
+ require 'rack/ticket-office/url_filter'
10
+ require 'rack/ticket-office/ticket'
11
+ require 'rack/ticket-office/ticket_collection'
12
+ require 'rack/ticket-office/booth'
13
+ require 'rack/ticket-office/barrier'
14
+
15
+ class Dummy_NoStringRep
16
+ undef to_s
17
+ end
18
+
19
+ class Dummy_RestrictedHash < Hash
20
+ undef each, each_key, each_pair, each_value
21
+ #TODO: expand this list, or just add dependency to one of the moneta implementations?
22
+ end
23
+
24
+ Spec::Runner.configure do |config|
25
+
26
+ end
@@ -0,0 +1,12 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "Rack::TicketOffice::TicketCollection" do
4
+ context "(persistence)" do
5
+ it "should include the HashPersistent::Collection module" do
6
+ Rack::TicketOffice::TicketCollection.included_modules.should include(HashPersistent::Collection)
7
+ end
8
+
9
+ # TODO: we ought to be able to specify more behaviour here. For instance, this class is designed to attach to
10
+ # the Ticket resource, using :user as a basis.
11
+ end
12
+ end
@@ -0,0 +1,97 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "Rack::TicketOffice::Ticket" do
4
+ context "(on creation)" do
5
+ it "should expect an argument" do
6
+ lambda{Rack::TicketOffice::Ticket.new}.should raise_error(ArgumentError)
7
+ end
8
+
9
+ it "should accept an initialiser that is an array with at least one filter initialiser" do
10
+ initialiser = [{:request_methods => [:all], :path => "/some/path/or.other/"}]
11
+ lambda{Rack::TicketOffice::Ticket.new(initialiser)}.should_not raise_error
12
+ end
13
+
14
+ # TODO: all this stuff seems over the top when we've already got a filter class checking its
15
+ # initialiser. Um...
16
+ it "should reject an initialiser that is not an array with at least one filter initialiser" do
17
+ initialiser_1 = {:request_methods => [:all], :path => "/some/path/or.other/"}
18
+ initialiser_2 = 1
19
+ initialiser_3 = []
20
+ initialiser_4 = {}
21
+ initialiser_5 = nil
22
+
23
+ lambda{Rack::TicketOffice::Ticket.new(initialiser_1)}.should raise_error(ArgumentError)
24
+ lambda{Rack::TicketOffice::Ticket.new(initialiser_2)}.should raise_error(ArgumentError)
25
+ lambda{Rack::TicketOffice::Ticket.new(initialiser_3)}.should raise_error(ArgumentError)
26
+ lambda{Rack::TicketOffice::Ticket.new(initialiser_4)}.should raise_error(ArgumentError)
27
+ lambda{Rack::TicketOffice::Ticket.new(initialiser_5)}.should raise_error(ArgumentError)
28
+ end
29
+
30
+ it "should reject an initialiser with an invalid filter initialiser" do
31
+ initialiser = [{:request_methods => [:put], :path => "/some/path/or.other/"},
32
+ {:request_methods => [:get], :pathx => "/some/other/path/or.other/"}]
33
+
34
+ lambda{Rack::TicketOffice::Ticket.new(initialiser)}.should raise_error(ArgumentError)
35
+ end
36
+ end
37
+
38
+ context "(after creation)" do
39
+ it "should return its array of filters when asked" do
40
+ initialiser_1 = {:request_methods => [:all], :path => "/some/path/or.other/"}
41
+ initialiser_2 = {:request_methods => [:get, :post], :path => "/another/path/"}
42
+ initialiser_3 = {:request_methods => [:put, :delete], :path => "/", :required_queries => {"query_a" => "value_a", "query_b" => "value_b"}}
43
+
44
+ filters = []
45
+ filters << Rack::TicketOffice::UrlFilter.new(initialiser_1)
46
+ filters << Rack::TicketOffice::UrlFilter.new(initialiser_2)
47
+ filters << Rack::TicketOffice::UrlFilter.new(initialiser_3)
48
+
49
+ Rack::TicketOffice::Ticket.new([initialiser_1, initialiser_2, initialiser_3]).filters.should == filters
50
+ end
51
+ end
52
+
53
+ context "(when matching against a rake env)" do
54
+ it "should report a match against any one of its set of filters" do
55
+ initialiser = [{:request_methods => [:all], :path => "/some/path/or.other/"},
56
+ {:request_methods => [:get, :post], :path => "/another/path/"},
57
+ {:request_methods => [:put, :delete], :path => "/", :required_queries => {"query_a" => "value_a"}}]
58
+
59
+ set = Rack::TicketOffice::Ticket.new(initialiser)
60
+
61
+ env_a = Rack::MockRequest.env_for("/some/path/or.other/", {:method => "POST"})
62
+ env_b = Rack::MockRequest.env_for("/another/path/", {:method => "GET"})
63
+ env_c = Rack::MockRequest.env_for("/?query_a=value_a", {:method => "DELETE"})
64
+
65
+ set.match_any?(env_a).should be_true
66
+ set.match_any?(env_b).should be_true
67
+ set.match_any?(env_c).should be_true
68
+ end
69
+
70
+ it "should report a non-match when none of its filters match" do
71
+ initialiser = [{:request_methods => [:all], :path => "/some/path/or.other/", :allowed_queries => ["query_c"]},
72
+ {:request_methods => [:get, :post], :path => "/another/path/"},
73
+ {:request_methods => [:put, :delete], :path => "/", :required_queries => {"query_a" => "value_a"}}]
74
+
75
+ set = Rack::TicketOffice::Ticket.new(initialiser)
76
+
77
+ env = Rack::MockRequest.env_for("/?query_a=value_a&query_c=value_c", {:method => "POST"})
78
+
79
+ set.match_any?(env).should be_false
80
+ end
81
+ end
82
+
83
+ context "(persistence)" do
84
+ it "should include the HashPersistent::Resource and HashPersistent::Counter modules" do
85
+ Rack::TicketOffice::Ticket.included_modules.should include(HashPersistent::Resource)
86
+ end
87
+ end
88
+
89
+ context "(other attributes)" do
90
+ it "should allow instances to be tagged with a user" do
91
+ initialiser = [{:request_methods => [:all], :path => "/some/path/or.other/"}]
92
+ filter_set = Rack::TicketOffice::Ticket.new(initialiser)
93
+ filter_set.user = "fred"
94
+ filter_set.user.should == "fred"
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,197 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "Rack::TicketOffice::UrlFilter" do
4
+ context "(on creation)" do
5
+ before(:each) do
6
+ @initialiser = {:request_methods => [:options, :head, :get, :post, :put, :delete, :all],
7
+ :path => "/some/path/or.other",
8
+ :required_queries => {"query_a" => "value_a", "query_a" => "value_b"},
9
+ :allowed_queries => ["query_c", "query_d"]}
10
+ end
11
+
12
+ it "should accept a valid initialiser" do
13
+ lambda {Rack::TicketOffice::UrlFilter.new(@initialiser)}.should_not raise_error
14
+ end
15
+
16
+ it "should reject unknown keys in the initialiser" do
17
+ @initialiser.merge!(:unknown => "unknown")
18
+ lambda {Rack::TicketOffice::UrlFilter.new(@initialiser)}.should raise_error(ArgumentError)
19
+ end
20
+
21
+ it "should an initialiser with missing methods" do
22
+ @initialiser.delete(:request_methods)
23
+ lambda {Rack::TicketOffice::UrlFilter.new(@initialiser)}.should raise_error(ArgumentError)
24
+ end
25
+
26
+ it "should reject invalid methods" do
27
+ @initialiser[:request_methods].push(:unknown)
28
+ lambda {Rack::TicketOffice::UrlFilter.new(@initialiser)}.should raise_error(ArgumentError)
29
+ end
30
+
31
+ it "should an initialiser with a missing path" do
32
+ @initialiser.delete(:path)
33
+ lambda {Rack::TicketOffice::UrlFilter.new(@initialiser)}.should raise_error(ArgumentError)
34
+ end
35
+
36
+ it "should reject a path that cannot be matched against a string" do
37
+ @initialiser[:path] = 1
38
+ lambda {Rack::TicketOffice::UrlFilter.new(@initialiser)}.should raise_error(ArgumentError)
39
+ end
40
+
41
+ end
42
+
43
+ context "(at any time)" do
44
+ it "should define an appropriate equality operator ==" do
45
+ @initialiser_a = {:request_methods => [:options, :head, :post, :delete],
46
+ :path => "/some/path/or.other",
47
+ :required_queries => {"query_a" => "value_a", "query_b" => "value_b"},
48
+ :allowed_queries => ["query_c", "query_d"]}
49
+
50
+ @initialiser_b = {:request_methods => [:head, :post, :delete],
51
+ :path => "/some/path/or.other",
52
+ :required_queries => {"query_a" => "value_a", "query_b" => "value_b"},
53
+ :allowed_queries => ["query_c", "query_d"]}
54
+
55
+ @initialiser_c = {:request_methods => [:options, :head, :post, :delete],
56
+ :path => "/some/new/path/or.other",
57
+ :required_queries => {"query_a" => "value_a", "query_b" => "value_b"},
58
+ :allowed_queries => ["query_c", "query_d"]}
59
+
60
+ @initialiser_d = {:request_methods => [:options, :head, :post, :delete],
61
+ :path => "/some/path/or.other",
62
+ :required_queries => {"query_a" => "value_b", "query_b" => "value_b"},
63
+ :allowed_queries => ["query_c", "query_d"]}
64
+
65
+ @initialiser_e = {:request_methods => [:options, :head, :post, :delete],
66
+ :path => "/some/path/or.other",
67
+ :required_queries => {"query_a" => "value_a", "query_b" => "value_b"},
68
+ :allowed_queries => ["query_c"]}
69
+
70
+ filter_a = Rack::TicketOffice::UrlFilter.new(@initialiser_a)
71
+ filter_aa = Rack::TicketOffice::UrlFilter.new(@initialiser_a)
72
+ filter_b = Rack::TicketOffice::UrlFilter.new(@initialiser_b)
73
+ filter_c = Rack::TicketOffice::UrlFilter.new(@initialiser_c)
74
+ filter_d = Rack::TicketOffice::UrlFilter.new(@initialiser_d)
75
+ filter_e = Rack::TicketOffice::UrlFilter.new(@initialiser_e)
76
+
77
+ filter_a.should_not == "fred"
78
+ filter_a.should == filter_aa
79
+ filter_a.should_not == filter_b
80
+ filter_a.should_not == filter_c
81
+ filter_a.should_not == filter_d
82
+ filter_a.should_not == filter_e
83
+ end
84
+
85
+ it "should be serialisable" do
86
+ @initialiser_a = {:request_methods => [:options, :head, :post, :delete],
87
+ :path => "/some/path/or.other",
88
+ :required_queries => {"query_a" => "value_a", "query_b" => "value_b"},
89
+ :allowed_queries => ["query_c", "query_d"]}
90
+
91
+ @initialiser_b = {:request_methods => [:head, :post, :delete],
92
+ :path => "/some/path/or.other",
93
+ :required_queries => {"query_a" => "value_a", "query_b" => "value_b"},
94
+ :allowed_queries => ["query_c", "query_d"]}
95
+
96
+ filter_a = Rack::TicketOffice::UrlFilter.new(@initialiser_a)
97
+ filter_b = Rack::TicketOffice::UrlFilter.new(@initialiser_b)
98
+
99
+ filter_a.should == Marshal.load(Marshal.dump(filter_a))
100
+ filter_a.should_not == Marshal.load(Marshal.dump(filter_b))
101
+ end
102
+
103
+ it "should correctly serialise a regexp path" do
104
+ @initialiser = {:request_methods => [:options, :head, :post, :delete],
105
+ :path => %r{some/regexp},
106
+ :required_queries => {"query_a" => "value_a", "query_b" => "value_b"},
107
+ :allowed_queries => ["query_c", "query_d"]}
108
+
109
+ filter = Rack::TicketOffice::UrlFilter.new(@initialiser)
110
+ filter.should == Marshal.load(Marshal.dump(filter))
111
+ end
112
+ end
113
+
114
+ context "(when matching against a Rack env)" do
115
+ before(:each) do
116
+ @initialiser = {:request_methods => [:options, :head, :post, :delete],
117
+ :path => "/some/path/or.other",
118
+ :required_queries => {"query_a" => "value_a", "query_b" => "value_b"},
119
+ :allowed_queries => ["query_c", "query_d"]}
120
+ end
121
+
122
+ it "should match a valid request" do
123
+ env = Rack::MockRequest.env_for("/some/path/or.other?query_a=value_a&query_b=value_b&query_c=value_c", {:method => "POST"})
124
+ Rack::TicketOffice::UrlFilter.new(@initialiser).match(env).should be_true
125
+ end
126
+
127
+ it "should reject an unmatched path" do
128
+ env = Rack::MockRequest.env_for("/some/new/path/or.other?query_a=value_a&query_b=value_b&query_c=value_c", {:method => "POST"})
129
+ Rack::TicketOffice::UrlFilter.new(@initialiser).match(env).should be_false
130
+ end
131
+
132
+ it "should reject a partial match with a superset path" do
133
+ env = Rack::MockRequest.env_for("/addthisbit/some/path/or.other?query_a=value_a&query_b=value_b&query_c=value_c", {:method => "POST"})
134
+ Rack::TicketOffice::UrlFilter.new(@initialiser).match(env).should be_false
135
+ end
136
+
137
+ it "should reject a partial match with a subset path" do
138
+ env = Rack::MockRequest.env_for("/path/or.other?query_a=value_a&query_b=value_b&query_c=value_c", {:method => "POST"})
139
+ Rack::TicketOffice::UrlFilter.new(@initialiser).match(env).should be_false
140
+ end
141
+
142
+ it "should reject a bad method" do
143
+ env = Rack::MockRequest.env_for("/some/path/or.other?query_a=value_a&query_b=value_b&query_c=value_c", {:method => "GET"})
144
+ Rack::TicketOffice::UrlFilter.new(@initialiser).match(env).should be_false
145
+ end
146
+
147
+ it "should accept any method when using :all" do
148
+ @initialiser[:request_methods] = [:all]
149
+ env = Rack::MockRequest.env_for("/some/path/or.other?query_a=value_a&query_b=value_b&query_c=value_c", {:method => "GET"})
150
+ Rack::TicketOffice::UrlFilter.new(@initialiser).match(env).should be_true
151
+ end
152
+
153
+ it "should reject a missing required query" do
154
+ env = Rack::MockRequest.env_for("/some/path/or.other?query_a=value_a&query_c=value_c", {:method => "POST"})
155
+ Rack::TicketOffice::UrlFilter.new(@initialiser).match(env).should be_false
156
+ end
157
+
158
+ it "should reject an incorrect query value for a required query" do
159
+ env = Rack::MockRequest.env_for("/some/path/or.other?query_a=value_x&query_b=value_b&query_c=value_c", {:method => "POST"})
160
+ Rack::TicketOffice::UrlFilter.new(@initialiser).match(env).should be_false
161
+ end
162
+
163
+ it "should reject a query not on the allowed list" do
164
+ env = Rack::MockRequest.env_for("/some/path/or.other?query_a=value_a&query_b=value_b&query_c=value_c&query_x=value_x", {:method => "POST"})
165
+ Rack::TicketOffice::UrlFilter.new(@initialiser).match(env).should be_false
166
+ end
167
+ end
168
+
169
+ context "(when matching a regexp path against a Rack env)" do
170
+ before(:each) do
171
+ @initialiser = {:request_methods => [:options, :head, :post, :delete],
172
+ :path => %r{/some/path/or.other},
173
+ :required_queries => {"query_a" => "value_a", "query_b" => "value_b"},
174
+ :allowed_queries => ["query_c", "query_d"]}
175
+ end
176
+
177
+ it "should match a valid request" do
178
+ env = Rack::MockRequest.env_for("/some/path/or.other?query_a=value_a&query_b=value_b&query_c=value_c", {:method => "POST"})
179
+ Rack::TicketOffice::UrlFilter.new(@initialiser).match(env).should be_true
180
+ end
181
+
182
+ it "should reject an unmatched path" do
183
+ env = Rack::MockRequest.env_for("/some/new/path/or.other?query_a=value_a&query_b=value_b&query_c=value_c", {:method => "POST"})
184
+ Rack::TicketOffice::UrlFilter.new(@initialiser).match(env).should be_false
185
+ end
186
+
187
+ it "should reject a partial match with a superset path" do
188
+ env = Rack::MockRequest.env_for("/addthisbit/some/path/or.other?query_a=value_a&query_b=value_b&query_c=value_c", {:method => "POST"})
189
+ Rack::TicketOffice::UrlFilter.new(@initialiser).match(env).should be_false
190
+ end
191
+
192
+ it "should reject a partial match with a subset path" do
193
+ env = Rack::MockRequest.env_for("/path/or.other?query_a=value_a&query_b=value_b&query_c=value_c", {:method => "POST"})
194
+ Rack::TicketOffice::UrlFilter.new(@initialiser).match(env).should be_false
195
+ end
196
+ end
197
+ end
metadata ADDED
@@ -0,0 +1,107 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kissifer-rack-ticket-office
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - kissifer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-06-09 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: hash-persistent
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.3.2
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: sinatra-options-route
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: sinatra-response-header-helpers
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ description:
46
+ email: tierneydrchris@gmail.com
47
+ executables: []
48
+
49
+ extensions: []
50
+
51
+ extra_rdoc_files:
52
+ - LICENSE
53
+ - README.rdoc
54
+ files:
55
+ - .document
56
+ - .gitignore
57
+ - LICENSE
58
+ - README.rdoc
59
+ - Rakefile
60
+ - VERSION
61
+ - lib/rack/ticket-office/barrier.rb
62
+ - lib/rack/ticket-office/booth.rb
63
+ - lib/rack/ticket-office/monkey_patches.rb
64
+ - lib/rack/ticket-office/ticket.rb
65
+ - lib/rack/ticket-office/ticket_collection.rb
66
+ - lib/rack/ticket-office/url_filter.rb
67
+ - rack-ticket-office.gemspec
68
+ - spec/barrier_spec.rb
69
+ - spec/booth_spec.rb
70
+ - spec/spec.opts
71
+ - spec/spec_helper.rb
72
+ - spec/ticket_collection_spec.rb
73
+ - spec/ticket_spec.rb
74
+ - spec/url_filter_spec.rb
75
+ has_rdoc: false
76
+ homepage: http://github.com/kissifer/rack-ticket-office
77
+ post_install_message:
78
+ rdoc_options:
79
+ - --charset=UTF-8
80
+ require_paths:
81
+ - lib
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: "0"
87
+ version:
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: "0"
93
+ version:
94
+ requirements: []
95
+
96
+ rubyforge_project:
97
+ rubygems_version: 1.2.0
98
+ signing_key:
99
+ specification_version: 3
100
+ summary: Rack middleware to allow/deny access to remote user by http method, path and query params.
101
+ test_files:
102
+ - spec/barrier_spec.rb
103
+ - spec/booth_spec.rb
104
+ - spec/spec_helper.rb
105
+ - spec/ticket_collection_spec.rb
106
+ - spec/ticket_spec.rb
107
+ - spec/url_filter_spec.rb