Example #1
0
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
Example #2
0
def test_empty_constraints_in(tag_nested_many_many_fixtures):
    """Test that ``unbound in partial.field`` without any further constraints
    on unbound translates into a check that COUNT(partial.field) > 0."""
    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")
    assert str(authorize_filter).startswith(
        "(AND: (NOT (AND: ('pk__in', []))), ('pk__in', <django.db.models.expressions.Subquery object at "
    )
    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"' + ' WHERE "test_app2_post"."id" IN' +
        ' (SELECT U0."id"' +
        ' FROM "test_app2_post" U0 LEFT OUTER JOIN "test_app2_post_tags" U1 ON (U0."id" = U1."post_id")'
        +
        ' GROUP BY U0."id", U0."contents", U0."access_level", U0."created_by_id", U0."needs_moderation"'
        + ' HAVING COUNT(U1."tag_id") > 0)')
    assert len(posts) == 4
    assert tag_nested_many_many_fixtures["not_tagged_post"] not in posts
Example #3
0
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"."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
Example #4
0
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"."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
Example #5
0
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"."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
Example #6
0
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"."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) == 4
    assert tag_nested_many_many_fixtures["not_tagged_post"] not in posts
Example #7
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)
Example #8
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
Example #9
0
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
Example #10
0
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")
Example #11
0
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
Example #12
0
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)
Example #13
0
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
Example #14
0
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
Example #15
0
def test_in_multiple_attribute_relationship(tag_fixtures):
    Oso.load_str("""
        allow(_: test_app2::User, "read", post: test_app2::Post) if
            post.access_level = "public";
        allow(user: test_app2::User, "read", post: test_app2::Post) if
            post.access_level = "private" and
            post.created_by = user;
        allow(_: test_app2::User, "read", post: test_app2::Post) if
            tag in post.tags and
            0 < tag.id and
            (tag.is_public = true or tag.name = "foo");
        """)

    user = User.objects.get(username="******")
    authorize_filter = authorize_model(None, Post, actor=user, action="read")
    posts = Post.objects.filter(authorize_filter)

    assert tag_fixtures["user_public_post"] in posts
    assert tag_fixtures["user_private_post"] in posts
    assert tag_fixtures["other_user_public_post"] in posts
    assert tag_fixtures["other_user_private_post"] not in posts
    assert tag_fixtures["other_user_random_post"] in posts
    assert tag_fixtures["other_user_foo_post"] in posts