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” upwardsdown
- runs db “schema” downwards (rolls back the actions ofup
)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 initializedMigration
objectssave_applied_number
- saves a number of lst applied up migration in the storage for later referencelast_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.