Java Client API

ℹ️

For changes to the Java Client API, see Changelog.

See the Oso Cloud Java client's Maven Central page (opens in a new tab) for instructions on how to add it to your project.

Before going through this guide, make sure you follow the Oso Cloud Quickstart to get your Oso Cloud API Key properly set in your environment.

Instantiating an Oso Cloud client

The Oso Cloud client provides an Oso class that takes your API key:


import com.osohq.oso_cloud.Oso;
...
Oso oso = new Oso(YOUR_API_KEY);
// Later:
oso.tell("has_role", user, role, resource);
// Wherever authorization needs to be performed:
boolean allowed = oso.authorize(user, action, resource);
if (allowed) {
// Action is allowed.
}

ℹ️

You should instantiate one client and share it across your application. Under the hood, it reuses connections to avoid paying the cost of negotiating a new connection on every request.

Specifying an Oso Fallback host

If you have deployed Oso Fallback nodes to your infrastructure, you may specify the host when instantiating the Oso Cloud client.


// Assumes Oso Fallback is hosted at http://localhost:8080
Oso oso = new Oso(YOUR_API_KEY, null, URI.create("http://localhost:8080"));

Passing application entities into the client

Under the hood, Oso Cloud represents an entity in your application as a combination of a type and an ID, which together uniquely identify the entity. The Java client represents these entities as Value objects with both type and id fields. For example:


import com.osohq.oso_cloud.api.Value;
...
Value alice = new Value("User", "alice");
Value anvilsRepository = new Value("Repository", "anvils");

You will pass objects like these into nearly every method call you make to the Java client.

Centralized Authorization Data API

Oso Cloud clients provide an API to manage authorization data stored directly in Oso Cloud.

Add fact: oso.tell(name, ...args)

Adds a fact named name with the provided arguments. Example:


oso.tell(
"has_role",
new Value("User", "bob"),
new Value("owner"),
new Value("Organization", "acme")
);

Add many facts: oso.bulkTell(facts)

ℹ️

For Oso Cloud developer accounts, bulkTell, bulkDelete, and bulk calls are limited to 20 facts. If you attempt to send more than 20 facts, the API will return an error.

ℹ️

The bulk commands have a 10MB body limit, an exception will be raised for exceeding this limit.

Adds many facts at once. Example:


oso.bulkTell(Arrays.asList(
new Fact(
"has_role",
new Value("User", "bob"),
new Value("owner"),
new Value("Organization", "acme")
),
new Fact(
"has_role",
new Value("User", "bob"),
new Value("maintainer"),
new Value("Repository", "anvil")
)
));

Delete fact: oso.delete(name, ...args)

Deletes a fact. Does not throw an error if the fact is not found. Example:


oso.delete(
"has_role",
new Value("User", "bob"),
new Value("maintainer"),
new Value("Repository", "anvil")
);

Delete many facts: oso.bulkDelete(facts)

Deletes many facts at once. Does not throw an error when some of the facts are not found. Example:


oso.bulkDelete(Arrays.asList(
new Fact(
"has_role",
new Value("User", "bob"),
new Value("owner"),
new Value("Organization", "acme")
),
new Fact(
"has_role",
new Value("User", "bob"),
new Value("maintainer"),
new Value("Repository", "anvil")
)
));

Transactionally delete and add facts: oso.bulk(delete, tell)

Deletes and adds many facts in one atomic transaction. The deletions are performed before the adds. Does not throw an error when the facts to delete are not found. Example:


oso.bulk(
Arrays.asList(
new Fact(
"has_role",
new Value("User", "bob"),
new Value("viewer"),
new Value("Repository", "anvil")
)
),
Arrays.asList(
new Fact(
"has_role",
new Value("User", "bob"),
new Value("maintainer"),
new Value("Repository", "anvil")
)
)
)

List facts: oso.get(name, ...args)

ℹ️

For Oso Cloud developer accounts, Get calls are limited to 1000 results. If you have more than 1000 facts, the function will return an error.

Lists facts that are stored in Oso Cloud. Can be used to check the existence of a particular fact, or used to fetch all facts that have a particular argument:


