blob: 4b2e2b5a31d51963ae89c99fe16c0be7b9892436 [file] [log] [blame]
% Licensed under the Apache License, Version 2.0 (the "License"); you may not
% use this file except in compliance with the License. You may obtain a copy of
% the License at
%
% http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
% License for the specific language governing permissions and limitations under
% the License.
-module(couchdb_cors_tests).
-include_lib("couch/include/couch_eunit.hrl").
-include_lib("couch/include/couch_db.hrl").
-define(SUPPORTED_METHODS,
"GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT, COPY, OPTIONS").
-define(TIMEOUT, 1000).
start() ->
Ctx = test_util:start_couch([ioq]),
ok = config:set("httpd", "enable_cors", "true", false),
ok = config:set("vhosts", "example.com", "/", false),
Ctx.
setup() ->
DbName = ?tempdb(),
{ok, Db} = couch_db:create(DbName, [?ADMIN_CTX]),
couch_db:close(Db),
config:set("cors", "credentials", "false", false),
config:set("cors", "origins", "http://example.com", false),
Addr = config:get("httpd", "bind_address", "127.0.0.1"),
Port = integer_to_list(mochiweb_socket_server:get(couch_httpd, port)),
Host = "http://" ++ Addr ++ ":" ++ Port,
{Host, ?b2l(DbName)}.
setup({Mod, VHost}) ->
{Host, DbName} = setup(),
Url = case Mod of
server ->
Host;
db ->
Host ++ "/" ++ DbName
end,
DefaultHeaders = [{"Origin", "http://example.com"}]
++ maybe_append_vhost(VHost),
{Host, DbName, Url, DefaultHeaders}.
teardown(DbName) when is_list(DbName) ->
ok = couch_server:delete(?l2b(DbName), [?ADMIN_CTX]),
ok;
teardown({_, DbName}) ->
teardown(DbName).
teardown(_, {_, DbName, _, _}) ->
teardown(DbName).
cors_test_() ->
Funs = [
fun should_not_allow_origin/2,
fun should_not_allow_origin_with_port_mismatch/2,
fun should_not_allow_origin_with_scheme_mismatch/2,
fun should_not_all_origin_due_case_mismatch/2,
fun should_make_simple_request/2,
fun should_make_preflight_request/2,
fun should_make_prefligh_request_with_port/2,
fun should_make_prefligh_request_with_scheme/2,
fun should_make_prefligh_request_with_wildcard_origin/2,
fun should_make_request_with_credentials/2,
fun should_make_origin_request_with_auth/2,
fun should_make_preflight_request_with_auth/2
],
{
"CORS (COUCHDB-431)",
{
setup,
fun start/0, fun test_util:stop_couch/1,
[
cors_tests(Funs),
vhost_cors_tests(Funs),
headers_tests()
]
}
}.
headers_tests() ->
{
"Various headers tests",
{
foreach,
fun setup/0, fun teardown/1,
[
fun should_not_return_cors_headers_for_invalid_origin/1,
fun should_not_return_cors_headers_for_invalid_origin_preflight/1,
fun should_make_request_against_attachment/1,
fun should_make_range_request_against_attachment/1,
fun should_make_request_with_if_none_match_header/1
]
}
}.
cors_tests(Funs) ->
{
"CORS tests",
[
make_test_case(server, false, Funs),
make_test_case(db, false, Funs)
]
}.
vhost_cors_tests(Funs) ->
{
"Virtual Host CORS",
[
make_test_case(server, true, Funs),
make_test_case(db, true, Funs)
]
}.
make_test_case(Mod, UseVhost, Funs) ->
{
case Mod of server -> "Server"; db -> "Database" end,
{foreachx, fun setup/1, fun teardown/2, [{{Mod, UseVhost}, Fun}
|| Fun <- Funs]}
}.
should_not_allow_origin(_, {_, _, Url, Headers0}) ->
?_assertEqual(undefined,
begin
config:delete("cors", "origins", false),
Headers1 = proplists:delete("Origin", Headers0),
Headers = [{"Origin", "http://127.0.0.1"}]
++ Headers1,
{ok, _, Resp, _} = test_request:get(Url, Headers),
proplists:get_value("Access-Control-Allow-Origin", Resp)
end).
should_not_allow_origin_with_port_mismatch({_, VHost}, {_, _, Url, _}) ->
?_assertEqual(undefined,
begin
Headers = [{"Origin", "http://example.com:5984"},
{"Access-Control-Request-Method", "GET"}]
++ maybe_append_vhost(VHost),
{ok, _, Resp, _} = test_request:options(Url, Headers),
proplists:get_value("Access-Control-Allow-Origin", Resp)
end).
should_not_allow_origin_with_scheme_mismatch({_, VHost}, {_, _, Url, _}) ->
?_assertEqual(undefined,
begin
Headers = [{"Origin", "http://example.com:5984"},
{"Access-Control-Request-Method", "GET"}]
++ maybe_append_vhost(VHost),
{ok, _, Resp, _} = test_request:options(Url, Headers),
proplists:get_value("Access-Control-Allow-Origin", Resp)
end).
should_not_all_origin_due_case_mismatch({_, VHost}, {_, _, Url, _}) ->
?_assertEqual(undefined,
begin
Headers = [{"Origin", "http://ExAmPlE.CoM"},
{"Access-Control-Request-Method", "GET"}]
++ maybe_append_vhost(VHost),
{ok, _, Resp, _} = test_request:options(Url, Headers),
proplists:get_value("Access-Control-Allow-Origin", Resp)
end).
should_make_simple_request(_, {_, _, Url, DefaultHeaders}) ->
?_test(begin
{ok, _, Resp, _} = test_request:get(Url, DefaultHeaders),
?assertEqual(
undefined,
proplists:get_value("Access-Control-Allow-Credentials", Resp)),
?assertEqual(
"http://example.com",
proplists:get_value("Access-Control-Allow-Origin", Resp)),
?assertEqual(
"Cache-Control, Content-Type, Server",
proplists:get_value("Access-Control-Expose-Headers", Resp))
end).
should_make_preflight_request(_, {_, _, Url, DefaultHeaders}) ->
?_assertEqual(?SUPPORTED_METHODS,
begin
Headers = DefaultHeaders
++ [{"Access-Control-Request-Method", "GET"}],
{ok, _, Resp, _} = test_request:options(Url, Headers),
proplists:get_value("Access-Control-Allow-Methods", Resp)
end).
should_make_prefligh_request_with_port({_, VHost}, {_, _, Url, _}) ->
?_assertEqual("http://example.com:5984",
begin
config:set("cors", "origins", "http://example.com:5984",
false),
Headers = [{"Origin", "http://example.com:5984"},
{"Access-Control-Request-Method", "GET"}]
++ maybe_append_vhost(VHost),
{ok, _, Resp, _} = test_request:options(Url, Headers),
proplists:get_value("Access-Control-Allow-Origin", Resp)
end).
should_make_prefligh_request_with_scheme({_, VHost}, {_, _, Url, _}) ->
?_assertEqual("https://example.com:5984",
begin
config:set("cors", "origins", "https://example.com:5984",
false),
Headers = [{"Origin", "https://example.com:5984"},
{"Access-Control-Request-Method", "GET"}]
++ maybe_append_vhost(VHost),
{ok, _, Resp, _} = test_request:options(Url, Headers),
proplists:get_value("Access-Control-Allow-Origin", Resp)
end).
should_make_prefligh_request_with_wildcard_origin({_, VHost}, {_, _, Url, _}) ->
?_assertEqual("https://example.com:5984",
begin
config:set("cors", "origins", "*", false),
Headers = [{"Origin", "https://example.com:5984"},
{"Access-Control-Request-Method", "GET"}]
++ maybe_append_vhost(VHost),
{ok, _, Resp, _} = test_request:options(Url, Headers),
proplists:get_value("Access-Control-Allow-Origin", Resp)
end).
should_make_request_with_credentials(_, {_, _, Url, DefaultHeaders}) ->
?_assertEqual("true",
begin
ok = config:set("cors", "credentials", "true", false),
{ok, _, Resp, _} = test_request:options(Url, DefaultHeaders),
proplists:get_value("Access-Control-Allow-Credentials", Resp)
end).
should_make_origin_request_with_auth(_, {_, _, Url, DefaultHeaders}) ->
?_assertEqual("http://example.com",
begin
Hashed = couch_passwords:hash_admin_password(<<"test">>),
config:set("admins", "test", ?b2l(Hashed), false),
{ok, _, Resp, _} = test_request:get(
Url, DefaultHeaders, [{basic_auth, {"test", "test"}}]),
config:delete("admins", "test", false),
proplists:get_value("Access-Control-Allow-Origin", Resp)
end).
should_make_preflight_request_with_auth(_, {_, _, Url, DefaultHeaders}) ->
?_assertEqual(?SUPPORTED_METHODS,
begin
Hashed = couch_passwords:hash_admin_password(<<"test">>),
config:set("admins", "test", ?b2l(Hashed), false),
Headers = DefaultHeaders
++ [{"Access-Control-Request-Method", "GET"}],
{ok, _, Resp, _} = test_request:options(
Url, Headers, [{basic_auth, {"test", "test"}}]),
config:delete("admins", "test", false),
proplists:get_value("Access-Control-Allow-Methods", Resp)
end).
should_not_return_cors_headers_for_invalid_origin({Host, _}) ->
?_assertEqual(undefined,
begin
Headers = [{"Origin", "http://127.0.0.1"}],
{ok, _, Resp, _} = test_request:get(Host, Headers),
proplists:get_value("Access-Control-Allow-Origin", Resp)
end).
should_not_return_cors_headers_for_invalid_origin_preflight({Host, _}) ->
?_assertEqual(undefined,
begin
Headers = [{"Origin", "http://127.0.0.1"},
{"Access-Control-Request-Method", "GET"}],
{ok, _, Resp, _} = test_request:options(Host, Headers),
proplists:get_value("Access-Control-Allow-Origin", Resp)
end).
should_make_request_against_attachment({Host, DbName}) ->
{"COUCHDB-1689",
?_assertEqual(200,
begin
Url = Host ++ "/" ++ DbName,
{ok, Code0, _, _} = test_request:put(
Url ++ "/doc/file.txt", [{"Content-Type", "text/plain"}],
"hello, couch!"),
?assert(Code0 =:= 201),
{ok, Code, _, _} = test_request:get(
Url ++ "/doc?attachments=true",
[{"Origin", "http://example.com"}]),
Code
end)}.
should_make_range_request_against_attachment({Host, DbName}) ->
{"COUCHDB-1689",
?_assertEqual(206,
begin
Url = Host ++ "/" ++ DbName,
{ok, Code0, _, _} = test_request:put(
Url ++ "/doc/file.txt",
[{"Content-Type", "application/octet-stream"}],
"hello, couch!"),
?assert(Code0 =:= 201),
{ok, Code, _, _} = test_request:get(
Url ++ "/doc/file.txt", [{"Origin", "http://example.com"},
{"Range", "bytes=0-6"}]),
Code
end)}.
should_make_request_with_if_none_match_header({Host, DbName}) ->
{"COUCHDB-1697",
?_assertEqual(304,
begin
Url = Host ++ "/" ++ DbName,
{ok, Code0, Headers0, _} = test_request:put(
Url ++ "/doc", [{"Content-Type", "application/json"}], "{}"),
?assert(Code0 =:= 201),
ETag = proplists:get_value("ETag", Headers0),
{ok, Code, _, _} = test_request:get(
Url ++ "/doc", [{"Origin", "http://example.com"},
{"If-None-Match", ETag}]),
Code
end)}.
maybe_append_vhost(true) ->
[{"Host", "http://example.com"}];
maybe_append_vhost(false) ->
[].