Skip to main content

The Simplest Attribute Based Access Control Python Library

Project description

Contracts, Policies

C5 is an authorization and access control library built to provide simple yet robust ABAC paradigms. (NOTE: C5 is not a strict ABAC system) - it is simpler but without sacrificing the power of ABAC.

Components

C5 works on a © Micro C5 Paradigm which translates to Control Content from Contact via Contracts Contextually. Sounds intimidating but it is really simple once broken down.

Every information system has:

  • Content: this is the data you might want to protect i.e. data stored in the system or system(s)

  • Contacts: Entities that want to come in contact with the data (owner of the data, someone granted access by owner, hacker, application) etc.

  • Context: Information within the information system or that the information system has access to but exists outside of the Content or protected data i.e. dates or times when no one is allowed to update content, ip address of a contact etc.

  • Contracts: What is a legal or illegal operation when Content, Contact, Context is combined together

  • Controls: The system, processes, tools etc. that deny or allows operations to and on content

So how does this all relate to C5 - From the definition it means: You (your application, company) wants to control (allow, deny access to or limit actions on) content (data, information) from contact (application, hacker, human) via contracts (combinatory rules) contextually (which might include the use of data not stored within the content being controlled).

Contracts

The first thing to understand is Contracts. Contracts are how you specify the relationships betweem Contacts and Content. How do we create Contracts? Let's look at some code

Code Snippet: 1a
from C5 import Contract

contract = Contract()

Simple enough, but then, how do you tell a contract what actions are possible, and how to identify Content to be Controlled?

Code Snippet: 1b
...  # code from above

contract.on('delete').to('/customers/:id')

To specify actions use the .on(*actions) method of the contracts object. Actions can be called whatever you want i.e. .on('foo') - for web applications HTTP verbs can be an intuitive option or popular candidate.

It is also important to note that all contract object method calls are order agnostic, this means you called have also called the .to('/customers/:id') method before the .on('deletes') method and still get the same output.

This means the code snippet in snippet 1b above is equivalent to code in snippet 1c below.

Code Snippet: 1c
contract.to('/customers/:id').on('delete') 

Contract Rules & Conditions

Great, now we know how to build a contract and tell it what actions and Content .to('a-unique-id-for-content') it identifies. But a contract is useless if we have no way of specifying the actual content, contact, and context data for which the contract is valid.

Controlling Access Based On Content

If you want to control access to content based on the data in the content itself - you tell your contract this by using the .content(conditions: dict) method of the contract object. It is important to note that by default C5 denies all access unless a contract is found.

from C5 import Contract


# C5 by default denies all access where there is no contract
# so to delete unlocked orders - this contract is needed
c = Contract()
c.on('delete').to('orders').content({
    'unlocked': True
})


# this will allow any content at the address `/orders/:id` with category
# `Perishables` to be deleted
d = Contract()
d.on('delete').to('/orders/:id').contact({
    'category': 'Perishables'
})

We are going to cover Contact, and Context blocks in later sections, but before we do it is important to explain how their configurations dictionary works. Dictionaries, hashmaps, associative arrays (different names for the same thing) have a common format - a key maps to a value. C5 conditions i.e. .contact(**conditions), .content(**conditions), .context(**conditions) are dictionaries that are passed into the content, contact, and context C5.Contract methods.

By default condition keys i.e. dict keys are used for specifying what fields in the block(s) - (content, contact, context) to target, and the values provide indication of what the value of those fields should be i.e. constant values expected to be seen for given contract conditions.

For all condition dicts across all blocks i.e. content, context, contact - the default combination logic is AND i.e.

{"key": 0, "key2": 1}

means where key = 0 AND key2 = 1

and is the same as

{"&key": 0, "&key2": 1}

this is because & as the default symbol for condition keys, in addition, AND is the default operation for condition dictionaries when no symbol is explicitly provided.

To use OR logic instead of AND, use the OR symbol ? as the first character of your conditions dict i.e.

