diff --git a/lib/remote_storage/swift.rb b/lib/remote_storage/swift.rb index d2b1aa7..679d8b7 100644 --- a/lib/remote_storage/swift.rb +++ b/lib/remote_storage/swift.rb @@ -24,12 +24,14 @@ module RemoteStorage return true if ["GET", "HEAD"].include?(request_method) && !listing end + server.halt 401, "Unauthorized" if token.empty? + authorizations = redis.smembers("authorizations:#{user}:#{token}") permission = directory_permission(authorizations, directory) - server.halt 401 unless permission + server.halt 401, "Unauthorized" unless permission if ["PUT", "DELETE"].include? request_method - server.halt 401 unless permission == "rw" + server.halt 401, "Unauthorized" unless permission == "rw" end end @@ -55,7 +57,7 @@ module RemoteStorage return res.body rescue RestClient::ResourceNotFound - server.halt 404 + server.halt 404, "Not Found" end def get_head_directory_listing(user, directory) @@ -131,16 +133,16 @@ module RemoteStorage def put_data(user, directory, key, data, content_type) server.halt 400 if server.env["HTTP_CONTENT_RANGE"] - server.halt 409 if has_name_collision?(user, directory, key) + server.halt 409, "Conflict" if has_name_collision?(user, directory, key) existing_metadata = redis.hgetall redis_metadata_object_key(user, directory, key) url = url_for_key(user, directory, key) if required_match = server.env["HTTP_IF_MATCH"] - server.halt 412 unless required_match == %Q("#{existing_metadata["e"]}") + server.halt 412, "Precondition Failed" unless required_match == %Q("#{existing_metadata["e"]}") end if server.env["HTTP_IF_NONE_MATCH"] == "*" - server.halt 412 unless existing_metadata.empty? + server.halt 412, "Precondition Failed" unless existing_metadata.empty? end res = do_put_request(url, data, content_type) @@ -176,7 +178,7 @@ module RemoteStorage existing_metadata = redis.hgetall "rs:m:#{user}:#{directory}/#{key}" if required_match = server.env["HTTP_IF_MATCH"] - server.halt 412 unless required_match == %Q("#{existing_metadata["e"]}") + server.halt 412, "Precondition Failed" unless required_match == %Q("#{existing_metadata["e"]}") end do_delete_request(url) @@ -186,7 +188,7 @@ module RemoteStorage server.headers["Etag"] = %Q("#{existing_metadata["e"]}") server.halt 200 rescue RestClient::ResourceNotFound - server.halt 404 + server.halt 404, "Not Found" end private diff --git a/spec/swift/app_spec.rb b/spec/swift/app_spec.rb index 87dcc37..4df2ad0 100644 --- a/spec/swift/app_spec.rb +++ b/spec/swift/app_spec.rb @@ -134,6 +134,7 @@ describe "App" do end last_response.status.must_equal 409 + last_response.body.must_equal "Conflict" metadata = redis.hgetall "rs:m:phil:food" metadata.must_be_empty @@ -164,7 +165,82 @@ describe "App" do last_response.status.must_equal 400 end end + + describe "If-Match header" do + before do + put_stub = OpenStruct.new(headers: { + etag: "oldetag", + last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" + }) + + RestClient.stub :put, put_stub do + put "/phil/food/aguacate", "si" + end + end + + it "allows the request if the header matches the current ETag" do + header "If-Match", "\"oldetag\"" + + put_stub = OpenStruct.new(headers: { + etag: "newetag", + last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" + }) + + RestClient.stub :put, put_stub do + put "/phil/food/aguacate", "aye" + end + + last_response.status.must_equal 200 + last_response.headers["Etag"].must_equal "\"newetag\"" + end + + it "fails the request if the header does not match the current ETag" do + header "If-Match", "someotheretag" + + put "/phil/food/aguacate", "aye" + + last_response.status.must_equal 412 + last_response.body.must_equal "Precondition Failed" + end + end + + describe "If-None-Match header set to '*'" do + it "succeeds when the document doesn't exist yet" do + put_stub = OpenStruct.new(headers: { + etag: "someetag", + last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" + }) + + header "If-None-Match", "*" + + RestClient.stub :put, put_stub do + put "/phil/food/aguacate", "si" + end + + last_response.status.must_equal 200 + end + + it "fails the request if the document already exsits" do + put_stub = OpenStruct.new(headers: { + etag: "someetag", + last_modified: "Fri, 04 Mar 2016 12:20:18 GMT" + }) + + RestClient.stub :put, put_stub do + put "/phil/food/aguacate", "si" + end + + header "If-None-Match", "*" + RestClient.stub :put, put_stub do + put "/phil/food/aguacate", "si" + end + + last_response.status.must_equal 412 + last_response.body.must_equal "Precondition Failed" + end + end end + end describe "DELETE requests" do @@ -173,6 +249,29 @@ describe "App" do purge_redis end + context "not authorized" do + + describe "with no token" do + it "says it's not authorized" do + delete "/phil/food/aguacate" + + last_response.status.must_equal 401 + last_response.body.must_equal "Unauthorized" + end + end + + describe "with wrong token" do + it "says it's not authorized" do + header "Authorization", "Bearer wrongtoken" + delete "/phil/food/aguacate" + + last_response.status.must_equal 401 + last_response.body.must_equal "Unauthorized" + end + end + + end + context "authorized" do before do redis.sadd "authorizations:phil:amarillo", [":rw"] @@ -240,13 +339,44 @@ describe "App" do redis.smembers("rs:m:phil:/:items").must_be_empty end - it "responds with the ETag of the deleted item in the haeder" do + it "responds with the ETag of the deleted item in the header" do RestClient.stub :delete, "" do delete "/phil/food/aguacate" end last_response.headers["ETag"].must_equal "\"bla\"" end + + it "returns a 404 when item doesn't exist" do + raises_exception = ->(url, headers) { raise RestClient::ResourceNotFound.new } + RestClient.stub :delete, raises_exception do + delete "/phil/food/steak" + end + + last_response.status.must_equal 404 + last_response.body.must_equal "Not Found" + end + + describe "If-Match header" do + it "succeeds when the header matches the current ETag" do + header "If-Match", "\"bla\"" + + RestClient.stub :delete, "" do + delete "/phil/food/aguacate" + end + + last_response.status.must_equal 200 + end + + it "fails the request if it does not match the current ETag" do + header "If-Match", "someotheretag" + + delete "/phil/food/aguacate" + + last_response.status.must_equal 412 + last_response.body.must_equal "Precondition Failed" + end + end end end @@ -256,6 +386,29 @@ describe "App" do purge_redis end + context "not authorized" do + + describe "without token" do + it "says it's not authorized" do + get "/phil/food/" + + last_response.status.must_equal 401 + last_response.body.must_equal "Unauthorized" + end + end + + describe "with wrong token" do + it "says it's not authorized" do + header "Authorization", "Bearer wrongtoken" + get "/phil/food/" + + last_response.status.must_equal 401 + last_response.body.must_equal "Unauthorized" + end + end + + end + context "authorized" do before do @@ -294,6 +447,16 @@ describe "App" do last_response.headers["Content-Type"].must_equal "text/plain; charset=utf-8" end + it "returns a 404 when data doesn't exist" do + raises_exception = ->(url, headers) { raise RestClient::ResourceNotFound.new } + RestClient.stub :get, raises_exception do + get "/phil/food/steak" + end + + last_response.status.must_equal 404 + last_response.body.must_equal "Not Found" + end + end describe "directory listings" do @@ -371,6 +534,29 @@ describe "App" do purge_redis end + context "not authorized" do + + describe "without token" do + it "says it's not authorized" do + head "/phil/food/camarones" + + last_response.status.must_equal 401 + last_response.body.must_be_empty + end + end + + describe "with wrong token" do + it "says it's not authorized" do + header "Authorization", "Bearer wrongtoken" + head "/phil/food/camarones" + + last_response.status.must_equal 401 + last_response.body.must_be_empty + end + end + + end + context "authorized" do before do @@ -399,6 +585,18 @@ describe "App" do end end + describe "data" do + it "returns a 404 when data doesn't exist" do + raises_exception = ->(url, headers) { raise RestClient::ResourceNotFound.new } + RestClient.stub :head, raises_exception do + head "/phil/food/steak" + end + + last_response.status.must_equal 404 + last_response.body.must_be_empty + end + end + end end