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)
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
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] )
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"])])
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)
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_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]))
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"
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")
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")
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}, ]
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]), ], )
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)
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])])
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
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")))