Metadata-Version: 2.4
Name: authzee
Version: 0.1.0a4
Summary: Authzee Python SDK.
Home-page: https://github.com/btemplep/authzee
Author: Brandon Temple Paul
Author-email: btemplepgit@gmail.com
License: MIT
Project-URL: Repository, https://github.com/btemplep/authzee-py
Keywords: auth,authz,authzee,authorization,engine,framework
Classifier: Development Status :: 3 - Alpha
Classifier: License :: OSI Approved :: MIT License
Classifier: Natural Language :: English
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: Unix
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Internet
Description-Content-Type: text/markdown
Requires-Dist: jsonschema-rs
Requires-Dist: loguru
Provides-Extra: jmespath
Requires-Dist: jmespath; extra == "jmespath"
Provides-Extra: all
Requires-Dist: authzee[jmespath]; extra == "all"
Provides-Extra: dev
Requires-Dist: build; extra == "dev"
Requires-Dist: coverage; extra == "dev"
Requires-Dist: moto[s3,server]; extra == "dev"
Requires-Dist: nox; extra == "dev"
Requires-Dist: piccolo-theme; extra == "dev"
Requires-Dist: pytest; extra == "dev"
Requires-Dist: pytest-asyncio; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"
Requires-Dist: sphinx; extra == "dev"
Requires-Dist: twine; extra == "dev"


<!-- ![authzee-logo](./docs/logo.svg) Documentation(Link TBD) -->
<img src="https://raw.githubusercontent.com/btemplep/authzee/main/docs/authzee_logo.svg" alt="Authzee Logo" width="500">

<!-- # `authzee` -->

This is the official python SDK for Authzee! It is a general usage SDK that is async, extensible, and scalable. 

