Beispiel #1
0
def test_partial_constraint(polar):
    class User:
        pass

    class Post:
        pass

    polar.register_class(User)
    polar.register_class(Post)

    polar.load_str("f(x: User) if x.user = 1;")
    polar.load_str("f(x: Post) if x.post = 1;")

    partial = Partial("x", TypeConstraint("User"))
    results = polar.query_rule("f", partial)

    first = next(results)["bindings"]["x"]
    and_args = unwrap_and(first)

    assert len(and_args) == 2

    assert and_args[0] == Expression("Isa", [Variable("_this"), Pattern("User", {})])

    unify = unwrap_and(and_args[1])
    assert unify == Expression(
        "Unify", [Expression("Dot", [Variable("_this"), "user"]), 1]
    )

    with pytest.raises(StopIteration):
        next(results)
Beispiel #2
0
def test_register_constants_with_decorator():
    @polar_class
    class RegisterDecoratorTest:
        x = 1

    p = Polar()
    p.load_str("foo_rule(x: RegisterDecoratorTest, y) if y = 1;")
    p.load_str("foo_class_attr(y) if y = RegisterDecoratorTest.x;")
    assert (
        next(p.query_rule("foo_rule", RegisterDecoratorTest(), Variable("y")))[
            "bindings"
        ]["y"]
        == 1
    )
    assert next(p.query_rule("foo_class_attr", Variable("y")))["bindings"]["y"] == 1

    p = Polar()
    p.load_str("foo_rule(x: RegisterDecoratorTest, y) if y = 1;")
    p.load_str("foo_class_attr(y) if y = RegisterDecoratorTest.x;")
    assert (
        next(p.query_rule("foo_rule", RegisterDecoratorTest(), Variable("y")))[
            "bindings"
        ]["y"]
        == 1
    )
    assert next(p.query_rule("foo_class_attr", Variable("y")))["bindings"]["y"] == 1
Beispiel #3
0
def test_partial(polar):
    polar.load_str("f(1);")
    polar.load_str("f(x) if x = 1 and x = 2;")

    results = polar.query_rule("f", Partial("x"))
    first = next(results)

    x = first["bindings"]["x"]
    assert unwrap_and(x) == Expression("Unify", [Variable("_this"), 1])

    second = next(results)
    x = second["bindings"]["x"]

    # Top level should be and
    and_args = unwrap_and(x)
    assert and_args[0] == Expression("Unify", [Variable("_this"), 1])

    polar.load_str("g(x) if x.bar = 1 and x.baz = 2;")

    results = polar.query_rule("g", Partial("x"))
    first = next(results)

    x = first["bindings"]["x"]
    and_args = unwrap_and(x)
    assert len(and_args) == 2
    assert unwrap_and(and_args[0]) == Expression(
        "Unify", [Expression("Dot", [Variable("_this"), "bar"]), 1]
    )
    assert unwrap_and(and_args[1]) == Expression(
        "Unify", [Expression("Dot", [Variable("_this"), "baz"]), 2]
    )
Beispiel #4
0
def test_partial(polar):
    polar.load_str("f(1);")
    polar.load_str("f(x) if x = 1 and x = 2;")

    results = polar.query_rule("f", Variable("x"), accept_expression=True)
    first = next(results)

    x = first["bindings"]["x"]
    assert x == 1

    with pytest.raises(StopIteration):
        next(results)

    polar.load_str("g(x) if x.bar = 1 and x.baz = 2;")

    results = polar.query_rule("g", Variable("x"), accept_expression=True)
    first = next(results)

    x = first["bindings"]["x"]
    and_args = unwrap_and(x)
    assert len(and_args) == 2
    assert and_args[0] == Expression(
        "Unify", [1, Expression("Dot", [Variable("_this"), "bar"])])
    assert and_args[1] == Expression(
        "Unify", [2, Expression("Dot", [Variable("_this"), "baz"])])
Beispiel #5
0
def test_partial_unification(polar):
    polar.load_str("f(x, y) if x = y;")
    x = Variable("x")
    y = Variable("y")
    results = polar.query_rule("f", x, y)
    result = next(results)["bindings"]
    assert result["x"] == y
    assert result["y"] == x
    with pytest.raises(StopIteration):
        next(results)
