Components

The different Py-ABAC components corresponding to those in the access control architecture are described in this section. Using these components you can implement fine-grained access control for your protected resources.

Policy

This is the component containing rules for accessing resources. A policy object can be created by first defining a policy JSON using the JSON-based Policy Language, and then parsing it using the Policy class.

from py_abac import Policy

# Policy definition in JSON-based policy language
policy_json = {
    "uid": "1",
    "description": "Max is not allowed to create, delete, get any resource",
    "effect": "deny",
    "rules": {
        "subject": {"$.name": {"condition": "Equals", "value": "Max"}},
        "resource": {"$.name": {"condition": "RegexMatch", "value": ".*"}},
        "action": [{"$.method": {"condition": "Equals", "value": "create"}},
                   {"$.method": {"condition": "Equals", "value": "delete"}},
                   {"$.method": {"condition": "Equals", "value": "get"}}],
        "context": {}
    },
    "targets": {},
    "priority": 0
}
# Prase policy JSON to create Policy object
policy = Policy.from_json(policy_json)

See the Policy Language section for detailed description of JSON structure.

Access Request

A AccessRequest object represents the access request generated by PEP in the ABAC architecture. All you need to do is take any kind of incoming user request (REST request, SOAP, etc.) and build an AccessRequest object out of it as shown below:

from py_abac import AccessRequest
from flask import request, session

# Create a access request JSON from flask request object
request_json = {
    "subject": {
        "id": "",
        "attributes": {"name": request.values.get("username")}
    },
    "resource": {
        "id": "",
        "attributes": {"name": request.path}
    },
    "action": {
        "id": "",
        "attributes": {"method": request.method}
    },
    "context": {}
}
# Parse JSON and create access request object
request = AccessRequest.from_json(request_json)

You might have noticed the presence of empty "id" fields for the subject , resource and action access control elements in the above example. These are called target IDs and are mandatory fields for creating an access request object in Py-ABAC. The purpose of these fields is explained in detail in the Targets Block subsection of Policy Language. If you are unsure of their usage, you can safely set them to an empty string.

Note

Target IDs are used by Storage for efficient policy retrieval. If you don’t set them in any polices, a default value of “*” is used which results in the storage returning all stored polices for evaluation to PDP. Thus even though targets are not mandatory, it is a encouraged to have them in systems with large number of policies.

Note

For backward compatibility with Py-ABAC v0.2.0 you can also use the Request class to create access request.

PDP

This component is the policy decision point, instantiated through the PDP class. It is the main entry point of Py-ABAC for evaluating policies. At a minimum, a Storage object is required to create a PDP object. It has one method, is_allowed, which when passed a AccessRequest object, gives you a boolean answer: is access allowed or not?

from pymongo import MongoClient
from py_abac import PDP
from py_abac.storage.mongo import MongoStorage

# Setup storage
client = MongoClient()
st = MongoStorage(client)
# Insert all polices to storage
for p in policies:
    st.add(p)

# Create PDP
pdp = PDP(st)

# Evaluate if access is allowed
if pdp.is_allowed(request):
    return "Access Allowed", 200
else:
    return "Unauthorized Access", 401