Authzee is a highly expressive grant-based authorization engine. Check out the [Authzee Repo](https://github.com/btemplep/authzee) for the core engine and specification.

<!-- [Authzee Docs](https://docs.authzee.org) -->


<!-- See the [full documentation](https://authzee-py.docs.authzee.org) for more info! -->


- [Installation](#installation)
- [Tutorial](#tutorial)
    - [Simple Example](#simple-example)
    - [Authzee App](#authzee-app)
        - [Execute Function](#execute-function)
        - [Compute and Storage Modules](#compute-and-storage-modules)
    - [Context](#context)
    - [Identity](#identity)
    - [Resource](#resource)
    - [Grant](#grant)
    - [Authorize](#authorize)
- [Full Example](#full-example)
- [Development](#development)
    - [Compute and Storage Module Development](#compute-and-storage-module-development)
    - [Module Caching](#module-caching)


## Installation

```text
$ pip install authzee
```

Extra dependencies can be installed like

```console
pip install authzee[jmespath,sql-storage]
```

Extra dependencies available/needed:

- `jmespath` - needed if using the built in jmespath execute functions
- `sql-storage` - needed for `SQLStorage` class
- `dev` - development dependencies 
- `all` - for all extra dependencies except for `dev`


## Tutorial

This is the simple tutorial covering all of the main pieces to start using Authzee locally. 

### Simple Example
```python
import json
from uuid import uuid4

from authzee import Authzee, DictStorage, InProcessCompute, jmespath_execute
# for asyncio use
# from authzee import AuthzeeAsync


storage_dict = {}
authz = Authzee( # for asyncio use AuthzeeAsync
    execute=jmespath_execute,
    compute_type=InProcessCompute,
    compute_kwargs={},
    storage_type=DictStorage,
    storage_kwargs={
        "storage_dict": storage_dict
    },
    config={  # optional - AuthzeeConfigOverride | None - All root and nested keys are optional
        "authzee": {
            "raise_crits": True
        }
        # "method_name": {<method config>}
    }
)
authz.construct() # one time setup for life of storage and compute
authz.start() # initialize the authzee app - must be run once for every instance
authz.put_context_def( # Context is used to pass structured data to authorization requests. Register them first as context definitions.
    {
        "context_type": "NONE", # unique
        "schema": { # JSON Schema
            "type": "object",
            "additionalProperties": False
        }
    }
)
authz.put_identity_def( # identities describe who is being authorized. Register them first as identity definitions.
    identity_def={
        "identity_type": "user", # unique
        "schema": { # JSON Schema
            "type":"object",
            "required": [
                "username",
                "department"
            ],
            "additionalProperties": False,
            "properties": {
                "username": {
                    "type": "string"
                },
                "department": {
                    "type": "string"
                }
            }
        }
    }
)
authz.put_resource_def( # resources define resource types and actions that can be taken on those resources.  Register them first as resource definitions.
    resource_def={
        "resource_type": "balloon", # unique
        "actions": [ # can be shared between resource types
            "balloon:read",
            "balloon:inflate",
            "balloon:pop"
        ],
        "schema": { # JSON Schema
            "type": "object",
            "required": [
                "color",
                "is_inflated"
            ],
            "additionalProperties": False,
            "properties": {
                "color": {
                    "type": "string"
                },
                "is_inflated": {
                    "type": "boolean"
                }
            }
        }
    }
)
authz.enact( # Enact grants to create authorization rules
    grant={
        "grant_uuid": str(uuid4()),
        "name": "Allow inflate for balloon department", # not unique
        "description": "Balloon department people are allowed to read and inflate all balloons.",
        "tags": {}, # tags for categorizing grants
        "effect": "allow", # allow or deny
        "actions": [ # list of actions to match
            "balloon:read",
            "balloon:inflate"
        ],
        "query": "length(request.identities.user[?department == 'Balloon Dept']) > `0`", # JSON Query for the request. JMESPath is preferred
        # query runs on {"request": <request>, "grant": <grant>}
        "evaluation_handler": "evaluate", 
        "equality": True, # expected result of the query
        "data": {} # data available to this grant
    }
)
result = authz.authorize(
    { # request for authorization runs on:
        "identities": { # identities
            "user": [ # identity_type with array of instances
                {
                    "username": "balloon_person",
                    "department": "Balloon Dept"
                }
            ]
        },
        "action": "balloon:inflate",
        "resource_type": "balloon",
        "resource": {
            "color": "inflated",
            "is_inflated": False
        },
        "evaluation_handler": "grant",
        "context_type": "NONE",
        "context": {}
    }
)
print(json.dumps(result, indent=4))
```
Authorization response: 
```json
{
    "is_authorized": true,
    "grant": {
        "grant_uuid": "49d5398a-cd5e-4944-bdb9-6543d061a53e",
        "name": "Allow inflate for balloon department",
        "description": "Balloon department people are allowed to read and inflate all balloons.",
        "tags": {},
        "effect": "allow",
        "actions": [
            "balloon:read",
            "balloon:inflate"
        ],
        "query": "length(request.identities.user[?department == 'Balloon Dept']) > `0`",
        "evaluation_handler": "evaluate",
        "equality": true,
        "data": {}
    },
    "message": "An allow grant is applicable to the request, and there are no deny grants that are applicable to the request. Therefore, the request is authorized.",
    "has_failed": false,
    "critical_errors": {}
}
```

### Authzee App

The `Authzee` class is the entrypoint to all authzee functionality.  `AuthzeeAsync` is available for asyncio — it has the same interface as `Authzee` except all methods are async.

You can check which version of the authzee specification the SDK implements via `authzee.authzee_specification_version` (currently `"0.3.0"`).

```python
from authzee import (
    Authzee, 
    DictStorage,
    InProcessCompute,
    jmespath_execute, 
    paginator,
    authzee_specification_version
)
# for asyncio use AuthzeeAsync - same interface, all methods are async (use await)
# from authzee import AuthzeeAsync, paginator_async
# to include custom JMESPath functions use
# from authzee import jmespath_custom_execute

print(f"Authzee Specification Version: {authzee_specification_version}")

storage_dict = {}
authz = Authzee( # for asyncio use AuthzeeAsync
    execute=jmespath_execute, # for custom JMESPath functions use jmespath_custom_execute 
    compute_type=InProcessCompute, # compute backend type
    compute_kwargs={},
    storage_type=DictStorage, # storage backend type
    storage_kwargs={
        "storage_dict": storage_dict
    },
    config={  # optional - AuthzeeConfigOverride | None - All keys are optional
        "authzee": {
            "raise_crits": True
        }
        # "method_name": {<method config>}
    }
)
authz.construct() # one time setup for life of storage and compute
authz.start() # initialize the authzee app - must be run once for every instance
```

The Authzee class requires a JSON query function, compute module, and storage module. 


#### Execute Function

The execute function is a wrapper around your choice of JSON query language.
Out of the box, the SDK has:

- `jmespath_execute` 
    - Standard JMESPath python implementation
    - Must install `authzee[jmespath]`
- `jmespath_custom_execute`
    - Standard JMESPath python implementation plus extra functions as outlined in the [SDK recommendations](https://github.com/btemplep/authzee/blob/main/docs/sdks.md#standard-jmespath-extensions)
    - Must install `authzee[jmespath]`


If you want to create your own, see `jmespath_execute` for a simple example.

#### Compute and Storage Modules
The compute is used to process authorization requests.  Storage is used to store grants and definitions. The Authzee SDK includes several of these out of the box. 

Built in Compute Modules include:

- `InProcessCompute` - Compute is all done within the same process/asyncio event loop.

Built in Storage Modules include:

- `DictStorage` - Storage is in main memory within a python dict.



### Context

Context is structured data that is passed in with authorization requests. Context types are registered in authzee with context definitions. 

```python
authz.put_context_def( # Context is used to pass structured data to authorization requests. Register them first as context definitions.
    {
        "context_type": "NONE", # unique
        "schema": { # JSON Schema
            "type": "object",
            "additionalProperties": False
        }
    }
)
```


### Identity

Identities define who is being authorized. They are registered with authzee via identity definitions. 

```python
authz.put_identity_def( # identities describe who is being authorized. Register them first as identity definitions.
    identity_def={
        "identity_type": "user", # unique
        "schema": { # JSON Schema
            "type":"object",
            "required": [
                "username",
                "department"
            ],
            "additionalProperties": False,
            "properties": {
                "username": {
                    "type": "string"
                },
                "department": {
                    "type": "string"
                }
            }
        }
    }
)
```

### Resource

Resources are structured data to represents your resources and actions that can be taken on them.  They are registered with Authzee as resource definitions.

```python
authz.put_resource_def( # resources define resource types and actions that can be taken on those resources.  Register them first as resource definitions.
    resource_def={
        "resource_type": "balloon", # unique
        "actions": [ # can be shared between resource types
            "balloon:read",
            "balloon:inflate",
            "balloon:pop"
        ],
        "schema": { # JSON Schema
            "type": "object",
            "required": [
                "color",
                "is_inflated"
            ],
            "additionalProperties": False,
            "properties": {
                "color": {
                    "type": "string"
                },
                "is_inflated": {
                    "type": "boolean"
                }
            }
        }
    }
)
```


### Grant 

Grants are how authorization rules are represented in Authzee. 

Grants are enacted in Authzee ie a new authorization rule is added.  Grants apply to any request that has a matching resource action.

```python
authz.enact( # Enact grants to create authorization rules
    grant={
        "grant_uuid": str(uuid4()),
        "name": "Allow inflate for balloon department", # not unique
        "description": "Balloon department people are allowed to read and inflate all balloons.",
        "tags": {}, # tags for categorizing grants
        "effect": "allow", # allow or deny
        "actions": [ # list of actions to match
            "balloon:read",
            "balloon:inflate"
        ],
        "query": "length(request.identities.user[?department == 'Balloon Dept']) > `0`", # JSON Query for the request. JMESPath is preferred
        # query runs on {"request": <request>, "grant": <grant>}
        "evaluation_handler": "evaluate", 
        "equality": True, # expected result of the query
        "data": {} # data available to this grant
    }
)
```


### Authorize

Authorization is one of the core *operations* (or ops) of an authorization engine and brings all of the pieces together. 

```python
result = authz.authorize(
    { # request for authorization runs on:
        "identities": { # identities
            "user": [ # identity_type with array of instances
                {
                    "username": "balloon_person",
                    "department": "Balloon Dept"
                }
            ]
        },
        "action": "balloon:inflate",
        "resource_type": "balloon",
        "resource": {
            "color": "inflated",
            "is_inflated": False
        },
        "evaluation_handler": "grant",
        "context_type": "NONE",
        "context": {}
    }
)
print(json.dumps(result, indent=4))
```
Authorization response: 
```json
{
    "is_authorized": true,
    "grant": {
        "grant_uuid": "49d5398a-cd5e-4944-bdb9-6543d061a53e",
        "name": "Allow inflate for balloon department",
        "description": "Balloon department people are allowed to read and inflate all balloons.",
        "tags": {},
        "effect": "allow",
        "actions": [
            "balloon:read",
            "balloon:inflate"
        ],
        "query": "length(request.identities.user[?department == 'Balloon Dept']) > `0`",
        "evaluation_handler": "evaluate",
        "equality": true,
        "data": {}
    },
    "message": "An allow grant is applicable to the request, and there are no deny grants that are applicable to the request. Therefore, the request is authorized.",
    "has_failed": false,
    "critical_errors": {}
}
```

For authorization, Authzee evaluates the given request against each grant.  

How this works is the grant query, in this case JMESpath, is run on the combined data structure. 

In the case of the above grant and request it would run on this data:

```json
{
    "request": { 
        "identities": { 
            "user": [ 
                {
                    "username": "balloon_person",
                    "department": "Balloon Dept"
                }
            ]
        },
        "action": "balloon:inflate",
        "resource_type": "balloon",
        "resource": {
            "color": "inflated",
            "is_inflated": false
        },
        "evaluation_handler": "grant",
        "context_type": "NONE",
        "context": {}
    },
    "grant": {
        "grant_uuid": "49d5398a-cd5e-4944-bdb9-6543d061a53e",
        "name": "Allow inflate for balloon department",
        "description": "Balloon department people are allowed to read and inflate all balloons.",
        "tags": {},
        "effect": "allow",
        "actions": [
            "balloon:read",
            "balloon:inflate"
        ],
        "query": "length(request.identities.user[?department == 'Balloon Dept']) > `0`",
        "evaluation_handler": "evaluate",
        "equality": true,
        "data": {}
    }
}
```

The query:

```
length(request.identities.user[?department == 'Balloon Dept']) > `0`
```

Will filter all user identities by those that are in the 'Balloon Dept'.  If there are more than 0 users like that, then the result of the query will be `true`.  The expected value for the grant to be considered applicable, the `equality` is `true`.  Since the result and equality are equal the grant is considered applicable to the request and the grant effect will be applied. 

For authorization, by default, no requests are allowed.  If a grant with the deny effect is applicable, the request is denied, no matter any other outcomes.  If a grant with the allow effect is applicable, and there are no deny grants applicable then the request is allowed. 


## Full Example

For a more comprehensive example that demonstrates all Authzee methods, see [`full_example.py`](./full_example.py).


## Development

Install all dependencies
```console
pip install -e .[dev,all]
```

Use [nox](https://nox.thea.codes/en/stable/) for the most common setups. 

### Compute and Storage Module Development

The compute and storage modules are meant to be that - modular!

You should be able to build custom ones based off of the base classes `ComputeModule` and `StorageModule`. Note that all underlying methods must be async.  

### Module Caching

Caching for validating a request or batch request should be self contained within the compute model per request. Besides that, it is up to the storage module to control caching for storage calls.

# Changelog

Changelog for `authzee`.
All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

<!-- 
## [Unreleased] - YYYY-MM-DD

### Added

### Changed

### Deprecated

### Removed

### Fixed

### Security 
-->

## [0.1.0a4] - 2026-06-15

New revamp to support Authzee spec 0.3.0. 

### [0.1.0a3] - 2024-02-19

- Remove sync methods and only accepts async
- `AuthzeeSync` class as a sync wrapper for Authzee app
- Locality checks streamlined. Only specify current backends locality
- Parallel pagination skeleton
- general renaming
- rework ResourceAuthzs. They are now passed as instances to the Authzee app. Logic is streamlined

### [0.1.0a2] - 2023-07-23

- Initial Alpha.

### [0.1.0a1] - 2023-07-03

- Initial stub 

