diff --git a/tests/test_permissions.py b/tests/test_permissions.py index 1b50060de9df368618e5e4966263dde30242de0d..9df914a1de5adc925acd4083b31fb9264f9f65eb 100644 --- a/tests/test_permissions.py +++ b/tests/test_permissions.py @@ -36,6 +36,7 @@ from nose.tools import (assert_equal, assert_is_not_none) from pytest import raises, mark + test_user = "test_user" test_role = "test_role" test_pw = "passphrase1P!" @@ -1195,3 +1196,125 @@ def test_deny_update_role(): p.name = "TestPropertyEvenNewer" with raises(db.TransactionError) as te: p.update() + + +@mark.xfail(reason="Fix insufficient permission checks of referenced entity names.") +def test_query_with_invisible_reference(): + """Names of references that are not visible to the test user should not be + usable as query filters. + + """ + + rt = db.RecordType(name="TestRT").insert() + rec_invisible = db.Record(name="TestInvisible").add_parent(rt).insert() + rec_visible = db.Record(name="TestVisible").add_parent( + rt).add_property(name=rt.name, value=rec_invisible.id).insert() + # test user is only allowed to see rec_visible, not rec_invisble + grant_permission(rec_visible, "RETRIEVE:*") + deny_permission(rec_invisible, "RETRIEVE:*") + + # as admin, I'm allowed to filter this + assert len(db.execute_query(f"FIND {rt.name} WITH {rt.name}={rec_invisible.name}")) == 1 + + switch_to_test_user() + + # Retrival is forbidden + with raises(db.TransactionError) as te: + retrieved_invisible = db.Record(id=rec_invisible.id).retrieve() + assert te.value.has_error(db.AuthorizationError) + + retrieved_visible = db.Record(id=rec_visible.id).retrieve() + assert retrieved_visible.name == rec_visible.name + assert retrieved_visible.get_property(rt.name) is not None + assert retrieved_visible.get_property(rt.name).value == rec_invisible.id + + # We cant see rec_invisible, so its name can't be used as a valid query filter. + assert len(db.execute_query( + f"FIND {rt.name} WITH {rt.name} WITH name={rec_invisible.name}")) == 0 + assert len(db.execute_query(f"FIND {rt.name} WITH {rt.name}={rec_invisible.name}")) == 0 + assert len(db.execute_query(f"FIND {rt.name} WITH {rt.name} LIKE '*invis*'")) == 0 + + +@mark.xfail(reason="Fix insufficient permission checks of referenced entity selectors.") +def test_select_query_with_invisible_reference(): + """SELECT queries must not leak property values of invisible referenced entities.""" + + visible_rt = db.RecordType(name="TestTypeVisible").insert() + invisible_rt = db.RecordType(name="TestTypeInvisible").insert() + other_rt = db.RecordType(name="TestTypeOther").insert() + prop = db.Property(name="TestProp", datatype=db.INTEGER).insert() + + other_rec = db.Record(name="TestOther").add_parent(other_rt).insert() + referenced_visible = db.Record(name="TestReferencedVisible").add_parent(visible_rt).insert() + # invisible rec will have one int property, one reference to the (invisible) + # other rt and one to the visible rt. + invisible_rec = db.Record(name="TestInvisible").add_parent(invisible_rt) + invisible_rec.add_property(name=prop.name, value=42) + invisible_rec.add_property(name=other_rt.name, value=other_rec.id) + invisible_rec.add_property(name=visible_rt.name, value=referenced_visible.id) + invisible_rec.insert() + visible_rec = db.Record(name="TestVisible").add_parent(visible_rt.name) + visible_rec.add_property(name=invisible_rt.name, value=invisible_rec.id) + visible_rec.insert() + + # Everything is there when queried as admin + select_results = db.execute_query( + f"SELECT name, {invisible_rt.name}, {invisible_rt.name}.name, " + f"{invisible_rt.name}.{prop.name}, {invisible_rt.name}.{other_rt.name}, " + f"{invisible_rt.name}.{other_rt.name}.name FROM {visible_rec.id}") + values = select_results.get_property_values( + "name", f"{invisible_rt.name}", (invisible_rt.name, "name"), + (invisible_rt.name, prop.name), (invisible_rt.name, other_rt.name), + (invisible_rt.name, other_rt.name, "name"))[0] + assert values[0] == visible_rec.name + assert values[1] == invisible_rec.id + assert values[2] == invisible_rec.name + assert values[3] == invisible_rec.get_property(prop.name).value + assert values[4] == other_rec.id + assert values[5] == other_rec.name + + for rec in [referenced_visible, visible_rec]: + grant_permission(rec, "RETRIEVE:*") + for rec in [invisible_rec, other_rec]: + deny_permission(rec, "RETRIEVE:*") + + switch_to_test_user() + + select_results = db.execute_query( + f"SELECT name, {invisible_rt.name}, {invisible_rt.name}.name, " + f"{invisible_rt.name}.{prop.name}, {invisible_rt.name}.{other_rt.name}, " + f"{invisible_rt.name}.{other_rt.name}.name FROM {visible_rec.id}") + values = select_results.get_property_values( + "name", f"{invisible_rt.name}", (invisible_rt.name, "name"), + (invisible_rt.name, prop.name), (invisible_rt.name, other_rt.name), + (invisible_rt.name, other_rt.name, "name"))[0] + assert values[0] == visible_rec.name + assert values[1] == invisible_rec.id # id is ok + assert values[2] is None # name isn't + assert values[3] is None # prop isn't either + assert values[4] is None # neither id ... + assert values[5] is None # ... nor name of other rec referenced by invisible + + # Special case of visible referencing invisible referencing visible + + switch_to_admin_user() + + select_results = db.execute_query( + f"SELECT {invisible_rt.name}.{visible_rt.name}, " + f"{invisible_rt.name}.{visible_rt.name}.name FROM {visible_rec.id}") + values = select_results.get_property_values( + (invisible_rt.name, visible_rt.name), + (invisible_rt.name, visible_rt.name, "name"))[0] + assert values[0] == referenced_visible.id + assert values[1] == referenced_visible.name + + switch_to_test_user() + + select_results = db.execute_query( + f"SELECT {invisible_rt.name}.{visible_rt.name}, " + f"{invisible_rt.name}.{visible_rt.name}.name FROM {visible_rec.id}") + values = select_results.get_property_values( + (invisible_rt.name, visible_rt.name), + (invisible_rt.name, visible_rt.name, "name"))[0] + assert values[0] is None + assert values[1] is None