Local Development Workflow

Local Development Workflow

This walkthrough assumes that you're familiar with the basics of Polar, facts, and policy tests. If you aren't, check out the Introduction to Polar and Polar: Testing and Policy Iteration pages.

If you've installed the Oso Cloud developer tools, then you can set up a local development workflow for your authorization code. By eliminating dependencies on remote services, you can create tighter feedback loops that let you detect many issues more quickly. There are a lot of ways to set up a local development workflow, but the general process looks like this:

  1. Make a change to your policy
  2. Validate the policy syntax
  3. Add policy tests
  4. Run the policy tests against the Oso Dev Server
  5. Push the updated policy to the Oso Dev Server
  6. Test application code against the updated policy

We'll illustrate the process with a simple example. All code is embedded in the document, so you can download it and follow along.

Starter policy

For this illustration, we'll use VS Code and the starter policy below. If you don't use VS Code, that's fine - just skip the parts that refer to it. It's included here to point out the features of the extension.

policy.polar

actor User {}
resource Organization {
roles = ["member"];
permissions = ["view"];
"view" if "member";
}
test "example test" {
setup {
has_role(User{"alice"}, "member", Organization{"org1"});
}
assert allow(User{"alice"}, "view", Organization{"org1"});
}

This policy defines a single actor, User, and a single resource, Organization. It states that a User has the view permission on an Organization if they have the member role on that Organization. Pretty basic, but it's all we need to get started. Let's do that.

Open the policy in an editor. If you're using VS Code, it will look something like this:

Polar policy in VS Code

Note the "Run Test" link over the test definition in line 10. You can click that to run the test and see the result in the editor window.

Initial test passing in VS Code

The success indicator is subtle - it's those 3 dots under the name of the rule that succeeded.

Let's add another resource to the policy.

Make a change to the policy

An Organization contains one or more Projects. Add a Project resource to the policy so you can model its authorization logic.

policy.polar

actor User {}
resource Organization {
roles = ["member"];
permissions = ["view"];
"view" if "member";
}
resource Project {
roles = ["member"];
permissions = ["view"];
relations = {
organization: Organization
};
"member" if "member" on "organization";
"view" if "member";
}
test "example test" {
setup {
has_role(User{"alice"}, "member", Organization{"org1"});
}
assert allow(User{"alice"}, "view", Organization{"org1"});
}

The policy now defines a Project resource and states that a User can inherit the member role on a Project from its parent Organization. Next, you'll validate the policy syntax.

Validate the policy syntax

From VS Code

Syntax errors are indicated in the VSCode editor. For example, if you forget a trailing semicolon, you'll see something like this:

Syntax error in VS Code editor

The filename is red in the tab and the word relations on line 13 is underlined in red. If you hover over relations, you'll see a description of the issue.

Syntax error in VS Code editor with explanation

Once you've fixed the problem, the VS Code editor window returns to normal.

Fixed syntax in VS Code

Using oso-cloud validate

You can also use the oso-cloud validate CLI command to validate the syntax from the command line.


