Working with Rust Types

Oso’s Rust authorization library allows you to write policy rules over Rust types directly. This document explains how different Rust types can be used in Oso policies.

Note

More detailed examples of working with application objects can be found in our Guides.

Structs + Enums

Rust structs and enums can be registered with Oso, which lets you pass them in and access their methods and fields in your policy (see Application Types).

Rust structs can also be constructed from inside an Oso policy using the new operator if a type constructor is provided at registration.

Numbers and Booleans

Polar supports integer and floating point real numbers, as well as booleans (see Primitive Types).

Strings

Rust Strings are mapped to Polar strings. Many of Rust’s String methods may be called in policies:

allow(actor, _action, _resource) if actor.username.ends_with("example.com");
#[derive(Clone, PolarClass)]
struct User {
  #[polar(attribute)]
  pub username: String
}

oso.register_class(User::get_polar_class())?;

let user = User{username: "alice@example.com".to_owned()};
assert!(oso.is_allowed(user, "foo", "bar")?);
Warning

Polar does not support methods that mutate strings in place.

Vec

Vec<T> maps to a Polar list, given that T: ToPolar.

Implementations also exist to convert LinkedList, VecDeque, BinaryHeap, HashSet, and BTreeSetto and from Polar lists, but lists are treated as Vec<T> when calling methods.

Currently, no methods on Vec are exposed to Polar.

allow(actor, _action, _resource) if "HR" in actor.groups;
#[derive(Clone, PolarClass)]
struct User {
    #[polar(attribute)]
    pub groups: Vec<String>,
}

oso.register_class(User::get_polar_class())?;

let user = User { groups: vec!["HR".to_string(), "payroll".to_string()] };
assert!(oso.is_allowed(user, "foo", "bar")?);
Warning

Polar does not support methods that mutate lists in place unless the list is also returned from the method.

Rust methods like Vec::get may be used for random access to list elements, but there is currently no Polar syntax that is equivalent to the Rust expression user.groups[1]. To access the elements of a list without using a method, you may iterate over it with the in operator or destructure it with pattern matching.

HashMap

A Rust HashMap maps to a Polar dictionary but requires that the HashMap key is a String.

Implementations also exist to convert BTreeMaps to and from Polar dictionaries, but dictionaries are treated as HashMap when calling methods.

allow(actor, _action, _resource) if actor.roles.project1 = "admin";
#[derive(Clone, PolarClass)]
struct User {
    #[polar(attribute)]
    pub roles: HashMap<String, String>,
}

oso.register_class(User::get_polar_class())?;

let user = User { roles: maplit::hashmap!{
    "project1".to_string() => "admin".to_string()
}};
assert!(oso.is_allowed(user, "foo", "bar")?);

Likewise, dictionaries constructed in Polar may be passed into Rust methods.

Iterators

You may iterate over a Rust iterator using Polar’s in operator:

allow(actor, _action, _resource) if "payroll" in actor.get_groups();
#[derive(Clone, PolarClass)]
struct User {
    groups: Vec<String>,
}

oso.register_class(
    User::get_polar_class_builder()
        .add_iterator_method("get_groups", |u: &User| u.groups.clone().into_iter())
        .build(),
)
.unwrap();

let user = User {
    groups: vec!["HR".to_string(), "payroll".to_string()],
};
assert!(oso.is_allowed(user, "foo", "bar")?);

Options

The Rust type Option<T> is registered as a class. You can use unwrap() on an option in a policy, but it’s safer to use the in operator, which will return 0 or 1 values depending on whether the value is None or Some(T) respectively.

The Option variant None is registered as the Polar constant nil. If a Rust method can return None, you may want to compare the result to nil:

allow(actor, _action, _resource) if
    "Jimmy" in actor.nickname or
    actor.get_optional() != nil;
#[derive(Clone, PolarClass)]
struct User {
    #[polar(attribute)]
    nickname: Option<String>,
}

oso.register_class(
    User::get_polar_class_builder()
        .add_method("get_optional", |u: &User| None)
        .build(),
)
.unwrap();

let user = User { nickname: Some("Jimmy".to_string()), };
assert!(oso.is_allowed(user, "foo", "bar")?);

UUIDs via the uuid crate

Oso supports UUIDs via the uuid crate behind a feature flag. To enable support, you’ll need to add a feature flag to your Cargo.toml file and make sure you have the uuid crate as a separate dependency. In Cargo.toml, an Oso dependency that supports UUIDs looks as follows:

oso = { version = "X.Y.Z", features = ["uuid-10"] }

Note that the numbers in the feature flags do not refer to the UUID version but to the version of the uuid crate. Most people will want the uuid-10 feature flag, as it supports recent versions of the uuid crate.

uuid Crate Version Feature Flag
0.6.5 - 0.6.x uuid-06
0.7.0 - 0.8.x uuid-07
1.0.0 - 2.0.0 uuid-10

Rust → Polar Types Summary

Rust type Polar type
i32, i64, usize Integer
f32, f64 Float
bool Boolean
String, &'static str, str String
HashMap, BTreeMap Dictionary
Vec, LinkedList, VecDeque BinaryHeap, HashSet, BTreeSet List
UUID (behind a feature flag) Uuid

Connect with us on Slack

If you have any questions, or just want to talk something through, jump into Slack. An Oso engineer or one of the thousands of developers in the growing community will be happy to help.