Beispiel #6
0
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)
Beispiel #7
0
def test_datetime(polar, query):
    # test datetime comparison
    t1 = datetime(2020, 5, 25)
    t2 = datetime.now()
    t3 = datetime(2030, 5, 25)
    t4 = datetime(2020, 5, 26)

    polar.load_str("lt(a, b) if a < b;")
    assert query(Predicate("lt", [t1, t2]))
    assert not query(Predicate("lt", [t2, t1]))

    # test creating datetime from polar
    polar.load_str("dt(x) if x = new Datetime(year: 2020, month: 5, day: 25);")
    assert query(Predicate("dt", [Variable("x")])) == [{
        "x":
        datetime(2020, 5, 25)
    }]
    polar.load_str("ltnow(x) if x < Datetime.now();")
    assert query(Predicate("ltnow", [t1]))
    assert not query(Predicate("ltnow", [t3]))

    polar.load_str(
        "timedelta(a: Datetime, b: Datetime) if a.__sub__(b) == new Timedelta(days: 1);"
    )
    assert query(Predicate("timedelta", [t4, t1]))
Beispiel #8
0
def test_partial_rule_filtering(polar):
    class A:
        def __init__(self):
            self.c = C()

    class B:
        pass

    class C:
        pass

    class D:
        pass

    polar.register_class(A)
    polar.register_class(B)
    polar.register_class(C)
    polar.register_class(D)

    polar.load_str("""f(x: A) if g(x.c);
           g(_: B);
           g(_: C);
           g(_: D);""")

    x = Variable("x")
    with pytest.raises(exceptions.PolarRuntimeError) as e:
        next(polar.query_rule("f", x, bindings={x: TypeConstraint(x, "A")}))
    assert str(e.value) == "Cannot generically walk fields of a Python class"
Beispiel #9
0
def test_dot_path():
    non_dot = Expression("And", [])
    assert dot_path(non_dot) == ()

    this = Variable("_this")
    assert dot_path(this) == (this, )

    var = Variable("x")
    assert dot_path(var) == (var, )

    single_dot = Expression("Dot", [this, "created_by"])
    assert dot_path(single_dot) == (this, "created_by")

    double_dot = Expression("Dot", [single_dot, "username"])
    assert dot_path(double_dot) == (this, "created_by", "username")

    triple_dot = Expression("Dot", [double_dot, "first"])
    assert dot_path(triple_dot) == (this, "created_by", "username", "first")
Beispiel #10
0
def test_dot_path():
    single = Expression("Dot", [Variable("_this"), "created_by"])
    assert dot_path(single) == ("created_by", )

    double = Expression("Dot", [single, "username"])
    assert dot_path(double) == ("created_by", "username")

    triple = Expression("Dot", [double, "first"])
    assert dot_path(triple) == ("created_by", "username", "first")
Beispiel #11
0
def test_query(load_file, polar, query):
    """Test that queries work with variable arguments"""

    load_file(Path(__file__).parent / "test_file.polar")
    # plaintext polar query: query("f(x)") == [{"x": 1}, {"x": 2}, {"x": 3}]

    assert query(Predicate(name="f", args=[Variable("a")])) == [
        {"a": 1},
        {"a": 2},
        {"a": 3},
    ]
Beispiel #12
0
def test_partial_unification():
    Oso.load_str("f(x, y) if x = y and x = 1;")
    results = Oso.query_rule("f",
                             Variable("x"),
                             Variable("y"),
                             accept_expression=True)
    first = next(results)["bindings"]
    assert first["x"] == 1
    assert first["y"] == 1

    with pytest.raises(StopIteration):
        next(results)

    Oso.load_str("g(x, y) if x = y and y > 1;")
    results = Oso.query_rule("g",
                             Variable("x"),
                             Variable("y"),
                             accept_expression=True)
    first = next(results)["bindings"]

    # TODO not ideal that these are swapped in order (y = x) not (x = y).
    # this is a hard case, we want the (y > 1) to be this in both cases AND keep the x = y.
    assert first["x"] == Expression(
        "And",
        [
            Expression("Unify",
                       [Variable("y"), Variable("_this")]),
            Expression("Gt", [Variable("y"), 1]),
        ],
    )
    assert first["y"] == Expression(
        "And",
        [
            Expression("Unify",
                       [Variable("_this"), Variable("x")]),
            Expression("Gt", [Variable("_this"), 1]),
        ],
    )
