Application Types

Any type defined in an application can be passed into oso, and its attributes may be accessed from within a policy. Using application types make it possible to take advantage of an app’s existing domain model. For example:

policy.polar
allow(actor, action, resource) if actor.is_admin;

The above rule expects the actor variable to be a Python instance with the attribute is_admin. The Python instance is passed into oso with a call to is_allowed():

app.py
class User:
    def __init__(self, name, is_admin):
        self.name = name
        self.is_admin = is_admin

user = User("alice", True)
assert(oso.is_allowed(user, "foo", "bar))

The code above provides a User object as the actor for our allow rule. Since User has an attribute called is_admin, it is evaluated by the policy and found to be true.

policy.polar
allow(actor, action, resource) if actor.is_admin;

The above rule expects the actor variable to be a Ruby instance with the attribute is_admin. The Ruby instance is passed into oso with a call to Oso#allowed?:

app.rb
class User
    attr_reader :name
    attr_reader :is_admin

    def initialize(name:, is_admin:)
        @name = name
        @is_admin = is_admin
    end
end

user = User.new(name: "alice", is_admin: true)
raise "should be allowed" unless OSO.allowed?(actor: user, action: "foo", resource: "bar")

The code above provides a User object as the actor for our allow rule. Since User has an attribute called is_admin, it is evaluated by the policy and found to be true.

policy.polar
allow(actor, action, resource) if actor.isAdmin;

The above rule expects the actor variable to be a Java instance with the field isAdmin. The Java instance is passed into oso with a call to Oso.isAllowed:

User.java
public class User {
    public boolean isAdmin;
    public String name;

    public User(String name, boolean isAdmin) {
        this.isAdmin = isAdmin;
        this.name = name;
    }

    public static void main(String[] args) {
        User user = new User("alice", true);
        assert oso.isAllowed(user, "foo", "bar");
    }
}

The code above provides a User object as the actor for our allow rule. Since User has a field called isAdmin, it is evaluated by the Polar rule and found to be true.

Note

You can also call methods on application instances in a policy. If the method takes arguments, the method must be called with ordered arguments, even if the method is defined to take keyword arguments.

Registering Application Types

Instances of application types can be constructed from inside an oso policy using the New operator if the class has been registered:

app.py
oso.register_class(User)

Once the class is registered, we can make a User object in Polar. This can be helpful for writing inline queries:

policy.polar
?= allow(new User{name: "alice", is_admin: true}, "foo", "bar");
app.rb
OSO.register_class(User)

Once the class is registered, we can make a User object in Polar. This can be helpful for writing inline queries:

policy.polar
?= allow(new User{name: "alice", is_admin: true}, "foo", "bar");
User.java
public static void main(String[] args) {
    oso.registerClass(User.class, (args) -> new User((String) args.get("name"), (boolean) args.get("isAdmin")), "User");
}

Once the class is registered, we can make a User object in Polar. This can be helpful for writing inline queries:

policy.polar
?= allow(new User{name: "alice", isAdmin: true}, "foo", "bar");

Registering classes also makes it possible to use Specialization and the Matches Operator with the registered class.

In our previous example, the allow rule expected the actor to be a User, but we couldn’t actually check that type assumption in the policy. If we register the User class, we can write the following rule:

policy.polar
allow(actor: User, action, resource) if actor.name = "alice";

This rule will only be evaluated when the actor is a User. We could also use matches to express the same logic:

policy.polar
allow(actor, action, resource) if actor matches User{name: "alice"};

We can then evaluate the rule:

app.py
oso.register_class(User)

user = User("alice", True)
assert oso.is_allowed(user, "foo", "bar")
assert not oso.is_allowed("notauser", "foo", "bar")

We can then evaluate the rule:

app.rb
OSO.register_class(User)
user = User.new(name: "alice", is_admin: true)
raise "should be allowed" unless OSO.allowed?(actor: user, action: "foo", resource: "bar")
raise "should not be allowed" unless not OSO.allowed?(actor: user, action: "foo", resource: "bar")

We can then evaluate the rule:

User.java
public static void main(String[] args) {
    oso.registerClass(User.class, (args) -> new User((String) args.get("name"), (boolean) args.get("isAdmin")), "User");

    User user = new User("alice", true);
    assert oso.isAllowed(user, "foo", "bar");
    assert !oso.isAllowed("notauser", "foo", "bar");
}

Note

Type specializers automatically respect the inheritance hierarchy of our application classes. See our Resources with Inheritance guide for an in-depth example of how this works.

Once a class is registered, its class methods can also be called from oso policies:

policy.polar
allow(actor: User, action, resource) if actor.name in User.superusers();
app.py
class User:
    ...
    @classmethod
    def superusers(cls):
        """ Class method to return list of superusers. """
        return ["alice", "bhavik", "clarice"]

oso.register_class(User)

user = User("alice", True)
assert(oso.is_allowed(user, "foo", "bar))
policy.polar
allow(actor: User, action, resource) if actor.name in User.superusers();
app.rb
class User
    # ...
    def self.superusers
        ["alice", "bhavik", "clarice"]
    end
end

OSO.register_class(User)

user = User.new(name: "alice", is_admin: true)
raise "should be allowed" unless OSO.allowed?(actor: user, action: "foo", resource: "bar")
policy.polar
allow(actor: User, action, resource) if actor.name in User.superusers();
User.java
public static List<String> superusers() {
    return List.of("alice", "bhavik", "clarice");
}

public static void main(String[] args) {
    oso.registerClass(User.class, (args) -> new User((String) args.get("name"), (boolean) args.get("isAdmin")), "User");

    User user = new User("alice", true);
    assert oso.isAllowed(user, "foo", "bar");
}

Built-in Types

Methods called on Polar built-ins (str, dict, number & list) call methods on the corresponding language type. That way you can use familiar methods like str.startswith() on strings regardless of whether they originated in your application or as a literal in your policy. This applies to all of the Polar supported types: strings, lists, dictionaries, and numbers, in any supported application language. For examples using built-in types, see the Libraries guides.

Warning

Do not attempt to mutate a literal using a method on it. Literals in Polar are constant, and any changes made to such objects by calling a method will not be persisted.

Summary

  • Application types and their associated application data are available within policies.

  • Types can be registered with oso, in order to:
    • Create instances of application types in policies

    • Leverage the inheritance structure of application types with specialized rules, supporting more sophisticated access control models.

  • You can use built-in methods on primitive types & literals like strings and dictionaries, exactly as if they were application types.

What’s next

  • Explore how to implement common authorization models in oso, like role-based and attribute-based access control: Policy Examples.

  • Learn more about using application types with your language in: Libraries.