// Get one fact:
oso.get(
"has_role",
new Value("User", "bob"),
new Value("admin"),
new Value("Repository", "anvils")
);
// List all roles on the `anvils` repo
oso.get(
"has_role",
new Value("User", null),
null,
new Value("Repository", "anvils")
);

Note that null fields for type or id behave like a wildcard: the above means "find all has_role facts where the first argument is a User and the Repository{"anvils"} is the third argument.".

Check API

ℹ️

For Oso Cloud developer accounts, the number of context facts per request is limited to 20; and the number of records returned is limited to 1000.

Context facts

The Check API lets you provide context facts with each request. When Oso Cloud performs a check, it considers the request's context facts in addition to any other centralized authorization data. Context facts are only used in the API call in which they're provided-- they do not persist across requests.

For more details, see Context Facts.

Check a permission: oso.authorize(actor, action, resource)

Determines whether or not an action is allowed, based on a combination of authorization data and policy logic. Example:


boolean allowed = oso.authorize(user, "read", anvilsRepository);
if (allowed) {
// Action is allowed.
}

You may provide a list of context facts as a fourth argument to this method. Example:


boolean allowed = oso.authorize(user, "read", issueOnAnvilsRepository, Arrays.asList(
// a context fact
new Fact("has_relation", issueOnAnvilsRepository, new Value("parent"), anvilsRepository)
));

Check authorized resources: oso.authorizeResources(actor, action, resources)

Returns a subset of resources on which an actor can perform a particular action. Ordering and duplicates, if any exist, are preserved.

ℹ️

For Oso Cloud developer accounts, the number of input resources is limited to 1000.

Example:


Value[] results = oso.authorizeResources(user, "read", Arrays.asList(anvilsRepository, acmeRepository));

You may provide a list of context facts as a fourth argument to this method. Example:


Value[] results = oso.authorizeResources(user, "read", Arrays.asList(issueOnAnvilsRepository, issueOnAcmeRepository),
// context facts
Arrays.asList(
new Fact("has_relation", issueOnAnvilsRepository, new Value("parent"), anvilsRepository),
new Fact("has_relation", issueOnAcmeRepository, new Value("parent"), acmeRepository)
)
);

List authorized resources: oso.list(actor, action, resourceType)

Fetches a list of resource IDs on which an actor can perform a particular action. Example:


String[] repositoryIds = oso.list(user, "read", "Repository");

You may provide a list of context facts as a fourth argument to this method. Example:


String[] repositoryIds = oso.list(user, "read", "Issue",
// context facts
Arrays.asList(
new Fact("has_relation", issueOnAnvilsRepository, new Value("parent"), anvilsRepository),
new Fact("has_relation", issueOnAcmeRepository, new Value("parent"), acmeRepository)
)
);

List authorized actions: oso.actions(actor, resource)

Fetches a list of actions which an actor can perform on a particular resource. Example:


String[] actions = oso.actions(user, anvilsRepository);

You may use provide a list of context facts as a third argument to this method. Example:


String[] actions = oso.actions(user, issueOnAnvilsRepository,
Arrays.asList(
// a context fact
new Fact("has_relation", issueOnAnvilsRepository, new Value("parent"), anvilsRepository)
)
);

Query for any rule: oso.buildQuery(predicate, ...args)

Query Oso Cloud for any predicate and any combination of concrete and wildcard arguments. Unlike oso.get, which only lists facts you've added, you can use oso.buildQuery to list derived information about any rule in your policy. Example:


import com.osohq.oso_cloud.api.TypedVar;
import com.osohq.oso_cloud.QueryBuilder.EvaluateArgs;
// ...
Value actor = new Value("User", "bob");
TypedVar repository = new TypedVar("Repository");
// Query for all the repos `User:bob` can `read`
List<String> results = oso.buildQuery("allow", actor, new Value("read"), repository)
.evaluate(EvaluateArgs.values(repository));
// => ["acme", "anvils"]

Query Builder API

The oso.buildQuery() API returns a builder-style API where you chain methods to construct a query and then execute it.

oso.buildQuery(predicate, ...args)

The oso.buildQuery() function takes the name of the rule you want to query and a list of arguments. The arguments can be concrete values (e.g., new Value("read") or new Value("User", "bob")) or type-constrained variables constructed via the TypedVar class:


Value actor = new Value("User", "bob");
TypedVar repository = new TypedVar("Repository");
// Query for all the repositories bob can read
QueryBuilder qb = oso.buildQuery("allow", actor, new Value("read"), repository);
// => QueryBuilder { ... }

Note: Once you've finished building up your query, you must call evaluate to run it and get the results.

QueryBuilder.and(predicate, ...args)

This function adds another condition that must be true of the query results.

For example:


Value actor = new Value("User", "bob");
TypedVar repository = new TypedVar("Repository");
Value folder = new Value("Folder", "folder-1");
// Query for all the repositories this user can read...
QueryBuilder qb = oso
.buildQuery("allow", actor, new Value("read"), repository)
// ...and require the repositories to belong to the given folder.
.and("has_relation", repository, new Value("folder"), folder);
// => QueryBuilder { ... }

Note: Once you've finished building up your query, you must call evaluate to run it and get the results.

QueryBuilder.in(variable, values)

This function requires a given TypedVar query variable to be included in a given set of values. You can only call in once per variable per query. Calling in a second time with the same variable on the same query builder will throw an error.

For example:


Value actor = new Value("User", "bob");
List<String> repositories = Arrays.asList("acme", "anvil");
TypedVar action = new TypedVar("String");
TypedVar repository = new TypedVar("Repository");
// Query for all the actions this user can perform on any repository...
QueryBuilder qb = oso
.buildQuery("allow", actor, action, repository)
// ...given that the repository's ID is in the given list of IDs.
.in(repository, repositories);
// => QueryBuilder { ... }

Note: Once you've finished building up your query, you must call evaluate to run it and get the results.

QueryBuilder.withContextFacts(contextFacts)

This function adds the given context facts to the query. For example:


Value actor = new Value("User", "bob");
TypedVar repository = new TypedVar("Repository");
// Query for all the repositories bob can read...
QueryBuilder qb = oso
.buildQuery("allow", actor, new Value("read"), repository)
// ...while including the fact that bob owns acme
.withContextFacts(Arrays.asList(
new Fact("has_role", actor, new Value("owner"), new Value("Repository", "acme"))
));
// => QueryBuilder { ... }

For more information on context facts, see this section.

Note: Once you've finished building up your query, you must call evaluate to run it and get the results.

QueryBuilder.evaluate(EvaluateArg)

This function evaluates the built query, fetching the results from Oso Cloud. The shape of the return value is determined by the EvaluateArg you provide. Use the static factory methods in EvaluateArgs to construct the desired argument structure.

  • EvaluateArgs.exists(): Returns a Boolean indicating if the query is satisfiable.


    Boolean allowed = oso
    .buildQuery("allow", actor, action, resource)
    .evaluate(EvaluateArgs.exists());
    // => true if the given actor can perform the given action on the given resource

  • EvaluateArgs.values(TypedVar variable): Returns a List<String> of values for that variable.


    TypedVar action = new TypedVar("String");
    List<String> actions = oso
    .buildQuery("allow", actor, action, resource)
    .evaluate(EvaluateArgs.values(action));
    // => all the actions the actor can perform on the given resource- eg. ["read", "write"]

  • EvaluateArgs.map(TypedVar keyVariable, EvaluateArg valueArg): Returns a Map<String, SubT> where keys are unique values of the key variable, and values are the results of recursively evaluating the nested value argument (SubT). This allows arbitrarily nested structures.


    TypedVar action = new TypedVar("String");
    TypedVar repository = new TypedVar("Repository");
    Map<String, List<String>> repoToActions = oso
    .buildQuery("allow", actor, action, repository)
    .evaluate(EvaluateArgs.map(repository, EvaluateArgs.values(action)));
    // => a map of repo IDs to allowed actions-
    // eg. {"acme"=["read"], "anvil"=["read", "write"]}
    // Example of deeply nested map: User -> Repo -> List<Action>
    TypedVar user = new TypedVar("User");
    Map<String, Map<String, List<String>>> userRepoActions = oso
    .buildQuery("allow", user, action, repository)
    .evaluate(EvaluateArgs.map(user, EvaluateArgs.map(repository, EvaluateArgs.values(action))));
    // => {"alice"={"acme"=["read"]}, "bob"={"anvil"=["read", "write"]}}

