Skip to main content

Extract a Piece of Authorization Logic

Identify the logic to extract

First, select a piece of authorization logic to move to Oso Cloud. For your section of logic should be:
  • Well understood
  • Low-impact
  • Straightforward to extract
In this guide, the process of migration to local authorization is illustrated with code samples from a hypothetical version control application. You can follow along with those or you use examples from your code. One permission this version control application requires is the ability to read a repository. That permission affects two operations:
  1. read a single repository
  2. list all the repositories that the user can read
The logic resides in the corresponding get handlers:
// Return the requested repository if the user has permission to read it
accountsRouter.get(
  "/orgs/:orgId/repos/:repoId",
  withAuthn,
  async (req, res) => {
    const user = await currentUser(req);
    const orgRole = user.orgRoles.find(
      (orgRole) => orgRole.orgId == parseInt(req.params.orgId)
    );
    const repoRole = user.repoRoles.find(
      (repoRole) => repoRole.repoId == parseInt(req.params.repoId)
    );

    const repo: WithPermissions<Repository> | null =
      await req.prisma.repository.findUnique({
        where: {
          orgId: parseInt(req.params.orgId),
          id: parseInt(req.params.repoId),
        },
      });
    if (repo && (orgRole || repoRole)) {
      repo.permissions = ["read"];

        ...

    }
  }
);
// List all repos that the user can read in the specified org
accountsRouter.get("/orgs/:orgId/repos", withAuthn, async (req, res) => {
  let repos = await req.prisma.repository.findMany({
    where: { orgId: parseInt(req.params.orgId) },
  });
  const user = await currentUser(req);
  const memberOfOrg = user.orgRoles.some(
    (orgRole) => orgRole.orgId == parseInt(req.params.orgId),
  );
  repos = repos.filter((repo) => {
    return (
      memberOfOrg ||
      user.repoRoles.some((repoRole) => repoRole.repoId == repo.id)
    );
  });
  res.send(repos);
});
This is typical of authorization code:
  • The same logic is implemented in multiple places
  • The implementation slightly differs in each place
  • It is not obvious it is critical authorization logic
This is a good candidate for early refactoring:
  • The logic is straightforward - a user can read a repository if:
    • They have any role on the repository
    • They have any role on the repository’s parent organization
  • It only grants read access.
  • The logic is already encapsulated.

Extract the logic into a dedicated function

With a piece of logic to refactor identified, extract it into a dedicated function. This decouples the authorization logic from the surrounding application logic. Create a function called canReadRepo() in a new file called authz.ts. That makes it obvious which permission it governs.
src/authz.ts
// A user can read a repo if they have any role on the repo or its parent organization.
function canReadRepo(user: UserWithRoles, repo: Repository): boolean {
  const orgRole = user.orgRoles.some((orgRole) => orgRole.orgId == repo.orgId);
  const repoRole = user.repoRoles.some(
    (repoRole) => repoRole.repoId == repo.id,
  );

  return orgRole || repoRole;
}
Now you call this function when you authorize a user’s request to read a repository.
src/routes/accounts.ts
import { canReadRepo } from "../authz";

// List all repos that the user can read in the specified org
accountsRouter.get("/orgs/:orgId/repos", withAuthn, async (req, res) => {
  let repos = await req.prisma.repository.findMany({
    where: { orgId: parseInt(req.params.orgId) },
  });
  const user = await currentUser(req);
  repos = repos.filter((repo) => {
    return canReadRepo(user, repo);
  });
  res.send(repos);
});
src/routes/accounts.ts
// Return the requested repository if the user has permission to read it
accountsRouter.get(
  "/orgs/:orgId/repos/:repoId",
  withAuthn,
  async (req, res) => {
    const user = await currentUser(req);
    const repo: WithPermissions<Repository> | null =
      await req.prisma.repository.findUnique({
        where: {
          orgId: parseInt(req.params.orgId),
          id: parseInt(req.params.repoId),
        },
      });

    if (repo && canReadRepo(user, repo)) {
      repo.permissions = ["read"];

      ...

    }
  }
);
Even this change provides meaningful benefits:
  • A dedicated file for authorization logic (src/authz.ts).
  • The logic for the “read repository” permission is easy to find, understand, and reason about.
  • The logic is defined only once.
  • The application code is cleaner.
Next, implement the logic in Oso Cloud.