def test_no_objects_collection_condition(): public_tag = Tag(name="public", is_public=True) private_tag = Tag(name="private", is_public=False) post0 = Post(id=0, contents="public tag", tags=[public_tag]) post1 = Post(id=1, contents="no tags", tags=[]) post2 = Post(id=2, contents="both tags", tags=[public_tag, private_tag]) post3 = Post(id=3, contents="public tag 2", tags=[public_tag]) post4 = Post(id=4, contents="private tag", tags=[private_tag]) public_tag.save() private_tag.save() post0.save() post1.save() post2.save() post3.save() post4.save() post0.tags.set([public_tag]) post2.tags.set([public_tag, private_tag]) post3.tags.set([public_tag]) post4.tags.set([private_tag]) Oso.load_str( """ allow(_, _, post: test_app2::Post) if not (tag in post.tags and tag.is_public = true); """ ) posts = Post.objects.authorize(None, actor="u", action="r").all() assert len(posts) == 2 assert post0 in posts assert post3 in posts
def test_route_authorization(client, settings, simple_policy): """Test route authorization middleware""" settings.MIDDLEWARE.append( "django.contrib.sessions.middleware.SessionMiddleware") settings.MIDDLEWARE.append( "django.contrib.auth.middleware.AuthenticationMiddleware") settings.MIDDLEWARE.append("django_oso.middleware.RouteAuthorization") response = client.get("/a/") assert response.status_code == 403 response = client.get("/b/") assert response.status_code == 403 Oso.load_str('allow(_, "GET", _: HttpRequest{path: "/a/"});') response = client.get("/a/") assert response.status_code == 200 response = client.post("/a/") assert response.status_code == 403 # Django runs url resolving after middleware, so there is no way # for the route authorization middleware to determine if the response # would be a 404 and not apply authorization. response = client.get("/notfound/") assert response.status_code == 403
def test_unify_ins(tag_nested_many_many_fixtures): Oso.load_str(""" allow(_, _, post) if user1 in post.users and user2 in post.users and user1.id = user2.id and user1.id > 1 and user2.id <= 2; """) user = User.objects.get(username="******") authorize_filter = authorize_model(None, Post, actor=user, action="read") posts = Post.objects.filter(authorize_filter) expected = """ SELECT "test_app2_post"."id", "test_app2_post"."contents", "test_app2_post"."title", "test_app2_post"."access_level", "test_app2_post"."created_by_id", "test_app2_post"."needs_moderation" FROM "test_app2_post" LEFT OUTER JOIN "test_app2_user_posts" ON ("test_app2_post"."id" = "test_app2_user_posts"."post_id") WHERE (EXISTS(SELECT U0."id" FROM "test_app2_user" U0 INNER JOIN "test_app2_user" V0 ON (U0."id" = V0."id") WHERE (U0."id" = "test_app2_user_posts"."user_id" AND V0."id" = "test_app2_user_posts"."user_id" AND U0."id" <= 2 AND V0."id" > 1))) """ assert str(posts.query) == " ".join(expected.split()) assert len(posts) == 1
def test_empty_constraints_in(tag_nested_many_many_fixtures): """Test that ``unbound in partial.field`` without any further constraints on unbound translates into an existence check.""" Oso.load_str(""" allow(_: test_app2::User, "read", post: test_app2::Post) if _tag in post.tags; """) user = User.objects.get(username="******") authorize_filter = authorize_model(None, Post, actor=user, action="read") posts = Post.objects.filter(authorize_filter).distinct() expected = f""" SELECT DISTINCT "test_app2_post"."id", "test_app2_post"."contents", "test_app2_post"."title", "test_app2_post"."access_level", "test_app2_post"."created_by_id", "test_app2_post"."needs_moderation" FROM "test_app2_post" WHERE "test_app2_post"."id" IN (SELECT V0."id" FROM "test_app2_post" V0 LEFT OUTER JOIN "test_app2_post_tags" V1 ON (V0."id" = V1."post_id") WHERE EXISTS(SELECT U0."id" FROM "test_app2_tag" U0 WHERE U0."id" = {parenthesize('V1."tag_id"')}){is_true()}) """ assert str(posts.query) == " ".join(expected.split()) assert len(posts) == 5 assert tag_nested_many_many_fixtures["not_tagged_post"] not in posts
def test_in_with_constraints_but_no_matching_objects( tag_nested_many_many_fixtures): Oso.load_str(""" allow(_: test_app2::User, "read", post: test_app2::Post) if tag in post.tags and tag.name = "bloop"; """) user = User.objects.get(username="******") posts = Post.objects.authorize(None, actor=user, action="read") expected = f""" SELECT "test_app2_post"."id", "test_app2_post"."contents", "test_app2_post"."title", "test_app2_post"."access_level", "test_app2_post"."created_by_id", "test_app2_post"."needs_moderation" FROM "test_app2_post" WHERE "test_app2_post"."id" IN (SELECT W0."id" FROM "test_app2_post" W0 WHERE W0."id" IN (SELECT V0."id" FROM "test_app2_post" V0 LEFT OUTER JOIN "test_app2_post_tags" V1 ON (V0."id" = V1."post_id") WHERE EXISTS(SELECT U0."id" FROM "test_app2_tag" U0 WHERE (U0."id" = {parenthesize('V1."tag_id"')} AND U0."name" = bloop)){is_true()})) """ assert str(posts.query) == " ".join(expected.split()) assert len(posts) == 0
def test_partial_subfield_isa(): from test_app.models import Post, User alice = User(name="alice") alice.save() not_alice = User(name="not alice") not_alice.save() Post(created_by=alice).save(), Post(created_by=not_alice).save(), Post(created_by=alice).save(), Oso.load_str(""" allow(_, _, post: test_app::Post) if check(post.created_by); check(user: test_app::User) if user.name = "alice"; check(post: test_app::Post) if post.is_private = false; """) authorize_filter = authorize_model(None, Post, actor="foo", action="bar") assert ( str(authorize_filter) == "(OR:" + " (AND: (NOT (AND: ('pk__in', []))), (NOT (AND: ('pk__in', []))), ('created_by__name', 'alice'))," + " ('pk__in', []))") authorized_posts = Post.objects.filter(authorize_filter) assert ( str(authorized_posts.query) == 'SELECT "test_app_post"."id", "test_app_post"."is_private", "test_app_post"."name",' + ' "test_app_post"."timestamp", "test_app_post"."option", "test_app_post"."created_by_id"' + ' FROM "test_app_post" LEFT OUTER JOIN "test_app_user"' + ' ON ("test_app_post"."created_by_id" = "test_app_user"."id") WHERE "test_app_user"."name" = alice' ) assert authorized_posts.count() == 2
def test_nested_relationship_many_many(tag_nested_many_many_fixtures): """Test that nested relationships work. post - (many) -> tags - (many) -> User A user can read a post with a tag if the tag's creator is the user. """ Oso.load_str(""" allow(user: test_app2::User, "read", post: test_app2::Post) if tag in post.tags and user in tag.users; """) user = User.objects.get(username="******") authorize_filter = authorize_model(None, Post, actor=user, action="read") posts = Post.objects.filter(authorize_filter) assert tag_nested_many_many_fixtures["user_eng_post"] in posts assert tag_nested_many_many_fixtures["user_user_post"] in posts assert tag_nested_many_many_fixtures["random_post"] not in posts assert tag_nested_many_many_fixtures["not_tagged_post"] not in posts assert tag_nested_many_many_fixtures["all_tagged_post"] in posts user = User.objects.get(username="******") authorize_filter = authorize_model(None, Post, actor=user, action="read") posts = Post.objects.filter(authorize_filter) assert tag_nested_many_many_fixtures["user_eng_post"] not in posts assert tag_nested_many_many_fixtures["user_user_post"] not in posts assert tag_nested_many_many_fixtures["random_post"] in posts assert tag_nested_many_many_fixtures["not_tagged_post"] not in posts assert tag_nested_many_many_fixtures["all_tagged_post"] in posts
def test_ground_object_in_collection(): tag = Tag(name="tag") post0 = Post(id=0, contents="tag post") post1 = Post(id=1, contents="no tag post") post2 = Post(id=2, contents="tag 2 post") tag.save() post0.save() post1.save() post2.save() post0.tags.set([tag]) post2.tags.set([tag]) Oso.register_constant(tag, "allowed_tag") Oso.load_str( """ allow(_, _, post: test_app2::Post) if allowed_tag in post.tags; """ ) posts = Post.objects.authorize(None, actor="u", action="r").all() assert len(posts) == 2 assert post0 in posts assert post2 in posts
def test_many_many_with_other_condition(tag_nested_many_many_fixtures): """Test that using a many-to-many condition OR any other condition does not result in duplicate results.""" Oso.load_str(""" allow(_: test_app2::User, "read", post: test_app2::Post) if tag in post.tags and tag.name = "eng"; allow(_: test_app2::User, "read", post: test_app2::Post) if post.access_level = "public"; """) user = User.objects.get(username="******") posts = Post.objects.authorize(None, actor=user, action="read") expected = f""" SELECT "test_app2_post"."id", "test_app2_post"."contents", "test_app2_post"."access_level", "test_app2_post"."created_by_id", "test_app2_post"."needs_moderation" FROM "test_app2_post" WHERE "test_app2_post"."id" IN (SELECT W0."id" FROM "test_app2_post" W0 WHERE (W0."id" IN (SELECT V0."id" FROM "test_app2_post" V0 LEFT OUTER JOIN "test_app2_post_tags" V1 ON (V0."id" = V1."post_id") WHERE EXISTS(SELECT U0."id" FROM "test_app2_tag" U0 WHERE (U0."id" = {parenthesize('V1."tag_id"')} AND U0."name" = eng)){is_true()}) OR W0."access_level" = public)) """ assert str(posts.query) == " ".join(expected.split()) # all should be returned with no duplicates assert list(posts) == list(tag_nested_many_many_fixtures.values())
def test_redundant_in_on_same_field(tag_nested_many_many_fixtures): Oso.load_str(""" allow(_, "read", post) if tag1 in post.tags and tag2 in post.tags and tag1.name = "random" and tag2.is_public = true; """) user = User.objects.get(username="******") authorize_filter = authorize_model(None, Post, actor=user, action="read") posts = Post.objects.filter(authorize_filter) expected = f""" SELECT "test_app2_post"."id", "test_app2_post"."contents", "test_app2_post"."title", "test_app2_post"."access_level", "test_app2_post"."created_by_id", "test_app2_post"."needs_moderation" FROM "test_app2_post" WHERE "test_app2_post"."id" IN (SELECT V0."id" FROM "test_app2_post" V0 LEFT OUTER JOIN "test_app2_post_tags" V1 ON (V0."id" = V1."post_id") WHERE (EXISTS(SELECT U0."id" FROM "test_app2_tag" U0 WHERE (U0."id" = {parenthesize('V1."tag_id"')} AND U0."name" = random)){is_true()} AND EXISTS(SELECT U0."id" FROM "test_app2_tag" U0 WHERE (U0."id" = {parenthesize('V1."tag_id"')} AND U0."is_public"{is_true()})){is_true()})) """ assert str(posts.query) == " ".join(expected.split()) assert len(posts) == 2
def test_in_intersection(tag_nested_many_many_fixtures): Oso.load_str(""" allow(_, _, post) if u in post.users and t in post.tags and u in t.users; """) user = User.objects.get(username="******") authorize_filter = authorize_model(None, Post, actor=user, action="read") posts = Post.objects.filter(authorize_filter) expected = f""" SELECT "test_app2_post"."id", "test_app2_post"."contents", "test_app2_post"."title", "test_app2_post"."access_level", "test_app2_post"."created_by_id", "test_app2_post"."needs_moderation" FROM "test_app2_post" WHERE "test_app2_post"."id" IN (SELECT X0."id" FROM "test_app2_post" X0 LEFT OUTER JOIN "test_app2_user_posts" X1 ON (X0."id" = X1."post_id") LEFT OUTER JOIN "test_app2_post_tags" X3 ON (X0."id" = X3."post_id") WHERE (EXISTS(SELECT U0."id" FROM "test_app2_user" U0 WHERE U0."id" = {parenthesize('X1."user_id"')}){is_true()} AND EXISTS(SELECT W0."id" FROM "test_app2_tag" W0 WHERE (W0."id" = {parenthesize('X3."tag_id"')} AND W0."id" IN (SELECT V0."id" FROM "test_app2_tag" V0 LEFT OUTER JOIN "test_app2_tag_users" V1 ON (V0."id" = V1."tag_id") WHERE EXISTS(SELECT U0."id" FROM "test_app2_user" U0 WHERE U0."id" = {parenthesize('V1."user_id"')}){is_true()}))){is_true()})) """ assert str(posts.query) == " ".join(expected.split()) assert len(posts) == 4
def test_rewrite_parameters(): from test_app.models import Post Oso.load_str("""allow(_, _, resource) if g(resource.created_by); g(resource) if resource matches test_app::User; """) authorize_filter = authorize_model(None, Post, actor="foo", action="bar") assert str(authorize_filter) == str(TRUE_FILTER)
def test_authorize_model_basic(post_fixtures): """Test that a simple policy with checks on non-relationship attributes is correct.""" Oso.load_str(""" allow(u, "read", post: test_app2::Post) if u in ["admin", "user"] and post.access_level = "public"; allow("user", "write", post: test_app2::Post) if post.access_level = "private"; allow("admin", "read", _post: test_app2::Post); allow("moderator", "read", post: test_app2::Post) if (post.access_level = "private" or post.access_level = "public") and post.needs_moderation = true; """) authorize_filter = authorize_model(None, Post, actor="user", action="read") assert str(authorize_filter) == "(AND: ('access_level', 'public'))" posts = Post.objects.filter(authorize_filter) assert posts.count() == 5 assert posts.all()[0].contents == "foo public post" authorize_filter = authorize_model(None, Post, actor="user", action="write") assert str(authorize_filter) == "(AND: ('access_level', 'private'))" posts = Post.objects.filter(authorize_filter) assert posts.count() == 4 assert posts.all()[0].contents == "foo private post" assert posts.all()[1].contents == "foo private post 2" authorize_filter = authorize_model(None, Post, actor="admin", action="read") assert str(authorize_filter) == str(TRUE_FILTER) posts = Post.objects.filter(authorize_filter) assert posts.count() == 9 authorize_filter = authorize_model(None, Post, actor="moderator", action="read") expected = """ (OR: (AND: ('access_level', 'private'), ('needs_moderation', True)), (AND: ('access_level', 'public'), ('needs_moderation', True))) """ assert str(authorize_filter) == " ".join(expected.split()) posts = Post.objects.filter(authorize_filter) assert posts.count() == 4 assert posts.all()[0].contents == "private for moderation" assert posts.all()[1].contents == "public for moderation" # Not authorized with pytest.raises(PermissionDenied): authorize_model(None, Post, actor="guest", action="read")
def handle(self, *args, **options): from django_oso.oso import Oso while True: # Run OSO (which runs in loop too), and handle unhandled exceptions (KB Interrupt is already handled within # repl as a normal return. try: Oso.repl() return except Exception as e: traceback.print_exc()
def test_negated_matches_with_partial(rf): from test_app.models import Post Post(name="test", is_private=False, timestamp=1).save() Oso.load_str(""" allow(1, _, post) if not post matches test_app::Post; allow(2, _, post) if not post matches test_app::User; allow(3, _, post) if not post.created_by matches test_app::User; allow(4, _, post) if not post.created_by matches test_app::Post; """) request = rf.get("/") request.user = 1 authorize_filter = authorize_model(request, Post) assert str(authorize_filter) == ( f"(AND: {str(TRUE_FILTER)}, (NOT (AND: {str(TRUE_FILTER)})))") authorized_posts = Post.objects.filter(authorize_filter) # For some reason, this only seems to be raised when stringifying. with pytest.raises(EmptyResultSet): str(authorized_posts.query) assert authorized_posts.count() == 0 request.user = 2 authorize_filter = authorize_model(request, Post) assert str(authorize_filter) == str(TRUE_FILTER) authorized_posts = Post.objects.filter(authorize_filter) expected = """ SELECT "test_app_post"."id", "test_app_post"."is_private", "test_app_post"."name", "test_app_post"."timestamp", "test_app_post"."option", "test_app_post"."created_by_id" FROM "test_app_post" """ assert str(authorized_posts.query) == " ".join(expected.split()) assert authorized_posts.count() == 1 request.user = 3 authorize_filter = authorize_model(request, Post) assert str(authorize_filter) == ( f"(AND: {str(TRUE_FILTER)}, (NOT (AND: {str(TRUE_FILTER)})))") authorized_posts = Post.objects.filter(authorize_filter) # For some reason, this only seems to be raised when stringifying. with pytest.raises(EmptyResultSet): str(authorized_posts.query) assert authorized_posts.count() == 0 request.user = 4 authorize_filter = authorize_model(request, Post) assert str(authorize_filter) == str(TRUE_FILTER) authorized_posts = Post.objects.filter(authorize_filter) expected = """ SELECT "test_app_post"."id", "test_app_post"."is_private", "test_app_post"."name", "test_app_post"."timestamp", "test_app_post"."option", "test_app_post"."created_by_id" FROM "test_app_post" """ assert str(authorized_posts.query) == " ".join(expected.split()) assert authorized_posts.count() == 1
def test_model_registration(): """Test that models are automatically registered with the policy.""" from test_app import models from oso import Variable assert (next( Oso.query_rule("models", models.TestRegistration(), Variable("x")))["bindings"]["x"] == 1) assert (next( Oso.query_rule("models", models.TestRegistration2(), Variable("x")))["bindings"]["x"] == 2)
def test_this_in_var(tag_nested_many_many_fixtures): Oso.load_str(""" # _this in var allow(_, _, post: test_app2::Post) if post in x and x in post.created_by.posts; """) user = User.objects.get(username="******") authorize_filter = authorize_model(None, Post, actor=user, action="read") posts = Post.objects.filter(authorize_filter) expected = """ """ assert str(posts.query) == " ".join(expected.split()) assert len(posts) == 5050
def test_many_many_with_other_condition(tag_nested_many_many_fixtures): """Test that using a many-to-many condition OR any other condition does not result in duplicate results.""" Oso.load_str(""" allow(_: test_app2::User, "read", post: test_app2::Post) if tag in post.tags and tag.name = "eng"; allow(_: test_app2::User, "read", post: test_app2::Post) if post.access_level = "public"; """) user = User.objects.get(username="******") posts = Post.objects.authorize(None, actor=user, action="read") # all should be returned with no duplicates assert list(posts) == list(tag_nested_many_many_fixtures.values())
def test_partial_with_allow_all(rf): from test_app.models import Post Post(name="test", is_private=False, timestamp=1).save() Oso.load_str("allow(_, _, _);") request = rf.get("/") request.user = "******" authorize_filter = authorize_model(request, Post) assert str(authorize_filter) == str(TRUE_FILTER) authorized_posts = Post.objects.filter(authorize_filter) expected = """ SELECT "test_app_post"."id", "test_app_post"."is_private", "test_app_post"."name", "test_app_post"."timestamp", "test_app_post"."option", "test_app_post"."created_by_id" FROM "test_app_post" """ assert str(authorized_posts.query) == " ".join(expected.split()) assert authorized_posts.count() == 1
def test_null_with_partial(rf): from test_app.models import Post Post(name="test", is_private=False, timestamp=1).save() Oso.load_str("allow(_, _, post: test_app::Post) if post.option = nil;") request = rf.get("/") request.user = "******" authorize_filter = authorize_model(request, Post) assert (str(authorize_filter) == "(AND: (NOT (AND: ('pk__in', []))), ('option', None))") authorized_posts = Post.objects.filter(authorize_filter) assert str(authorized_posts.query) == ( 'SELECT "test_app_post"."id", "test_app_post"."is_private", "test_app_post"."name", ' + '"test_app_post"."timestamp", "test_app_post"."option", "test_app_post"."created_by_id"' + ' FROM "test_app_post"' + ' WHERE "test_app_post"."option" IS NULL') assert authorized_posts.count() == 1
def test_deeply_nested_in(tag_nested_many_many_fixtures): Oso.load_str(""" allow(_, _, post: test_app2::Post) if foo in post.created_by.posts and foo.id > 1 and bar in foo.created_by.posts and bar.id > 2 and baz in bar.created_by.posts and baz.id > 3 and post in baz.created_by.posts and post.id > 4; """) user = User.objects.get(username="******") authorize_filter = authorize_model(None, Post, actor=user, action="read") posts = Post.objects.filter(authorize_filter).distinct() expected = """ SELECT DISTINCT "test_app2_post"."id", "test_app2_post"."contents", "test_app2_post"."access_level", "test_app2_post"."created_by_id", "test_app2_post"."needs_moderation" FROM "test_app2_post" INNER JOIN "test_app2_user" ON ("test_app2_post"."created_by_id" = "test_app2_user"."id") LEFT OUTER JOIN "test_app2_user_posts" ON ("test_app2_user"."id" = "test_app2_user_posts"."user_id") WHERE (EXISTS(SELECT W0."id" FROM "test_app2_post" W0 INNER JOIN "test_app2_user" W1 ON (W0."created_by_id" = W1."id") LEFT OUTER JOIN "test_app2_user_posts" W2 ON (W1."id" = W2."user_id") WHERE (EXISTS(SELECT V0."id" FROM "test_app2_post" V0 INNER JOIN "test_app2_user" V1 ON (V0."created_by_id" = V1."id") LEFT OUTER JOIN "test_app2_user_posts" V2 ON (V1."id" = V2."user_id") WHERE (EXISTS(SELECT U0."id" FROM "test_app2_post" U0 INNER JOIN "test_app2_user" U1 ON (U0."created_by_id" = U1."id") INNER JOIN "test_app2_user_posts" U2 ON (U1."id" = U2."user_id") WHERE (U0."id" = V2."post_id" AND U0."id" > 3 # This is not the sql that is generated. # Instead U0."id" is the LHS of below. AND "test_app2_post"."id" = U2."post_id")) AND V0."id" = W2."post_id" AND V0."id" > 2)) AND W0."id" = "test_app2_user_posts"."post_id" AND W0."id" > 1)) AND "test_app2_post"."id" > 4) """ assert str(posts.query) == " ".join(expected.split()) assert len(posts) == 1
def test_null_with_partial(rf): from test_app.models import Post Post(name="test", is_private=False, timestamp=1).save() Oso.load_str("allow(_, _, post: test_app::Post) if post.option = nil;") request = rf.get("/") request.user = "******" authorize_filter = authorize_model(request, Post) assert str(authorize_filter) == "(AND: ('option', None))" authorized_posts = Post.objects.filter(authorize_filter) expected = """ SELECT "test_app_post"."id", "test_app_post"."is_private", "test_app_post"."name", "test_app_post"."timestamp", "test_app_post"."option", "test_app_post"."created_by_id" FROM "test_app_post" WHERE "test_app_post"."option" IS NULL """ assert str(authorized_posts.query) == " ".join(expected.split()) assert authorized_posts.count() == 1
def test_field_comparison(): post0 = Post(id=0, contents="private post", title="not private post") post1 = Post(id=1, contents="private post", title="private post") post2 = Post(id=2, contents="post", title="post") post0.save() post1.save() post2.save() Oso.load_str( """ allow(_, _, post: test_app2::Post) if post.title = post.contents; """ ) posts = Post.objects.authorize(None, actor="u", action="r").all() assert len(posts) == 2 assert post1 in posts assert post2 in posts
def test_scalar_in_list(): post0 = Post(id=0, contents="private post", title="not private post") post1 = Post(id=1, contents="allowed posts", title="private post") post2 = Post(id=2, contents="post", title="post") post0.save() post1.save() post2.save() Oso.load_str( """ allow(_, _, post: test_app2::Post) if post.contents in ["post", "allowed posts"]; """ ) posts = Post.objects.authorize(None, actor="u", action="r").all() assert len(posts) == 2 assert post1 in posts assert post2 in posts
def test_partial_errors(rf, settings): from test_app.models import Post Post(name="test", is_private=False, timestamp=1).save() Post(name="test_past", is_private=False, timestamp=-1).save() Post(name="test_public", is_private=False, timestamp=1).save() Post(name="test_private", is_private=True, timestamp=1).save() Post(name="test_private_2", is_private=True, timestamp=1).save() request = rf.get("/") request.user = "******" Oso.load_str('allow(_, "fail", post: test_app::Post) if post matches {x: 1};') with pytest.raises(UnsupportedError): q = Post.objects.authorize(request, action="fail") # No rules for this. q = Post.objects.authorize(request, action="get") assert q.count() == 0
def test_authorize_scalar_attribute_condition(post_fixtures): """Scalar attribute condition checks.""" Oso.load_str(""" # Object equals another object allow(actor: test_app2::User, "read", post: test_app2::Post) if post.created_by.is_banned = false and post.created_by = actor and post.access_level = "private"; allow(_actor: test_app2::User, "read", post: test_app2::Post) if post.created_by.is_banned = false and post.access_level = "public"; # moderator can see posts made by banned users. allow(actor: test_app2::User, "read", post: test_app2::Post) if actor.is_moderator = true and post.created_by.is_banned = true; """) foo = User.objects.get(username="******") authorize_filter = authorize_model(None, Post, actor=foo, action="read") posts = Post.objects.filter(authorize_filter) def allowed(post, user): return (post.access_level == "public" and not post.created_by.is_banned) or ( post.access_level == "private" and post.created_by == user) assert posts.count() == 7 assert all(allowed(post, foo) for post in posts) admin = User.objects.get(username="******") authorize_filter = authorize_model(None, Post, actor=admin, action="read") posts = Post.objects.filter(authorize_filter) def allowed_admin(post): return post.created_by.is_banned assert posts.count() == 6 for post in posts: assert allowed(post, admin) or allowed_admin(post)
def test_reverse_many_relationship(tag_nested_many_many_fixtures): """Test an authorization rule over a reverse relationship""" Oso.load_str(""" allow(actor, _, post: test_app2::Post) if post.users matches test_app2::User and actor in post.users; """) user = User.objects.get(username="******") authorize_filter = authorize_model(None, Post, actor=user, action="read") assert str(authorize_filter) == "(AND: ('users', <User: User object (1)>))" posts = Post.objects.filter(authorize_filter) expected = """ SELECT "test_app2_post"."id", "test_app2_post"."contents", "test_app2_post"."title", "test_app2_post"."access_level", "test_app2_post"."created_by_id", "test_app2_post"."needs_moderation" FROM "test_app2_post" INNER JOIN "test_app2_user_posts" ON ("test_app2_post"."id" = "test_app2_user_posts"."post_id") WHERE "test_app2_user_posts"."user_id" = 1 """ assert str(posts.query) == " ".join(expected.split()) assert len(posts) == 5
def test_authorize_scalar_attribute_eq(post_fixtures): """Test authorization rules on a relationship with one object equaling another.""" # Object equals another object Oso.load_str(""" allow(actor: test_app2::User, "read", _: test_app2::Post{created_by: actor, access_level: "private"}); allow(_: test_app2::User, "read", post) if post matches test_app2::Post{access_level: "public"}; allow(_: test_app2::User{is_moderator: true}, "read", post: test_app2::Post) if post matches {access_level: "public"}; """) foo = User.objects.get(username="******") authorize_filter = authorize_model(None, Post, actor=foo, action="read") posts = Post.objects.filter(authorize_filter) def allowed(post): return (post.access_level == "public" or post.access_level == "private" and post.created_by == foo) assert posts.count() == 8 assert all(allowed(post) for post in posts)
def test_in_with_constraints_but_no_matching_objects( tag_nested_many_many_fixtures): Oso.load_str(""" allow(_: test_app2::User, "read", post: test_app2::Post) if tag in post.tags and tag.name = "bloop"; """) user = User.objects.get(username="******") authorize_filter = authorize_model(None, Post, actor=user, action="read") assert (str(authorize_filter) == "(AND: (NOT (AND: ('pk__in', []))), ('tags__name', 'bloop'))") posts = Post.objects.filter(authorize_filter) assert ( str(posts.query) == 'SELECT "test_app2_post"."id", "test_app2_post"."contents", "test_app2_post"."access_level",' + ' "test_app2_post"."created_by_id", "test_app2_post"."needs_moderation"' + ' FROM "test_app2_post"' + ' INNER JOIN "test_app2_post_tags"' + ' ON ("test_app2_post"."id" = "test_app2_post_tags"."post_id")' + ' INNER JOIN "test_app2_tag"' + ' ON ("test_app2_post_tags"."tag_id" = "test_app2_tag"."id")' + ' WHERE "test_app2_tag"."name" = bloop') assert len(posts) == 0
def test_partial_in_collection(tag_nested_many_many_fixtures): Oso.load_str(""" allow(user: test_app2::User, "read", post: test_app2::Post) if post in user.posts.all(); """) user = User.objects.get(username="******") authorize_filter = authorize_model(None, Post, actor=user, action="read") posts = Post.objects.filter(authorize_filter) assert tag_nested_many_many_fixtures["user_eng_post"] in posts assert tag_nested_many_many_fixtures["user_user_post"] in posts assert tag_nested_many_many_fixtures["random_post"] not in posts assert tag_nested_many_many_fixtures["not_tagged_post"] in posts assert tag_nested_many_many_fixtures["all_tagged_post"] in posts user = User.objects.get(username="******") authorize_filter = authorize_model(None, Post, actor=user, action="read") posts = Post.objects.filter(authorize_filter) assert tag_nested_many_many_fixtures["user_eng_post"] not in posts assert tag_nested_many_many_fixtures["user_user_post"] not in posts assert tag_nested_many_many_fixtures["random_post"] in posts assert tag_nested_many_many_fixtures["not_tagged_post"] not in posts assert tag_nested_many_many_fixtures["all_tagged_post"] not in posts