Some queries have unconstrained results. For instance, maybe users with the admin role can read all Repository entities in your application. In this case, rather than returning a list containing the ID of every repository, evaluate will return a list containing the string "*". For example:


TypedVar repos = new TypedVar("Repository");
List<String> results = oso
.buildQuery("allow", new Value("User", "admin"), new Value("read"), repos)
.evaluate(EvaluateArgs.values(repos)); // Return just the IDs of the repos admin can read
// => ["*"] // admin can read anything

Query Builder examples

Field-level access control

Value actor = new Value("User", "alice");
Value resource = new Value("Repository", "anvil");
TypedVar field = new TypedVar("Field");
List<String> results = oso
.buildQuery("allow_field", actor, new Value("read"), resource, field)
.evaluate(EvaluateArgs.values(field));
// => Returns a list of the fields alice can read on the given repo- eg.
// ["name", "stars"]

Checking a global permission

Value actor = new Value("User", "alice");
Boolean result = oso
.buildQuery("has_permission", actor, new Value("create_repository"))
.evaluate(EvaluateArgs.exists());
// => true if alice has the global "create_repository" permission

Fetching authorized actions for a collection of resources

List<String> repos = Arrays.asList("anvil", "acme");
Value actor = new Value("User", "alice");
TypedVar action = new TypedVar("String");
TypedVar repo = new TypedVar("Repository");
Map<String, List<String>> results = oso
.buildQuery("allow", actor, action, repo)
.in(repo, repos)
.evaluate(EvaluateArgs.map(repo, EvaluateArgs.values(action)));
// => Returns a map of the given repos to the actions alice can perform on those repos- eg.
// {"anvil"=["read"], "acme"=["read", "write"]}

Filtering out unauthorized resources from a collection

List<String> repos = Arrays.asList("anvil", "acme");
Value actor = new Value("User", "bob");
TypedVar repo = new TypedVar("Repository");
List<String> results = oso
.buildQuery("allow", actor, new Value("read"), repo)
.in(repo, repos)
.evaluate(EvaluateArgs.values(repo));
// => Returns the subset of `repos` that bob can read- eg.
// ["anvil"]

Filtering an authorize() query based on a relation

Value actor = new Value("User", "bob");
TypedVar repo = new TypedVar("Repository");
Value org = new Value("Org", "coolguys");
List<String> results = oso
.buildQuery("allow", actor, new Value("read"), repo)
.and("has_relation", repo, new Value("parent"), org)
.evaluate(EvaluateArgs.values(repo));
// => Returns the IDs of the repos in the coolguys org that bob can read- eg.
// ["acme", "anvil"]

Learn more about how to query Oso Cloud.

Local Check API

The local check API lets you perform authorization using data that's across Oso Cloud and your own database.

After creating your Local Authorization configuration, provide the path to the YAML file that specifies how to resolve facts in your database.


Oso oso = new Oso(..., ..., ..., "path/to/local_authorization_config.yaml");

For more information, see Local Authorization.

List authorized resources with local data: oso.listLocal(actor, action, resourceType, column)

Fetches a filter that can be applied to a database query to return just the resources on which an actor can perform an action. Example with JPA:


List<Issue> authorizedIssues = entityManager.createNativeQuery(
"SELECT * FROM issues WHERE " + oso.listLocal(alice, "read", "Issue", "id"),
Issue.class
).getResultList();

You may provide a list of context facts as a fifth argument to this method. Example:


Fact context = new Fact("has_role", carol, new Value("member"), organization);
List<Issue> authorizedIssues = entityManager.createNativeQuery(
"SELECT * FROM issues WHERE " + oso.listLocal(carol, "read", "Issue", "id", Arrays.asList(context)),
Issue.class
).getResultList();

Check a permission with local data: oso.authorizeLocal(actor, action, resource)

Fetches a query that can be run against your database to determine whether an actor can perform an action on a resource. Example with JPA:


boolean allowed = (Boolean) entityManager.createNativeQuery(
oso.authorizeLocal(alice, "read", swageIssue)
).getSingleResult();
if (allowed) {
// Action is allowed.
}

You may provide a list of context facts as a fourth argument to this method. Example:


