blob: af0928efaac29e7eca6507e9de927e79ee15e1cb [file] [log] [blame]
defmodule ViewSandboxingTest do
use CouchTestCase
@document %{integer: 1, string: "1", array: [1, 2, 3]}
@tag :with_db
test "attempting to change the document has no effect", context do
db_name = context[:db_name]
{:ok, _} = create_doc(db_name, @document)
map_fun = """
function(doc) {
doc.integer = 2;
emit(null, doc);
}
"""
resp = query(db_name, map_fun, nil, %{include_docs: true})
rows = resp["rows"]
# either we have an error or our doc is unchanged
assert resp["total_rows"] == 0 or Enum.at(rows, 0)["doc"]["integer"] == 1
map_fun = """
function(doc) {
doc.array[0] = 0;
emit(null, doc);
}
"""
resp = query(db_name, map_fun, nil, %{include_docs: true})
row = Enum.at(resp["rows"], 0)
# either we have an error or our doc is unchanged
assert resp["total_rows"] == 0 or Enum.at(row["doc"]["array"], 0) == 1
end
@tag :with_db
test "view cannot invoke interpreter internals", context do
db_name = context[:db_name]
{:ok, _} = create_doc(db_name, @document)
map_fun = """
function(doc) {
gc();
emit(null, doc);
}
"""
# make sure that a view cannot invoke interpreter internals such as the
# garbage collector
resp = query(db_name, map_fun)
assert resp["total_rows"] == 0
end
@tag :with_db
test "view cannot access the map_funs and map_results array", context do
db_name = context[:db_name]
{:ok, _} = create_doc(db_name, @document)
map_fun = """
function(doc) {
map_funs.push(1);
emit(null, doc);
}
"""
resp = query(db_name, map_fun)
assert resp["total_rows"] == 0
map_fun = """
function(doc) {
map_results.push(1);
emit(null, doc);
}
"""
resp = query(db_name, map_fun)
assert resp["total_rows"] == 0
end
@tag :with_db
test "COUCHDB-925 - altering 'doc' variable in map function affects other map functions",
context do
db_name = context[:db_name]
ddoc = %{
_id: "_design/foobar",
language: "javascript",
views: %{
view1: %{
map: """
function(doc) {
if (doc.values) {
doc.values = [666];
}
if (doc.tags) {
doc.tags.push("qwerty");
}
if (doc.tokens) {
doc.tokens["c"] = 3;
}
}
"""
},
view2: %{
map: """
function(doc) {
if (doc.values) {
emit(doc._id, doc.values);
}
if (doc.tags) {
emit(doc._id, doc.tags);
}
if (doc.tokens) {
emit(doc._id, doc.tokens);
}
}
"""
}
}
}
doc1 = %{
_id: "doc1",
values: [1, 2, 3]
}
doc2 = %{
_id: "doc2",
tags: ["foo", "bar"],
tokens: %{a: 1, b: 2}
}
{:ok, _} = create_doc(db_name, ddoc)
{:ok, _} = create_doc(db_name, doc1)
{:ok, _} = create_doc(db_name, doc2)
resp1 = view(db_name, "foobar/view1")
resp2 = view(db_name, "foobar/view2")
assert Enum.empty?(resp1.body["rows"])
assert length(resp2.body["rows"]) == 3
assert doc1[:_id] == Enum.at(resp2.body["rows"], 0)["key"]
assert doc2[:_id] == Enum.at(resp2.body["rows"], 1)["key"]
assert doc2[:_id] == Enum.at(resp2.body["rows"], 2)["key"]
assert length(Enum.at(resp2.body["rows"], 0)["value"]) == 3
row0_values = Enum.at(resp2.body["rows"], 0)["value"]
assert Enum.at(row0_values, 0) == 1
assert Enum.at(row0_values, 1) == 2
assert Enum.at(row0_values, 2) == 3
row1_values = Enum.at(resp2.body["rows"], 1)["value"]
row2_values = Enum.at(resp2.body["rows"], 2)["value"]
# we can't be 100% sure about the order for the same key
assert (is_map(row1_values) and row1_values["a"] == 1) or
(is_list(row1_values) and Enum.at(row1_values, 0) == "foo")
assert (is_map(row1_values) and row1_values["b"] == 2) or
(is_list(row1_values) and Enum.at(row1_values, 1) == "bar")
assert (is_map(row2_values) and row2_values["a"] == 1) or
(is_list(row2_values) and Enum.at(row2_values, 0) == "foo")
assert (is_map(row2_values) and row2_values["b"] == 2) or
(is_list(row2_values) and Enum.at(row2_values, 1) == "bar")
assert is_list(row1_values) or !Map.has_key?(row1_values, "c")
assert is_list(row2_values) or !Map.has_key?(row2_values, "c")
end
@tag :with_db
test "runtime code evaluation can be prevented", context do
db_name = context[:db_name]
{:ok, _} = create_doc(db_name, @document)
map_fun = """
function(doc) {
var glob = emit.constructor('return this')();
emit(doc._id, null);
}
"""
resp = query(db_name, map_fun)
assert resp["total_rows"] == 0
end
end