from C5 import Contract
from C5.conditions import GT  # other options -> GTE, LT, BETWEEN, IN, NIN etc


# this contract will allow
#   any contact
# to retrieve
#   any content
# where
#   name = 'Jon' OR age > 18
Contract().on('gets').to('/customers').content({
    '?name': 'Jon',
    '?age': GT(18)
})


# OR, AND and others are non greedy and top to bottom
# that means &age, &name, ?role, &department
c = Contract()

# this contract means give everyone access when orders content is public AND unlocked
c.on('gets').to('/orders').content({
    'public': True,
    'unlocked': True
})

# this is the same as above but using explicit symbol notation
c.on('gets').to('/orders').content({
    '&public': True,
    '&unlocked': True
})


# this contract means give everyone access when orders content is public OR unlocked
c.on('gets').to('/orders').content({
    '?public': True,
    '?unlocked': True
})

To specify OR combinations the keys must use the explicit symbol notation with the symbol for OR which is a ?.

c = Contract()

# this contract means allow Contact to the content
# at address `/products/:id` when it is public OR unlocked
c.on('updates').to('/products/:id').content({
    '?public': True,
    '?unlocked': True
})
from C5 import Controls

... # code from previous block here

controls = Controls()
controls.add(c, d)

These contracts are loaded in memory and for test use cases that is fine, but to make the most of C5 you might want to connect a storage engine like a database for contracts to be saved and retrieved easily.

Currently only redis, postgres, sql server, oracle db, and mysql is supported as a storage engine.

from C5 import Contract, Controls

controls = Controls(engine='postgres', dsn=...)
contract = Contract()

contract.on('patches').to('/customers/:id').context({
    'cidr': '10.0.0.0/24'
})
# contracts above are added in-memory but not saved, to persist contracts?
await controls.save()

# for synchronous use cases use save sync
controls.saves()

# now we can provide some context, content, and contact rules/conditions we
# want to combine with the possible actions specified above

c.context({})  # let's use an empty dict for now - this will allow everyone but it's good enough for now
c.content({})  # empty dict for now
c.contact({})  # empty dict for now


# finally let's add this contract to our controls for saving so it can be used later
# await protocol.add_contract(c) also possible
protocol.add_contract_sync(c)
# this will allow any contact with first_name of Tersoo to delete an order
d = Contract()
d.on('delete').to('orders/:id').contact({
    'first_name': 'Tersoo'
})

Contact

This is recorded personal identifying information that can be used to identify a persona (app, human etc).

Crash Course

C5 is all about identifying contacts, protecting content, they can have access to, as well as the context for that access to be valid. There are a few things to note about C5 namely:

  • Context:

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pidentity-0.1.0.tar.gz (11.7 kB view details)

Uploaded Source

Built Distribution

pidentity-0.1.0-py3-none-any.whl (11.0 kB view details)

Uploaded Python 3

File details

Details for the file pidentity-0.1.0.tar.gz.

File metadata

  • Download URL: pidentity-0.1.0.tar.gz
  • Upload date:
  • Size: 11.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.8.3 CPython/3.12.5 Darwin/23.6.0

File hashes

Hashes for pidentity-0.1.0.tar.gz
Algorithm Hash digest
SHA256 d60889cf05a78e404004ba9251c5ab674aef9985674e190caab1f2c73c88bb31
MD5 f7e10245ccb294fe9889bd8084a21fac
BLAKE2b-256 01717a43f8a6318a0b8920c316e90aaaba3e0c3a7547dcc2794f4aa9a7593edb

See more details on using hashes here.

File details

Details for the file pidentity-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: pidentity-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 11.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.8.3 CPython/3.12.5 Darwin/23.6.0

File hashes

Hashes for pidentity-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c986a5c5f8e5c9229b37b57fd202a325673f47076b8e25260cef0dbc794f0195
MD5 75c64fba7a3787c91e9e9b6b6eda8850
BLAKE2b-256 92f5db2e6ec7a7530934955f782548808735d9971c3cc690abf96c0fe72c33a3

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page