| Name | c5 JSON |
| Version |
0.1.0
JSON |
| download |
| home_page | None |
| Summary | The Simplest Attribute Based Access Control Python Library |
| upload_time | 2024-10-20 23:22:14 |
| maintainer | None |
| docs_url | None |
| author | Raymond |
| requires_python | <4.0,>=3.7 |
| license | None |
| keywords |
|
| VCS |
|
| bugtrack_url |
|
| requirements |
No requirements were recorded.
|
| Travis-CI |
No Travis.
|
| coveralls test coverage |
No coveralls.
|
# Contracts, Policies
Pydentity is an authorization and access control library built to provide simple yet robust ABAC paradigms. (**NOTE: pydentity is not a strict ABAC system**) - it is simpler but without
sacrificing the power of ABAC.
## Components
Pydentity 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
```py
from pydentity 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 **Control**led?
##### Code Snippet: 1b
```py
... # 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
```py
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 **`pydentity denies all access unless a contract is found`**.
```py
from pydentity import Contract
# pydentity 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`.
Pydentity *conditions* i.e. `.contact(**conditions)`, `.content(**conditions)`, `.context(**conditions)` are dictionaries that are passed into the *content*, *contact*, and *context* `pydentity.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.
```json
{"key": 0, "key2": 1}
```
means where key = 0 `AND` key2 = 1
and **`is the same as`**
```json
{"&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.
```py
from pydentity import Contract
from pydentity.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)
})
```
```py
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 `?`.
```py
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
})
```
```py
from pydentity 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 pydentity 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.
```py
from pydentity 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)
```
```py
# 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
Pydentity 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 Pydentity namely:
- Context:
Raw data
{
"_id": null,
"home_page": null,
"name": "c5",
"maintainer": null,
"docs_url": null,
"requires_python": "<4.0,>=3.7",
"maintainer_email": null,
"keywords": null,
"author": "Raymond",
"author_email": "ortserga@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/d4/5d/529e2ef634adfb2fcfdf228d7bac0a4bd3897598fa05bf601e1088b37021/c5-0.1.0.tar.gz",
"platform": null,
"description": "# Contracts, Policies\nPydentity is an authorization and access control library built to provide simple yet robust ABAC paradigms. (**NOTE: pydentity is not a strict ABAC system**) - it is simpler but without\nsacrificing the power of ABAC.\n\n\n## Components\nPydentity 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.\n\n\nEvery information system has:\n\n- **Content**: this is the data you might want to protect i.e. data stored in the system or system(s)\n\n- **Contacts**: Entities that want to come in contact with the data (owner of the data, someone granted access by owner, hacker, application) etc.\n\n- **Context**: Information within the information system or that the information system has access to but exists outside of the **Content** or\nprotected data i.e. dates or times when no one is allowed to update content, ip address of a contact etc.\n\n- **Contracts**: What is a legal or illegal operation when **Content**, **Contact**, **Context** is combined together\n\n- **Controls**: The system, processes, tools etc. that deny or allows operations _to_ and _on_ content\n\nSo how does this all relate to C5 - From the definition it means:\nYou (your application, company) wants to **control** (allow, deny access _to_ or limit actions _on_) **content** (data, information) from **contact** (application, hacker, human)\nvia **contracts** (combinatory rules) **contextually** (which might include the use of data not stored within the content being controlled).\n\n\n## Contracts\nThe 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\n\n##### Code Snippet: 1a\n```py\nfrom pydentity import Contract\n\ncontract = Contract()\n```\n\n\nSimple enough, but then, how do you tell a contract what actions are possible, and how to identify **Content** to be **Control**led?\n\n##### Code Snippet: 1b\n```py\n... # code from above\n\ncontract.on('delete').to('/customers/:id')\n```\nTo 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\ncan be an intuitive option or popular candidate.\n\nIt 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\nthe `.on('deletes')` method and still get the same output.\n\nThis means the code snippet in snippet `1b` above is equivalent to code in snippet `1c` below.\n\n##### Code Snippet: 1c\n```py\ncontract.to('/customers/:id').on('delete') \n```\n\n\n### Contract Rules & Conditions\nGreat, 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\nactual `content`, `contact`, and `context` data for which the contract is valid.\n\n\n#### Controlling Access Based On Content\nIf 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\nof the `contract` object. It is important to note that by default **`pydentity denies all access unless a contract is found`**.\n\n```py\nfrom pydentity import Contract\n\n\n# pydentity by default denies all access where there is no contract\n# so to delete unlocked orders - this contract is needed\nc = Contract()\nc.on('delete').to('orders').content({\n 'unlocked': True\n})\n\n\n# this will allow any content at the address `/orders/:id` with category\n# `Perishables` to be deleted\nd = Contract()\nd.on('delete').to('/orders/:id').contact({\n 'category': 'Perishables'\n})\n```\nWe 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,\nassociative arrays (different names for the same thing) have a common format - `a key` maps to `a value`.\nPydentity *conditions* i.e. `.contact(**conditions)`, `.content(**conditions)`, `.context(**conditions)` are dictionaries that are passed into the *content*, *contact*, and *context* `pydentity.Contract`\nmethods.\n\nBy 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\nshould be i.e. `constant values` expected to be seen for given contract conditions.\n\nFor all condition dicts across all blocks i.e. `content, context, contact` - the default\ncombination logic is `AND` i.e.\n\n```json\n{\"key\": 0, \"key2\": 1}\n```\n\nmeans where key = 0 `AND` key2 = 1\n\nand **`is the same as`**\n\n```json\n{\"&key\": 0, \"&key2\": 1}\n```\n\nthis 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.\n\nTo use `OR` logic instead of `AND`, use the **OR** symbol `?` as the first character of your conditions dict i.e.\n\n```py\nfrom pydentity import Contract\nfrom pydentity.conditions import GT # other options -> GTE, LT, BETWEEN, IN, NIN etc\n\n\n# this contract will allow\n# any contact\n# to retrieve\n# any content\n# where\n# name = 'Jon' OR age > 18\nContract().on('gets').to('/customers').content({\n '?name': 'Jon',\n '?age': GT(18)\n})\n```\n\n```py\nc = Contract()\n\n# this contract means give everyone access when orders content is public AND unlocked\nc.on('gets').to('/orders').content({\n 'public': True,\n 'unlocked': True\n})\n\n# this is the same as above but using explicit symbol notation\nc.on('gets').to('/orders').content({\n '&public': True,\n '&unlocked': True\n})\n\n\n# this contract means give everyone access when orders content is public OR unlocked\nc.on('gets').to('/orders').content({\n '?public': True,\n '?unlocked': True\n})\n```\nTo specify `OR` combinations the keys must use the explicit symbol notation with the symbol for `OR` which is a `?`.\n\n```py\nc = Contract()\n\n# this contract means allow Contact to the content\n# at address `/products/:id` when it is public OR unlocked\nc.on('updates').to('/products/:id').content({\n '?public': True,\n '?unlocked': True\n})\n```\n\n```py\nfrom pydentity import Controls\n\n... # code from previous block here\n\ncontrols = Controls()\ncontrols.add(c, d)\n```\nThese contracts are loaded in memory and for test use cases that is fine, but to make the most of pydentity you might want to connect a storage engine\nlike a database for contracts to be saved and retrieved easily.\n\nCurrently only redis, postgres, sql server, oracle db, and mysql is supported as a storage engine.\n\n```py\nfrom pydentity import Contract, Controls\n\ncontrols = Controls(engine='postgres', dsn=...)\ncontract = Contract()\n\ncontract.on('patches').to('/customers/:id').context({\n 'cidr': '10.0.0.0/24'\n})\n# contracts above are added in-memory but not saved, to persist contracts?\nawait controls.save()\n\n# for synchronous use cases use save sync\ncontrols.saves()\n```\n\n```\n\n# now we can provide some context, content, and contact rules/conditions we\n# want to combine with the possible actions specified above\n\nc.context({}) # let's use an empty dict for now - this will allow everyone but it's good enough for now\nc.content({}) # empty dict for now\nc.contact({}) # empty dict for now\n\n\n# finally let's add this contract to our controls for saving so it can be used later\n# await protocol.add_contract(c) also possible\nprotocol.add_contract_sync(c)\n```\n\n\n```py\n# this will allow any contact with first_name of Tersoo to delete an order\nd = Contract()\nd.on('delete').to('orders/:id').contact({\n 'first_name': 'Tersoo'\n})\n```\n\n\n#### Contact\nThis is recorded personal identifying information that can be used to identify a persona (app, human etc).\n\n\n\n## Crash Course\nPydentity 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 Pydentity namely:\n\n- Context: ",
"bugtrack_url": null,
"license": null,
"summary": "The Simplest Attribute Based Access Control Python Library",
"version": "0.1.0",
"project_urls": null,
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "4fea491a8216807e867cab605bdc1ba186cdae507bd61187377e45766651311f",
"md5": "2706dcdf0270165b8261f31cb660534e",
"sha256": "8d89d67a4306c56027802d9f87665530a879fe3f8b9fe92f74ec40b26b9686d9"
},
"downloads": -1,
"filename": "c5-0.1.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "2706dcdf0270165b8261f31cb660534e",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<4.0,>=3.7",
"size": 9234,
"upload_time": "2024-10-20T23:22:12",
"upload_time_iso_8601": "2024-10-20T23:22:12.963721Z",
"url": "https://files.pythonhosted.org/packages/4f/ea/491a8216807e867cab605bdc1ba186cdae507bd61187377e45766651311f/c5-0.1.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "d45d529e2ef634adfb2fcfdf228d7bac0a4bd3897598fa05bf601e1088b37021",
"md5": "c61b60147af4ac0108cc1ac0e4a42771",
"sha256": "54c6cfda7e707ffc66b8b71280480c2ec2b6b181a3d0833164ffbeee4df77425"
},
"downloads": -1,
"filename": "c5-0.1.0.tar.gz",
"has_sig": false,
"md5_digest": "c61b60147af4ac0108cc1ac0e4a42771",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<4.0,>=3.7",
"size": 10529,
"upload_time": "2024-10-20T23:22:14",
"upload_time_iso_8601": "2024-10-20T23:22:14.918211Z",
"url": "https://files.pythonhosted.org/packages/d4/5d/529e2ef634adfb2fcfdf228d7bac0a4bd3897598fa05bf601e1088b37021/c5-0.1.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-10-20 23:22:14",
"github": false,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"lcname": "c5"
}