doze 0.0.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/README +6 -0
  2. data/lib/doze/application.rb +92 -0
  3. data/lib/doze/collection/object.rb +14 -0
  4. data/lib/doze/entity.rb +62 -0
  5. data/lib/doze/error.rb +75 -0
  6. data/lib/doze/media_type.rb +135 -0
  7. data/lib/doze/negotiator.rb +107 -0
  8. data/lib/doze/request.rb +119 -0
  9. data/lib/doze/resource/error.rb +21 -0
  10. data/lib/doze/resource/proxy.rb +81 -0
  11. data/lib/doze/resource.rb +193 -0
  12. data/lib/doze/responder/error.rb +34 -0
  13. data/lib/doze/responder/main.rb +41 -0
  14. data/lib/doze/responder/resource.rb +262 -0
  15. data/lib/doze/responder.rb +58 -0
  16. data/lib/doze/response.rb +78 -0
  17. data/lib/doze/router/anchored_route_set.rb +68 -0
  18. data/lib/doze/router/route.rb +88 -0
  19. data/lib/doze/router/route_set.rb +34 -0
  20. data/lib/doze/router.rb +100 -0
  21. data/lib/doze/serialization/entity.rb +34 -0
  22. data/lib/doze/serialization/form_data_helpers.rb +40 -0
  23. data/lib/doze/serialization/html.rb +116 -0
  24. data/lib/doze/serialization/json.rb +29 -0
  25. data/lib/doze/serialization/multipart_form_data.rb +162 -0
  26. data/lib/doze/serialization/resource.rb +30 -0
  27. data/lib/doze/serialization/resource_proxy.rb +14 -0
  28. data/lib/doze/serialization/www_form_encoded.rb +42 -0
  29. data/lib/doze/serialization/yaml.rb +25 -0
  30. data/lib/doze/uri_template.rb +220 -0
  31. data/lib/doze/utils.rb +53 -0
  32. data/lib/doze/version.rb +3 -0
  33. data/lib/doze.rb +5 -0
  34. data/test/functional/auth_test.rb +69 -0
  35. data/test/functional/base.rb +159 -0
  36. data/test/functional/cache_header_test.rb +76 -0
  37. data/test/functional/direct_response_test.rb +16 -0
  38. data/test/functional/error_handling_test.rb +131 -0
  39. data/test/functional/get_and_conneg_test.rb +182 -0
  40. data/test/functional/media_type_extensions_test.rb +102 -0
  41. data/test/functional/media_type_test.rb +40 -0
  42. data/test/functional/method_support_test.rb +49 -0
  43. data/test/functional/non_get_method_test.rb +173 -0
  44. data/test/functional/precondition_test.rb +84 -0
  45. data/test/functional/raw_path_info_test.rb +69 -0
  46. data/test/functional/resource_representation_test.rb +14 -0
  47. data/test/functional/router_test.rb +196 -0
  48. data/test/functional/serialization_test.rb +142 -0
  49. data/test/functional/uri_template_test.rb +51 -0
  50. metadata +221 -0