Fact context = new Fact("has_role", carol, new Value("member"), organization);
boolean allowed = (Boolean) entityManager.createNativeQuery(
oso.authorizeLocal(carol, "read", swageIssue, Arrays.asList(context))
).getSingleResult();
if (allowed) {
// Action is allowed.
}

List authorized actions with local data: oso.actionsLocal(actor, resource)

Fetches a query that can be run against your database to fetch the actions an actor can perform on a resource. Example with JPA:


List<String> actions = entityManager.createNativeQuery(
oso.actionsLocal(bob, swageIssue))
.getResultList();

You may provide a list of context facts as a third argument to this method. Example:


Fact context = new Fact("has_role", carol, new Value("member"), organization);
List<String> actions = entityManager.createNativeQuery(
oso.actionsLocal(carol, swageIssue, Arrays.asList(context)))
.getResultList();

Query for any rule with local data: QueryBuilder

You can use the Query Builder to construct SQL queries that you can run against your database to answer arbitrary questions about authorization.

See the Query Builder API documentation for information on how to construct queries. Once you've built your query, instead of calling evaluate (which would evaluate your query exclusively against data stored in Oso Cloud), call either evaluateLocalSelect or evaluateLocalFilter to construct a SQL query which you can run against your database.

Local Query API

QueryBuilder.evaluateLocalSelect(columnNamesToQueryVars)

Fetches a complete SQL query representing the given Query Builder query. (See the Query Builder API documentation for information on how to construct queries.)

The argument to this function is a map where keys are the desired column names that will appear in the SELECT clause of the SQL query, and values are the Query Builder variables whose values should be selected. For example, query.evaluateLocalSelect(Map.of("user_id", userVar)) will select all the authorized values of userVar (that is, all the values of userVar that satisfy the Query Builder query) into a column called "user_id".

If you pass in an empty map, the SQL query will return a single row selecting a boolean column called result. This column will be true when there's some combination of data in your database that can satisfy the given Query Builder query and false otherwise. Use evaluateLocalSelect() with no arguments for this behavior.

Note that each query variable can appear at most once in the columnNamesToQueryVars mapping. For example, query.evaluateLocalSelect(Map.of("user_id", userVar, "another_user_id", userVar)) will throw an IllegalArgumentException, because userVar appears twice in the mapping parameter.

Note that column names will be double-quoted and are thus case-sensitive in the generated SQL.

Limitations

evaluateLocalSelect has the following limitations:

  • Queries that would return a wildcard ("*") for one of the selected query variables are currently unsupported.

    Example:

    policy.polar

    has_permission(user: User, "read", _: Repo) if is_global_admin(user);


    // alice is a global admin, and can read *any* repo
    oso.tell("is_global_admin", new Value("User", "alice"));
    // UNSUPPORTED: Attempt to query for each authorized user / repo pair
    TypedVar userVar = new TypedVar("User");
    TypedVar repoVar = new TypedVar("Repo");
    try {
    String sql = oso
    .buildQuery("allow", userVar, new Value("read"), repoVar)
    .evaluateLocalSelect(Map.of("user_id", userVar, "repo_id", repoVar));
    // => throws ApiException, because alice can read any repo, so this query would return
    // a wildcard for the repos alice can read
    } catch (ApiException e) {
    // Handle error
    }

QueryBuilder.evaluateLocalFilter(columnName, queryVar)

Fetches a SQL fragment representing the given Query Builder query that you can embed in the WHERE clause of some other SQL query. Use this to filter out unauthorized results from your SQL query.

(See the Query Builder API documentation for information on how to construct queries.)

columnName is the name of the column you want to filter in your SQL query, and queryVar is the query variable to filter against. For example, query.evaluateLocalFilter("user_id", userVar) will return a SQL fragment of the form "user_id" IN (...), constraining the column "user_id" to the authorized values of userVar.

Note that the column name will be double-quoted and is thus case-sensitive in the generated SQL.

Local query examples

These examples all use JPA's createNativeQuery to execute the returned SQL queries, but you can use any database client you prefer.

Field-level access control

Value actor = new Value("User", "alice");
Value resource = new Value("Repository", "anvil");
TypedVar field = new TypedVar("Field");
String sqlQuery = oso
.buildQuery("allow_field", actor, new Value("read"), resource, field)
.evaluateLocalSelect(Map.of("field_name", field));
// => 'SELECT "field_name" FROM (... /* only the fields alice can read on anvil */)'
List<String> fields = entityManager.createNativeQuery(sqlQuery).getResultList();
// => ["name", "stars"]