By default PDP` uses the DenyOverrides algorithm for policy evaluation. To specify otherwise, pass the evaluation algorithm at creation. Moreover, a list of AttributeProvider objects can also be provided. See the sub-section AttributeProvider for details of their usage.

from py_abac import PDP, EvaluationAlgorithm
from py_abac.storage.mongo import MongoStorage
from py_abac.providers import AttributeProvider

# A simple email attribute provider class
class EmailAttributeProvider(AttributeProvider):
    def get_attribute_value(self, ace, attribute_path, ctx):
        return "example@gmail.com"

# Setup storage
client = MongoClient()
st = MongoStorage(client)
# Insert all polices to storage
for p in policies:
    st.add(p)

# Create PDP configured to use highest priority algorithm
# and an additional email attribute provider
pdp = PDP(st, EvaluationAlgorithm.HIGHEST_PRIORITY, [EmailAttributeProvider()])

The three supported evaluation algorithms are:

  • EvaluationAlgorithm.DENY_OVERRIDES

  • EvaluationAlgorithm.ALLOW_OVERRIDES

  • EvaluationAlgorithm.HIGHEST_PRIORITY

Storage

Storage is a component which provides interface for implementing policy persistence. Thus it is used in creating a PAP with the exposed methods described in Custom Backend subsection. It is also used by PDP to retrieve policies for evaluation. There can be various backend implementations like RDBMS, NoSQL databases, etc. Py-ABAC ships with some out of the box:

Custom Backend

You can create your own policy storage by implementing Storage. It exposes the following methods:

from py_abac.storage.base import Storage


class MyStorage(Storage):
    """
        My custom backend specific storage
    """

    def add(policy):
        """
            Store a Policy
        """

    def get(uid):
        """
            Retrieve a Policy by its ID
        """

    def get_all(limit, offset):
        """
            Retrieve all stored Policies (with pagination)
        """

    def update(policy):
        """
            Store an updated Policy
        """

    def delete(uid):
        """
            Delete Policy from storage by its ID
        """

    def get_for_target(subject_id, resource_id, action_id):
        """
            Retrieve Policies that match the given target IDs
        """

The first 5 methods are used for creating a PAP while the last method is used by PDP.

Important

Care must be taken when implementing get_for_target. Incorrect filtering strategies in the method may lead to wrong access decisions by PDP.

Migrations

Migrations are a set of components that are useful for Storage backends. The design and implementation is taken from the Vakt SDK. It’s recommended in favor over manual actions on DB schema/data since it’s aware of Py-ABAC requirements. But it’s not mandatory. It is up to a particular storage to decide whether it needs migrations. It consists of 3 components:

  • Migration

  • MigrationSet

  • Migrator

Migration allows you to describe data modifications between versions. Each storage can have a number of Migration classes to address different releases with the order of the migration specified in order property. The class should be located inside corresponding storage package and should implement py_abac.storage.migration.Migration. Migration has 2 main methods (as you might guess) and 1 property:

  • up - runs db “schema” upwards

  • down - runs db “schema” downwards (rolls back the actions of up)

  • order - tells the number of the current migration in a row

MigrationSet is a component that represents a collection of migrations for a storage. You should define your own migration-set. It should be located inside corresponding storage package and should implement py_abac.storage.migration.MigrationSet. It has 3 methods that lest unimplemented:

  • migrations - should return all initialized Migration objects

  • save_applied_number - saves a number of lst applied up migration in the storage for later reference

  • last_applied - returns a number of a lst applied up migration from the storage

Migrator is an executor of migrations. It can execute all migrations up or down, or execute a particular migration if number argument is provided.

Example usage:

from pymongo import MongoClient
from py_abac.storage.mongo import MongoStorage, MongoMigrationSet
from py_abac.storage.migration import Migrator

client = MongoClient('localhost', 27017)
storage = MongoStorage(client, 'database-name', collection='optional-collection-name')

migrator = Migrator(MongoMigrationSet(storage))
migrator.up()
...
migrator.down()
...
migrator.up(number=2)
...
migrator.down(number=2)

AttributeProvider

AttributeProvider is an interface to create a PIP. The purpose of this object is to provide attribute values missing in the AccessRequest object. During policy evaluation, the PDP first checks the Request object for attribute values; If no values are found, it then checks the list of AttributeProvider objects passed during PDP creation. In order to create an AttributeProvider object, you need to implement the get_attribute_value method:

from py_abac.provider.base import AttributeProvider

# A simple email attribute provider class
class EmailAttributeProvider(AttributeProvider):
    def get_attribute_value(self, ace, attribute_path, ctx):
        """
            Returns a value for an attribute. If value not found
            then return None.


            :param ace: string value indicating the access control
                        element, i.e. "subject", "resource", "action"
                        or "context".
            :param attribute_path: string in ObjectPath notation indicating
                                   the attribute for which the value is
                                   requested.
            :param ctx: evaluation context
        """
        return "example@gmail.com"

As seen in the above example, get_attribute_value takes in three arguments: ace, attribute_path and ctx. The ace is a string value indicating for which access control element the attribute value is being requested. This argument will be set to either "subject", "resource", "action", or "context"`. The attribute_path argument is a string in ObjectPath notation denoting the attribute for which the value is being requested. The ctx argument is an EvaluationContext object. The primary purpose of this argument is to retrieve values of other attributes. Thus a common use-case would be to return values conditioned upon other attributes:

# An email attribute provider class
class EmailAttributeProvider(AttributeProvider):
    def get_attribute_value(self, ace, attribute_path, ctx):
        # Return email for Max
        if ctx.get_attribute_value("subject", "$.name") == "Max":
            return "max@gmail.com"
        # Else return default email
        return "default@gmail.com"

Note

If the AttributeProvider does not contain value for an attribute, the get_attribute_value must return None.

EvaluationContext

An EvaluationContext object is created by the PDP during policy evaluation. This object is used by PDP for retrieval of those attribute values referred by a policy. It has following properties:

# The target ID for subject access control element
ctx.subject_id

# The target ID for resource access control element
ctx.resource_id

# The target ID for action access control element
ctx.action_id

# Lookup a value for an attribute of an access control element
ctx.get_attribute_value(ace: str, attribute_path: str)

During retrieval, the EvaluationContext first checks for an attribute value in the AccessRequest object. If the value is not found, it then checks all the AttributeProvider objects sequentially.

Note

As attribute values are retrived from AttributeProvider objects sequentially, an eager lookup is performed. This means any subsequent AttributeProvider objects will be skipped the instant very first provider returns a value.