@@ -0,0 +1,162 @@
1
+ require 'doze/media_type'
2
+ require 'doze/serialization/entity'
3
+ require 'doze/serialization/form_data_helpers'
4
+ require 'doze/error'
5
+ require 'doze/utils'
6
+ require 'tempfile'
7
+
8
+ module Doze::Serialization
9
+ # Also ripped off largely from Merb::Parse.
10
+ #
11
+ # Small differences in the hash it returns for an uploaded file - it will have string keys,
12
+ # use media_type rather than content_type (for consistency with rest of doze) and adds a temp_path
13
+ # key.
14
+ #
15
+ # These enable it to be used interchangably with nginx upload module if you use config like eg:
16
+ #
17
+ # upload_set_form_field $upload_field_name[filename] "$upload_file_name";
18
+ # upload_set_form_field $upload_field_name[media_type] "$upload_content_type";
19
+ # upload_set_form_field $upload_field_name[temp_path] "$upload_tmp_path";
20
+ # upload_aggregate_form_field $upload_field_name[size] "$upload_file_size";
21
+ #
22
+ class Entity::MultipartFormData < Entity
23
+ include FormDataHelpers
24
+
25
+ NAME_REGEX = /Content-Disposition:.* name="?([^\";]*)"?/ni.freeze
26
+ CONTENT_TYPE_REGEX = /Content-Type: (.*)\r\n/ni.freeze
27
+ FILENAME_REGEX = /Content-Disposition:.* filename="?([^\";]*)"?/ni.freeze
28
+ CRLF = "\r\n".freeze
29
+ EOL = CRLF
30
+
31
+ def object_data(try_deserialize=true)
32
+ @object_data ||= if @lazy_object_data
33
+ @lazy_object_data.call
34
+ elsif try_deserialize
35
+ @binary_data_stream && deserialize_stream
36
+ end
37
+ end
38
+
39
+ def deserialize_stream
40
+ boundary = @media_type_params && @media_type_params['boundary'] or raise "missing boundary parameter for multipart/form-data"
41
+ boundary = "--#{boundary}"
42
+ paramhsh = {}
43
+ buf = ""
44
+ input = @binary_data_stream
45
+ input.binmode if defined? input.binmode
46
+ boundary_size = boundary.size + EOL.size
47
+ bufsize = 16384
48
+ length = @binary_data_length or raise "expected Content-Length for multipart/form-data"
49
+ length -= boundary_size
50
+ # status is boundary delimiter line
51
+ status = input.read(boundary_size)
52
+ return {} if status == nil || status.empty?
53
+ raise "bad content body:\n'#{status}' should == '#{boundary + EOL}'" unless status == boundary + EOL
54
+ # second argument to Regexp.quote is for KCODE
55
+ rx = /(?:#{EOL})?#{Regexp.quote(boundary,'n')}(#{EOL}|--)/
56
+ loop {
57
+ head = nil
58
+ body = ''
59
+ filename = content_type = name = nil
60
+ read_size = 0
61
+ until head && buf =~ rx
62
+ i = buf.index("\r\n\r\n")
63
+ if( i == nil && read_size == 0 && length == 0 )
64
+ length = -1
65
+ break
66
+ end
67
+ if !head && i
68
+ head = buf.slice!(0, i+2) # First \r\n
69
+ buf.slice!(0, 2) # Second \r\n
70
+
71
+ # String#[] with 2nd arg here is returning
72
+ # a group from match data
73
+ filename = head[FILENAME_REGEX, 1]
74
+ content_type = head[CONTENT_TYPE_REGEX, 1]
75
+ name = head[NAME_REGEX, 1]
76
+
77
+ if filename && !filename.empty?
78
+ body = Tempfile.new(:Doze)
79
+ body.binmode if defined? body.binmode
80
+ end
81
+ next
82
+ end
83
+
84
+ # Save the read body part.
85
+ if head && (boundary_size+4 < buf.size)
86
+ body << buf.slice!(0, buf.size - (boundary_size+4))
87
+ end
88
+
89
+ read_size = bufsize < length ? bufsize : length
90
+ if( read_size > 0 )
91
+ c = input.read(read_size)
92
+ raise "bad content body" if c.nil? || c.empty?
93
+ buf << c
94
+ length -= c.size
95
+ end
96
+ end
97
+
98
+ # Save the rest.
99
+ if i = buf.index(rx)
100
+ # correct value of i for some edge cases
101
+ if (i > 2) && (j = buf.index(rx, i-2)) && (j < i)
102
+ i = j
103
+ end
104
+ body << buf.slice!(0, i)
105
+ buf.slice!(0, boundary_size+2)
106
+
107
+ length = -1 if $1 == "--"
108
+ end
109
+
110
+ if filename && !filename.empty?
111
+ body.rewind
112
+ data = {
113
+ "filename" => File.basename(filename),
114
+ "media_type" => content_type,
115
+ "tempfile" => body,
116
+ "temp_path" => body.path,
117
+ "size" => File.size(body.path)
118
+ }
119
+ else
120
+ data = body
121
+ end
122
+ paramhsh = normalize_params(paramhsh,name,data)
123
+ break if buf.empty? || length == -1
124
+ }
125
+ paramhsh
126
+ end
127
+
128
+ # This is designed to work with either actual file upload fields, or the corresponding
129
+ # fields generated by nginx upload module as described above.
130
+ #
131
+ # yields or returns a Doze::Entity for the uploaded file, with the correct media_type and binary_data_length.
132
+ # ensures to close and unlinks the underlying tempfile afterwards where used with a block.
133
+ def param_entity(name)
134
+ meta = object_data[name]; return unless meta.is_a?(Hash)
135
+ media_type = meta["media_type"] and media_type = Doze::MediaType[media_type] or return
136
+ size = meta["size"] && meta["size"].to_i
137
+ if (tempfile = meta["tempfile"])
138
+ temp_path = tempfile.path
139
+ elsif (temp_path = meta["temp_path"])
140
+ tempfile = File.open(meta["temp_path"], "rb")
141
+ end
142
+ return unless tempfile
143
+ entity = media_type.new_entity(:binary_data_stream => tempfile, :binary_data_length => size)
144
+
145
+ return entity unless block_given?
146
+ begin
147
+ yield entity
148
+ ensure
149
+ tempfile.close
150
+ begin
151
+ File.unlink(temp_path)
152
+ rescue StandardError
153
+ # we made an effort to clean up - but it is a tempfile, so no biggie
154
+ end
155
+ end
156
+ end
157
+ end
158
+
159
+ # A browser-friendly media type for use with Doze::Serialization::Resource.
160
+ MULTIPART_FORM_DATA = Doze::MediaType.register('multipart/form-data', :entity_class => Entity::MultipartFormData)
161
+ end
162
+
@@ -0,0 +1,30 @@
1
+ require 'doze/serialization/json'
2
+ require 'doze/serialization/www_form_encoded'
3
+ require 'doze/serialization/multipart_form_data'
4
+ require 'doze/serialization/html'
5
+
6
+ # A resource whose representations are all serializations of some ruby data.
7
+ # A good example of how to do media type negotiation
8
+ module Doze::Serialization
9
+ module Resource
10
+ # You probably want to override these
11
+ def serialization_media_types
12
+ [JSON, HTML]
13
+ end
14
+
15
+ # Analogous to get, but returns data which may be serialized into entities of any one of serialization_media_types
16
+ def get_data
17
+ end
18
+
19
+ def get
20
+ serialization_media_types.map do |media_type|
21
+ media_type.entity_class.new(media_type, :lazy_object_data => proc {get_data})
22
+ end
23
+ end
24
+
25
+ # You may want to be more particular than this if you can only deal with certain serialization types
26
+ def accepts_method_with_media_type?(method, entity)
27
+ entity.is_a?(Doze::Serialization::Entity)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,14 @@
1
+ require 'doze/serialization/resource'
2
+ require 'doze/resource/proxy'
3
+
4
+ class Doze::Serialization::ResourceProxy < Doze::Resource::Proxy
5
+ include Doze::Serialization::Resource
6
+
7
+ def serialization_media_types
8
+ target && target.serialization_media_types
9
+ end
10
+
11
+ def get_data
12
+ target && target.get_data
13
+ end
14
+ end
@@ -0,0 +1,42 @@
1
+ require 'doze/media_type'
2
+ require 'doze/serialization/entity'
3
+ require 'doze/serialization/form_data_helpers'
4
+ require 'doze/error'
5
+ require 'doze/utils'
6
+
7
+ module Doze::Serialization
8
+ # ripped off largely from Merb::Parse
9
+ # Supports PHP-style nested hashes via foo[bar][baz]=boz
10
+ class Entity::WWWFormEncoded < Entity
11
+ include FormDataHelpers
12
+
13
+ def serialize(value, prefix=nil)
14
+ case value
15
+ when Array
16
+ value.map {|v| serialize(v, "#{prefix}[]")}.join("&")
17
+ when Hash
18
+ value.map {|k,v| serialize(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))}.join("&")
19
+ else
20
+ "#{prefix}=#{escape(value)}"
21
+ end
22
+ end
23
+
24
+ def deserialize(data)
25
+ query = {}
26
+ for pair in data.split(/[&;] */n)
27
+ key, value = unescape(pair).split('=',2)
28
+ next if key.nil?
29
+ if key.include?('[')
30
+ normalize_params(query, key, value)
31
+ else
32
+ query[key] = value
33
+ end
34
+ end
35
+ query
36
+ end
37
+ end
38
+
39
+ # A browser-friendly media type for use with Doze::Serialization::Resource.
40
+ WWW_FORM_ENCODED = Doze::MediaType.register('application/x-www-form-urlencoded', :entity_class => Entity::WWWFormEncoded)
41
+ end
42
+
@@ -0,0 +1,25 @@
1
+ require 'yaml'
2
+ require 'doze/media_type'
3
+ require 'doze/serialization/entity'
4
+ require 'doze/error'
5
+
6
+ # Note that it isn't safe to accept YAML input, unless you trust the sender, as
7
+ # it is possible to craft a YAML message to allow remote code execution (see
8
+ # cve-2013-0156)
9
+ module Doze::Serialization
10
+ class Entity::YAML < Entity
11
+ def serialize(ruby_data)
12
+ ruby_data.to_yaml
13
+ end
14
+
15
+ def deserialize(binary_data)
16
+ begin
17
+ ::YAML.load(binary_data)
18
+ rescue ::YAML::ParseError, ArgumentError
19
+ raise Doze::ClientEntityError, "Could not parse YAML"
20
+ end
21
+ end
22
+ end
23
+
24
+ YAML = Doze::MediaType.register('application/yaml', :plus_suffix => 'yaml', :entity_class => Entity::YAML, :extension => 'yaml')
25
+ end
@@ -0,0 +1,220 @@
1
+ # Implements a subset of URI template spec.
2
+ # This is somewhat optimised for fast matching and generation of URI strings, although probably
3
+ # a fair bit of mileage still to be gotten out of it.
4
+ class Doze::URITemplate
5
+ def self.compile(string, var_regexps={})
6
+ is_varexp = true
7
+ parts = string.split(/\{(.*?)\}/).map do |bit|
8
+ if (is_varexp = !is_varexp)
9
+ case bit
10
+ when /^\/(.*).quadhexbytes\*$/
11
+ QuadHexBytesVariable.new($1.to_sym)
12
+ else
13
+ var = bit.to_sym
14
+ Variable.new(var, var_regexps[var] || Variable::DEFAULT_REGEXP)
15
+ end
16
+ else
17
+ String.new(bit)
18
+ end
19
+ end
20
+ template = parts.length > 1 ? Composite.new(parts) : parts.first
21
+ template.compile_expand!
22
+ template
23
+ end
24
+
25
+ # Compile a ruby string substitution expression for the 'expand' method to make filling out these templates blazing fast.
26
+ # This was actually a bottleneck in some simple cache lookups by list of URIs
27
+ def compile_expand!
28
+ instance_eval "def expand(vars); \"#{expand_code_fragment}\"; end", __FILE__, __LINE__
29
+ end
30
+
31
+ def anchored_regexp
32
+ @anchored_regexp ||= Regexp.new("^#{regexp_fragment}$")
33
+ end
34
+
35
+ def start_anchored_regexp
36
+ @start_anchored_regexp ||= Regexp.new("^#{regexp_fragment}")
37
+ end
38
+
39
+ def parts; @parts ||= [self]; end
40
+
41
+ def +(other)
42
+ other = String.new(other.to_s) unless other.is_a?(Doze::URITemplate)
43
+ Composite.new(parts + other.parts)
44
+ end
45
+
46
+ def inspect
47
+ "#<#{self.class} #{to_s}>"
48
+ end
49
+
50
+ def match(uri)
51
+ match = anchored_regexp.match(uri) or return
52
+ result = {}; captures = match.captures
53
+ variables.each_with_index do |var, index|
54
+ result[var.name] = var.translate_captured_string(captures[index])
55
+ end
56
+ result
57
+ end
58
+
59
+ def match_with_trailing(uri)
60
+ match = start_anchored_regexp.match(uri) or return
61
+ result = {}; captures = match.captures
62
+ variables.each_with_index do |var, index|
63
+ result[var.name] = var.translate_captured_string(captures[index])
64
+ end
65
+ trailing = match.post_match
66
+ trailing = nil if trailing.empty?
67
+ [result, match.to_s, trailing]
68
+ end
69
+
70
+ class Variable < Doze::URITemplate
71
+ DEFAULT_REGEXP = "[^\/.,;?]+"
72
+
73
+ attr_reader :name
74
+
75
+ def initialize(name, regexp=DEFAULT_REGEXP)
76
+ @name = name; @regexp = regexp
77
+ end
78
+
79
+ def regexp_fragment
80
+ "(#{@regexp})"
81
+ end
82
+
83
+ def to_s
84
+ "{#{@name}}"
85
+ end
86
+
87
+ def variables; [self]; end
88
+
89
+ def expand(vars)
90
+ Doze::Utils.escape(vars[@name].to_s)
91
+ end
92
+
93
+ def partially_expand(vars)
94
+ if vars.has_key?(@name)
95
+ String.new(expand(vars))
96
+ else
97
+ self
98
+ end
99
+ end
100
+
101
+ def translate_captured_string(string)
102
+ # inlines Doze::Utils.unescape, but with gsub! rather than gsub since this is faster and the matched string is throwaway
103
+ string.gsub!(/((?:%[0-9a-fA-F]{2})+)/n) {[$1.delete('%')].pack('H*')}; string
104
+ end
105
+
106
+ # String#size under Ruby 1.8 and String#bytesize under 1.9.
107
+ BYTESIZE_METHOD = ''.respond_to?(:bytesize) ? 'bytesize' : 'size'
108
+
109
+ # inlines Doze::Utils.escape (optimised from Rack::Utils.escape) with further effort to avoid an extra method call for bytesize 1.9 compat.
110
+ def expand_code_fragment
111
+ "\#{vars[#{@name.inspect}].to_s.gsub(/([^a-zA-Z0-9_.-]+)/n) {'%'+$1.unpack('H2'*$1.#{BYTESIZE_METHOD}).join('%').upcase}}"
112
+ end
113
+ end
114
+
115
+ class QuadHexBytesVariable < Variable
116
+ REGEXP = "(?:/[0-9a-f]{2}){4}"
117
+
118
+ def initialize(name)
119
+ super(name, REGEXP)
120
+ end
121
+
122
+ def to_s
123
+ "{/#{@name}.quadhexbytes*}"
124
+ end
125
+
126
+ def expand(vars)
127
+ hex = vars[@name].to_i.to_s(16).rjust(8,'0')
128
+ "/#{hex[0..1]}/#{hex[2..3]}/#{hex[4..5]}/#{hex[6..7]}"
129
+ end
130
+
131
+ def expand_code_fragment
132
+ "/\#{hex=vars[#{@name.inspect}].to_i.to_s(16).rjust(8,'0');hex[0..1]}/\#{hex[2..3]}/\#{hex[4..5]}/\#{hex[6..7]}"
133
+ end
134
+
135
+ def translate_captured_string(string)
136
+ string.tr('/','').to_i(16)
137
+ end
138
+ end
139
+
140
+ class String < Doze::URITemplate
141
+ attr_reader :string
142
+
143
+ def initialize(string)
144
+ @string = string
145
+ end
146
+
147
+ def regexp_fragment
148
+ Regexp.escape(@string)
149
+ end
150
+
151
+ def to_s
152
+ @string
153
+ end
154
+
155
+ def expand(vars)
156
+ @string
157
+ end
158
+
159
+ def partially_expand(vars); self; end
160
+
161
+ NO_VARS = [].freeze
162
+ def variables; NO_VARS; end
163
+
164
+ def expand_code_fragment
165
+ @string.inspect[1...-1]
166
+ end
167
+ end
168
+
169
+ class Composite < Doze::URITemplate
170
+ def initialize(parts)
171
+ @parts = parts
172
+ end
173
+
174
+ def regexp_fragment
175
+ @parts.map {|p| p.regexp_fragment}.join
176
+ end
177
+
178
+ def to_s
179
+ @parts.join
180
+ end
181
+
182
+ def expand(vars)
183
+ @parts.map {|p| p.expand(vars)}.join
184
+ end
185
+
186
+ def partially_expand(vars)
187
+ Composite.new(@parts.map {|p| p.partially_expand(vars)})
188
+ end
189
+
190
+ def variables
191
+ @variables ||= @parts.map {|p| p.variables}.flatten
192
+ end
193
+
194
+ def expand_code_fragment
195
+ @parts.map {|p| p.expand_code_fragment}.join
196
+ end
197
+
198
+ attr_reader :parts
199
+ end
200
+
201
+ # A simple case of Composite where a template is prefixed by a string.
202
+ # This allows the same compiled URI template to be used with many different prefixes
203
+ # without having to re-compile the expand method for each of them, or use the slower
204
+ # default implementation
205
+ class WithPrefix < Composite
206
+ def initialize(template, prefix)
207
+ @template = template
208
+ @prefix = prefix
209
+ @parts = [String.new(prefix.to_s), *@template.parts]
210
+ end
211
+
212
+ def expand(vars)
213
+ "#{@prefix}#{@template.expand(vars)}"
214
+ end
215
+ end
216
+
217
+ def with_prefix(prefix)
218
+ WithPrefix.new(self, prefix)
219
+ end
220
+ end
data/lib/doze/utils.rb ADDED
@@ -0,0 +1,53 @@
1
+ # Various stateless utility functions which aid the conversion back and forth between HTTP syntax and the more abstracted ruby representations we use.
2
+ module Doze::Utils
3
+ # Strictly this is a WebDAV extension but very useful in the wider HTTP context
4
+ # see http://tools.ietf.org/html/rfc4918#section-11.2
5
+ Rack::Utils::HTTP_STATUS_CODES[422] = 'Unprocessable entity'
6
+
7
+ Rack::Utils::HTTP_STATUS_CODES.each do |code,text|
8
+ const_set('STATUS_' << text.upcase.gsub(/[^A-Z]+/, '_'), code)
9
+ end
10
+
11
+ URI_SCHEMES = Hash.new(URI::Generic).merge!(
12
+ 'http' => URI::HTTP,
13
+ 'https' => URI::HTTPS
14
+ )
15
+
16
+ def request_base_uri(request)
17
+ URI_SCHEMES[request.scheme].build(
18
+ :scheme => request.scheme,
19
+ :port => request.port,
20
+ :host => request.host
21
+ )
22
+ end
23
+
24
+ def quote(str)
25
+ '"' << str.gsub(/[\\\"]/o, "\\\1") << '"'
26
+ end
27
+
28
+ # Note: unescape and escape proved bottlenecks in URI template matching and URI template generation which in turn
29
+ # were bottlenecks for serving some simple requests and for generating URIs to use for cache lookups.
30
+ # So perhaps a bit micro-optimised here, but there was a reason :)
31
+
32
+ # Rack::Utils.unescape, but without turning '+' into ' '
33
+ # Also must be passed a string.
34
+ def unescape(s)
35
+ s.gsub(/((?:%[0-9a-fA-F]{2})+)/n) {[$1.delete('%')].pack('H*')}
36
+ end
37
+
38
+ # Rack::Utils.escape, but turning ' ' into '%20' rather than '+' (which is not a necessary part of the URI spec) to save an extra call to tr.
39
+ # Also must be passed a string.
40
+ # Also avoids an extra call to 1.8/1.9 compatibility wrapper for bytesize/size.
41
+ if ''.respond_to?(:bytesize)
42
+ def escape(s)
43
+ s.gsub(/([^a-zA-Z0-9_.-]+)/n) {'%'+$1.unpack('H2'*$1.bytesize).join('%').upcase}
44
+ end
45
+ else
46
+ def escape(s)
47
+ s.gsub(/([^a-zA-Z0-9_.-]+)/n) {'%'+$1.unpack('H2'*$1.size).join('%').upcase}
48
+ end
49
+ end
50
+
51
+ # So utility functions are accessible as Doze::Utils.foo as well as via including the module
52
+ extend self
53
+ end
@@ -0,0 +1,3 @@
1
+ module Doze
2
+ VERSION = '0.0.11'
3
+ end
data/lib/doze.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'rubygems'
2
+ require 'rack'
3
+ require 'uri'
4
+ module Doze; end
5
+ require 'doze/application'
@@ -0,0 +1,69 @@
1
+ require 'functional/base'
2
+
3
+ class AuthTest < Test::Unit::TestCase
4
+ include Doze::Utils
5
+ include Doze::TestCase
6
+
7
+ def test_deny_unauthenticated_user
8
+ root.expects(:authorize).with(nil, :get).returns(false).once
9
+ assert_equal STATUS_UNAUTHORIZED, get.status
10
+ end
11
+
12
+ def test_deny_authenticated_user
13
+ root.expects(:authorize).with('username', :get).returns(false).once
14
+ get('REMOTE_USER' => 'username')
15
+ assert_equal STATUS_FORBIDDEN, last_response.status
16
+ end
17
+
18
+ def test_allow_authenticated_user
19
+ root.expects(:authorize).with('username', :get).returns(true).once
20
+ get('REMOTE_USER' => 'username')
21
+ assert_equal STATUS_OK, last_response.status
22
+ end
23
+
24
+ def test_post_auth
25
+ root.expects(:supports_post?).returns(true)
26
+ root.expects(:authorize).with('username', :post).returns(false).once
27
+ root.expects(:post).never
28
+ post('REMOTE_USER' => 'username')
29
+ assert_equal STATUS_FORBIDDEN, last_response.status
30
+ end
31
+
32
+ def test_put_auth
33
+ root.expects(:supports_put?).returns(true)
34
+ root.expects(:authorize).with('username', :put).returns(false).once
35
+ root.expects(:put).never
36
+ put('REMOTE_USER' => 'username')
37
+ assert_equal STATUS_FORBIDDEN, last_response.status
38
+ end
39
+
40
+ def test_delete_auth
41
+ root.expects(:supports_delete?).returns(true)
42
+ root.expects(:authorize).with('username', :delete).returns(false).once
43
+ root.expects(:delete).never
44
+ delete('REMOTE_USER' => 'username')
45
+ assert_equal STATUS_FORBIDDEN, last_response.status
46
+ end
47
+
48
+ def test_other_method_auth
49
+ app(:recognized_methods => [:get, :patch])
50
+ root.expects(:supports_patch?).returns(true)
51
+ root.expects(:authorize).with('username', :patch).returns(false).once
52
+ root.expects(:patch).never
53
+ other_request_method('PATCH', {'REMOTE_USER' => 'username'})
54
+ assert_equal STATUS_FORBIDDEN, last_response.status
55
+ end
56
+
57
+ def test_can_raise_unauthd_errors
58
+ root.expects(:get).raises(Doze::UnauthorizedError.new('no homers allowed'))
59
+ assert_equal STATUS_UNAUTHORIZED, get.status
60
+ assert_match /Unauthorized\: no homers allowed/, last_response.body
61
+ end
62
+
63
+ def test_can_raise_forbidden_errors
64
+ root.expects(:get).raises(Doze::ForbiddenError.new('do not go there, girlfriend'))
65
+ assert_equal STATUS_FORBIDDEN, get.status
66
+ assert_match /Forbidden\: do not go there, girlfriend/, last_response.body
67
+ end
68
+
69
+ end