Checking a global permission

Value actor = new Value("User", "alice");
String sqlQuery = oso
.buildQuery("has_permission", actor, new Value("create_repository"))
.evaluateLocalSelect(); // No args -> selects boolean 'result' column
// => 'SELECT EXISTS (... /* alice has the create_repository permission */) AS result'
Boolean hasPermission = (Boolean) entityManager.createNativeQuery(sqlQuery).getSingleResult();
// => true

Fetching authorized actions for a paginated collection of resources

Value actor = new Value("User", "alice");
TypedVar actionVar = new TypedVar("String");
TypedVar repoVar = new TypedVar("Repository");
String authorizedActionsAndReposSql = oso
.buildQuery("allow", actor, actionVar, repoVar)
.evaluateLocalSelect(Map.of("action", actionVar, "repo_id", repoVar));
// => 'SELECT "action", "repo_id" FROM (... /* pairs of alice's authorized actions / repos */)'
String finalSqlQuery = String.format(
"WITH authorized_actions AS (%s)\n" +
"SELECT r.id AS id, aa.action AS action FROM repos r\n" +
"INNER JOIN authorized_actions aa ON r.id = aa.repo_id\n" +
"ORDER BY r.id ASC LIMIT 10",
authorizedActionsAndReposSql
);
// Define a mapping for the result tuple
@SqlResultSetMapping(
name = "RepoActionMapping",
classes = @ConstructorResult(
targetClass = RepoActionPair.class,
columns = {
@ColumnResult(name = "id", type = String.class),
@ColumnResult(name = "action", type = String.class)
}
)
)
// Assuming RepoActionPair is a simple class with constructor (String id, String action) and getters
List<RepoActionPair> results = entityManager.createNativeQuery(finalSqlQuery, "RepoActionMapping").getResultList();
// => [RepoActionPair{id='1', action='read'}, RepoActionPair{id='1', action='write'}, ...]

Filtering an authorize() query based on a relation

Value actor = new Value("User", "bob");
TypedVar repo = new TypedVar("Repository");
Value org = new Value("Org", "coolguys");
String sqlQuery = oso
.buildQuery("allow", actor, new Value("read"), repo)
.and("has_relation", repo, new Value("parent"), org)
.evaluateLocalSelect(Map.of("repo_id", repo));
// => 'SELECT "repo_id" FROM (... /* only repos bob can read which belong to coolguys */)'
List<String> repoIds = entityManager.createNativeQuery(sqlQuery).getResultList();
// => ["acme", "anvil"]

Filtering on users who can read a certain resource

TypedVar actor = new TypedVar("User");
Value resource = new Value("Repository", "anvil");
String authorizedUserFragment = oso
.buildQuery("allow", actor, new Value("read"), resource)
.evaluateLocalFilter("user_id", actor); // Assuming user table has a "user_id" column
// => '"user_id" IN (... /* only users who can read anvil */)'
String sqlQuery = "SELECT email FROM users WHERE " + authorizedUserFragment;
List<String> emails = entityManager.createNativeQuery(sqlQuery).getResultList();
// => ["alice@example.com", "bob@example.com"]

Policy API

Update the active policy: oso.policy(policy)

Updates the policy in Oso Cloud. The string passed into this method should be written in Polar. Example:


oso.policy("actor User {}")

This command will run any tests defined in your policy. If one or more of these tests fail, an exception will be thrown and your policy will not be updated.

Get policy metadata: oso.policyMetadata()

Returns metadata about the currently active policy. Example:


PolicyMetadata metadata = oso.policyMetadata()
System.out.println(Arrays.toString(metadata.resources.keySet().toArray()));
// prints [Organization, Repository, User, global]
System.out.println(Arrays.toString(org.roles));
// returns [admin, member]

See the Policy Metadata guide for more information on use cases.

Talk to an Oso engineer

If you'd like to learn more about using Oso Cloud in your app or have any questions about this guide, schedule a 1x1 with an Oso engineer. We're happy to help.

Get started with Oso Cloud →