❯ oso-cloud validate policy.polar
Policy failed validation with 1 errors:
Policy failed validation due to parser error: did not expect to find the token 'relations' at line 13, column 3 of file policy.polar:
013: relations = {
^

Once you've fixed the problem, oso-cloud validate passes.


❯ oso-cloud validate policy.polar
Policy validated successfully.

Add policy tests

Next, you'll add tests to confirm that the authorization logic for Projects is correct. The starter policy has a test that validates the Organization role logic.


test "example test" {
setup {
has_role(User{"alice"}, "member", Organization{"org1"});
}
assert allow(User{"alice"}, "view", Organization{"org1"});
}

Now that there's a Project resource, you should add tests to confirm the Project authorization logic. You can create new tests or extend the existing one. For simplicity, we'll extend the existing test.


test "example test" {
setup {
# Project "project1" belongs to Organization "org1"
has_relation(Project{"project1"}, "organization", Organization{"org1"});
# User "alice" has the "member" role on Organization "org1"
has_role(User{"alice"}, "member", Organization{"org1"});
# User "bob" has the "member" role on Project "project1"
has_role(User{"bob"}, "member", Project{"project1"});
}
# Alice can view the "org1" Organization
assert allow(User{"alice"}, "view", Organization{"org1"});
# Alice can view the "project1" Project via inheritance from the "org1" Organization
assert allow(User{"alice"}, "view", Project{"project1"});
# Bob can not view the "org1" Organization
assert_not allow(User{"bob"}, "view", Organization{"org1"});
# Bob can view the "project1" Project via direct assignment
assert allow(User{"bob"}, "view", Project{"project1"});
# Charlie can view neither the "org1" Organization nor the "project1" Project
assert_not allow(User{"charlie"}, "view", Organization{"org1"});
assert_not allow(User{"charlie"}, "view", Project{"project1"});
}

Now the test covers the positive and negative cases of all authorization paths.

Run the policy tests against the Oso Dev Server

Next, you'll run the policy tests against the Oso Dev Server running on your local machine.

Start the Oso Dev Server

First, make sure the Oso Dev Server is running. The filename is standalone, and you can start it from wherever you saved it.


❯ ~/.local/bin/standalone
Server is in test mode, use token 'e_0123456789_12345_osotesttoken01xiIn'

The server runs in the foreground and listens on port 8080. On startup, it writes the API key to stdout. You can interact with it from the oso-cloud CLI by setting the OSO_URL and OSO_AUTH environment variables as follows:


❯ export OSO_URL='http://localhost:3000'
❯ export OSO_AUTH='e_0123456789_12345_osotesttoken01xiIn'

You can either export them to set them for the session, or prepend them to each oso-cloud command.

Run the policy tests

Run the policy tests against the Oso Dev Server by issuing the oso-cloud test command:


❯ oso-cloud test policy.polar
Loading policy from files:
policy.polar
Ran 1 test:
example test: 6/6 PASS
PASS

If your policy is split into multiple .polar files, you must pass all of them to oso-cloud test.

Once the tests pass, you can push the updated policy to the Oso Dev Server and test your application code against it.

Push the updated policy to the Oso Dev Server

Push the updated policy to the local binary with the oso-cloud policy command:

If your policy is split into multiple .polar files, you must push all of them to the Oso Dev Server on any update.


❯ oso-cloud policy policy.polar
Loading policy from files:
policy.polar
Policy successfully loaded.

Verify the policy (optional)

You can verify that the correct policy is loaded by issuing the oso-cloud policy command without an argument.


❯ oso-cloud policy

The currently loaded policy will be written to stdout.

Click to expand policy

== Policy: policy.polar ==
actor User {}
resource Organization {
roles = ["member"];
permissions = ["view"];
"view" if "member";
}
resource Project {
roles = ["member"];
permissions = ["view"];
relations = {
organization: Organization
};
"member" if "member" on "organization";
"view" if "member";
}
test "example test" {
setup {
# Project "project1" belongs to Organization "org1"
has_relation(Project{"project1"}, "organization", Organization{"org1"});
# User "alice" has the "member" role on Organization "org1"
has_role(User{"alice"}, "member", Organization{"org1"});
# User "bob" has the "member" role on Project "project1"
has_role(User{"bob"}, "member", Project{"project1"});
}
# Alice can view the "org1" Organization
assert allow(User{"alice"}, "view", Organization{"org1"});
# Alice can view the "project1" Project via inheritance from the "org1" Organization
assert allow(User{"alice"}, "view", Project{"project1"});
# Bob can not view the "org1" Organization
assert_not allow(User{"bob"}, "view", Organization{"org1"});
# Bob can view the "project1" Project via direct assignment
assert allow(User{"bob"}, "view", Project{"project1"});
# Charlie can view neither the "org1" Organization nor the "project1" Project
assert_not allow(User{"charlie"}, "view", Organization{"org1"});
assert_not allow(User{"charlie"}, "view", Project{"project1"});
}

You can also start the Oso Dev Server with a list of policy files and the --watch-for-changes flag to instruct it to automatically reload your policy files and rerun tests any time they change. See the docs for more details.

Now you're ready to test your application locally against the updated policy.

Test application code against the Oso Dev Server

Local integration tests and functional tests should run against the local Oso Dev Server after you've updated the policy.

Clear old fact data

To ensure that the Oso Dev Server is in a known state before running local integration tests, you can delete all data by either:

⚠️

Make sure that you run these commands against your local Oso Dev Server instance and not the live Oso Cloud environment.

Re-seed initial state

After deleting old data from the Oso Dev Server, you can re-seed it with any initial state data that isn't recreated by your integration tests. For example, if you have a role table that contains pre-seeded roles, you should add facts for those roles to the Oso Dev Server before starting your tests. But if your tests add new roles, you should NOT pre-seed the Oso Dev Server with facts for those roles. Instead, you should let your application code do that as part of the tests.

💡

Your integration tests should exercise all of your code paths, including the ones that add data to Oso Cloud. If you preseed facts for data that is added by your tests, then you won't be able to catch bugs in the code that adds those facts.

However, if your testing database has an initial state, you should add facts to the Oso Dev Server to make sure it's in the same initial state. Otherwise tests that rely on a consistent initial state will fail

We recommend defining the initial state in a script that issues oso-cloud tell commands to the Oso Dev Server for all facts that need to be present before the integration test suite runs.

Initialize the SDK against the Oso Dev Server

To use the Oso Dev Server from your code, you just need to initialize the client in your application against it. Refer to the documentation for the SDK you're using for details. For example, you'd initialize the Node.js client like this:


const { Oso } = require("oso-cloud");
const oso = new Oso(
"http://localhost:8080",
"e_0123456789_12345_osotesttoken01xiIn"
);

Conclusion

With the Oso Cloud developer tools, you can build a local development workflow that lets you iterate quickly on authorization code. By following these steps ...

  1. Make a change to your policy
  2. Validate the policy syntax
  3. Add policy tests
  4. Run the policy tests against the Oso Dev Server
  5. Push the updated policy to the Oso Dev Server
  6. Test application code against the updated policy

... you can make a series of small authorization changes and validate them on your local system before you commit anything to version control or introduce dependencies on remote services. This will make you and your team more effective - you because you're developing more quickly and your team because you're less likely to push broken code to a shared environment.

Once you've validated your changes locally and are ready to commit, you're ready to start moving your code to production. Read the CI/CD doc to learn how to set up a deployment pipeline that works with Oso Cloud.

Read on for detailed configuration options for the Oso Dev Server, which you can use to refine this workflow to meet your particular needs.