Beispiel #13
0
    def get_allowed_actions(self,
                            actor,
                            resource,
                            allow_wildcard=False) -> list:
        """Determine the actions ``actor`` is allowed to take on ``resource``.

        Collects all actions allowed by allow rules in the Polar policy for the
        given combination of actor and resource.

        :param actor: The actor for whom to collect allowed actions

        :param resource: The resource being accessed

        :param allow_wildcard: Flag to determine behavior if the policy \
        includes a wildcard action. E.g., a rule allowing any action: \
        ``allow(_actor, _action, _resource)``. If ``True``, the method will \
        return ``["*"]``, if ``False``, the method will raise an exception.

        :type allow_wildcard: bool

        :return: A list of the unique allowed actions.
        """
        results = self.query_rule("allow", actor, Variable("action"), resource)
        actions = set()
        for result in results:
            action = result.get("bindings").get("action")
            if isinstance(action, Variable):
                if not allow_wildcard:
                    raise exceptions.OsoError(
                        """The result of get_allowed_actions() contained an
                        "unconstrained" action that could represent any
                        action, but allow_wildcard was set to False. To fix,
                        set allow_wildcard to True and compare with the "*"
                        string.""")
                else:
                    return ["*"]
            actions.add(action)

        return list(actions)
Beispiel #14
0
def test_partial_unification():
    Oso.load_str("f(x, y) if x = y and x = 1;")
    results = Oso.query_rule("f",
                             Variable("x"),
                             Variable("y"),
                             accept_expression=True)
    first = next(results)["bindings"]
    assert first["x"] == 1
    assert first["y"] == 1

    with pytest.raises(StopIteration):
        next(results)

    Oso.load_str("g(x, y) if x = y and y > 1;")
    results = Oso.query_rule("g",
                             Variable("x"),
                             Variable("y"),
                             accept_expression=True)
    first = next(results)["bindings"]
    assert first["x"] == Expression("And",
                                    [Expression("Gt", [Variable("_this"), 1])])
    assert first["y"] == Expression("And",
                                    [Expression("Gt", [Variable("_this"), 1])])
Beispiel #15
0
def authorize_model(oso: Oso, actor, action, session: Session, model):
    """Return SQLAlchemy expression that applies the policy to ``model``.

    Executing this query will return only authorized objects. If the request is
    not authorized, a query that always contains no result will be returned.

    :param oso: The oso class to use for evaluating the policy.
    :param actor: The actor to authorize.
    :param action: The action to authorize.

    :param session: The SQLAlchemy session.
    :param model: The model to authorize, must be a SQLAlchemy model or alias.
    """
    def get_field_type(model, field):
        try:
            field = getattr(model, field)
        except AttributeError:
            raise PolarRuntimeError(f"Cannot get property {field} on {model}.")

        try:
            return field.entity.class_
        except AttributeError as e:
            raise PolarRuntimeError(
                f"Cannot determine type of {field} on {model}.") from e

    oso.host.get_field = get_field_type

    try:
        mapped_class = inspect(model, raiseerr=True).class_
    except AttributeError:
        raise TypeError(f"Expected a model; received: {model}")

    resource = Variable("resource")
    constraint = TypeConstraint(resource, polar_model_name(mapped_class))
    results = oso.query_rule(
        "allow",
        actor,
        action,
        resource,
        bindings={resource: constraint},
        accept_expression=True,
    )

    combined_filter = None
    has_result = False
    for result in results:
        has_result = True

        resource_partial = result["bindings"]["resource"]
        filter = partial_to_filter(resource_partial,
                                   session,
                                   model,
                                   get_model=oso.get_class)
        if combined_filter is None:
            combined_filter = filter
        else:
            combined_filter = combined_filter | filter

    if not has_result:
        return sql.false()

    return combined_filter
Beispiel #16
0
def test_unexpected_expression(polar):
    """Ensure expression type raises error from core."""
    polar.load_str("f(x) if x > 2;")

    with pytest.raises(UnexpectedPolarTypeError):
        next(polar.query_rule("f", Variable("x")))