diff --git a/.gitignore b/.gitignore index 705f99a..1dbc96a 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ build/ docs/_build/ *.sqlite tester.py +TODO.md \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index b142537..1292109 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Security --> +## [0.1.0a5] - 2026-08-19 + +New revamp to support Authzee spec 0.4.0. + + ## [0.1.0a4] - 2026-06-15 New revamp to support Authzee spec 0.3.0. diff --git a/README.md b/README.md index 00e4d3a..1143f76 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Authzee Logo - +# Authzee Python SDK This is the official python SDK for Authzee! It is a general usage SDK that is async, extensible, and scalable. @@ -76,7 +76,7 @@ authz = Authzee( # for asyncio use AuthzeeAsync }, config={ # optional - AuthzeeConfigOverride | None - All root and nested keys are optional "authzee": { - "raise_crits": True + "raise_errors": True } # "method_name": {} } @@ -152,8 +152,8 @@ authz.enact( # Enact grants to create authorization rules ], "query": "length(request.identities.user[?department == 'Balloon Dept']) > `0`", # JSON Query for the request. JMESPath is preferred # query runs on {"request": , "grant": } - "evaluation_handler": "evaluate", "equality": True, # expected result of the query + "applicable_on_failure": False, # if True, grant is applicable even when query fails "data": {} # data available to this grant } ) @@ -173,7 +173,6 @@ result = authz.authorize( "color": "inflated", "is_inflated": False }, - "evaluation_handler": "grant", "context_type": "NONE", "context": {} } @@ -195,13 +194,12 @@ Authorization response: "balloon:inflate" ], "query": "length(request.identities.user[?department == 'Balloon Dept']) > `0`", - "evaluation_handler": "evaluate", "equality": true, + "applicable_on_failure": false, "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": {} + "error": null } ``` @@ -209,7 +207,7 @@ Authorization response: 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"`). +You can check which version of the authzee specification the SDK implements via `authzee.authzee_specification_version`. ```python from authzee import ( @@ -238,7 +236,7 @@ authz = Authzee( # for asyncio use AuthzeeAsync }, config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True } # "method_name": {} } @@ -377,8 +375,8 @@ authz.enact( # Enact grants to create authorization rules ], "query": "length(request.identities.user[?department == 'Balloon Dept']) > `0`", # JSON Query for the request. JMESPath is preferred # query runs on {"request": , "grant": } - "evaluation_handler": "evaluate", "equality": True, # expected result of the query + "applicable_on_failure": False, # if True, grant is applicable even when query fails "data": {} # data available to this grant } ) @@ -406,7 +404,6 @@ result = authz.authorize( "color": "inflated", "is_inflated": False }, - "evaluation_handler": "grant", "context_type": "NONE", "context": {} } @@ -428,13 +425,12 @@ Authorization response: "balloon:inflate" ], "query": "length(request.identities.user[?department == 'Balloon Dept']) > `0`", - "evaluation_handler": "evaluate", "equality": true, + "applicable_on_failure": false, "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": {} + "error": null } ``` @@ -461,7 +457,6 @@ In the case of the above grant and request it would run on this data: "color": "inflated", "is_inflated": false }, - "evaluation_handler": "grant", "context_type": "NONE", "context": {} }, @@ -476,8 +471,8 @@ In the case of the above grant and request it would run on this data: "balloon:inflate" ], "query": "length(request.identities.user[?department == 'Balloon Dept']) > `0`", - "evaluation_handler": "evaluate", "equality": true, + "applicable_on_failure": false, "data": {} } } diff --git a/clr.py b/clr.py new file mode 100644 index 0000000..73dd596 --- /dev/null +++ b/clr.py @@ -0,0 +1,10 @@ +from cleer import cleer_default_config, Cleer + + +clr = Cleer( + config=cleer_default_config( + python_packages=[ + "authzee" + ] + ) +) \ No newline at end of file diff --git a/full_example.py b/full_example.py index 9bd298e..d22fc4c 100644 --- a/full_example.py +++ b/full_example.py @@ -2,19 +2,23 @@ This file is runnable as-is. Destructive or unnecessary calls are commented out. """ -import json + import datetime +import json from uuid import uuid4 from authzee import ( + AuditResultPage, Authzee, + BatchAuditResultPage, DictStorage, InProcessCompute, - jmespath_execute, - paginator, authzee_specification_version, + jmespath_execute, + paginator ) + # The version of the authzee specification that this library implements. print(f"Authzee Specification Version: {authzee_specification_version}") @@ -22,17 +26,17 @@ # DictStorage uses an in-memory dict. InProcessCompute runs evaluation in the current process. storage_dict = {} authz = Authzee( - execute=jmespath_execute, # JSON query function (JMESPath) - compute_type=InProcessCompute, # Compute module type - compute_kwargs={}, # KWArgs for compute module instances - storage_type=DictStorage, # Storage module type - storage_kwargs={ # KWArgs for storage module instances + execute=jmespath_execute, # JSON query function (JMESPath) + compute_type=InProcessCompute, # Compute module type + compute_kwargs={}, # KWArgs for compute module instances + storage_type=DictStorage, # Storage module type + storage_kwargs={ # KWArgs for storage module instances "storage_dict": storage_dict }, - compute_storage_kwargs=None, # Optional override storage KWArgs for compute module - config={ # Optional AuthzeeConfigOverride + compute_storage_kwargs=None, # Optional override storage KWArgs for compute module + config={ # Optional AuthzeeConfigOverride "authzee": { - "raise_crits": True # raise exceptions on critical errors + "raise_errors": True # raise exceptions on errors } # "method_name": {} # per-method config overrides } @@ -43,24 +47,22 @@ result = authz.construct() print(json.dumps(result, indent=4)) # { -# "has_failed": false, -# "errors": {} +# "error": null # } # Initialize the authzee app. Must be run once for every Authzee instance. result = authz.start() print(json.dumps(result, indent=4)) # { -# "has_failed": false, -# "errors": {} +# "error": null # } # Validate a context definition without storing it. # Context is used to pass structured data to authorization requests. result = authz.validate_context_def( context_def={ - "context_type": "NONE", # unique identifier for this context type - "schema": { # JSON Schema + "context_type": "NONE", # unique identifier for this context type + "schema": { # JSON Schema "type": "object", "additionalProperties": False } @@ -68,8 +70,7 @@ ) print(json.dumps(result, indent=4)) # { -# "has_failed": false, -# "errors": {} +# "error": null # } # Create or update a context definition. @@ -84,8 +85,7 @@ ) print(json.dumps(result, indent=4)) # { -# "has_failed": false, -# "errors": {} +# "error": null # } # Retrieve a context definition by its context_type. @@ -99,8 +99,7 @@ # "additionalProperties": false # } # }, -# "has_failed": false, -# "errors": {} +# "error": null # } # Retrieve all context definitions. Use paginator for full iteration. @@ -108,6 +107,7 @@ for page in paginator(authz.list_context_defs): for context_def in page['context_defs']: print(json.dumps(context_def, indent=4)) + # { # "context_type": "NONE", # "schema": { @@ -121,15 +121,14 @@ # result = authz.delete_context_def(context_type="NONE") # print(json.dumps(result, indent=4)) # { -# "has_failed": false, -# "errors": {} +# "error": null # } # Validate an identity definition without storing it. # Identities describe who is being authorized. result = authz.validate_identity_def( identity_def={ - "identity_type": "user", # unique identifier for this identity type + "identity_type": "user", # unique identifier for this identity type "schema": { "type": "object", "required": [ @@ -150,8 +149,7 @@ ) print(json.dumps(result, indent=4)) # { -# "has_failed": false, -# "errors": {} +# "error": null # } # Create or update an identity definition. @@ -178,8 +176,7 @@ ) print(json.dumps(result, indent=4)) # { -# "has_failed": false, -# "errors": {} +# "error": null # } # Retrieve an identity definition by its identity_type. @@ -205,8 +202,7 @@ # } # } # }, -# "has_failed": false, -# "errors": {} +# "error": null # } # Retrieve all identity definitions. Use paginator for full iteration. @@ -220,16 +216,15 @@ # result = authz.delete_identity_def(identity_type="user") # print(json.dumps(result, indent=4)) # { -# "has_failed": false, -# "errors": {} +# "error": null # } # Validate a resource definition without storing it. # Resources define resource types and the actions that can be taken on them. result = authz.validate_resource_def( resource_def={ - "resource_type": "balloon", # unique identifier for this resource type - "actions": [ # actions that can be taken on this resource + "resource_type": "balloon", # unique identifier for this resource type + "actions": [ # actions that can be taken on this resource "balloon:read", "balloon:inflate", "balloon:pop" @@ -254,8 +249,7 @@ ) print(json.dumps(result, indent=4)) # { -# "has_failed": false, -# "errors": {} +# "error": null # } # Create or update a resource definition. @@ -287,8 +281,7 @@ ) print(json.dumps(result, indent=4)) # { -# "has_failed": false, -# "errors": {} +# "error": null # } # Retrieve a resource definition by its resource_type. @@ -319,8 +312,7 @@ # } # } # }, -# "has_failed": false, -# "errors": {} +# "error": null # } # Retrieve all resource definitions. Use paginator for full iteration. @@ -334,8 +326,7 @@ # result = authz.delete_resource_def(resource_type="balloon") # print(json.dumps(result, indent=4)) # { -# "has_failed": false, -# "errors": {} +# "error": null # } # Validate a grant without storing it. @@ -354,15 +345,14 @@ "balloon:inflate" ], "query": "length(request.identities.user[?department == 'Balloon Dept']) > `0`", - "evaluation_handler": "evaluate", "equality": True, + "applicable_on_failure": False, "data": {} } result = authz.validate_grant(grant=grant) print(json.dumps(result, indent=4)) # { -# "has_failed": false, -# "errors": {} +# "error": null # } # Enact (store) a grant to create an authorization rule. @@ -370,8 +360,7 @@ result = authz.enact(grant=grant) print(json.dumps(result, indent=4)) # { -# "has_failed": false, -# "errors": {} +# "error": null # } # Retrieve a grant by its UUID. @@ -391,12 +380,11 @@ # "balloon:inflate" # ], # "query": "length(request.identities.user[?department == 'Balloon Dept']) > `0`", -# "evaluation_handler": "evaluate", # "equality": true, +# "applicable_on_failure": false, # "data": {} # }, -# "has_failed": false, -# "errors": {} +# "error": null # } # Retrieve grants with optional effect and action filtering. Use paginator for full iteration. @@ -404,6 +392,7 @@ for page in paginator(authz.list_grants, effect="allow"): for g in page['grants']: print(json.dumps(g, indent=4)) + # { # "grant_uuid": "", # "name": "Allow inflate for balloon department", @@ -417,8 +406,8 @@ # "balloon:inflate" # ], # "query": "length(request.identities.user[?department == 'Balloon Dept']) > `0`", -# "evaluation_handler": "evaluate", # "equality": true, +# "applicable_on_failure": false, # "data": {} # } @@ -435,8 +424,7 @@ # result = authz.repeal(grant_uuid=grant_uuid, purge=False) # print(json.dumps(result, indent=4)) # { -# "has_failed": false, -# "errors": {} +# "error": null # } # Define the authorization request used for authorize, audit, and batch methods. @@ -455,7 +443,6 @@ "color": "blue", "is_inflated": False }, - "evaluation_handler": "grant", "context_type": "NONE", "context": {} } @@ -478,23 +465,24 @@ # "balloon:inflate" # ], # "query": "length(request.identities.user[?department == 'Balloon Dept']) > `0`", -# "evaluation_handler": "evaluate", # "equality": true, +# "applicable_on_failure": false, # "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": {} +# "error": null # } # Audit how each grant evaluates against the request. Returns per-grant results. # Use paginator for full iteration over all grants. # For AuthzeeAsync use: async for page in paginator_async(authz.audit, request=request): for page in paginator(authz.audit, request=request): - for g, r in zip(page['grants'], page['results']): - print(f" Grant: {g['name']}") + page: AuditResultPage + for r in page['results']: + print(f"Grant: {r['grant']['name']}") print(f" is_applicable: {r['is_applicable']}") print(f" query_result: {r['query_result']}") + # Grant: Allow inflate for balloon department # is_applicable: True # query_result: True @@ -515,10 +503,9 @@ "color": "blue", "is_inflated": False }, - "evaluation_handler": "grant", "context_type": "NONE", "context": {}, - "batch": [ # each batch item overrides the specified root fields + "batch": [ # each batch item overrides the specified root fields { "resource": { "color": "red", @@ -538,7 +525,7 @@ result = authz.batch_authorize(batch_request=batch_request) print(json.dumps(result, indent=4)) # { -# "batch_results": [ +# "batch": [ # { # "is_authorized": true, # "grant": { @@ -547,24 +534,25 @@ # ... # }, # "message": "An allow grant is applicable to the request...", -# "has_failed": false, -# "critical_errors": {} +# "error": null # }, # ... # ], -# "has_failed": false, -# "critical_errors": {} +# "error": null # } # Audit how each grant evaluates against each item in the batch request. # Use paginator for full iteration over all grants. # For AuthzeeAsync use: async for page in paginator_async(authz.batch_audit, batch_request=batch_request): for page in paginator(authz.batch_audit, batch_request=batch_request): + page: BatchAuditResultPage for g in page['grants']: print(f" Grant: {g['name']}") - for batch_result in page['batch_results']: + + for batch_result in page['batch']: for r in batch_result['results']: print(f" is_applicable: {r['is_applicable']}, query_result: {r['query_result']}") + # Grant: Allow inflate for balloon department # is_applicable: True, query_result: True # is_applicable: True, query_result: True @@ -574,16 +562,14 @@ result = authz.cleanup_latches(before=datetime.datetime(2026, 1, 1)) print(json.dumps(result, indent=4)) # { -# "has_failed": false, -# "errors": {} +# "error": null # } # Shutdown the authzee app. Should be run before exit for every Authzee instance. result = authz.shutdown() print(json.dumps(result, indent=4)) # { -# "has_failed": false, -# "errors": {} +# "error": null # } # Tear down everything that construct set up. Deletes DB tables, storage, etc. @@ -591,6 +577,5 @@ # result = authz.destroy() # print(json.dumps(result, indent=4)) # { -# "has_failed": false, -# "errors": {} +# "error": null # } diff --git a/pyproject.toml b/pyproject.toml index 9787c3b..9e8f677 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,66 @@ [build-system] requires = ["setuptools", "wheel"] build-backend = "setuptools.build_meta" + +[project] +name = "authzee" +version = "0.1.0a4" +description = "Official Authzee Python SDK." +readme = {file = "README.md", content-type = "text/markdown"} +license = "MIT" +license-files = ["LICENSE"] +authors = [ + {name = "Brandon Temple Paul", email = "btemplepgit@gmail.com"} +] +keywords = [ + "auth", + "authz", + "authzee", + "authorization", + "engine", + "framework" +] +classifiers = [ + "Development Status :: 3 - Alpha", + "Natural Language :: English", + "Operating System :: POSIX :: Linux", + "Operating System :: Unix", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: Implementation :: CPython", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Internet" +] +requires-python = ">= 3.11" +dependencies = [ + "jsonschema-rs", + "loguru" +] + +[project.optional-dependencies] +jmespath = ["jmespath"] +all = ["authzee[jmespath]"] +dev = [ + "build", + "coverage", + "moto[s3,server]", + "nox", + "piccolo-theme", + "pytest", + "pytest-asyncio", + "pytest-cov", + "sphinx", + "twine" +] + +[project.urls] +Homepage = "https://github.com/btemplep/authzee" +Repository = "https://github.com/btemplep/authzee-py" + +[tool.setuptools.packages.find] +where = ["src"] +exclude = ["tests"] diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 2e63fd1..0000000 --- a/setup.cfg +++ /dev/null @@ -1,74 +0,0 @@ -[metadata] -name = authzee -version = attr: authzee.__version__ -description = Authzee Python SDK. -long_description = file: README.md, CHANGELOG.md -long_description_content_type = text/markdown -author = Brandon Temple Paul -author_email = btemplepgit@gmail.com -url = https://github.com/btemplep/authzee -project_urls = - Repository = https://github.com/btemplep/authzee-py -classifiers = - Development Status :: 3 - Alpha - License :: OSI Approved :: MIT License - Natural Language :: English - Operating System :: POSIX :: Linux - Operating System :: Unix - Programming Language :: Python :: 3 - Programming Language :: Python :: 3 :: Only - Programming Language :: Python :: 3.11 - Programming Language :: Python :: 3.12 - Programming Language :: Python :: 3.13 - Programming Language :: Python :: 3.14 - Programming Language :: Python :: Implementation :: CPython - Topic :: Software Development :: Libraries :: Python Modules - Topic :: Internet -license = MIT -license_files = - License -keywords = - auth - authz - authzee - authorization - engine - framework -python_requires = >= 3.11 - -[options] -package_dir= - =src -packages = find: -install_requires = - jsonschema-rs - loguru - -[options.packages.find] -where=src -exclude = - tests - -[options.extras_require] -jmespath = - jmespath -# s3 = -# aioboto3 -# aiobotocore -# sql = -# SQLAlchemy ~= 2.0 -# taskiq = -# taskiq - -all = authzee[jmespath] -dev = - build - coverage - moto[s3, server] - nox - piccolo-theme - pytest - pytest-asyncio - pytest-cov - sphinx - twine diff --git a/src/authzee/__init__.py b/src/authzee/__init__.py index dcc20d6..512d3c9 100644 --- a/src/authzee/__init__.py +++ b/src/authzee/__init__.py @@ -1,56 +1,58 @@ -"""This is the official python SDK for Authzee! It is a general usage SDK that is async, extensible, and scalable. +"""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. -See {py:class}`authzee.authzee.Authzee` -or {py:class}`authzee.authzee_async.AuthzeeAsync` for asyncio support! +See [](authzee.authzee.Authzee) +or [](authzee.authzee_async.AuthzeeAsync) for asyncio support! """ -__version__ = "0.1.0a4" +__version__ = "0.1.0a5" __all__ = [ - "authzee_specification_version", "Authzee", "AuthzeeAsync", + "authzee_specification_version", "context_def_schema", - "identity_def_schema", - "resource_def_schema", + "exceptions", "grant_schema", + "identity_def_schema", "paginator", "paginator_async", - "exceptions", "reference", + "resource_def_schema", "types" ] from loguru import logger + + logger.disable("authzee") -authzee_specification_version = "0.3.0" +authzee_specification_version = "0.4.0" +from authzee import exceptions, reference, types from authzee.authzee import Authzee from authzee.authzee_async import AuthzeeAsync +from authzee.compute import * +from authzee.compute import __all__ as compute_all from authzee.core import ( context_def_schema, + grant_schema, identity_def_schema, - resource_def_schema, - grant_schema + resource_def_schema ) +from authzee.paginators import paginator, paginator_async +from authzee.storage import * +from authzee.storage import __all__ as storage_all +from authzee.types import * +from authzee.types import __all__ as types_all + +__all__ += compute_all + storage_all + types_all + + try: from authzee.jmespath import * from authzee.jmespath import __all__ as jmespath_all __all__ += jmespath_all except ModuleNotFoundError: # pragma: no cover pass - -from authzee.paginators import paginator, paginator_async -from authzee import exceptions, reference, types - -from authzee.compute import * -from authzee.compute import __all__ as compute_all -__all__ += compute_all - -from authzee.storage import * -from authzee.storage import __all__ as storage_all -__all__ += storage_all - diff --git a/src/authzee/authzee.py b/src/authzee/authzee.py index 8f1492e..a709c37 100644 --- a/src/authzee/authzee.py +++ b/src/authzee/authzee.py @@ -1,17 +1,18 @@ -"""See {py:class}`authzee.authzee.Authzee`""" +"""See [](authzee.authzee.Authzee)""" + __all__ = [ - "Authzee", + "Authzee" ] import asyncio import datetime from typing import Any, Callable, Dict, Type -from authzee.types.authzee import * -from authzee.types.config_override import AuthzeeConfigOverride +from authzee.authzee_async import AuthzeeAsync from authzee.compute.compute_module import ComputeModule from authzee.storage.storage_module import StorageModule -from authzee.authzee_async import AuthzeeAsync +from authzee.types.authzee import * +from authzee.types.config_override import AuthzeeConfigOverride class Authzee: @@ -26,14 +27,14 @@ class Authzee: compute_kwargs : Dict[str, Any] Compute module KWArgs used to create instances. storage_type : Type[StorageModule] - Storage Module Type. + Storage Module Type. storage_kwargs : Dict[str, Any] Storage module KWArgs used to create instances. compute_storage_kwargs : Dict[str, Any], optional Override storage module KWArgs that the compute module will use. May only include KWArgs you want to override. config : AuthzeeConfigOverride, optional Authzee configuration. May only include config keys you want to override. - + Examples -------- Simple full example: @@ -53,9 +54,9 @@ class Authzee: storage_kwargs={ "storage_dict": storage_dict }, - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True } # "method_name": {} } @@ -131,7 +132,6 @@ class Authzee: ], "query": "length(request.identities.user[?department == 'Balloon Dept']) > `0`", # JSON Query for the request. JMESPath is preferred # query runs on {"request": , "grant": } - "evaluation_handler": "evaluate", "equality": True, # expected result of the query "data": {} # data available to this grant } @@ -152,7 +152,6 @@ class Authzee: "color": "inflated", "is_inflated": False }, - "evaluation_handler": "grant", "context_type": "NONE", "context": {} } @@ -174,26 +173,25 @@ class Authzee: "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": {} + "error": null } ``` """ + def __init__( - self, + self, execute: Callable[[str, Any], Any], compute_type: Type[ComputeModule], compute_kwargs: Dict[str, Any], storage_type: Type[StorageModule], storage_kwargs: Dict[str, Any], - compute_storage_kwargs: Dict[str, Any] = None, - config: AuthzeeConfigOverride = None + compute_storage_kwargs: Dict[str, Any]=None, + config: AuthzeeConfigOverride=None ): self._authzee_async = AuthzeeAsync( execute=execute, @@ -206,7 +204,7 @@ def __init__( ) - def start(self, config: AuthzeeConfigOverride | None = None) -> GenericResult: + def start(self, config: AuthzeeConfigOverride | None=None) -> GenericResult: """Initialize the authzee app. Must be run once for every instance. Parameters @@ -219,9 +217,9 @@ def start(self, config: AuthzeeConfigOverride | None = None) -> GenericResult: ```python # Assumes authz is an Authzee instance result = authz.start( - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "start": { "compute_start": {}, @@ -236,32 +234,34 @@ def start(self, config: AuthzeeConfigOverride | None = None) -> GenericResult: GenericResult ```python { - "has_failed": False, - "errors": { - "start": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "start", + "message": "Description of what went wrong." } } ``` Raises ------ - StartError - If a critical error occurs during initialization. + ComputeError + An error occurred in the Compute Module. + StorageError + An error occurred in the Storage Module. LocalityIncompatibilityError If the storage and compute localities are not compatible. """ return asyncio.run(self._authzee_async.start(config)) - def shutdown( - self, - config: AuthzeeConfigOverride | None = None - ) -> GenericResult: + def shutdown(self, config: AuthzeeConfigOverride | None=None) -> GenericResult: """Shutdown the authzee app. Should be run before exit for every authzee instance. @@ -276,9 +276,9 @@ def shutdown( ```python # Assumes authz is an Authzee instance result = authz.shutdown( - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "shutdown": { "compute_shutdown": {}, @@ -293,33 +293,35 @@ def shutdown( GenericResult ```python { - "has_failed": False, - "errors": { - "start": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "shutdown", + "message": "Description of what went wrong." } } ``` Raises ------ - ShutdownError - If a critical error occurs during shutdown. + ComputeError + An error occurred in the Compute Module. + StorageError + An error occurred in the Storage Module. """ return asyncio.run(self._authzee_async.shutdown(config)) - def construct( - self, - config: AuthzeeConfigOverride | None = None - ) -> GenericResult: + def construct(self, config: AuthzeeConfigOverride | None=None) -> GenericResult: """One time setup for the life of storage and compute. Creates DB tables, storage setup, etc. - Should only be run once. + Should only be run once. Parameters ---------- @@ -331,9 +333,9 @@ def construct( ```python # Assumes authz is an Authzee instance result = authz.construct( - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "construct": { "compute_construct": {}, @@ -348,33 +350,35 @@ def construct( GenericResult ```python { - "has_failed": False, - "errors": { - "start": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "construct", + "message": "Description of what went wrong." } } ``` Raises ------ - ConstructError - If a critical error occurs during construction. + ComputeError + An error occurred in the Compute Module. + StorageError + An error occurred in the Storage Module. """ return asyncio.run(self._authzee_async.construct(config)) - def destroy( - self, - config: AuthzeeConfigOverride | None = None - ) -> GenericResult: + def destroy(self, config: AuthzeeConfigOverride | None=None) -> GenericResult: """Tear down everything that construct set up. Deletes DB tables, storage, etc. - Can be destructive. Only run if needed. + Can be destructive. Only run if needed. Parameters ---------- @@ -386,9 +390,9 @@ def destroy( ```python # Assumes authz is an Authzee instance result = authz.destroy( - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "destroy": { "compute_destroy": {}, @@ -403,30 +407,35 @@ def destroy( GenericResult ```python { - "has_failed": False, - "errors": { - "start": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "destroy", + "message": "Description of what went wrong." } } ``` Raises ------ - DestroyError - If a critical error occurs during destruction. + ComputeError + An error occurred in the Compute Module. + StorageError + An error occurred in the Storage Module. """ return asyncio.run(self._authzee_async.destroy(config)) def validate_context_def( self, - context_def: ContextDef, - config: AuthzeeConfigOverride | None = None + context_def: ContextDef, + config: AuthzeeConfigOverride | None=None ) -> GenericResult: """Validate a context definition without storing it. @@ -449,9 +458,9 @@ def validate_context_def( "additionalProperties": False } }, - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "validate_context_def": {} } @@ -463,14 +472,17 @@ def validate_context_def( GenericResult ```python { - "has_failed": False, - "errors": { - "definition": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "definition", + "message": "Description of what went wrong." } } ``` @@ -478,7 +490,7 @@ def validate_context_def( Raises ------ DefinitionError - If the context definition is invalid and raise_crits is True. + If the context definition is invalid and raise_errors is True. """ return asyncio.run( self._authzee_async.validate_context_def( @@ -489,9 +501,9 @@ def validate_context_def( def list_context_defs( - self, - page_ref: str | None = None, - config: AuthzeeConfigOverride | None = None + self, + page_ref: str | None=None, + config: AuthzeeConfigOverride | None=None ) -> ContextDefsPage: """Retrieve a page of context definitions. @@ -508,9 +520,9 @@ def list_context_defs( # Assumes authz is an Authzee instance result = authz.list_context_defs( page_ref="abc123", # optional - str | None - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "list_context_defs": { "page_size": 100, @@ -545,22 +557,25 @@ def list_context_defs( } ], "next_page_ref": None, # str | None - None means pagination is complete - "has_failed": False, - "errors": { - "page_reference": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "page_reference", + "message": "Description of what went wrong." } } ``` Raises ------ - PageReferenceError - If the page reference is invalid. + StorageError + An error occurred in the Storage Module. """ return asyncio.run( self._authzee_async.list_context_defs( @@ -571,9 +586,9 @@ def list_context_defs( def get_context_def( - self, - context_type: str, - config: AuthzeeConfigOverride | None = None + self, + context_type: str, + config: AuthzeeConfigOverride | None=None ) -> ContextDefResult: """Retrieve a context definition by its `context_type`. @@ -590,9 +605,9 @@ def get_context_def( # Assumes authz is an Authzee instance result = authz.get_context_def( context_type="NONE", - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "get_context_def": { "use_cache": False @@ -606,21 +621,24 @@ def get_context_def( ContextDefResult ```python { - "context_def": { # dict | None + "context_def": { # dict | None "context_type": "NONE", "schema": { "type": "object", "additionalProperties": False } }, - "has_failed": False, - "errors": { - "resource_not_found": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "resource_not_found", + "message": "Description of what went wrong." } } ``` @@ -639,9 +657,9 @@ def get_context_def( def put_context_def( - self, - context_def: ContextDef, - config: AuthzeeConfigOverride | None = None + self, + context_def: ContextDef, + config: AuthzeeConfigOverride | None=None ) -> GenericResult: """Create or update a context definition. @@ -664,9 +682,9 @@ def put_context_def( "additionalProperties": False } }, - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "put_context_def": {} } @@ -678,14 +696,17 @@ def put_context_def( GenericResult ```python { - "has_failed": False, - "errors": { - "definition": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "definition", + "message": "Description of what went wrong." } } ``` @@ -693,7 +714,7 @@ def put_context_def( Raises ------ DefinitionError - If the context definition is invalid and raise_crits is True. + If the context definition is invalid and raise_errors is True. """ return asyncio.run( self._authzee_async.put_context_def( @@ -704,9 +725,9 @@ def put_context_def( def delete_context_def( - self, - context_type: str, - config: AuthzeeConfigOverride | None = None + self, + context_type: str, + config: AuthzeeConfigOverride | None=None ) -> GenericResult: """Deletes the context definition if found. @@ -723,9 +744,9 @@ def delete_context_def( # Assumes authz is an Authzee instance result = authz.delete_context_def( context_type="NONE", - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "delete_context_def": {} } @@ -737,22 +758,25 @@ def delete_context_def( GenericResult ```python { - "has_failed": False, - "errors": { - "resource_not_found": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "resource_not_found", + "message": "Description of what went wrong." } } ``` Raises ------ - DeleteError - If a critical error occurs during deletion. + StorageError + An error occurred in the Storage Module. """ return asyncio.run( self._authzee_async.delete_context_def( @@ -764,8 +788,8 @@ def delete_context_def( def validate_identity_def( self, - identity_def: IdentityDef, - config: AuthzeeConfigOverride | None = None + identity_def: IdentityDef, + config: AuthzeeConfigOverride | None=None ) -> GenericResult: """Validate an identity definition without storing it. @@ -795,9 +819,9 @@ def validate_identity_def( } } }, - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "validate_identity_def": {} } @@ -809,14 +833,17 @@ def validate_identity_def( GenericResult ```python { - "has_failed": False, - "errors": { - "definition": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "definition", + "message": "Description of what went wrong." } } ``` @@ -824,7 +851,7 @@ def validate_identity_def( Raises ------ DefinitionError - If the identity definition is invalid and raise_crits is True. + If the identity definition is invalid and raise_errors is True. """ return asyncio.run( self._authzee_async.validate_identity_def( @@ -835,9 +862,9 @@ def validate_identity_def( def list_identity_defs( - self, - page_ref: str | None = None, - config: AuthzeeConfigOverride | None = None + self, + page_ref: str | None=None, + config: AuthzeeConfigOverride | None=None ) -> IdentityDefsPage: """Retrieve a page of identity definitions. @@ -854,9 +881,9 @@ def list_identity_defs( # Assumes authz is an Authzee instance result = authz.list_identity_defs( page_ref="abc123", # optional - str | None - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "list_identity_defs": { "page_size": 100, @@ -895,22 +922,25 @@ def list_identity_defs( } ], "next_page_ref": None, # str | None - None means pagination is complete - "has_failed": False, - "errors": { - "page_reference": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "page_reference", + "message": "Description of what went wrong." } } ``` Raises ------ - PageReferenceError - If the page reference is invalid. + StorageError + An error occurred in the Storage Module. """ return asyncio.run( self._authzee_async.list_identity_defs( @@ -921,9 +951,9 @@ def list_identity_defs( def get_identity_def( - self, + self, identity_type: str, - config: AuthzeeConfigOverride | None = None + config: AuthzeeConfigOverride | None=None ) -> IdentityDefResult: """Retrieve an identity definition by its `identity_type`. @@ -940,9 +970,9 @@ def get_identity_def( # Assumes authz is an Authzee instance result = authz.get_identity_def( identity_type="user", - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "get_identity_def": { "use_cache": False @@ -956,7 +986,7 @@ def get_identity_def( IdentityDefResult ```python { - "identity_def": { # dict | None + "identity_def": { # dict | None "identity_type": "user", "schema": { "type": "object", @@ -967,14 +997,17 @@ def get_identity_def( } } }, - "has_failed": False, - "errors": { - "resource_not_found": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "resource_not_found", + "message": "Description of what went wrong." } } ``` @@ -993,9 +1026,9 @@ def get_identity_def( def put_identity_def( - self, - identity_def: IdentityDef, - config: AuthzeeConfigOverride | None = None + self, + identity_def: IdentityDef, + config: AuthzeeConfigOverride | None=None ) -> GenericResult: """Create or update an identity definition. @@ -1025,9 +1058,9 @@ def put_identity_def( } } }, - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "put_identity_def": {} } @@ -1039,14 +1072,17 @@ def put_identity_def( GenericResult ```python { - "has_failed": False, - "errors": { - "definition": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "definition", + "message": "Description of what went wrong." } } ``` @@ -1054,7 +1090,7 @@ def put_identity_def( Raises ------ DefinitionError - If the identity definition is invalid and raise_crits is True. + If the identity definition is invalid and raise_errors is True. """ return asyncio.run( self._authzee_async.put_identity_def( @@ -1065,9 +1101,9 @@ def put_identity_def( def delete_identity_def( - self, + self, identity_type: str, - config: AuthzeeConfigOverride | None = None + config: AuthzeeConfigOverride | None=None ) -> GenericResult: """Deletes the identity definition if found. @@ -1084,9 +1120,9 @@ def delete_identity_def( # Assumes authz is an Authzee instance result = authz.delete_identity_def( identity_type="user", - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "delete_identity_def": {} } @@ -1098,22 +1134,25 @@ def delete_identity_def( GenericResult ```python { - "has_failed": False, - "errors": { - "resource_not_found": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "resource_not_found", + "message": "Description of what went wrong." } } ``` Raises ------ - DeleteError - If a critical error occurs during deletion. + StorageError + An error occurred in the Storage Module. """ return asyncio.run( self._authzee_async.delete_identity_def( @@ -1125,8 +1164,8 @@ def delete_identity_def( def validate_resource_def( self, - resource_def: ResourceDef, - config: AuthzeeConfigOverride | None = None + resource_def: ResourceDef, + config: AuthzeeConfigOverride | None=None ) -> GenericResult: """Validate a resource definition without storing it. @@ -1161,9 +1200,9 @@ def validate_resource_def( } } }, - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "validate_resource_def": {} } @@ -1175,14 +1214,17 @@ def validate_resource_def( GenericResult ```python { - "has_failed": False, - "errors": { - "definition": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "definition", + "message": "Description of what went wrong." } } ``` @@ -1190,7 +1232,7 @@ def validate_resource_def( Raises ------ DefinitionError - If the resource definition is invalid and raise_crits is True. + If the resource definition is invalid and raise_errors is True. """ return asyncio.run( self._authzee_async.validate_resource_def( @@ -1201,9 +1243,9 @@ def validate_resource_def( def list_resource_defs( - self, - page_ref: str | None = None, - config: AuthzeeConfigOverride | None = None + self, + page_ref: str | None=None, + config: AuthzeeConfigOverride | None=None ) -> ResourceDefsPage: """Retrieve a page of resource definitions. @@ -1220,9 +1262,9 @@ def list_resource_defs( # Assumes authz is an Authzee instance result = authz.list_resource_defs( page_ref="abc123", # optional - str | None - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "list_resource_defs": { "page_size": 100, @@ -1265,22 +1307,25 @@ def list_resource_defs( } ], "next_page_ref": None, # str | None - None means pagination is complete - "has_failed": False, - "errors": { - "page_reference": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "page_reference", + "message": "Description of what went wrong." } } ``` Raises ------ - PageReferenceError - If the page reference is invalid. + StorageError + An error occurred in the Storage Module. """ return asyncio.run( self._authzee_async.list_resource_defs( @@ -1291,9 +1336,9 @@ def list_resource_defs( def get_resource_def( - self, + self, resource_type: str, - config: AuthzeeConfigOverride | None = None + config: AuthzeeConfigOverride | None=None ) -> ResourceDefResult: """Retrieve a resource definition by its `resource_type`. @@ -1310,9 +1355,9 @@ def get_resource_def( # Assumes authz is an Authzee instance result = authz.get_resource_def( resource_type="balloon", - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "get_resource_def": { "use_cache": False @@ -1326,7 +1371,7 @@ def get_resource_def( ResourceDefResult ```python { - "resource_def": { # dict | None + "resource_def": { # dict | None "resource_type": "balloon", "actions": [ "balloon:read", @@ -1341,14 +1386,17 @@ def get_resource_def( } } }, - "has_failed": False, - "errors": { - "resource_not_found": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "resource_not_found", + "message": "Description of what went wrong." } } ``` @@ -1367,9 +1415,9 @@ def get_resource_def( def put_resource_def( - self, + self, resource_def: ResourceDef, - config: AuthzeeConfigOverride | None = None + config: AuthzeeConfigOverride | None=None ) -> GenericResult: """Create or update a resource definition. @@ -1404,9 +1452,9 @@ def put_resource_def( } } }, - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "put_resource_def": {} } @@ -1418,14 +1466,17 @@ def put_resource_def( GenericResult ```python { - "has_failed": False, - "errors": { - "definition": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "definition", + "message": "Description of what went wrong." } } ``` @@ -1433,7 +1484,7 @@ def put_resource_def( Raises ------ DefinitionError - If the resource definition is invalid and raise_crits is True. + If the resource definition is invalid and raise_errors is True. """ return asyncio.run( self._authzee_async.put_resource_def( @@ -1444,9 +1495,9 @@ def put_resource_def( def delete_resource_def( - self, + self, resource_type: str, - config: AuthzeeConfigOverride | None = None + config: AuthzeeConfigOverride | None=None ) -> GenericResult: """Deletes the resource definition if found. @@ -1463,9 +1514,9 @@ def delete_resource_def( # Assumes authz is an Authzee instance result = authz.delete_resource_def( resource_type="balloon", - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "delete_resource_def": {} } @@ -1477,22 +1528,25 @@ def delete_resource_def( GenericResult ```python { - "has_failed": False, - "errors": { - "resource_not_found": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "resource_not_found", + "message": "Description of what went wrong." } } ``` Raises ------ - DeleteError - If a critical error occurs during deletion. + StorageError + An error occurred in the Storage Module. """ return asyncio.run( self._authzee_async.delete_resource_def( @@ -1503,9 +1557,9 @@ def delete_resource_def( def validate_grant( - self, + self, grant: Grant, - config: AuthzeeConfigOverride | None = None + config: AuthzeeConfigOverride | None=None ) -> GenericResult: """Validate a grant without storing it. @@ -1533,13 +1587,12 @@ def validate_grant( "balloon:inflate" ], "query": "length(request.identities.user[?department == 'Balloon Dept']) > `0`", - "evaluation_handler": "evaluate", # "evaluate" | "error" | "critical" "equality": True, # bool | str | int | float | None | list | dict "data": {} }, - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "validate_grant": {} } @@ -1551,14 +1604,17 @@ def validate_grant( GenericResult ```python { - "has_failed": False, - "errors": { - "grant": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "grant", + "message": "Description of what went wrong." } } ``` @@ -1566,7 +1622,7 @@ def validate_grant( Raises ------ GrantError - If the grant is invalid and raise_crits is True. + If the grant is invalid and raise_errors is True. """ return asyncio.run( self._authzee_async.validate_grant( @@ -1577,9 +1633,9 @@ def validate_grant( def enact( - self, + self, grant: Grant, - config: AuthzeeConfigOverride | None = None + config: AuthzeeConfigOverride | None=None ) -> GenericResult: """Enact (store) a grant to create an authorization rule. @@ -1607,13 +1663,12 @@ def enact( "balloon:inflate" ], "query": "length(request.identities.user[?department == 'Balloon Dept']) > `0`", - "evaluation_handler": "evaluate", # "evaluate" | "error" | "critical" "equality": True, # bool | str | int | float | None | list | dict "data": {} }, - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "enact": {} } @@ -1625,14 +1680,17 @@ def enact( GenericResult ```python { - "has_failed": False, - "errors": { - "grant": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "grant", + "message": "Description of what went wrong." } } ``` @@ -1640,21 +1698,18 @@ def enact( Raises ------ GrantError - If the grant is invalid and raise_crits is True. + If the grant is invalid and raise_errors is True. """ return asyncio.run( - self._authzee_async.enact( - grant=grant, - config=config - ) + self._authzee_async.enact(grant=grant, config=config) ) def repeal( - self, - grant_uuid: str, - purge: bool = False, - config: AuthzeeConfigOverride | None = None + self, + grant_uuid: str, + purge: bool=False, + config: AuthzeeConfigOverride | None=None ) -> GenericResult: """Repeal (remove) a grant by its UUID. @@ -1663,7 +1718,7 @@ def repeal( grant_uuid : str The UUID of the grant to repeal. purge : bool, default=False - If True, all grants and partitions may be scanned to completely remove. + If True, all grants and partitions may be scanned to completely remove. Useful if corruption by update is suspected. config : AuthzeeConfigOverride | None, optional Override configuration for this call. Only include keys to override. @@ -1675,9 +1730,9 @@ def repeal( result = authz.repeal( grant_uuid="0da5dfc6-c919-4bd6-b80f-a351a9ac8d27", purge=False, # optional - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "repeal": {} } @@ -1689,14 +1744,9 @@ def repeal( GenericResult ```python { - "has_failed": True, - "errors": { - "resource_not_found": [ - { - "is_critical": True, - "message": "Error message." - } - ] + "error": { # dict | None + "error_type": "resource_not_found", + "message": "Error message." } } ``` @@ -1716,9 +1766,9 @@ def repeal( def get_grant( - self, + self, grant_uuid: str, - config: AuthzeeConfigOverride | None = None + config: AuthzeeConfigOverride | None=None ) -> GrantResult: """Retrieve a grant by its UUID. @@ -1735,9 +1785,9 @@ def get_grant( # Assumes authz is an Authzee instance result = authz.get_grant( grant_uuid="0da5dfc6-c919-4bd6-b80f-a351a9ac8d27", - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "get_grant": { "use_cache": False @@ -1751,7 +1801,7 @@ def get_grant( GrantResult ```python { - "grant": { # dict | None + "grant": { # dict | None "grant_uuid": "0da5dfc6-c919-4bd6-b80f-a351a9ac8d27", "name": "Allow inflate", "description": "Allow balloon inflate for users.", @@ -1763,18 +1813,20 @@ def get_grant( "balloon:inflate" ], "query": "length(request.identities.user[?department == 'Balloon Dept']) > `0`", - "evaluation_handler": "evaluate", "equality": True, "data": {} }, - "has_failed": False, - "errors": { - "resource_not_found": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "resource_not_found", + "message": "Description of what went wrong." } } ``` @@ -1794,10 +1846,10 @@ def get_grant( def list_grants( self, - effect: str | None = None, - action: str | None = None, - page_ref: str | None = None, - config: AuthzeeConfigOverride | None = None + effect: str | None=None, + action: str | None=None, + page_ref: str | None=None, + config: AuthzeeConfigOverride | None=None ) -> GrantsPage: """Retrieve a page of grants with optional filtering. @@ -1820,9 +1872,9 @@ def list_grants( effect="allow", # optional - str | None - "allow" | "deny" action="balloon:inflate", # optional - str | None page_ref="abc123", # optional - str | None - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "list_grants": { "page_size": 100, @@ -1860,28 +1912,30 @@ def list_grants( "balloon:inflate" ], "query": "length(request.identities.user[?department == 'Balloon Dept']) > `0`", - "evaluation_handler": "evaluate", "equality": True, "data": {} } ], "next_page_ref": None, # str | None - None means pagination is complete - "has_failed": False, - "errors": { - "page_reference": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "page_reference", + "message": "Description of what went wrong." } } ``` Raises ------ - PageReferenceError - If the page reference is invalid. + StorageError + An error occurred in the Storage Module. """ return asyncio.run( self._authzee_async.list_grants( @@ -1895,10 +1949,10 @@ def list_grants( def list_grant_refs( self, - effect: str | None = None, - action: str | None = None, - page_ref: str | None = None, - config: AuthzeeConfigOverride | None = None + effect: str | None=None, + action: str | None=None, + page_ref: str | None=None, + config: AuthzeeConfigOverride | None=None ) -> PageRefsPage: """Retrieve a page of grant page references for parallel pagination. @@ -1921,9 +1975,9 @@ def list_grant_refs( effect="allow", # optional - str | None - "allow" | "deny" action="balloon:inflate", # optional - str | None page_ref="abc123", # optional - str | None - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "list_grant_refs": { "page_size": 10, @@ -1953,22 +2007,25 @@ def list_grant_refs( "page_ref_2" ], "next_page_ref": None, # str | None - None means pagination is complete - "has_failed": False, - "errors": { - "page_reference": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "page_reference", + "message": "Description of what went wrong." } } ``` Raises ------ - PageReferenceError - If the page reference is invalid. + StorageError + An error occurred in the Storage Module. ParallelPaginationNotSupported If the storage backend does not support parallel pagination. """ @@ -1983,9 +2040,9 @@ def list_grant_refs( def cleanup_latches( - self, - before: datetime.datetime, - config: AuthzeeConfigOverride | None = None + self, + before: datetime.datetime, + config: AuthzeeConfigOverride | None=None ) -> GenericResult: """Clean up storage latches created before the given datetime. @@ -2007,9 +2064,9 @@ def cleanup_latches( # Assumes authz is an Authzee instance result = authz.cleanup_latches( before=datetime.datetime(2026, 1, 1), - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "cleanup_latches": {} } @@ -2021,22 +2078,27 @@ def cleanup_latches( GenericResult ```python { - "has_failed": False, - "errors": { - "start": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "start", + "message": "Description of what went wrong." } } ``` Raises ------ - StartError - If a critical error occurs during cleanup. + ComputeError + An error occurred in the Compute Module. + StorageError + An error occurred in the Storage Module. """ return asyncio.run( self._authzee_async.cleanup_latches( @@ -2048,9 +2110,9 @@ def cleanup_latches( def audit( self, - request: AuthzeeRequest, - page_ref: str | None = None, - config: AuthzeeConfigOverride | None = None + request: AuthzeeRequest, + page_ref: str | None=None, + config: AuthzeeConfigOverride | None=None ) -> AuditResultPage: """Retrieve a page of audit results showing how each grant evaluated against the request. @@ -2083,14 +2145,13 @@ def audit( "color": "blue", "is_inflated": False }, - "evaluation_handler": "grant", # "grant" | "evaluate" | "error" | "critical" "context_type": "NONE", "context": {} }, page_ref="abc123", # optional - str | None - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "audit": { "validate_request": { @@ -2135,7 +2196,7 @@ def audit( for page in paginator( # Assumes authz is an Authzee instance authz.audit, - request={ F + request={ "identities": { "user": [ { @@ -2150,13 +2211,12 @@ def audit( "color": "blue", "is_inflated": False }, - "evaluation_handler": "grant", "context_type": "NONE", "context": {} } ): - for grant, result in zip(page['grants'], page['results']): - print(grant['grant_uuid'], result['is_applicable']) + for result_item in page['results']: + print(result_item['grant']['grant_uuid'], result_item['is_applicable']) ``` Returns @@ -2164,38 +2224,38 @@ def audit( AuditResultPage ```python { - "grants": [ - { - "grant_uuid": "0da5dfc6-c919-4bd6-b80f-a351a9ac8d27", - "name": "Allow inflate", - "description": "Allow balloon inflate for users.", - "tags": {}, - "effect": "allow", - "actions": [ - "balloon:inflate" - ], - "query": "length(request.identities.user[?department == 'Balloon Dept']) > `0`", - "evaluation_handler": "evaluate", - "equality": True, - "data": {} - } - ], "results": [ { + "grant": { + "grant_uuid": "0da5dfc6-c919-4bd6-b80f-a351a9ac8d27", + "name": "Allow inflate", + "description": "Allow balloon inflate for users.", + "tags": {}, + "effect": "allow", + "actions": [ + "balloon:inflate" + ], + "query": "length(request.identities.user[?department == 'Balloon Dept']) > `0`", + "equality": True, + "data": {} + }, "is_applicable": True, "query_result": True, - "errors": {} + "error": None } ], "next_page_ref": None, # str | None - None means pagination is complete - "has_failed": False, - "errors": { - "request": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "request", + "message": "Description of what went wrong." } } ``` @@ -2203,11 +2263,9 @@ def audit( Raises ------ RequestError - If the request is invalid and raise_crits is True. - EvaluationError - If a critical evaluation error occurs and raise_crits is True. - PageReferenceError - If the page reference is invalid. + If the request is invalid and raise_errors is True. + StorageError + An error occurred in the Storage Module. """ return asyncio.run( self._authzee_async.audit( @@ -2219,9 +2277,9 @@ def audit( def authorize( - self, + self, request: AuthzeeRequest, - config: AuthzeeConfigOverride | None = None + config: AuthzeeConfigOverride | None=None ) -> AuthorizeResult: """Determine if the request is authorized. @@ -2252,13 +2310,12 @@ def authorize( "color": "blue", "is_inflated": False }, - "evaluation_handler": "grant", # "grant" | "evaluate" | "error" | "critical" "context_type": "NONE", "context": {} }, - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "authorize": { "validate_request": { @@ -2307,7 +2364,7 @@ def authorize( ```python { "is_authorized": True, - "grant": { # dict | None + "grant": { # dict | None "grant_uuid": "0da5dfc6-c919-4bd6-b80f-a351a9ac8d27", "name": "Allow inflate", "description": "Allow balloon inflate for users.", @@ -2317,19 +2374,21 @@ def authorize( "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": { - "evaluation": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "evaluation", + "message": "Description of what went wrong." } } ``` @@ -2337,9 +2396,11 @@ def authorize( Raises ------ RequestError - If the request is invalid and raise_crits is True. - EvaluationError - If a critical evaluation error occurs and raise_crits is True. + If the request is invalid and raise_errors is True. + ComputeError + An error occurred in the Compute Module. + StorageError + An error occurred in the Storage Module. """ return asyncio.run( self._authzee_async.authorize( @@ -2351,9 +2412,9 @@ def authorize( def batch_audit( self, - batch_request: AuthzeeBatchRequest, - page_ref: str | None = None, - config: AuthzeeConfigOverride | None = None + batch_request: AuthzeeBatchRequest, + page_ref: str | None=None, + config: AuthzeeConfigOverride | None=None ) -> BatchAuditResultPage: """Retrieve a page of batch audit results showing how each grant evaluated against the batch request. @@ -2386,12 +2447,11 @@ def batch_audit( "color": "blue", "is_inflated": False }, - "evaluation_handler": "grant", # "grant" | "evaluate" | "error" | "critical" "context_type": "NONE", "context": {}, "batch": [ { - "resource": { # optional - dict | None + "resource": { # optional - dict | None "color": "red", "is_inflated": True } @@ -2399,9 +2459,9 @@ def batch_audit( ] }, page_ref="abc123", # optional - str | None - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "batch_audit": { "validate_batch_request": { @@ -2461,7 +2521,6 @@ def batch_audit( "color": "blue", "is_inflated": False }, - "evaluation_handler": "grant", "context_type": "NONE", "context": {}, "batch": [ @@ -2474,8 +2533,8 @@ def batch_audit( ] } ): - for batch_result in page['batch_results']: - for result_item in batch_result['results']: + for batch_item in page['batch']: + for result_item in batch_item['results']: print(result_item['is_applicable']) ``` @@ -2484,44 +2543,43 @@ def batch_audit( BatchAuditResultPage ```python { - "grants": [ - { - "grant_uuid": "0da5dfc6-c919-4bd6-b80f-a351a9ac8d27", - "name": "Allow inflate", - "description": "Allow balloon inflate for users.", - "tags": {}, - "effect": "allow", - "actions": [ - "balloon:inflate" - ], - "query": "length(request.identities.user[?department == 'Balloon Dept']) > `0`", - "evaluation_handler": "evaluate", - "equality": True, - "data": {} - } - ], - "batch_results": [ + "batch": [ { "results": [ { + "grant": { + "grant_uuid": "0da5dfc6-c919-4bd6-b80f-a351a9ac8d27", + "name": "Allow inflate", + "description": "Allow balloon inflate for users.", + "tags": {}, + "effect": "allow", + "actions": [ + "balloon:inflate" + ], + "query": "length(request.identities.user[?department == 'Balloon Dept']) > `0`", + "equality": True, + "data": {} + }, "is_applicable": True, "query_result": True, - "errors": {} + "error": None } ], - "has_failed": False, - "errors": {} + "error": None } ], "next_page_ref": None, # str | None - None means pagination is complete - "has_failed": False, - "errors": { - "request": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "request", + "message": "Description of what went wrong." } } ``` @@ -2529,11 +2587,9 @@ def batch_audit( Raises ------ RequestError - If the batch request is invalid and raise_crits is True. - EvaluationError - If a critical evaluation error occurs and raise_crits is True. - PageReferenceError - If the page reference is invalid. + If the batch request is invalid and raise_errors is True. + StorageError + An error occurred in the Storage Module. """ return asyncio.run( self._authzee_async.batch_audit( @@ -2545,9 +2601,9 @@ def batch_audit( def batch_authorize( - self, + self, batch_request: AuthzeeBatchRequest, - config: AuthzeeConfigOverride | None = None + config: AuthzeeConfigOverride | None=None ) -> BatchAuthorizeResult: """Determine if each item in the batch request is authorized. @@ -2578,21 +2634,20 @@ def batch_authorize( "color": "blue", "is_inflated": False }, - "evaluation_handler": "grant", # "grant" | "evaluate" | "error" | "critical" "context_type": "NONE", "context": {}, "batch": [ { - "resource": { # optional - dict | None + "resource": { # optional - dict | None "color": "red", "is_inflated": True } } ] }, - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "batch_authorize": { "validate_batch_request": { @@ -2666,7 +2721,7 @@ def batch_authorize( BatchAuthorizeResult ```python { - "batch_results": [ + "batch": [ { "is_authorized": True, "grant": { @@ -2679,30 +2734,30 @@ def batch_authorize( "balloon:inflate" ], "query": "length(request.identities.user[?department == 'Balloon Dept']) > `0`", - "evaluation_handler": "evaluate", "equality": True, "data": {} }, "message": "Authorized by grant.", - "has_failed": False, - "critical_errors": {} + "error": None }, { "is_authorized": False, "grant": None, "message": "No matching allow grants.", - "has_failed": False, - "critical_errors": {} + "error": None } ], - "has_failed": False, - "critical_errors": { - "evaluation": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "evaluation", + "message": "Description of what went wrong." } } ``` @@ -2710,9 +2765,11 @@ def batch_authorize( Raises ------ RequestError - If the batch request is invalid and raise_crits is True. - EvaluationError - If a critical evaluation error occurs and raise_crits is True. + If the batch request is invalid and raise_errors is True. + ComputeError + An error occurred in the Compute Module. + StorageError + An error occurred in the Storage Module. """ return asyncio.run( self._authzee_async.batch_authorize( diff --git a/src/authzee/authzee_async.py b/src/authzee/authzee_async.py index 1d1065e..5eecf99 100644 --- a/src/authzee/authzee_async.py +++ b/src/authzee/authzee_async.py @@ -1,23 +1,22 @@ -"""See {py:class}`authzee.authzee_async.AuthzeeAsync`""" +"""See [](authzee.authzee_async.AuthzeeAsync)""" + __all__ = [ - "AuthzeeAsync", + "AuthzeeAsync" ] from asyncio import gather import datetime from typing import Any, Callable, Dict, Type +from authzee.compute.compute_module import ComputeModule from authzee.config import default_config, override_config -from authzee.types.authzee import * -from authzee.types.config import AuthzeeConfig -from authzee.types.config_override import AuthzeeConfigOverride from authzee.exceptions import * from authzee.exceptions import _exception_map -from authzee import core -from authzee.compute.compute_module import ComputeModule -from authzee.storage.storage_module import StorageModule - from authzee.module_locality import locality_compatibility +from authzee.storage.storage_module import StorageModule +from authzee.types.authzee import * +from authzee.types.config import AuthzeeConfig +from authzee.types.config_override import AuthzeeConfigOverride class AuthzeeAsync: @@ -32,14 +31,14 @@ class AuthzeeAsync: compute_kwargs : Dict[str, Any] Compute module KWArgs used to create instances. storage_type : Type[StorageModule] - Storage Module Type. + Storage Module Type. storage_kwargs : Dict[str, Any] Storage module KWArgs used to create instances. compute_storage_kwargs : Dict[str, Any], optional Override storage module KWArgs that the compute module will use. May only include KWArgs you want to override. config : AuthzeeConfigOverride, optional Authzee configuration. May only include config keys you want to override. - + Examples -------- Simple full example: @@ -132,7 +131,6 @@ async def main(): "balloon:inflate" ], "query": "length(request.identities.user[?department == 'Balloon Dept']) > `0`", - "evaluation_handler": "evaluate", "equality": True, "data": {} } @@ -153,7 +151,6 @@ async def main(): "color": "inflated", "is_inflated": False }, - "evaluation_handler": "grant", "context_type": "NONE", "context": {} } @@ -177,26 +174,25 @@ async def main(): "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": {} + "error": null } ``` """ + def __init__( - self, + self, execute: Callable[[str, Any], Any], compute_type: Type[ComputeModule], compute_kwargs: Dict[str, Any], storage_type: Type[StorageModule], storage_kwargs: Dict[str, Any], - compute_storage_kwargs: Dict[str, Any] = None, - config: AuthzeeConfigOverride = None + compute_storage_kwargs: Dict[str, Any]=None, + config: AuthzeeConfigOverride=None ): self._execute = execute self._compute_type = compute_type @@ -204,44 +200,30 @@ def __init__( self._storage_type = storage_type self._storage_kwargs = storage_kwargs self._compute_storage_kwargs = storage_kwargs if compute_storage_kwargs is None else storage_kwargs | compute_storage_kwargs - self._config : AuthzeeConfig = override_config(config, default_config) + self._config: AuthzeeConfig = override_config(config, default_config) self._compute: ComputeModule = None self._storage: StorageModule = None - - - def _raise_result(self, result: GenericResult, config: AuthzeeConfigOverride) -> None: - if config['authzee']['raise_crits'] is True and result['has_failed'] is True: - if "critical_errors" in result: - errors = result['critical_errors'] - else: - errors = result['errors'] - - for error_type in errors: - for err in errors[error_type]: - if err['is_critical']: - raise _exception_map[error_type]( - message=err['message'], - result=result - ) - - - def _combine_errors(self, result: GenericResult, *args: dict) -> None: - errors = result['errors'] - for new_result in args: - if new_result['has_failed'] is True: - result['has_failed'] = True - - new_errors = new_result['errors'] - for k in errors: - if k in new_errors: - errors[k] += new_errors[k] - - for k in new_errors: - if k not in errors: - errors[k] = new_errors[k] - - - async def start(self, config: AuthzeeConfigOverride | None = None) -> GenericResult: + + + def _raise_result( + self, + result: GenericResult, + config: AuthzeeConfigOverride + ) -> None: + if ( + config['authzee']['raise_errors'] is True + and result['error'] is not None + ): + raise _exception_map[result['error']['error_type']]( + message=result['error']['message'], + result=result + ) + + + async def start( + self, + config: AuthzeeConfigOverride | None=None + ) -> GenericResult: """Initialize the authzee app. Must be run once for every instance. Parameters @@ -254,9 +236,9 @@ async def start(self, config: AuthzeeConfigOverride | None = None) -> GenericRes ```python # Assumes authz is an AuthzeeAsync instance and this is in a running event loop result = await authz.start( - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "start": { "compute_start": {}, @@ -271,22 +253,27 @@ async def start(self, config: AuthzeeConfigOverride | None = None) -> GenericRes GenericResult ```python { - "has_failed": False, - "errors": { - "start": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "start", + "message": "Description of what went wrong." } } ``` Raises ------ - StartError - If a critical error occurs during initialization. + ComputeError + An error occurred in the Compute Module. + StorageError + An error occurred in the Storage Module. LocalityIncompatibilityError If the storage and compute localities are not compatible. """ @@ -294,8 +281,7 @@ async def start(self, config: AuthzeeConfigOverride | None = None) -> GenericRes self._compute = self._compute_type(**self._compute_kwargs) self._storage = self._storage_type(**self._storage_kwargs) result = { - "has_failed": False, - "errors": {} + "error": None } compute_results, storage_result = await gather( self._compute.start( @@ -306,23 +292,28 @@ async def start(self, config: AuthzeeConfigOverride | None = None) -> GenericRes ), self._storage.start(config['start']['storage_start']) ) - core.combine_errors(result, compute_results, storage_result) + if compute_results['error'] is not None: + result['error'] = compute_results['error'] + elif storage_result['error'] is not None: + result['error'] = storage_result['error'] + self._raise_result(result, config) - if self._storage.locality not in locality_compatibility[self._compute.locality]: - result['errors']['locality_incompatibility'] = [ - { - "is_critical": False, - "message": f"The '{self._storage.locality}' storage locality is not compatible with the '{self._compute.locality}' compute locality." - } - ] + if ( + self._storage.locality + not in locality_compatibility[self._compute.locality] + ): + result['error'] = { + "error_type": "locality_incompatibility", + "message": f"The '{self._storage.locality}' storage locality is not compatible with the '{self._compute.locality}' compute locality." + } return result async def shutdown( - self, - config: AuthzeeConfigOverride | None = None + self, + config: AuthzeeConfigOverride | None=None ) -> GenericResult: """Shutdown the authzee app. @@ -338,9 +329,9 @@ async def shutdown( ```python # Assumes authz is an AuthzeeAsync instance and this is in a running event loop result = await authz.shutdown( - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "shutdown": { "compute_shutdown": {}, @@ -355,41 +346,53 @@ async def shutdown( GenericResult ```python { - "has_failed": False, - "errors": { - "start": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "shutdown", + "message": "Description of what went wrong." } } ``` Raises ------ - ShutdownError - If a critical error occurs during shutdown. + ComputeError + An error occurred in the Compute Module. + StorageError + An error occurred in the Storage Module. """ config = override_config(config, self._config) result = { - "has_failed": False, - "errors": {} + "error": None } compute_result, storage_result = await gather( - self._compute.shutdown(config['shutdown']['compute_shutdown']), - self._storage.shutdown(config['shutdown']['storage_shutdown']) + self._compute.shutdown( + config['shutdown']['compute_shutdown'] + ), + self._storage.shutdown( + config['shutdown']['storage_shutdown'] + ) ) - core.combine_errors(result, compute_result, storage_result) + if compute_result['error'] is not None: + result['error'] = compute_result['error'] + elif storage_result['error'] is not None: + result['error'] = storage_result['error'] + self._raise_result(result, config) return result - + async def construct( - self, - config: AuthzeeConfigOverride | None = None + self, + config: AuthzeeConfigOverride | None=None ) -> GenericResult: """One time setup for the life of storage and compute. Creates DB tables, storage setup, etc. @@ -405,9 +408,9 @@ async def construct( ```python # Assumes authz is an AuthzeeAsync instance and this is in a running event loop result = await authz.construct( - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "construct": { "compute_construct": {}, @@ -422,27 +425,31 @@ async def construct( GenericResult ```python { - "has_failed": False, - "errors": { - "start": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "construct", + "message": "Description of what went wrong." } } ``` Raises ------ - ConstructError - If a critical error occurs during construction. + ComputeError + An error occurred in the Compute Module. + StorageError + An error occurred in the Storage Module. """ config = override_config(config, self._config) result = { - "has_failed": False, - "errors": {} + "error": None } compute = self._compute_type(**self._compute_kwargs) storage = self._storage_type(**self._storage_kwargs) @@ -450,15 +457,19 @@ async def construct( compute.construct(config['construct']['compute_construct']), storage.construct(config['construct']['storage_construct']) ) - core.combine_errors(result, compute_result, storage_result) + if compute_result['error'] is not None: + result['error'] = compute_result['error'] + elif storage_result['error'] is not None: + result['error'] = storage_result['error'] + self._raise_result(result, config) return result async def destroy( - self, - config: AuthzeeConfigOverride | None = None + self, + config: AuthzeeConfigOverride | None=None ) -> GenericResult: """Tear down everything that construct set up. Deletes DB tables, storage, etc. @@ -474,9 +485,9 @@ async def destroy( ```python # Assumes authz is an AuthzeeAsync instance and this is in a running event loop result = await authz.destroy( - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "destroy": { "compute_destroy": {}, @@ -491,33 +502,41 @@ async def destroy( GenericResult ```python { - "has_failed": False, - "errors": { - "start": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "destroy", + "message": "Description of what went wrong." } } ``` Raises ------ - DestroyError - If a critical error occurs during destruction. + ComputeError + An error occurred in the Compute Module. + StorageError + An error occurred in the Storage Module. """ config = override_config(config, self._config) result = { - "has_failed": False, - "errors": {} + "error": None } compute_result, storage_result = await gather( self._compute.destroy(config['destroy']['compute_destroy']), self._storage.destroy(config['destroy']['storage_destroy']) ) - core.combine_errors(result, compute_result, storage_result) + if compute_result['error'] is not None: + result['error'] = compute_result['error'] + elif storage_result['error'] is not None: + result['error'] = storage_result['error'] + self._raise_result(result, config) return result @@ -525,8 +544,8 @@ async def destroy( async def validate_context_def( self, - context_def: ContextDef, - config: AuthzeeConfigOverride | None = None + context_def: ContextDef, + config: AuthzeeConfigOverride | None=None ) -> GenericResult: """Validate a context definition without storing it. @@ -549,9 +568,9 @@ async def validate_context_def( "additionalProperties": False } }, - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "validate_context_def": {} } @@ -563,14 +582,17 @@ async def validate_context_def( GenericResult ```python { - "has_failed": False, - "errors": { - "definition": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "definition", + "message": "Description of what went wrong." } } ``` @@ -578,7 +600,7 @@ async def validate_context_def( Raises ------ DefinitionError - If the context definition is invalid and raise_crits is True. + If the context definition is invalid and raise_errors is True. """ config = override_config(config, self._config) result = await self._compute.validate_context_def( @@ -586,14 +608,14 @@ async def validate_context_def( config=config['validate_context_def'] ) self._raise_result(result, config) - + return result async def list_context_defs( - self, - page_ref: str | None = None, - config: AuthzeeConfigOverride | None = None + self, + page_ref: str | None=None, + config: AuthzeeConfigOverride | None=None ) -> ContextDefsPage: """Retrieve a page of context definitions. @@ -610,9 +632,9 @@ async def list_context_defs( # Assumes authz is an AuthzeeAsync instance and this is in a running event loop result = await authz.list_context_defs( page_ref="abc123", # optional - str | None - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "list_context_defs": { "page_size": 100, @@ -647,38 +669,40 @@ async def list_context_defs( } ], "next_page_ref": None, # str | None - None means pagination is complete - "has_failed": False, - "errors": { - "page_reference": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "page_reference", + "message": "Description of what went wrong." } } ``` Raises ------ - PageReferenceError - If the page reference is invalid. + StorageError + An error occurred in the Storage Module. """ config = override_config(config, self._config) - result = await self._storage.list_context_defs( + result = await self._storage.list_context_defs( page_ref=page_ref, config=config['list_context_defs'] ) self._raise_result(result, config) - + return result - async def get_context_def( - self, - context_type: str, - config: AuthzeeConfigOverride | None = None + self, + context_type: str, + config: AuthzeeConfigOverride | None=None ) -> ContextDefResult: """Retrieve a context definition by its `context_type`. @@ -695,9 +719,9 @@ async def get_context_def( # Assumes authz is an AuthzeeAsync instance and this is in a running event loop result = await authz.get_context_def( context_type="NONE", - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "get_context_def": { "use_cache": False @@ -711,21 +735,24 @@ async def get_context_def( ContextDefResult ```python { - "context_def": { # dict | None + "context_def": { # dict | None "context_type": "NONE", "schema": { "type": "object", "additionalProperties": False } }, - "has_failed": False, - "errors": { - "resource_not_found": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "resource_not_found", + "message": "Description of what went wrong." } } ``` @@ -741,14 +768,14 @@ async def get_context_def( config=config['get_context_def'] ) self._raise_result(result, config) - + return result async def put_context_def( - self, - context_def: ContextDef, - config: AuthzeeConfigOverride | None = None + self, + context_def: ContextDef, + config: AuthzeeConfigOverride | None=None ) -> GenericResult: """Create or update a context definition. @@ -771,9 +798,9 @@ async def put_context_def( "additionalProperties": False } }, - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "put_context_def": {} } @@ -785,14 +812,17 @@ async def put_context_def( GenericResult ```python { - "has_failed": False, - "errors": { - "definition": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "definition", + "message": "Description of what went wrong." } } ``` @@ -800,13 +830,13 @@ async def put_context_def( Raises ------ DefinitionError - If the context definition is invalid and raise_crits is True. + If the context definition is invalid and raise_errors is True. """ valid_result = await self.validate_context_def( context_def=context_def, config=config ) - if valid_result['has_failed'] is True: + if valid_result['error'] is not None: return valid_result config = override_config(config, self._config) @@ -815,14 +845,14 @@ async def put_context_def( config=config['put_context_def'] ) self._raise_result(result, config) - + return result async def delete_context_def( - self, - context_type: str, - config: AuthzeeConfigOverride | None = None + self, + context_type: str, + config: AuthzeeConfigOverride | None=None ) -> GenericResult: """Deletes the context definition if found. @@ -839,9 +869,9 @@ async def delete_context_def( # Assumes authz is an AuthzeeAsync instance and this is in a running event loop result = await authz.delete_context_def( context_type="NONE", - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "delete_context_def": {} } @@ -853,22 +883,25 @@ async def delete_context_def( GenericResult ```python { - "has_failed": False, - "errors": { - "resource_not_found": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "resource_not_found", + "message": "Description of what went wrong." } } ``` Raises ------ - DeleteError - If a critical error occurs during deletion. + StorageError + An error occurred in the Storage Module. """ config = override_config(config, self._config) result = await self._storage.delete_context_def( @@ -876,14 +909,14 @@ async def delete_context_def( config=config['delete_context_def'] ) self._raise_result(result, config) - + return result async def validate_identity_def( self, - identity_def: IdentityDef, - config: AuthzeeConfigOverride | None = None + identity_def: IdentityDef, + config: AuthzeeConfigOverride | None=None ) -> GenericResult: """Validate an identity definition without storing it. @@ -913,9 +946,9 @@ async def validate_identity_def( } } }, - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "validate_identity_def": {} } @@ -927,14 +960,17 @@ async def validate_identity_def( GenericResult ```python { - "has_failed": False, - "errors": { - "definition": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "definition", + "message": "Description of what went wrong." } } ``` @@ -942,7 +978,7 @@ async def validate_identity_def( Raises ------ DefinitionError - If the identity definition is invalid and raise_crits is True. + If the identity definition is invalid and raise_errors is True. """ config = override_config(config, self._config) result = await self._compute.validate_identity_def( @@ -950,14 +986,14 @@ async def validate_identity_def( config=config['validate_identity_def'] ) self._raise_result(result, config) - + return result async def list_identity_defs( - self, - page_ref: str | None = None, - config: AuthzeeConfigOverride | None = None + self, + page_ref: str | None=None, + config: AuthzeeConfigOverride | None=None ) -> IdentityDefsPage: """Retrieve a page of identity definitions. @@ -974,9 +1010,9 @@ async def list_identity_defs( # Assumes authz is an AuthzeeAsync instance and this is in a running event loop result = await authz.list_identity_defs( page_ref="abc123", # optional - str | None - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "list_identity_defs": { "page_size": 100, @@ -1015,22 +1051,25 @@ async def list_identity_defs( } ], "next_page_ref": None, # str | None - None means pagination is complete - "has_failed": False, - "errors": { - "page_reference": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "page_reference", + "message": "Description of what went wrong." } } ``` Raises ------ - PageReferenceError - If the page reference is invalid. + StorageError + An error occurred in the Storage Module. """ config = override_config(config, self._config) result = await self._storage.list_identity_defs( @@ -1038,14 +1077,14 @@ async def list_identity_defs( config=config['list_identity_defs'] ) self._raise_result(result, config) - + return result async def get_identity_def( - self, + self, identity_type: str, - config: AuthzeeConfigOverride | None = None + config: AuthzeeConfigOverride | None=None ) -> IdentityDefResult: """Retrieve an identity definition by its `identity_type`. @@ -1062,9 +1101,9 @@ async def get_identity_def( # Assumes authz is an AuthzeeAsync instance and this is in a running event loop result = await authz.get_identity_def( identity_type="user", - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "get_identity_def": { "use_cache": False @@ -1078,7 +1117,7 @@ async def get_identity_def( IdentityDefResult ```python { - "identity_def": { # dict | None + "identity_def": { # dict | None "identity_type": "user", "schema": { "type": "object", @@ -1089,14 +1128,17 @@ async def get_identity_def( } } }, - "has_failed": False, - "errors": { - "resource_not_found": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "resource_not_found", + "message": "Description of what went wrong." } } ``` @@ -1112,14 +1154,14 @@ async def get_identity_def( config=config['get_identity_def'] ) self._raise_result(result, config) - + return result async def put_identity_def( - self, - identity_def: IdentityDef, - config: AuthzeeConfigOverride | None = None + self, + identity_def: IdentityDef, + config: AuthzeeConfigOverride | None=None ) -> GenericResult: """Create or update an identity definition. @@ -1149,9 +1191,9 @@ async def put_identity_def( } } }, - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "put_identity_def": {} } @@ -1163,14 +1205,17 @@ async def put_identity_def( GenericResult ```python { - "has_failed": False, - "errors": { - "definition": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "definition", + "message": "Description of what went wrong." } } ``` @@ -1178,13 +1223,13 @@ async def put_identity_def( Raises ------ DefinitionError - If the identity definition is invalid and raise_crits is True. + If the identity definition is invalid and raise_errors is True. """ valid_result = await self.validate_identity_def( identity_def=identity_def, config=config ) - if valid_result['has_failed'] is True: + if valid_result['error'] is not None: return valid_result config = override_config(config, self._config) @@ -1193,14 +1238,14 @@ async def put_identity_def( config=config['put_identity_def'] ) self._raise_result(result, config) - + return result async def delete_identity_def( - self, + self, identity_type: str, - config: AuthzeeConfigOverride | None = None + config: AuthzeeConfigOverride | None=None ) -> GenericResult: """Deletes the identity definition if found. @@ -1217,9 +1262,9 @@ async def delete_identity_def( # Assumes authz is an AuthzeeAsync instance and this is in a running event loop result = await authz.delete_identity_def( identity_type="user", - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "delete_identity_def": {} } @@ -1231,22 +1276,25 @@ async def delete_identity_def( GenericResult ```python { - "has_failed": False, - "errors": { - "resource_not_found": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "resource_not_found", + "message": "Description of what went wrong." } } ``` Raises ------ - DeleteError - If a critical error occurs during deletion. + StorageError + An error occurred in the Storage Module. """ config = override_config(config, self._config) result = await self._storage.delete_identity_def( @@ -1254,14 +1302,14 @@ async def delete_identity_def( config=config['delete_identity_def'] ) self._raise_result(result, config) - + return result async def validate_resource_def( self, - resource_def: ResourceDef, - config: AuthzeeConfigOverride | None = None + resource_def: ResourceDef, + config: AuthzeeConfigOverride | None=None ) -> GenericResult: """Validate a resource definition without storing it. @@ -1296,9 +1344,9 @@ async def validate_resource_def( } } }, - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "validate_resource_def": {} } @@ -1310,14 +1358,17 @@ async def validate_resource_def( GenericResult ```python { - "has_failed": False, - "errors": { - "definition": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "definition", + "message": "Description of what went wrong." } } ``` @@ -1325,7 +1376,7 @@ async def validate_resource_def( Raises ------ DefinitionError - If the resource definition is invalid and raise_crits is True. + If the resource definition is invalid and raise_errors is True. """ config = override_config(config, self._config) result = await self._compute.validate_resource_def( @@ -1333,14 +1384,14 @@ async def validate_resource_def( config=config['validate_resource_def'] ) self._raise_result(result, config) - + return result async def list_resource_defs( - self, - page_ref: str | None = None, - config: AuthzeeConfigOverride | None = None + self, + page_ref: str | None=None, + config: AuthzeeConfigOverride | None=None ) -> ResourceDefsPage: """Retrieve a page of resource definitions. @@ -1357,9 +1408,9 @@ async def list_resource_defs( # Assumes authz is an AuthzeeAsync instance and this is in a running event loop result = await authz.list_resource_defs( page_ref="abc123", # optional - str | None - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "list_resource_defs": { "page_size": 100, @@ -1402,22 +1453,25 @@ async def list_resource_defs( } ], "next_page_ref": None, # str | None - None means pagination is complete - "has_failed": False, - "errors": { - "page_reference": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "page_reference", + "message": "Description of what went wrong." } } ``` Raises ------ - PageReferenceError - If the page reference is invalid. + StorageError + An error occurred in the Storage Module. """ config = override_config(config, self._config) result = await self._storage.list_resource_defs( @@ -1425,14 +1479,14 @@ async def list_resource_defs( config=config['list_resource_defs'] ) self._raise_result(result, config) - + return result async def get_resource_def( - self, + self, resource_type: str, - config: AuthzeeConfigOverride | None = None + config: AuthzeeConfigOverride | None=None ) -> ResourceDefResult: """Retrieve a resource definition by its `resource_type`. @@ -1449,9 +1503,9 @@ async def get_resource_def( # Assumes authz is an AuthzeeAsync instance and this is in a running event loop result = await authz.get_resource_def( resource_type="balloon", - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "get_resource_def": { "use_cache": False @@ -1465,7 +1519,7 @@ async def get_resource_def( ResourceDefResult ```python { - "resource_def": { # dict | None + "resource_def": { # dict | None "resource_type": "balloon", "actions": [ "balloon:read", @@ -1480,14 +1534,17 @@ async def get_resource_def( } } }, - "has_failed": False, - "errors": { - "resource_not_found": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "resource_not_found", + "message": "Description of what went wrong." } } ``` @@ -1503,14 +1560,14 @@ async def get_resource_def( config=config['get_resource_def'] ) self._raise_result(result, config) - + return result - - + + async def put_resource_def( - self, + self, resource_def: ResourceDef, - config: AuthzeeConfigOverride | None = None + config: AuthzeeConfigOverride | None=None ) -> GenericResult: """Create or update a resource definition. @@ -1545,9 +1602,9 @@ async def put_resource_def( } } }, - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "put_resource_def": {} } @@ -1559,14 +1616,17 @@ async def put_resource_def( GenericResult ```python { - "has_failed": False, - "errors": { - "definition": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "definition", + "message": "Description of what went wrong." } } ``` @@ -1574,13 +1634,13 @@ async def put_resource_def( Raises ------ DefinitionError - If the resource definition is invalid and raise_crits is True. + If the resource definition is invalid and raise_errors is True. """ valid_result = await self.validate_resource_def( resource_def=resource_def, config=config ) - if valid_result['has_failed'] is True: + if valid_result['error'] is not None: return valid_result config = override_config(config, self._config) @@ -1589,14 +1649,14 @@ async def put_resource_def( config=config['put_resource_def'] ) self._raise_result(result, config) - + return result async def delete_resource_def( - self, + self, resource_type: str, - config: AuthzeeConfigOverride | None = None + config: AuthzeeConfigOverride | None=None ) -> GenericResult: """Deletes the resource definition if found. @@ -1613,9 +1673,9 @@ async def delete_resource_def( # Assumes authz is an AuthzeeAsync instance and this is in a running event loop result = await authz.delete_resource_def( resource_type="balloon", - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "delete_resource_def": {} } @@ -1627,22 +1687,25 @@ async def delete_resource_def( GenericResult ```python { - "has_failed": False, - "errors": { - "resource_not_found": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "resource_not_found", + "message": "Description of what went wrong." } } ``` Raises ------ - DeleteError - If a critical error occurs during deletion. + StorageError + An error occurred in the Storage Module. """ config = override_config(config, self._config) result = await self._storage.delete_resource_def( @@ -1650,14 +1713,14 @@ async def delete_resource_def( config=config['delete_resource_def'] ) self._raise_result(result, config) - + return result async def validate_grant( - self, + self, grant: Grant, - config: AuthzeeConfigOverride | None = None + config: AuthzeeConfigOverride | None=None ) -> GenericResult: """Validate a grant without storing it. @@ -1685,13 +1748,12 @@ async def validate_grant( "balloon:inflate" ], "query": "length(request.identities.user[?department == 'Balloon Dept']) > `0`", - "evaluation_handler": "evaluate", # "evaluate" | "error" | "critical" "equality": True, # bool | str | int | float | None | list | dict "data": {} }, - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "validate_grant": {} } @@ -1703,14 +1765,17 @@ async def validate_grant( GenericResult ```python { - "has_failed": False, - "errors": { - "grant": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "grant", + "message": "Description of what went wrong." } } ``` @@ -1718,7 +1783,7 @@ async def validate_grant( Raises ------ GrantError - If the grant is invalid and raise_crits is True. + If the grant is invalid and raise_errors is True. """ config = override_config(config, self._config) result = await self._compute.validate_grant( @@ -1726,14 +1791,14 @@ async def validate_grant( config=config['validate_grant'] ) self._raise_result(result, config) - + return result async def enact( - self, + self, grant: Grant, - config: AuthzeeConfigOverride | None = None + config: AuthzeeConfigOverride | None=None ) -> GenericResult: """Enact (store) a grant to create an authorization rule. @@ -1761,13 +1826,12 @@ async def enact( "balloon:inflate" ], "query": "length(request.identities.user[?department == 'Balloon Dept']) > `0`", - "evaluation_handler": "evaluate", # "evaluate" | "error" | "critical" "equality": True, # bool | str | int | float | None | list | dict "data": {} }, - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "enact": {} } @@ -1779,14 +1843,17 @@ async def enact( GenericResult ```python { - "has_failed": False, - "errors": { - "grant": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "grant", + "message": "Description of what went wrong." } } ``` @@ -1794,30 +1861,24 @@ async def enact( Raises ------ GrantError - If the grant is invalid and raise_crits is True. + If the grant is invalid and raise_errors is True. """ - valid_result = await self.validate_grant( - grant=grant, - config=config - ) - if valid_result['has_failed'] is True: + valid_result = await self.validate_grant(grant=grant, config=config) + if valid_result['error'] is not None: return valid_result config = override_config(config, self._config) - result = await self._storage.enact( - grant=grant, - config=config['enact'] - ) + result = await self._storage.enact(grant=grant, config=config['enact']) self._raise_result(result, config) - + return result - + async def repeal( - self, - grant_uuid: str, + self, + grant_uuid: str, purge: bool, - config: AuthzeeConfigOverride | None = None + config: AuthzeeConfigOverride | None=None ) -> GenericResult: """Repeal (remove) a grant by its UUID. @@ -1826,7 +1887,7 @@ async def repeal( grant_uuid : str The UUID of the grant to repeal. purge : bool - If True, all grants and partitions may be scanned to completely remove. + If True, all grants and partitions may be scanned to completely remove. Useful if corruption by update is suspected. config : AuthzeeConfigOverride | None, optional Override configuration for this call. Only include keys to override. @@ -1838,9 +1899,9 @@ async def repeal( result = await authz.repeal( grant_uuid="0da5dfc6-c919-4bd6-b80f-a351a9ac8d27", purge=False, - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "repeal": {} } @@ -1852,14 +1913,17 @@ async def repeal( GenericResult ```python { - "has_failed": True, - "errors": { - "resource_not_found": [ - { - "is_critical": True, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "resource_not_found", + "message": "Description of what went wrong." } } ``` @@ -1876,14 +1940,14 @@ async def repeal( config=config['repeal'] ) self._raise_result(result, config) - + return result async def get_grant( - self, + self, grant_uuid: str, - config: AuthzeeConfigOverride | None = None + config: AuthzeeConfigOverride | None=None ) -> GrantResult: """Retrieve a grant by its UUID. @@ -1900,9 +1964,9 @@ async def get_grant( # Assumes authz is an AuthzeeAsync instance and this is in a running event loop result = await authz.get_grant( grant_uuid="0da5dfc6-c919-4bd6-b80f-a351a9ac8d27", - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "get_grant": { "use_cache": False @@ -1916,7 +1980,7 @@ async def get_grant( GrantResult ```python { - "grant": { # dict | None + "grant": { # dict | None "grant_uuid": "0da5dfc6-c919-4bd6-b80f-a351a9ac8d27", "name": "Allow inflate", "description": "Allow balloon inflate for users.", @@ -1928,18 +1992,20 @@ async def get_grant( "balloon:inflate" ], "query": "length(request.identities.user[?department == 'Balloon Dept']) > `0`", - "evaluation_handler": "evaluate", "equality": True, "data": {} }, - "has_failed": False, - "errors": { - "resource_not_found": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "resource_not_found", + "message": "Description of what went wrong." } } ``` @@ -1955,16 +2021,16 @@ async def get_grant( config=config['get_grant'] ) self._raise_result(result, config) - + return result async def list_grants( self, - effect: str | None = None, - action: str | None = None, - page_ref: str | None = None, - config: AuthzeeConfigOverride | None = None + effect: str | None=None, + action: str | None=None, + page_ref: str | None=None, + config: AuthzeeConfigOverride | None=None ) -> GrantsPage: """Retrieve a page of grants with optional filtering. @@ -1987,9 +2053,9 @@ async def list_grants( effect="allow", # optional - str | None - "allow" | "deny" action="balloon:inflate", # optional - str | None page_ref="abc123", # optional - str | None - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "list_grants": { "page_size": 100, @@ -2027,28 +2093,30 @@ async def list_grants( "balloon:inflate" ], "query": "length(request.identities.user[?department == 'Balloon Dept']) > `0`", - "evaluation_handler": "evaluate", "equality": True, "data": {} } ], "next_page_ref": None, # str | None - None means pagination is complete - "has_failed": False, - "errors": { - "page_reference": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "page_reference", + "message": "Description of what went wrong." } } ``` Raises ------ - PageReferenceError - If the page reference is invalid. + StorageError + An error occurred in the Storage Module. """ config = override_config(config, self._config) result = await self._storage.list_grants( @@ -2058,16 +2126,16 @@ async def list_grants( config=config['list_grants'] ) self._raise_result(result, config) - + return result async def list_grant_refs( self, - effect: str | None = None, - action: str | None = None, - page_ref: str | None = None, - config: AuthzeeConfigOverride | None = None + effect: str | None=None, + action: str | None=None, + page_ref: str | None=None, + config: AuthzeeConfigOverride | None=None ) -> PageRefsPage: """Retrieve a page of grant page references for parallel pagination. @@ -2090,9 +2158,9 @@ async def list_grant_refs( effect="allow", # optional - str | None - "allow" | "deny" action="balloon:inflate", # optional - str | None page_ref="abc123", # optional - str | None - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "list_grant_refs": { "page_size": 10, @@ -2122,22 +2190,25 @@ async def list_grant_refs( "page_ref_2" ], "next_page_ref": None, # str | None - None means pagination is complete - "has_failed": False, - "errors": { - "page_reference": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "page_reference", + "message": "Description of what went wrong." } } ``` Raises ------ - PageReferenceError - If the page reference is invalid. + StorageError + An error occurred in the Storage Module. ParallelPaginationNotSupported If the storage backend does not support parallel pagination. """ @@ -2149,14 +2220,14 @@ async def list_grant_refs( config=config['list_grant_refs'] ) self._raise_result(result, config) - + return result async def cleanup_latches( - self, - before: datetime.datetime, - config: AuthzeeConfigOverride | None = None + self, + before: datetime.datetime, + config: AuthzeeConfigOverride | None=None ) -> GenericResult: """Clean up storage latches created before the given datetime. @@ -2178,9 +2249,9 @@ async def cleanup_latches( # Assumes authz is an AuthzeeAsync instance and this is in a running event loop result = await authz.cleanup_latches( before=datetime.datetime(2026, 1, 1), - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "cleanup_latches": {} } @@ -2192,22 +2263,27 @@ async def cleanup_latches( GenericResult ```python { - "has_failed": False, - "errors": { - "start": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "start", + "message": "Description of what went wrong." } } ``` Raises ------ - StartError - If a critical error occurs during cleanup. + ComputeError + An error occurred in the Compute Module. + StorageError + An error occurred in the Storage Module. """ config = override_config(config, self._config) result = await self._storage.cleanup_latches( @@ -2215,14 +2291,14 @@ async def cleanup_latches( config=config['cleanup_latches'] ) self._raise_result(result, config) - + return result - - + + async def validate_request( self, request: AuthzeeRequest, - config: AuthzeeConfigOverride | None = None + config: AuthzeeConfigOverride | None=None ) -> GenericResult: """Validate an authorization request without evaluating it. @@ -2253,13 +2329,12 @@ async def validate_request( "color": "blue", "is_inflated": False }, - "evaluation_handler": "grant", "context_type": "NONE", "context": {} }, - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "validate_request": { "get_context_def": { @@ -2296,14 +2371,17 @@ async def validate_request( GenericResult ```python { - "has_failed": False, - "errors": { - "request": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "request", + "message": "Description of what went wrong." } } ``` @@ -2311,7 +2389,7 @@ async def validate_request( Raises ------ RequestError - If the request is invalid and raise_crits is True. + If the request is invalid and raise_errors is True. """ config = override_config(config, self._config) result = await self._compute.validate_request( @@ -2319,15 +2397,15 @@ async def validate_request( config=config['validate_request'] ) self._raise_result(result, config) - + return result async def audit( self, - request: AuthzeeRequest, - page_ref: str | None = None, - config: AuthzeeConfigOverride | None = None + request: AuthzeeRequest, + page_ref: str | None=None, + config: AuthzeeConfigOverride | None=None ) -> AuditResultPage: """Retrieve a page of audit results showing how each grant evaluated against the request. @@ -2360,14 +2438,13 @@ async def audit( "color": "blue", "is_inflated": False }, - "evaluation_handler": "grant", # "grant" | "evaluate" | "error" | "critical" "context_type": "NONE", "context": {} }, page_ref="abc123", # optional - str | None - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "audit": { "validate_request": { @@ -2427,13 +2504,12 @@ async def audit( "color": "blue", "is_inflated": False }, - "evaluation_handler": "grant", "context_type": "NONE", "context": {} } ): - for grant, result in zip(page['grants'], page['results']): - print(grant['grant_uuid'], result['is_applicable']) + for result_item in page['results']: + print(result_item['grant']['grant_uuid'], result_item['is_applicable']) ``` Returns @@ -2441,38 +2517,38 @@ async def audit( AuditResultPage ```python { - "grants": [ - { - "grant_uuid": "0da5dfc6-c919-4bd6-b80f-a351a9ac8d27", - "name": "Allow inflate", - "description": "Allow balloon inflate for users.", - "tags": {}, - "effect": "allow", - "actions": [ - "balloon:inflate" - ], - "query": "length(request.identities.user[?department == 'Balloon Dept']) > `0`", - "evaluation_handler": "evaluate", - "equality": True, - "data": {} - } - ], "results": [ { + "grant": { + "grant_uuid": "0da5dfc6-c919-4bd6-b80f-a351a9ac8d27", + "name": "Allow inflate", + "description": "Allow balloon inflate for users.", + "tags": {}, + "effect": "allow", + "actions": [ + "balloon:inflate" + ], + "query": "length(request.identities.user[?department == 'Balloon Dept']) > `0`", + "equality": True, + "data": {} + }, "is_applicable": True, "query_result": True, - "errors": {} + "error": None } ], "next_page_ref": None, # str | None - None means pagination is complete - "has_failed": False, - "errors": { - "request": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "request", + "message": "Description of what went wrong." } } ``` @@ -2480,27 +2556,23 @@ async def audit( Raises ------ RequestError - If the request is invalid and raise_crits is True. - EvaluationError - If a critical evaluation error occurs and raise_crits is True. - PageReferenceError - If the page reference is invalid. + If the request is invalid and raise_errors is True. + StorageError + An error occurred in the Storage Module. """ config = override_config(config, self._config) valid_result = await self._compute.validate_request( request=request, config=config['audit']['validate_request'] ) - if valid_result['has_failed'] is True: + if valid_result['error'] is not None: result = { - "grants": [], "results": [], "next_page_ref": None, - "has_failed": True, - "errors": valid_result['errors'] + "error": valid_result['error'] } self._raise_result(result, config) - + return result result = await self._compute.audit( @@ -2509,27 +2581,14 @@ async def audit( config=config['audit'] ) self._raise_result(result, config) - - return result - - def _get_critical_errors(self, errors: ResultErrors) -> ResultErrors: - critical_errors = {} - for et in errors: - for error in errors[et]: - if error['is_critical']: - if et not in critical_errors: - critical_errors[et] = [] - - critical_errors[et].append(error) - - return critical_errors + return result async def authorize( - self, + self, request: AuthzeeRequest, - config: AuthzeeConfigOverride | None = None + config: AuthzeeConfigOverride | None=None ) -> AuthorizeResult: """Determine if the request is authorized. @@ -2560,13 +2619,12 @@ async def authorize( "color": "blue", "is_inflated": False }, - "evaluation_handler": "grant", # "grant" | "evaluate" | "error" | "critical" "context_type": "NONE", "context": {} }, - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "authorize": { "validate_request": { @@ -2615,7 +2673,7 @@ async def authorize( ```python { "is_authorized": True, - "grant": { # dict | None + "grant": { # dict | None "grant_uuid": "0da5dfc6-c919-4bd6-b80f-a351a9ac8d27", "name": "Allow inflate", "description": "Allow balloon inflate for users.", @@ -2625,19 +2683,21 @@ async def authorize( "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": { - "evaluation": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "evaluation", + "message": "Description of what went wrong." } } ``` @@ -2645,26 +2705,27 @@ async def authorize( Raises ------ RequestError - If the request is invalid and raise_crits is True. - EvaluationError - If a critical evaluation error occurs and raise_crits is True. + If the request is invalid and raise_errors is True. + ComputeError + An error occurred in the Compute Module. + StorageError + An error occurred in the Storage Module. """ config = override_config(config, self._config) valid_result = await self._compute.validate_request( request=request, config=config['authorize']['validate_request'] ) - - if valid_result['has_failed'] is True: + + if valid_result['error'] is not None: result = { "is_authorized": False, "grant": None, - "message": "A critical error has occurred. Therefore, the request is not authorized.", - "has_failed": valid_result['has_failed'], - "critical_errors": self._get_critical_errors(valid_result['errors']) + "message": "An error has occurred. Therefore, the request is not authorized.", + "error": valid_result['error'] } self._raise_result(valid_result, config) - + return result result = await self._compute.authorize( @@ -2672,14 +2733,14 @@ async def authorize( config=config['authorize'] ) self._raise_result(result, config) - + return result async def validate_batch_request( self, batch_request: AuthzeeBatchRequest, - config: AuthzeeConfigOverride | None = None + config: AuthzeeConfigOverride | None=None ) -> GenericResult: """Validate a batch authorization request without evaluating it. @@ -2710,7 +2771,6 @@ async def validate_batch_request( "color": "blue", "is_inflated": False }, - "evaluation_handler": "grant", "context_type": "NONE", "context": {}, "batch": [ @@ -2722,9 +2782,9 @@ async def validate_batch_request( } ] }, - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "validate_batch_request": { "get_context_def": { @@ -2761,14 +2821,17 @@ async def validate_batch_request( GenericResult ```python { - "has_failed": False, - "errors": { - "request": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "request", + "message": "Description of what went wrong." } } ``` @@ -2776,7 +2839,7 @@ async def validate_batch_request( Raises ------ RequestError - If the batch request is invalid and raise_crits is True. + If the batch request is invalid and raise_errors is True. """ config = override_config(config, self._config) result = await self._compute.validate_batch_request( @@ -2784,15 +2847,15 @@ async def validate_batch_request( config=config['validate_batch_request'] ) self._raise_result(result, config) - + return result async def batch_audit( self, - batch_request: AuthzeeBatchRequest, - page_ref: str | None = None, - config: AuthzeeConfigOverride | None = None + batch_request: AuthzeeBatchRequest, + page_ref: str | None=None, + config: AuthzeeConfigOverride | None=None ) -> BatchAuditResultPage: """Retrieve a page of batch audit results showing how each grant evaluated against the batch request. @@ -2825,12 +2888,11 @@ async def batch_audit( "color": "blue", "is_inflated": False }, - "evaluation_handler": "grant", # "grant" | "evaluate" | "error" | "critical" "context_type": "NONE", "context": {}, "batch": [ { - "resource": { # optional - dict | None + "resource": { # optional - dict | None "color": "red", "is_inflated": True } @@ -2838,9 +2900,9 @@ async def batch_audit( ] }, page_ref="abc123", # optional - str | None - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "batch_audit": { "validate_batch_request": { @@ -2900,7 +2962,6 @@ async def batch_audit( "color": "blue", "is_inflated": False }, - "evaluation_handler": "grant", "context_type": "NONE", "context": {}, "batch": [ @@ -2913,8 +2974,8 @@ async def batch_audit( ] } ): - for batch_result in page['batch_results']: - for result_item in batch_result['results']: + for batch_item in page['batch']: + for result_item in batch_item['results']: print(result_item['is_applicable']) ``` @@ -2934,33 +2995,34 @@ async def batch_audit( "balloon:inflate" ], "query": "length(request.identities.user[?department == 'Balloon Dept']) > `0`", - "evaluation_handler": "evaluate", "equality": True, "data": {} } ], - "batch_results": [ + "batch": [ { "results": [ { "is_applicable": True, "query_result": True, - "errors": {} + "error": None } ], - "has_failed": False, - "errors": {} + "error": None } ], "next_page_ref": None, # str | None - None means pagination is complete - "has_failed": False, - "errors": { - "request": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "request", + "message": "Description of what went wrong." } } ``` @@ -2968,43 +3030,40 @@ async def batch_audit( Raises ------ RequestError - If the batch request is invalid and raise_crits is True. - EvaluationError - If a critical evaluation error occurs and raise_crits is True. - PageReferenceError - If the page reference is invalid. + If the batch request is invalid and raise_errors is True. + StorageError + An error occurred in the Storage Module. """ config = override_config(config, self._config) valid_result = await self._compute.validate_batch_request( batch_request=batch_request, config=config['batch_audit']['validate_batch_request'] ) - if valid_result['has_failed'] is True: + if valid_result['error'] is not None: result = { "grants": [], - "batch_results": [], + "batch": [], "next_page_ref": None, - "has_failed": True, - "errors": valid_result['errors'] + "error": valid_result['error'] } self._raise_result(result, config) - + return result - + result = await self._compute.batch_audit( batch_request=batch_request, page_ref=page_ref, config=config['batch_audit'] ) self._raise_result(result, config) - + return result async def batch_authorize( - self, + self, batch_request: AuthzeeBatchRequest, - config: AuthzeeConfigOverride | None = None + config: AuthzeeConfigOverride | None=None ) -> BatchAuthorizeResult: """Determine if each item in the batch request is authorized. @@ -3035,21 +3094,20 @@ async def batch_authorize( "color": "blue", "is_inflated": False }, - "evaluation_handler": "grant", # "grant" | "evaluate" | "error" | "critical" "context_type": "NONE", "context": {}, "batch": [ { - "resource": { # optional - dict | None + "resource": { # optional - dict | None "color": "red", "is_inflated": True } } ] }, - config={ # optional - AuthzeeConfigOverride | None - All keys are optional + config={ # optional - AuthzeeConfigOverride | None - All keys are optional "authzee": { - "raise_crits": True + "raise_errors": True }, "batch_authorize": { "validate_batch_request": { @@ -3123,7 +3181,7 @@ async def batch_authorize( BatchAuthorizeResult ```python { - "batch_results": [ + "batch": [ { "is_authorized": True, "grant": { @@ -3136,30 +3194,30 @@ async def batch_authorize( "balloon:inflate" ], "query": "length(request.identities.user[?department == 'Balloon Dept']) > `0`", - "evaluation_handler": "evaluate", "equality": True, "data": {} }, "message": "Authorized by grant.", - "has_failed": False, - "critical_errors": {} + "error": None }, { "is_authorized": False, "grant": None, "message": "No matching allow grants.", - "has_failed": False, - "critical_errors": {} + "error": None } ], - "has_failed": False, - "critical_errors": { - "evaluation": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": None + } + ``` + + Or on error: + + ```python + { + "error": { + "error_type": "evaluation", + "message": "Description of what went wrong." } } ``` @@ -3167,23 +3225,24 @@ async def batch_authorize( Raises ------ RequestError - If the batch request is invalid and raise_crits is True. - EvaluationError - If a critical evaluation error occurs and raise_crits is True. + If the batch request is invalid and raise_errors is True. + ComputeError + An error occurred in the Compute Module. + StorageError + An error occurred in the Storage Module. """ config = override_config(config, self._config) valid_result = await self._compute.validate_batch_request( batch_request=batch_request, config=config['batch_authorize']['validate_batch_request'] ) - if valid_result['has_failed'] is True: + if valid_result['error'] is not None: result = { - "batch_results": [], - "has_failed": True, - "critical": self._get_critical_errors(valid_result['errors']) + "batch": [], + "error": valid_result['error'] } self._raise_result(result, config) - + return result result = await self._compute.batch_authorize( @@ -3191,6 +3250,5 @@ async def batch_authorize( config=config['batch_authorize'] ) self._raise_result(result, config) - + return result - \ No newline at end of file diff --git a/src/authzee/compute/__init__.py b/src/authzee/compute/__init__.py index f3c25d6..7d2a172 100644 --- a/src/authzee/compute/__init__.py +++ b/src/authzee/compute/__init__.py @@ -1,6 +1,8 @@ +"""Authzee compute modules.""" + __all__ = [ "ComputeModule", - "InProcessCompute", + "InProcessCompute" ] from authzee.compute.compute_module import ComputeModule diff --git a/src/authzee/compute/compute_module.py b/src/authzee/compute/compute_module.py index 011fb85..f70acb8 100644 --- a/src/authzee/compute/compute_module.py +++ b/src/authzee/compute/compute_module.py @@ -1,35 +1,35 @@ - """Base compute module for Authzee. -See {py:class}`authzee.compute.compute_module.ComputeModule` +See [](authzee.compute.compute_module.ComputeModule) """ __all__ = [ - "ComputeModule", + "ComputeModule" ] from typing import Any, Callable, Dict, Type +from authzee.exceptions import NotImplementedError +from authzee.module_locality import ModuleLocality +from authzee.storage.storage_module import StorageModule from authzee.types.authzee import * from authzee.types.config import ( - ComputeStartConfig, - ComputeShutdownConfig, + AuditConfig, + AuthorizeConfig, + BatchAuditConfig, + BatchAuthorizeConfig, ComputeConstructConfig, ComputeDestroyConfig, + ComputeShutdownConfig, + ComputeStartConfig, + ValidateBatchRequestConfig, ValidateContextDefConfig, - ValidateIdentityDefConfig, - ValidateResourceDefConfig, ValidateGrantConfig, + ValidateIdentityDefConfig, ValidateRequestConfig, - ValidateBatchRequestConfig, - AuditConfig, - AuthorizeConfig, - BatchAuditConfig, - BatchAuthorizeConfig + ValidateResourceDefConfig ) -from authzee.exceptions import NotImplementedError -from authzee.module_locality import ModuleLocality -from authzee.storage.storage_module import StorageModule + class ComputeModule: @@ -87,7 +87,6 @@ async def validate_context_def( raise NotImplementedError() - async def validate_identity_def( self, identity_def: IdentityDef, @@ -110,7 +109,7 @@ async def validate_grant( config: ValidateGrantConfig ) -> GenericResult: raise NotImplementedError() - + async def validate_request( self, @@ -175,4 +174,4 @@ async def batch_authorize( ) -> BatchAuthorizeResult: """Run the Batch Authorize Operation. """ - raise NotImplementedError() \ No newline at end of file + raise NotImplementedError() diff --git a/src/authzee/compute/in_process_compute.py b/src/authzee/compute/in_process_compute.py index b74a04b..be96f76 100644 --- a/src/authzee/compute/in_process_compute.py +++ b/src/authzee/compute/in_process_compute.py @@ -1,48 +1,47 @@ """In-process compute module for Authzee. -See {py:class}`authzee.compute.in_process_compute.InProcessCompute` +All compute is done within the same process/asyncio event loop. """ __all__ = [ - "InProcessCompute", + "InProcessCompute" ] -from asyncio import as_completed, create_task, Task +from asyncio import Task, as_completed, create_task from typing import Any, Callable, Dict, List, Type import jsonschema_rs from authzee.compute.compute_module import ComputeModule from authzee.core import ( - combine_errors, - evaluate, + evaluate, + validate_batch_request_schema, validate_context_def, - validate_identity_def, - validate_resource_def, validate_grant, + validate_identity_def, validate_request_schema, - validate_batch_request_schema + validate_resource_def ) +from authzee.module_locality import ModuleLocality from authzee.paginators import paginator_async +from authzee.storage.storage_module import StorageModule from authzee.types.authzee import * from authzee.types.config import ( - ComputeStartConfig, - ComputeShutdownConfig, + AuditConfig, + AuthorizeConfig, + BatchAuditConfig, + BatchAuthorizeConfig, ComputeConstructConfig, ComputeDestroyConfig, + ComputeShutdownConfig, + ComputeStartConfig, + ValidateBatchRequestConfig, ValidateContextDefConfig, - ValidateIdentityDefConfig, - ValidateResourceDefConfig, ValidateGrantConfig, + ValidateIdentityDefConfig, ValidateRequestConfig, - ValidateBatchRequestConfig, - AuditConfig, - AuthorizeConfig, - BatchAuditConfig, - BatchAuthorizeConfig + ValidateResourceDefConfig ) -from authzee.module_locality import ModuleLocality -from authzee.storage.storage_module import StorageModule class InProcessCompute(ComputeModule): @@ -55,16 +54,9 @@ async def start( storage_kwargs: Dict[str, Any], config: ComputeStartConfig ) -> GenericResult: - """Start up compute module. - - - run before use - - After this method is complete these public instance vars or getters must be available and stable: - - locality - Compute [Module Locality](#module-locality) - - has_parallel_paging - if the compute module supports processing grants with parallel paging - """ await super().start( execute=execute, - storage_type=storage_type, + storage_type=storage_type, storage_kwargs=storage_kwargs, config=config ) @@ -72,45 +64,29 @@ async def start( self.has_parallel_paging = False self._storage = storage_type(**storage_kwargs) await self._storage.start(config['storage']) - + return { - "has_failed": False, - "errors": {} + "error": None } async def shutdown(self, config: ComputeShutdownConfig) -> GenericResult: - """Shutdown Compute module. - - - clean up runtime resources - """ await self._storage.shutdown(config['storage']) return { - "has_failed": False, - "errors": {} + "error": None } async def construct(self, config: ComputeConstructConfig) -> GenericResult: - """Construct backend resources for compute. - - - one time setup - """ return { - "has_failed": False, - "errors": {} + "error": None } async def destroy(self, config: ComputeDestroyConfig) -> GenericResult: - """Tear down backend resources. - - - destructive - may lose all long lasting compute resources - """ return { - "has_failed": False, - "errors": {} + "error": None } @@ -151,110 +127,105 @@ async def validate_request( request: AuthzeeRequest, config: ValidateRequestConfig ) -> GenericResult: - """Validate a request. - """ result = validate_request_schema(request) - if result['has_failed'] is True: + if result['error'] is not None: return result context_def_task = create_task( self._storage.get_context_def( - request['context_type'], + request['context_type'], config['get_identity_def'] ) ) resource_def_task = create_task( self._storage.get_resource_def( - request['resource_type'], + request['resource_type'], config['get_resource_def'] ) ) - identity_def_tasks = [create_task(self._storage.get_identity_def(it, config['get_identity_def'])) for it in request['identities']] + identity_def_tasks = [ + create_task(self._storage.get_identity_def(it, config['get_identity_def'])) + for it in request['identities'] + ] context_def = (await context_def_task)['context_def'] if context_def is None: - result['has_failed'] = True - result['errors']['request'] = [ - { - "is_critical": True, + return { + "error": { + "error_type": "request", "message": f"context_type '{request['context_type']}' is not a registered context type." } - ] - - return result - - if jsonschema_rs.validator_for(context_def['schema']).is_valid(request['context']) is False: - result['has_failed'] = True - result['errors']['request'] = [ - { - "is_critical": True, + } + + if ( + jsonschema_rs.validator_for(context_def['schema']).is_valid(request['context']) + is False + ): + return { + "error": { + "error_type": "request", "message": f"The given context is not valid against the '{request['context_type']}' context type." } - ] - - return result + } resource_def = (await resource_def_task)['resource_def'] if resource_def is None: - result['has_failed'] = True - result['errors']['request'] = [ - { - "is_critical": True, + return { + "error": { + "error_type": "request", "message": f"resource_type '{request['resource_type']}' is not a registered resource type." } - ] + } - return result - - if jsonschema_rs.validator_for(resource_def['schema']).is_valid(request['resource']) is False: - result['has_failed'] = True - result['errors']['request'] = [ - { - "is_critical": True, + if ( + jsonschema_rs.validator_for( + resource_def['schema'] + ).is_valid( + request['resource'] + ) + is False + ): + return { + "error": { + "error_type": "request", "message": f"The given resource is not valid against the '{request['resource_type']}' resource type." } - ] + } - return result - if request['action'] not in resource_def['actions']: - result['has_failed'] = True - result['errors']['request'] = [ - { - "is_critical": True, - "message": f"The given resource action is valid for the '{request['resource_type']}' resource type." + return { + "error": { + "error_type": "request", + "message": f"The given resource action is not valid for the '{request['resource_type']}' resource type." } - ] + } - return result - for id_task, i_type in zip(identity_def_tasks, request['identities']): identity_def = (await id_task)['identity_def'] if identity_def is None: - result['has_failed'] = True - result['errors']['request'] = [ - { - "is_critical": True, + return { + "error": { + "error_type": "request", "message": f"identity_type '{i_type}' is not a registered identity type." } - ] + } - return result - id_validator = jsonschema_rs.validator_for(identity_def['schema']) - for id, i in zip(request['identities'][i_type], range(len(request['identities']))): + for id, i in zip( + request['identities'][i_type], + range(len(request['identities'][i_type])) + ): if id_validator.is_valid(id) is False: - result['has_failed'] = True - result['errors']['request'] = [ - { - "is_critical": True, + return { + "error": { + "error_type": "request", "message": f"The given identity in '{i_type}[{i}]' is not valid against the '{i_type}' identity type." } - ] + } - return result - - return result + return { + "error": None + } async def validate_batch_request( @@ -262,25 +233,15 @@ async def validate_batch_request( batch_request: AuthzeeBatchRequest, config: ValidateBatchRequestConfig ) -> GenericResult: - """Validate a batch request. - """ - # this is a very inefficient way to do this - # TODO try and reuse as needed and only do partial verification of new fields result = validate_batch_request_schema(batch_request) - if result['has_failed'] is True: + if result['error'] is not None: return result base_request: AuthzeeBatchRequest = batch_request.copy() base_request.pop("batch") - base_result = await self.validate_request( - request=base_request, - config=config # technically not the same typeddict type - ) - combine_errors(result, base_result) - if base_result['has_failed'] is True: - result['has_failed'] = True - - return result + base_result = await self.validate_request(request=base_request, config=config) + if base_result['error'] is not None: + return base_result batch_tasks: List[Task] = [] for item in batch_request['batch']: @@ -288,18 +249,19 @@ async def validate_batch_request( create_task( self.validate_request( request=base_request | item, - config=config # technically not the same typeddict type + config=config ) ) ) - + for bt in as_completed(batch_tasks): - bt: GenericResult = await bt - combine_errors(result, bt) - if bt['has_failed'] is True: - result['has_failed'] = True - - return result + bt_result: GenericResult = await bt + if bt_result['error'] is not None: + return bt_result + + return { + "error": None + } async def audit( @@ -308,16 +270,10 @@ async def audit( page_ref: str | None, config: AuditConfig ) -> AuditResultPage: - """Run the Audit Operation for a page of results. - - Pass the returned page reference to get the next page until a null page reference is returned. - """ result = { - "grants": [], "results": [], "next_page_ref": None, - "has_failed": False, - "errors": {} + "error": None } grants_page = ( await self._storage.list_grants( @@ -327,34 +283,27 @@ async def audit( config=config['list_grants'] ) ) - if grants_page['has_failed'] is True: - result['has_failed'] = True - result['errors'] = grants_page['errors'] + if grants_page['error'] is not None: + result['error'] = grants_page['error'] return result - - result['grants'] = grants_page['grants'] + result['next_page_ref'] = grants_page['next_page_ref'] - for grant in result['grants']: + for grant in grants_page['grants']: eval_result = evaluate( request=request, grant=grant, - execute=self._execute, - only_crits=False + execute=self._execute + ) + result['results'].append( + { + "grant": grant, + "is_applicable": eval_result['is_applicable'], + "query_result": eval_result['query_result'], + "failure": eval_result['failure'] + } ) - result['results'].append(eval_result) - if eval_result['has_failed'] is True: - result['next_page_ref'] = None - result['has_failed'] = True - result['errors']['evaluation'] = [ - { - "is_critical": True, - "message": f"A critical error occurred when evaluation grants[{len(result['results']) - 1}]." - } - ] - return result - return result @@ -363,15 +312,6 @@ async def authorize( request: AuthzeeRequest, config: AuthorizeConfig ) -> AuthorizeResult: - """Run the Authorize Operation. - """ - result = { - "is_authorized": False, - "grant": None, - "message": "A critical error has occurred. Therefore, the request is not authorized.", - "has_failed": True, - "critical_errors": {} - } async for page in paginator_async( self._storage.list_grants, effect="deny", @@ -380,34 +320,28 @@ async def authorize( config=config['list_grants'] ): page: GrantsPage - if page['has_failed'] is True: - result['critical_errors'] = page['errors'] + if page['error'] is not None: + return { + "is_authorized": False, + "grant": None, + "message": "An error has occurred. Therefore, the request is not authorized.", + "error": page['error'] + } - return result - for grant in page['grants']: eval_result = evaluate( request=request, grant=grant, - execute=self._execute, - only_crits=True + execute=self._execute ) - if eval_result['has_failed'] is True: - result['grant'] = grant - result['critical_errors'] = eval_result['errors'] - - return result - if eval_result['is_applicable'] is True: return { "is_authorized": False, "grant": grant, "message": "A deny grant is applicable to the request. Therefore, the request is not authorized.", - "has_failed": False, - "critical_errors": {} + "error": None } - # got through all allow grants async for page in paginator_async( self._storage.list_grants, effect="allow", @@ -416,39 +350,33 @@ async def authorize( config=config['list_grants'] ): page: GrantsPage - if page['has_failed'] is True: - result['critical_errors'] = page['errors'] + if page['error'] is not None: + return { + "is_authorized": False, + "grant": None, + "message": "An error has occurred. Therefore, the request is not authorized.", + "error": page['error'] + } - return result - for grant in page['grants']: eval_result = evaluate( request=request, grant=grant, - execute=self._execute, - only_crits=True + execute=self._execute ) - if eval_result['has_failed'] is True: - result['grant'] = grant - result['critical_errors'] = eval_result['errors'] - - return result - if eval_result['is_applicable'] is True: return { "is_authorized": True, "grant": grant, "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": {} + "error": None } return { "is_authorized": False, "grant": None, "message": "No grants are applicable to the request. Therefore, the request is implicitly denied and is not authorized.", - "has_failed": False, - "critical_errors": {} + "error": None } @@ -458,16 +386,11 @@ async def batch_audit( page_ref: str | None, config: BatchAuditConfig ) -> BatchAuditResultPage: - """Run the Batch Audit Operation for a page of results. - - Pass the returned page reference to get the next page until a null page reference is returned. - """ batch_result = { "grants": [], - "batch_results": [], + "batch": [], "next_page_ref": None, - "has_failed": False, - "errors": {} + "error": None } grants_page = ( await self._storage.list_grants( @@ -477,46 +400,37 @@ async def batch_audit( config=config['list_grants'] ) ) - batch_result['errors'] = grants_page['errors'] - if grants_page['has_failed'] is True: - batch_result['has_failed'] = True + if grants_page['error'] is not None: + batch_result['error'] = grants_page['error'] return batch_result - + batch_result['grants'] = grants_page['grants'] batch_result['next_page_ref'] = grants_page['next_page_ref'] for _ in range(len(batch_request['batch'])): - batch_result['batch_results'].append( + batch_result['batch'].append( { "results": [], - "has_failed": False, - "errors": {} + "error": None } ) - + base_request = batch_request.copy() base_request.pop("batch") for grant in grants_page['grants']: - for request, result in zip(batch_request['batch'], batch_result['batch_results']): - if result['has_failed'] is True: - continue - + for request, item_result in zip(batch_request['batch'], batch_result['batch']): eval_result = evaluate( request=base_request | request, grant=grant, - execute=self._execute, - only_crits=False + execute=self._execute + ) + item_result['results'].append( + { + "is_applicable": eval_result['is_applicable'], + "query_result": eval_result['query_result'], + "failure": eval_result['failure'] + } ) - if eval_result['has_failed'] is True: - result['has_failed'] = True - result['errors']['evaluation'] = [ - { - "is_critical": True, - "message": f"A critical error occurred when evaluation grants[{len(result['results']) - 1}]." - } - ] - else: - result['results'].append(eval_result) return batch_result @@ -526,21 +440,17 @@ async def batch_authorize( batch_request: AuthzeeBatchRequest, config: BatchAuthorizeConfig ) -> BatchAuthorizeResult: - """Run the Batch Authorize Operation. - """ batch_result: BatchAuthorizeResult = { - "batch_results": [], - "has_failed": False, - "critical_errors": [] + "batch": [], + "error": None } for _ in range(len(batch_request['batch'])): - batch_result['batch_results'].append( + batch_result['batch'].append( { "is_authorized": False, "grant": None, "message": "", - "has_failed": False, - "critical_errors": {}, + "error": None, "__complete": False } ) @@ -555,30 +465,21 @@ async def batch_authorize( config=config['list_grants'] ): page: GrantsPage - if page['has_failed'] is True: - batch_result['critical_errors'] = page['errors'] + if page['error'] is not None: + batch_result['error'] = page['error'] return batch_result for grant in page['grants']: - for request, result in zip(batch_request['batch'], batch_result['batch_results']): + for request, result in zip(batch_request['batch'], batch_result['batch']): if result['__complete'] is True: continue - + eval_result = evaluate( request=base_request | request, grant=grant, - execute=self._execute, - only_crits=True + execute=self._execute ) - if eval_result['has_failed'] is True: - result['grant'] = grant - result['message'] = "A critical error has occurred. Therefore, the request is not authorized." - result['has_failed'] = True - result['critical_errors'] = eval_result['errors'] - result['__complete'] = True - continue - if eval_result['is_applicable'] is True: result['grant'] = grant result['message'] = "A deny grant is applicable to the request. Therefore, the request is not authorized." @@ -593,30 +494,21 @@ async def batch_authorize( config=config['list_grants'] ): page: GrantsPage - if page['has_failed'] is True: - batch_result['critical_errors'] = page['errors'] + if page['error'] is not None: + batch_result['error'] = page['error'] return batch_result - + for grant in page['grants']: - for request, result in zip(batch_request['batch'], batch_result['batch_results']): + for request, result in zip(batch_request['batch'], batch_result['batch']): if result['__complete'] is True: continue eval_result = evaluate( request=base_request | request, grant=grant, - execute=self._execute, - only_crits=True + execute=self._execute ) - if eval_result['has_failed'] is True: - result['grant'] = grant - result['message'] = "A critical error has occurred. Therefore, the request is not authorized." - result['has_failed'] = True - result['critical_errors'] = eval_result['errors'] - result['__complete'] = True - continue - if eval_result['is_applicable'] is True: result['is_authorized'] = True result['grant'] = grant @@ -624,9 +516,9 @@ async def batch_authorize( result['__complete'] = True continue - for result in batch_result['batch_results']: + for result in batch_result['batch']: is_complete = result.pop("__complete") if is_complete is False: result['message'] = "No grants are applicable to the request. Therefore, the request is implicitly denied and is not authorized." - return batch_result \ No newline at end of file + return batch_result diff --git a/src/authzee/compute/shared_mem_latch.py b/src/authzee/compute/shared_mem_latch.py index a9a979f..3b982e6 100644 --- a/src/authzee/compute/shared_mem_latch.py +++ b/src/authzee/compute/shared_mem_latch.py @@ -1,3 +1,5 @@ +"""See [](authzee.compute.shared_mem_latch.SharedMemLatch)""" + __all__ = [ "SharedMemLatch" ] @@ -8,7 +10,7 @@ class SharedMemLatch: """ Shared Memory Latch linked to a ``SharedMemoryManager``. - Must call ``unlink()`` to free the memory. + Must call ``unlink()`` to free the memory. Parameters ---------- @@ -19,15 +21,15 @@ class SharedMemLatch: def __init__(self, smm: SharedMemoryManager): self._sm = smm.SharedMemory(size=1) - + def is_set(self) -> bool: return self._sm.buf[0] == 1 - + def set(self) -> None: self._sm.buf[0] = 1 - + def unlink(self) -> None: - self._sm.unlink() \ No newline at end of file + self._sm.unlink() diff --git a/src/authzee/config.py b/src/authzee/config.py index faa239e..6ee0fe0 100644 --- a/src/authzee/config.py +++ b/src/authzee/config.py @@ -11,27 +11,27 @@ default_config: AuthzeeConfig = { "authzee": { - "raise_crits": True + "raise_errors": True }, "start": { "compute_start": { "storage": {} }, - "storage_start": {}, + "storage_start": {} }, "shutdown": { "compute_shutdown": { "storage": {} }, - "storage_shutdown": {}, + "storage_shutdown": {} }, "construct": { "compute_construct": {}, - "storage_construct": {}, + "storage_construct": {} }, "destroy": { "compute_destroy": {}, - "storage_destroy": {}, + "storage_destroy": {} }, "validate_context_def": {}, "list_context_defs": { @@ -49,7 +49,7 @@ "use_cache": False }, "get_identity_def": { - "use_cache": False + "use_cache": False }, "put_identity_def": {}, "delete_identity_def": {}, @@ -59,7 +59,7 @@ "use_cache": False }, "get_resource_def": { - "use_cache": False + "use_cache": False }, "put_resource_def": {}, "delete_resource_def": {}, @@ -69,7 +69,7 @@ "use_cache": False }, "get_grant": { - "use_cache": False + "use_cache": False }, "enact": {}, "repeal": {}, @@ -300,7 +300,7 @@ def override_config(override: dict | None, default: dict) -> dict: if override is None: return default - + full = {} for key in default: if key in override: @@ -308,9 +308,8 @@ def override_config(override: dict | None, default: dict) -> dict: full[key] = override_config(override[key], default[key]) else: full[key] = override[key] + else: - full[key] = default[key] + full[key] = default[key] return full - - \ No newline at end of file diff --git a/src/authzee/core.py b/src/authzee/core.py index 4717b7e..7d97756 100644 --- a/src/authzee/core.py +++ b/src/authzee/core.py @@ -1,52 +1,60 @@ -"""Core functionality for the Authzee SDK. +"""Core functionality for the Authzee SDK. The functionality of this module is optimized for SDK use. It conforms to the Authzee Specification but is not a one to one copy of the reference implementation. -For reference implementation see {py:mod}`authzee.reference` +For reference implementation see [](authzee.reference) """ __all__ = [ + "batch_request_validator", "context_def_schema", - "identity_def_schema", - "resource_def_schema", - "grant_schema", "context_def_validator", - "identity_def_validator", - "resource_def_validator", + "evaluate", + "grant_schema", "grant_validator", + "identity_def_schema", + "identity_def_validator", "request_validator", - "batch_request_validator", + "resource_def_schema", + "resource_def_validator", + "validate_batch_request_schema", "validate_context_def", - "validate_identity_def", - "validate_resource_def", "validate_grant", + "validate_identity_def", "validate_request_schema", - "validate_batch_request_schema", - "evaluate", - "combine_errors" + "validate_resource_def" ] import copy from typing import Callable -import jsonschema_rs +import jsonschema_rs -from authzee.types import * from authzee import reference +from authzee.types import * -context_def_schema = copy.deepcopy(reference.context_definition_schema) | { - "title": "SDK Context Definition", - "additionalProperties": False -} -identity_def_schema = copy.deepcopy(reference.identity_definition_schema) | { - "title": "SDK Identity Definition", - "additionalProperties": False -} -resource_def_schema = copy.deepcopy(reference.resource_definition_schema) | { - "title": "SDK Resource Definition", - "additionalProperties": False -} +context_def_schema = ( + copy.deepcopy(reference.context_definition_schema) + | { + "title": "SDK Context Definition", + "additionalProperties": False + } +) +identity_def_schema = ( + copy.deepcopy(reference.identity_definition_schema) + | { + "title": "SDK Identity Definition", + "additionalProperties": False + } +) +resource_def_schema = ( + copy.deepcopy(reference.resource_definition_schema) + | { + "title": "SDK Resource Definition", + "additionalProperties": False + } +) grant_schema = { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "SDK Grant", @@ -62,8 +70,8 @@ "actions", "data", "query", - "evaluation_handler", - "equality" + "equality", + "applicable_on_failure" ], "properties": { "grant_uuid": { @@ -115,9 +123,12 @@ "type": "string", "description": "JSON query to run on the authorization data. {\"grant\": , \"request\": }" }, - "evaluation_handler": reference._evaluation_handler_schema, "equality": { "description": "Expected value for the query to return. If the query result matches this value the grant is a considered applicable to the request." + }, + "applicable_on_failure": { + "type": "boolean", + "description": "If true, the grant is considered applicable even when the query execution produces a failure." } } } @@ -134,122 +145,92 @@ def validate_context_def(context_def: ContextDef) -> GenericResult: is_valid = context_def_validator.is_valid(context_def) if not is_valid: return { - "has_failed": True, - "errors": { - "definition": [ - { - "is_critical": True, - "message": "The given context definition is not valid against the context definition JSON Schema." - } - ] + "error": { + "error_type": "definition", + "message": "The given context definition is not valid against the context definition JSON Schema." } } - + if not( "type" in context_def['schema'] and context_def['schema']['type'] == "object" ): return { - "has_failed": True, - "errors": { - "definition": [ - { - "is_critical": True, - "message": "Context Definition schemas must have a root type of object." - } - ] + "error": { + "error_type": "definition", + "message": "Context Definition schemas must have a root type of object." } } - return {"has_failed": False, "errors": {}} + return { + "error": None + } + - def validate_identity_def(identity_def: IdentityDef) -> GenericResult: is_valid = identity_def_validator.is_valid(identity_def) if not is_valid: return { - "has_failed": True, - "errors": { - "definition": [ - { - "is_critical": True, - "message": "The given identity definition is not valid against the identity definition JSON Schema." - } - ] + "error": { + "error_type": "definition", + "message": "The given identity definition is not valid against the identity definition JSON Schema." } } - + if not( "type" in identity_def['schema'] and identity_def['schema']['type'] == "object" ): return { - "has_failed": True, - "errors": { - "definition": [ - { - "is_critical": True, - "message": "Identity Definition schemas must have a root type of object." - } - ] + "error": { + "error_type": "definition", + "message": "Identity Definition schemas must have a root type of object." } } - return {"has_failed": False, "errors": {}} + return { + "error": None + } def validate_resource_def(resource_def: ResourceDef) -> GenericResult: is_valid = resource_def_validator.is_valid(resource_def) if not is_valid: return { - "has_failed": True, - "errors": { - "definition": [ - { - "is_critical": True, - "message": "The given resource definition is not valid against the resource definition JSON Schema." - } - ] + "error": { + "error_type": "definition", + "message": "The given resource definition is not valid against the resource definition JSON Schema." } } - + if not( "type" in resource_def['schema'] and resource_def['schema']['type'] == "object" ): return { - "has_failed": True, - "errors": { - "definition": [ - { - "is_critical": True, - "message": "Resource Definition schemas must have a root type of object." - } - ] + "error": { + "error_type": "definition", + "message": "Resource Definition schemas must have a root type of object." } } - return {"has_failed": False, "errors": {}} + return { + "error": None + } def validate_grant(grant: Grant) -> GenericResult: is_valid = grant_validator.is_valid(grant) if not is_valid: return { - "has_failed": True, - "errors": { - "grant": [ - { - "is_critical": True, - "message": "The grant is not valid against the Grant Schema." - } - ] + "error": { + "error_type": "grant", + "message": "The grant is not valid against the Grant Schema." } } return { - "has_failed": False, - "errors": {} + "error": None } @@ -257,100 +238,59 @@ def validate_request_schema(request: AuthzeeRequest) -> GenericResult: is_valid = request_validator.is_valid(request) if not is_valid: return { - "has_failed": True, - "errors": { - "definition": [ - { - "is_critical": True, - "message": "The given request is not valid against the request JSON Schema." - } - ] + "error": { + "error_type": "request", + "message": "The given request is not valid against the request JSON Schema." } } return { - "has_failed": False, - "errors": {} + "error": None } -def validate_batch_request_schema(batch_request: AuthzeeBatchRequest) -> GenericResult: +def validate_batch_request_schema( + batch_request: AuthzeeBatchRequest +) -> GenericResult: is_valid = batch_request_validator.is_valid(batch_request) if not is_valid: return { - "has_failed": True, - "errors": { - "definition": [ - { - "is_critical": True, - "message": "The given batch request is not valid against the batch request JSON Schema." - } - ] + "error": { + "error_type": "request", + "message": "The given batch request is not valid against the batch request JSON Schema." } } return { - "has_failed": False, - "errors": {} + "error": None } def evaluate( - request: AuthzeeRequest, - grant: Grant, - execute: Callable[[str, AnyJSON], ExecuteResult], - only_crits: bool + request: AuthzeeRequest, + grant: Grant, + execute: Callable[[str, AnyJSON], ExecuteResult] ) -> EvaluateResult: result = { "is_applicable": False, "query_result": None, - "has_failed": False, - "errors": {} + "failure": None } query_result = execute( - grant['query'], + grant['query'], { "request": request, "grant": grant } ) - if query_result['has_failed'] is False: + if query_result['failure'] is None: result['query_result'] = query_result['result'] if query_result['result'] == grant['equality']: result['is_applicable'] = True + else: - q_val = grant['evaluation_handler'] if request['evaluation_handler'] == "grant" else request['evaluation_handler'] - is_q_val_crit = q_val == "critical" - if ( - ( - q_val == "error" - and only_crits is False - ) - or is_q_val_crit is True - ): - result['errors']['evaluation'] = [ - { - "is_critical": is_q_val_crit, - "message": f"A JSON Query error has occurred: {query_result['error_message']}." - } - ] - if is_q_val_crit is True: - result['has_failed'] = True + result['failure'] = query_result['failure'] + if grant['applicable_on_failure'] is True: + result['is_applicable'] = True return result - - -def combine_errors(result: GenericResult, *args: dict) -> None: - errors = result['errors'] - for new_result in args: - if new_result['has_failed'] is True: - result['has_failed'] = True - - new_errors = new_result['errors'] - for k in errors: - if k in new_errors: - errors[k] += new_errors[k] - - for k in new_errors: - if k not in errors: - errors[k] = new_errors[k] diff --git a/src/authzee/exceptions.py b/src/authzee/exceptions.py index 1cf81d6..feae3ee 100644 --- a/src/authzee/exceptions.py +++ b/src/authzee/exceptions.py @@ -3,18 +3,17 @@ __all__ = [ "AuthzeeError", + "AuthzeeSDKError", "AuthzeeSpecError", + "ComputeError", "DefinitionError", "GrantError", - "EvaluationError", - "RequestError", - "AuthzeeSDKError", "LocalityIncompatibilityError", - "ResourceNotFoundError", - "StartError", "NotImplementedError", "ParallelPaginationNotSupported", - "PageReferenceError" + "RequestError", + "ResourceNotFoundError", + "StorageError" ] from authzee.types import GenericResult @@ -29,12 +28,9 @@ class AuthzeeError(Exception): class AuthzeeSpecError(AuthzeeError): """Base exception for errors defined in the Authzee Specification. """ - - def __init__( - self, - message: str, - result: GenericResult - ): + + + def __init__(self, message: str, result: GenericResult): super().__init__(message) self.message = message self.result = result @@ -45,11 +41,6 @@ class DefinitionError(AuthzeeSpecError): pass -class EvaluationError(AuthzeeSpecError): - """Error when running an evaluation for a request.""" - pass - - class GrantError(AuthzeeSpecError): """Error when validating grants.""" pass @@ -63,12 +54,9 @@ class RequestError(AuthzeeSpecError): class AuthzeeSDKError(AuthzeeError): """Base exception for errors from the Authzee SDK that are **not** defined by the specification. """ - - def __init__( - self, - message: str, - result: GenericResult - ): + + + def __init__(self, message: str, result: GenericResult): super().__init__(message) self.message = message self.result = result @@ -86,7 +74,13 @@ class NotImplementedError(AuthzeeSDKError): """The given method is not implemented for this class. """ - def __init__(self, msg: str = "This method is not implemented.", *args, **kwargs): + + def __init__( + self, + msg: str="This method is not implemented.", + *args, + **kwargs + ): super().__init__(msg, *args, **kwargs) @@ -96,34 +90,32 @@ class ParallelPaginationNotSupported(AuthzeeSDKError): pass -class PageReferenceError(AuthzeeSDKError): - """Error when processing a page reference. +class ComputeError(AuthzeeSDKError): + """Base exception for errors specific to compute modules. """ pass -class ResourceNotFoundError(AuthzeeSDKError): - """The resource with a specific UUID or type was not found in the storage backend. - """ +class StorageError(AuthzeeSDKError): + """Base exception for errors specific to storage modules.""" pass -class StartError(AuthzeeSDKError): - """There was an error during initialization of the Authzee App and modules. +class ResourceNotFoundError(StorageError): + """The resource with a specific UUID or type was not found in the storage backend. """ pass _exception_map = { "definition": DefinitionError, - "evaluation": EvaluationError, "grant": GrantError, - "request": RequestError, + "request": RequestError, "locality_incompatibility": LocalityIncompatibilityError, "not_implemented": NotImplementedError, "parallel_pagination_not_supported": ParallelPaginationNotSupported, - "page_reference": PageReferenceError, - "resource_not_found": ResourceNotFoundError, - "start": StartError + "compute": ComputeError, + "storage": StorageError, + "resource_not_found": ResourceNotFoundError } -"""Mapping of error type strings to Exception classes.""" \ No newline at end of file +"""Mapping of error type strings to Exception classes.""" diff --git a/src/authzee/jmespath.py b/src/authzee/jmespath.py index 094ea69..d64e77f 100644 --- a/src/authzee/jmespath.py +++ b/src/authzee/jmespath.py @@ -1,17 +1,18 @@ -"""JMESPath custom function provided by Authzee. +"""JMESPath custom function provided by Authzee. -See {py:class}`authzee.jmespath.CustomJMESPathFunctions` +See [](authzee.jmespath.CustomJMESPathFunctions) """ __all__ = [ "CustomJMESPathFunctions", - "jmespath_execute", - "jmespath_custom_execute" + "jmespath_custom_execute", + "jmespath_execute" ] import re from typing import Any, Dict, List, Union + try: from jmespath import exceptions, functions, Options, search except ModuleNotFoundError: # pragma: no cover @@ -24,23 +25,23 @@ class CustomJMESPathFunctions(functions.Functions): Along with the standard [Built-in JMESPath Functions](https://jmespath.org/specification.html#built-in-functions) the following custom functions are added" - - `array[object] inner_join(array[any] $lhs, array[any] $rhs, expression->boolean expr)` + - `array[object] inner_join(array[any] $lhs, array[any] $rhs, expression->boolean expr)` - Like an SQL INNER JOIN - See [SDK Docs INNER JOIN](https://github.com/btemplep/authzee/blob/main/docs/sdks.md#inner-join) - - `array[object] left_join(array[any] $lhs, array[any] $rhs, expression->boolean expr)` + - `array[object] left_join(array[any] $lhs, array[any] $rhs, expression->boolean expr)` - Like an SQL LEFT JOIN - See [SDK Docs LEFT JOIN](https://github.com/btemplep/authzee/blob/main/docs/sdks.md#left-join) - - `array[object] outer_join(array[any] $lhs, array[any] $rhs, expression->boolean expr)` + - `array[object] outer_join(array[any] $lhs, array[any] $rhs, expression->boolean expr)` - Like an SQL OUTER JOIN - See [SDK Docs OUTER JOIN](https://github.com/btemplep/authzee/blob/main/docs/sdks.md#outer-join) - - `boolean is_identity_present(string $itype, object $request)` + - `boolean is_identity_present(string $itype, object $request)` - Checks if at least one entry of the specified identity type is present in the request - Returns true if present, or else false - See [SDK Docs Is Identity Present](https://github.com/btemplep/authzee/blob/main/docs/sdks.md#is-identity-present) - `string|null|array[string|null] regex_find(string $pattern, string|array[string] $subject)` - The return value depends on the subject type: - `string` - Run a regex pattern against a string and return the first occurrence of the pattern or `null` if there are none. - - `array[string]` - Run a regex pattern on an array of strings and return an equal length array where each element is the first occurrence of the pattern or `null` if there are none. + - `array[string]` - Run a regex pattern on an array of strings and return an equal length array where each element is the first occurrence of the pattern or `null` if there are none. - See [SDK Docs regex Find](https://github.com/btemplep/authzee/blob/main/docs/sdks.md#regex-find) - `array[string]|array[array[string]] regex_find_all(string $pattern, string|array[string] $subject)` - The return value depends on the subject type: @@ -57,25 +58,43 @@ class CustomJMESPathFunctions(functions.Functions): - `string` - Run a regex pattern against a string and return an array where each item is an array of groups for each occurrence of the pattern. If a group has no value it will be `null`. - `array[string]` - Run a regex pattern on an array of strings and return an equal length array where each element is an array of all occurrences of the pattern. Each element in the array of occurrences is an array of the groups. If a group has no value it will be `null`. - See [SDK Docs regex Groups All](https://github.com/btemplep/authzee/blob/main/docs/sdks.md#regex-groups-all) - - `string lower(string $subject)` + - `string lower(string $subject)` - Convert string to lowercase. - See [SDK Docs String Lower](https://github.com/btemplep/authzee/blob/main/docs/sdks.md#string-lower) - - `string upper(string $subject)` + - `string upper(string $subject)` - Convert string to uppercase. - See [SDK Docs String Upper](https://github.com/btemplep/authzee/blob/main/docs/sdks.md#string-upper) """ + def __init__(self): super().__init__() self._custom_options = Options(custom_functions=self) @functions.signature( - {"types": ["array"]}, - {"types": ["array"]}, - {"types": ["string"]} + { + "types": [ + "array" + ] + }, + { + "types": [ + "array" + ] + }, + { + "types": [ + "string" + ] + } ) - def _func_inner_join(self, lhs: List[Any], rhs: List[Any], expr: str) -> List[Dict[str, Any]]: + def _func_inner_join( + self, + lhs: List[Any], + rhs: List[Any], + expr: str + ) -> List[Dict[str, Any]]: result = [] for l in lhs: for r in rhs: @@ -94,16 +113,33 @@ def _func_inner_join(self, lhs: List[Any], rhs: List[Any], expr: str) -> List[Di "rhs": r } ) - + return result - + @functions.signature( - {"types": ["array"]}, - {"types": ["array"]}, - {"types": ["string"]} + { + "types": [ + "array" + ] + }, + { + "types": [ + "array" + ] + }, + { + "types": [ + "string" + ] + } ) - def _func_left_join(self, lhs: List[Any], rhs: List[Any], expr: str) -> List[Dict[str, Any]]: + def _func_left_join( + self, + lhs: List[Any], + rhs: List[Any], + expr: str + ) -> List[Dict[str, Any]]: result = [] for l in lhs: lhs_match = False @@ -123,7 +159,7 @@ def _func_left_join(self, lhs: List[Any], rhs: List[Any], expr: str) -> List[Dic "rhs": r } ) - + if lhs_match is False: result.append( { @@ -131,16 +167,33 @@ def _func_left_join(self, lhs: List[Any], rhs: List[Any], expr: str) -> List[Dic "rhs": None } ) - + return result @functions.signature( - {"types": ["array"]}, - {"types": ["array"]}, - {"types": ["string"]} + { + "types": [ + "array" + ] + }, + { + "types": [ + "array" + ] + }, + { + "types": [ + "string" + ] + } ) - def _func_outer_join(self, lhs: List[Any], rhs: List[Any], expr: str) -> List[Dict[str, Any]]: + def _func_outer_join( + self, + lhs: List[Any], + rhs: List[Any], + expr: str + ) -> List[Dict[str, Any]]: result = [] unmatched_rhs = set(rhs) for l in lhs: @@ -162,7 +215,7 @@ def _func_outer_join(self, lhs: List[Any], rhs: List[Any], expr: str) -> List[Di "rhs": r } ) - + if lhs_match is False: result.append( { @@ -170,7 +223,7 @@ def _func_outer_join(self, lhs: List[Any], rhs: List[Any], expr: str) -> List[Di "rhs": None } ) - + for r in unmatched_rhs: result.append( { @@ -178,33 +231,57 @@ def _func_outer_join(self, lhs: List[Any], rhs: List[Any], expr: str) -> List[Di "rhs": r } ) - + return result @functions.signature( - {"types": ["string"]}, - {"types": ["object"]} + { + "types": [ + "string" + ] + }, + { + "types": [ + "object" + ] + } ) def _func_is_identity_present(itype: str, request: dict) -> bool: - if itype in request['identities'] and len(request['identities'][itype]) > 0: + if ( + itype in request['identities'] + and len(request['identities'][itype]) > 0 + ): return True - + return False @functions.signature( - {"types": ["string"]}, - {"types": ["string", "array-string"]} + { + "types": [ + "string" + ] + }, + { + "types": [ + "string", + "array-string" + ] + } ) - def _func_regex_find(pattern: str, subject: Union[str, List[str]]) -> Union[None, str, List[Union[None, str]]]: + def _func_regex_find( + pattern: str, + subject: Union[str, List[str]] + ) -> Union[None, str, List[Union[None, str]]]: if type(subject) is str: match = re.search(pattern, subject) if match is not None: return match.group() + else: return None - + if type(subject) is list: result = [] for sub in subject: @@ -213,52 +290,63 @@ def _func_regex_find(pattern: str, subject: Union[str, List[str]]) -> Union[None result.append(match.group()) else: result.append(None) - + return result - - + + @functions.signature( - {"types": ["string"]}, - {"types": ["string", "array-string"]} + { + "types": [ + "string" + ] + }, + { + "types": [ + "string", + "array-string" + ] + } ) - def _func_regex_find_all(pattern: str, subject: Union[str, List[str]]) -> Union[List[str], List[List[str]]]: + def _func_regex_find_all( + pattern: str, + subject: Union[str, List[str]] + ) -> Union[List[str], List[List[str]]]: if type(subject) is str: return re.findall(pattern, subject) - + if type(subject) is list: result = [] for sub in subject: result.append(re.findall(pattern, sub)) - + return result - + @functions.signature( - {"types": ["string"]}, - {"types": ["string", "array-string"]} + { + "types": [ + "string" + ] + }, + { + "types": [ + "string", + "array-string" + ] + } ) def _func_regex_groups( - pattern: str, + pattern: str, subject: Union[str, List[str]] - ) -> Union[ - None, - List[Union[None, str]], - List[ - Union[ - None, - List[ - Union[None, str] - ] - ] - ] - ]: + ) -> Union[None, List[Union[None, str]], List[Union[None, List[Union[None, str]]]]]: if type(subject) is str: match = re.search(pattern, subject) if match is not None: return list(match.groups()) + else: return None - + if type(subject) is list: result = [] for sub in subject: @@ -267,44 +355,65 @@ def _func_regex_groups( result.append(list(match.groups())) else: result.append(None) - + return result @functions.signature( - {"types": ["string"]}, - {"types": ["string", "array-string"]} + { + "types": [ + "string" + ] + }, + { + "types": [ + "string", + "array-string" + ] + } ) def _func_regex_groups_all( - pattern: str, + pattern: str, subject: Union[str, List[str]] ) -> Union[List[str], List[List[str]]]: if type(subject) is str: return [list(m.groups()) if m is not None else None for m in re.finditer(pattern, subject)] - + if type(subject) is list: result = [] for sub in subject: result.append( [list(m.groups()) if m is not None else None for m in re.finditer(pattern, sub)] ) - + return result - @functions.signature({"types": ["string"]}) + @functions.signature( + { + "types": [ + "string" + ] + } + ) def _func_lower(self, string: str) -> str: return string.lower() - @functions.signature({"types": ["string"]}) + @functions.signature( + { + "types": [ + "string" + ] + } + ) def _func_upper(self, string: str) -> str: return string.upper() def jmespath_execute(expression: str, data: Any) -> dict: """Standard JMESPath JSON execute function for Authzee. - + See the standard [Built-in JMESPath Functions](https://jmespath.org/specification.html#built-in-functions). """ query_result = None @@ -313,14 +422,12 @@ def jmespath_execute(expression: str, data: Any) -> dict: except Exception as exc: return { "result": None, - "has_failed": True, - "error_message": f"A JMESPath Query error has occurred: {exc}" + "failure": f"A JMESPath Query error has occurred. [{exc.__class__.__qualname__}] - {exc}" } return { "result": query_result, - "has_failed": False, - "error_message": None + "failure": None } @@ -329,27 +436,27 @@ def jmespath_execute(expression: str, data: Any) -> dict: def jmespath_custom_execute(expression: str, data: Any) -> dict: """Standard JMESPath JSON execute function for Authzee that includes SDK recommended custom functions: - + Along with the standard [Built-in JMESPath Functions](https://jmespath.org/specification.html#built-in-functions) the following custom functions are added" - - `array[object] inner_join(array[any] $lhs, array[any] $rhs, expression->boolean expr)` + - `array[object] inner_join(array[any] $lhs, array[any] $rhs, expression->boolean expr)` - Like an SQL INNER JOIN - See [SDK Docs INNER JOIN](https://github.com/btemplep/authzee/blob/main/docs/sdks.md#inner-join) - - `array[object] left_join(array[any] $lhs, array[any] $rhs, expression->boolean expr)` + - `array[object] left_join(array[any] $lhs, array[any] $rhs, expression->boolean expr)` - Like an SQL LEFT JOIN - See [SDK Docs LEFT JOIN](https://github.com/btemplep/authzee/blob/main/docs/sdks.md#left-join) - - `array[object] outer_join(array[any] $lhs, array[any] $rhs, expression->boolean expr)` + - `array[object] outer_join(array[any] $lhs, array[any] $rhs, expression->boolean expr)` - Like an SQL OUTER JOIN - See [SDK Docs OUTER JOIN](https://github.com/btemplep/authzee/blob/main/docs/sdks.md#outer-join) - - `boolean is_identity_present(string $itype, object $request)` + - `boolean is_identity_present(string $itype, object $request)` - Checks if at least one entry of the specified identity type is present in the request - Returns true if present, or else false - See [SDK Docs Is Identity Present](https://github.com/btemplep/authzee/blob/main/docs/sdks.md#is-identity-present) - `string|null|array[string|null] regex_find(string $pattern, string|array[string] $subject)` - The return value depends on the subject type: - `string` - Run a regex pattern against a string and return the first occurrence of the pattern or `null` if there are none. - - `array[string]` - Run a regex pattern on an array of strings and return an equal length array where each element is the first occurrence of the pattern or `null` if there are none. + - `array[string]` - Run a regex pattern on an array of strings and return an equal length array where each element is the first occurrence of the pattern or `null` if there are none. - See [SDK Docs regex Find](https://github.com/btemplep/authzee/blob/main/docs/sdks.md#regex-find) - `array[string]|array[array[string]] regex_find_all(string $pattern, string|array[string] $subject)` - The return value depends on the subject type: @@ -366,26 +473,27 @@ def jmespath_custom_execute(expression: str, data: Any) -> dict: - `string` - Run a regex pattern against a string and return an array where each item is an array of groups for each occurrence of the pattern. If a group has no value it will be `null`. - `array[string]` - Run a regex pattern on an array of strings and return an equal length array where each element is an array of all occurrences of the pattern. Each element in the array of occurrences is an array of the groups. If a group has no value it will be `null`. - See [SDK Docs regex Groups All](https://github.com/btemplep/authzee/blob/main/docs/sdks.md#regex-groups-all) - - `string lower(string $subject)` + - `string lower(string $subject)` - Convert string to lowercase. - See [SDK Docs String Lower](https://github.com/btemplep/authzee/blob/main/docs/sdks.md#string-lower) - - `string upper(string $subject)` + - `string upper(string $subject)` - Convert string to uppercase. - See [SDK Docs String Upper](https://github.com/btemplep/authzee/blob/main/docs/sdks.md#string-upper) """ query_result = None try: - query_result = search(expression, data, options=_custom_options) + query_result = search( + expression, + data, + options=_custom_options + ) except Exception as exc: return { "result": None, - "has_failed": True, - "error_message": f"A JMESPath Query error has occurred: {exc}" + "failure": f"A JMESPath Query error has occurred. [{exc.__class__.__qualname__}] - {exc}" } return { "result": query_result, - "has_failed": False, - "error_message": None + "failure": None } - diff --git a/src/authzee/module_locality.py b/src/authzee/module_locality.py index 38e6a8a..5d0b06e 100644 --- a/src/authzee/module_locality.py +++ b/src/authzee/module_locality.py @@ -1,7 +1,8 @@ -"""Enum for locality and compatibility""" +"""See [](authzee.module_locality.ModuleLocality)""" + __all__ = [ "ModuleLocality", - "locality_compatibility", + "locality_compatibility" ] from enum import Enum @@ -10,23 +11,23 @@ class ModuleLocality(Enum): """Describes the the scope or "locality" of where compute or storage modules exist and communicate among Authzee apps. - - ``PROCESS`` + - ``PROCESS`` - Compute runs in same process as the Authzee app. - Storage is limited to the same process as the Authzee app. - - ``SYSTEM`` + - ``SYSTEM`` - Compute resources are on the same system as the Authzee app. - Storage is limited to the system running the Authzee app. - - ``NETWORK`` + - ``NETWORK`` - Compute resources are communicated to over the network. They are external to the system running the Authzee app. - Storage is reachable over the network. It is (or can be) external to the system running the Authzee app. - - The purpose of this enum is to help identify incompatibilities in compute and storage modules for authzee. - See the ``authzee.locality_compatibility`` dictionary for the compatibility matrix. + + The purpose of this enum is to help identify incompatibilities in compute and storage modules for authzee. + See the ``authzee.locality_compatibility`` dictionary for the compatibility matrix. """ PROCESS: str = "PROCESS" SYSTEM: str = "SYSTEM" NETWORK: str = "NETWORK" - + locality_compatibility = { ModuleLocality.PROCESS: { diff --git a/src/authzee/paginators.py b/src/authzee/paginators.py index 51837aa..e401fb7 100644 --- a/src/authzee/paginators.py +++ b/src/authzee/paginators.py @@ -1,28 +1,29 @@ -"""Paginators for {py:class}`authzee.authzee.Authzee` and {py:class}`authzee.authzee_async.AuthzeeAsync`. +"""Paginators for [](authzee.authzee.Authzee) and [](authzee.authzee_async.AuthzeeAsync). """ + __all__ = [ "paginator", - "paginator_async", + "paginator_async" ] from typing import Any, AsyncGenerator, Callable, Generator def paginator(func: Callable, **kwargs) -> Generator[Any, None, None]: - """Paginator for {py:class}`authzee.authzee.Authzee`. + """Paginator for [](authzee.authzee.Authzee). Parameters ---------- func : Callable Method to paginate. **kwargs - The KWArgs to pass to the method for pagination. + The KWArgs to pass to the method for pagination. Yields ------ Generator[Any, None, None] The page of results - + Examples -------- ```python @@ -40,25 +41,31 @@ def paginator(func: Callable, **kwargs) -> Generator[Any, None, None]: yield result kwargs['page_ref'] = result['next_page_ref'] - if result['next_page_ref'] is None or result['has_failed'] is True: + if ( + result['next_page_ref'] is None + or result['error'] is not None + ): break -async def paginator_async(afunc: Callable, **kwargs) -> AsyncGenerator[Any, None]: - """Paginator for {py:class}`authzee.authzee_async.AuthzeeAsync`. +async def paginator_async( + afunc: Callable, + **kwargs +) -> AsyncGenerator[Any, None]: + """Paginator for [](authzee.authzee_async.AuthzeeAsync). Parameters ---------- afunc : Callable Async method to paginate. **kwargs - The KWArgs to pass to the method for pagination. + The KWArgs to pass to the method for pagination. Yields ------ AsyncGenerator[Any, None] The page of results - + Examples -------- ```python @@ -72,9 +79,12 @@ async def paginator_async(afunc: Callable, **kwargs) -> AsyncGenerator[Any, None """ while True: result = await afunc(**kwargs) - + yield result kwargs['page_ref'] = result['next_page_ref'] - if result['next_page_ref'] is None or result['has_failed'] is True: + if ( + result['next_page_ref'] is None + or result['error'] is not None + ): break diff --git a/src/authzee/reference.py b/src/authzee/reference.py index 230890f..2b94b6f 100644 --- a/src/authzee/reference.py +++ b/src/authzee/reference.py @@ -1,7 +1,7 @@ """A reference implementation for the Authzee specification. -SDK may only use part of this reference implementation or none at all. -See {py:mod}`authzee.core` for internally focused SDK code. +SDK may only use part of this reference implementation or none at all. +See [](authzee.core) for internally focused SDK code. Core workflow: @@ -19,38 +19,36 @@ """ __all__ = [ - "context_definition_schema", - "identity_definition_schema", - "resource_definition_schema", - "grant_schema", - "generic_error_schema", - "validate_defs_result_schema", - "validate_grants_result_schema", - "request_schema", - "validate_request_result_schema", - "query_execute_result_schema", - "evaluate_one_result_schema", + "audit", "audit_result_schema", + "audit_workflow", + "authorize", "authorize_result_schema", - "batch_request_schema", - "validate_batch_request_result_schema", + "authorize_workflow", + "batch_audit", "batch_audit_result_schema", + "batch_audit_workflow", + "batch_authorize", "batch_authorize_result_schema", + "batch_authorize_workflow", + "batch_request_schema", + "context_definition_schema", + "evaluate_one", + "evaluate_one_result_schema", + "general_result_schema", + "generic_error_schema", + "grant_schema", + "identity_definition_schema", + "query_execute_result_schema", + "request_schema", + "resource_definition_schema", + "validate_batch_request", + "validate_batch_request_result_schema", "validate_context_defs", - "validate_identity_defs", - "validate_resource_defs", "validate_grants", + "validate_identity_defs", "validate_request", - "validate_batch_request", - "evaluate_one", - "audit", - "authorize", - "audit_workflow", - "authorize_workflow", - "batch_audit", - "batch_authorize", - "batch_audit_workflow", - "batch_authorize_workflow" + "validate_resource_defs" ] from typing import Callable, Dict, List, Union @@ -58,7 +56,15 @@ import jsonschema_rs -AnyJSON = Union[bool, str, int, float, None, list, dict] +AnyJSON = Union[ + bool, + str, + int, + float, + None, + list, + dict +] _type_regex = "^[A-Za-z0-9_]*$" _type_schema = { @@ -94,21 +100,40 @@ "title": "Core and Validation specifications meta-schema", "allOf": [ - {"$ref": "meta/core"}, - {"$ref": "meta/applicator"}, - {"$ref": "meta/unevaluated"}, - {"$ref": "meta/validation"}, - {"$ref": "meta/meta-data"}, - {"$ref": "meta/format-annotation"}, - {"$ref": "meta/content"} + { + "$ref": "meta/core" + }, + { + "$ref": "meta/applicator" + }, + { + "$ref": "meta/unevaluated" + }, + { + "$ref": "meta/validation" + }, + { + "$ref": "meta/meta-data" + }, + { + "$ref": "meta/format-annotation" + }, + { + "$ref": "meta/content" + } + ], + "type": [ + "object", + "boolean" ], - "type": ["object", "boolean"], "$comment": "This meta-schema also defines keywords that have appeared in previous drafts in order to prevent incompatible extensions as they remain in common use.", "properties": { "definitions": { "$comment": "\"definitions\" has been replaced by \"$defs\".", "type": "object", - "additionalProperties": { "$dynamicRef": "#meta" }, + "additionalProperties": { + "$dynamicRef": "#meta" + }, "deprecated": True, "default": {} }, @@ -117,8 +142,12 @@ "type": "object", "additionalProperties": { "anyOf": [ - { "$dynamicRef": "#meta" }, - { "$ref": "meta/validation#/$defs/stringArray" } + { + "$dynamicRef": "#meta" + }, + { + "$ref": "meta/validation#/$defs/stringArray" + } ] }, "deprecated": True, @@ -136,18 +165,27 @@ } } } -_context_type_schema = _type_schema | { - "title": "Authzee Context Type", - "description": "A unique name to identity this context type." -} -_identity_type_schema = _type_schema | { - "title": "Authzee Identity Type", - "description": "A unique name to identity this identity type." -} -_resource_type_schema = _type_schema | { - "title": "Authzee Resource Type", - "description": "A unique name to identity this resource type." -} +_context_type_schema = ( + _type_schema + | { + "title": "Authzee Context Type", + "description": "A unique name to identity this context type." + } +) +_identity_type_schema = ( + _type_schema + | { + "title": "Authzee Identity Type", + "description": "A unique name to identity this identity type." + } +) +_resource_type_schema = ( + _type_schema + | { + "title": "Authzee Resource Type", + "description": "A unique name to identity this resource type." + } +) context_definition_schema = { "$schema": "https://json-schema.org/draft/2020-12/schema", @@ -200,21 +238,7 @@ "schema": _schema_schema } } -_evaluation_handler_schema = { - "title": "Grant-Level Evaluation Handler Setting", - "description": ( - "Set how evaluation errors are handled." - "'evaluate' - Evaluation is run and any errors cause the grant to be inapplicable to the request, but are not included in the result." - "'error' - Includes the 'validate' setting checks, and also includes errors in the result. " - "'critical' - Includes the 'error' setting checks, and will flag the error as critical, thus exiting the Authzee Operation early." - ), - "type": "string", - "enum": [ - "evaluate", - "error", - "critical" - ] -} + grant_schema = { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Grant", @@ -226,8 +250,8 @@ "actions", "data", "query", - "evaluation_handler", - "equality" + "equality", + "applicable_on_failure" ], "properties": { "effect": { @@ -256,98 +280,51 @@ "type": "string", "description": "JSON query to run on the authorization data. {\"grant\": , \"request\": }" }, - "evaluation_handler": _evaluation_handler_schema, "equality": { "description": "Expected value for the query to return. If the query result matches this value the grant is a considered applicable to the request." + }, + "applicable_on_failure": { + "type": "boolean", + "description": "If true, the grant is considered applicable even when the query execution produces a failure." } } } generic_error_schema = { - "title": "Authzee Error", - "description": "Object representing an instance of an error.", - "type": "object", - "additionalProperties": False, + "title": "Operation Error", + "description": "Error from an Authzee operation, or null if no error.", + "type": [ + "object", + "null" + ], "required": [ - "is_critical", + "error_type", "message" ], "properties": { - "is_critical": { - "type": "boolean", - "description": "If this error is critical. Critical errors generally halt further operations." + "error_type": { + "type": "string", + "description": "The type of error." }, "message": { "type": "string", - "description": "Detailed message about what caused the error." + "description": "Message describing the error." } } } -_is_valid_schema = { - "type": "boolean", - "description": "If the inputs have been successfully validated or not." -} -validate_defs_result_schema = { +general_result_schema = { "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "Definition Validation Result.", - "description": "Definition validation result.", + "title": "General Result", + "description": "General result, where no distinct return value is needed. Only passes on if there was an error or not. ", "type": "object", "additionalProperties": False, "required": [ - "is_valid", - "errors" + "error" ], "properties": { - "is_valid": _is_valid_schema, - "errors": { - "type": "object", - "additionalProperties": False, - "required": [], - "properties": { - "definition": { - "type": "array", - "items": generic_error_schema - } - } - } + "error": generic_error_schema } } -validate_grants_result_schema = { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "Grant Validation Result.", - "description": "Grant Validation Result.", - "type": "object", - "additionalProperties": False, - "required": [ - "is_valid", - "errors" - ], - "properties": { - "is_valid": _is_valid_schema, - "errors": { - "type": "object", - "additionalProperties": False, - "required": [], - "properties": { - "grant": { - "type": "array", - "items": generic_error_schema - } - } - } - } -} -_request_evaluation_handler_schema = { - "title": "Request-Level Evaluation Error Handling Setting", - "description": ( - "Request-level Evaluation Handler Setting. Can be used to override grant level evaluation handling. " - "'grant' - Use the grant level setting. No override. " - "'evaluation' - Evaluation is run and any errors cause the grant to be inapplicable to the request, but are not included in the result. " - "'error' - Includes the 'validate' setting checks, and also includes errors in the result. " - "'critical' - Includes the 'error' setting checks, and will flag the error as critical, thus exiting the Authzee Operation early." - ), - "type": "string", - "enum": ["grant"] + _evaluation_handler_schema['enum'] -} + _request_identities_schema = { "description": "Object whose keys are the identity types, and values are an array of instances of that identity type.", "type": "object", @@ -381,8 +358,7 @@ "resource_type", "resource", "context_type", - "context", - "evaluation_handler" + "context" ], "properties": { "identities": _request_identities_schema, @@ -390,55 +366,11 @@ "resource_type": _resource_type_schema, "resource": _request_resource_schema, "context_type": _context_type_schema, - "context": _request_context_schema, - "evaluation_handler": _request_evaluation_handler_schema + "context": _request_context_schema } } -validate_request_result_schema = { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "Request Validation Result", - "description": "Request Validation Result schema.", - "type": "object", - "additionalProperties": False, - "required": [ - "is_valid", - "errors" - ], - "properties": { - "is_valid": _is_valid_schema, - "errors": { - "type": "object", - "additionalProperties": False, - "required": [], - "properties": { - "request": { - "type": "array", - "items": generic_error_schema - } - } - } - } -} -_operation_errors_schema = { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "Operation Result Errors", - "description": "Errors returned from Authzee Operations.", - "type": "object", - "additionalProperties": False, - "required": [], - "properties": { - "evaluation": { - "type": "array", - "items": generic_error_schema - } - } -} -_has_failed_schema = { - "type": "boolean", - "description": "If the request has failed from a critical error or not." -} _query_result_schema = { - "description": "Result from running the JSON query." + "description": "Result from running the JSON query in the grant." } query_execute_result_schema = { "$schema": "https://json-schema.org/draft/2020-12/schema", @@ -448,18 +380,16 @@ "additionalProperties": False, "required": [ "result", - "has_failed", - "error_message" + "failure" ], "properties": { "result": _query_result_schema, - "has_failed": _has_failed_schema, - "error_message": { + "failure": { "type": [ "string", "null" ], - "description": "Details of why the query failed. `null` if there are no errors." + "description": "A message describing why the query execution failed, or null if no failure occurred." } } } @@ -476,31 +406,20 @@ "required": [ "is_applicable", "query_result", - "has_failed", - "errors" + "failure" ], "properties": { "is_applicable": _is_applicable_schema, "query_result": _query_result_schema, - "has_failed": _has_failed_schema, - "errors": { - "type": "object", - "additionalProperties": False, - "required": [], - "properties": { - "evaluation": { - "type": "array", - "items": generic_error_schema - } - } + "failure": { + "type": [ + "string", + "null" + ], + "description": "A message describing why the evaluation failed, or null if no failure occurred. Evaluation failures do not cause the operation to fail." } } } -_audit_grant_list_schema = { - "type": "array", - "description": "List of grants that have been processed for the request.", - "items": grant_schema -} audit_result_schema = { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Audit Result", @@ -508,33 +427,37 @@ "type": "object", "additionalProperties": True, "required": [ - "grants", "results", - "has_failed", - "errors" + "error" ], "properties": { - "grants": _audit_grant_list_schema, "results": { "type": "array", - "description": "List of grant evaluation results for each respective grant index.", + "description": "List of grant evaluation results.", "items": { "type": "object", "additionalProperties": True, "required": [ + "grant", "is_applicable", "query_result", - "errors" + "failure" ], "properties": { + "grant": grant_schema, "is_applicable": _is_applicable_schema, "query_result": _query_result_schema, - "errors": _operation_errors_schema + "failure": { + "type": [ + "string", + "null" + ], + "description": "A message describing why the evaluation failed, or null if no failure occurred." + } } } }, - "has_failed": _has_failed_schema, - "errors": _operation_errors_schema + "error": generic_error_schema } } authorize_result_schema = { @@ -547,8 +470,7 @@ "is_authorized", "grant", "message", - "has_failed", - "critical_errors" + "error" ], "properties": { "is_authorized": { @@ -569,14 +491,13 @@ "type": "string", "description": "Details about why the request was authorized or not.", "enum": [ - "A critical error has occurred. Therefore, the request is not authorized.", + "An error has occurred. Therefore, the request is not authorized.", "A deny grant is applicable to the request. Therefore, the request is not authorized.", "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.", "No grants are applicable to the request. Therefore, the request is implicitly denied and is not authorized." ] }, - "has_failed": _has_failed_schema, - "critical_errors": _operation_errors_schema + "error": generic_error_schema } } @@ -594,7 +515,6 @@ "resource", "context_type", "context", - "evaluation_handler", "batch" ], "properties": { @@ -603,21 +523,18 @@ }, "action": _action_schema, "resource_type": _resource_type_schema | { - "description": _resource_type_schema['description'] + _request_level_description + "description": _resource_type_schema['description'] + _request_level_description }, "resource": _request_resource_schema | { - "description": _request_resource_schema['description'] + _request_level_description + "description": _request_resource_schema['description'] + _request_level_description }, "context_type": _context_type_schema, "context": _request_context_schema | { "description": _request_context_schema['description'] + _request_level_description }, - "evaluation_handler": _request_evaluation_handler_schema | { - "description": _request_evaluation_handler_schema['description'] + _request_level_description - }, "batch": { "type": "array", - "description": "Batch of resources and contexts to process with shared identities, action, resource type, and context type.", + "description": "Batch of items to process with shared resource types. When evaluated, each item is merged with the root request, where the batch item fields take precedence.", "minItems": 1, "items": { "type": "object", @@ -636,10 +553,10 @@ "string", "null" ], - "description": _resource_type_schema['description'] + _batch_item_level_description + "description": _resource_type_schema['description'] + _batch_item_level_description }, "resource": _request_resource_schema | { - "description": "Resource for this batch item, that is an instance of the given resource_type" + "description": "Resource for this batch item, that is an instance of the given resource_type. Overrides the batch request level if the field exists and is not null." }, "context_type": _context_type_schema | { "type": [ @@ -654,13 +571,6 @@ "null" ], "description": "Context for the request that is an instance of context_type." + _batch_item_level_description - }, - "evaluation_handler": _request_evaluation_handler_schema | { - "type": [ - "string", - "null" - ], - "description": _request_evaluation_handler_schema['description'] + _batch_item_level_description } } } @@ -674,53 +584,19 @@ "type": "object", "additionalProperties": False, "required": [ - "is_valid", - "errors", + "error", "batch_errors" ], "properties": { - "is_valid": _is_valid_schema, - "errors": { - "type": "object", - "additionalProperties": False, - "required": [], - "properties": { - "request": { - "type": "array", - "items": generic_error_schema - } - } - }, + "error": generic_error_schema, "batch_errors": { "type": "array", "description": "Each result corresponds to the batch request item of the same index.", - "items": { - "type": "object", - "additionalProperties": False, - "required": [], - "properties": { - "request": { - "type": "array", - "items": generic_error_schema - } - } - } + "items": generic_error_schema } } } -_batch_result_errors_schema = { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "Batch Result Errors", - "description": "Errors returned from Authzee Batch requests.", - "type": "object", - "additionalProperties": True, - "required": [], - "properties": {} -} -_has_failed_batch_schema = { - "type": "boolean", - "description": "If the batch request could not be validated and failed or not. " -} + batch_audit_result_schema = { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Batch Audit Result", @@ -729,13 +605,16 @@ "additionalProperties": True, "required": [ "grants", - "batch_results", - "has_failed", - "errors" + "batch", + "error" ], "properties": { - "grants": _audit_grant_list_schema, - "batch_results": { + "grants": { + "type": "array", + "description": "List of grants that have been processed for the request.", + "items": grant_schema + }, + "batch": { "type": "array", "description": "Array of results from a batch request. Each result corresponds to the batch request item of the same index.", "items": { @@ -744,8 +623,7 @@ "additionalProperties": True, "required": [ "results", - "has_failed", - "errors" + "error" ], "properties": { "results": { @@ -757,22 +635,26 @@ "required": [ "is_applicable", "query_result", - "errors" + "failure" ], "properties": { "is_applicable": _is_applicable_schema, "query_result": _query_result_schema, - "errors": _operation_errors_schema + "failure": { + "type": [ + "string", + "null" + ], + "description": "A message describing why the evaluation failed, or null if no failure occurred." + } } } }, - "has_failed": _has_failed_schema, - "errors": _operation_errors_schema + "error": generic_error_schema } } }, - "has_failed": _has_failed_batch_schema, - "errors": _batch_result_errors_schema + "error": generic_error_schema } } batch_authorize_result_schema = { @@ -782,460 +664,456 @@ "type": "object", "additionalProperties": True, "required": [ - "batch_results", - "has_failed", - "errors" + "batch", + "error" ], "properties": { - "batch_results": { + "batch": { "type": "array", "description": "Array of results from a batch request. Each result corresponds to the batch request item of the same index.", "items": authorize_result_schema }, - "has_failed": _has_failed_batch_schema, - "errors": _batch_result_errors_schema + "error": generic_error_schema } } -def validate_context_defs(context_defs: List[Dict[str, AnyJSON]]) -> Dict[str, AnyJSON]: - errors = [] +def validate_context_defs( + context_defs: List[Dict[str, AnyJSON]] +) -> Dict[str, AnyJSON]: context_types = set() for c_def in context_defs: try: jsonschema_rs.validate(context_definition_schema, c_def) except jsonschema_rs.ValidationError as exc: - errors.append( - { - "is_critical": True, + return { + "error": { + "error_type": "definition", "message": f"Context def is not valid. Schema Error: {exc}'" } - ) - continue + } if c_def['context_type'] not in context_types: context_types.add(c_def['context_type']) else: - errors.append( - { - "is_critical": True, + return { + "error": { + "error_type": "definition", "message": f"Context types must be unique. '{c_def['context_type']}' is present more than once." } - ) - - if "type" not in c_def['schema'] or c_def['schema']['type'] != "object": - errors.append( - { - "is_critical": True, + } + + if ( + "type" not in c_def['schema'] + or c_def['schema']['type'] != "object" + ): + return { + "error": { + "error_type": "definition", "message": "Context schemas must declare the root type to be an object." - } - ) + } + } return { - "is_valid": True if len(errors) == 0 else False, - "errors": errors + "error": None } -def validate_identity_defs(identity_defs: List[Dict[str, AnyJSON]]) -> Dict[str, AnyJSON]: - errors = [] +def validate_identity_defs( + identity_defs: List[Dict[str, AnyJSON]] +) -> Dict[str, AnyJSON]: id_types = [] for id_def in identity_defs: - print(id_def) try: jsonschema_rs.validate(identity_definition_schema, id_def) except jsonschema_rs.ValidationError as exc: - print(exc) - errors.append( - { - "is_critical": True, + return { + "error": { + "error_type": "definition", "message": f"Identity definition is not valid. Schema Error: {exc}'" } - ) - continue + } if id_def['identity_type'] not in id_types: id_types.append(id_def['identity_type']) else: - errors.append( - { - "is_critical": True, + return { + "error": { + "error_type": "definition", "message": f"Identity types must be unique. '{id_def['identity_type']}' is present more than once." } - ) - - if "type" not in id_def['schema'] or id_def['schema']['type'] != "object": - errors.append( - { - "is_critical": True, + } + + if ( + "type" not in id_def['schema'] + or id_def['schema']['type'] != "object" + ): + return { + "error": { + "error_type": "definition", "message": "Identity schemas must declare the root type to be an object." - } - ) + } + } return { - "is_valid": True if len(errors) == 0 else False, - "errors": errors + "error": None } -def validate_resource_defs(resource_defs: List[Dict[str, AnyJSON]]) -> Dict[str, AnyJSON]: - errors = [] +def validate_resource_defs( + resource_defs: List[Dict[str, AnyJSON]] +) -> Dict[str, AnyJSON]: r_types = set() for r_def in resource_defs: try: jsonschema_rs.validate(resource_definition_schema, r_def) except jsonschema_rs.ValidationError as exc: - errors.append( - { - "is_critical": True, + return { + "error": { + "error_type": "definition", "message": f"Resource definition is not valid. Schema Error: {exc}" } - ) - continue + } if r_def['resource_type'] not in r_types: r_types.add(r_def['resource_type']) else: - errors.append( - { - "is_critical": True, + return { + "error": { + "error_type": "definition", "message": f"Resource types must be unique. '{r_def['resource_type']}' is present more than once." } - ) - - if "type" not in r_def['schema'] or r_def['schema']['type'] != "object": - errors.append( - { - "is_critical": True, + } + + if ( + "type" not in r_def['schema'] + or r_def['schema']['type'] != "object" + ): + return { + "error": { + "error_type": "definition", "message": "Resource schemas must declare the root type to be an object." - } - ) - + } + } + return { - "is_valid": True if len(errors) == 0 else False, - "errors": errors + "error": None } -def validate_grants( - grants: List[Dict[str, AnyJSON]] -) -> Dict[str, AnyJSON]: - errors = [] +def validate_grants(grants: List[Dict[str, AnyJSON]]) -> Dict[str, AnyJSON]: for g in grants: try: jsonschema_rs.validate(grant_schema, g) except jsonschema_rs.ValidationError as exc: - errors.append( - { - "is_critical": True, - "message": f"The grant is not valid. Schema Error: {exc}" + return { + "error": { + "error_type": "grant", + "message": f"The grant is not valid. Schema Error: {exc}" } - ) - + } + return { - "is_valid": True if len(errors) == 0 else False, - "errors": errors + "error": None } + def _validate_request_identities( identities: Dict[str, AnyJSON], - identity_lut: dict, - errors: list -) -> None: + identity_lut: dict +) -> str | None: for i_type in identities: if i_type not in identity_lut: - errors.append( - { - "is_critical": True, - "message": f"Identity Type '{i_type}' is not valid." - } - ) + return f"Identity Type '{i_type}' is not valid." + else: for identity, i_num in zip(identities[i_type], range(len(identities[i_type]))): try: - jsonschema_rs.validate(identity_lut[i_type]['schema'], identity) - except jsonschema_rs.ValidationError as exc: - errors.append( - { - "is_critical": True, - "message": f"Identity '{i_type}[{i_num}]' is not valid. Schema Error: {exc}" - } + jsonschema_rs.validate( + identity_lut[i_type]['schema'], + identity ) + except jsonschema_rs.ValidationError as exc: + return f"Identity '{i_type}[{i_num}]' is not valid. Schema Error: {exc}" + + return None def _validate_request_resource( resource_type: str, resource: dict, action: str, - resource_lut: dict, - errors: list -) -> None: + resource_lut: dict +) -> str | None: if resource_type not in resource_lut: - errors.append( - { - "is_critical": True, - "message": f"Resource type '{resource_type}' is not valid." - } + return f"Resource type '{resource_type}' is not valid." + + try: + jsonschema_rs.validate( + resource_lut[resource_type]['schema'], + resource ) - else: - try: - jsonschema_rs.validate(resource_lut[resource_type]['schema'], resource) - except jsonschema_rs.ValidationError as exc: - errors.append( - { - "is_critical": True, - "message": f"The request resource is not valid for the '{resource_type}' resource type. Schema Error: {exc}" - } - ) + except jsonschema_rs.ValidationError as exc: + return f"The request resource is not valid for the '{resource_type}' resource type. Schema Error: {exc}" + + if action not in resource_lut[resource_type]['actions']: + return f"'{action}' is not a valid action for the '{resource_type}' resource type." + + return None - if action not in resource_lut[resource_type]['actions']: - errors.append( - { - "is_critical": True, - "message": f"'{action}' is not a valid action for the '{resource_type}' resource type." - } - ) def _validate_request_context( context_type: str, context: dict, - context_lut: dict, - errors: list -) -> None: + context_lut: dict +) -> str | None: if context_type not in context_lut: - errors.append( - { - "is_critical": True, - "message": f"Context type '{context_type}' is not valid." - } + return f"Context type '{context_type}' is not valid." + + try: + jsonschema_rs.validate( + context_lut[context_type]['schema'], + context ) - else: - try: - jsonschema_rs.validate(context_lut[context_type]['schema'], context) - except jsonschema_rs.ValidationError as exc: - errors.append( - { - "is_critical": True, - "message": f"The request context is not valid for the '{context_type}' context type. Schema Error: {exc}" - } - ) - + except jsonschema_rs.ValidationError as exc: + return f"The request context is not valid for the '{context_type}' context type. Schema Error: {exc}" + + return None + def validate_request( request: Dict[str, AnyJSON], context_defs: List[Dict[str, AnyJSON]], - identity_defs:List[Dict[str, AnyJSON]], + identity_defs: List[Dict[str, AnyJSON]], resource_defs: List[Dict[str, AnyJSON]] ) -> Dict[str, AnyJSON]: try: jsonschema_rs.validate(request_schema, request) except jsonschema_rs.ValidationError as exc: return { - "is_valid": False, - "errors" : [ - { - "is_critical": True, - "message": f"The request is not valid. Schema Error: {exc}" - } - ] + "error": { + "error_type": "request", + "message": f"The request is not valid. Schema Error: {exc}" + } } - - errors = [] - _validate_request_identities( + + err = _validate_request_identities( identities=request['identities'], - identity_lut={i['identity_type']: i for i in identity_defs}, - errors=errors + identity_lut={i['identity_type']: i for i in identity_defs} ) - _validate_request_resource( + if err is not None: + return { + "error": { + "error_type": "request", + "message": err + } + } + + err = _validate_request_resource( resource_type=request['resource_type'], resource=request['resource'], action=request['action'], - resource_lut={r['resource_type']: r for r in resource_defs}, - errors=errors + resource_lut={r['resource_type']: r for r in resource_defs} ) - _validate_request_context( + if err is not None: + return { + "error": { + "error_type": "request", + "message": err + } + } + + err = _validate_request_context( context_type=request['context_type'], context=request['context'], - context_lut={c['context_type']: c for c in context_defs}, - errors=errors + context_lut={c['context_type']: c for c in context_defs} ) + if err is not None: + return { + "error": { + "error_type": "request", + "message": err + } + } return { - "is_valid": True if len(errors) == 0 else False, - "errors": errors + "error": None } def validate_batch_request( batch_request: Dict[str, AnyJSON], context_defs: List[Dict[str, AnyJSON]], - identity_defs:List[Dict[str, AnyJSON]], + identity_defs: List[Dict[str, AnyJSON]], resource_defs: List[Dict[str, AnyJSON]] ) -> Dict[str, AnyJSON]: try: jsonschema_rs.validate(batch_request_schema, batch_request) except jsonschema_rs.ValidationError as exc: return { - "is_valid": False, - "errors" : [ - { - "is_critical": True, - "message": f"The batch request is not valid. Schema Error: {exc}" - } - ], - "results": None # return None if we can't validate the schema + "error": { + "error_type": "request", + "message": f"The batch request is not valid. Schema Error: {exc}" + }, + "batch_errors": [] } - errors = [] - batch_item_errors = [[] for _ in batch_request['batch']] identity_lut = {i['identity_type']: i for i in identity_defs} resource_lut = {r['resource_type']: r for r in resource_defs} context_lut = {c['context_type']: c for c in context_defs} - _validate_request_identities( + + err = _validate_request_identities( identities=batch_request['identities'], - identity_lut=identity_lut, - errors=errors + identity_lut=identity_lut ) - _validate_request_resource( + if err is not None: + return { + "error": { + "error_type": "request", + "message": err + }, + "batch_errors": [] + } + + err = _validate_request_resource( resource_type=batch_request['resource_type'], resource=batch_request['resource'], action=batch_request['action'], - resource_lut=resource_lut, - errors=errors + resource_lut=resource_lut ) - _validate_request_context( + if err is not None: + return { + "error": { + "error_type": "request", + "message": err + }, + "batch_errors": [] + } + + err = _validate_request_context( context_type=batch_request['context_type'], context=batch_request['context'], - context_lut=context_lut, - errors=errors + context_lut=context_lut ) - for item, bi_errors in zip(batch_request['batch'], batch_item_errors): - if item.get("identities", None) is not None: - _validate_request_identities( + if err is not None: + return { + "error": { + "error_type": "request", + "message": err + }, + "batch_errors": [] + } + + batch_errors = [] + for item in batch_request['batch']: + item_err = None + if ( + item_err is None + and item.get("identities", None) is not None + ): + item_err = _validate_request_identities( identities=item['identities'], - identity_lut=identity_lut, - errors=bi_errors + identity_lut=identity_lut ) - - if ( - item.get("resource_type", None) is not None + + if item_err is None and ( + item.get("resource_type", None) is not None or item.get("resource", None) is not None ): - _validate_request_resource( + item_err = _validate_request_resource( resource_type=item.get("resource_type", batch_request['resource_type']), resource=item.get("resource", batch_request['resource']), action=batch_request['action'], - resource_lut=resource_lut, - errors=bi_errors + resource_lut=resource_lut ) - if ( - item.get("context_type", None) is not None + if item_err is None and ( + item.get("context_type", None) is not None or item.get("context", None) is not None ): - _validate_request_context( + item_err = _validate_request_context( context_type=item.get("context_type", batch_request['context_type']), - context=item.get("context", batch_request['context_type']), - context_lut=context_lut, - errors=bi_errors + context=item.get("context", batch_request['context']), + context_lut=context_lut + ) + + if item_err is not None: + batch_errors.append( + { + "error_type": "request", + "message": item_err + } ) - + else: + batch_errors.append(None) + return { - "is_valid": True if len(errors) == 0 else False, - "errors": errors, - "batch_errors": [{"request": errors} for errors in batch_item_errors] + "error": None, + "batch_errors": batch_errors } def evaluate_one( - request: Dict[str, AnyJSON], + request: Dict[str, AnyJSON], grant: Dict[str, AnyJSON], - execute: Callable[[str, AnyJSON], AnyJSON], - only_crits: bool + execute: Callable[[str, AnyJSON], AnyJSON] ) -> Dict[str, AnyJSON]: result = { "is_applicable": False, "query_result": None, - "has_failed": False, - "errors": {} + "failure": None } if ( len(grant['actions']) > 0 - and request['action'] not in grant['actions'] + and request['action'] not in grant['actions'] ): return result query_result = execute( - grant['query'], + grant['query'], { "request": request, "grant": grant } ) - if query_result['has_failed'] is False: + if query_result['failure'] is None: result['query_result'] = query_result['result'] if query_result['result'] == grant['equality']: result['is_applicable'] = True + else: - q_val = grant['evaluation_handler'] if request['evaluation_handler'] == "grant" else request['evaluation_handler'] - is_q_val_crit = q_val == "critical" - if ( - ( - q_val == "error" - and only_crits is False - ) - or is_q_val_crit is True - ): - result['errors']['evaluation'] = [ - { - "is_critical": is_q_val_crit, - "message": f"A JSON Query error has occurred: {query_result['error_message']}." - } - ] - if is_q_val_crit is True: - result['has_failed'] = True + result['failure'] = f"A JSON Query error has occurred: {query_result['failure']}." + if grant['applicable_on_failure'] is True: + result['is_applicable'] = True return result def audit( - request: Dict[str, AnyJSON], + request: Dict[str, AnyJSON], grants: List[Dict[str, AnyJSON]], execute: Callable[[str, AnyJSON], AnyJSON] -) -> Dict[str, List[Dict[str, AnyJSON]]]: +) -> Dict[str, List[Dict[str, AnyJSON]]]: result = { - "grants": grants, "results": [], - "has_failed": False, - "errors": {} + "error": None } for g in grants: - g_eval = evaluate_one(request, g, execute, False) + g_eval = evaluate_one(request, g, execute) result['results'].append( { + "grant": g, "is_applicable": g_eval['is_applicable'], "query_result": g_eval['query_result'], - "errors": g_eval['errors'] + "failure": g_eval['failure'] } ) - if g_eval['has_failed'] is True: - result['has_failed'] = True - result['errors'] = { - "evaluation": [ - { - "is_critical": True, - "message": "A critical error occurred when processing the last returned result." - } - ] - } - - return result return result def authorize( - request: Dict[str, AnyJSON], + request: Dict[str, AnyJSON], grants: List[Dict[str, AnyJSON]], execute: Callable[[str, AnyJSON], AnyJSON] ) -> Dict[str, AnyJSON]: @@ -1246,53 +1124,32 @@ def authorize( allow_grants.append(g) else: deny_grants.append(g) - + for g in deny_grants: - g_eval = evaluate_one(request, g, execute, True) - if g_eval['has_failed'] is True: - return { - "is_authorized": False, - "grant": g, - "message": "A critical error has occurred. Therefore, the request is not authorized.", - "has_failed": True, - "critical_errors":g_eval['errors'] - } - + g_eval = evaluate_one(request, g, execute) if g_eval['is_applicable'] is True: return { "is_authorized": False, "grant": g, "message": "A deny grant is applicable to the request. Therefore, the request is not authorized.", - "has_failed": False, - "critical_errors": {} + "error": None } - + for g in allow_grants: - g_eval = evaluate_one(request, g, execute, True) - if g_eval['has_failed'] is True: - return { - "is_authorized": False, - "grant": g, - "message": "A critical error has occurred. Therefore, the request is not authorized.", - "has_failed": True, - "critical_errors": g_eval['errors'] - } - + g_eval = evaluate_one(request, g, execute) if g_eval['is_applicable'] is True: return { "is_authorized": True, "grant": g, "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": {} + "error": None } - + return { "is_authorized": False, "grant": None, "message": "No grants are applicable to the request. Therefore, the request is implicitly denied and is not authorized.", - "has_failed": False, - "critical_errors": {} + "error": None } @@ -1305,21 +1162,21 @@ def _validate( is_batch: bool ) -> Dict[str, AnyJSON]: c_val = validate_context_defs(context_defs) - if c_val['is_valid'] is False: + if c_val['error'] is not None: return c_val - + i_val = validate_identity_defs(identity_defs) - if i_val['is_valid'] is False: + if i_val['error'] is not None: return i_val - + r_val = validate_resource_defs(resource_defs) - if r_val['is_valid'] is False: + if r_val['error'] is not None: return r_val g_val = validate_grants(grants) - if g_val['is_valid'] is False: + if g_val['error'] is not None: return g_val - + if is_batch is True: req_val = validate_batch_request( request, @@ -1327,19 +1184,25 @@ def _validate( identity_defs, resource_defs ) - else: - req_val = validate_request( - request, - context_defs, - identity_defs, - resource_defs - ) + if req_val['error'] is not None: + return req_val - if req_val['is_valid'] is False: + return { + "error": None, + "batch_errors": req_val['batch_errors'] + } + + req_val = validate_request( + request, + context_defs, + identity_defs, + resource_defs + ) + if req_val['error'] is not None: return req_val - + return { - "is_valid": True + "error": None } @@ -1359,8 +1222,11 @@ def audit_workflow( request, False ) - if val['is_valid'] is False: - return val + if val['error'] is not None: + return { + "results": [], + "error": val['error'] + } return audit(request, grants, execute) @@ -1381,70 +1247,82 @@ def authorize_workflow( request, False ) - if val['is_valid'] is False: - return val + if val['error'] is not None: + return { + "is_authorized": False, + "grant": None, + "message": "An error has occurred. Therefore, the request is not authorized.", + "error": val['error'] + } return authorize(request, grants, execute) def batch_audit( - batch_request: Dict[str, AnyJSON], + batch_request: Dict[str, AnyJSON], grants: List[Dict[str, AnyJSON]], execute: Callable[[str, AnyJSON], AnyJSON] -) -> Dict[str, List[Dict[str, AnyJSON]]]: +) -> Dict[str, List[Dict[str, AnyJSON]]]: batch_results = [] for item in batch_request['batch']: - audit_result = audit( + request = { + "identities": item.get("identities") or batch_request['identities'], + "action": batch_request['action'], + "resource_type": item.get("resource_type") or batch_request['resource_type'], + "resource": item.get("resource") or batch_request['resource'], + "context_type": item.get("context_type") or batch_request['context_type'], + "context": item.get("context") if item.get("context") is not None else batch_request['context'] + } + results = [] + for g in grants: + g_eval = evaluate_one(request, g, execute) + results.append( + { + "is_applicable": g_eval['is_applicable'], + "query_result": g_eval['query_result'], + "failure": g_eval['failure'] + } + ) + + batch_results.append( { - "identities": item.get("identities", batch_request['identities']), - "action": batch_request['action'], - "resource_type": item.get("resource_type", batch_request['resource_type']), - "resource": item.get("resource", batch_request['resource']), - "context_type": item.get("context_type", batch_request['context_type']), - "context": item.get("context", batch_request['context']), - "evaluation_handler": item.get("evaluation_handler", batch_request['evaluation_handler']) - }, - grants, - execute + "results": results, + "error": None + } ) - audit_result.pop("grants") - batch_results.append(audit_result) - + return { "grants": grants, - "batch_results": batch_results, - "has_failed": False, - "errors": {} + "batch": batch_results, + "error": None } def batch_authorize( - batch_request: Dict[str, AnyJSON], + batch_request: Dict[str, AnyJSON], grants: List[Dict[str, AnyJSON]], execute: Callable[[str, AnyJSON], AnyJSON] -) -> Dict[str, List[Dict[str, AnyJSON]]]: +) -> Dict[str, List[Dict[str, AnyJSON]]]: results = [] for item in batch_request['batch']: results.append( authorize( { - "identities": item.get("identities", batch_request['identities']), + "identities": item.get("identities") or batch_request['identities'], "action": batch_request['action'], - "resource_type": item.get("resource_type", batch_request['resource_type']), - "resource": item.get("resource", batch_request['resource']), - "context_type": item.get("context_type", batch_request['context_type']), - "context": item.get("context", batch_request['context']), - "evaluation_handler": item.get("evaluation_handler", batch_request['evaluation_handler']) + "resource_type": item.get("resource_type") or batch_request['resource_type'], + "resource": item.get("resource") or batch_request['resource'], + "context_type": item.get("context_type") or batch_request['context_type'], + "context": item.get("context") if item.get("context") is not None else batch_request['context'] }, grants, execute ) ) - + return { - "results": results, - "has_failed": False, - "errors": {} + "batch": results, + "error": None } @@ -1464,10 +1342,40 @@ def batch_audit_workflow( batch_request, True ) - if val['is_valid'] is False: - return val + if val['error'] is not None: + return { + "grants": [], + "batch": [], + "error": val['error'] + } - return batch_audit(batch_request, grants, execute) + batch_results = [] + batch = [] + batch_results_indexes = [] + for error, request, i in zip( + val['batch_errors'], + batch_request['batch'], + range(len(val['batch_errors'])) + ): + if error is None: + batch_results.append(None) + batch.append(request) + batch_results_indexes.append(i) + else: + batch_results.append( + { + "results": [], + "error": error + } + ) + + result = batch_audit(batch_request, grants, execute) + for request, i in zip(result['batch'], batch_results_indexes): + batch_results[i] = request + + result['batch'] = batch_results + + return result def batch_authorize_workflow( @@ -1486,7 +1394,38 @@ def batch_authorize_workflow( batch_request, True ) - if val['is_valid'] is False: - return val + if val['error'] is not None: + return { + "batch": [], + "error": val['error'] + } + + batch_results = [] + batch = [] + batch_results_indexes = [] + for error, request, i in zip( + val['batch_errors'], + batch_request['batch'], + range(len(val['batch_errors'])) + ): + if error is None: + batch_results.append(None) + batch.append(request) + batch_results_indexes.append(i) + else: + batch_results.append( + { + "is_authorized": False, + "grant": None, + "message": "An error has occurred. Therefore, the request is not authorized.", + "error": error + } + ) - return batch_authorize(batch_request, grants, execute) \ No newline at end of file + result = batch_authorize(batch_request, grants, execute) + for request, i in zip(result['batch'], batch_results_indexes): + batch_results[i] = request + + result['batch'] = batch_results + + return result diff --git a/src/authzee/storage/__init__.py b/src/authzee/storage/__init__.py index ac0fc75..dc564cf 100644 --- a/src/authzee/storage/__init__.py +++ b/src/authzee/storage/__init__.py @@ -1,7 +1,9 @@ +"""Authzee storage modules.""" + __all__ = [ - "StorageModule", - "DictStorage" + "DictStorage", + "StorageModule" ] -from authzee.storage.storage_module import StorageModule from authzee.storage.dict_storage import DictStorage +from authzee.storage.storage_module import StorageModule diff --git a/src/authzee/storage/dict_storage.py b/src/authzee/storage/dict_storage.py index 15188f2..4b6daa7 100644 --- a/src/authzee/storage/dict_storage.py +++ b/src/authzee/storage/dict_storage.py @@ -1,89 +1,73 @@ """Dict-based in-memory storage module for Authzee. -See {py:class}`authzee.storage.dict_storage.DictStorage` +See [](authzee.storage.dict_storage.DictStorage) """ __all__ = [ - "DictStorage", + "DictStorage" ] import datetime from typing import List from uuid import uuid4 +from authzee.module_locality import ModuleLocality from authzee.storage.storage_module import StorageModule from authzee.types.authzee import * from authzee.types.config import ( - StorageStartConfig, - StorageShutdownConfig, - StorageConstructConfig, - StorageDestroyConfig, - ListContextDefsConfig, - GetContextDefConfig, - PutContextDefConfig, + CleanupLatchesConfig, + CreateLatchConfig, DeleteContextDefConfig, - ListIdentityDefsConfig, - GetIdentityDefConfig, - PutIdentityDefConfig, DeleteIdentityDefConfig, - ListResourceDefsConfig, - GetResourceDefConfig, - PutResourceDefConfig, + DeleteLatchConfig, DeleteResourceDefConfig, EnactConfig, - RepealConfig, + GetContextDefConfig, GetGrantConfig, - ListGrantsConfig, - ListGrantRefsConfig, - CreateLatchConfig, + GetIdentityDefConfig, GetLatchConfig, + GetResourceDefConfig, + ListContextDefsConfig, + ListGrantRefsConfig, + ListGrantsConfig, + ListIdentityDefsConfig, + ListResourceDefsConfig, + PutContextDefConfig, + PutIdentityDefConfig, + PutResourceDefConfig, + RepealConfig, SetLatchConfig, - DeleteLatchConfig, - CleanupLatchesConfig + StorageConstructConfig, + StorageDestroyConfig, + StorageShutdownConfig, + StorageStartConfig ) -from authzee.module_locality import ModuleLocality class DictStorage(StorageModule): - def __init__(self, storage_dict: dict): + + def __init__(self, storage_dict: dict): super().__init__() self._storage_dict = storage_dict async def start(self, config: StorageStartConfig) -> GenericResult: - """Start up storage module. - - - run before use - - After this method is complete these public instance vars or getters must be available: - - locality - Storage [Module Locality](#module-locality) - - has_parallel_paging - if the storage module supports parallel paging (returning a page of grant page references). - """ self.locality = ModuleLocality.PROCESS self.has_parallel_paging = True - + return { - "has_failed": False, - "errors": {} + "error": None } async def shutdown(self, config: StorageShutdownConfig) -> GenericResult: - """Shutdown storage module. - - - clean up runtime resources - """ return { - "has_failed": False, - "errors": {} + "error": None } async def construct(self, config: StorageConstructConfig) -> GenericResult: - """Construct backend resources for storage. - - - one time setup - """ self._storage_dict['context_defs_lut'] = {} self._storage_dict['identity_defs_lut'] = {} self._storage_dict['resource_defs_lut'] = {} @@ -91,16 +75,11 @@ async def construct(self, config: StorageConstructConfig) -> GenericResult: self._storage_dict['latches_lut'] = {} return { - "has_failed": False, - "errors": {} + "error": None } async def destroy(self, config: StorageDestroyConfig) -> GenericResult: - """Tear down backend resources. - - - destructive - may lose all long lasting storage resources - """ self._storage_dict.pop("context_defs_lut", None) self._storage_dict.pop("identity_defs_lut", None) self._storage_dict.pop("resource_defs_lut", None) @@ -108,8 +87,7 @@ async def destroy(self, config: StorageDestroyConfig) -> GenericResult: self._storage_dict.pop("latches_lut", None) return { - "has_failed": False, - "errors": {} + "error": None } @@ -118,84 +96,69 @@ async def list_context_defs( page_ref: str | None, config: ListContextDefsConfig ) -> ContextDefsPage: - """Get a page of context definitions. - - Pass the returned page reference to get the next page until a null page reference is returned. - """ if page_ref is None: start_index = 0 else: start_index = int(page_ref) context_defs = list(self._storage_dict['context_defs_lut'].values()) - end_index = start_index + config['page_size'] - + end_index = start_index + config['page_size'] + return { "context_defs": context_defs[start_index:end_index], "next_page_ref": str(end_index) if end_index < len(context_defs) else None, - "has_failed": False, - "errors": {} + "error": None } async def get_context_def( - self, + self, context_type: str, config: GetContextDefConfig ) -> ContextDefResult: - """Get a context definition by type. - """ - context_def = self._storage_dict['context_defs_lut'].get(context_type, None) + context_def = self._storage_dict['context_defs_lut'].get( + context_type, + None + ) if context_def is None: return { "context_def": None, - "has_failed": True, - "errors": { - "resource_not_found": [ - { - "is_critical": True, - "message": f"Context type '{context_type}' was not found." - } - ] + "error": { + "error_type": "resource_not_found", + "message": f"Context type '{context_type}' was not found." } } - + return { "context_def": context_def, - "has_failed": False, - "errors": {} + "error": None } async def put_context_def( - self, + self, context_def: ContextDef, config: PutContextDefConfig ) -> GenericResult: - """Add a new Context Definition or update an existing one. - - Validated by authzee class - """ self._storage_dict['context_defs_lut'][context_def['context_type']] = context_def return { - "has_failed": False, - "errors": {} + "error": None } - + async def delete_context_def( - self, + self, context_type: str, config: DeleteContextDefConfig ) -> GenericResult: - """Delete a context definition by type. - """ - self._storage_dict['context_defs_lut'].pop(context_type, None) - + self._storage_dict['context_defs_lut'].pop( + context_type, + None + ) + return { - "has_failed": False, - "errors": {} + "error": None } @@ -204,82 +167,69 @@ async def list_identity_defs( page_ref: str | None, config: ListIdentityDefsConfig ) -> IdentityDefsPage: - """Get a page of identity definitions. - - Pass the returned page reference to get the next page until a null page reference is returned. - """ if page_ref is None: start_index = 0 else: start_index = int(page_ref) identity_defs = list(self._storage_dict['identity_defs_lut'].values()) - end_index = start_index + config['page_size'] - + end_index = start_index + config['page_size'] + return { "identity_defs": identity_defs[start_index:end_index], "next_page_ref": str(end_index) if end_index < len(identity_defs) else None, - "has_failed": False, - "errors": {} + "error": None } async def get_identity_def( - self, + self, identity_type: str, config: GetIdentityDefConfig ) -> IdentityDefResult: - """Get an identity definition by type. - """ - identity_def = self._storage_dict['identity_defs_lut'].get(identity_type, None) + identity_def = self._storage_dict['identity_defs_lut'].get( + identity_type, + None + ) if identity_def is None: return { "identity_def": None, - "has_failed": True, - "errors": { - "resource_not_found": [ - { - "is_critical": True, - "message": f"identity type '{identity_type}' was not found." - } - ] + "error": { + "error_type": "resource_not_found", + "message": f"identity type '{identity_type}' was not found." } } - + return { "identity_def": identity_def, - "has_failed": False, - "errors": {} + "error": None } async def put_identity_def( - self, + self, identity_def: IdentityDef, config: PutIdentityDefConfig ) -> GenericResult: - """Add a new Identity Definition or update an existing one. - """ self._storage_dict['identity_defs_lut'][identity_def['identity_type']] = identity_def return { - "has_failed": False, - "errors": {} + "error": None } async def delete_identity_def( - self, + self, identity_type: str, config: DeleteIdentityDefConfig ) -> GenericResult: - """Delete an identity definition by type. - """ - self._storage_dict['identity_defs_lut'].pop(identity_type, None) - + self._storage_dict['identity_defs_lut'].pop( + identity_type, + None + ) + return { - "has_failed": False, - "errors": {} + "error": None } @@ -288,142 +238,111 @@ async def list_resource_defs( page_ref: str | None, config: ListResourceDefsConfig ) -> ResourceDefsPage: - """Get a page of resource definitions. - - Pass the returned page reference to get the next page until a null page reference is returned. - """ if page_ref is None: start_index = 0 else: start_index = int(page_ref) resource_defs = list(self._storage_dict['resource_defs_lut'].values()) - end_index = start_index + config['page_size'] - + end_index = start_index + config['page_size'] + return { "resource_defs": resource_defs[start_index:end_index], "next_page_ref": str(end_index) if end_index < len(resource_defs) else None, - "has_failed": False, - "errors": {} + "error": None } async def get_resource_def( - self, + self, resource_type: str, config: GetResourceDefConfig ) -> ResourceDefResult: - """Get a resource definition by type. - """ - resource_def = self._storage_dict['resource_defs_lut'].get(resource_type, None) + resource_def = self._storage_dict['resource_defs_lut'].get( + resource_type, + None + ) if resource_def is None: return { "resource_def": None, - "has_failed": True, - "errors": { - "resource_not_found": [ - { - "is_critical": True, - "message": f"resource type '{resource_type}' was not found." - } - ] + "error": { + "error_type": "resource_not_found", + "message": f"resource type '{resource_type}' was not found." } } - + return { "resource_def": resource_def, - "has_failed": False, - "errors": {} + "error": None } async def put_resource_def( - self, + self, resource_def: ResourceDef, config: PutResourceDefConfig ) -> GenericResult: - """Add a new Resource Definition or update an existing one. - """ self._storage_dict['resource_defs_lut'][resource_def['resource_type']] = resource_def return { - "has_failed": False, - "errors": {} + "error": None } async def delete_resource_def( - self, + self, resource_type: str, config: DeleteResourceDefConfig ) -> GenericResult: - """Delete a resource definition by type. - """ - self._storage_dict['resource_defs_lut'].pop(resource_type, None) - + self._storage_dict['resource_defs_lut'].pop( + resource_type, + None + ) + return { - "has_failed": False, - "errors": {} + "error": None } - async def enact( - self, - grant: Grant, - config: EnactConfig - ) -> GenericResult: - """Add a new grant. - """ + async def enact(self, grant: Grant, config: EnactConfig) -> GenericResult: self._storage_dict['grants_lut'][grant['grant_uuid']] = grant return { - "has_failed": False, - "errors": {} + "error": None } async def repeal( - self, - grant_uuid: str, + self, + grant_uuid: str, purge: bool, config: RepealConfig ) -> GenericResult: - """Delete a grant. - """ self._storage_dict['grants_lut'].pop(grant_uuid, None) return { - "has_failed": False, - "errors": {} + "error": None } async def get_grant( - self, + self, grant_uuid: str, config: GetGrantConfig ) -> GrantResult: - """Get a grant by UUID. - """ grant = self._storage_dict['grants_lut'].get(grant_uuid, None) if grant is None: return { "grant": None, - "has_failed": True, - "errors": { - "resource_not_found": [ - { - "is_critical": True, - "message": f"Grant with UUID '{grant_uuid}' was not found." - } - ] + "error": { + "error_type": "resource_not_found", + "message": f"Grant with UUID '{grant_uuid}' was not found." } } - + return { "grant": grant, - "has_failed": False, - "errors": {} + "error": None } @@ -434,10 +353,6 @@ async def list_grants( page_ref: str | None, config: ListGrantsConfig ) -> GrantsPage: - """Retrieve a page of grants. - - Pass the returned page reference to get the next page until a null page reference is returned. - """ if page_ref is None: start_index = 0 else: @@ -446,17 +361,16 @@ async def list_grants( grants: List[Grant] = list(self._storage_dict['grants_lut'].values()) if effect is not None: grants = [g for g in grants if g['effect'] == effect] - + if action is not None: grants = [g for g in grants if action in g['actions']] - end_index = start_index + config['page_size'] - + end_index = start_index + config['page_size'] + return { "grants": grants[start_index:end_index], "next_page_ref": str(end_index) if end_index < len(grants) else None, - "has_failed": False, - "errors": {} + "error": None } @@ -467,13 +381,6 @@ async def list_grant_refs( page_ref: str | None, config: ListGrantRefsConfig ) -> PageRefsPage: - """Retrieve a page of grant page references for parallel pagination. - - Pass the returned page reference to get the next page until a null page reference is returned. - - For some storage modules this may not be possible. - Check the `parallel_paging` attribute on the storage module after `start()` is complete. - """ if page_ref is None: start_index = 0 else: @@ -482,32 +389,29 @@ async def list_grant_refs( grants: List[Grant] = list(self._storage_dict['grants_lut'].values()) if effect is not None: grants = [g for g in grants if g['effect'] == effect] - + if action is not None: grants = [g for g in grants if action in g['actions']] - + num_grants = len(grants) refs = [] for _ in range(config['page_size']): - end_index = start_index + config['page_size'] - next_page_ref = end_index + end_index = start_index + config['page_size'] + next_page_ref = end_index refs.append(start_index) start_index = end_index if end_index >= num_grants: next_page_ref = None break - + return { "page_refs": refs, "next_page_ref": next_page_ref, - "has_failed": False, - "errors": {} + "error": None } async def create_latch(self, config: CreateLatchConfig) -> StorageLatchResult: - """Create a new [storage latch](#storage-latches). - """ latch_uuid = str(uuid4()) latch = { "storage_latch_uuid": latch_uuid, @@ -518,92 +422,78 @@ async def create_latch(self, config: CreateLatchConfig) -> StorageLatchResult: return { "storage_latch": latch, - "has_failed": False, - "errors": {} + "error": None } async def get_latch( - self, + self, storage_latch_uuid: str, config: GetLatchConfig ) -> StorageLatchResult: - """Get a [storage latch](#storage-latches) by UUID. - """ - latch = self._storage_dict['latches_lut'].get(storage_latch_uuid, None) + latch = self._storage_dict['latches_lut'].get( + storage_latch_uuid, + None + ) if latch is None: return { "storage_latch": None, - "has_failed": True, - "errors": { - "resource_not_found": [ - { - "is_critical": True, - "message": f"Storage latch with UUID '{storage_latch_uuid}' was not found." - } - ] + "error": { + "error_type": "resource_not_found", + "message": f"Storage latch with UUID '{storage_latch_uuid}' was not found." } } - + return { "storage_latch": latch, - "has_failed": False, - "errors": {} + "error": None } async def set_latch( - self, + self, storage_latch_uuid: str, config: SetLatchConfig ) -> StorageLatchResult: - """Set a [storage latch](#storage-latches) by UUID. - """ result = await self.get_latch( storage_latch_uuid=storage_latch_uuid, config=config ) - if result['has_failed'] is True: + if result['error'] is not None: return result - + result['storage_latch']['is_set'] = True - + return result async def delete_latch( - self, + self, storage_latch_uuid: str, config: DeleteLatchConfig ) -> GenericResult: - """Delete a [storage latch](#storage-latches) by UUID. - """ - self._storage_dict['latches_lut'].pop(storage_latch_uuid, None) + self._storage_dict['latches_lut'].pop( + storage_latch_uuid, + None + ) return { - "has_failed": False, - "errors": {} + "error": None } - async def cleanup_latches( - self, + self, before: datetime.datetime, config: CleanupLatchesConfig ) -> GenericResult: - """Delete all latches before the specified datetime. - - - operations should clean up their own latches, but in case of a failure this can be used to clean up zombie latches. - """ new_lut = {} for lu, l in self._storage_dict['latches_lut'].items(): if l['created_at'] > before: new_lut[lu] = l - + self._storage_dict['latches_lut'] = new_lut - + return { - "has_failed": False, - "errors": {} + "error": None } diff --git a/src/authzee/storage/storage_module.py b/src/authzee/storage/storage_module.py index 2fd4465..7d68164 100644 --- a/src/authzee/storage/storage_module.py +++ b/src/authzee/storage/storage_module.py @@ -1,51 +1,51 @@ - """Base storage module for Authzee. -See {py:class}`authzee.storage.storage_module.StorageModule` +See [](authzee.storage.storage_module.StorageModule) """ __all__ = [ - "StorageModule", + "StorageModule" ] import datetime +from authzee.exceptions import NotImplementedError +from authzee.module_locality import ModuleLocality from authzee.types.authzee import * from authzee.types.config import ( - StorageStartConfig, - StorageShutdownConfig, - StorageConstructConfig, - StorageDestroyConfig, - ListContextDefsConfig, - GetContextDefConfig, - PutContextDefConfig, + CleanupLatchesConfig, + CreateLatchConfig, DeleteContextDefConfig, - ListIdentityDefsConfig, - GetIdentityDefConfig, - PutIdentityDefConfig, DeleteIdentityDefConfig, - ListResourceDefsConfig, - GetResourceDefConfig, - PutResourceDefConfig, + DeleteLatchConfig, DeleteResourceDefConfig, EnactConfig, - RepealConfig, + GetContextDefConfig, GetGrantConfig, - ListGrantsConfig, - ListGrantRefsConfig, - CreateLatchConfig, + GetIdentityDefConfig, GetLatchConfig, + GetResourceDefConfig, + ListContextDefsConfig, + ListGrantRefsConfig, + ListGrantsConfig, + ListIdentityDefsConfig, + ListResourceDefsConfig, + PutContextDefConfig, + PutIdentityDefConfig, + PutResourceDefConfig, + RepealConfig, SetLatchConfig, - DeleteLatchConfig, - CleanupLatchesConfig + StorageConstructConfig, + StorageDestroyConfig, + StorageShutdownConfig, + StorageStartConfig ) -from authzee.exceptions import NotImplementedError -from authzee.module_locality import ModuleLocality class StorageModule: - def __init__(self): + + def __init__(self): pass @@ -59,8 +59,10 @@ async def start(self, config: StorageStartConfig) -> GenericResult: """ self.locality = ModuleLocality.PROCESS self.has_parallel_paging = False - - return GenericResult(has_failed=False) + + return { + "error": None + } async def shutdown(self, config: StorageShutdownConfig) -> GenericResult: @@ -100,7 +102,7 @@ async def list_context_defs( async def get_context_def( - self, + self, context_type: str, config: GetContextDefConfig ) -> ContextDefResult: @@ -110,7 +112,7 @@ async def get_context_def( async def put_context_def( - self, + self, context_def: ContextDef, config: PutContextDefConfig ) -> GenericResult: @@ -120,7 +122,7 @@ async def put_context_def( async def delete_context_def( - self, + self, context_type: str, config: DeleteContextDefConfig ) -> GenericResult: @@ -142,7 +144,7 @@ async def list_identity_defs( async def get_identity_def( - self, + self, identity_type: str, config: GetIdentityDefConfig ) -> IdentityDefResult: @@ -152,7 +154,7 @@ async def get_identity_def( async def put_identity_def( - self, + self, identity_def: IdentityDef, config: PutIdentityDefConfig ) -> GenericResult: @@ -162,7 +164,7 @@ async def put_identity_def( async def delete_identity_def( - self, + self, identity_type: str, config: DeleteIdentityDefConfig ) -> GenericResult: @@ -184,7 +186,7 @@ async def list_resource_defs( async def get_resource_def( - self, + self, resource_type: str, config: GetResourceDefConfig ) -> ResourceDefResult: @@ -194,7 +196,7 @@ async def get_resource_def( async def put_resource_def( - self, + self, resource_def: ResourceDef, config: PutResourceDefConfig ) -> GenericResult: @@ -204,7 +206,7 @@ async def put_resource_def( async def delete_resource_def( - self, + self, resource_type: str, config: DeleteResourceDefConfig ) -> GenericResult: @@ -213,19 +215,15 @@ async def delete_resource_def( raise NotImplementedError() - async def enact( - self, - grant: Grant, - config: EnactConfig - ) -> GenericResult: + async def enact(self, grant: Grant, config: EnactConfig) -> GenericResult: """Add a new grant. """ raise NotImplementedError() async def repeal( - self, - grant_uuid: str, + self, + grant_uuid: str, purge: bool, config: RepealConfig ) -> GenericResult: @@ -235,7 +233,7 @@ async def repeal( async def get_grant( - self, + self, grant_uuid: str, config: GetGrantConfig ) -> GrantResult: @@ -282,7 +280,7 @@ async def create_latch(self, config: CreateLatchConfig) -> StorageLatchResult: async def get_latch( - self, + self, storage_latch_uuid: str, config: GetLatchConfig ) -> StorageLatchResult: @@ -292,7 +290,7 @@ async def get_latch( async def set_latch( - self, + self, storage_latch_uuid: str, config: SetLatchConfig ) -> StorageLatchResult: @@ -302,7 +300,7 @@ async def set_latch( async def delete_latch( - self, + self, storage_latch_uuid: str, config: DeleteLatchConfig ) -> GenericResult: @@ -312,7 +310,7 @@ async def delete_latch( async def cleanup_latches( - self, + self, before: datetime.datetime, config: CleanupLatchesConfig ) -> GenericResult: diff --git a/src/authzee/types/__init__.py b/src/authzee/types/__init__.py index 0060c38..b70fe08 100644 --- a/src/authzee/types/__init__.py +++ b/src/authzee/types/__init__.py @@ -2,14 +2,12 @@ __all__ = [] - -from authzee.types.authzee import * +from authzee.types.authzee import * from authzee.types.authzee import __all__ as authzee_all -__all__ += authzee_all from authzee.types.config import * from authzee.types.config import __all__ as config_all -__all__ += config_all from authzee.types.config_override import * from authzee.types.config_override import __all__ as config_override_all -__all__ += config_override_all + +__all__ += authzee_all + config_all + config_override_all diff --git a/src/authzee/types/authzee.py b/src/authzee/types/authzee.py index e918eb9..9f34b6a 100644 --- a/src/authzee/types/authzee.py +++ b/src/authzee/types/authzee.py @@ -1,101 +1,70 @@ """Authzee core types.""" -from typing import Any, Dict, List, Literal, TypedDict - - __all__ = [ "AnyJSON", - "GenericError", - "ResultErrors", - "GenericResult", + "AuditResultItem", + "AuditResultPage", + "AuthorizeResult", + "AuthzeeBatchRequest", + "AuthzeeError", + "AuthzeeRequest", + "BatchAuditResultItem", + "BatchAuditResultPage", + "BatchAuthorizeResult", + "BatchItem", "ContextDef", "ContextDefResult", "ContextDefsPage", + "EvaluateResult", + "ExecuteResult", + "GenericResult", + "Grant", + "GrantResult", + "GrantsPage", "IdentityDef", "IdentityDefResult", "IdentityDefsPage", + "PageRefsPage", "ResourceDef", "ResourceDefResult", "ResourceDefsPage", - "Grant", - "GrantResult", - "GrantsPage", - "PageRefsPage", "StorageLatch", - "StorageLatchResult", - "AuthzeeRequest", - "BatchItem", - "AuthzeeBatchRequest", - "ExecuteResult", - "EvaluateResult", - "AuditResultItem", - "AuditResultPage", - "AuthorizeResult", - "BatchAuditResultItem", - "BatchAuditResultPage", - "BatchAuthorizeResult", + "StorageLatchResult" ] +from typing import Any, Dict, List, Literal, TypedDict -AnyJSON = bool | str | int | float | None | list | dict +AnyJSON = ( + bool + | str + | int + | float + | None + | list + | dict +) -class GenericError(TypedDict): + +class AuthzeeError(TypedDict): """```python Dict[str, Any] ``` - Generic Error Type + Error from an Authzee operation. Examples -------- ```python { - "is_critical": False, - "message": "Error message here" + "error_type": "evaluation", + "message": "A JSON Query error has occurred." } ``` """ - is_critical: bool + error_type: str message: str -ResultErrors = Dict[ - Literal[ - "definition", - "grant", - "request", - "evaluation", - "locality_incompatibility", - "not_implemented", - "parallel_pagination_not_supported", - "page_reference", - "resource_not_found", - "start" - ], - List[GenericError] -] -"""Result errors for all responses - - Examples - -------- - ```python - { - "": [ - { - "is_critical": False, - "message": "Error message." - } - ], - "": [ - { - "is_critical": False, - "message": "Error message." - } - ] - } - ``` - """ - class GenericResult(TypedDict): """```python Dict[str, Any] @@ -105,20 +74,14 @@ class GenericResult(TypedDict): -------- ```python { - "has_failed": False, - "errors": { - "": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": { # or None + "error_type": "", + "message": "" } } ``` """ - has_failed: bool - errors: ResultErrors + error: AuthzeeError | None class ContextDef(TypedDict): @@ -132,7 +95,7 @@ class ContextDef(TypedDict): { "context_type": "MyContext", "schema": { - "type": "object" , + "type": "object", "properties": { "my_prop": { "type": "string" @@ -151,16 +114,14 @@ class ContextDefResult(TypedDict): Dict[str, Any] ``` - Result of - Examples -------- ```python { - "context_def": { # dict | None + "context_def": { # Or None "context_type": "MyContext", "schema": { - "type": "object" , + "type": "object", "properties": { "my_prop": { "type": "string" @@ -168,21 +129,15 @@ class ContextDefResult(TypedDict): } } }, - "has_failed": False, - "errors": { - "": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": { # or None + "error_type": "", + "message": "" } } ``` """ context_def: ContextDef | None - has_failed: bool - errors: ResultErrors + error: AuthzeeError | None class ContextDefsPage(TypedDict): @@ -198,7 +153,7 @@ class ContextDefsPage(TypedDict): { "context_type": "MyContext", "schema": { - "type": "object" , + "type": "object", "properties": { "my_prop": { "type": "string" @@ -207,23 +162,17 @@ class ContextDefsPage(TypedDict): } } ], - "next_page_ref": "abc12": # str | None - "has_failed": False, - "errors": { - "": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "next_page_ref": "abc123", + "error": { # or None + "error_type": "", + "message": "" } } ``` """ context_defs: List[ContextDef] next_page_ref: str | None - has_failed: bool - errors: ResultErrors + error: AuthzeeError | None class IdentityDef(TypedDict): @@ -237,7 +186,7 @@ class IdentityDef(TypedDict): { "identity_type": "MyIdentity", "schema": { - "type": "object" , + "type": "object", "properties": { "my_prop": { "type": "string" @@ -260,10 +209,10 @@ class IdentityDefResult(TypedDict): -------- ```python { - "identity_def": { # dict | None + "identity_def": { "identity_type": "MyIdentity", "schema": { - "type": "object" , + "type": "object", "properties": { "my_prop": { "type": "string" @@ -271,21 +220,15 @@ class IdentityDefResult(TypedDict): } } }, - "has_failed": False, - "errors": { - "": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": { # or None + "error_type": "", + "message": "" } } ``` """ - identity_def: IdentityDef| None - has_failed: bool - errors: ResultErrors + identity_def: IdentityDef | None + error: AuthzeeError | None class IdentityDefsPage(TypedDict): @@ -301,7 +244,7 @@ class IdentityDefsPage(TypedDict): { "identity_type": "MyIdentity", "schema": { - "type": "object" , + "type": "object", "properties": { "my_prop": { "type": "string" @@ -310,23 +253,17 @@ class IdentityDefsPage(TypedDict): } } ], - "next_page_ref": "abc12": # str | None - "has_failed": False, - "errors": { - "": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "next_page_ref": "abc123", + "error": { # or None + "error_type": "", + "message": "" } } ``` """ identity_defs: List[IdentityDef] next_page_ref: str | None - has_failed: bool - errors: ResultErrors + error: AuthzeeError | None class ResourceDef(TypedDict): @@ -338,12 +275,12 @@ class ResourceDef(TypedDict): -------- ```python { - "resource_type": "MyResource", + "resource_type": "Balloon", "actions": [ - "MyResource.MyAction" + "balloon:inflate" ], "schema": { - "type": "object" , + "type": "object", "properties": { "my_prop": { "type": "string" @@ -367,13 +304,13 @@ class ResourceDefResult(TypedDict): -------- ```python { - "resource_def": { # dict | None - "resource_type": "MyResource", + "resource_def": { + "resource_type": "Balloon", "actions": [ - "MyResource.MyAction" + "balloon:inflate" ], "schema": { - "type": "object" , + "type": "object", "properties": { "my_prop": { "type": "string" @@ -381,21 +318,15 @@ class ResourceDefResult(TypedDict): } } }, - "has_failed": False, - "errors": { - "": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": { # or None + "error_type": "", + "message": "" } } ``` """ - resource_def: ResourceDef| None - has_failed: bool - errors: ResultErrors + resource_def: ResourceDef | None + error: AuthzeeError | None class ResourceDefsPage(TypedDict): @@ -409,12 +340,12 @@ class ResourceDefsPage(TypedDict): { "resource_defs": [ { - "resource_type": "MyResource", + "resource_type": "Balloon", "actions": [ - "MyResource.MyAction" + "balloon:inflate" ], "schema": { - "type": "object" , + "type": "object", "properties": { "my_prop": { "type": "string" @@ -423,23 +354,17 @@ class ResourceDefsPage(TypedDict): } } ], - "next_page_ref": "abc12": # str | None - "has_failed": False, - "errors": { - "": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "next_page_ref": "abc123", + "error": { # or None + "error_type": "", + "message": "" } } ``` """ resource_defs: List[ResourceDef] next_page_ref: str | None - has_failed: bool - errors: ResultErrors + error: AuthzeeError | None class Grant(TypedDict): @@ -455,18 +380,18 @@ class Grant(TypedDict): "name": "People friendly name", "description": "Long description", "tags": { - "my tag key": "my tag value" + "my_tag_key": "my tag value" }, - "effect": "allow", # allow | deny + "effect": "allow", "actions": [ - "MyResource.MyAction" + "balloon:inflate" ], "query": "contains(request.identities, 'User')", - "evaluation_handler": "evaluate", # evaluate | error | critical - equality: True # AnyJSON - data: { # top level dictionary with str keys, everything else is free form - "str here": "anything else here - } + "equality": True, + "applicable_on_failure": False, + "data": { + "str_here": "anything else here" + } } ``` """ @@ -477,12 +402,8 @@ class Grant(TypedDict): effect: Literal["allow", "deny"] actions: List[str] query: str - evaluation_handler: Literal[ - "evaluate", - "error", - "critical" - ] equality: AnyJSON + applicable_on_failure: bool data: Dict[str, Any] @@ -495,39 +416,30 @@ class GrantResult(TypedDict): -------- ```python { - "grant": { # dict | None + "grant": { "grant_uuid": "0da5dfc6-c919-4bd6-b80f-a351a9ac8d27", "name": "People friendly name", "description": "Long description", "tags": { - "my tag key": "my tag value" + "my_tag_key": "my tag value" }, - "effect": "allow", # allow | deny + "effect": "allow", "actions": [ - "MyResource.MyAction" + "balloon:inflate" ], "query": "contains(request.identities, 'User')", - "evaluation_handler": "evaluate", # evaluate | error | critical - equality: True # AnyJSON - data: { # top level dictionary with str keys, everything else is free form - "str here": "anything else here - } + "equality": True, + "data": {} }, - "has_failed": False, - "errors": { - "": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": { # or None + "error_type": "", + "message": "" } } ``` """ grant: Grant | None - has_failed: bool - errors: ResultErrors + error: AuthzeeError | None class GrantsPage(TypedDict): @@ -540,42 +452,33 @@ class GrantsPage(TypedDict): ```python { "grants": [ - { + { "grant_uuid": "0da5dfc6-c919-4bd6-b80f-a351a9ac8d27", "name": "People friendly name", "description": "Long description", "tags": { - "my tag key": "my tag value" + "my_tag_key": "my tag value" }, - "effect": "allow", # allow | deny + "effect": "allow", "actions": [ - "MyResource.MyAction" + "balloon:inflate" ], "query": "contains(request.identities, 'User')", - "evaluation_handler": "evaluate", # evaluate | error | critical - equality: True # AnyJSON - data: { # top level dictionary with str keys, everything else is free form - "str here": "anything else here - } + "equality": True, + "data": {} } ], - "next_page_ref": "abc123", # str | None - "has_failed": False, - "errors": { - "": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "next_page_ref": "abc123", + "error": { # or None + "error_type": "", + "message": "" } } ``` """ grants: List[Grant] next_page_ref: str | None - has_failed: bool - errors: ResultErrors + error: AuthzeeError | None class PageRefsPage(TypedDict): @@ -588,25 +491,19 @@ class PageRefsPage(TypedDict): ```python { "page_refs": [ - abc123" + "abc123" ], - "next_page_ref": "abc123", # str | None - "has_failed": False, - "errors": { - "": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "next_page_ref": "abc123", + "error": { # or None + "error_type": "", + "message": "" } } ``` """ page_refs: List[str] next_page_ref: str | None - has_failed: bool - errors: ResultErrors + error: AuthzeeError | None class StorageLatch(TypedDict): @@ -620,12 +517,12 @@ class StorageLatch(TypedDict): { "storage_latch_uuid": "0da5dfc6-c919-4bd6-b80f-a351a9ac8d27", "is_set": False, - "created": "2026-04-26T16:21:10.521220" + "created_at": "2026-04-26T16:21:10.521220" } ``` """ storage_latch_uuid: str - is_set: bool = False + is_set: bool created_at: str @@ -638,25 +535,20 @@ class StorageLatchResult(TypedDict): -------- ```python { - "storage_latch": { # dict | None + "storage_latch": { "storage_latch_uuid": "0da5dfc6-c919-4bd6-b80f-a351a9ac8d27", "is_set": False, - "created": "2026-04-26T16:21:10.521220" + "created_at": "2026-04-26T16:21:10.521220" }, - "has_failed": False, - "errors": { - "": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": { # or None + "error_type": "", + "message": "" } + } ``` """ storage_latch: StorageLatch | None - has_failed: bool - errors: ResultErrors + error: AuthzeeError | None class AuthzeeRequest(TypedDict): @@ -681,7 +573,6 @@ class AuthzeeRequest(TypedDict): "color": "blue", "size": 27.0 }, - "evaluation_handler": "evaluate", # grant | evaluate | error | critical "context_type": "MyContext", "context": { "allowed_sizes": [20.0, 27.0] @@ -689,59 +580,52 @@ class AuthzeeRequest(TypedDict): } ``` """ - identities: Dict[str, List[Dict[str, AnyJSON]]] + identities: Dict[ + str, + List[Dict[str, AnyJSON]] + ] action: str resource_type: str resource: Dict[str, AnyJSON] - evaluation_handler: Literal[ - "grant", - "evaluate", - "error", - "critical" - ] context_type: str context: Dict[str, AnyJSON] -class BatchItem(TypedDict): +class BatchItem(TypedDict, total=False): """```python Dict[str, Any] ``` Examples -------- - **All base fields are not required.** + **All fields are optional.** ```python { - "identities": { # dict | None + "identities": { "ADUser": [ {"cn": "authzee_user_1"} ] }, - "resource_type": "Balloon", # str | None - "resource": { # dict | None + "resource_type": "Balloon", + "resource": { "color": "blue", "size": 27.0 }, - "evaluation_handler": "evaluate", # evaluate | error | critical | None - "context_type": "MyContext", # str | None - "context": { # dict | None + "context_type": "MyContext", + "context": { "allowed_sizes": [20.0, 27.0] } } ``` """ - identities: Dict[str, List[Dict[str, AnyJSON]]] | None = None - resource_type: str | None = None - resource: Dict[str, AnyJSON] | None = None - evaluation_handler: Literal[ - "grant", - "evaluate", - "error", - "critical" - ] | None = None - context_type: str | None = None - context: Dict[str, AnyJSON] | None = None + identities: Dict[ + str, + List[Dict[str, AnyJSON]] + ] | None + resource_type: str | None + resource: Dict[str, AnyJSON] | None + context_type: str | None + context: Dict[str, AnyJSON] | None class AuthzeeBatchRequest(TypedDict): @@ -766,7 +650,6 @@ class AuthzeeBatchRequest(TypedDict): "color": "blue", "size": 27.0 }, - "evaluation_handler": "evaluate", # grant | evaluate | error | critical "context_type": "MyContext", "context": { "allowed_sizes": [20.0, 27.0] @@ -782,20 +665,18 @@ class AuthzeeBatchRequest(TypedDict): } ``` """ - identities: Dict[str, List[Dict[str, AnyJSON]]] + identities: Dict[ + str, + List[Dict[str, AnyJSON]] + ] action: str resource_type: str resource: Dict[str, AnyJSON] - evaluation_handler: Literal[ - "grant", - "evaluate", - "error", - "critical" - ] context_type: str context: Dict[str, AnyJSON] batch: List[BatchItem] + class ExecuteResult(TypedDict): """```python Dict[str, Any] @@ -806,14 +687,12 @@ class ExecuteResult(TypedDict): ```python { "result": True, - "has_failed": False, - "error_message": None # str | None + "failure": "A JMESPath Query error has occurred: ..." # or None } ``` """ result: Any - has_failed: bool - error_message: str | None + failure: str | None class EvaluateResult(TypedDict): @@ -827,22 +706,13 @@ class EvaluateResult(TypedDict): { "is_applicable": True, "query_result": True, - "has_failed": False, - "errors": { - "": [ - { - "is_critical": False, - "message": "Error message." - } - ] - } + "failure": "A JSON Query error has occurred: ..." # or None } ``` """ is_applicable: bool query_result: Any - has_failed: bool - errors: ResultErrors + failure: str | None class AuditResultItem(TypedDict): @@ -854,22 +724,27 @@ class AuditResultItem(TypedDict): -------- ```python { + "grant": { + "grant_uuid": "0da5dfc6-c919-4bd6-b80f-a351a9ac8d27", + "name": "People friendly name", + "description": "Long description", + "tags": {}, + "effect": "allow", + "actions": ["balloon:inflate"], + "query": "contains(request.identities, 'User')", + "equality": True, + "data": {} + }, "is_applicable": True, "query_result": True, - "errors": { - "": [ - { - "is_critical": False, - "message": "Error message." - } - ] - } + "failure": "A JSON Query error has occurred: ..." # Or None } ``` """ + grant: Grant is_applicable: bool query_result: AnyJSON - errors: ResultErrors + failure: str | None class AuditResultPage(TypedDict): @@ -881,52 +756,35 @@ class AuditResultPage(TypedDict): -------- ```python { - "grants": [ - { - "grant_uuid": "0da5dfc6-c919-4bd6-b80f-a351a9ac8d27", - "name": "People friendly name", - "description": "Long description", - "tags": {"my tag key": "my tag value"}, - "effect": "allow", - "actions": ["MyResource.MyAction"], - "query": "contains(request.identities, 'User')", - "evaluation_handler": "evaluate", - "equality": True, - "data": {} - } - ], "results": [ { + "grant": { + "grant_uuid": "0da5dfc6-c919-4bd6-b80f-a351a9ac8d27", + "name": "People friendly name", + "description": "Long description", + "tags": {}, + "effect": "allow", + "actions": ["balloon:inflate"], + "query": "contains(request.identities, 'User')", + "equality": True, + "data": {} + }, "is_applicable": True, "query_result": True, - "errors": { # result errors - "": [ - { - "is_critical": False, - "message": "Error message." - } - ] - } + "failure": "A JSON Query error has occurred: ..." # Or None } ], - "next_page_ref": "abc123", # str | None - "has_failed": False, - "errors": { # request errors and propagated result errors - "": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "next_page_ref": "abc123", + "error": { # or None + "error_type": "", + "message": "" } } ``` """ - grants: List[Grant] results: List[AuditResultItem] next_page_ref: str | None - has_failed: bool - errors: ResultErrors + error: AuthzeeError | None class AuthorizeResult(TypedDict): @@ -939,7 +797,7 @@ class AuthorizeResult(TypedDict): ```python { "is_authorized": True, - "grant": { # dict | None + "grant": { "grant_uuid": "0da5dfc6-c919-4bd6-b80f-a351a9ac8d27", "name": "People friendly name", "description": "Long description", @@ -947,21 +805,15 @@ class AuthorizeResult(TypedDict): "my tag key": "my tag value" }, "effect": "allow", - "actions": ["MyResource.MyAction"], + "actions": ["balloon:inflate"], "query": "contains(request.identities, 'User')", - "evaluation_handler": "evaluate", "equality": True, "data": {} }, - "message": "Authorized by grant.", - "has_failed": False, - "critical_errors": { - "": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "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.", + "error": { # or None + "error_type": "", + "message": "" } } ``` @@ -969,8 +821,7 @@ class AuthorizeResult(TypedDict): is_authorized: bool grant: Grant | None message: str - has_failed: bool - critical_errors: ResultErrors + error: AuthzeeError | None class BatchAuditResultItem(TypedDict): @@ -986,31 +837,18 @@ class BatchAuditResultItem(TypedDict): { "is_applicable": True, "query_result": True, - "errors": { # result errors - "": [ - { - "is_critical": False, - "message": "Error message." - } - ] - } + "failure": "A JSON Query error has occurred: ..." # Or None } ], - "has_failed": False, - "errors": { # request errors and propagated result errors - "": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": { # or None + "error_type": "", + "message": "" } } ``` """ - results: List[AuditResultItem] - has_failed: bool - errors: ResultErrors + results: List[EvaluateResult] + error: AuthzeeError | None class BatchAuditResultPage(TypedDict): @@ -1027,60 +865,41 @@ class BatchAuditResultPage(TypedDict): "grant_uuid": "0da5dfc6-c919-4bd6-b80f-a351a9ac8d27", "name": "People friendly name", "description": "Long description", - "tags": {"my tag key": "my tag value"}, + "tags": {}, "effect": "allow", - "actions": ["MyResource.MyAction"], + "actions": ["balloon:inflate"], "query": "contains(request.identities, 'User')", - "evaluation_handler": "evaluate", "equality": True, "data": {} } ], - "batch_results": [ + "batch": [ { "results": [ { "is_applicable": True, "query_result": True, - "errors": { # result errors - "": [ - { - "is_critical": False, - "message": "Error message." - } - ] - } + "failure": "A JSON Query error has occurred: ..." # Or None } ], - "has_failed": False, - "errors": { # request errors and propagated result errors - "": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": { # or None + "error_type": "", + "message": "" } } ], - "next_page_ref": "abc123", # str | None - "has_failed": False, - "errors": { # Batch request errors and propagated request errors - "": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "next_page_ref": "abc123", + "error": { # or None + "error_type": "", + "message": "" } } ``` """ grants: List[Grant] - batch_results: List[BatchAuditResultItem] + batch: List[BatchAuditResultItem] next_page_ref: str | None - has_failed: bool - errors: ResultErrors + error: AuthzeeError | None class BatchAuthorizeResult(TypedDict): @@ -1092,7 +911,7 @@ class BatchAuthorizeResult(TypedDict): -------- ```python { - "batch_results": [ + "batch": [ { "is_authorized": True, "grant": { @@ -1101,36 +920,24 @@ class BatchAuthorizeResult(TypedDict): "description": "Long description", "tags": {}, "effect": "allow", - "actions": ["MyResource.MyAction"], + "actions": ["balloon:inflate"], "query": "contains(request.identities, 'User')", - "evaluation_handler": "evaluate", "equality": True, "data": {} }, - "message": "Authorized by grant.", - "has_failed": False, - "critical_errors": { # request errors - "": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "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.", + "error": { # or None + "error_type": "", + "message": "" } } ], - "has_failed": False, - "critical_errors": { # batch errors and propagated request errors - "": [ - { - "is_critical": False, - "message": "Error message." - } - ] + "error": { # or None + "error_type": "", + "message": "" } } ``` """ - batch_results: List[AuthorizeResult] - has_failed: bool - critical_errors: ResultErrors + batch: List[AuthorizeResult] + error: AuthzeeError | None diff --git a/src/authzee/types/config.py b/src/authzee/types/config.py index 2c5068e..e02eef8 100644 --- a/src/authzee/types/config.py +++ b/src/authzee/types/config.py @@ -1,57 +1,56 @@ """Authzee config types.""" -from typing import TypedDict - - __all__ = [ + "AuditConfig", + "AuthorizeConfig", "AuthzeeBaseConfig", - "ComputeStartConfig", - "StorageStartConfig", - "StartConfig", - "ComputeShutdownConfig", - "StorageShutdownConfig", - "ShutdownConfig", + "AuthzeeConfig", + "BatchAuditConfig", + "BatchAuthorizeConfig", + "CleanupLatchesConfig", "ComputeConstructConfig", - "StorageConstructConfig", - "ConstructConfig", "ComputeDestroyConfig", - "StorageDestroyConfig", + "ComputeShutdownConfig", + "ComputeStartConfig", + "ConstructConfig", + "CreateLatchConfig", + "DeleteContextDefConfig", + "DeleteIdentityDefConfig", + "DeleteLatchConfig", + "DeleteResourceDefConfig", "DestroyConfig", + "EnactConfig", + "GetContextDefConfig", + "GetGrantConfig", + "GetIdentityDefConfig", + "GetLatchConfig", + "GetResourceDefConfig", "ListContextDefsConfig", + "ListGrantRefsConfig", + "ListGrantsConfig", "ListIdentityDefsConfig", "ListResourceDefsConfig", - "ListGrantsConfig", - "ValidateContextDefConfig", - "GetContextDefConfig", "PutContextDefConfig", - "DeleteContextDefConfig", - "ValidateIdentityDefConfig", - "GetIdentityDefConfig", "PutIdentityDefConfig", - "DeleteIdentityDefConfig", - "ValidateResourceDefConfig", - "GetResourceDefConfig", "PutResourceDefConfig", - "DeleteResourceDefConfig", - "ValidateGrantConfig", - "GetGrantConfig", - "EnactConfig", "RepealConfig", - "CreateLatchConfig", - "GetLatchConfig", "SetLatchConfig", - "DeleteLatchConfig", - "CleanupLatchesConfig", - "ListGrantRefsConfig", - "ValidateRequestConfig", + "ShutdownConfig", + "StartConfig", + "StorageConstructConfig", + "StorageDestroyConfig", + "StorageShutdownConfig", + "StorageStartConfig", "ValidateBatchRequestConfig", - "AuditConfig", - "BatchAuditConfig", - "AuthorizeConfig", - "BatchAuthorizeConfig", - "AuthzeeConfig", + "ValidateContextDefConfig", + "ValidateGrantConfig", + "ValidateIdentityDefConfig", + "ValidateRequestConfig", + "ValidateResourceDefConfig" ] +from typing import TypedDict + class AuthzeeBaseConfig(TypedDict): """```python @@ -63,16 +62,16 @@ class AuthzeeBaseConfig(TypedDict): -------- ```python { - "raise_crits": True + "raise_errors": True } ``` Attributes ---------- - raise_crits : bool + raise_errors : bool Whether to raise on critical errors. """ - raise_crits: bool + raise_errors: bool class StorageStartConfig(TypedDict): @@ -980,7 +979,6 @@ class AuditConfig(TypedDict): """ validate_request: ValidateRequestConfig list_grants: ListGrantsConfig - class BatchAuditConfig(TypedDict): @@ -1099,7 +1097,7 @@ class AuthorizeConfig(TypedDict): validate_request: ValidateRequestConfig list_grants: ListGrantsConfig parallel_paging: bool - list_grant_refs: ListGrantRefsConfig + list_grant_refs: ListGrantRefsConfig class BatchAuthorizeConfig(TypedDict): @@ -1193,16 +1191,16 @@ class BatchAuthorizeConfig(TypedDict): validate_request: ValidateRequestConfig list_grants: ListGrantsConfig parallel_paging: bool - list_grant_refs: ListGrantRefsConfig + list_grant_refs: ListGrantRefsConfig class AuthzeeConfig(TypedDict): """```python Dict[str, Dict[str, Any]] ``` - Authzee configuration Type. Held in each Authzee class instance to feed configuration for everything. + Authzee configuration Type. Held in each Authzee class instance to feed configuration for everything. - The configuration can be set at several different levels where only the provided values override the previous levels values. + The configuration can be set at several different levels where only the provided values override the previous levels values. The order of least to most precedence is: - Default config values - None Set @@ -1210,8 +1208,8 @@ class AuthzeeConfig(TypedDict): - Function/Method call config - The root fields all represent the config that will be passed to the method in Authzee by name. - The `authzee` root key is just for general Authzee instance level configuration. + The root fields all represent the config that will be passed to the method in Authzee by name. + The `authzee` root key is just for general Authzee instance level configuration. Examples -------- @@ -1220,7 +1218,7 @@ class AuthzeeConfig(TypedDict): ```python { "authzee": { - "raise_crits": True + "raise_errors": True }, "start": { "compute_start": { @@ -1608,4 +1606,3 @@ class AuthzeeConfig(TypedDict): batch_audit: BatchAuditConfig authorize: AuthorizeConfig batch_authorize: BatchAuthorizeConfig - diff --git a/src/authzee/types/config_override.py b/src/authzee/types/config_override.py index 0c05d0a..bd8da67 100644 --- a/src/authzee/types/config_override.py +++ b/src/authzee/types/config_override.py @@ -1,53 +1,52 @@ """Authzee config override types.""" -from typing import TypedDict - - __all__ = [ + "AuditConfigOverride", + "AuthorizeConfigOverride", "AuthzeeBaseConfigOverride", - "ComputeStartConfigOverride", - "StorageStartConfigOverride", - "StartConfigOverride", - "ComputeShutdownConfigOverride", - "StorageShutdownConfigOverride", - "ShutdownConfigOverride", + "AuthzeeConfigOverride", + "BatchAuditConfigOverride", + "BatchAuthorizeConfigOverride", + "CleanupLatchesConfigOverride", "ComputeConstructConfigOverride", - "StorageConstructConfigOverride", - "ConstructConfigOverride", "ComputeDestroyConfigOverride", - "StorageDestroyConfigOverride", + "ComputeShutdownConfigOverride", + "ComputeStartConfigOverride", + "ConstructConfigOverride", + "DeleteContextDefConfigOverride", + "DeleteIdentityDefConfigOverride", + "DeleteResourceDefConfigOverride", "DestroyConfigOverride", + "EnactConfigOverride", + "GetContextDefConfigOverride", + "GetGrantConfigOverride", + "GetIdentityDefConfigOverride", + "GetResourceDefConfigOverride", "ListContextDefsConfigOverride", + "ListGrantRefsConfigOverride", + "ListGrantsConfigOverride", "ListIdentityDefsConfigOverride", "ListResourceDefsConfigOverride", - "ListGrantsConfigOverride", - "ValidateContextDefConfigOverride", - "GetContextDefConfigOverride", "PutContextDefConfigOverride", - "DeleteContextDefConfigOverride", - "ValidateIdentityDefConfigOverride", - "GetIdentityDefConfigOverride", "PutIdentityDefConfigOverride", - "DeleteIdentityDefConfigOverride", - "ValidateResourceDefConfigOverride", - "GetResourceDefConfigOverride", "PutResourceDefConfigOverride", - "DeleteResourceDefConfigOverride", - "ValidateGrantConfigOverride", - "GetGrantConfigOverride", - "EnactConfigOverride", "RepealConfigOverride", - "CleanupLatchesConfigOverride", - "ListGrantRefsConfigOverride", - "ValidateRequestConfigOverride", + "ShutdownConfigOverride", + "StartConfigOverride", + "StorageConstructConfigOverride", + "StorageDestroyConfigOverride", + "StorageShutdownConfigOverride", + "StorageStartConfigOverride", "ValidateBatchRequestConfigOverride", - "AuditConfigOverride", - "BatchAuditConfigOverride", - "AuthorizeConfigOverride", - "BatchAuthorizeConfigOverride", - "AuthzeeConfigOverride", + "ValidateContextDefConfigOverride", + "ValidateGrantConfigOverride", + "ValidateIdentityDefConfigOverride", + "ValidateRequestConfigOverride", + "ValidateResourceDefConfigOverride" ] +from typing import TypedDict + class AuthzeeBaseConfigOverride(TypedDict, total=False): """```python @@ -59,16 +58,16 @@ class AuthzeeBaseConfigOverride(TypedDict, total=False): -------- ```python { - "raise_crits": True + "raise_errors": True } ``` Attributes ---------- - raise_crits : bool + raise_errors : bool Whether to raise on critical errors. """ - raise_crits: bool + raise_errors: bool class StorageStartConfigOverride(TypedDict, total=False): @@ -1137,13 +1136,13 @@ class AuthzeeConfigOverride(TypedDict, total=False): ``` Authzee configuration override Type. All keys and nested keys are optional. - The configuration can be set at several different levels where only the provided values override the previous levels values. + The configuration can be set at several different levels where only the provided values override the previous levels values. The order of least to most precedence is: - Default config values - None Set - Authzee class instances config - Function/Method call config - + Examples -------- **All base and nested fields are optional for this Dict.** @@ -1151,7 +1150,7 @@ class AuthzeeConfigOverride(TypedDict, total=False): ```python { "authzee": { - "raise_crits": True + "raise_errors": True }, "start": { "compute_start": { diff --git a/tests/__init__.py b/tests/__init__.py index e69de29..c2069e5 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1 @@ +"""TODO: Add module docstring.""" diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py index e69de29..c2069e5 100644 --- a/tests/unit/__init__.py +++ b/tests/unit/__init__.py @@ -0,0 +1 @@ +"""TODO: Add module docstring.""" diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index e69de29..c2069e5 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -0,0 +1 @@ +"""TODO: Add module docstring.""" diff --git a/tests/unit/test_authzee.py b/tests/unit/test_authzee.py index aaacc58..c6c243f 100644 --- a/tests/unit/test_authzee.py +++ b/tests/unit/test_authzee.py @@ -3,6 +3,7 @@ Tests use DictStorage and InProcessCompute as the storage/compute modules. Black box testing - only uses public API methods of Authzee. """ + import datetime from uuid import uuid4 @@ -12,10 +13,10 @@ Authzee, DictStorage, InProcessCompute, - jmespath_execute, - paginator, authzee_specification_version, exceptions, + jmespath_execute, + paginator ) @@ -26,23 +27,24 @@ def storage_dict(): @pytest.fixture def authz(storage_dict): - """Create a fully initialized Authzee instance with raise_crits=False for black-box testing.""" + """Create a fully initialized Authzee instance with raise_errors=False for black-box testing.""" a = Authzee( execute=jmespath_execute, compute_type=InProcessCompute, compute_kwargs={}, storage_type=DictStorage, storage_kwargs={ - "storage_dict": storage_dict, + "storage_dict": storage_dict }, config={ "authzee": { - "raise_crits": False, - }, - }, + "raise_errors": False + } + } ) a.construct() a.start() + return a @@ -52,8 +54,8 @@ def context_def(): "context_type": "NONE", "schema": { "type": "object", - "additionalProperties": False, - }, + "additionalProperties": False + } } @@ -65,18 +67,18 @@ def identity_def(): "type": "object", "required": [ "username", - "department", + "department" ], "additionalProperties": False, "properties": { "username": { - "type": "string", + "type": "string" }, "department": { - "type": "string", - }, - }, - }, + "type": "string" + } + } + } } @@ -87,24 +89,24 @@ def resource_def(): "actions": [ "balloon:read", "balloon:inflate", - "balloon:pop", + "balloon:pop" ], "schema": { "type": "object", "required": [ "color", - "is_inflated", + "is_inflated" ], "additionalProperties": False, "properties": { "color": { - "type": "string", + "type": "string" }, "is_inflated": { - "type": "boolean", - }, - }, - }, + "type": "boolean" + } + } + } } @@ -115,17 +117,17 @@ def grant(): "name": "Allow inflate for balloon department", "description": "Balloon dept can read and inflate balloons.", "tags": { - "team": "balloon", + "team": "balloon" }, "effect": "allow", "actions": [ "balloon:read", - "balloon:inflate", + "balloon:inflate" ], "query": "length(request.identities.user[?department == 'Balloon Dept']) > `0`", - "evaluation_handler": "evaluate", "equality": True, - "data": {}, + "applicable_on_failure": False, + "data": {} } @@ -138,12 +140,12 @@ def deny_grant(): "tags": {}, "effect": "deny", "actions": [ - "balloon:pop", + "balloon:pop" ], "query": "length(request.identities.user[?department == 'Intern']) > `0`", - "evaluation_handler": "evaluate", "equality": True, - "data": {}, + "applicable_on_failure": False, + "data": {} } @@ -154,19 +156,18 @@ def auth_request(): "user": [ { "username": "balloon_person", - "department": "Balloon Dept", - }, + "department": "Balloon Dept" + } ] }, "action": "balloon:inflate", "resource_type": "balloon", "resource": { "color": "blue", - "is_inflated": False, + "is_inflated": False }, - "evaluation_handler": "grant", "context_type": "NONE", - "context": {}, + "context": {} } @@ -177,43 +178,49 @@ def batch_request(): "user": [ { "username": "balloon_person", - "department": "Balloon Dept", - }, + "department": "Balloon Dept" + } ] }, "action": "balloon:inflate", "resource_type": "balloon", "resource": { "color": "blue", - "is_inflated": False, + "is_inflated": False }, - "evaluation_handler": "grant", "context_type": "NONE", "context": {}, "batch": [ { "resource": { "color": "red", - "is_inflated": True, - }, + "is_inflated": True + } }, { "resource": { "color": "green", - "is_inflated": False, - }, - }, - ], + "is_inflated": False + } + } + ] } @pytest.fixture -def seeded_authz(authz, context_def, identity_def, resource_def, grant): +def seeded_authz( + authz, + context_def, + identity_def, + resource_def, + grant +): """An Authzee instance with definitions and a grant already stored.""" authz.put_context_def(context_def) authz.put_identity_def(identity_def) authz.put_resource_def(resource_def) authz.enact(grant) + return authz @@ -229,11 +236,11 @@ def test_construct(storage_dict): compute_kwargs={}, storage_type=DictStorage, storage_kwargs={ - "storage_dict": storage_dict, - }, + "storage_dict": storage_dict + } ) result = authz.construct() - assert result["has_failed"] is False + assert result['error'] is None def test_start(storage_dict): @@ -243,17 +250,17 @@ def test_start(storage_dict): compute_kwargs={}, storage_type=DictStorage, storage_kwargs={ - "storage_dict": storage_dict, - }, + "storage_dict": storage_dict + } ) authz.construct() result = authz.start() - assert result["has_failed"] is False + assert result['error'] is None def test_shutdown(authz): result = authz.shutdown() - assert result["has_failed"] is False + assert result['error'] is None def test_destroy(storage_dict): @@ -263,13 +270,13 @@ def test_destroy(storage_dict): compute_kwargs={}, storage_type=DictStorage, storage_kwargs={ - "storage_dict": storage_dict, - }, + "storage_dict": storage_dict + } ) authz.construct() authz.start() result = authz.destroy() - assert result["has_failed"] is False + assert result['error'] is None def test_construct_with_config(storage_dict): @@ -279,15 +286,17 @@ def test_construct_with_config(storage_dict): compute_kwargs={}, storage_type=DictStorage, storage_kwargs={ - "storage_dict": storage_dict, - }, + "storage_dict": storage_dict + } ) - result = authz.construct(config={ + result = authz.construct( + config={ "authzee": { - "raise_crits": True, - }, - }) - assert result["has_failed"] is False + "raise_errors": True + } + } + ) + assert result['error'] is None def test_start_with_config(storage_dict): @@ -297,25 +306,29 @@ def test_start_with_config(storage_dict): compute_kwargs={}, storage_type=DictStorage, storage_kwargs={ - "storage_dict": storage_dict, - }, + "storage_dict": storage_dict + } ) authz.construct() - result = authz.start(config={ + result = authz.start( + config={ "authzee": { - "raise_crits": True, - }, - }) - assert result["has_failed"] is False + "raise_errors": True + } + } + ) + assert result['error'] is None def test_shutdown_with_config(authz): - result = authz.shutdown(config={ + result = authz.shutdown( + config={ "authzee": { - "raise_crits": True, - }, - }) - assert result["has_failed"] is False + "raise_errors": True + } + } + ) + assert result['error'] is None def test_destroy_with_config(storage_dict): @@ -325,30 +338,34 @@ def test_destroy_with_config(storage_dict): compute_kwargs={}, storage_type=DictStorage, storage_kwargs={ - "storage_dict": storage_dict, - }, + "storage_dict": storage_dict + } ) authz.construct() authz.start() - result = authz.destroy(config={ + result = authz.destroy( + config={ "authzee": { - "raise_crits": True, - }, - }) - assert result["has_failed"] is False + "raise_errors": True + } + } + ) + assert result['error'] is None def test_validate_context_def_valid(authz, context_def): result = authz.validate_context_def(context_def) - assert result["has_failed"] is False + assert result['error'] is None def test_validate_context_def_invalid(authz): - result = authz.validate_context_def({ + result = authz.validate_context_def( + { "context_type": "BAD", - "schema": "not_a_dict", - }) - assert result["has_failed"] is True + "schema": "not_a_dict" + } + ) + assert result['error'] is not None def test_validate_context_def_non_object_schema(authz): @@ -356,111 +373,114 @@ def test_validate_context_def_non_object_schema(authz): { "context_type": "BAD", "schema": { - "type": "array", - }, + "type": "array" + } } ) - assert result["has_failed"] is True + assert result['error'] is not None def test_put_context_def(authz, context_def): result = authz.put_context_def(context_def) - assert result["has_failed"] is False + assert result['error'] is None def test_put_context_def_invalid(authz): - result = authz.put_context_def({ + result = authz.put_context_def( + { "context_type": "BAD", - "schema": "nope", - }) - assert result["has_failed"] is True + "schema": "nope" + } + ) + assert result['error'] is not None def test_get_context_def(authz, context_def): authz.put_context_def(context_def) result = authz.get_context_def(context_type="NONE") - assert result["has_failed"] is False - assert result["context_def"]["context_type"] == "NONE" + assert result['error'] is None + assert result['context_def']['context_type'] == "NONE" def test_get_context_def_not_found(authz): result = authz.get_context_def(context_type="DOES_NOT_EXIST") - assert result["context_def"] is None - assert result["has_failed"] is True + assert result['context_def'] is None + assert result['error'] is not None def test_list_context_defs_empty(authz): result = authz.list_context_defs() - assert result["has_failed"] is False - assert result["context_defs"] == [] - assert result["next_page_ref"] is None + assert result['error'] is None + assert result['context_defs'] == [] + assert result['next_page_ref'] is None def test_list_context_defs_with_data(authz, context_def): authz.put_context_def(context_def) result = authz.list_context_defs() - assert len(result["context_defs"]) == 1 - assert result["context_defs"][0]["context_type"] == "NONE" + assert len(result['context_defs']) == 1 + assert result['context_defs'][0]['context_type'] == "NONE" def test_list_context_defs_paginator(authz, context_def): authz.put_context_def(context_def) all_defs = [] for page in paginator(authz.list_context_defs): - all_defs.extend(page["context_defs"]) + all_defs.extend(page['context_defs']) + assert len(all_defs) == 1 def test_delete_context_def(authz, context_def): authz.put_context_def(context_def) result = authz.delete_context_def(context_type="NONE") - assert result["has_failed"] is False + assert result['error'] is None # Verify it was deleted - get returns has_failed=True with resource_not_found get_result = authz.get_context_def(context_type="NONE") - assert get_result["context_def"] is None - assert get_result["has_failed"] is True + assert get_result['context_def'] is None + assert get_result['error'] is not None def test_delete_context_def_not_found(authz): result = authz.delete_context_def(context_type="DOES_NOT_EXIST") - assert result["has_failed"] is False + assert result['error'] is None def test_validate_context_def_with_config(authz, context_def): result = authz.validate_context_def( context_def, config={ "authzee": { - "raise_crits": True, - }, + "raise_errors": True + } } ) - assert result["has_failed"] is False + assert result['error'] is None def test_put_context_def_with_config(authz, context_def): result = authz.put_context_def( context_def, config={ "authzee": { - "raise_crits": False, - }, + "raise_errors": False + } } ) - assert result["has_failed"] is False + assert result['error'] is None def test_validate_identity_def_valid(authz, identity_def): result = authz.validate_identity_def(identity_def) - assert result["has_failed"] is False + assert result['error'] is None def test_validate_identity_def_invalid(authz): result = authz.validate_identity_def( { "identity_type": "BAD", - "schema": "not_a_dict", + "schema": "not_a_dict" } ) - assert result["has_failed"] is True + assert result['error'] is not None def test_validate_identity_def_non_object_schema(authz): @@ -468,87 +488,90 @@ def test_validate_identity_def_non_object_schema(authz): { "identity_type": "BAD", "schema": { - "type": "string", - }, + "type": "string" + } } ) - assert result["has_failed"] is True + assert result['error'] is not None def test_put_identity_def(authz, identity_def): result = authz.put_identity_def(identity_def) - assert result["has_failed"] is False + assert result['error'] is None def test_put_identity_def_invalid(authz): - result = authz.put_identity_def({ + result = authz.put_identity_def( + { "identity_type": "X", - "schema": 123, - }) - assert result["has_failed"] is True + "schema": 123 + } + ) + assert result['error'] is not None def test_get_identity_def(authz, identity_def): authz.put_identity_def(identity_def) result = authz.get_identity_def(identity_type="user") - assert result["has_failed"] is False - assert result["identity_def"]["identity_type"] == "user" + assert result['error'] is None + assert result['identity_def']['identity_type'] == "user" def test_get_identity_def_not_found(authz): result = authz.get_identity_def(identity_type="DOES_NOT_EXIST") - assert result["identity_def"] is None - assert result["has_failed"] is True + assert result['identity_def'] is None + assert result['error'] is not None def test_list_identity_defs_empty(authz): result = authz.list_identity_defs() - assert result["has_failed"] is False - assert result["identity_defs"] == [] + assert result['error'] is None + assert result['identity_defs'] == [] def test_list_identity_defs_with_data(authz, identity_def): authz.put_identity_def(identity_def) result = authz.list_identity_defs() - assert len(result["identity_defs"]) == 1 + assert len(result['identity_defs']) == 1 def test_list_identity_defs_paginator(authz, identity_def): authz.put_identity_def(identity_def) all_defs = [] for page in paginator(authz.list_identity_defs): - all_defs.extend(page["identity_defs"]) + all_defs.extend(page['identity_defs']) + assert len(all_defs) == 1 def test_delete_identity_def(authz, identity_def): authz.put_identity_def(identity_def) result = authz.delete_identity_def(identity_type="user") - assert result["has_failed"] is False + assert result['error'] is None get_result = authz.get_identity_def(identity_type="user") - assert get_result["identity_def"] is None - assert get_result["has_failed"] is True + assert get_result['identity_def'] is None + assert get_result['error'] is not None def test_delete_identity_def_not_found(authz): result = authz.delete_identity_def(identity_type="DOES_NOT_EXIST") - assert result["has_failed"] is False + assert result['error'] is None def test_validate_identity_def_with_config(authz, identity_def): result = authz.validate_identity_def( identity_def, config={ "authzee": { - "raise_crits": True, - }, + "raise_errors": True + } } ) - assert result["has_failed"] is False + assert result['error'] is None def test_validate_resource_def_valid(authz, resource_def): result = authz.validate_resource_def(resource_def) - assert result["has_failed"] is False + assert result['error'] is None def test_validate_resource_def_invalid(authz): @@ -556,10 +579,10 @@ def test_validate_resource_def_invalid(authz): { "resource_type": "X", "actions": [], - "schema": "bad", + "schema": "bad" } ) - assert result["has_failed"] is True + assert result['error'] is not None def test_validate_resource_def_non_object_schema(authz): @@ -568,16 +591,16 @@ def test_validate_resource_def_non_object_schema(authz): "resource_type": "X", "actions": [], "schema": { - "type": "array", - }, + "type": "array" + } } ) - assert result["has_failed"] is True + assert result['error'] is not None def test_put_resource_def(authz, resource_def): result = authz.put_resource_def(resource_def) - assert result["has_failed"] is False + assert result['error'] is None def test_put_resource_def_invalid(authz): @@ -585,117 +608,122 @@ def test_put_resource_def_invalid(authz): { "resource_type": "X", "actions": [], - "schema": "bad", + "schema": "bad" } ) - assert result["has_failed"] is True + assert result['error'] is not None def test_get_resource_def(authz, resource_def): authz.put_resource_def(resource_def) result = authz.get_resource_def(resource_type="balloon") - assert result["has_failed"] is False - assert result["resource_def"]["resource_type"] == "balloon" + assert result['error'] is None + assert result['resource_def']['resource_type'] == "balloon" def test_get_resource_def_not_found(authz): result = authz.get_resource_def(resource_type="DOES_NOT_EXIST") - assert result["resource_def"] is None - assert result["has_failed"] is True + assert result['resource_def'] is None + assert result['error'] is not None def test_list_resource_defs_empty(authz): result = authz.list_resource_defs() - assert result["has_failed"] is False - assert result["resource_defs"] == [] + assert result['error'] is None + assert result['resource_defs'] == [] def test_list_resource_defs_with_data(authz, resource_def): authz.put_resource_def(resource_def) result = authz.list_resource_defs() - assert len(result["resource_defs"]) == 1 + assert len(result['resource_defs']) == 1 def test_list_resource_defs_paginator(authz, resource_def): authz.put_resource_def(resource_def) all_defs = [] for page in paginator(authz.list_resource_defs): - all_defs.extend(page["resource_defs"]) + all_defs.extend(page['resource_defs']) + assert len(all_defs) == 1 def test_delete_resource_def(authz, resource_def): authz.put_resource_def(resource_def) result = authz.delete_resource_def(resource_type="balloon") - assert result["has_failed"] is False + assert result['error'] is None get_result = authz.get_resource_def(resource_type="balloon") - assert get_result["resource_def"] is None - assert get_result["has_failed"] is True + assert get_result['resource_def'] is None + assert get_result['error'] is not None def test_delete_resource_def_not_found(authz): result = authz.delete_resource_def(resource_type="DOES_NOT_EXIST") - assert result["has_failed"] is False + assert result['error'] is None def test_validate_resource_def_with_config(authz, resource_def): result = authz.validate_resource_def( resource_def, config={ "authzee": { - "raise_crits": True, - }, + "raise_errors": True + } } ) - assert result["has_failed"] is False + assert result['error'] is None def test_validate_grant_valid(authz, grant): result = authz.validate_grant(grant) - assert result["has_failed"] is False + assert result['error'] is None def test_validate_grant_invalid(authz): - result = authz.validate_grant({ - "effect": "bad", - }) - assert result["has_failed"] is True + result = authz.validate_grant( + { + "effect": "bad" + } + ) + assert result['error'] is not None def test_enact_grant(authz, grant): result = authz.enact(grant) - assert result["has_failed"] is False + assert result['error'] is None def test_enact_invalid_grant(authz): - result = authz.enact({ - "effect": "bad", - }) - assert result["has_failed"] is True + result = authz.enact( + { + "effect": "bad" + } + ) + assert result['error'] is not None def test_get_grant(authz, grant): authz.enact(grant) - result = authz.get_grant(grant_uuid=grant["grant_uuid"]) - assert result["has_failed"] is False - assert result["grant"]["grant_uuid"] == grant["grant_uuid"] + result = authz.get_grant(grant_uuid=grant['grant_uuid']) + assert result['error'] is None + assert result['grant']['grant_uuid'] == grant['grant_uuid'] def test_get_grant_not_found(authz): result = authz.get_grant(grant_uuid="nonexistent-uuid") - assert result["grant"] is None - assert result["has_failed"] is True + assert result['grant'] is None + assert result['error'] is not None def test_list_grants_empty(authz): result = authz.list_grants() - assert result["has_failed"] is False - assert result["grants"] == [] + assert result['error'] is None + assert result['grants'] == [] def test_list_grants_with_data(authz, grant): authz.enact(grant) result = authz.list_grants() - assert len(result["grants"]) == 1 + assert len(result['grants']) == 1 def test_list_grants_filter_by_effect(authz, grant, deny_grant): @@ -703,29 +731,30 @@ def test_list_grants_filter_by_effect(authz, grant, deny_grant): authz.enact(deny_grant) allow_result = authz.list_grants(effect="allow") deny_result = authz.list_grants(effect="deny") - assert all(g["effect"] == "allow" for g in allow_result["grants"]) - assert all(g["effect"] == "deny" for g in deny_result["grants"]) + assert all(g['effect'] == "allow" for g in allow_result['grants']) + assert all(g['effect'] == "deny" for g in deny_result['grants']) def test_list_grants_filter_by_action(authz, grant, deny_grant): authz.enact(grant) authz.enact(deny_grant) result = authz.list_grants(action="balloon:pop") - assert all("balloon:pop" in g["actions"] for g in result["grants"]) + assert all("balloon:pop" in g['actions'] for g in result['grants']) def test_list_grants_paginator(authz, grant): authz.enact(grant) all_grants = [] for page in paginator(authz.list_grants): - all_grants.extend(page["grants"]) + all_grants.extend(page['grants']) + assert len(all_grants) == 1 def test_list_grant_refs(authz, grant): authz.enact(grant) result = authz.list_grant_refs() - assert result["has_failed"] is False + assert result['error'] is None assert "page_refs" in result @@ -733,60 +762,67 @@ def test_list_grant_refs_filter_by_effect(authz, grant, deny_grant): authz.enact(grant) authz.enact(deny_grant) result = authz.list_grant_refs(effect="allow") - assert result["has_failed"] is False + assert result['error'] is None def test_list_grant_refs_paginator(authz, grant): authz.enact(grant) all_refs = [] for page in paginator(authz.list_grant_refs): - all_refs.extend(page["page_refs"]) + all_refs.extend(page['page_refs']) + assert isinstance(all_refs, list) def test_repeal_grant(authz, grant): authz.enact(grant) - result = authz.repeal(grant_uuid=grant["grant_uuid"], purge=False) - assert result["has_failed"] is False + result = authz.repeal(grant_uuid=grant['grant_uuid'], purge=False) + assert result['error'] is None # Verify it was repealed - get returns has_failed=True with not found - get_result = authz.get_grant(grant_uuid=grant["grant_uuid"]) - assert get_result["grant"] is None - assert get_result["has_failed"] is True + get_result = authz.get_grant(grant_uuid=grant['grant_uuid']) + assert get_result['grant'] is None + assert get_result['error'] is not None def test_repeal_grant_purge(authz, grant): authz.enact(grant) - result = authz.repeal(grant_uuid=grant["grant_uuid"], purge=True) - assert result["has_failed"] is False + result = authz.repeal(grant_uuid=grant['grant_uuid'], purge=True) + assert result['error'] is None def test_repeal_grant_not_found(authz): # DictStorage repeal returns has_failed=False even when not found result = authz.repeal(grant_uuid="nonexistent-uuid", purge=False) - assert result["has_failed"] is False + assert result['error'] is None def test_validate_grant_with_config(authz, grant): - result = authz.validate_grant(grant, config={ + result = authz.validate_grant( + grant, + config={ "authzee": { - "raise_crits": True, - }, - }) - assert result["has_failed"] is False + "raise_errors": True + } + } + ) + assert result['error'] is None def test_enact_with_config(authz, grant): - result = authz.enact(grant, config={ + result = authz.enact( + grant, + config={ "authzee": { - "raise_crits": False, - }, - }) - assert result["has_failed"] is False + "raise_errors": False + } + } + ) + assert result['error'] is None def test_cleanup_latches(authz): result = authz.cleanup_latches(before=datetime.datetime(2030, 1, 1)) - assert result["has_failed"] is False + assert result['error'] is None def test_cleanup_latches_with_config(authz): @@ -794,26 +830,29 @@ def test_cleanup_latches_with_config(authz): before=datetime.datetime(2030, 1, 1), config={ "authzee": { - "raise_crits": True, - }, - }, + "raise_errors": True + } + } ) - assert result["has_failed"] is False + assert result['error'] is None def test_authorize_allowed(seeded_authz, auth_request): result = seeded_authz.authorize(request=auth_request) - assert result["is_authorized"] is True - assert result["has_failed"] is False - assert result["grant"] is not None - assert isinstance(result["message"], str) + assert result['is_authorized'] is True + assert result['error'] is None + assert result['grant'] is not None + assert isinstance(result['message'], str) def test_authorize_denied_no_matching_grant(seeded_authz, auth_request): # Change to an action that has no allow grant - request = {**auth_request, "action": "balloon:pop"} + request = { + **auth_request, + "action": "balloon:pop" + } result = seeded_authz.authorize(request=request) - assert result["is_authorized"] is False + assert result['is_authorized'] is False def test_authorize_denied_by_deny_grant(seeded_authz, deny_grant): @@ -824,54 +863,54 @@ def test_authorize_denied_by_deny_grant(seeded_authz, deny_grant): "user": [ { "username": "intern_1", - "department": "Intern", - }, - ], + "department": "Intern" + } + ] }, "action": "balloon:pop", "resource_type": "balloon", "resource": { "color": "blue", - "is_inflated": True, + "is_inflated": True }, - "evaluation_handler": "grant", "context_type": "NONE", - "context": {}, + "context": {} } result = seeded_authz.authorize(request=request) - assert result["is_authorized"] is False + assert result['is_authorized'] is False def test_authorize_with_config(seeded_authz, auth_request): result = seeded_authz.authorize( request=auth_request, config={ "authzee": { - "raise_crits": True, - }, + "raise_errors": True + } } ) - assert result["is_authorized"] is True + assert result['is_authorized'] is True def test_audit(seeded_authz, auth_request): result = seeded_authz.audit(request=auth_request) - assert result["has_failed"] is False - assert "grants" in result + assert result['error'] is None assert "results" in result - assert len(result["grants"]) == len(result["results"]) + assert len(result['results']) > 0 + assert result['results'][0]['grant'] is not None def test_audit_with_applicable_grant(seeded_authz, auth_request): result = seeded_authz.audit(request=auth_request) - assert any(r["is_applicable"] for r in result["results"]) + assert any(r['is_applicable'] for r in result['results']) def test_audit_paginator(seeded_authz, auth_request): all_grants = [] all_results = [] for page in paginator(seeded_authz.audit, request=auth_request): - all_grants.extend(page["grants"]) - all_results.extend(page["results"]) + all_grants.extend([r['grant'] for r in page['results']]) + all_results.extend(page['results']) + assert len(all_grants) >= 1 @@ -879,51 +918,55 @@ def test_audit_with_config(seeded_authz, auth_request): result = seeded_authz.audit( request=auth_request, config={ "authzee": { - "raise_crits": True, - }, + "raise_errors": True + } } ) - assert result["has_failed"] is False + assert result['error'] is None def test_batch_authorize(seeded_authz, batch_request): result = seeded_authz.batch_authorize(batch_request=batch_request) - assert result["has_failed"] is False - assert "batch_results" in result - assert len(result["batch_results"]) == 2 + assert result['error'] is None + assert "batch" in result + assert len(result['batch']) == 2 def test_batch_authorize_all_authorized(seeded_authz, batch_request): result = seeded_authz.batch_authorize(batch_request=batch_request) - for item in result["batch_results"]: - assert item["is_authorized"] is True + for item in result['batch']: + assert item['is_authorized'] is True def test_batch_authorize_with_config(seeded_authz, batch_request): result = seeded_authz.batch_authorize( batch_request=batch_request, config={ "authzee": { - "raise_crits": True, - }, + "raise_errors": True + } } ) - assert result["has_failed"] is False + assert result['error'] is None def test_batch_audit(seeded_authz, batch_request): result = seeded_authz.batch_audit(batch_request=batch_request) - assert result["has_failed"] is False + assert result['error'] is None assert "grants" in result - assert "batch_results" in result - assert len(result["batch_results"]) == 2 + assert "batch" in result + assert len(result['batch']) == 2 def test_batch_audit_paginator(seeded_authz, batch_request): all_grants = [] all_batch = [] - for page in paginator(seeded_authz.batch_audit, batch_request=batch_request): - all_grants.extend(page["grants"]) - all_batch.extend(page["batch_results"]) + for page in paginator( + seeded_authz.batch_audit, + batch_request=batch_request + ): + all_grants.extend(page['grants']) + all_batch.extend(page['batch']) + assert len(all_grants) >= 1 @@ -931,11 +974,11 @@ def test_batch_audit_with_config(seeded_authz, batch_request): result = seeded_authz.batch_audit( batch_request=batch_request, config={ "authzee": { - "raise_crits": True, - }, + "raise_errors": True + } } ) - assert result["has_failed"] is False + assert result['error'] is None def test_instance_level_config(): @@ -947,22 +990,22 @@ def test_instance_level_config(): compute_kwargs={}, storage_type=DictStorage, storage_kwargs={ - "storage_dict": storage_dict, + "storage_dict": storage_dict }, config={ "authzee": { - "raise_crits": False, - }, - }, + "raise_errors": False + } + } ) result = authz.construct() - assert result["has_failed"] is False + assert result['error'] is None result = authz.start() - assert result["has_failed"] is False + assert result['error'] is None -def test_raise_crits_config_raises_on_invalid_def(): - """Test that raise_crits=True raises DefinitionError on invalid put.""" +def test_raise_errors_config_raises_on_invalid_def(): + """Test that raise_errors=True raises DefinitionError on invalid put.""" storage_dict = {} authz = Authzee( execute=jmespath_execute, @@ -970,57 +1013,59 @@ def test_raise_crits_config_raises_on_invalid_def(): compute_kwargs={}, storage_type=DictStorage, storage_kwargs={ - "storage_dict": storage_dict, + "storage_dict": storage_dict }, config={ "authzee": { - "raise_crits": True, - }, - }, + "raise_errors": True + } + } ) authz.construct() authz.start() with pytest.raises(exceptions.DefinitionError): - authz.validate_context_def({ - "context_type": "BAD", - "schema": "not_a_dict", - }) + authz.validate_context_def( + { + "context_type": "BAD", + "schema": "not_a_dict" + } + ) -def test_raise_crits_override_at_call_level(authz): +def test_raise_errors_override_at_call_level(authz): """Test that config override at method level takes precedence.""" with pytest.raises(exceptions.DefinitionError): authz.validate_context_def( { - "context_type": "BAD", - "schema": "not_a_dict", - }, - config={ - "authzee": { - "raise_crits": True, + "context_type": "BAD", + "schema": "not_a_dict" }, - }, + config={ + "authzee": { + "raise_errors": True + } + } ) -def test_raise_crits_false_does_not_raise(authz): - """Test that raise_crits=False returns error result without raising.""" +def test_raise_errors_false_does_not_raise(authz): + """Test that raise_errors=False returns error result without raising.""" result = authz.validate_context_def( { "context_type": "BAD", - "schema": "not_a_dict", + "schema": "not_a_dict" }, config={ "authzee": { - "raise_crits": False, - }, - }, + "raise_errors": False + } + } ) - assert result["has_failed"] is True + assert result['error'] is not None -def test_raise_crits_grant_error(): - """Test that raise_crits raises GrantError on invalid grant validation.""" +def test_raise_errors_grant_error(): + """Test that raise_errors raises GrantError on invalid grant validation.""" storage_dict = {} authz = Authzee( execute=jmespath_execute, @@ -1028,20 +1073,22 @@ def test_raise_crits_grant_error(): compute_kwargs={}, storage_type=DictStorage, storage_kwargs={ - "storage_dict": storage_dict, + "storage_dict": storage_dict }, config={ "authzee": { - "raise_crits": True, - }, - }, + "raise_errors": True + } + } ) authz.construct() authz.start() with pytest.raises(exceptions.GrantError): - authz.validate_grant({ - "effect": "bad", - }) + authz.validate_grant( + { + "effect": "bad" + } + ) def test_compute_storage_kwargs_override(): @@ -1053,25 +1100,28 @@ def test_compute_storage_kwargs_override(): compute_kwargs={}, storage_type=DictStorage, storage_kwargs={ - "storage_dict": storage_dict, + "storage_dict": storage_dict }, compute_storage_kwargs={ - "storage_dict": storage_dict, - }, + "storage_dict": storage_dict + } ) authz.construct() result = authz.start() - assert result["has_failed"] is False + assert result['error'] is None def test_put_context_def_overwrite(authz, context_def): """Putting the same context_type twice should succeed (upsert).""" authz.put_context_def(context_def) - updated_def = {**context_def, "schema": { - "type": "object", - }} + updated_def = { + **context_def, + "schema": { + "type": "object" + } + } result = authz.put_context_def(updated_def) - assert result["has_failed"] is False + assert result['error'] is None def test_put_identity_def_overwrite(authz, identity_def): @@ -1082,13 +1132,13 @@ def test_put_identity_def_overwrite(authz, identity_def): "type": "object", "properties": { "username": { - "type": "string", - }, - }, - }, + "type": "string" + } + } + } } result = authz.put_identity_def(updated_def) - assert result["has_failed"] is False + assert result['error'] is None def test_put_resource_def_overwrite(authz, resource_def): @@ -1099,18 +1149,18 @@ def test_put_resource_def_overwrite(authz, resource_def): "balloon:read", "balloon:inflate", "balloon:pop", - "balloon:tie", - ], + "balloon:tie" + ] } result = authz.put_resource_def(updated_def) - assert result["has_failed"] is False + assert result['error'] is None def test_multiple_grants(authz, grant, deny_grant): authz.enact(grant) authz.enact(deny_grant) result = authz.list_grants() - assert len(result["grants"]) == 2 + assert len(result['grants']) == 2 def test_multiple_context_defs(authz): @@ -1118,20 +1168,20 @@ def test_multiple_context_defs(authz): { "context_type": "A", "schema": { - "type": "object", - }, + "type": "object" + } } ) authz.put_context_def( { "context_type": "B", "schema": { - "type": "object", - }, + "type": "object" + } } ) result = authz.list_context_defs() - assert len(result["context_defs"]) == 2 + assert len(result['context_defs']) == 2 def test_multiple_identity_defs(authz): @@ -1139,20 +1189,20 @@ def test_multiple_identity_defs(authz): { "identity_type": "A", "schema": { - "type": "object", - }, + "type": "object" + } } ) authz.put_identity_def( { "identity_type": "B", "schema": { - "type": "object", - }, + "type": "object" + } } ) result = authz.list_identity_defs() - assert len(result["identity_defs"]) == 2 + assert len(result['identity_defs']) == 2 def test_multiple_resource_defs(authz): @@ -1160,23 +1210,23 @@ def test_multiple_resource_defs(authz): { "resource_type": "A", "actions": [ - "A:read", + "A:read" ], "schema": { - "type": "object", - }, + "type": "object" + } } ) authz.put_resource_def( { "resource_type": "B", "actions": [ - "B:read", + "B:read" ], "schema": { - "type": "object", - }, + "type": "object" + } } ) result = authz.list_resource_defs() - assert len(result["resource_defs"]) == 2 + assert len(result['resource_defs']) == 2 diff --git a/tests/unit/test_authzee_async.py b/tests/unit/test_authzee_async.py index a9d9a30..7457226 100644 --- a/tests/unit/test_authzee_async.py +++ b/tests/unit/test_authzee_async.py @@ -4,6 +4,7 @@ Black box testing - only uses public API methods of AuthzeeAsync. Uses asyncio.run() pattern since pytest-asyncio is not installed. """ + import asyncio import datetime from uuid import uuid4 @@ -14,11 +15,12 @@ AuthzeeAsync, DictStorage, InProcessCompute, - jmespath_execute, - paginator_async, authzee_specification_version, exceptions, + jmespath_execute, + paginator_async ) +from authzee.module_locality import ModuleLocality @pytest.fixture @@ -28,23 +30,24 @@ def storage_dict(): @pytest.fixture def authz(storage_dict): - """Create a fully initialized AuthzeeAsync instance with raise_crits=False.""" + """Create a fully initialized AuthzeeAsync instance with raise_errors=False.""" a = AuthzeeAsync( execute=jmespath_execute, compute_type=InProcessCompute, compute_kwargs={}, storage_type=DictStorage, storage_kwargs={ - "storage_dict": storage_dict, + "storage_dict": storage_dict }, config={ "authzee": { - "raise_crits": False, - }, - }, + "raise_errors": False + } + } ) asyncio.run(a.construct()) asyncio.run(a.start()) + return a @@ -54,8 +57,8 @@ def context_def(): "context_type": "NONE", "schema": { "type": "object", - "additionalProperties": False, - }, + "additionalProperties": False + } } @@ -67,18 +70,18 @@ def identity_def(): "type": "object", "required": [ "username", - "department", + "department" ], "additionalProperties": False, "properties": { "username": { - "type": "string", + "type": "string" }, "department": { - "type": "string", - }, - }, - }, + "type": "string" + } + } + } } @@ -89,24 +92,24 @@ def resource_def(): "actions": [ "balloon:read", "balloon:inflate", - "balloon:pop", + "balloon:pop" ], "schema": { "type": "object", "required": [ "color", - "is_inflated", + "is_inflated" ], "additionalProperties": False, "properties": { "color": { - "type": "string", + "type": "string" }, "is_inflated": { - "type": "boolean", - }, - }, - }, + "type": "boolean" + } + } + } } @@ -117,17 +120,17 @@ def grant(): "name": "Allow inflate for balloon department", "description": "Balloon dept can read and inflate balloons.", "tags": { - "team": "balloon", + "team": "balloon" }, "effect": "allow", "actions": [ "balloon:read", - "balloon:inflate", + "balloon:inflate" ], "query": "length(request.identities.user[?department == 'Balloon Dept']) > `0`", - "evaluation_handler": "evaluate", "equality": True, - "data": {}, + "applicable_on_failure": False, + "data": {} } @@ -140,12 +143,12 @@ def deny_grant(): "tags": {}, "effect": "deny", "actions": [ - "balloon:pop", + "balloon:pop" ], "query": "length(request.identities.user[?department == 'Intern']) > `0`", - "evaluation_handler": "evaluate", "equality": True, - "data": {}, + "applicable_on_failure": False, + "data": {} } @@ -156,19 +159,18 @@ def auth_request(): "user": [ { "username": "balloon_person", - "department": "Balloon Dept", - }, + "department": "Balloon Dept" + } ] }, "action": "balloon:inflate", "resource_type": "balloon", "resource": { "color": "blue", - "is_inflated": False, + "is_inflated": False }, - "evaluation_handler": "grant", "context_type": "NONE", - "context": {}, + "context": {} } @@ -179,43 +181,49 @@ def batch_request(): "user": [ { "username": "balloon_person", - "department": "Balloon Dept", - }, + "department": "Balloon Dept" + } ] }, "action": "balloon:inflate", "resource_type": "balloon", "resource": { "color": "blue", - "is_inflated": False, + "is_inflated": False }, - "evaluation_handler": "grant", "context_type": "NONE", "context": {}, "batch": [ { "resource": { "color": "red", - "is_inflated": True, - }, + "is_inflated": True + } }, { "resource": { "color": "green", - "is_inflated": False, - }, - }, - ], + "is_inflated": False + } + } + ] } @pytest.fixture -def seeded_authz(authz, context_def, identity_def, resource_def, grant): +def seeded_authz( + authz, + context_def, + identity_def, + resource_def, + grant +): """An AuthzeeAsync instance with definitions and a grant already stored.""" asyncio.run(authz.put_context_def(context_def)) asyncio.run(authz.put_identity_def(identity_def)) asyncio.run(authz.put_resource_def(resource_def)) asyncio.run(authz.enact(grant)) + return authz @@ -231,11 +239,11 @@ def test_construct(storage_dict): compute_kwargs={}, storage_type=DictStorage, storage_kwargs={ - "storage_dict": storage_dict, - }, + "storage_dict": storage_dict + } ) result = asyncio.run(authz.construct()) - assert result["has_failed"] is False + assert result['error'] is None def test_start(storage_dict): @@ -245,17 +253,17 @@ def test_start(storage_dict): compute_kwargs={}, storage_type=DictStorage, storage_kwargs={ - "storage_dict": storage_dict, - }, + "storage_dict": storage_dict + } ) asyncio.run(authz.construct()) result = asyncio.run(authz.start()) - assert result["has_failed"] is False + assert result['error'] is None def test_shutdown(authz): result = asyncio.run(authz.shutdown()) - assert result["has_failed"] is False + assert result['error'] is None def test_destroy(storage_dict): @@ -265,13 +273,13 @@ def test_destroy(storage_dict): compute_kwargs={}, storage_type=DictStorage, storage_kwargs={ - "storage_dict": storage_dict, - }, + "storage_dict": storage_dict + } ) asyncio.run(authz.construct()) asyncio.run(authz.start()) result = asyncio.run(authz.destroy()) - assert result["has_failed"] is False + assert result['error'] is None def test_construct_with_config(storage_dict): @@ -281,15 +289,19 @@ def test_construct_with_config(storage_dict): compute_kwargs={}, storage_type=DictStorage, storage_kwargs={ - "storage_dict": storage_dict, - }, + "storage_dict": storage_dict + } ) - result = asyncio.run(authz.construct(config={ - "authzee": { - "raise_crits": True, - }, - })) - assert result["has_failed"] is False + result = asyncio.run( + authz.construct( + config={ + "authzee": { + "raise_errors": True + } + } + ) + ) + assert result['error'] is None def test_start_with_config(storage_dict): @@ -299,25 +311,33 @@ def test_start_with_config(storage_dict): compute_kwargs={}, storage_type=DictStorage, storage_kwargs={ - "storage_dict": storage_dict, - }, + "storage_dict": storage_dict + } ) asyncio.run(authz.construct()) - result = asyncio.run(authz.start(config={ - "authzee": { - "raise_crits": True, - }, - })) - assert result["has_failed"] is False + result = asyncio.run( + authz.start( + config={ + "authzee": { + "raise_errors": True + } + } + ) + ) + assert result['error'] is None def test_shutdown_with_config(authz): - result = asyncio.run(authz.shutdown(config={ - "authzee": { - "raise_crits": True, - }, - })) - assert result["has_failed"] is False + result = asyncio.run( + authz.shutdown( + config={ + "authzee": { + "raise_errors": True + } + } + ) + ) + assert result['error'] is None def test_destroy_with_config(storage_dict): @@ -327,88 +347,98 @@ def test_destroy_with_config(storage_dict): compute_kwargs={}, storage_type=DictStorage, storage_kwargs={ - "storage_dict": storage_dict, - }, + "storage_dict": storage_dict + } ) asyncio.run(authz.construct()) asyncio.run(authz.start()) - result = asyncio.run(authz.destroy(config={ - "authzee": { - "raise_crits": True, - }, - })) - assert result["has_failed"] is False + result = asyncio.run( + authz.destroy( + config={ + "authzee": { + "raise_errors": True + } + } + ) + ) + assert result['error'] is None def test_validate_context_def_valid(authz, context_def): result = asyncio.run(authz.validate_context_def(context_def)) - assert result["has_failed"] is False + assert result['error'] is None def test_validate_context_def_invalid(authz): result = asyncio.run( - authz.validate_context_def({ - "context_type": "BAD", - "schema": "not_a_dict", - }) + authz.validate_context_def( + { + "context_type": "BAD", + "schema": "not_a_dict" + } + ) ) - assert result["has_failed"] is True + assert result['error'] is not None def test_validate_context_def_non_object_schema(authz): result = asyncio.run( authz.validate_context_def( { - "context_type": "BAD", - "schema": { - "type": "array", - }, - } + "context_type": "BAD", + "schema": { + "type": "array" + } + } ) ) - assert result["has_failed"] is True + assert result['error'] is not None def test_put_context_def(authz, context_def): result = asyncio.run(authz.put_context_def(context_def)) - assert result["has_failed"] is False + assert result['error'] is None def test_put_context_def_invalid(authz): result = asyncio.run( - authz.put_context_def({ - "context_type": "BAD", - "schema": "nope", - }) + authz.put_context_def( + { + "context_type": "BAD", + "schema": "nope" + } + ) ) - assert result["has_failed"] is True + assert result['error'] is not None def test_get_context_def(authz, context_def): asyncio.run(authz.put_context_def(context_def)) result = asyncio.run(authz.get_context_def(context_type="NONE")) - assert result["has_failed"] is False - assert result["context_def"]["context_type"] == "NONE" + assert result['error'] is None + assert result['context_def']['context_type'] == "NONE" def test_get_context_def_not_found(authz): - result = asyncio.run(authz.get_context_def(context_type="DOES_NOT_EXIST")) - assert result["context_def"] is None - assert result["has_failed"] is True + result = asyncio.run( + authz.get_context_def(context_type="DOES_NOT_EXIST") + ) + assert result['context_def'] is None + assert result['error'] is not None def test_list_context_defs_empty(authz): result = asyncio.run(authz.list_context_defs()) - assert result["has_failed"] is False - assert result["context_defs"] == [] - assert result["next_page_ref"] is None + assert result['error'] is None + assert result['context_defs'] == [] + assert result['next_page_ref'] is None def test_list_context_defs_with_data(authz, context_def): asyncio.run(authz.put_context_def(context_def)) result = asyncio.run(authz.list_context_defs()) - assert len(result["context_defs"]) == 1 - assert result["context_defs"][0]["context_type"] == "NONE" + assert len(result['context_defs']) == 1 + assert result['context_defs'][0]['context_type'] == "NONE" def test_list_context_defs_paginator_async(authz, context_def): @@ -417,7 +447,8 @@ def test_list_context_defs_paginator_async(authz, context_def): async def _collect(): all_defs = [] async for page in paginator_async(authz.list_context_defs): - all_defs.extend(page["context_defs"]) + all_defs.extend(page['context_defs']) + return all_defs all_defs = asyncio.run(_collect()) @@ -427,108 +458,119 @@ async def _collect(): def test_delete_context_def(authz, context_def): asyncio.run(authz.put_context_def(context_def)) result = asyncio.run(authz.delete_context_def(context_type="NONE")) - assert result["has_failed"] is False + assert result['error'] is None get_result = asyncio.run(authz.get_context_def(context_type="NONE")) - assert get_result["context_def"] is None - assert get_result["has_failed"] is True + assert get_result['context_def'] is None + assert get_result['error'] is not None def test_delete_context_def_not_found(authz): - result = asyncio.run(authz.delete_context_def(context_type="DOES_NOT_EXIST")) - assert result["has_failed"] is False + result = asyncio.run( + authz.delete_context_def(context_type="DOES_NOT_EXIST") + ) + assert result['error'] is None def test_validate_context_def_with_config(authz, context_def): result = asyncio.run( authz.validate_context_def( context_def, config={ - "authzee": { - "raise_crits": True, - }, - } + "authzee": { + "raise_errors": True + } + } ) ) - assert result["has_failed"] is False + assert result['error'] is None def test_put_context_def_with_config(authz, context_def): result = asyncio.run( - authz.put_context_def(context_def, config={ - "authzee": { - "raise_crits": False, - }, - }) + authz.put_context_def( + context_def, + config={ + "authzee": { + "raise_errors": False + } + } + ) ) - assert result["has_failed"] is False + assert result['error'] is None def test_validate_identity_def_valid(authz, identity_def): result = asyncio.run(authz.validate_identity_def(identity_def)) - assert result["has_failed"] is False + assert result['error'] is None def test_validate_identity_def_invalid(authz): result = asyncio.run( - authz.validate_identity_def({ - "identity_type": "BAD", - "schema": "not_a_dict", - }) + authz.validate_identity_def( + { + "identity_type": "BAD", + "schema": "not_a_dict" + } + ) ) - assert result["has_failed"] is True + assert result['error'] is not None def test_validate_identity_def_non_object_schema(authz): result = asyncio.run( authz.validate_identity_def( { - "identity_type": "BAD", - "schema": { - "type": "string", - }, - } + "identity_type": "BAD", + "schema": { + "type": "string" + } + } ) ) - assert result["has_failed"] is True + assert result['error'] is not None def test_put_identity_def(authz, identity_def): result = asyncio.run(authz.put_identity_def(identity_def)) - assert result["has_failed"] is False + assert result['error'] is None def test_put_identity_def_invalid(authz): result = asyncio.run( - authz.put_identity_def({ - "identity_type": "X", - "schema": 123, - }) + authz.put_identity_def( + { + "identity_type": "X", + "schema": 123 + } + ) ) - assert result["has_failed"] is True + assert result['error'] is not None def test_get_identity_def(authz, identity_def): asyncio.run(authz.put_identity_def(identity_def)) result = asyncio.run(authz.get_identity_def(identity_type="user")) - assert result["has_failed"] is False - assert result["identity_def"]["identity_type"] == "user" + assert result['error'] is None + assert result['identity_def']['identity_type'] == "user" def test_get_identity_def_not_found(authz): - result = asyncio.run(authz.get_identity_def(identity_type="DOES_NOT_EXIST")) - assert result["identity_def"] is None - assert result["has_failed"] is True + result = asyncio.run( + authz.get_identity_def(identity_type="DOES_NOT_EXIST") + ) + assert result['identity_def'] is None + assert result['error'] is not None def test_list_identity_defs_empty(authz): result = asyncio.run(authz.list_identity_defs()) - assert result["has_failed"] is False - assert result["identity_defs"] == [] + assert result['error'] is None + assert result['identity_defs'] == [] def test_list_identity_defs_with_data(authz, identity_def): asyncio.run(authz.put_identity_def(identity_def)) result = asyncio.run(authz.list_identity_defs()) - assert len(result["identity_defs"]) == 1 + assert len(result['identity_defs']) == 1 def test_list_identity_defs_paginator_async(authz, identity_def): @@ -537,7 +579,8 @@ def test_list_identity_defs_paginator_async(authz, identity_def): async def _collect(): all_defs = [] async for page in paginator_async(authz.list_identity_defs): - all_defs.extend(page["identity_defs"]) + all_defs.extend(page['identity_defs']) + return all_defs all_defs = asyncio.run(_collect()) @@ -547,104 +590,108 @@ async def _collect(): def test_delete_identity_def(authz, identity_def): asyncio.run(authz.put_identity_def(identity_def)) result = asyncio.run(authz.delete_identity_def(identity_type="user")) - assert result["has_failed"] is False + assert result['error'] is None get_result = asyncio.run(authz.get_identity_def(identity_type="user")) - assert get_result["identity_def"] is None - assert get_result["has_failed"] is True + assert get_result['identity_def'] is None + assert get_result['error'] is not None def test_delete_identity_def_not_found(authz): - result = asyncio.run(authz.delete_identity_def(identity_type="DOES_NOT_EXIST")) - assert result["has_failed"] is False + result = asyncio.run( + authz.delete_identity_def(identity_type="DOES_NOT_EXIST") + ) + assert result['error'] is None def test_validate_identity_def_with_config(authz, identity_def): result = asyncio.run( authz.validate_identity_def( identity_def, config={ - "authzee": { - "raise_crits": True, - }, - } + "authzee": { + "raise_errors": True + } + } ) ) - assert result["has_failed"] is False + assert result['error'] is None def test_validate_resource_def_valid(authz, resource_def): result = asyncio.run(authz.validate_resource_def(resource_def)) - assert result["has_failed"] is False + assert result['error'] is None def test_validate_resource_def_invalid(authz): result = asyncio.run( authz.validate_resource_def( { - "resource_type": "X", - "actions": [], - "schema": "bad", - } + "resource_type": "X", + "actions": [], + "schema": "bad" + } ) ) - assert result["has_failed"] is True + assert result['error'] is not None def test_validate_resource_def_non_object_schema(authz): result = asyncio.run( authz.validate_resource_def( { - "resource_type": "X", - "actions": [], - "schema": { - "type": "array", - }, - } + "resource_type": "X", + "actions": [], + "schema": { + "type": "array" + } + } ) ) - assert result["has_failed"] is True + assert result['error'] is not None def test_put_resource_def(authz, resource_def): result = asyncio.run(authz.put_resource_def(resource_def)) - assert result["has_failed"] is False + assert result['error'] is None def test_put_resource_def_invalid(authz): result = asyncio.run( authz.put_resource_def( { - "resource_type": "X", - "actions": [], - "schema": "bad", - } + "resource_type": "X", + "actions": [], + "schema": "bad" + } ) ) - assert result["has_failed"] is True + assert result['error'] is not None def test_get_resource_def(authz, resource_def): asyncio.run(authz.put_resource_def(resource_def)) result = asyncio.run(authz.get_resource_def(resource_type="balloon")) - assert result["has_failed"] is False - assert result["resource_def"]["resource_type"] == "balloon" + assert result['error'] is None + assert result['resource_def']['resource_type'] == "balloon" def test_get_resource_def_not_found(authz): - result = asyncio.run(authz.get_resource_def(resource_type="DOES_NOT_EXIST")) - assert result["resource_def"] is None - assert result["has_failed"] is True + result = asyncio.run( + authz.get_resource_def(resource_type="DOES_NOT_EXIST") + ) + assert result['resource_def'] is None + assert result['error'] is not None def test_list_resource_defs_empty(authz): result = asyncio.run(authz.list_resource_defs()) - assert result["has_failed"] is False - assert result["resource_defs"] == [] + assert result['error'] is None + assert result['resource_defs'] == [] def test_list_resource_defs_with_data(authz, resource_def): asyncio.run(authz.put_resource_def(resource_def)) result = asyncio.run(authz.list_resource_defs()) - assert len(result["resource_defs"]) == 1 + assert len(result['resource_defs']) == 1 def test_list_resource_defs_paginator_async(authz, resource_def): @@ -653,7 +700,8 @@ def test_list_resource_defs_paginator_async(authz, resource_def): async def _collect(): all_defs = [] async for page in paginator_async(authz.list_resource_defs): - all_defs.extend(page["resource_defs"]) + all_defs.extend(page['resource_defs']) + return all_defs all_defs = asyncio.run(_collect()) @@ -662,78 +710,78 @@ async def _collect(): def test_delete_resource_def(authz, resource_def): asyncio.run(authz.put_resource_def(resource_def)) - result = asyncio.run(authz.delete_resource_def(resource_type="balloon")) - assert result["has_failed"] is False + result = asyncio.run( + authz.delete_resource_def(resource_type="balloon") + ) + assert result['error'] is None get_result = asyncio.run(authz.get_resource_def(resource_type="balloon")) - assert get_result["resource_def"] is None - assert get_result["has_failed"] is True + assert get_result['resource_def'] is None + assert get_result['error'] is not None def test_delete_resource_def_not_found(authz): - result = asyncio.run(authz.delete_resource_def(resource_type="DOES_NOT_EXIST")) - assert result["has_failed"] is False + result = asyncio.run( + authz.delete_resource_def(resource_type="DOES_NOT_EXIST") + ) + assert result['error'] is None def test_validate_resource_def_with_config(authz, resource_def): result = asyncio.run( authz.validate_resource_def( resource_def, config={ - "authzee": { - "raise_crits": True, - }, - } + "authzee": { + "raise_errors": True + } + } ) ) - assert result["has_failed"] is False + assert result['error'] is None def test_validate_grant_valid(authz, grant): result = asyncio.run(authz.validate_grant(grant)) - assert result["has_failed"] is False + assert result['error'] is None def test_validate_grant_invalid(authz): - result = asyncio.run(authz.validate_grant({ - "effect": "bad", - })) - assert result["has_failed"] is True + result = asyncio.run(authz.validate_grant({"effect": "bad"})) + assert result['error'] is not None def test_enact_grant(authz, grant): result = asyncio.run(authz.enact(grant)) - assert result["has_failed"] is False + assert result['error'] is None def test_enact_invalid_grant(authz): - result = asyncio.run(authz.enact({ - "effect": "bad", - })) - assert result["has_failed"] is True + result = asyncio.run(authz.enact({"effect": "bad"})) + assert result['error'] is not None def test_get_grant(authz, grant): asyncio.run(authz.enact(grant)) - result = asyncio.run(authz.get_grant(grant_uuid=grant["grant_uuid"])) - assert result["has_failed"] is False - assert result["grant"]["grant_uuid"] == grant["grant_uuid"] + result = asyncio.run(authz.get_grant(grant_uuid=grant['grant_uuid'])) + assert result['error'] is None + assert result['grant']['grant_uuid'] == grant['grant_uuid'] def test_get_grant_not_found(authz): result = asyncio.run(authz.get_grant(grant_uuid="nonexistent-uuid")) - assert result["grant"] is None - assert result["has_failed"] is True + assert result['grant'] is None + assert result['error'] is not None def test_list_grants_empty(authz): result = asyncio.run(authz.list_grants()) - assert result["has_failed"] is False - assert result["grants"] == [] + assert result['error'] is None + assert result['grants'] == [] def test_list_grants_with_data(authz, grant): asyncio.run(authz.enact(grant)) result = asyncio.run(authz.list_grants()) - assert len(result["grants"]) == 1 + assert len(result['grants']) == 1 def test_list_grants_filter_by_effect(authz, grant, deny_grant): @@ -741,15 +789,15 @@ def test_list_grants_filter_by_effect(authz, grant, deny_grant): asyncio.run(authz.enact(deny_grant)) allow_result = asyncio.run(authz.list_grants(effect="allow")) deny_result = asyncio.run(authz.list_grants(effect="deny")) - assert all(g["effect"] == "allow" for g in allow_result["grants"]) - assert all(g["effect"] == "deny" for g in deny_result["grants"]) + assert all(g['effect'] == "allow" for g in allow_result['grants']) + assert all(g['effect'] == "deny" for g in deny_result['grants']) def test_list_grants_filter_by_action(authz, grant, deny_grant): asyncio.run(authz.enact(grant)) asyncio.run(authz.enact(deny_grant)) result = asyncio.run(authz.list_grants(action="balloon:pop")) - assert all("balloon:pop" in g["actions"] for g in result["grants"]) + assert all("balloon:pop" in g['actions'] for g in result['grants']) def test_list_grants_paginator_async(authz, grant): @@ -758,7 +806,8 @@ def test_list_grants_paginator_async(authz, grant): async def _collect(): all_grants = [] async for page in paginator_async(authz.list_grants): - all_grants.extend(page["grants"]) + all_grants.extend(page['grants']) + return all_grants all_grants = asyncio.run(_collect()) @@ -768,7 +817,7 @@ async def _collect(): def test_list_grant_refs(authz, grant): asyncio.run(authz.enact(grant)) result = asyncio.run(authz.list_grant_refs()) - assert result["has_failed"] is False + assert result['error'] is None assert "page_refs" in result @@ -776,7 +825,7 @@ def test_list_grant_refs_filter_by_effect(authz, grant, deny_grant): asyncio.run(authz.enact(grant)) asyncio.run(authz.enact(deny_grant)) result = asyncio.run(authz.list_grant_refs(effect="allow")) - assert result["has_failed"] is False + assert result['error'] is None def test_list_grant_refs_paginator_async(authz, grant): @@ -785,7 +834,8 @@ def test_list_grant_refs_paginator_async(authz, grant): async def _collect(): all_refs = [] async for page in paginator_async(authz.list_grant_refs): - all_refs.extend(page["page_refs"]) + all_refs.extend(page['page_refs']) + return all_refs all_refs = asyncio.run(_collect()) @@ -794,50 +844,64 @@ async def _collect(): def test_repeal_grant(authz, grant): asyncio.run(authz.enact(grant)) - result = asyncio.run(authz.repeal(grant_uuid=grant["grant_uuid"], purge=False)) - assert result["has_failed"] is False - get_result = asyncio.run(authz.get_grant(grant_uuid=grant["grant_uuid"])) - assert get_result["grant"] is None - assert get_result["has_failed"] is True + result = asyncio.run( + authz.repeal(grant_uuid=grant['grant_uuid'], purge=False) + ) + assert result['error'] is None + get_result = asyncio.run(authz.get_grant(grant_uuid=grant['grant_uuid'])) + assert get_result['grant'] is None + assert get_result['error'] is not None def test_repeal_grant_purge(authz, grant): asyncio.run(authz.enact(grant)) - result = asyncio.run(authz.repeal(grant_uuid=grant["grant_uuid"], purge=True)) - assert result["has_failed"] is False + result = asyncio.run( + authz.repeal(grant_uuid=grant['grant_uuid'], purge=True) + ) + assert result['error'] is None def test_repeal_grant_not_found(authz): # DictStorage repeal returns has_failed=False even when not found - result = asyncio.run(authz.repeal(grant_uuid="nonexistent-uuid", purge=False)) - assert result["has_failed"] is False + result = asyncio.run( + authz.repeal(grant_uuid="nonexistent-uuid", purge=False) + ) + assert result['error'] is None def test_validate_grant_with_config(authz, grant): result = asyncio.run( - authz.validate_grant(grant, config={ - "authzee": { - "raise_crits": True, - }, - }) + authz.validate_grant( + grant, + config={ + "authzee": { + "raise_errors": True + } + } + ) ) - assert result["has_failed"] is False + assert result['error'] is None def test_enact_with_config(authz, grant): result = asyncio.run( - authz.enact(grant, config={ - "authzee": { - "raise_crits": False, - }, - }) + authz.enact( + grant, + config={ + "authzee": { + "raise_errors": False + } + } + ) ) - assert result["has_failed"] is False + assert result['error'] is None def test_cleanup_latches(authz): - result = asyncio.run(authz.cleanup_latches(before=datetime.datetime(2030, 1, 1))) - assert result["has_failed"] is False + result = asyncio.run( + authz.cleanup_latches(before=datetime.datetime(2030, 1, 1)) + ) + assert result['error'] is None def test_cleanup_latches_with_config(authz): @@ -845,27 +909,30 @@ def test_cleanup_latches_with_config(authz): authz.cleanup_latches( before=datetime.datetime(2030, 1, 1), config={ - "authzee": { - "raise_crits": True, - }, - }, + "authzee": { + "raise_errors": True + } + } ) ) - assert result["has_failed"] is False + assert result['error'] is None def test_authorize_allowed(seeded_authz, auth_request): result = asyncio.run(seeded_authz.authorize(request=auth_request)) - assert result["is_authorized"] is True - assert result["has_failed"] is False - assert result["grant"] is not None - assert isinstance(result["message"], str) + assert result['is_authorized'] is True + assert result['error'] is None + assert result['grant'] is not None + assert isinstance(result['message'], str) def test_authorize_denied_no_matching_grant(seeded_authz, auth_request): - request = {**auth_request, "action": "balloon:pop"} + request = { + **auth_request, + "action": "balloon:pop" + } result = asyncio.run(seeded_authz.authorize(request=request)) - assert result["is_authorized"] is False + assert result['is_authorized'] is False def test_authorize_denied_by_deny_grant(seeded_authz, deny_grant): @@ -875,48 +942,47 @@ def test_authorize_denied_by_deny_grant(seeded_authz, deny_grant): "user": [ { "username": "intern_1", - "department": "Intern", - }, - ], + "department": "Intern" + } + ] }, "action": "balloon:pop", "resource_type": "balloon", "resource": { "color": "blue", - "is_inflated": True, + "is_inflated": True }, - "evaluation_handler": "grant", "context_type": "NONE", - "context": {}, + "context": {} } result = asyncio.run(seeded_authz.authorize(request=request)) - assert result["is_authorized"] is False + assert result['is_authorized'] is False def test_authorize_with_config(seeded_authz, auth_request): result = asyncio.run( seeded_authz.authorize( request=auth_request, config={ - "authzee": { - "raise_crits": True, - }, - } + "authzee": { + "raise_errors": True + } + } ) ) - assert result["is_authorized"] is True + assert result['is_authorized'] is True def test_audit(seeded_authz, auth_request): result = asyncio.run(seeded_authz.audit(request=auth_request)) - assert result["has_failed"] is False - assert "grants" in result + assert result['error'] is None assert "results" in result - assert len(result["grants"]) == len(result["results"]) + assert len(result['results']) > 0 + assert result['results'][0]['grant'] is not None def test_audit_with_applicable_grant(seeded_authz, auth_request): result = asyncio.run(seeded_authz.audit(request=auth_request)) - assert any(r["is_applicable"] for r in result["results"]) + assert any(r['is_applicable'] for r in result['results']) def test_audit_paginator_async(seeded_authz, auth_request): @@ -924,8 +990,9 @@ async def _collect(): all_grants = [] all_results = [] async for page in paginator_async(seeded_authz.audit, request=auth_request): - all_grants.extend(page["grants"]) - all_results.extend(page["results"]) + all_grants.extend([r['grant'] for r in page['results']]) + all_results.extend(page['results']) + return all_grants, all_results all_grants, all_results = asyncio.run(_collect()) @@ -936,47 +1003,53 @@ def test_audit_with_config(seeded_authz, auth_request): result = asyncio.run( seeded_authz.audit( request=auth_request, config={ - "authzee": { - "raise_crits": True, - }, - } + "authzee": { + "raise_errors": True + } + } ) ) - assert result["has_failed"] is False + assert result['error'] is None def test_batch_authorize(seeded_authz, batch_request): - result = asyncio.run(seeded_authz.batch_authorize(batch_request=batch_request)) - assert result["has_failed"] is False - assert "batch_results" in result - assert len(result["batch_results"]) == 2 + result = asyncio.run( + seeded_authz.batch_authorize(batch_request=batch_request) + ) + assert result['error'] is None + assert "batch" in result + assert len(result['batch']) == 2 def test_batch_authorize_all_authorized(seeded_authz, batch_request): - result = asyncio.run(seeded_authz.batch_authorize(batch_request=batch_request)) - for item in result["batch_results"]: - assert item["is_authorized"] is True + result = asyncio.run( + seeded_authz.batch_authorize(batch_request=batch_request) + ) + for item in result['batch']: + assert item['is_authorized'] is True def test_batch_authorize_with_config(seeded_authz, batch_request): result = asyncio.run( seeded_authz.batch_authorize( batch_request=batch_request, config={ - "authzee": { - "raise_crits": True, - }, - } + "authzee": { + "raise_errors": True + } + } ) ) - assert result["has_failed"] is False + assert result['error'] is None def test_batch_audit(seeded_authz, batch_request): - result = asyncio.run(seeded_authz.batch_audit(batch_request=batch_request)) - assert result["has_failed"] is False + result = asyncio.run( + seeded_authz.batch_audit(batch_request=batch_request) + ) + assert result['error'] is None assert "grants" in result - assert "batch_results" in result - assert len(result["batch_results"]) == 2 + assert "batch" in result + assert len(result['batch']) == 2 def test_batch_audit_paginator_async(seeded_authz, batch_request): @@ -984,10 +1057,12 @@ async def _collect(): all_grants = [] all_batch = [] async for page in paginator_async( - seeded_authz.batch_audit, batch_request=batch_request + seeded_authz.batch_audit, + batch_request=batch_request ): - all_grants.extend(page["grants"]) - all_batch.extend(page["batch_results"]) + all_grants.extend(page['grants']) + all_batch.extend(page['batch']) + return all_grants, all_batch all_grants, all_batch = asyncio.run(_collect()) @@ -998,13 +1073,13 @@ def test_batch_audit_with_config(seeded_authz, batch_request): result = asyncio.run( seeded_authz.batch_audit( batch_request=batch_request, config={ - "authzee": { - "raise_crits": True, - }, - } + "authzee": { + "raise_errors": True + } + } ) ) - assert result["has_failed"] is False + assert result['error'] is None def test_instance_level_config(): @@ -1016,22 +1091,22 @@ def test_instance_level_config(): compute_kwargs={}, storage_type=DictStorage, storage_kwargs={ - "storage_dict": storage_dict, + "storage_dict": storage_dict }, config={ "authzee": { - "raise_crits": False, - }, - }, + "raise_errors": False + } + } ) result = asyncio.run(authz.construct()) - assert result["has_failed"] is False + assert result['error'] is None result = asyncio.run(authz.start()) - assert result["has_failed"] is False + assert result['error'] is None -def test_raise_crits_config_raises_on_invalid_def(): - """Test that raise_crits=True raises DefinitionError on invalid validation.""" +def test_raise_errors_config_raises_on_invalid_def(): + """Test that raise_errors=True raises DefinitionError on invalid validation.""" storage_dict = {} authz = AuthzeeAsync( execute=jmespath_execute, @@ -1039,63 +1114,65 @@ def test_raise_crits_config_raises_on_invalid_def(): compute_kwargs={}, storage_type=DictStorage, storage_kwargs={ - "storage_dict": storage_dict, + "storage_dict": storage_dict }, config={ "authzee": { - "raise_crits": True, - }, - }, + "raise_errors": True + } + } ) asyncio.run(authz.construct()) asyncio.run(authz.start()) with pytest.raises(exceptions.DefinitionError): asyncio.run( - authz.validate_context_def({ - "context_type": "BAD", - "schema": "not_a_dict", - }) + authz.validate_context_def( + { + "context_type": "BAD", + "schema": "not_a_dict" + } + ) ) -def test_raise_crits_override_at_call_level(authz): +def test_raise_errors_override_at_call_level(authz): """Test that config override at method level takes precedence.""" with pytest.raises(exceptions.DefinitionError): asyncio.run( authz.validate_context_def( { - "context_type": "BAD", - "schema": "not_a_dict", - }, + "context_type": "BAD", + "schema": "not_a_dict" + }, config={ - "authzee": { - "raise_crits": True, - }, - }, + "authzee": { + "raise_errors": True + } + } ) ) -def test_raise_crits_false_does_not_raise(authz): - """Test that raise_crits=False returns error result without raising.""" +def test_raise_errors_false_does_not_raise(authz): + """Test that raise_errors=False returns error result without raising.""" result = asyncio.run( authz.validate_context_def( { - "context_type": "BAD", - "schema": "not_a_dict", - }, - config={ - "authzee": { - "raise_crits": False, + "context_type": "BAD", + "schema": "not_a_dict" }, - }, + config={ + "authzee": { + "raise_errors": False + } + } ) ) - assert result["has_failed"] is True + assert result['error'] is not None -def test_raise_crits_grant_error(): - """Test that raise_crits raises GrantError on invalid grant validation.""" +def test_raise_errors_grant_error(): + """Test that raise_errors raises GrantError on invalid grant validation.""" storage_dict = {} authz = AuthzeeAsync( execute=jmespath_execute, @@ -1103,20 +1180,18 @@ def test_raise_crits_grant_error(): compute_kwargs={}, storage_type=DictStorage, storage_kwargs={ - "storage_dict": storage_dict, + "storage_dict": storage_dict }, config={ "authzee": { - "raise_crits": True, - }, - }, + "raise_errors": True + } + } ) asyncio.run(authz.construct()) asyncio.run(authz.start()) with pytest.raises(exceptions.GrantError): - asyncio.run(authz.validate_grant({ - "effect": "bad", - })) + asyncio.run(authz.validate_grant({"effect": "bad"})) def test_compute_storage_kwargs_override(): @@ -1128,25 +1203,28 @@ def test_compute_storage_kwargs_override(): compute_kwargs={}, storage_type=DictStorage, storage_kwargs={ - "storage_dict": storage_dict, + "storage_dict": storage_dict }, compute_storage_kwargs={ - "storage_dict": storage_dict, - }, + "storage_dict": storage_dict + } ) asyncio.run(authz.construct()) result = asyncio.run(authz.start()) - assert result["has_failed"] is False + assert result['error'] is None def test_put_context_def_overwrite(authz, context_def): """Putting the same context_type twice should succeed (upsert).""" asyncio.run(authz.put_context_def(context_def)) - updated_def = {**context_def, "schema": { - "type": "object", - }} + updated_def = { + **context_def, + "schema": { + "type": "object" + } + } result = asyncio.run(authz.put_context_def(updated_def)) - assert result["has_failed"] is False + assert result['error'] is None def test_put_identity_def_overwrite(authz, identity_def): @@ -1157,13 +1235,13 @@ def test_put_identity_def_overwrite(authz, identity_def): "type": "object", "properties": { "username": { - "type": "string", - }, - }, - }, + "type": "string" + } + } + } } result = asyncio.run(authz.put_identity_def(updated_def)) - assert result["has_failed"] is False + assert result['error'] is None def test_put_resource_def_overwrite(authz, resource_def): @@ -1174,112 +1252,122 @@ def test_put_resource_def_overwrite(authz, resource_def): "balloon:read", "balloon:inflate", "balloon:pop", - "balloon:tie", - ], + "balloon:tie" + ] } result = asyncio.run(authz.put_resource_def(updated_def)) - assert result["has_failed"] is False + assert result['error'] is None def test_multiple_grants(authz, grant, deny_grant): asyncio.run(authz.enact(grant)) asyncio.run(authz.enact(deny_grant)) result = asyncio.run(authz.list_grants()) - assert len(result["grants"]) == 2 + assert len(result['grants']) == 2 def test_multiple_context_defs(authz): - asyncio.run(authz.put_context_def( - { - "context_type": "A", - "schema": { - "type": "object", - }, - } - )) - asyncio.run(authz.put_context_def( - { - "context_type": "B", - "schema": { - "type": "object", - }, - } - )) + asyncio.run( + authz.put_context_def( + { + "context_type": "A", + "schema": { + "type": "object" + } + } + ) + ) + asyncio.run( + authz.put_context_def( + { + "context_type": "B", + "schema": { + "type": "object" + } + } + ) + ) result = asyncio.run(authz.list_context_defs()) - assert len(result["context_defs"]) == 2 + assert len(result['context_defs']) == 2 def test_multiple_identity_defs(authz): - asyncio.run(authz.put_identity_def( - { - "identity_type": "A", - "schema": { - "type": "object", - }, - } - )) - asyncio.run(authz.put_identity_def( - { - "identity_type": "B", - "schema": { - "type": "object", - }, - } - )) + asyncio.run( + authz.put_identity_def( + { + "identity_type": "A", + "schema": { + "type": "object" + } + } + ) + ) + asyncio.run( + authz.put_identity_def( + { + "identity_type": "B", + "schema": { + "type": "object" + } + } + ) + ) result = asyncio.run(authz.list_identity_defs()) - assert len(result["identity_defs"]) == 2 + assert len(result['identity_defs']) == 2 def test_multiple_resource_defs(authz): - asyncio.run(authz.put_resource_def( - { - "resource_type": "A", - "actions": [ - "A:read", - ], - "schema": { - "type": "object", - }, - } - )) - asyncio.run(authz.put_resource_def( - { - "resource_type": "B", - "actions": [ - "B:read", - ], - "schema": { - "type": "object", - }, - } - )) + asyncio.run( + authz.put_resource_def( + { + "resource_type": "A", + "actions": [ + "A:read" + ], + "schema": { + "type": "object" + } + } + ) + ) + asyncio.run( + authz.put_resource_def( + { + "resource_type": "B", + "actions": [ + "B:read" + ], + "schema": { + "type": "object" + } + } + ) + ) result = asyncio.run(authz.list_resource_defs()) - assert len(result["resource_defs"]) == 2 + assert len(result['resource_defs']) == 2 def test_raise_result_raises_on_critical_definition_error(storage_dict): - """Test that _raise_result raises DefinitionError when raise_crits=True.""" + """Test that _raise_result raises DefinitionError when raise_errors=True.""" a = AuthzeeAsync( execute=jmespath_execute, compute_type=InProcessCompute, compute_kwargs={}, storage_type=DictStorage, storage_kwargs={ - "storage_dict": storage_dict, + "storage_dict": storage_dict }, config={ "authzee": { - "raise_crits": True, - }, - }, + "raise_errors": True + } + } ) asyncio.run(a.construct()) asyncio.run(a.start()) # Try to put an invalid context def - should raise with pytest.raises(exceptions.DefinitionError): - asyncio.run(a.put_context_def({ - "bad": "data", - })) + asyncio.run(a.put_context_def({"bad": "data"})) def test_raise_result_raises_on_critical_resource_not_found(storage_dict): @@ -1290,13 +1378,13 @@ def test_raise_result_raises_on_critical_resource_not_found(storage_dict): compute_kwargs={}, storage_type=DictStorage, storage_kwargs={ - "storage_dict": storage_dict, + "storage_dict": storage_dict }, config={ "authzee": { - "raise_crits": True, - }, - }, + "raise_errors": True + } + } ) asyncio.run(a.construct()) asyncio.run(a.start()) @@ -1304,91 +1392,6 @@ def test_raise_result_raises_on_critical_resource_not_found(storage_dict): asyncio.run(a.get_context_def("NONEXISTENT")) -def test_raise_result_with_critical_errors_key(storage_dict): - """Test _raise_result when result has 'critical_errors' key (authorize path).""" - a = AuthzeeAsync( - execute=jmespath_execute, - compute_type=InProcessCompute, - compute_kwargs={}, - storage_type=DictStorage, - storage_kwargs={ - "storage_dict": storage_dict, - }, - config={ - "authzee": { - "raise_crits": True, - }, - }, - ) - asyncio.run(a.construct()) - asyncio.run(a.start()) - asyncio.run(a.put_context_def({ - "context_type": "NONE", - "schema": { - "type": "object", - "additionalProperties": False, - }, - })) - asyncio.run(a.put_identity_def({ - "identity_type": "user", - "schema": { - "type": "object", - "required": ["username"], - "additionalProperties": False, - "properties": { - "username": { - "type": "string", - }, - }, - }, - })) - asyncio.run(a.put_resource_def({ - "resource_type": "file", - "actions": ["read"], - "schema": { - "type": "object", - "required": ["path"], - "additionalProperties": False, - "properties": { - "path": { - "type": "string", - }, - }, - }, - })) - # Enact a grant with bad query + critical handler - asyncio.run(a.enact({ - "grant_uuid": str(uuid4()), - "name": "Bad Grant", - "description": "", - "tags": {}, - "effect": "allow", - "actions": ["read"], - "query": "bad.[invalid", - "evaluation_handler": "critical", - "equality": True, - "data": {}, - })) - with pytest.raises(exceptions.EvaluationError): - asyncio.run(a.authorize({ - "identities": { - "user": [ - { - "username": "test", - }, - ], - }, - "action": "read", - "resource_type": "file", - "resource": { - "path": "/tmp", - }, - "evaluation_handler": "grant", - "context_type": "NONE", - "context": {}, - })) - - def test_combine_errors_called_during_start(storage_dict): """Start calls _combine_errors internally with compute and storage results.""" a = AuthzeeAsync( @@ -1397,147 +1400,137 @@ def test_combine_errors_called_during_start(storage_dict): compute_kwargs={}, storage_type=DictStorage, storage_kwargs={ - "storage_dict": storage_dict, - }, + "storage_dict": storage_dict + } ) asyncio.run(a.construct()) result = asyncio.run(a.start()) - assert result["has_failed"] is False + assert result['error'] is None def test_authorize_validation_failure(seeded_authz): """authorize with an invalid request returns failure without raising.""" - result = asyncio.run(seeded_authz.authorize({ - "bad": "request", - })) - assert result["has_failed"] is True - assert result["is_authorized"] is False - assert "critical_errors" in result + result = asyncio.run(seeded_authz.authorize({"bad": "request"})) + assert result['error'] is not None + assert result['is_authorized'] is False + assert result['error'] is not None def test_authorize_validation_failure_raises(storage_dict): - """authorize with invalid request and raise_crits=True raises an exception.""" + """authorize with invalid request and raise_errors=True raises an exception.""" a = AuthzeeAsync( execute=jmespath_execute, compute_type=InProcessCompute, compute_kwargs={}, storage_type=DictStorage, storage_kwargs={ - "storage_dict": storage_dict, + "storage_dict": storage_dict }, config={ "authzee": { - "raise_crits": True, - }, - }, + "raise_errors": True + } + } ) asyncio.run(a.construct()) asyncio.run(a.start()) with pytest.raises(Exception): - asyncio.run(a.authorize({ - "bad": "request", - })) + asyncio.run(a.authorize({"bad": "request"})) def test_audit_validation_failure(seeded_authz): """audit with an invalid request returns failure.""" - result = asyncio.run(seeded_authz.audit({ - "bad": "request", - })) - assert result["has_failed"] is True - assert result["grants"] == [] - assert result["results"] == [] + result = asyncio.run(seeded_authz.audit({"bad": "request"})) + assert result['error'] is not None + assert result['results'] == [] + assert result['results'] == [] def test_audit_validation_failure_raises(storage_dict): - """audit with invalid request and raise_crits=True raises an exception.""" + """audit with invalid request and raise_errors=True raises an exception.""" a = AuthzeeAsync( execute=jmespath_execute, compute_type=InProcessCompute, compute_kwargs={}, storage_type=DictStorage, storage_kwargs={ - "storage_dict": storage_dict, + "storage_dict": storage_dict }, config={ "authzee": { - "raise_crits": True, - }, - }, + "raise_errors": True + } + } ) asyncio.run(a.construct()) asyncio.run(a.start()) with pytest.raises(Exception): - asyncio.run(a.audit({ - "bad": "request", - })) + asyncio.run(a.audit({"bad": "request"})) def test_batch_audit_validation_failure(seeded_authz): """batch_audit with an invalid request returns failure.""" - result = asyncio.run(seeded_authz.batch_audit({ - "bad": "request", - })) - assert result["has_failed"] is True - assert result["grants"] == [] - assert result["batch_results"] == [] + result = asyncio.run(seeded_authz.batch_audit({"bad": "request"})) + assert result['error'] is not None + assert result['grants'] == [] + assert result['batch'] == [] def test_batch_audit_validation_failure_raises(storage_dict): - """batch_audit with invalid request and raise_crits=True raises an exception.""" + """batch_audit with invalid request and raise_errors=True raises an exception.""" a = AuthzeeAsync( execute=jmespath_execute, compute_type=InProcessCompute, compute_kwargs={}, storage_type=DictStorage, storage_kwargs={ - "storage_dict": storage_dict, + "storage_dict": storage_dict }, config={ "authzee": { - "raise_crits": True, - }, - }, + "raise_errors": True + } + } ) asyncio.run(a.construct()) asyncio.run(a.start()) with pytest.raises(Exception): - asyncio.run(a.batch_audit({ - "bad": "request", - })) + asyncio.run(a.batch_audit({"bad": "request"})) def test_batch_authorize_validation_failure(seeded_authz): """batch_authorize with an invalid request returns failure.""" - result = asyncio.run(seeded_authz.batch_authorize({ - "bad": "request", - })) - assert result["has_failed"] is True - assert result["batch_results"] == [] + result = asyncio.run( + seeded_authz.batch_authorize( + { + "bad": "request" + } + ) + ) + assert result['error'] is not None + assert result['batch'] == [] def test_batch_authorize_validation_failure_raises(storage_dict): - """batch_authorize with invalid request and raise_crits=True raises an exception.""" + """batch_authorize with invalid request and raise_errors=True raises an exception.""" a = AuthzeeAsync( execute=jmespath_execute, compute_type=InProcessCompute, compute_kwargs={}, storage_type=DictStorage, storage_kwargs={ - "storage_dict": storage_dict, + "storage_dict": storage_dict }, config={ "authzee": { - "raise_crits": True, - }, - }, + "raise_errors": True + } + } ) asyncio.run(a.construct()) asyncio.run(a.start()) with pytest.raises(Exception): - asyncio.run(a.batch_authorize({ - "bad": "request", - })) + asyncio.run(a.batch_authorize({"bad": "request"})) def test_compute_storage_kwargs_override(storage_dict): @@ -1548,15 +1541,15 @@ def test_compute_storage_kwargs_override(storage_dict): compute_kwargs={}, storage_type=DictStorage, storage_kwargs={ - "storage_dict": storage_dict, + "storage_dict": storage_dict }, compute_storage_kwargs={ - "storage_dict": storage_dict, - }, + "storage_dict": storage_dict + } ) asyncio.run(a.construct()) result = asyncio.run(a.start()) - assert result["has_failed"] is False + assert result['error'] is None def test_validate_batch_request_valid(seeded_authz): @@ -1565,35 +1558,42 @@ def test_validate_batch_request_valid(seeded_authz): "user": [ { "username": "balloon_person", - "department": "Balloon Dept", - }, + "department": "Balloon Dept" + } ] }, "action": "balloon:inflate", "resource_type": "balloon", "resource": { "color": "blue", - "is_inflated": False, + "is_inflated": False }, - "evaluation_handler": "grant", "context_type": "NONE", "context": {}, - "batch": [{ + "batch": [ + { "resource": { "color": "red", - "is_inflated": True, - }, - }], + "is_inflated": True + } + } + ] } - result = asyncio.run(seeded_authz.validate_batch_request(batch_request)) - assert result["has_failed"] is False + result = asyncio.run( + seeded_authz.validate_batch_request(batch_request) + ) + assert result['error'] is None def test_validate_batch_request_invalid(seeded_authz): - result = asyncio.run(seeded_authz.validate_batch_request({ - "bad": "data", - })) - assert result["has_failed"] is True + result = asyncio.run( + seeded_authz.validate_batch_request( + { + "bad": "data" + } + ) + ) + assert result['error'] is not None def test_validate_request_valid(seeded_authz): @@ -1602,29 +1602,26 @@ def test_validate_request_valid(seeded_authz): "user": [ { "username": "balloon_person", - "department": "Balloon Dept", - }, + "department": "Balloon Dept" + } ] }, "action": "balloon:inflate", "resource_type": "balloon", "resource": { "color": "blue", - "is_inflated": False, + "is_inflated": False }, - "evaluation_handler": "grant", "context_type": "NONE", - "context": {}, + "context": {} } result = asyncio.run(seeded_authz.validate_request(request)) - assert result["has_failed"] is False + assert result['error'] is None def test_validate_request_invalid(seeded_authz): - result = asyncio.run(seeded_authz.validate_request({ - "bad": "data", - })) - assert result["has_failed"] is True + result = asyncio.run(seeded_authz.validate_request({"bad": "data"})) + assert result['error'] is not None def test_combine_errors_method_via_shutdown(storage_dict): @@ -1635,115 +1632,413 @@ def test_combine_errors_method_via_shutdown(storage_dict): compute_kwargs={}, storage_type=DictStorage, storage_kwargs={ - "storage_dict": storage_dict, - }, + "storage_dict": storage_dict + } ) asyncio.run(a.construct()) asyncio.run(a.start()) # Calling shutdown exercises _combine_errors internally via core.combine_errors result = asyncio.run(a.shutdown()) - assert result["has_failed"] is False + assert result['error'] is None + + +def test_locality_incompatibility_warning(storage_dict): + """Test that an incompatible locality produces an error in the start result.""" + from authzee.compute.in_process_compute import InProcessCompute as _IPC + from authzee.module_locality import ModuleLocality + + class NetworkCompute(_IPC): + """A compute module that reports NETWORK locality.""" + + + async def start( + self, + execute, + storage_type, + storage_kwargs, + config + ): + result = await super().start(execute, storage_type, storage_kwargs, config) + self.locality = ModuleLocality.NETWORK + + return result + + a = AuthzeeAsync( + execute=jmespath_execute, + compute_type=NetworkCompute, + compute_kwargs={}, + storage_type=DictStorage, + storage_kwargs={ + "storage_dict": storage_dict + } + ) + asyncio.run(a.construct()) + result = asyncio.run(a.start()) + # DictStorage has PROCESS locality, NetworkCompute has NETWORK locality + # PROCESS storage is not compatible with NETWORK compute (only NETWORK storage is) + assert ( + result.get("error") is not None + and result['error']['error_type'] == "locality_incompatibility" + ) -def test_combine_errors_instance_method_directly(storage_dict): - """Directly test the _combine_errors private method for coverage.""" +def test_start_compute_error(storage_dict): + """start() returns compute error when compute module fails.""" a = AuthzeeAsync( execute=jmespath_execute, compute_type=InProcessCompute, compute_kwargs={}, storage_type=DictStorage, storage_kwargs={ - "storage_dict": storage_dict, + "storage_dict": storage_dict }, + config={ + "authzee": { + "raise_errors": False + } + } ) - result = { - "has_failed": False, - "errors": { - "a": [ - { - "is_critical": False, - "message": "x", - }, - ], + + async def _test(): + orig_compute_start = InProcessCompute.start + + async def failing_compute_start(self, *args, **kwargs): + self.locality = ModuleLocality.PROCESS + + return { + "error": { + "error_type": "compute", + "message": "compute start failed" + } + } + + InProcessCompute.start = failing_compute_start + try: + result = await a.start() + finally: + InProcessCompute.start = orig_compute_start + + return result + + result = asyncio.run(_test()) + assert result['error'] is not None + assert result['error']['error_type'] == "compute" + + +def test_start_storage_error(storage_dict): + """start() returns storage error when storage module fails.""" + a = AuthzeeAsync( + execute=jmespath_execute, + compute_type=InProcessCompute, + compute_kwargs={}, + storage_type=DictStorage, + storage_kwargs={ + "storage_dict": storage_dict }, - } - new_result = { - "has_failed": True, - "errors": { - "b": [ - { - "is_critical": True, - "message": "y", - }, - ], + config={ + "authzee": { + "raise_errors": False + } + } + ) + + async def _test(): + orig_storage_start = DictStorage.start + + async def failing_storage_start(self, *args, **kwargs): + self.locality = ModuleLocality.PROCESS + + return { + "error": { + "error_type": "storage", + "message": "storage start failed" + } + } + + DictStorage.start = failing_storage_start + try: + result = await a.start() + finally: + DictStorage.start = orig_storage_start + + return result + + result = asyncio.run(_test()) + assert result['error'] is not None + assert result['error']['error_type'] == "storage" + + +def test_shutdown_compute_error(storage_dict): + """shutdown() returns compute error when compute module fails.""" + from unittest.mock import AsyncMock + + a = AuthzeeAsync( + execute=jmespath_execute, + compute_type=InProcessCompute, + compute_kwargs={}, + storage_type=DictStorage, + storage_kwargs={ + "storage_dict": storage_dict }, - } - a._combine_errors(result, new_result) - assert result["has_failed"] is True - assert "a" in result["errors"] - assert "b" in result["errors"] + config={ + "authzee": { + "raise_errors": False + } + } + ) + asyncio.run(a.construct()) + asyncio.run(a.start()) + + async def _test(): + a._compute.shutdown = AsyncMock( + return_value={ + "error": { + "error_type": "compute", + "message": "compute shutdown failed" + } + } + ) + a._storage.shutdown = AsyncMock( + return_value={ + "error": None + } + ) + result = await a.shutdown() + + return result + + result = asyncio.run(_test()) + assert result['error'] is not None + assert result['error']['error_type'] == "compute" + +def test_shutdown_storage_error(storage_dict): + """shutdown() returns storage error when storage module fails.""" + from unittest.mock import AsyncMock -def test_combine_errors_instance_method_merges_existing_keys(storage_dict): - """Test _combine_errors merging into existing error keys.""" a = AuthzeeAsync( execute=jmespath_execute, compute_type=InProcessCompute, compute_kwargs={}, storage_type=DictStorage, storage_kwargs={ - "storage_dict": storage_dict, + "storage_dict": storage_dict }, + config={ + "authzee": { + "raise_errors": False + } + } ) - result = { - "has_failed": False, - "errors": { - "a": [ - { - "is_critical": False, - "message": "1", - }, - ], + asyncio.run(a.construct()) + asyncio.run(a.start()) + + async def _test(): + a._compute.shutdown = AsyncMock( + return_value={ + "error": None + } + ) + a._storage.shutdown = AsyncMock( + return_value={ + "error": { + "error_type": "storage", + "message": "storage shutdown failed" + } + } + ) + result = await a.shutdown() + + return result + + result = asyncio.run(_test()) + assert result['error'] is not None + assert result['error']['error_type'] == "storage" + + +def test_construct_compute_error(storage_dict): + """construct() returns compute error when compute module fails.""" + from unittest.mock import AsyncMock, patch + + a = AuthzeeAsync( + execute=jmespath_execute, + compute_type=InProcessCompute, + compute_kwargs={}, + storage_type=DictStorage, + storage_kwargs={ + "storage_dict": storage_dict }, - } - new_result = { - "has_failed": False, - "errors": { - "a": [ - { - "is_critical": False, - "message": "2", - }, - ], + config={ + "authzee": { + "raise_errors": False + } + } + ) + + async def _test(): + with patch.object( + InProcessCompute, + "construct", + new=AsyncMock( + return_value={ + "error": { + "error_type": "compute", + "message": "compute construct failed" + } + } + ) + ): + with patch.object( + DictStorage, + "construct", + new=AsyncMock( + return_value={ + "error": None + } + ) + ): + result = await a.construct() + + return result + + result = asyncio.run(_test()) + assert result['error'] is not None + assert result['error']['error_type'] == "compute" + + +def test_construct_storage_error(storage_dict): + """construct() returns storage error when storage module fails.""" + from unittest.mock import AsyncMock, patch + + a = AuthzeeAsync( + execute=jmespath_execute, + compute_type=InProcessCompute, + compute_kwargs={}, + storage_type=DictStorage, + storage_kwargs={ + "storage_dict": storage_dict }, - } - a._combine_errors(result, new_result) - assert len(result["errors"]["a"]) == 2 + config={ + "authzee": { + "raise_errors": False + } + } + ) + async def _test(): + with patch.object( + InProcessCompute, + "construct", + new=AsyncMock( + return_value={ + "error": None + } + ) + ): + with patch.object( + DictStorage, + "construct", + new=AsyncMock( + return_value={ + "error": { + "error_type": "storage", + "message": "storage construct failed" + } + } + ) + ): + result = await a.construct() + + return result + + result = asyncio.run(_test()) + assert result['error'] is not None + assert result['error']['error_type'] == "storage" + + +def test_destroy_compute_error(storage_dict): + """destroy() returns compute error when compute module fails.""" + from unittest.mock import AsyncMock -def test_locality_incompatibility_warning(storage_dict): - """Test that an incompatible locality produces an error in the start result.""" - from authzee.compute.in_process_compute import InProcessCompute as _IPC - from authzee.module_locality import ModuleLocality + a = AuthzeeAsync( + execute=jmespath_execute, + compute_type=InProcessCompute, + compute_kwargs={}, + storage_type=DictStorage, + storage_kwargs={ + "storage_dict": storage_dict + }, + config={ + "authzee": { + "raise_errors": False + } + } + ) + asyncio.run(a.construct()) + asyncio.run(a.start()) + + async def _test(): + a._compute.destroy = AsyncMock( + return_value={ + "error": { + "error_type": "compute", + "message": "compute destroy failed" + } + } + ) + a._storage.destroy = AsyncMock( + return_value={ + "error": None + } + ) + result = await a.destroy() + + return result + + result = asyncio.run(_test()) + assert result['error'] is not None + assert result['error']['error_type'] == "compute" - class NetworkCompute(_IPC): - """A compute module that reports NETWORK locality.""" - async def start(self, execute, storage_type, storage_kwargs, config): - result = await super().start(execute, storage_type, storage_kwargs, config) - self.locality = ModuleLocality.NETWORK - return result + +def test_destroy_storage_error(storage_dict): + """destroy() returns storage error when storage module fails.""" + from unittest.mock import AsyncMock a = AuthzeeAsync( execute=jmespath_execute, - compute_type=NetworkCompute, + compute_type=InProcessCompute, compute_kwargs={}, storage_type=DictStorage, storage_kwargs={ - "storage_dict": storage_dict, + "storage_dict": storage_dict }, + config={ + "authzee": { + "raise_errors": False + } + } ) asyncio.run(a.construct()) - result = asyncio.run(a.start()) - # DictStorage has PROCESS locality, NetworkCompute has NETWORK locality - # PROCESS storage is not compatible with NETWORK compute (only NETWORK storage is) - assert "locality_incompatibility" in result.get("errors", {}) + asyncio.run(a.start()) + + async def _test(): + a._compute.destroy = AsyncMock( + return_value={ + "error": None + } + ) + a._storage.destroy = AsyncMock( + return_value={ + "error": { + "error_type": "storage", + "message": "storage destroy failed" + } + } + ) + result = await a.destroy() + + return result + + result = asyncio.run(_test()) + assert result['error'] is not None + assert result['error']['error_type'] == "storage" diff --git a/tests/unit/test_core.py b/tests/unit/test_core.py index eda0c35..d52b58c 100644 --- a/tests/unit/test_core.py +++ b/tests/unit/test_core.py @@ -5,14 +5,13 @@ import pytest from authzee.core import ( + evaluate, + validate_batch_request_schema, validate_context_def, - validate_identity_def, - validate_resource_def, validate_grant, + validate_identity_def, validate_request_schema, - validate_batch_request_schema, - evaluate, - combine_errors, + validate_resource_def ) from authzee.jmespath import jmespath_execute @@ -22,34 +21,30 @@ def test_validate_context_def_valid(): "context_type": "NONE", "schema": { "type": "object", - "additionalProperties": False, - }, + "additionalProperties": False + } } result = validate_context_def(context_def) - assert result["has_failed"] is False - assert result["errors"] == {} + assert result['error'] is None def test_validate_context_def_invalid_schema(): context_def = { - "bad_key": "nope", + "bad_key": "nope" } result = validate_context_def(context_def) - assert result["has_failed"] is True - assert "definition" in result["errors"] + assert result['error'] is not None def test_validate_context_def_schema_not_object_type(): context_def = { "context_type": "NONE", "schema": { - "type": "string", - }, + "type": "string" + } } result = validate_context_def(context_def) - assert result["has_failed"] is True - assert "definition" in result["errors"] - assert "root type of object" in result["errors"]["definition"][0]["message"] + assert "root type of object" in result['error']['message'] def test_validate_identity_def_valid(): @@ -59,34 +54,29 @@ def test_validate_identity_def_valid(): "type": "object", "properties": { "name": { - "type": "string", - }, - }, - }, + "type": "string" + } + } + } } result = validate_identity_def(identity_def) - assert result["has_failed"] is False def test_validate_identity_def_invalid_schema(): identity_def = { - "bad": "data", + "bad": "data" } result = validate_identity_def(identity_def) - assert result["has_failed"] is True - assert "definition" in result["errors"] def test_validate_identity_def_schema_not_object_type(): identity_def = { "identity_type": "user", "schema": { - "type": "array", - }, + "type": "array" + } } result = validate_identity_def(identity_def) - assert result["has_failed"] is True - assert "root type of object" in result["errors"]["definition"][0]["message"] def test_validate_resource_def_valid(): @@ -94,43 +84,38 @@ def test_validate_resource_def_valid(): "resource_type": "file", "actions": [ "read", - "write", + "write" ], "schema": { "type": "object", "properties": { "path": { - "type": "string", - }, - }, - }, + "type": "string" + } + } + } } result = validate_resource_def(resource_def) - assert result["has_failed"] is False def test_validate_resource_def_invalid_schema(): resource_def = { - "bad": "data", + "bad": "data" } result = validate_resource_def(resource_def) - assert result["has_failed"] is True - assert "definition" in result["errors"] def test_validate_resource_def_schema_not_object_type(): resource_def = { "resource_type": "file", "actions": [ - "read", + "read" ], "schema": { - "type": "number", - }, + "type": "number" + } } result = validate_resource_def(resource_def) - assert result["has_failed"] is True - assert "root type of object" in result["errors"]["definition"][0]["message"] def test_validate_grant_valid(): @@ -141,24 +126,21 @@ def test_validate_grant_valid(): "tags": {}, "effect": "allow", "actions": [ - "read", + "read" ], "query": "`true`", - "evaluation_handler": "evaluate", "equality": True, - "data": {}, + "applicable_on_failure": False, + "data": {} } result = validate_grant(grant) - assert result["has_failed"] is False def test_validate_grant_invalid(): grant = { - "bad": "data", + "bad": "data" } result = validate_grant(grant) - assert result["has_failed"] is True - assert "grant" in result["errors"] def test_validate_request_schema_valid(): @@ -166,30 +148,26 @@ def test_validate_request_schema_valid(): "identities": { "user": [ { - "name": "test", - }, - ], + "name": "test" + } + ] }, "action": "read", "resource_type": "file", "resource": { - "path": "/tmp", + "path": "/tmp" }, - "evaluation_handler": "grant", "context_type": "NONE", - "context": {}, + "context": {} } result = validate_request_schema(request) - assert result["has_failed"] is False def test_validate_request_schema_invalid(): request = { - "bad": "data", + "bad": "data" } result = validate_request_schema(request) - assert result["has_failed"] is True - assert "definition" in result["errors"] def test_validate_batch_request_schema_valid(): @@ -197,37 +175,33 @@ def test_validate_batch_request_schema_valid(): "identities": { "user": [ { - "name": "test", - }, - ], + "name": "test" + } + ] }, "action": "read", "resource_type": "file", "resource": { - "path": "/tmp", + "path": "/tmp" }, - "evaluation_handler": "grant", "context_type": "NONE", "context": {}, "batch": [ { "resource": { - "path": "/other", - }, - }, - ], + "path": "/other" + } + } + ] } result = validate_batch_request_schema(batch_request) - assert result["has_failed"] is False def test_validate_batch_request_schema_invalid(): batch_request = { - "bad": "data", + "bad": "data" } result = validate_batch_request_schema(batch_request) - assert result["has_failed"] is True - assert "definition" in result["errors"] def test_evaluate_applicable(): @@ -235,18 +209,17 @@ def test_evaluate_applicable(): "identities": { "user": [ { - "name": "test", - }, - ], + "name": "test" + } + ] }, "action": "read", "resource_type": "file", "resource": { - "path": "/tmp", + "path": "/tmp" }, - "evaluation_handler": "grant", "context_type": "NONE", - "context": {}, + "context": {} } grant = { "grant_uuid": str(uuid4()), @@ -255,16 +228,16 @@ def test_evaluate_applicable(): "tags": {}, "effect": "allow", "actions": [ - "read", + "read" ], "query": "`true`", - "evaluation_handler": "evaluate", "equality": True, - "data": {}, + "applicable_on_failure": False, + "data": {} } - result = evaluate(request, grant, jmespath_execute, only_crits=False) - assert result["is_applicable"] is True - assert result["has_failed"] is False + result = evaluate(request, grant, jmespath_execute) + assert result['is_applicable'] is True + assert result['failure'] is None def test_evaluate_not_applicable(): @@ -272,18 +245,17 @@ def test_evaluate_not_applicable(): "identities": { "user": [ { - "name": "test", - }, - ], + "name": "test" + } + ] }, "action": "read", "resource_type": "file", "resource": { - "path": "/tmp", + "path": "/tmp" }, - "evaluation_handler": "grant", "context_type": "NONE", - "context": {}, + "context": {} } grant = { "grant_uuid": str(uuid4()), @@ -292,115 +264,34 @@ def test_evaluate_not_applicable(): "tags": {}, "effect": "allow", "actions": [ - "read", + "read" ], "query": "`false`", - "evaluation_handler": "evaluate", - "equality": True, - "data": {}, - } - result = evaluate(request, grant, jmespath_execute, only_crits=False) - assert result["is_applicable"] is False - assert result["has_failed"] is False - - -def test_evaluate_query_error_with_error_handler(): - """When evaluation_handler is 'error' on the grant and request uses 'grant', - a query error should produce an error result but not fail critically.""" - request = { - "identities": { - "user": [ - { - "name": "test", - }, - ], - }, - "action": "read", - "resource_type": "file", - "resource": { - "path": "/tmp", - }, - "evaluation_handler": "grant", - "context_type": "NONE", - "context": {}, - } - grant = { - "grant_uuid": str(uuid4()), - "name": "Test", - "description": "", - "tags": {}, - "effect": "allow", - "actions": [ - "read", - ], - "query": "bad_query.[invalid", - "evaluation_handler": "error", - "equality": True, - "data": {}, - } - result = evaluate(request, grant, jmespath_execute, only_crits=False) - assert result["is_applicable"] is False - assert result["has_failed"] is False - assert "evaluation" in result["errors"] - - -def test_evaluate_query_error_with_critical_handler(): - """When evaluation_handler is 'critical', a query error should fail critically.""" - request = { - "identities": { - "user": [ - { - "name": "test", - }, - ], - }, - "action": "read", - "resource_type": "file", - "resource": { - "path": "/tmp", - }, - "evaluation_handler": "grant", - "context_type": "NONE", - "context": {}, - } - grant = { - "grant_uuid": str(uuid4()), - "name": "Test", - "description": "", - "tags": {}, - "effect": "allow", - "actions": [ - "read", - ], - "query": "bad_query.[invalid", - "evaluation_handler": "critical", "equality": True, - "data": {}, + "applicable_on_failure": False, + "data": {} } - result = evaluate(request, grant, jmespath_execute, only_crits=False) - assert result["is_applicable"] is False - assert result["has_failed"] is True - assert "evaluation" in result["errors"] + result = evaluate(request, grant, jmespath_execute) + assert result['is_applicable'] is False -def test_evaluate_query_error_with_error_handler_only_crits(): - """When only_crits is True and handler is 'error', errors should be suppressed.""" +def test_evaluate_query_failure_not_applicable(): + """When a query fails and applicable_on_failure is False, grant is not applicable.""" request = { "identities": { "user": [ { - "name": "test", - }, - ], + "name": "test" + } + ] }, "action": "read", "resource_type": "file", "resource": { - "path": "/tmp", + "path": "/tmp" }, - "evaluation_handler": "grant", "context_type": "NONE", - "context": {}, + "context": {} } grant = { "grant_uuid": str(uuid4()), @@ -409,37 +300,35 @@ def test_evaluate_query_error_with_error_handler_only_crits(): "tags": {}, "effect": "allow", "actions": [ - "read", + "read" ], "query": "bad_query.[invalid", - "evaluation_handler": "error", "equality": True, - "data": {}, + "applicable_on_failure": False, + "data": {} } - result = evaluate(request, grant, jmespath_execute, only_crits=True) - assert result["is_applicable"] is False - assert result["has_failed"] is False - assert result["errors"] == {} + result = evaluate(request, grant, jmespath_execute) + assert result['failure'] is not None + assert "JMESPath Query error" in result['failure'] -def test_evaluate_request_evaluation_handler_overrides_grant(): - """When request evaluation_handler is not 'grant', it overrides the grant's handler.""" +def test_evaluate_query_failure_applicable_on_failure(): + """When a query fails and applicable_on_failure is True, grant is still applicable.""" request = { "identities": { "user": [ { - "name": "test", - }, - ], + "name": "test" + } + ] }, "action": "read", "resource_type": "file", "resource": { - "path": "/tmp", + "path": "/tmp" }, - "evaluation_handler": "critical", "context_type": "NONE", - "context": {}, + "context": {} } grant = { "grant_uuid": str(uuid4()), @@ -448,131 +337,11 @@ def test_evaluate_request_evaluation_handler_overrides_grant(): "tags": {}, "effect": "allow", "actions": [ - "read", + "read" ], "query": "bad_query.[invalid", - "evaluation_handler": "error", "equality": True, - "data": {}, - } - result = evaluate(request, grant, jmespath_execute, only_crits=False) - assert result["has_failed"] is True - assert "evaluation" in result["errors"] - - -def test_combine_errors_empty(): - result = { - "has_failed": False, - "errors": {}, - } - combine_errors(result) - assert result["has_failed"] is False - assert result["errors"] == {} - - -def test_combine_errors_merges_new_keys(): - result = { - "has_failed": False, - "errors": { - "definition": [ - { - "is_critical": False, - "message": "a", - }, - ], - }, - } - new_result = { - "has_failed": False, - "errors": { - "grant": [ - { - "is_critical": False, - "message": "b", - }, - ], - }, - } - combine_errors(result, new_result) - assert "definition" in result["errors"] - assert "grant" in result["errors"] - - -def test_combine_errors_merges_existing_keys(): - result = { - "has_failed": False, - "errors": { - "definition": [ - { - "is_critical": False, - "message": "a", - }, - ], - }, - } - new_result = { - "has_failed": False, - "errors": { - "definition": [ - { - "is_critical": False, - "message": "b", - }, - ], - }, - } - combine_errors(result, new_result) - assert len(result["errors"]["definition"]) == 2 - - -def test_combine_errors_propagates_failure(): - result = { - "has_failed": False, - "errors": {}, - } - new_result = { - "has_failed": True, - "errors": { - "grant": [ - { - "is_critical": True, - "message": "fail", - }, - ], - }, - } - combine_errors(result, new_result) - assert result["has_failed"] is True - - -def test_combine_errors_multiple_args(): - result = { - "has_failed": False, - "errors": {}, - } - r1 = { - "has_failed": False, - "errors": { - "a": [ - { - "is_critical": False, - "message": "1", - }, - ], - }, - } - r2 = { - "has_failed": True, - "errors": { - "b": [ - { - "is_critical": True, - "message": "2", - }, - ], - }, + "applicable_on_failure": True, + "data": {} } - combine_errors(result, r1, r2) - assert result["has_failed"] is True - assert "a" in result["errors"] - assert "b" in result["errors"] + result = evaluate(request, grant, jmespath_execute) diff --git a/tests/unit/test_dict_storage.py b/tests/unit/test_dict_storage.py index 92c8f4f..675b5d0 100644 --- a/tests/unit/test_dict_storage.py +++ b/tests/unit/test_dict_storage.py @@ -73,7 +73,9 @@ def test_storage_module_put_context_def_raises(): def test_storage_module_delete_context_def_raises(): sm = StorageModule() with pytest.raises(TypeError): - asyncio.run(sm.delete_context_def(context_type="x", config={})) + asyncio.run( + sm.delete_context_def(context_type="x", config={}) + ) def test_storage_module_list_identity_defs_raises(): @@ -85,7 +87,9 @@ def test_storage_module_list_identity_defs_raises(): def test_storage_module_get_identity_def_raises(): sm = StorageModule() with pytest.raises(TypeError): - asyncio.run(sm.get_identity_def(identity_type="x", config={})) + asyncio.run( + sm.get_identity_def(identity_type="x", config={}) + ) def test_storage_module_put_identity_def_raises(): @@ -97,7 +101,9 @@ def test_storage_module_put_identity_def_raises(): def test_storage_module_delete_identity_def_raises(): sm = StorageModule() with pytest.raises(TypeError): - asyncio.run(sm.delete_identity_def(identity_type="x", config={})) + asyncio.run( + sm.delete_identity_def(identity_type="x", config={}) + ) def test_storage_module_list_resource_defs_raises(): @@ -109,7 +115,9 @@ def test_storage_module_list_resource_defs_raises(): def test_storage_module_get_resource_def_raises(): sm = StorageModule() with pytest.raises(TypeError): - asyncio.run(sm.get_resource_def(resource_type="x", config={})) + asyncio.run( + sm.get_resource_def(resource_type="x", config={}) + ) def test_storage_module_put_resource_def_raises(): @@ -121,7 +129,9 @@ def test_storage_module_put_resource_def_raises(): def test_storage_module_delete_resource_def_raises(): sm = StorageModule() with pytest.raises(TypeError): - asyncio.run(sm.delete_resource_def(resource_type="x", config={})) + asyncio.run( + sm.delete_resource_def(resource_type="x", config={}) + ) def test_storage_module_enact_raises(): @@ -137,7 +147,7 @@ def test_storage_module_repeal_raises(): sm.repeal( grant_uuid="x", purge=False, - config={}, + config={} ) ) @@ -156,7 +166,7 @@ def test_storage_module_list_grants_raises(): effect=None, action=None, page_ref=None, - config={}, + config={} ) ) @@ -169,7 +179,7 @@ def test_storage_module_list_grant_refs_raises(): effect=None, action=None, page_ref=None, - config={}, + config={} ) ) @@ -195,33 +205,40 @@ def test_storage_module_set_latch_raises(): def test_storage_module_delete_latch_raises(): sm = StorageModule() with pytest.raises(TypeError): - asyncio.run(sm.delete_latch(storage_latch_uuid="x", config={})) + asyncio.run( + sm.delete_latch(storage_latch_uuid="x", config={}) + ) def test_storage_module_cleanup_latches_raises(): sm = StorageModule() with pytest.raises(TypeError): - asyncio.run(sm.cleanup_latches(before=datetime.datetime.now(), config={})) + asyncio.run( + sm.cleanup_latches( + before=datetime.datetime.now(), + config={} + ) + ) def test_dict_storage_start(storage_dict): s = DictStorage(storage_dict=storage_dict) asyncio.run(s.construct(config={})) result = asyncio.run(s.start(config={})) - assert result["has_failed"] is False + assert result['error'] is None assert s.locality == ModuleLocality.PROCESS assert s.has_parallel_paging is True def test_dict_storage_shutdown(storage): result = asyncio.run(storage.shutdown(config={})) - assert result["has_failed"] is False + assert result['error'] is None def test_dict_storage_construct(storage_dict): s = DictStorage(storage_dict=storage_dict) result = asyncio.run(s.construct(config={})) - assert result["has_failed"] is False + assert result['error'] is None assert "context_defs_lut" in storage_dict assert "identity_defs_lut" in storage_dict assert "resource_defs_lut" in storage_dict @@ -231,7 +248,7 @@ def test_dict_storage_construct(storage_dict): def test_dict_storage_destroy(storage, storage_dict): result = asyncio.run(storage.destroy(config={})) - assert result["has_failed"] is False + assert result['error'] is None assert "context_defs_lut" not in storage_dict @@ -239,19 +256,19 @@ def test_dict_storage_put_and_get_context_def(storage): context_def = { "context_type": "NONE", "schema": { - "type": "object", - }, + "type": "object" + } } asyncio.run(storage.put_context_def(context_def, config={})) result = asyncio.run(storage.get_context_def("NONE", config={})) - assert result["has_failed"] is False - assert result["context_def"] == context_def + assert result['error'] is None + assert result['context_def'] == context_def def test_dict_storage_get_context_def_not_found(storage): result = asyncio.run(storage.get_context_def("MISSING", config={})) - assert result["has_failed"] is True - assert result["context_def"] is None + assert result['error'] is not None + assert result['context_def'] is None def test_dict_storage_list_context_defs(storage): @@ -260,10 +277,10 @@ def test_dict_storage_list_context_defs(storage): { "context_type": "A", "schema": { - "type": "object", - }, + "type": "object" + } }, - config={}, + config={} ) ) asyncio.run( @@ -271,21 +288,23 @@ def test_dict_storage_list_context_defs(storage): { "context_type": "B", "schema": { - "type": "object", - }, + "type": "object" + } }, - config={}, + config={} ) ) result = asyncio.run( storage.list_context_defs( page_ref=None, - config={"page_size": 10}, + config={ + "page_size": 10 + } ) ) - assert result["has_failed"] is False - assert len(result["context_defs"]) == 2 - assert result["next_page_ref"] is None + assert result['error'] is None + assert len(result['context_defs']) == 2 + assert result['next_page_ref'] is None def test_dict_storage_list_context_defs_pagination(storage): @@ -295,27 +314,32 @@ def test_dict_storage_list_context_defs_pagination(storage): { "context_type": f"T{i}", "schema": { - "type": "object", - }, + "type": "object" + } }, - config={}, + config={} ) ) + result = asyncio.run( storage.list_context_defs( page_ref=None, - config={"page_size": 2}, + config={ + "page_size": 2 + } ) ) - assert len(result["context_defs"]) == 2 - assert result["next_page_ref"] is not None + assert len(result['context_defs']) == 2 + assert result['next_page_ref'] is not None result2 = asyncio.run( storage.list_context_defs( - page_ref=result["next_page_ref"], - config={"page_size": 2}, + page_ref=result['next_page_ref'], + config={ + "page_size": 2 + } ) ) - assert len(result2["context_defs"]) == 2 + assert len(result2['context_defs']) == 2 def test_dict_storage_delete_context_def(storage): @@ -324,35 +348,37 @@ def test_dict_storage_delete_context_def(storage): { "context_type": "DEL", "schema": { - "type": "object", - }, + "type": "object" + } }, - config={}, + config={} ) ) result = asyncio.run(storage.delete_context_def("DEL", config={})) - assert result["has_failed"] is False + assert result['error'] is None get_result = asyncio.run(storage.get_context_def("DEL", config={})) - assert get_result["context_def"] is None + assert get_result['context_def'] is None def test_dict_storage_put_and_get_identity_def(storage): identity_def = { "identity_type": "user", "schema": { - "type": "object", - }, + "type": "object" + } } - asyncio.run(storage.put_identity_def(identity_def, config={})) + asyncio.run( + storage.put_identity_def(identity_def, config={}) + ) result = asyncio.run(storage.get_identity_def("user", config={})) - assert result["has_failed"] is False - assert result["identity_def"] == identity_def + assert result['error'] is None + assert result['identity_def'] == identity_def def test_dict_storage_get_identity_def_not_found(storage): result = asyncio.run(storage.get_identity_def("MISSING", config={})) - assert result["has_failed"] is True - assert result["identity_def"] is None + assert result['error'] is not None + assert result['identity_def'] is None def test_dict_storage_list_identity_defs(storage): @@ -361,19 +387,21 @@ def test_dict_storage_list_identity_defs(storage): { "identity_type": "A", "schema": { - "type": "object", - }, + "type": "object" + } }, - config={}, + config={} ) ) result = asyncio.run( storage.list_identity_defs( page_ref=None, - config={"page_size": 10}, + config={ + "page_size": 10 + } ) ) - assert len(result["identity_defs"]) == 1 + assert len(result['identity_defs']) == 1 def test_dict_storage_list_identity_defs_pagination(storage): @@ -383,27 +411,32 @@ def test_dict_storage_list_identity_defs_pagination(storage): { "identity_type": f"T{i}", "schema": { - "type": "object", - }, + "type": "object" + } }, - config={}, + config={} ) ) + result = asyncio.run( storage.list_identity_defs( page_ref=None, - config={"page_size": 2}, + config={ + "page_size": 2 + } ) ) - assert len(result["identity_defs"]) == 2 - assert result["next_page_ref"] is not None + assert len(result['identity_defs']) == 2 + assert result['next_page_ref'] is not None result2 = asyncio.run( storage.list_identity_defs( - page_ref=result["next_page_ref"], - config={"page_size": 2}, + page_ref=result['next_page_ref'], + config={ + "page_size": 2 + } ) ) - assert len(result2["identity_defs"]) == 2 + assert len(result2['identity_defs']) == 2 def test_dict_storage_delete_identity_def(storage): @@ -412,37 +445,39 @@ def test_dict_storage_delete_identity_def(storage): { "identity_type": "DEL", "schema": { - "type": "object", - }, + "type": "object" + } }, - config={}, + config={} ) ) asyncio.run(storage.delete_identity_def("DEL", config={})) result = asyncio.run(storage.get_identity_def("DEL", config={})) - assert result["identity_def"] is None + assert result['identity_def'] is None def test_dict_storage_put_and_get_resource_def(storage): resource_def = { "resource_type": "file", "actions": [ - "read", + "read" ], "schema": { - "type": "object", - }, + "type": "object" + } } - asyncio.run(storage.put_resource_def(resource_def, config={})) + asyncio.run( + storage.put_resource_def(resource_def, config={}) + ) result = asyncio.run(storage.get_resource_def("file", config={})) - assert result["has_failed"] is False - assert result["resource_def"] == resource_def + assert result['error'] is None + assert result['resource_def'] == resource_def def test_dict_storage_get_resource_def_not_found(storage): result = asyncio.run(storage.get_resource_def("MISSING", config={})) - assert result["has_failed"] is True - assert result["resource_def"] is None + assert result['error'] is not None + assert result['resource_def'] is None def test_dict_storage_list_resource_defs(storage): @@ -451,22 +486,24 @@ def test_dict_storage_list_resource_defs(storage): { "resource_type": "A", "actions": [ - "x", + "x" ], "schema": { - "type": "object", - }, + "type": "object" + } }, - config={}, + config={} ) ) result = asyncio.run( storage.list_resource_defs( page_ref=None, - config={"page_size": 10}, + config={ + "page_size": 10 + } ) ) - assert len(result["resource_defs"]) == 1 + assert len(result['resource_defs']) == 1 def test_dict_storage_list_resource_defs_pagination(storage): @@ -476,30 +513,35 @@ def test_dict_storage_list_resource_defs_pagination(storage): { "resource_type": f"T{i}", "actions": [ - "x", + "x" ], "schema": { - "type": "object", - }, + "type": "object" + } }, - config={}, + config={} ) ) + result = asyncio.run( storage.list_resource_defs( page_ref=None, - config={"page_size": 2}, + config={ + "page_size": 2 + } ) ) - assert len(result["resource_defs"]) == 2 - assert result["next_page_ref"] is not None + assert len(result['resource_defs']) == 2 + assert result['next_page_ref'] is not None result2 = asyncio.run( storage.list_resource_defs( - page_ref=result["next_page_ref"], - config={"page_size": 2}, + page_ref=result['next_page_ref'], + config={ + "page_size": 2 + } ) ) - assert len(result2["resource_defs"]) == 2 + assert len(result2['resource_defs']) == 2 def test_dict_storage_delete_resource_def(storage): @@ -508,18 +550,18 @@ def test_dict_storage_delete_resource_def(storage): { "resource_type": "DEL", "actions": [ - "x", + "x" ], "schema": { - "type": "object", - }, + "type": "object" + } }, - config={}, + config={} ) ) asyncio.run(storage.delete_resource_def("DEL", config={})) result = asyncio.run(storage.get_resource_def("DEL", config={})) - assert result["resource_def"] is None + assert result['resource_def'] is None @pytest.fixture @@ -531,40 +573,46 @@ def sample_grant(): "tags": {}, "effect": "allow", "actions": [ - "read", + "read" ], "query": "`true`", - "evaluation_handler": "evaluate", "equality": True, - "data": {}, + "applicable_on_failure": False, + "data": {} } def test_dict_storage_enact_and_get_grant(storage, sample_grant): asyncio.run(storage.enact(sample_grant, config={})) - result = asyncio.run(storage.get_grant(sample_grant["grant_uuid"], config={})) - assert result["has_failed"] is False - assert result["grant"] == sample_grant + result = asyncio.run( + storage.get_grant(sample_grant['grant_uuid'], config={}) + ) + assert result['error'] is None + assert result['grant'] == sample_grant def test_dict_storage_get_grant_not_found(storage): - result = asyncio.run(storage.get_grant("nonexistent-uuid", config={})) - assert result["has_failed"] is True - assert result["grant"] is None + result = asyncio.run( + storage.get_grant("nonexistent-uuid", config={}) + ) + assert result['error'] is not None + assert result['grant'] is None def test_dict_storage_repeal(storage, sample_grant): asyncio.run(storage.enact(sample_grant, config={})) result = asyncio.run( storage.repeal( - sample_grant["grant_uuid"], + sample_grant['grant_uuid'], purge=True, - config={}, + config={} ) ) - assert result["has_failed"] is False - get_result = asyncio.run(storage.get_grant(sample_grant["grant_uuid"], config={})) - assert get_result["grant"] is None + assert result['error'] is None + get_result = asyncio.run( + storage.get_grant(sample_grant['grant_uuid'], config={}) + ) + assert get_result['grant'] is None def test_dict_storage_list_grants(storage, sample_grant): @@ -574,10 +622,12 @@ def test_dict_storage_list_grants(storage, sample_grant): effect=None, action=None, page_ref=None, - config={"page_size": 10}, + config={ + "page_size": 10 + } ) ) - assert len(result["grants"]) == 1 + assert len(result['grants']) == 1 def test_dict_storage_list_grants_filter_effect(storage): @@ -588,12 +638,12 @@ def test_dict_storage_list_grants_filter_effect(storage): "tags": {}, "effect": "allow", "actions": [ - "read", + "read" ], "query": "`true`", - "evaluation_handler": "evaluate", "equality": True, - "data": {}, + "applicable_on_failure": False, + "data": {} } deny_grant = { "grant_uuid": str(uuid4()), @@ -602,12 +652,12 @@ def test_dict_storage_list_grants_filter_effect(storage): "tags": {}, "effect": "deny", "actions": [ - "read", + "read" ], "query": "`true`", - "evaluation_handler": "evaluate", "equality": True, - "data": {}, + "applicable_on_failure": False, + "data": {} } asyncio.run(storage.enact(allow_grant, config={})) asyncio.run(storage.enact(deny_grant, config={})) @@ -616,11 +666,13 @@ def test_dict_storage_list_grants_filter_effect(storage): effect="allow", action=None, page_ref=None, - config={"page_size": 10}, + config={ + "page_size": 10 + } ) ) - assert len(result["grants"]) == 1 - assert result["grants"][0]["effect"] == "allow" + assert len(result['grants']) == 1 + assert result['grants'][0]['effect'] == "allow" def test_dict_storage_list_grants_filter_action(storage): @@ -632,12 +684,12 @@ def test_dict_storage_list_grants_filter_action(storage): "effect": "allow", "actions": [ "read", - "write", + "write" ], "query": "`true`", - "evaluation_handler": "evaluate", "equality": True, - "data": {}, + "applicable_on_failure": False, + "data": {} } grant2 = { "grant_uuid": str(uuid4()), @@ -646,12 +698,12 @@ def test_dict_storage_list_grants_filter_action(storage): "tags": {}, "effect": "allow", "actions": [ - "delete", + "delete" ], "query": "`true`", - "evaluation_handler": "evaluate", "equality": True, - "data": {}, + "applicable_on_failure": False, + "data": {} } asyncio.run(storage.enact(grant1, config={})) asyncio.run(storage.enact(grant2, config={})) @@ -660,11 +712,13 @@ def test_dict_storage_list_grants_filter_action(storage): effect=None, action="write", page_ref=None, - config={"page_size": 10}, + config={ + "page_size": 10 + } ) ) - assert len(result["grants"]) == 1 - assert result["grants"][0]["name"] == "G1" + assert len(result['grants']) == 1 + assert result['grants'][0]['name'] == "G1" def test_dict_storage_list_grants_pagination(storage): @@ -676,33 +730,38 @@ def test_dict_storage_list_grants_pagination(storage): "tags": {}, "effect": "allow", "actions": [ - "read", + "read" ], "query": "`true`", - "evaluation_handler": "evaluate", "equality": True, - "data": {}, + "applicable_on_failure": False, + "data": {} } asyncio.run(storage.enact(g, config={})) + result = asyncio.run( storage.list_grants( effect=None, action=None, page_ref=None, - config={"page_size": 2}, + config={ + "page_size": 2 + } ) ) - assert len(result["grants"]) == 2 - assert result["next_page_ref"] is not None + assert len(result['grants']) == 2 + assert result['next_page_ref'] is not None result2 = asyncio.run( storage.list_grants( effect=None, action=None, - page_ref=result["next_page_ref"], - config={"page_size": 2}, + page_ref=result['next_page_ref'], + config={ + "page_size": 2 + } ) ) - assert len(result2["grants"]) == 2 + assert len(result2['grants']) == 2 def test_dict_storage_list_grant_refs(storage): @@ -714,34 +773,39 @@ def test_dict_storage_list_grant_refs(storage): "tags": {}, "effect": "allow", "actions": [ - "read", + "read" ], "query": "`true`", - "evaluation_handler": "evaluate", "equality": True, - "data": {}, + "applicable_on_failure": False, + "data": {} } asyncio.run(storage.enact(g, config={})) + result = asyncio.run( storage.list_grant_refs( effect=None, action=None, page_ref=None, - config={"page_size": 2}, + config={ + "page_size": 2 + } ) ) - assert result["has_failed"] is False - assert len(result["page_refs"]) > 0 - if result["next_page_ref"] is not None: + assert result['error'] is None + assert len(result['page_refs']) > 0 + if result['next_page_ref'] is not None: result2 = asyncio.run( storage.list_grant_refs( effect=None, action=None, - page_ref=str(result["next_page_ref"]), - config={"page_size": 2}, + page_ref=str(result['next_page_ref']), + config={ + "page_size": 2 + } ) ) - assert result2["has_failed"] is False + assert result2['error'] is None def test_dict_storage_list_grant_refs_filter_effect(storage): @@ -752,12 +816,12 @@ def test_dict_storage_list_grant_refs_filter_effect(storage): "tags": {}, "effect": "allow", "actions": [ - "read", + "read" ], "query": "`true`", - "evaluation_handler": "evaluate", "equality": True, - "data": {}, + "applicable_on_failure": False, + "data": {} } asyncio.run(storage.enact(allow_grant, config={})) result = asyncio.run( @@ -765,10 +829,12 @@ def test_dict_storage_list_grant_refs_filter_effect(storage): effect="deny", action=None, page_ref=None, - config={"page_size": 2}, + config={ + "page_size": 2 + } ) ) - assert result["page_refs"] == [0] + assert result['page_refs'] == [0] def test_dict_storage_list_grant_refs_filter_action(storage): @@ -779,12 +845,12 @@ def test_dict_storage_list_grant_refs_filter_action(storage): "tags": {}, "effect": "allow", "actions": [ - "write", + "write" ], "query": "`true`", - "evaluation_handler": "evaluate", "equality": True, - "data": {}, + "applicable_on_failure": False, + "data": {} } asyncio.run(storage.enact(g, config={})) result = asyncio.run( @@ -792,62 +858,74 @@ def test_dict_storage_list_grant_refs_filter_action(storage): effect=None, action="write", page_ref=None, - config={"page_size": 10}, + config={ + "page_size": 10 + } ) ) - assert result["has_failed"] is False + assert result['error'] is None def test_dict_storage_create_and_get_latch(storage): create_result = asyncio.run(storage.create_latch(config={})) - assert create_result["has_failed"] is False - latch = create_result["storage_latch"] - assert latch["is_set"] is False + assert create_result['error'] is None + latch = create_result['storage_latch'] + assert latch['is_set'] is False - get_result = asyncio.run(storage.get_latch(latch["storage_latch_uuid"], config={})) - assert get_result["has_failed"] is False - assert get_result["storage_latch"] == latch + get_result = asyncio.run( + storage.get_latch(latch['storage_latch_uuid'], config={}) + ) + assert get_result['error'] is None + assert get_result['storage_latch'] == latch def test_dict_storage_get_latch_not_found(storage): result = asyncio.run(storage.get_latch("nonexistent", config={})) - assert result["has_failed"] is True + assert result['error'] is not None def test_dict_storage_set_latch(storage): create_result = asyncio.run(storage.create_latch(config={})) - latch_uuid = create_result["storage_latch"]["storage_latch_uuid"] + latch_uuid = create_result['storage_latch']['storage_latch_uuid'] set_result = asyncio.run(storage.set_latch(latch_uuid, config={})) - assert set_result["has_failed"] is False - assert set_result["storage_latch"]["is_set"] is True + assert set_result['error'] is None + assert set_result['storage_latch']['is_set'] is True def test_dict_storage_set_latch_not_found(storage): result = asyncio.run(storage.set_latch("nonexistent", config={})) - assert result["has_failed"] is True + assert result['error'] is not None def test_dict_storage_delete_latch(storage): create_result = asyncio.run(storage.create_latch(config={})) - latch_uuid = create_result["storage_latch"]["storage_latch_uuid"] + latch_uuid = create_result['storage_latch']['storage_latch_uuid'] del_result = asyncio.run(storage.delete_latch(latch_uuid, config={})) - assert del_result["has_failed"] is False + assert del_result['error'] is None get_result = asyncio.run(storage.get_latch(latch_uuid, config={})) - assert get_result["has_failed"] is True + assert get_result['error'] is not None def test_dict_storage_cleanup_latches(storage): asyncio.run(storage.create_latch(config={})) asyncio.run(storage.create_latch(config={})) - future = datetime.datetime.now(tz=datetime.timezone.utc) + datetime.timedelta(seconds=1) - result = asyncio.run(storage.cleanup_latches(before=future, config={})) - assert result["has_failed"] is False - assert len(storage._storage_dict["latches_lut"]) == 0 + future = ( + datetime.datetime.now(tz=datetime.timezone.utc) + + datetime.timedelta(seconds=1) + ) + result = asyncio.run( + storage.cleanup_latches(before=future, config={}) + ) + assert result['error'] is None + assert len(storage._storage_dict['latches_lut']) == 0 def test_dict_storage_cleanup_latches_keeps_recent(storage): asyncio.run(storage.create_latch(config={})) - past = datetime.datetime.now(tz=datetime.timezone.utc) - datetime.timedelta(seconds=10) + past = ( + datetime.datetime.now(tz=datetime.timezone.utc) + - datetime.timedelta(seconds=10) + ) result = asyncio.run(storage.cleanup_latches(before=past, config={})) - assert result["has_failed"] is False - assert len(storage._storage_dict["latches_lut"]) == 1 + assert result['error'] is None + assert len(storage._storage_dict['latches_lut']) == 1 diff --git a/tests/unit/test_exceptions.py b/tests/unit/test_exceptions.py index 9c99019..81cf3bb 100644 --- a/tests/unit/test_exceptions.py +++ b/tests/unit/test_exceptions.py @@ -4,19 +4,18 @@ from authzee.exceptions import ( AuthzeeError, - AuthzeeSpecError, AuthzeeSDKError, + AuthzeeSpecError, + ComputeError, DefinitionError, - EvaluationError, GrantError, - RequestError, LocalityIncompatibilityError, NotImplementedError as AuthzeeNotImplementedError, ParallelPaginationNotSupported, - PageReferenceError, + RequestError, ResourceNotFoundError, - StartError, - _exception_map, + StorageError, + _exception_map ) @@ -27,8 +26,10 @@ def test_authzee_error_is_exception(): def test_authzee_spec_error(): result = { - "has_failed": True, - "errors": {}, + "error": { + "error_type": "definition", + "message": "test message" + } } exc = AuthzeeSpecError("test message", result) assert exc.message == "test message" @@ -38,10 +39,10 @@ def test_authzee_spec_error(): def test_definition_error(): result = { - "has_failed": True, - "errors": { - "definition": [], - }, + "error": { + "error_type": "definition", + "message": "def error" + } } exc = DefinitionError("def error", result) assert isinstance(exc, AuthzeeSpecError) @@ -49,23 +50,12 @@ def test_definition_error(): assert exc.result is result -def test_evaluation_error(): - result = { - "has_failed": True, - "errors": { - "evaluation": [], - }, - } - exc = EvaluationError("eval error", result) - assert isinstance(exc, AuthzeeSpecError) - - def test_grant_error(): result = { - "has_failed": True, - "errors": { - "grant": [], - }, + "error": { + "error_type": "grant", + "message": "grant error" + } } exc = GrantError("grant error", result) assert isinstance(exc, AuthzeeSpecError) @@ -73,10 +63,10 @@ def test_grant_error(): def test_request_error(): result = { - "has_failed": True, - "errors": { - "request": [], - }, + "error": { + "error_type": "request", + "message": "request error" + } } exc = RequestError("request error", result) assert isinstance(exc, AuthzeeSpecError) @@ -84,8 +74,10 @@ def test_request_error(): def test_authzee_sdk_error(): result = { - "has_failed": True, - "errors": {}, + "error": { + "error_type": "compute", + "message": "sdk error" + } } exc = AuthzeeSDKError("sdk error", result) assert exc.message == "sdk error" @@ -94,8 +86,10 @@ def test_authzee_sdk_error(): def test_locality_incompatibility_error(): result = { - "has_failed": True, - "errors": {}, + "error": { + "error_type": "locality_incompatibility", + "message": "locality error" + } } exc = LocalityIncompatibilityError("locality error", result) assert isinstance(exc, AuthzeeSDKError) @@ -103,8 +97,10 @@ def test_locality_incompatibility_error(): def test_not_implemented_error_default_message(): result = { - "has_failed": True, - "errors": {}, + "error": { + "error_type": "not_implemented", + "message": "This method is not implemented." + } } exc = AuthzeeNotImplementedError(result=result) assert "not implemented" in exc.message.lower() @@ -112,8 +108,10 @@ def test_not_implemented_error_default_message(): def test_not_implemented_error_custom_message(): result = { - "has_failed": True, - "errors": {}, + "error": { + "error_type": "not_implemented", + "message": "Custom msg" + } } exc = AuthzeeNotImplementedError("Custom msg", result=result) assert exc.message == "Custom msg" @@ -121,52 +119,59 @@ def test_not_implemented_error_custom_message(): def test_parallel_pagination_not_supported(): result = { - "has_failed": True, - "errors": {}, + "error": { + "error_type": "parallel_pagination_not_supported", + "message": "no parallel" + } } exc = ParallelPaginationNotSupported("no parallel", result) assert isinstance(exc, AuthzeeSDKError) -def test_page_reference_error(): +def test_compute_error(): result = { - "has_failed": True, - "errors": {}, + "error": { + "error_type": "compute", + "message": "compute failed" + } } - exc = PageReferenceError("bad page ref", result) + exc = ComputeError("compute failed", result) assert isinstance(exc, AuthzeeSDKError) -def test_resource_not_found_error(): +def test_storage_error(): result = { - "has_failed": True, - "errors": {}, + "error": { + "error_type": "storage", + "message": "storage failed" + } } - exc = ResourceNotFoundError("not found", result) + exc = StorageError("storage failed", result) assert isinstance(exc, AuthzeeSDKError) -def test_start_error(): +def test_resource_not_found_error(): result = { - "has_failed": True, - "errors": {}, + "error": { + "error_type": "resource_not_found", + "message": "not found" + } } - exc = StartError("start failed", result) - assert isinstance(exc, AuthzeeSDKError) + exc = ResourceNotFoundError("not found", result) + assert isinstance(exc, StorageError) def test_exception_map_contains_expected_keys(): expected_keys = [ "definition", - "evaluation", "grant", "request", "locality_incompatibility", "not_implemented", "parallel_pagination_not_supported", - "page_reference", - "resource_not_found", - "start", + "compute", + "storage", + "resource_not_found" ] for key in expected_keys: assert key in _exception_map diff --git a/tests/unit/test_in_process_compute.py b/tests/unit/test_in_process_compute.py index 106d08c..5183f15 100644 --- a/tests/unit/test_in_process_compute.py +++ b/tests/unit/test_in_process_compute.py @@ -31,8 +31,12 @@ async def setup(): await c.start( execute=jmespath_execute, storage_type=DictStorage, - storage_kwargs={"storage_dict": storage_dict}, - config={"storage": {}}, + storage_kwargs={ + "storage_dict": storage_dict + }, + config={ + "storage": {} + } ) return c @@ -54,10 +58,10 @@ async def seed(): "context_type": "NONE", "schema": { "type": "object", - "additionalProperties": False, - }, + "additionalProperties": False + } }, - config={}, + config={} ) await storage.put_identity_def( { @@ -66,20 +70,20 @@ async def seed(): "type": "object", "required": [ "username", - "department", + "department" ], "additionalProperties": False, "properties": { "username": { - "type": "string", + "type": "string" }, "department": { - "type": "string", - }, - }, - }, + "type": "string" + } + } + } }, - config={}, + config={} ) await storage.put_resource_def( { @@ -87,26 +91,26 @@ async def seed(): "actions": [ "balloon:read", "balloon:inflate", - "balloon:pop", + "balloon:pop" ], "schema": { "type": "object", "required": [ "color", - "is_inflated", + "is_inflated" ], "additionalProperties": False, "properties": { "color": { - "type": "string", + "type": "string" }, "is_inflated": { - "type": "boolean", - }, - }, - }, + "type": "boolean" + } + } + } }, - config={}, + config={} ) await storage.enact( grant={ @@ -117,14 +121,14 @@ async def seed(): "effect": "allow", "actions": [ "balloon:read", - "balloon:inflate", + "balloon:inflate" ], "query": "length(request.identities.user[?department == 'Balloon Dept']) > `0`", - "evaluation_handler": "evaluate", "equality": True, - "data": {}, + "applicable_on_failure": False, + "data": {} }, - config={}, + config={} ) await storage.enact( grant={ @@ -134,14 +138,14 @@ async def seed(): "tags": {}, "effect": "deny", "actions": [ - "balloon:pop", + "balloon:pop" ], "query": "length(request.identities.user[?department == 'Intern']) > `0`", - "evaluation_handler": "evaluate", "equality": True, - "data": {}, + "applicable_on_failure": False, + "data": {} }, - config={}, + config={} ) asyncio.run(seed()) @@ -170,19 +174,25 @@ def test_compute_module_destroy_raises(): def test_compute_module_validate_context_def_raises(): cm = ComputeModule() with pytest.raises(TypeError): - asyncio.run(cm.validate_context_def(context_def={}, config={})) + asyncio.run( + cm.validate_context_def(context_def={}, config={}) + ) def test_compute_module_validate_identity_def_raises(): cm = ComputeModule() with pytest.raises(TypeError): - asyncio.run(cm.validate_identity_def(identity_def={}, config={})) + asyncio.run( + cm.validate_identity_def(identity_def={}, config={}) + ) def test_compute_module_validate_resource_def_raises(): cm = ComputeModule() with pytest.raises(TypeError): - asyncio.run(cm.validate_resource_def(resource_def={}, config={})) + asyncio.run( + cm.validate_resource_def(resource_def={}, config={}) + ) def test_compute_module_validate_grant_raises(): @@ -200,7 +210,9 @@ def test_compute_module_validate_request_raises(): def test_compute_module_validate_batch_request_raises(): cm = ComputeModule() with pytest.raises(TypeError): - asyncio.run(cm.validate_batch_request(batch_request={}, config={})) + asyncio.run( + cm.validate_batch_request(batch_request={}, config={}) + ) def test_compute_module_audit_raises(): @@ -210,7 +222,7 @@ def test_compute_module_audit_raises(): cm.audit( request={}, page_ref=None, - config={}, + config={} ) ) @@ -228,7 +240,7 @@ def test_compute_module_batch_audit_raises(): cm.batch_audit( batch_request={}, page_ref=None, - config={}, + config={} ) ) @@ -248,33 +260,37 @@ async def run(): result = await c.start( execute=jmespath_execute, storage_type=DictStorage, - storage_kwargs={"storage_dict": storage_dict}, - config={"storage": {}}, + storage_kwargs={ + "storage_dict": storage_dict + }, + config={ + "storage": {} + } ) return result result = asyncio.run(run()) - assert result["has_failed"] is False + assert result['error'] is None assert c.locality == ModuleLocality.PROCESS assert c.has_parallel_paging is False def test_in_process_compute_shutdown(compute): result = asyncio.run(compute.shutdown(config={"storage": {}})) - assert result["has_failed"] is False + assert result['error'] is None def test_in_process_compute_construct(storage_dict): c = InProcessCompute() result = asyncio.run(c.construct(config={})) - assert result["has_failed"] is False + assert result['error'] is None def test_in_process_compute_destroy(storage_dict): c = InProcessCompute() result = asyncio.run(c.destroy(config={})) - assert result["has_failed"] is False + assert result['error'] is None def test_in_process_validate_context_def_valid(compute): @@ -284,23 +300,25 @@ def test_in_process_validate_context_def_valid(compute): "context_type": "NONE", "schema": { "type": "object", - "additionalProperties": False, - }, + "additionalProperties": False + } }, - config={}, + config={} ) ) - assert result["has_failed"] is False + assert result['error'] is None def test_in_process_validate_context_def_invalid(compute): result = asyncio.run( compute.validate_context_def( - context_def={"bad": "data"}, - config={}, + context_def={ + "bad": "data" + }, + config={} ) ) - assert result["has_failed"] is True + assert result['error'] is not None def test_in_process_validate_identity_def_valid(compute): @@ -309,23 +327,25 @@ def test_in_process_validate_identity_def_valid(compute): identity_def={ "identity_type": "user", "schema": { - "type": "object", - }, + "type": "object" + } }, - config={}, + config={} ) ) - assert result["has_failed"] is False + assert result['error'] is None def test_in_process_validate_identity_def_invalid(compute): result = asyncio.run( compute.validate_identity_def( - identity_def={"bad": "data"}, - config={}, + identity_def={ + "bad": "data" + }, + config={} ) ) - assert result["has_failed"] is True + assert result['error'] is not None def test_in_process_validate_resource_def_valid(compute): @@ -334,26 +354,28 @@ def test_in_process_validate_resource_def_valid(compute): resource_def={ "resource_type": "file", "actions": [ - "read", + "read" ], "schema": { - "type": "object", - }, + "type": "object" + } }, - config={}, + config={} ) ) - assert result["has_failed"] is False + assert result['error'] is None def test_in_process_validate_resource_def_invalid(compute): result = asyncio.run( compute.validate_resource_def( - resource_def={"bad": "data"}, - config={}, + resource_def={ + "bad": "data" + }, + config={} ) ) - assert result["has_failed"] is True + assert result['error'] is not None def test_in_process_validate_grant_valid(compute): @@ -366,22 +388,29 @@ def test_in_process_validate_grant_valid(compute): "tags": {}, "effect": "allow", "actions": [ - "read", + "read" ], "query": "`true`", - "evaluation_handler": "evaluate", "equality": True, - "data": {}, + "applicable_on_failure": False, + "data": {} }, - config={}, + config={} ) ) - assert result["has_failed"] is False + assert result['error'] is None def test_in_process_validate_grant_invalid(compute): - result = asyncio.run(compute.validate_grant(grant={"bad": "data"}, config={})) - assert result["has_failed"] is True + result = asyncio.run( + compute.validate_grant( + grant={ + "bad": "data" + }, + config={} + ) + ) + assert result['error'] is not None def test_in_process_validate_request_valid(seeded_compute): @@ -390,37 +419,43 @@ def test_in_process_validate_request_valid(seeded_compute): "user": [ { "username": "balloon_person", - "department": "Balloon Dept", - }, - ], + "department": "Balloon Dept" + } + ] }, "action": "balloon:inflate", "resource_type": "balloon", "resource": { "color": "blue", - "is_inflated": False, + "is_inflated": False }, - "evaluation_handler": "grant", "context_type": "NONE", - "context": {}, + "context": {} } config = { "get_context_def": {}, "get_identity_def": {}, - "get_resource_def": {}, + "get_resource_def": {} } - result = asyncio.run(seeded_compute.validate_request(request=request, config=config)) - assert result["has_failed"] is False + result = asyncio.run( + seeded_compute.validate_request( + request=request, + config=config + ) + ) + assert result['error'] is None def test_in_process_validate_request_invalid_schema(seeded_compute): result = asyncio.run( seeded_compute.validate_request( - request={"bad": "data"}, - config={}, + request={ + "bad": "data" + }, + config={} ) ) - assert result["has_failed"] is True + assert result['error'] is not None def test_in_process_validate_request_unknown_context_type(seeded_compute): @@ -429,28 +464,32 @@ def test_in_process_validate_request_unknown_context_type(seeded_compute): "user": [ { "username": "a", - "department": "b", - }, - ], + "department": "b" + } + ] }, "action": "balloon:inflate", "resource_type": "balloon", "resource": { "color": "blue", - "is_inflated": False, + "is_inflated": False }, - "evaluation_handler": "grant", "context_type": "UNKNOWN", - "context": {}, + "context": {} } config = { "get_context_def": {}, "get_identity_def": {}, - "get_resource_def": {}, + "get_resource_def": {} } - result = asyncio.run(seeded_compute.validate_request(request=request, config=config)) - assert result["has_failed"] is True - assert "request" in result["errors"] + result = asyncio.run( + seeded_compute.validate_request( + request=request, + config=config + ) + ) + assert result['error'] is not None + assert result['error'] is not None def test_in_process_validate_request_invalid_context_data(seeded_compute): @@ -459,29 +498,33 @@ def test_in_process_validate_request_invalid_context_data(seeded_compute): "user": [ { "username": "a", - "department": "b", - }, - ], + "department": "b" + } + ] }, "action": "balloon:inflate", "resource_type": "balloon", "resource": { "color": "blue", - "is_inflated": False, + "is_inflated": False }, - "evaluation_handler": "grant", "context_type": "NONE", "context": { - "extra_field": "not allowed", - }, + "extra_field": "not allowed" + } } config = { "get_context_def": {}, "get_identity_def": {}, - "get_resource_def": {}, + "get_resource_def": {} } - result = asyncio.run(seeded_compute.validate_request(request=request, config=config)) - assert result["has_failed"] is True + result = asyncio.run( + seeded_compute.validate_request( + request=request, + config=config + ) + ) + assert result['error'] is not None def test_in_process_validate_request_unknown_resource_type(seeded_compute): @@ -490,27 +533,31 @@ def test_in_process_validate_request_unknown_resource_type(seeded_compute): "user": [ { "username": "a", - "department": "b", - }, - ], + "department": "b" + } + ] }, "action": "balloon:inflate", "resource_type": "UNKNOWN", "resource": { "color": "blue", - "is_inflated": False, + "is_inflated": False }, - "evaluation_handler": "grant", "context_type": "NONE", - "context": {}, + "context": {} } config = { "get_context_def": {}, "get_identity_def": {}, - "get_resource_def": {}, + "get_resource_def": {} } - result = asyncio.run(seeded_compute.validate_request(request=request, config=config)) - assert result["has_failed"] is True + result = asyncio.run( + seeded_compute.validate_request( + request=request, + config=config + ) + ) + assert result['error'] is not None def test_in_process_validate_request_invalid_resource_data(seeded_compute): @@ -519,27 +566,31 @@ def test_in_process_validate_request_invalid_resource_data(seeded_compute): "user": [ { "username": "a", - "department": "b", - }, - ], + "department": "b" + } + ] }, "action": "balloon:inflate", "resource_type": "balloon", "resource": { "color": 123, - "is_inflated": "not_bool", + "is_inflated": "not_bool" }, - "evaluation_handler": "grant", "context_type": "NONE", - "context": {}, + "context": {} } config = { "get_context_def": {}, "get_identity_def": {}, - "get_resource_def": {}, + "get_resource_def": {} } - result = asyncio.run(seeded_compute.validate_request(request=request, config=config)) - assert result["has_failed"] is True + result = asyncio.run( + seeded_compute.validate_request( + request=request, + config=config + ) + ) + assert result['error'] is not None def test_in_process_validate_request_invalid_action(seeded_compute): @@ -548,27 +599,31 @@ def test_in_process_validate_request_invalid_action(seeded_compute): "user": [ { "username": "a", - "department": "b", - }, - ], + "department": "b" + } + ] }, "action": "balloon:NONEXISTENT", "resource_type": "balloon", "resource": { "color": "blue", - "is_inflated": False, + "is_inflated": False }, - "evaluation_handler": "grant", "context_type": "NONE", - "context": {}, + "context": {} } config = { "get_context_def": {}, "get_identity_def": {}, - "get_resource_def": {}, + "get_resource_def": {} } - result = asyncio.run(seeded_compute.validate_request(request=request, config=config)) - assert result["has_failed"] is True + result = asyncio.run( + seeded_compute.validate_request( + request=request, + config=config + ) + ) + assert result['error'] is not None def test_in_process_validate_request_unknown_identity_type(seeded_compute): @@ -577,27 +632,31 @@ def test_in_process_validate_request_unknown_identity_type(seeded_compute): "unknown_id": [ { "username": "a", - "department": "b", - }, - ], + "department": "b" + } + ] }, "action": "balloon:inflate", "resource_type": "balloon", "resource": { "color": "blue", - "is_inflated": False, + "is_inflated": False }, - "evaluation_handler": "grant", "context_type": "NONE", - "context": {}, + "context": {} } config = { "get_context_def": {}, "get_identity_def": {}, - "get_resource_def": {}, + "get_resource_def": {} } - result = asyncio.run(seeded_compute.validate_request(request=request, config=config)) - assert result["has_failed"] is True + result = asyncio.run( + seeded_compute.validate_request( + request=request, + config=config + ) + ) + assert result['error'] is not None def test_in_process_validate_request_invalid_identity_data(seeded_compute): @@ -606,27 +665,31 @@ def test_in_process_validate_request_invalid_identity_data(seeded_compute): "user": [ { "username": 123, - "department": 456, - }, - ], + "department": 456 + } + ] }, "action": "balloon:inflate", "resource_type": "balloon", "resource": { "color": "blue", - "is_inflated": False, + "is_inflated": False }, - "evaluation_handler": "grant", "context_type": "NONE", - "context": {}, + "context": {} } config = { "get_context_def": {}, "get_identity_def": {}, - "get_resource_def": {}, + "get_resource_def": {} } - result = asyncio.run(seeded_compute.validate_request(request=request, config=config)) - assert result["has_failed"] is True + result = asyncio.run( + seeded_compute.validate_request( + request=request, + config=config + ) + ) + assert result['error'] is not None def test_in_process_validate_batch_request_valid(seeded_compute): @@ -635,47 +698,51 @@ def test_in_process_validate_batch_request_valid(seeded_compute): "user": [ { "username": "balloon_person", - "department": "Balloon Dept", - }, - ], + "department": "Balloon Dept" + } + ] }, "action": "balloon:inflate", "resource_type": "balloon", "resource": { "color": "blue", - "is_inflated": False, + "is_inflated": False }, - "evaluation_handler": "grant", "context_type": "NONE", "context": {}, "batch": [ { "resource": { "color": "red", - "is_inflated": True, - }, - }, - ], + "is_inflated": True + } + } + ] } config = { "get_context_def": {}, "get_identity_def": {}, - "get_resource_def": {}, + "get_resource_def": {} } result = asyncio.run( - seeded_compute.validate_batch_request(batch_request=batch_request, config=config) + seeded_compute.validate_batch_request( + batch_request=batch_request, + config=config + ) ) - assert result["has_failed"] is False + assert result['error'] is None def test_in_process_validate_batch_request_invalid_schema(seeded_compute): result = asyncio.run( seeded_compute.validate_batch_request( - batch_request={"bad": "data"}, - config={}, + batch_request={ + "bad": "data" + }, + config={} ) ) - assert result["has_failed"] is True + assert result['error'] is not None def test_in_process_validate_batch_request_invalid_batch_item(seeded_compute): @@ -684,37 +751,39 @@ def test_in_process_validate_batch_request_invalid_batch_item(seeded_compute): "user": [ { "username": "balloon_person", - "department": "Balloon Dept", - }, - ], + "department": "Balloon Dept" + } + ] }, "action": "balloon:inflate", "resource_type": "balloon", "resource": { "color": "blue", - "is_inflated": False, + "is_inflated": False }, - "evaluation_handler": "grant", "context_type": "NONE", "context": {}, "batch": [ { "resource": { "color": 123, - "is_inflated": "bad", - }, - }, - ], + "is_inflated": "bad" + } + } + ] } config = { "get_context_def": {}, "get_identity_def": {}, - "get_resource_def": {}, + "get_resource_def": {} } result = asyncio.run( - seeded_compute.validate_batch_request(batch_request=batch_request, config=config) + seeded_compute.validate_batch_request( + batch_request=batch_request, + config=config + ) ) - assert result["has_failed"] is True + assert result['error'] is not None def test_in_process_audit(seeded_compute): @@ -723,99 +792,40 @@ def test_in_process_audit(seeded_compute): "user": [ { "username": "balloon_person", - "department": "Balloon Dept", - }, - ], - }, - "action": "balloon:inflate", - "resource_type": "balloon", - "resource": { - "color": "blue", - "is_inflated": False, - }, - "evaluation_handler": "grant", - "context_type": "NONE", - "context": {}, - } - config = { - "validate_request": { - "get_context_def": {}, - "get_identity_def": {}, - "get_resource_def": {}, - }, - "list_grants": { - "page_size": 100, - "use_cache": False, - }, - } - result = asyncio.run( - seeded_compute.audit( - request=request, - page_ref=None, - config=config, - ) - ) - assert result["has_failed"] is False - assert len(result["grants"]) > 0 - assert len(result["results"]) > 0 - - -def test_in_process_audit_with_critical_query_error(seeded_compute, storage_dict): - """Test audit with a grant that has a bad query and critical evaluation handler.""" - bad_grant = { - "grant_uuid": str(uuid4()), - "name": "Bad Grant", - "description": "", - "tags": {}, - "effect": "allow", - "actions": [ - "balloon:inflate", - ], - "query": "bad_query.[invalid", - "evaluation_handler": "critical", - "equality": True, - "data": {}, - } - storage_dict["grants_lut"][bad_grant["grant_uuid"]] = bad_grant - - request = { - "identities": { - "user": [ - { - "username": "balloon_person", - "department": "Balloon Dept", - }, - ], + "department": "Balloon Dept" + } + ] }, "action": "balloon:inflate", "resource_type": "balloon", "resource": { "color": "blue", - "is_inflated": False, + "is_inflated": False }, - "evaluation_handler": "grant", "context_type": "NONE", - "context": {}, + "context": {} } config = { "validate_request": { "get_context_def": {}, "get_identity_def": {}, - "get_resource_def": {}, + "get_resource_def": {} }, "list_grants": { "page_size": 100, - "use_cache": False, - }, + "use_cache": False + } } result = asyncio.run( seeded_compute.audit( request=request, page_ref=None, - config=config, + config=config ) ) - assert result["has_failed"] is True + assert result['error'] is None + assert len(result['results']) > 0 + assert result['results'][0]['grant'] is not None def test_in_process_authorize_allowed(seeded_compute): @@ -824,34 +834,35 @@ def test_in_process_authorize_allowed(seeded_compute): "user": [ { "username": "balloon_person", - "department": "Balloon Dept", - }, - ], + "department": "Balloon Dept" + } + ] }, "action": "balloon:inflate", "resource_type": "balloon", "resource": { "color": "blue", - "is_inflated": False, + "is_inflated": False }, - "evaluation_handler": "grant", "context_type": "NONE", - "context": {}, + "context": {} } config = { "validate_request": { "get_context_def": {}, "get_identity_def": {}, - "get_resource_def": {}, + "get_resource_def": {} }, "list_grants": { "page_size": 100, - "use_cache": False, - }, + "use_cache": False + } } - result = asyncio.run(seeded_compute.authorize(request=request, config=config)) - assert result["is_authorized"] is True - assert result["has_failed"] is False + result = asyncio.run( + seeded_compute.authorize(request=request, config=config) + ) + assert result['is_authorized'] is True + assert result['error'] is None def test_in_process_authorize_denied(seeded_compute): @@ -860,34 +871,35 @@ def test_in_process_authorize_denied(seeded_compute): "user": [ { "username": "intern_person", - "department": "Intern", - }, - ], + "department": "Intern" + } + ] }, "action": "balloon:pop", "resource_type": "balloon", "resource": { "color": "blue", - "is_inflated": True, + "is_inflated": True }, - "evaluation_handler": "grant", "context_type": "NONE", - "context": {}, + "context": {} } config = { "validate_request": { "get_context_def": {}, "get_identity_def": {}, - "get_resource_def": {}, + "get_resource_def": {} }, "list_grants": { "page_size": 100, - "use_cache": False, - }, + "use_cache": False + } } - result = asyncio.run(seeded_compute.authorize(request=request, config=config)) - assert result["is_authorized"] is False - assert result["has_failed"] is False + result = asyncio.run( + seeded_compute.authorize(request=request, config=config) + ) + assert result['is_authorized'] is False + assert result['error'] is None def test_in_process_authorize_implicit_deny(seeded_compute): @@ -897,87 +909,36 @@ def test_in_process_authorize_implicit_deny(seeded_compute): "user": [ { "username": "nobody", - "department": "None", - }, - ], - }, - "action": "balloon:inflate", - "resource_type": "balloon", - "resource": { - "color": "blue", - "is_inflated": False, - }, - "evaluation_handler": "grant", - "context_type": "NONE", - "context": {}, - } - config = { - "validate_request": { - "get_context_def": {}, - "get_identity_def": {}, - "get_resource_def": {}, - }, - "list_grants": { - "page_size": 100, - "use_cache": False, - }, - } - result = asyncio.run(seeded_compute.authorize(request=request, config=config)) - assert result["is_authorized"] is False - assert result["has_failed"] is False - assert "implicitly denied" in result["message"] - - -def test_in_process_authorize_critical_error(seeded_compute, storage_dict): - """Test authorize with a grant that has bad query and critical handler.""" - bad_grant = { - "grant_uuid": str(uuid4()), - "name": "Bad Grant", - "description": "", - "tags": {}, - "effect": "deny", - "actions": [ - "balloon:inflate", - ], - "query": "bad_query.[invalid", - "evaluation_handler": "critical", - "equality": True, - "data": {}, - } - storage_dict["grants_lut"][bad_grant["grant_uuid"]] = bad_grant - - request = { - "identities": { - "user": [ - { - "username": "balloon_person", - "department": "Balloon Dept", - }, - ], + "department": "None" + } + ] }, "action": "balloon:inflate", "resource_type": "balloon", "resource": { "color": "blue", - "is_inflated": False, + "is_inflated": False }, - "evaluation_handler": "grant", "context_type": "NONE", - "context": {}, + "context": {} } config = { "validate_request": { "get_context_def": {}, "get_identity_def": {}, - "get_resource_def": {}, + "get_resource_def": {} }, "list_grants": { "page_size": 100, - "use_cache": False, - }, + "use_cache": False + } } - result = asyncio.run(seeded_compute.authorize(request=request, config=config)) - assert result["has_failed"] is True + result = asyncio.run( + seeded_compute.authorize(request=request, config=config) + ) + assert result['is_authorized'] is False + assert result['error'] is None + assert "implicitly denied" in result['message'] def test_in_process_batch_audit(seeded_compute): @@ -986,121 +947,53 @@ def test_in_process_batch_audit(seeded_compute): "user": [ { "username": "balloon_person", - "department": "Balloon Dept", - }, - ], + "department": "Balloon Dept" + } + ] }, "action": "balloon:inflate", "resource_type": "balloon", "resource": { "color": "blue", - "is_inflated": False, + "is_inflated": False }, - "evaluation_handler": "grant", "context_type": "NONE", "context": {}, "batch": [ { "resource": { "color": "red", - "is_inflated": True, - }, + "is_inflated": True + } }, { "resource": { "color": "green", - "is_inflated": False, - }, - }, - ], - } - config = { - "validate_batch_request": { - "get_context_def": {}, - "get_identity_def": {}, - "get_resource_def": {}, - }, - "list_grants": { - "page_size": 100, - "use_cache": False, - }, - } - result = asyncio.run( - seeded_compute.batch_audit( - batch_request=batch_request, - page_ref=None, - config=config, - ) - ) - assert result["has_failed"] is False - assert len(result["batch_results"]) == 2 - - -def test_in_process_batch_audit_critical_error(seeded_compute, storage_dict): - """Test batch_audit with a grant that causes critical error.""" - bad_grant = { - "grant_uuid": str(uuid4()), - "name": "Bad Grant", - "description": "", - "tags": {}, - "effect": "allow", - "actions": [ - "balloon:inflate", - ], - "query": "bad_query.[invalid", - "evaluation_handler": "critical", - "equality": True, - "data": {}, - } - storage_dict["grants_lut"][bad_grant["grant_uuid"]] = bad_grant - - batch_request = { - "identities": { - "user": [ - { - "username": "balloon_person", - "department": "Balloon Dept", - }, - ], - }, - "action": "balloon:inflate", - "resource_type": "balloon", - "resource": { - "color": "blue", - "is_inflated": False, - }, - "evaluation_handler": "grant", - "context_type": "NONE", - "context": {}, - "batch": [ - { - "resource": { - "color": "red", - "is_inflated": True, - }, - }, - ], + "is_inflated": False + } + } + ] } config = { "validate_batch_request": { "get_context_def": {}, "get_identity_def": {}, - "get_resource_def": {}, + "get_resource_def": {} }, "list_grants": { "page_size": 100, - "use_cache": False, - }, + "use_cache": False + } } result = asyncio.run( seeded_compute.batch_audit( batch_request=batch_request, page_ref=None, - config=config, + config=config ) ) - has_failure = any(br["has_failed"] for br in result["batch_results"]) - assert has_failure is True + assert result['error'] is None + assert len(result['batch']) == 2 def test_in_process_batch_authorize_mixed(seeded_compute): @@ -1109,52 +1002,54 @@ def test_in_process_batch_authorize_mixed(seeded_compute): "user": [ { "username": "balloon_person", - "department": "Balloon Dept", - }, - ], + "department": "Balloon Dept" + } + ] }, "action": "balloon:inflate", "resource_type": "balloon", "resource": { "color": "blue", - "is_inflated": False, + "is_inflated": False }, - "evaluation_handler": "grant", "context_type": "NONE", "context": {}, "batch": [ { "resource": { "color": "red", - "is_inflated": True, - }, + "is_inflated": True + } }, { "resource": { "color": "green", - "is_inflated": False, - }, - }, - ], + "is_inflated": False + } + } + ] } config = { "validate_batch_request": { "get_context_def": {}, "get_identity_def": {}, - "get_resource_def": {}, + "get_resource_def": {} }, "list_grants": { "page_size": 100, - "use_cache": False, - }, + "use_cache": False + } } result = asyncio.run( - seeded_compute.batch_authorize(batch_request=batch_request, config=config) + seeded_compute.batch_authorize( + batch_request=batch_request, + config=config + ) ) - assert result["has_failed"] is False - assert len(result["batch_results"]) == 2 - for br in result["batch_results"]: - assert br["is_authorized"] is True + assert result['error'] is None + assert len(result['batch']) == 2 + for br in result['batch']: + assert br['is_authorized'] is True def test_in_process_batch_authorize_deny(seeded_compute): @@ -1164,45 +1059,47 @@ def test_in_process_batch_authorize_deny(seeded_compute): "user": [ { "username": "intern_person", - "department": "Intern", - }, - ], + "department": "Intern" + } + ] }, "action": "balloon:pop", "resource_type": "balloon", "resource": { "color": "blue", - "is_inflated": True, + "is_inflated": True }, - "evaluation_handler": "grant", "context_type": "NONE", "context": {}, "batch": [ { "resource": { "color": "red", - "is_inflated": True, - }, - }, - ], + "is_inflated": True + } + } + ] } config = { "validate_batch_request": { "get_context_def": {}, "get_identity_def": {}, - "get_resource_def": {}, + "get_resource_def": {} }, "list_grants": { "page_size": 100, - "use_cache": False, - }, + "use_cache": False + } } result = asyncio.run( - seeded_compute.batch_authorize(batch_request=batch_request, config=config) + seeded_compute.batch_authorize( + batch_request=batch_request, + config=config + ) ) - assert result["has_failed"] is False - for br in result["batch_results"]: - assert br["is_authorized"] is False + assert result['error'] is None + for br in result['batch']: + assert br['is_authorized'] is False def test_in_process_batch_authorize_implicit_deny(seeded_compute): @@ -1212,131 +1109,95 @@ def test_in_process_batch_authorize_implicit_deny(seeded_compute): "user": [ { "username": "nobody", - "department": "None", - }, - ], - }, - "action": "balloon:inflate", - "resource_type": "balloon", - "resource": { - "color": "blue", - "is_inflated": False, - }, - "evaluation_handler": "grant", - "context_type": "NONE", - "context": {}, - "batch": [ - { - "resource": { - "color": "red", - "is_inflated": True, - }, - }, - ], - } - config = { - "validate_batch_request": { - "get_context_def": {}, - "get_identity_def": {}, - "get_resource_def": {}, - }, - "list_grants": { - "page_size": 100, - "use_cache": False, - }, - } - result = asyncio.run( - seeded_compute.batch_authorize(batch_request=batch_request, config=config) - ) - assert result["has_failed"] is False - for br in result["batch_results"]: - assert br["is_authorized"] is False - assert "implicitly denied" in br["message"] - - -def test_in_process_batch_authorize_critical_error(seeded_compute, storage_dict): - """Batch authorize with a critical query error.""" - bad_grant = { - "grant_uuid": str(uuid4()), - "name": "Bad Grant", - "description": "", - "tags": {}, - "effect": "deny", - "actions": [ - "balloon:inflate", - ], - "query": "bad_query.[invalid", - "evaluation_handler": "critical", - "equality": True, - "data": {}, - } - storage_dict["grants_lut"][bad_grant["grant_uuid"]] = bad_grant - - batch_request = { - "identities": { - "user": [ - { - "username": "balloon_person", - "department": "Balloon Dept", - }, - ], + "department": "None" + } + ] }, "action": "balloon:inflate", "resource_type": "balloon", "resource": { "color": "blue", - "is_inflated": False, + "is_inflated": False }, - "evaluation_handler": "grant", "context_type": "NONE", "context": {}, "batch": [ { "resource": { "color": "red", - "is_inflated": True, - }, - }, - ], + "is_inflated": True + } + } + ] } config = { "validate_batch_request": { "get_context_def": {}, "get_identity_def": {}, - "get_resource_def": {}, + "get_resource_def": {} }, "list_grants": { "page_size": 100, - "use_cache": False, - }, + "use_cache": False + } } result = asyncio.run( - seeded_compute.batch_authorize(batch_request=batch_request, config=config) + seeded_compute.batch_authorize( + batch_request=batch_request, + config=config + ) ) - has_failure = any(br["has_failed"] for br in result["batch_results"]) - assert has_failure is True + assert result['error'] is None + for br in result['batch']: + assert br['is_authorized'] is False + assert "implicitly denied" in br['message'] class FailingStorage(DictStorage): - """A storage class that always returns has_failed=True for list_grants.""" + """A storage class that always returns an error for list_grants.""" - async def list_grants(self, effect, action, page_ref, config): + async def list_grants( + self, + effect, + action, + page_ref, + config + ): return { "grants": [], "next_page_ref": None, - "has_failed": True, - "errors": { - "start": [ - { - "is_critical": True, - "message": "Storage failure", - }, - ], - }, + "error": { + "error_type": "storage", + "message": "forced failure" + } } +class FailOnAllowStorage(DictStorage): + """A storage class that fails only when listing allow grants.""" + + + async def list_grants( + self, + effect, + action, + page_ref, + config + ): + if effect == "allow": + return { + "grants": [], + "next_page_ref": None, + "error": { + "error_type": "storage", + "message": "forced failure" + } + } + + return await super().list_grants(effect, action, page_ref, config) + + @pytest.fixture def failing_compute(storage_dict): """InProcessCompute with a failing storage module.""" @@ -1348,8 +1209,12 @@ async def setup(): await c.start( execute=jmespath_execute, storage_type=DictStorage, - storage_kwargs={"storage_dict": storage_dict}, - config={"storage": {}}, + storage_kwargs={ + "storage_dict": storage_dict + }, + config={ + "storage": {} + } ) failing = FailingStorage(storage_dict=storage_dict) await failing.start(config={}) @@ -1374,10 +1239,10 @@ async def seed(): "context_type": "NONE", "schema": { "type": "object", - "additionalProperties": False, - }, + "additionalProperties": False + } }, - config={}, + config={} ) await storage.put_identity_def( { @@ -1385,38 +1250,38 @@ async def seed(): "schema": { "type": "object", "required": [ - "username", + "username" ], "additionalProperties": False, "properties": { "username": { - "type": "string", - }, - }, - }, + "type": "string" + } + } + } }, - config={}, + config={} ) await storage.put_resource_def( { "resource_type": "file", "actions": [ - "read", + "read" ], "schema": { "type": "object", "required": [ - "path", + "path" ], "additionalProperties": False, "properties": { "path": { - "type": "string", - }, - }, - }, + "type": "string" + } + } + } }, - config={}, + config={} ) asyncio.run(seed()) @@ -1430,38 +1295,37 @@ def test_in_process_audit_storage_failure(seeded_failing_compute): "identities": { "user": [ { - "username": "test", - }, - ], + "username": "test" + } + ] }, "action": "read", "resource_type": "file", "resource": { - "path": "/tmp", + "path": "/tmp" }, - "evaluation_handler": "grant", "context_type": "NONE", - "context": {}, + "context": {} } config = { "validate_request": { "get_context_def": {}, "get_identity_def": {}, - "get_resource_def": {}, + "get_resource_def": {} }, "list_grants": { "page_size": 100, - "use_cache": False, - }, + "use_cache": False + } } result = asyncio.run( seeded_failing_compute.audit( request=request, page_ref=None, - config=config, + config=config ) ) - assert result["has_failed"] is True + assert result['error'] is not None def test_in_process_authorize_storage_failure(seeded_failing_compute): @@ -1470,32 +1334,36 @@ def test_in_process_authorize_storage_failure(seeded_failing_compute): "identities": { "user": [ { - "username": "test", - }, - ], + "username": "test" + } + ] }, "action": "read", "resource_type": "file", "resource": { - "path": "/tmp", + "path": "/tmp" }, - "evaluation_handler": "grant", "context_type": "NONE", - "context": {}, + "context": {} } config = { "validate_request": { "get_context_def": {}, "get_identity_def": {}, - "get_resource_def": {}, + "get_resource_def": {} }, "list_grants": { "page_size": 100, - "use_cache": False, - }, + "use_cache": False + } } - result = asyncio.run(seeded_failing_compute.authorize(request=request, config=config)) - assert result["has_failed"] is True + result = asyncio.run( + seeded_failing_compute.authorize( + request=request, + config=config + ) + ) + assert result['error'] is not None def test_in_process_batch_audit_storage_failure(seeded_failing_compute): @@ -1504,45 +1372,44 @@ def test_in_process_batch_audit_storage_failure(seeded_failing_compute): "identities": { "user": [ { - "username": "test", - }, - ], + "username": "test" + } + ] }, "action": "read", "resource_type": "file", "resource": { - "path": "/tmp", + "path": "/tmp" }, - "evaluation_handler": "grant", "context_type": "NONE", "context": {}, "batch": [ { "resource": { - "path": "/other", - }, - }, - ], + "path": "/other" + } + } + ] } config = { "validate_batch_request": { "get_context_def": {}, "get_identity_def": {}, - "get_resource_def": {}, + "get_resource_def": {} }, "list_grants": { "page_size": 100, - "use_cache": False, - }, + "use_cache": False + } } result = asyncio.run( seeded_failing_compute.batch_audit( batch_request=batch_request, page_ref=None, - config=config, + config=config ) ) - assert result["has_failed"] is True + assert result['error'] is not None def test_in_process_batch_authorize_storage_failure(seeded_failing_compute): @@ -1551,184 +1418,44 @@ def test_in_process_batch_authorize_storage_failure(seeded_failing_compute): "identities": { "user": [ { - "username": "test", - }, - ], + "username": "test" + } + ] }, "action": "read", "resource_type": "file", "resource": { - "path": "/tmp", + "path": "/tmp" }, - "evaluation_handler": "grant", "context_type": "NONE", "context": {}, "batch": [ { "resource": { - "path": "/other", - }, - }, - ], + "path": "/other" + } + } + ] } config = { "validate_batch_request": { "get_context_def": {}, "get_identity_def": {}, - "get_resource_def": {}, + "get_resource_def": {} }, "list_grants": { "page_size": 100, - "use_cache": False, - }, + "use_cache": False + } } result = asyncio.run( seeded_failing_compute.batch_authorize( batch_request=batch_request, - config=config, + config=config ) ) - assert result['critical_errors'] != {} - assert result['critical_errors'] != [] - - -def test_in_process_authorize_allow_grant_critical_error(storage_dict): - """Test authorize where critical error is in the allow grants phase.""" - c = InProcessCompute() - - async def setup_and_run(): - storage = DictStorage(storage_dict=storage_dict) - await storage.construct(config={}) - await c.start( - execute=jmespath_execute, - storage_type=DictStorage, - storage_kwargs={"storage_dict": storage_dict}, - config={"storage": {}}, - ) - await storage.start(config={}) - await storage.put_context_def( - { - "context_type": "NONE", - "schema": { - "type": "object", - "additionalProperties": False, - }, - }, - config={}, - ) - await storage.put_identity_def( - { - "identity_type": "user", - "schema": { - "type": "object", - "required": [ - "username", - ], - "additionalProperties": False, - "properties": { - "username": { - "type": "string", - }, - }, - }, - }, - config={}, - ) - await storage.put_resource_def( - { - "resource_type": "file", - "actions": [ - "read", - ], - "schema": { - "type": "object", - "required": [ - "path", - ], - "additionalProperties": False, - "properties": { - "path": { - "type": "string", - }, - }, - }, - }, - config={}, - ) - await storage.enact( - grant={ - "grant_uuid": str(uuid4()), - "name": "Bad Allow Grant", - "description": "", - "tags": {}, - "effect": "allow", - "actions": [ - "read", - ], - "query": "bad_query.[invalid", - "evaluation_handler": "critical", - "equality": True, - "data": {}, - }, - config={}, - ) - - request = { - "identities": { - "user": [ - { - "username": "test", - }, - ], - }, - "action": "read", - "resource_type": "file", - "resource": { - "path": "/tmp", - }, - "evaluation_handler": "grant", - "context_type": "NONE", - "context": {}, - } - config_val = { - "validate_request": { - "get_context_def": {}, - "get_identity_def": {}, - "get_resource_def": {}, - }, - "list_grants": { - "page_size": 100, - "use_cache": False, - }, - } - - return await c.authorize(request=request, config=config_val) - - result = asyncio.run(setup_and_run()) - assert result["has_failed"] is True - - -class FailOnAllowStorage(DictStorage): - """A storage class that fails only when listing allow grants.""" - - async def list_grants(self, effect, action, page_ref, config): - if effect == "allow": - - return { - "grants": [], - "next_page_ref": None, - "has_failed": True, - "errors": { - "start": [ - { - "is_critical": True, - "message": "Allow storage failure", - }, - ], - }, - } - - return await super().list_grants(effect, action, page_ref, config) + assert result['error'] is not None + assert result['error'] is not None def test_in_process_batch_authorize_allow_phase_storage_failure(storage_dict): @@ -1741,8 +1468,12 @@ async def setup_and_run(): await c.start( execute=jmespath_execute, storage_type=DictStorage, - storage_kwargs={"storage_dict": storage_dict}, - config={"storage": {}}, + storage_kwargs={ + "storage_dict": storage_dict + }, + config={ + "storage": {} + } ) await storage.start(config={}) await storage.put_context_def( @@ -1750,10 +1481,10 @@ async def setup_and_run(): "context_type": "NONE", "schema": { "type": "object", - "additionalProperties": False, - }, + "additionalProperties": False + } }, - config={}, + config={} ) await storage.put_identity_def( { @@ -1761,38 +1492,38 @@ async def setup_and_run(): "schema": { "type": "object", "required": [ - "username", + "username" ], "additionalProperties": False, "properties": { "username": { - "type": "string", - }, - }, - }, + "type": "string" + } + } + } }, - config={}, + config={} ) await storage.put_resource_def( { "resource_type": "file", "actions": [ - "read", + "read" ], "schema": { "type": "object", "required": [ - "path", + "path" ], "additionalProperties": False, "properties": { "path": { - "type": "string", - }, - }, - }, + "type": "string" + } + } + } }, - config={}, + config={} ) failing = FailOnAllowStorage(storage_dict=storage_dict) await failing.start(config={}) @@ -1802,46 +1533,45 @@ async def setup_and_run(): "identities": { "user": [ { - "username": "test", - }, - ], + "username": "test" + } + ] }, "action": "read", "resource_type": "file", "resource": { - "path": "/tmp", + "path": "/tmp" }, - "evaluation_handler": "grant", "context_type": "NONE", "context": {}, "batch": [ { "resource": { - "path": "/other", - }, - }, - ], + "path": "/other" + } + } + ] } config_val = { "validate_batch_request": { "get_context_def": {}, "get_identity_def": {}, - "get_resource_def": {}, + "get_resource_def": {} }, "list_grants": { "page_size": 100, - "use_cache": False, - }, + "use_cache": False + } } return await c.batch_authorize( batch_request=batch_request, - config=config_val, + config=config_val ) result = asyncio.run(setup_and_run()) - assert result['critical_errors'] != {} - assert result['critical_errors'] != [] + assert result['error'] is not None + assert result['error'] is not None def test_in_process_authorize_allow_phase_storage_failure(storage_dict): @@ -1854,8 +1584,12 @@ async def setup_and_run(): await c.start( execute=jmespath_execute, storage_type=DictStorage, - storage_kwargs={"storage_dict": storage_dict}, - config={"storage": {}}, + storage_kwargs={ + "storage_dict": storage_dict + }, + config={ + "storage": {} + } ) await storage.start(config={}) await storage.put_context_def( @@ -1863,10 +1597,10 @@ async def setup_and_run(): "context_type": "NONE", "schema": { "type": "object", - "additionalProperties": False, - }, + "additionalProperties": False + } }, - config={}, + config={} ) await storage.put_identity_def( { @@ -1874,38 +1608,38 @@ async def setup_and_run(): "schema": { "type": "object", "required": [ - "username", + "username" ], "additionalProperties": False, "properties": { "username": { - "type": "string", - }, - }, - }, + "type": "string" + } + } + } }, - config={}, + config={} ) await storage.put_resource_def( { "resource_type": "file", "actions": [ - "read", + "read" ], "schema": { "type": "object", "required": [ - "path", + "path" ], "additionalProperties": False, "properties": { "path": { - "type": "string", - }, - }, - }, + "type": "string" + } + } + } }, - config={}, + config={} ) failing = FailOnAllowStorage(storage_dict=storage_dict) await failing.start(config={}) @@ -1915,35 +1649,34 @@ async def setup_and_run(): "identities": { "user": [ { - "username": "test", - }, - ], + "username": "test" + } + ] }, "action": "read", "resource_type": "file", "resource": { - "path": "/tmp", + "path": "/tmp" }, - "evaluation_handler": "grant", "context_type": "NONE", - "context": {}, + "context": {} } config_val = { "validate_request": { "get_context_def": {}, "get_identity_def": {}, - "get_resource_def": {}, + "get_resource_def": {} }, "list_grants": { "page_size": 100, - "use_cache": False, - }, + "use_cache": False + } } return await c.authorize(request=request, config=config_val) result = asyncio.run(setup_and_run()) - assert result["has_failed"] is True + assert result['error'] is not None def test_in_process_validate_batch_request_base_request_invalid(seeded_compute): @@ -1953,431 +1686,39 @@ def test_in_process_validate_batch_request_base_request_invalid(seeded_compute): "user": [ { "username": "balloon_person", - "department": "Balloon Dept", - }, - ], + "department": "Balloon Dept" + } + ] }, "action": "balloon:inflate", "resource_type": "balloon", "resource": { "color": "blue", - "is_inflated": False, + "is_inflated": False }, - "evaluation_handler": "grant", "context_type": "NONEXISTENT", "context": {}, "batch": [ { "resource": { "color": "red", - "is_inflated": True, - }, - }, - ], + "is_inflated": True + } + } + ] } config = { "get_context_def": {}, "get_identity_def": {}, - "get_resource_def": {}, + "get_resource_def": {} } result = asyncio.run( - seeded_compute.validate_batch_request(batch_request=batch_request, config=config) - ) - assert result["has_failed"] is True - - -def test_in_process_batch_audit_skip_failed_items(storage_dict): - """Test batch_audit where the first grant causes a critical error, - and the second grant should be skipped for that item.""" - c = InProcessCompute() - - async def setup_and_run(): - storage = DictStorage(storage_dict=storage_dict) - await storage.construct(config={}) - await c.start( - execute=jmespath_execute, - storage_type=DictStorage, - storage_kwargs={"storage_dict": storage_dict}, - config={"storage": {}}, - ) - await storage.start(config={}) - await storage.put_context_def( - { - "context_type": "NONE", - "schema": { - "type": "object", - "additionalProperties": False, - }, - }, - config={}, - ) - await storage.put_identity_def( - { - "identity_type": "user", - "schema": { - "type": "object", - "required": [ - "username", - ], - "additionalProperties": False, - "properties": { - "username": { - "type": "string", - }, - }, - }, - }, - config={}, - ) - await storage.put_resource_def( - { - "resource_type": "file", - "actions": [ - "read", - ], - "schema": { - "type": "object", - "required": [ - "path", - ], - "additionalProperties": False, - "properties": { - "path": { - "type": "string", - }, - }, - }, - }, - config={}, - ) - await storage.enact( - grant={ - "grant_uuid": str(uuid4()), - "name": "Bad Grant", - "description": "", - "tags": {}, - "effect": "allow", - "actions": [ - "read", - ], - "query": "bad.[invalid", - "evaluation_handler": "critical", - "equality": True, - "data": {}, - }, - config={}, - ) - await storage.enact( - grant={ - "grant_uuid": str(uuid4()), - "name": "Good Grant", - "description": "", - "tags": {}, - "effect": "allow", - "actions": [ - "read", - ], - "query": "`true`", - "evaluation_handler": "evaluate", - "equality": True, - "data": {}, - }, - config={}, - ) - - batch_request = { - "identities": { - "user": [ - { - "username": "test", - }, - ], - }, - "action": "read", - "resource_type": "file", - "resource": { - "path": "/tmp", - }, - "evaluation_handler": "grant", - "context_type": "NONE", - "context": {}, - "batch": [ - { - "resource": { - "path": "/other", - }, - }, - ], - } - config_val = { - "validate_batch_request": { - "get_context_def": {}, - "get_identity_def": {}, - "get_resource_def": {}, - }, - "list_grants": { - "page_size": 100, - "use_cache": False, - }, - } - - return await c.batch_audit( + seeded_compute.validate_batch_request( batch_request=batch_request, - page_ref=None, - config=config_val, - ) - - result = asyncio.run(setup_and_run()) - has_failure = any(br["has_failed"] for br in result["batch_results"]) - assert has_failure is True - - -def test_in_process_batch_authorize_deny_critical_error(storage_dict): - """Test batch_authorize where deny grant causes critical error.""" - c = InProcessCompute() - - async def setup_and_run(): - storage = DictStorage(storage_dict=storage_dict) - await storage.construct(config={}) - await c.start( - execute=jmespath_execute, - storage_type=DictStorage, - storage_kwargs={"storage_dict": storage_dict}, - config={"storage": {}}, - ) - await storage.start(config={}) - await storage.put_context_def( - { - "context_type": "NONE", - "schema": { - "type": "object", - "additionalProperties": False, - }, - }, - config={}, - ) - await storage.put_identity_def( - { - "identity_type": "user", - "schema": { - "type": "object", - "required": [ - "username", - ], - "additionalProperties": False, - "properties": { - "username": { - "type": "string", - }, - }, - }, - }, - config={}, - ) - await storage.put_resource_def( - { - "resource_type": "file", - "actions": [ - "read", - ], - "schema": { - "type": "object", - "required": [ - "path", - ], - "additionalProperties": False, - "properties": { - "path": { - "type": "string", - }, - }, - }, - }, - config={}, + config=config ) - await storage.enact( - grant={ - "grant_uuid": str(uuid4()), - "name": "Bad Deny Grant", - "description": "", - "tags": {}, - "effect": "deny", - "actions": [ - "read", - ], - "query": "bad.[invalid", - "evaluation_handler": "critical", - "equality": True, - "data": {}, - }, - config={}, - ) - - batch_request = { - "identities": { - "user": [ - { - "username": "test", - }, - ], - }, - "action": "read", - "resource_type": "file", - "resource": { - "path": "/tmp", - }, - "evaluation_handler": "grant", - "context_type": "NONE", - "context": {}, - "batch": [ - { - "resource": { - "path": "/other", - }, - }, - ], - } - config_val = { - "validate_batch_request": { - "get_context_def": {}, - "get_identity_def": {}, - "get_resource_def": {}, - }, - "list_grants": { - "page_size": 100, - "use_cache": False, - }, - } - - return await c.batch_authorize(batch_request=batch_request, config=config_val) - - result = asyncio.run(setup_and_run()) - has_failure = any(br["has_failed"] for br in result["batch_results"]) - assert has_failure is True - - -def test_in_process_batch_authorize_allow_critical_error(storage_dict): - """Test batch_authorize where allow grant causes critical error.""" - c = InProcessCompute() - - async def setup_and_run(): - storage = DictStorage(storage_dict=storage_dict) - await storage.construct(config={}) - await c.start( - execute=jmespath_execute, - storage_type=DictStorage, - storage_kwargs={"storage_dict": storage_dict}, - config={"storage": {}}, - ) - await storage.start(config={}) - await storage.put_context_def( - { - "context_type": "NONE", - "schema": { - "type": "object", - "additionalProperties": False, - }, - }, - config={}, - ) - await storage.put_identity_def( - { - "identity_type": "user", - "schema": { - "type": "object", - "required": [ - "username", - ], - "additionalProperties": False, - "properties": { - "username": { - "type": "string", - }, - }, - }, - }, - config={}, - ) - await storage.put_resource_def( - { - "resource_type": "file", - "actions": [ - "read", - ], - "schema": { - "type": "object", - "required": [ - "path", - ], - "additionalProperties": False, - "properties": { - "path": { - "type": "string", - }, - }, - }, - }, - config={}, - ) - await storage.enact( - grant={ - "grant_uuid": str(uuid4()), - "name": "Bad Allow Grant", - "description": "", - "tags": {}, - "effect": "allow", - "actions": [ - "read", - ], - "query": "bad.[invalid", - "evaluation_handler": "critical", - "equality": True, - "data": {}, - }, - config={}, - ) - - batch_request = { - "identities": { - "user": [ - { - "username": "test", - }, - ], - }, - "action": "read", - "resource_type": "file", - "resource": { - "path": "/tmp", - }, - "evaluation_handler": "grant", - "context_type": "NONE", - "context": {}, - "batch": [ - { - "resource": { - "path": "/other", - }, - }, - ], - } - config_val = { - "validate_batch_request": { - "get_context_def": {}, - "get_identity_def": {}, - "get_resource_def": {}, - }, - "list_grants": { - "page_size": 100, - "use_cache": False, - }, - } - - return await c.batch_authorize(batch_request=batch_request, config=config_val) - - result = asyncio.run(setup_and_run()) - has_failure = any(br["has_failed"] for br in result["batch_results"]) - assert has_failure is True + ) + assert result['error'] is not None def test_in_process_batch_authorize_deny_applicable_continue(storage_dict): @@ -2390,8 +1731,12 @@ async def setup_and_run(): await c.start( execute=jmespath_execute, storage_type=DictStorage, - storage_kwargs={"storage_dict": storage_dict}, - config={"storage": {}}, + storage_kwargs={ + "storage_dict": storage_dict + }, + config={ + "storage": {} + } ) await storage.start(config={}) await storage.put_context_def( @@ -2399,10 +1744,10 @@ async def setup_and_run(): "context_type": "NONE", "schema": { "type": "object", - "additionalProperties": False, - }, + "additionalProperties": False + } }, - config={}, + config={} ) await storage.put_identity_def( { @@ -2410,38 +1755,38 @@ async def setup_and_run(): "schema": { "type": "object", "required": [ - "username", + "username" ], "additionalProperties": False, "properties": { "username": { - "type": "string", - }, - }, - }, + "type": "string" + } + } + } }, - config={}, + config={} ) await storage.put_resource_def( { "resource_type": "file", "actions": [ - "read", + "read" ], "schema": { "type": "object", "required": [ - "path", + "path" ], "additionalProperties": False, "properties": { "path": { - "type": "string", - }, - }, - }, + "type": "string" + } + } + } }, - config={}, + config={} ) await storage.enact( grant={ @@ -2451,14 +1796,14 @@ async def setup_and_run(): "tags": {}, "effect": "deny", "actions": [ - "read", + "read" ], "query": "`true`", - "evaluation_handler": "evaluate", "equality": True, - "data": {}, + "applicable_on_failure": False, + "data": {} }, - config={}, + config={} ) await storage.enact( grant={ @@ -2468,57 +1813,59 @@ async def setup_and_run(): "tags": {}, "effect": "allow", "actions": [ - "read", + "read" ], "query": "`true`", - "evaluation_handler": "evaluate", "equality": True, - "data": {}, + "applicable_on_failure": False, + "data": {} }, - config={}, + config={} ) batch_request = { "identities": { "user": [ { - "username": "test", - }, - ], + "username": "test" + } + ] }, "action": "read", "resource_type": "file", "resource": { - "path": "/tmp", + "path": "/tmp" }, - "evaluation_handler": "grant", "context_type": "NONE", "context": {}, "batch": [ { "resource": { - "path": "/other", - }, - }, - ], + "path": "/other" + } + } + ] } config_val = { "validate_batch_request": { "get_context_def": {}, "get_identity_def": {}, - "get_resource_def": {}, + "get_resource_def": {} }, "list_grants": { "page_size": 100, - "use_cache": False, - }, + "use_cache": False + } } - return await c.batch_authorize(batch_request=batch_request, config=config_val) + return await c.batch_authorize( + batch_request=batch_request, + config=config_val + ) result = asyncio.run(setup_and_run()) - assert result["batch_results"][0]["is_authorized"] is False - assert "deny grant" in result["batch_results"][0]["message"] + assert result['batch'][0]['is_authorized'] is False + assert "deny grant" in result['batch'][0]['message'] def test_in_process_batch_authorize_deny_phase_skip_complete(storage_dict): @@ -2532,8 +1879,12 @@ async def setup_and_run(): await c.start( execute=jmespath_execute, storage_type=DictStorage, - storage_kwargs={"storage_dict": storage_dict}, - config={"storage": {}}, + storage_kwargs={ + "storage_dict": storage_dict + }, + config={ + "storage": {} + } ) await storage.start(config={}) await storage.put_context_def( @@ -2541,10 +1892,10 @@ async def setup_and_run(): "context_type": "NONE", "schema": { "type": "object", - "additionalProperties": False, - }, + "additionalProperties": False + } }, - config={}, + config={} ) await storage.put_identity_def( { @@ -2552,38 +1903,38 @@ async def setup_and_run(): "schema": { "type": "object", "required": [ - "username", + "username" ], "additionalProperties": False, "properties": { "username": { - "type": "string", - }, - }, - }, + "type": "string" + } + } + } }, - config={}, + config={} ) await storage.put_resource_def( { "resource_type": "file", "actions": [ - "read", + "read" ], "schema": { "type": "object", "required": [ - "path", + "path" ], "additionalProperties": False, "properties": { "path": { - "type": "string", - }, - }, - }, + "type": "string" + } + } + } }, - config={}, + config={} ) await storage.enact( grant={ @@ -2593,14 +1944,14 @@ async def setup_and_run(): "tags": {}, "effect": "deny", "actions": [ - "read", + "read" ], "query": "`true`", - "evaluation_handler": "evaluate", "equality": True, - "data": {}, + "applicable_on_failure": False, + "data": {} }, - config={}, + config={} ) await storage.enact( grant={ @@ -2610,53 +1961,55 @@ async def setup_and_run(): "tags": {}, "effect": "deny", "actions": [ - "read", + "read" ], "query": "`true`", - "evaluation_handler": "evaluate", "equality": True, - "data": {}, + "applicable_on_failure": False, + "data": {} }, - config={}, + config={} ) batch_request = { "identities": { "user": [ { - "username": "test", - }, - ], + "username": "test" + } + ] }, "action": "read", "resource_type": "file", "resource": { - "path": "/tmp", + "path": "/tmp" }, - "evaluation_handler": "grant", "context_type": "NONE", "context": {}, "batch": [ { "resource": { - "path": "/other", - }, - }, - ], + "path": "/other" + } + } + ] } config_val = { "validate_batch_request": { "get_context_def": {}, "get_identity_def": {}, - "get_resource_def": {}, + "get_resource_def": {} }, "list_grants": { "page_size": 100, - "use_cache": False, - }, + "use_cache": False + } } - return await c.batch_authorize(batch_request=batch_request, config=config_val) + return await c.batch_authorize( + batch_request=batch_request, + config=config_val + ) result = asyncio.run(setup_and_run()) - assert result["batch_results"][0]["is_authorized"] is False + assert result['batch'][0]['is_authorized'] is False diff --git a/tests/unit/test_jmespath.py b/tests/unit/test_jmespath.py index 20cae90..6a71b43 100644 --- a/tests/unit/test_jmespath.py +++ b/tests/unit/test_jmespath.py @@ -4,70 +4,94 @@ from authzee.jmespath import ( CustomJMESPathFunctions, - jmespath_execute, jmespath_custom_execute, + jmespath_execute ) def test_jmespath_execute_simple_expression(): - result = jmespath_execute("a", {"a": 1, "b": 2}) + result = jmespath_execute( + "a", + { + "a": 1, + "b": 2 + } + ) assert result == { "result": 1, - "has_failed": False, - "error_message": None, + "failure": None } def test_jmespath_execute_returns_none_for_missing_key(): - result = jmespath_execute("z", {"a": 1}) - assert result["result"] is None - assert result["has_failed"] is False + result = jmespath_execute( + "z", + { + "a": 1 + } + ) + assert result['result'] is None + assert result['failure'] is None def test_jmespath_execute_invalid_expression(): - result = jmespath_execute("a.[invalid", {"a": 1}) - assert result["has_failed"] is True - assert result["result"] is None - assert "JMESPath Query error" in result["error_message"] + result = jmespath_execute( + "a.[invalid", + { + "a": 1 + } + ) + assert result['failure'] is not None + assert result['result'] is None + assert "JMESPath Query error" in result['failure'] def test_jmespath_execute_nested(): data = { "a": { "b": { - "c": 42, - }, - }, + "c": 42 + } + } } result = jmespath_execute("a.b.c", data) - assert result["result"] == 42 - assert result["has_failed"] is False + assert result['result'] == 42 + assert result['failure'] is None def test_jmespath_custom_execute_simple(): - result = jmespath_custom_execute("a", {"a": "hello"}) + result = jmespath_custom_execute( + "a", + { + "a": "hello" + } + ) assert result == { "result": "hello", - "has_failed": False, - "error_message": None, + "failure": None } def test_jmespath_custom_execute_invalid_expression(): - result = jmespath_custom_execute("a.[invalid", {"a": 1}) - assert result["has_failed"] is True - assert result["result"] is None - assert "JMESPath Query error" in result["error_message"] + result = jmespath_custom_execute( + "a.[invalid", + { + "a": 1 + } + ) + assert result['failure'] is not None + assert result['result'] is None + assert "JMESPath Query error" in result['failure'] def test_custom_lower(): result = jmespath_custom_execute("lower('HELLO')", {}) - assert result["result"] == "hello" + assert result['result'] == "hello" def test_custom_upper(): result = jmespath_custom_execute("upper('hello')", {}) - assert result["result"] == "HELLO" + assert result['result'] == "HELLO" def test_inner_join_basic(): @@ -75,38 +99,46 @@ def test_inner_join_basic(): "lhs_arr": [ 1, 2, - 3, + 3 ], "rhs_arr": [ 2, 3, - 4, - ], + 4 + ] } result = jmespath_custom_execute( - "inner_join(lhs_arr, rhs_arr, 'lhs == rhs')", data + "inner_join(lhs_arr, rhs_arr, 'lhs == rhs')", + data ) - assert result["has_failed"] is False - joined = result["result"] + assert result['failure'] is None + joined = result['result'] assert len(joined) == 2 - assert {"lhs": 2, "rhs": 2} in joined - assert {"lhs": 3, "rhs": 3} in joined + assert { + "lhs": 2, + "rhs": 2 + } in joined + assert { + "lhs": 3, + "rhs": 3 + } in joined def test_inner_join_no_matches(): data = { "lhs_arr": [ - 1, + 1 ], "rhs_arr": [ - 2, - ], + 2 + ] } result = jmespath_custom_execute( - "inner_join(lhs_arr, rhs_arr, 'lhs == rhs')", data + "inner_join(lhs_arr, rhs_arr, 'lhs == rhs')", + data ) - assert result["has_failed"] is False - assert result["result"] == [] + assert result['failure'] is None + assert result['result'] == [] def test_left_join_basic(): @@ -114,193 +146,211 @@ def test_left_join_basic(): "lhs_arr": [ 1, 2, - 3, + 3 ], "rhs_arr": [ 2, 3, - 4, - ], + 4 + ] } result = jmespath_custom_execute( - "left_join(lhs_arr, rhs_arr, 'lhs == rhs')", data + "left_join(lhs_arr, rhs_arr, 'lhs == rhs')", + data ) - assert result["has_failed"] is False - joined = result["result"] - assert {"lhs": 1, "rhs": None} in joined - assert {"lhs": 2, "rhs": 2} in joined - assert {"lhs": 3, "rhs": 3} in joined + assert result['failure'] is None + joined = result['result'] + assert { + "lhs": 1, + "rhs": None + } in joined + assert { + "lhs": 2, + "rhs": 2 + } in joined + assert { + "lhs": 3, + "rhs": 3 + } in joined def test_left_join_no_rhs_matches(): data = { "lhs_arr": [ 1, - 2, + 2 ], "rhs_arr": [ - 5, - ], + 5 + ] } result = jmespath_custom_execute( - "left_join(lhs_arr, rhs_arr, 'lhs == rhs')", data + "left_join(lhs_arr, rhs_arr, 'lhs == rhs')", + data ) - assert result["has_failed"] is False - joined = result["result"] - assert {"lhs": 1, "rhs": None} in joined - assert {"lhs": 2, "rhs": None} in joined + assert result['failure'] is None + joined = result['result'] + assert { + "lhs": 1, + "rhs": None + } in joined + assert { + "lhs": 2, + "rhs": None + } in joined def test_outer_join_basic(): data = { "lhs_arr": [ 1, - 2, + 2 ], "rhs_arr": [ 2, - 3, - ], + 3 + ] } result = jmespath_custom_execute( - "outer_join(lhs_arr, rhs_arr, 'lhs == rhs')", data + "outer_join(lhs_arr, rhs_arr, 'lhs == rhs')", + data ) - assert result["has_failed"] is False - joined = result["result"] - assert {"lhs": 1, "rhs": None} in joined - assert {"lhs": 2, "rhs": 2} in joined - assert {"lhs": None, "rhs": 3} in joined + assert result['failure'] is None + joined = result['result'] + assert { + "lhs": 1, + "rhs": None + } in joined + assert { + "lhs": 2, + "rhs": 2 + } in joined + assert { + "lhs": None, + "rhs": 3 + } in joined def test_regex_find_direct_string_match(): - result = CustomJMESPathFunctions._func_regex_find("\\d+", "hello 123 world") + result = CustomJMESPathFunctions._func_regex_find( + "\\d+", + "hello 123 world" + ) assert result == "123" def test_regex_find_direct_string_no_match(): - result = CustomJMESPathFunctions._func_regex_find("\\d+", "hello world") + result = CustomJMESPathFunctions._func_regex_find( + "\\d+", + "hello world" + ) assert result is None def test_regex_find_direct_array(): result = CustomJMESPathFunctions._func_regex_find( "\\d+", - [ - "abc123", - "def", - "456ghi", - ], + ["abc123", "def", "456ghi"] ) - assert result == [ - "123", - None, - "456", - ] + assert result == ["123", None, "456"] def test_regex_find_all_direct_string(): - result = CustomJMESPathFunctions._func_regex_find_all("\\d", "a1b2c3") - assert result == [ - "1", - "2", - "3", - ] + result = CustomJMESPathFunctions._func_regex_find_all( + "\\d", + "a1b2c3" + ) + assert result == ["1", "2", "3"] def test_regex_find_all_direct_array(): result = CustomJMESPathFunctions._func_regex_find_all( "\\d", - [ - "a1b2", - "c3", - ], + ["a1b2", "c3"] ) assert result == [ [ "1", - "2", + "2" ], [ - "3", - ], + "3" + ] ] def test_regex_groups_direct_string_match(): result = CustomJMESPathFunctions._func_regex_groups( - "(\\d{4})-(\\d{2})-(\\d{2})", "2024-01-15" + "(\\d{4})-(\\d{2})-(\\d{2})", + "2024-01-15" ) - assert result == [ - "2024", - "01", - "15", - ] + assert result == ["2024", "01", "15"] def test_regex_groups_direct_string_no_match(): - result = CustomJMESPathFunctions._func_regex_groups("(\\d{4})-(\\d{2})", "no date") + result = CustomJMESPathFunctions._func_regex_groups( + "(\\d{4})-(\\d{2})", + "no date" + ) assert result is None def test_regex_groups_direct_array(): result = CustomJMESPathFunctions._func_regex_groups( "(\\d+)", - [ - "abc123", - "def", - ], + ["abc123", "def"] ) assert result == [ [ - "123", + "123" ], - None, + None ] def test_regex_groups_all_direct_string(): - result = CustomJMESPathFunctions._func_regex_groups_all("([a-z])(\\d)", "a1b2c3") + result = CustomJMESPathFunctions._func_regex_groups_all( + "([a-z])(\\d)", + "a1b2c3" + ) assert result == [ [ "a", - "1", + "1" ], [ "b", - "2", + "2" ], [ "c", - "3", - ], + "3" + ] ] def test_regex_groups_all_direct_array(): result = CustomJMESPathFunctions._func_regex_groups_all( "([a-z])(\\d)", - [ - "a1b2", - "c3", - ], + ["a1b2", "c3"] ) assert result == [ [ [ "a", - "1", + "1" ], [ "b", - "2", - ], + "2" + ] ], [ [ "c", - "3", - ], - ], + "3" + ] + ] ] @@ -311,15 +361,18 @@ def test_is_identity_present_true(): "identities": { "user": [ { - "name": "test", - }, - ], - }, - }, + "name": "test" + } + ] + } + } } - result = jmespath_custom_execute("is_identity_present(itype, request)", data) - if result["has_failed"] is False: - assert result["result"] is True + result = jmespath_custom_execute( + "is_identity_present(itype, request)", + data + ) + if result['failure'] is None: + assert result['result'] is True def test_is_identity_present_false(): @@ -329,15 +382,18 @@ def test_is_identity_present_false(): "identities": { "user": [ { - "name": "test", - }, - ], - }, - }, + "name": "test" + } + ] + } + } } - result = jmespath_custom_execute("is_identity_present(itype, request)", data) - if result["has_failed"] is False: - assert result["result"] is False + result = jmespath_custom_execute( + "is_identity_present(itype, request)", + data + ) + if result['failure'] is None: + assert result['result'] is False def test_is_identity_present_direct(): @@ -347,11 +403,11 @@ def test_is_identity_present_direct(): "identities": { "user": [ { - "name": "test", - }, - ], - }, - }, + "name": "test" + } + ] + } + } ) assert result is True @@ -361,11 +417,11 @@ def test_is_identity_present_direct(): "identities": { "user": [ { - "name": "test", - }, - ], - }, - }, + "name": "test" + } + ] + } + } ) assert result is False @@ -373,8 +429,8 @@ def test_is_identity_present_direct(): "user", { "identities": { - "user": [], - }, - }, + "user": [] + } + } ) assert result is False diff --git a/tests/unit/test_reference.py b/tests/unit/test_reference.py index e88e427..4d0e76a 100644 --- a/tests/unit/test_reference.py +++ b/tests/unit/test_reference.py @@ -1,30 +1,30 @@ -import pytest +"""TODO: Add module docstring.""" + import jmespath +import pytest from authzee.reference import * def execute(expression, data): - result = { - "result": None, - "has_failed": False, - "error_message": None, - } try: - result['result'] = jmespath.search(expression, data) + result = jmespath.search(expression, data) except Exception as exc: - result['has_failed'] = True - result['error_message'] = str(exc) + return { + "result": None, + "failure": str(exc) + } - return result + return { + "result": result, + "failure": None + } def failing_execute(expression, data): - return { "result": None, - "has_failed": True, - "error_message": "forced failure", + "failure": "forced failure" } @@ -35,9 +35,9 @@ def context_defs(): "context_type": "NULL", "schema": { "type": "object", - "additionalProperties": False, - }, - }, + "additionalProperties": False + } + } ] @@ -50,18 +50,18 @@ def identity_defs(): "type": "object", "required": [ "id", - "role", + "role" ], "properties": { "id": { - "type": "string", + "type": "string" }, "role": { - "type": "string", - }, - }, - }, - }, + "type": "string" + } + } + } + } ] @@ -72,20 +72,20 @@ def resource_defs(): "resource_type": "Widget", "actions": [ "Widget:Read", - "Widget:Write", + "Widget:Write" ], "schema": { "type": "object", "required": [ - "id", + "id" ], "properties": { "id": { - "type": "string", - }, - }, - }, - }, + "type": "string" + } + } + } + } ] @@ -94,12 +94,12 @@ def allow_grant(): return { "effect": "allow", "actions": [ - "Widget:Read", + "Widget:Read" ], "query": "request.identities.User[0].role == 'admin'", - "evaluation_handler": "evaluate", "equality": True, - "data": {}, + "applicable_on_failure": False, + "data": {} } @@ -108,12 +108,12 @@ def deny_grant(): return { "effect": "deny", "actions": [ - "Widget:Read", + "Widget:Read" ], "query": "request.identities.User[0].role == 'banned'", - "evaluation_handler": "evaluate", "equality": True, - "data": {}, + "applicable_on_failure": False, + "data": {} } @@ -124,18 +124,17 @@ def admin_request(): "User": [ { "id": "u1", - "role": "admin", - }, - ], + "role": "admin" + } + ] }, "action": "Widget:Read", "resource_type": "Widget", "resource": { - "id": "w1", + "id": "w1" }, "context_type": "NULL", - "context": {}, - "evaluation_handler": "grant", + "context": {} } @@ -147,10 +146,10 @@ def banned_request(admin_request): "User": [ { "id": "u2", - "role": "banned", - }, - ], - }, + "role": "banned" + } + ] + } } @@ -162,10 +161,10 @@ def guest_request(admin_request): "User": [ { "id": "u3", - "role": "guest", - }, - ], - }, + "role": "guest" + } + ] + } } @@ -176,314 +175,346 @@ def base_batch(): "User": [ { "id": "u1", - "role": "admin", - }, - ], + "role": "admin" + } + ] }, "action": "Widget:Read", "resource_type": "Widget", "resource": { - "id": "w1", + "id": "w1" }, "context_type": "NULL", "context": {}, - "evaluation_handler": "grant", "batch": [ - {}, - ], + {} + ] } def test_validate_context_defs_valid(context_defs): r = validate_context_defs(context_defs) - assert r['is_valid'] is True - assert r['errors'] == [] + assert r['error'] is None + assert r['error'] is None def test_validate_context_defs_invalid_schema(): - r = validate_context_defs([ - { - "context_type": "X", - "schema": "bad", - }, - ]) - assert r['is_valid'] is False + r = validate_context_defs( + [ + { + "context_type": "X", + "schema": "bad" + } + ] + ) + assert r['error'] is not None def test_validate_context_defs_duplicate_type(context_defs): r = validate_context_defs(context_defs + context_defs) - assert r['is_valid'] is False - assert any("more than once" in e['message'] for e in r['errors']) + assert r['error'] is not None + assert "more than once" in r['error']['message'] def test_validate_context_defs_non_object_schema(): - r = validate_context_defs([ - { - "context_type": "X", - "schema": { - "type": "array", - }, - }, - ]) - assert r['is_valid'] is False - assert any("object" in e['message'] for e in r['errors']) + r = validate_context_defs( + [ + { + "context_type": "X", + "schema": { + "type": "array" + } + } + ] + ) + assert r['error'] is not None + assert "object" in r['error']['message'] def test_validate_context_defs_missing_type_in_schema(): - r = validate_context_defs([ - { - "context_type": "X", - "schema": {}, - }, - ]) - assert r['is_valid'] is False + r = validate_context_defs( + [ + { + "context_type": "X", + "schema": {} + } + ] + ) + assert r['error'] is not None def test_validate_context_defs_empty(): - assert validate_context_defs([])['is_valid'] is True + assert validate_context_defs([])['error'] is None def test_validate_identity_defs_valid(identity_defs): - assert validate_identity_defs(identity_defs)['is_valid'] is True + assert validate_identity_defs(identity_defs)['error'] is None def test_validate_identity_defs_invalid_schema(): - r = validate_identity_defs([ - { - "identity_type": "X", - "schema": 123, - }, - ]) - assert r['is_valid'] is False + r = validate_identity_defs( + [ + { + "identity_type": "X", + "schema": 123 + } + ] + ) + assert r['error'] is not None def test_validate_identity_defs_duplicate_type(identity_defs): r = validate_identity_defs(identity_defs + identity_defs) - assert r['is_valid'] is False - assert any("more than once" in e['message'] for e in r['errors']) + assert r['error'] is not None + assert "more than once" in r['error']['message'] def test_validate_identity_defs_non_object_schema(): - r = validate_identity_defs([ - { - "identity_type": "X", - "schema": { - "type": "string", - }, - }, - ]) - assert r['is_valid'] is False + r = validate_identity_defs( + [ + { + "identity_type": "X", + "schema": { + "type": "string" + } + } + ] + ) + assert r['error'] is not None def test_validate_identity_defs_empty(): - assert validate_identity_defs([])['is_valid'] is True + assert validate_identity_defs([])['error'] is None def test_validate_resource_defs_valid(resource_defs): - assert validate_resource_defs(resource_defs)['is_valid'] is True + assert validate_resource_defs(resource_defs)['error'] is None def test_validate_resource_defs_invalid_schema(): - r = validate_resource_defs([ - { - "resource_type": "X", - "actions": [], - "schema": "bad", - }, - ]) - assert r['is_valid'] is False + r = validate_resource_defs( + [ + { + "resource_type": "X", + "actions": [], + "schema": "bad" + } + ] + ) + assert r['error'] is not None def test_validate_resource_defs_duplicate_type(resource_defs): r = validate_resource_defs(resource_defs + resource_defs) - assert r['is_valid'] is False - assert any("more than once" in e['message'] for e in r['errors']) + assert r['error'] is not None + assert "more than once" in r['error']['message'] def test_validate_resource_defs_non_object_schema(): - r = validate_resource_defs([ - { - "resource_type": "X", - "actions": [], - "schema": { - "type": "array", - }, - }, - ]) - assert r['is_valid'] is False + r = validate_resource_defs( + [ + { + "resource_type": "X", + "actions": [], + "schema": { + "type": "array" + } + } + ] + ) + assert r['error'] is not None def test_validate_resource_defs_empty(): - assert validate_resource_defs([])['is_valid'] is True + assert validate_resource_defs([])['error'] is None def test_validate_grants_valid(allow_grant, deny_grant): - assert validate_grants([allow_grant, deny_grant])['is_valid'] is True + assert validate_grants([allow_grant, deny_grant])['error'] is None def test_validate_grants_invalid(): - assert validate_grants([ - { - "effect": "bad", - }, - ])['is_valid'] is False + assert validate_grants([{"effect": "bad"}])['error'] is not None def test_validate_grants_empty(): - assert validate_grants([])['is_valid'] is True + assert validate_grants([])['error'] is None def test_validate_request_valid( admin_request, context_defs, identity_defs, - resource_defs, + resource_defs ): - assert validate_request( - admin_request, context_defs, identity_defs, resource_defs - )['is_valid'] is True + assert validate_request(admin_request, context_defs, identity_defs, resource_defs)['error'] is None -def test_validate_request_invalid_schema(context_defs, identity_defs, resource_defs): - assert validate_request( - {}, context_defs, identity_defs, resource_defs - )['is_valid'] is False +def test_validate_request_invalid_schema( + context_defs, + identity_defs, + resource_defs +): + assert validate_request({}, context_defs, identity_defs, resource_defs)['error'] is not None def test_validate_request_unknown_identity_type( admin_request, context_defs, identity_defs, - resource_defs, + resource_defs ): req = { **admin_request, "identities": { "Ghost": [ { - "id": "g1", - }, - ], - }, + "id": "g1" + } + ] + } } - r = validate_request(req, context_defs, identity_defs, resource_defs) - assert r['is_valid'] is False - assert any("Ghost" in e['message'] for e in r['errors']) + r = validate_request( + req, + context_defs, + identity_defs, + resource_defs + ) + assert r['error'] is not None + assert "Ghost" in r['error']['message'] def test_validate_request_invalid_identity_instance( admin_request, context_defs, identity_defs, - resource_defs, + resource_defs ): req = { **admin_request, "identities": { "User": [ { - "id": 123, - }, - ], - }, + "id": 123 + } + ] + } } - assert validate_request( - req, context_defs, identity_defs, resource_defs - )['is_valid'] is False + assert validate_request(req, context_defs, identity_defs, resource_defs)['error'] is not None def test_validate_request_unknown_resource_type( admin_request, context_defs, identity_defs, - resource_defs, + resource_defs ): - req = {**admin_request, "resource_type": "Unknown"} - r = validate_request(req, context_defs, identity_defs, resource_defs) - assert r['is_valid'] is False - assert any("Unknown" in e['message'] for e in r['errors']) + req = { + **admin_request, + "resource_type": "Unknown" + } + r = validate_request( + req, + context_defs, + identity_defs, + resource_defs + ) + assert r['error'] is not None + assert "Unknown" in r['error']['message'] def test_validate_request_invalid_resource_instance( admin_request, context_defs, identity_defs, - resource_defs, + resource_defs ): req = { **admin_request, "resource": { - "id": 999, - }, + "id": 999 + } } - assert validate_request( - req, context_defs, identity_defs, resource_defs - )['is_valid'] is False + assert validate_request(req, context_defs, identity_defs, resource_defs)['error'] is not None def test_validate_request_invalid_action( admin_request, context_defs, identity_defs, - resource_defs, + resource_defs ): - req = {**admin_request, "action": "Widget:Delete"} - r = validate_request(req, context_defs, identity_defs, resource_defs) - assert r['is_valid'] is False - assert any("Widget:Delete" in e['message'] for e in r['errors']) + req = { + **admin_request, + "action": "Widget:Delete" + } + r = validate_request( + req, + context_defs, + identity_defs, + resource_defs + ) + assert r['error'] is not None + assert "Widget:Delete" in r['error']['message'] def test_validate_request_unknown_context_type( admin_request, context_defs, identity_defs, - resource_defs, + resource_defs ): - req = {**admin_request, "context_type": "Unknown"} - r = validate_request(req, context_defs, identity_defs, resource_defs) - assert r['is_valid'] is False - assert any("Unknown" in e['message'] for e in r['errors']) + req = { + **admin_request, + "context_type": "Unknown" + } + r = validate_request( + req, + context_defs, + identity_defs, + resource_defs + ) + assert r['error'] is not None + assert "Unknown" in r['error']['message'] def test_validate_request_invalid_context_instance( admin_request, context_defs, identity_defs, - resource_defs, + resource_defs ): req = { **admin_request, "context": { - "extra": "not_allowed", - }, + "extra": "not_allowed" + } } - assert validate_request( - req, context_defs, identity_defs, resource_defs - )['is_valid'] is False + assert validate_request(req, context_defs, identity_defs, resource_defs)['error'] is not None def test_validate_batch_request_valid( base_batch, context_defs, identity_defs, - resource_defs, + resource_defs ): - assert validate_batch_request( - base_batch, context_defs, identity_defs, resource_defs - )['is_valid'] is True + assert validate_batch_request(base_batch, context_defs, identity_defs, resource_defs)['error'] is None -def test_validate_batch_request_invalid_schema(context_defs, identity_defs, resource_defs): - assert validate_batch_request( - {}, context_defs, identity_defs, resource_defs - )['is_valid'] is False +def test_validate_batch_request_invalid_schema( + context_defs, + identity_defs, + resource_defs +): + assert validate_batch_request({}, context_defs, identity_defs, resource_defs)['error'] is not None def test_validate_batch_request_item_overrides_identities( base_batch, context_defs, identity_defs, - resource_defs, + resource_defs ): batch = { **base_batch, @@ -493,23 +524,21 @@ def test_validate_batch_request_item_overrides_identities( "User": [ { "id": "u2", - "role": "guest", - }, - ], - }, - }, - ], + "role": "guest" + } + ] + } + } + ] } - assert validate_batch_request( - batch, context_defs, identity_defs, resource_defs - )['is_valid'] is True + assert validate_batch_request(batch, context_defs, identity_defs, resource_defs)['error'] is None def test_validate_batch_request_item_invalid_identity( base_batch, context_defs, identity_defs, - resource_defs, + resource_defs ): batch = { **base_batch, @@ -517,42 +546,45 @@ def test_validate_batch_request_item_invalid_identity( { "identities": { "Ghost": [ - {}, - ], - }, - }, - ], + {} + ] + } + } + ] } - r = validate_batch_request(batch, context_defs, identity_defs, resource_defs) - assert r['batch_errors'][0]['request'] != [] + r = validate_batch_request( + batch, + context_defs, + identity_defs, + resource_defs + ) + assert r['batch_errors'][0] is not None def test_validate_batch_request_item_overrides_resource( base_batch, context_defs, identity_defs, - resource_defs, + resource_defs ): batch = { **base_batch, "batch": [ { "resource": { - "id": "w2", - }, - }, - ], + "id": "w2" + } + } + ] } - assert validate_batch_request( - batch, context_defs, identity_defs, resource_defs - )['is_valid'] is True + assert validate_batch_request(batch, context_defs, identity_defs, resource_defs)['error'] is None def test_validate_batch_request_item_overrides_resource_type( base_batch, context_defs, identity_defs, - resource_defs, + resource_defs ): batch = { **base_batch, @@ -560,195 +592,158 @@ def test_validate_batch_request_item_overrides_resource_type( { "resource_type": "Widget", "resource": { - "id": "w2", - }, - }, - ], + "id": "w2" + } + } + ] } - assert validate_batch_request( - batch, context_defs, identity_defs, resource_defs - )['is_valid'] is True + assert validate_batch_request(batch, context_defs, identity_defs, resource_defs)['error'] is None def test_validate_batch_request_item_overrides_context( base_batch, context_defs, identity_defs, - resource_defs, + resource_defs ): batch = { **base_batch, "batch": [ { "context": {}, - "context_type": "NULL", - }, - ], + "context_type": "NULL" + } + ] } - assert validate_batch_request( - batch, context_defs, identity_defs, resource_defs - )['is_valid'] is True + assert validate_batch_request(batch, context_defs, identity_defs, resource_defs)['error'] is None def test_validate_batch_request_item_context_only( base_batch, context_defs, identity_defs, - resource_defs, + resource_defs ): batch = { **base_batch, "batch": [ { - "context": {}, - }, - ], + "context": {} + } + ] } - assert validate_batch_request( - batch, context_defs, identity_defs, resource_defs - )['is_valid'] is True + assert validate_batch_request(batch, context_defs, identity_defs, resource_defs)['error'] is None def test_validate_batch_request_top_level_invalid_identity( base_batch, context_defs, identity_defs, - resource_defs, + resource_defs ): batch = { **base_batch, "identities": { "Ghost": [ - {}, - ], - }, + {} + ] + } } - assert validate_batch_request( - batch, context_defs, identity_defs, resource_defs - )['is_valid'] is False + assert validate_batch_request(batch, context_defs, identity_defs, resource_defs)['error'] is not None def test_evaluate_one_action_not_in_grant(admin_request, allow_grant): grant = { **allow_grant, "actions": [ - "Widget:Write", - ], + "Widget:Write" + ] } - r = evaluate_one(admin_request, grant, execute, False) + r = evaluate_one(admin_request, grant, execute) assert r['is_applicable'] is False assert r['query_result'] is None def test_evaluate_one_empty_actions_matches_any(admin_request, allow_grant): - grant = {**allow_grant, "actions": [], "query": "`true`", "equality": True} - assert evaluate_one(admin_request, grant, execute, False)['is_applicable'] is True + grant = { + **allow_grant, + "actions": [], + "query": "`true`", + "equality": True + } + assert evaluate_one(admin_request, grant, execute)['is_applicable'] is True def test_evaluate_one_applicable(admin_request, allow_grant): - assert evaluate_one(admin_request, allow_grant, execute, False)['is_applicable'] is True + assert evaluate_one(admin_request, allow_grant, execute)['is_applicable'] is True def test_evaluate_one_wrong_equality(admin_request, allow_grant): - grant = {**allow_grant, "equality": False} - assert evaluate_one(admin_request, grant, execute, False)['is_applicable'] is False + grant = { + **allow_grant, + "equality": False + } + assert evaluate_one(admin_request, grant, execute)['is_applicable'] is False -def test_evaluate_one_query_failure_evaluate_no_error(admin_request, allow_grant): - r = evaluate_one(admin_request, allow_grant, failing_execute, False) +def test_evaluate_one_query_failure_not_applicable(admin_request, allow_grant): + r = evaluate_one(admin_request, allow_grant, failing_execute) assert r['is_applicable'] is False - assert r['has_failed'] is False - assert "evaluation" not in r['errors'] - - -def test_evaluate_one_query_failure_error_handler(admin_request, allow_grant): - grant = {**allow_grant, "evaluation_handler": "error"} - r = evaluate_one(admin_request, grant, failing_execute, False) - assert r['has_failed'] is False - assert r['errors']['evaluation'][0]['is_critical'] is False + assert r['query_result'] is None + assert r['failure'] is not None + assert "forced failure" in r['failure'] -def test_evaluate_one_query_failure_critical_handler(admin_request, allow_grant): - grant = {**allow_grant, "evaluation_handler": "critical"} - r = evaluate_one(admin_request, grant, failing_execute, False) - assert r['has_failed'] is True - assert r['errors']['evaluation'][0]['is_critical'] is True +def test_evaluate_one_query_failure_applicable_on_failure( + admin_request, + allow_grant +): + grant = { + **allow_grant, + "applicable_on_failure": True + } + r = evaluate_one(admin_request, grant, failing_execute) + assert r['is_applicable'] is True + assert r['query_result'] is None + assert r['failure'] is not None + assert "forced failure" in r['failure'] -def test_evaluate_one_only_crits_suppresses_error(admin_request, allow_grant): - grant = {**allow_grant, "evaluation_handler": "error"} - assert "evaluation" not in evaluate_one( +def test_audit_applicable_grant(admin_request, allow_grant): + r = audit( admin_request, - grant, - failing_execute, - True, - )['errors'] - - -def test_evaluate_one_request_override_critical(admin_request, allow_grant): - req = {**admin_request, "evaluation_handler": "critical"} - grant = {**allow_grant, "evaluation_handler": "evaluate"} - assert evaluate_one( - req, - grant, - failing_execute, - False, - )['has_failed'] is True - - -def test_evaluate_one_request_override_error(admin_request, allow_grant): - req = {**admin_request, "evaluation_handler": "error"} - grant = {**allow_grant, "evaluation_handler": "evaluate"} - r = evaluate_one( - req, - grant, - failing_execute, - False, + [allow_grant], + execute ) - assert "evaluation" in r['errors'] - assert r['has_failed'] is False - - -def test_audit_applicable_grant(admin_request, allow_grant): - r = audit(admin_request, [allow_grant], execute) - assert r['has_failed'] is False assert r['results'][0]['is_applicable'] is True + assert r['error'] is None def test_audit_no_applicable_grant(guest_request, allow_grant): - assert audit( - guest_request, - [allow_grant], - execute, - )['results'][0]['is_applicable'] is False + assert audit(guest_request, [allow_grant], execute)['results'][0]['is_applicable'] is False -def test_audit_critical_error_stops_early(admin_request, allow_grant): - grant = {**allow_grant, "evaluation_handler": "critical"} +def test_audit_failure_recorded(admin_request, allow_grant): r = audit( admin_request, - [grant, allow_grant], - failing_execute, + [allow_grant], + failing_execute ) - assert r['has_failed'] is True - assert len(r['results']) == 1 + assert r['results'][0]['is_applicable'] is False + assert r['results'][0]['failure'] is not None def test_audit_empty_grants(admin_request): - r = audit( - admin_request, - [], - execute, - ) + r = audit(admin_request, [], execute) assert r['results'] == [] - assert r['has_failed'] is False + assert r['error'] is None def test_authorize_allow_grant(admin_request, allow_grant): r = authorize( admin_request, [allow_grant], - execute, + execute ) assert r['is_authorized'] is True assert r['grant'] == allow_grant @@ -758,7 +753,7 @@ def test_authorize_deny_grant(banned_request, allow_grant, deny_grant): r = authorize( banned_request, [allow_grant, deny_grant], - execute, + execute ) assert r['is_authorized'] is False assert "deny" in r['message'] @@ -768,42 +763,26 @@ def test_authorize_no_applicable_grant(guest_request, allow_grant): r = authorize( guest_request, [allow_grant], - execute, + execute ) assert r['is_authorized'] is False assert r['grant'] is None assert "implicitly denied" in r['message'] -def test_authorize_critical_error_in_deny(admin_request, deny_grant): - grant = {**deny_grant, "evaluation_handler": "critical"} - r = authorize( - admin_request, - [grant], - failing_execute, - ) - assert r['is_authorized'] is False - assert r['has_failed'] is True - assert "critical error" in r['message'] - - -def test_authorize_critical_error_in_allow(admin_request, allow_grant): - grant = {**allow_grant, "evaluation_handler": "critical"} - r = authorize( - admin_request, - [grant], - failing_execute, - ) - assert r['is_authorized'] is False - assert r['has_failed'] is True - - -def test_authorize_deny_checked_before_allow(admin_request, allow_grant, deny_grant): - deny = {**deny_grant, "query": "request.identities.User[0].role == 'admin'"} +def test_authorize_deny_checked_before_allow( + admin_request, + allow_grant, + deny_grant +): + deny = { + **deny_grant, + "query": "request.identities.User[0].role == 'admin'" + } r = authorize( admin_request, [allow_grant, deny], - execute, + execute ) assert r['is_authorized'] is False assert r['grant']['effect'] == "deny" @@ -813,10 +792,10 @@ def test_batch_audit_basic(base_batch, allow_grant): r = batch_audit( base_batch, [allow_grant], - execute, + execute ) - assert len(r['batch_results']) == 1 - assert r['batch_results'][0]['results'][0]['is_applicable'] is True + assert len(r['batch']) == 1 + assert r['batch'][0]['results'][0]['is_applicable'] is True def test_batch_audit_item_overrides(base_batch, allow_grant): @@ -828,36 +807,34 @@ def test_batch_audit_item_overrides(base_batch, allow_grant): "User": [ { "id": "u2", - "role": "guest", - }, - ], - }, - }, - ], + "role": "guest" + } + ] + } + } + ] } - assert batch_audit( - batch, - [allow_grant], - execute, - )['batch_results'][0]['results'][0]['is_applicable'] is False + assert batch_audit(batch, [allow_grant], execute)['batch'][0]['results'][0]['is_applicable'] is False def test_batch_audit_multiple_items(base_batch, allow_grant): - batch = {**base_batch, "batch": [{}, {}]} - assert len(batch_audit( - batch, - [allow_grant], - execute, - )['batch_results']) == 2 + batch = { + **base_batch, + "batch": [ + {}, + {} + ] + } + assert len(batch_audit(batch, [allow_grant], execute)['batch']) == 2 def test_batch_authorize_basic(base_batch, allow_grant): r = batch_authorize( base_batch, [allow_grant], - execute, + execute ) - assert r['results'][0]['is_authorized'] is True + assert r['batch'][0]['is_authorized'] is True def test_batch_authorize_item_overrides(base_batch, allow_grant): @@ -869,27 +846,25 @@ def test_batch_authorize_item_overrides(base_batch, allow_grant): "User": [ { "id": "u2", - "role": "guest", - }, - ], - }, - }, - ], + "role": "guest" + } + ] + } + } + ] } - assert batch_authorize( - batch, - [allow_grant], - execute, - )['results'][0]['is_authorized'] is False + assert batch_authorize(batch, [allow_grant], execute)['batch'][0]['is_authorized'] is False def test_batch_authorize_multiple_items(base_batch, allow_grant): - batch = {**base_batch, "batch": [{}, {}]} - assert len(batch_authorize( - batch, - [allow_grant], - execute, - )['results']) == 2 + batch = { + **base_batch, + "batch": [ + {}, + {} + ] + } + assert len(batch_authorize(batch, [allow_grant], execute)['batch']) == 2 def test_audit_workflow_valid( @@ -897,7 +872,7 @@ def test_audit_workflow_valid( identity_defs, resource_defs, allow_grant, - admin_request, + admin_request ): assert "results" in audit_workflow( context_defs, @@ -905,7 +880,7 @@ def test_audit_workflow_valid( resource_defs, [allow_grant], admin_request, - execute, + execute ) @@ -913,105 +888,70 @@ def test_audit_workflow_invalid_context_defs( identity_defs, resource_defs, allow_grant, - admin_request, + admin_request ): bad_ctx = [ { "context_type": "X", "schema": { - "type": "array", - }, - }, + "type": "array" + } + } ] - assert audit_workflow( - bad_ctx, - identity_defs, - resource_defs, - [allow_grant], - admin_request, - execute, - )['is_valid'] is False + assert audit_workflow(bad_ctx, identity_defs, resource_defs, [allow_grant], admin_request, execute)['error'] is not None def test_audit_workflow_invalid_identity_defs( context_defs, resource_defs, allow_grant, - admin_request, + admin_request ): bad_id = [ { "identity_type": "X", "schema": { - "type": "string", - }, - }, + "type": "string" + } + } ] - assert audit_workflow( - context_defs, - bad_id, - resource_defs, - [allow_grant], - admin_request, - execute, - )['is_valid'] is False + assert audit_workflow(context_defs, bad_id, resource_defs, [allow_grant], admin_request, execute)['error'] is not None def test_audit_workflow_invalid_resource_defs( context_defs, identity_defs, allow_grant, - admin_request, + admin_request ): bad_res = [ { "resource_type": "X", "actions": [], "schema": { - "type": "array", - }, - }, + "type": "array" + } + } ] - assert audit_workflow( - context_defs, - identity_defs, - bad_res, - [allow_grant], - admin_request, - execute, - )['is_valid'] is False + assert audit_workflow(context_defs, identity_defs, bad_res, [allow_grant], admin_request, execute)['error'] is not None def test_audit_workflow_invalid_grants( context_defs, identity_defs, resource_defs, - admin_request, + admin_request ): - assert audit_workflow( - context_defs, - identity_defs, - resource_defs, - [{"effect": "bad"}], - admin_request, - execute, - )['is_valid'] is False + assert audit_workflow(context_defs, identity_defs, resource_defs, [{"effect": "bad"}], admin_request, execute)['error'] is not None def test_audit_workflow_invalid_request( context_defs, identity_defs, resource_defs, - allow_grant, + allow_grant ): - assert audit_workflow( - context_defs, - identity_defs, - resource_defs, - [allow_grant], - {}, - execute, - )['is_valid'] is False + assert audit_workflow(context_defs, identity_defs, resource_defs, [allow_grant], {}, execute)['error'] is not None def test_authorize_workflow_authorized( @@ -1019,16 +959,9 @@ def test_authorize_workflow_authorized( identity_defs, resource_defs, allow_grant, - admin_request, + admin_request ): - assert authorize_workflow( - context_defs, - identity_defs, - resource_defs, - [allow_grant], - admin_request, - execute, - )['is_authorized'] is True + assert authorize_workflow(context_defs, identity_defs, resource_defs, [allow_grant], admin_request, execute)['is_authorized'] is True def test_authorize_workflow_not_authorized( @@ -1036,32 +969,18 @@ def test_authorize_workflow_not_authorized( identity_defs, resource_defs, allow_grant, - guest_request, + guest_request ): - assert authorize_workflow( - context_defs, - identity_defs, - resource_defs, - [allow_grant], - guest_request, - execute, - )['is_authorized'] is False + assert authorize_workflow(context_defs, identity_defs, resource_defs, [allow_grant], guest_request, execute)['is_authorized'] is False def test_authorize_workflow_invalid_request( context_defs, identity_defs, resource_defs, - allow_grant, + allow_grant ): - assert authorize_workflow( - context_defs, - identity_defs, - resource_defs, - [allow_grant], - {}, - execute, - )['is_valid'] is False + assert authorize_workflow(context_defs, identity_defs, resource_defs, [allow_grant], {}, execute)['error'] is not None def test_batch_audit_workflow_valid( @@ -1069,15 +988,15 @@ def test_batch_audit_workflow_valid( identity_defs, resource_defs, allow_grant, - base_batch, + base_batch ): - assert "batch_results" in batch_audit_workflow( + assert "batch" in batch_audit_workflow( context_defs, identity_defs, resource_defs, [allow_grant], base_batch, - execute, + execute ) @@ -1085,70 +1004,198 @@ def test_batch_audit_workflow_invalid_batch( context_defs, identity_defs, resource_defs, - allow_grant, + allow_grant ): - assert batch_audit_workflow( - context_defs, - identity_defs, - resource_defs, - [allow_grant], - {}, - execute, - )['is_valid'] is False + assert batch_audit_workflow(context_defs, identity_defs, resource_defs, [allow_grant], {}, execute)['error'] is not None def test_batch_audit_workflow_invalid_context_defs( identity_defs, resource_defs, allow_grant, - base_batch, + base_batch ): bad_ctx = [ { "context_type": "X", "schema": { - "type": "array", - }, - }, + "type": "array" + } + } ] - assert batch_audit_workflow( - bad_ctx, + assert batch_audit_workflow(bad_ctx, identity_defs, resource_defs, [allow_grant], base_batch, execute)['error'] is not None + + +def test_batch_authorize_workflow_valid( + context_defs, + identity_defs, + resource_defs, + allow_grant, + base_batch +): + assert "batch" in batch_authorize_workflow( + context_defs, identity_defs, resource_defs, [allow_grant], base_batch, - execute, - )['is_valid'] is False + execute + ) -def test_batch_authorize_workflow_valid( +def test_batch_authorize_workflow_invalid_batch( + context_defs, + identity_defs, + resource_defs, + allow_grant +): + assert batch_authorize_workflow(context_defs, identity_defs, resource_defs, [allow_grant], {}, execute)['error'] is not None + + +def test_validate_batch_request_invalid_resource_type( + context_defs, + identity_defs, + resource_defs +): + """validate_batch_request with invalid resource_type at root level.""" + batch = { + "identities": { + "User": [ + { + "id": "u1", + "role": "admin" + } + ] + }, + "action": "Widget:Read", + "resource_type": "NonExistent", + "resource": { + "id": "w1" + }, + "context_type": "NULL", + "context": {}, + "batch": [ + {} + ] + } + r = validate_batch_request( + batch, + context_defs, + identity_defs, + resource_defs + ) + assert r['error'] is not None + assert ( + "NonExistent" in r['error']['message'] + or "resource" in r['error']['message'].lower() + ) + + +def test_validate_batch_request_invalid_context_type( + context_defs, + identity_defs, + resource_defs +): + """validate_batch_request with invalid context_type at root level.""" + batch = { + "identities": { + "User": [ + { + "id": "u1", + "role": "admin" + } + ] + }, + "action": "Widget:Read", + "resource_type": "Widget", + "resource": { + "id": "w1" + }, + "context_type": "NonExistentContext", + "context": {}, + "batch": [ + {} + ] + } + r = validate_batch_request( + batch, + context_defs, + identity_defs, + resource_defs + ) + assert r['error'] is not None + assert ( + "NonExistentContext" in r['error']['message'] + or "context" in r['error']['message'].lower() + ) + + +def test_batch_audit_workflow_per_item_error( context_defs, identity_defs, resource_defs, allow_grant, - base_batch, + base_batch ): - assert "results" in batch_authorize_workflow( + """batch_audit_workflow with a batch item that has a validation error.""" + batch = { + **base_batch, + "batch": [ + {}, + { + "identities": { + "Ghost": [ + {} + ] + } + } + ] + } + r = batch_audit_workflow( context_defs, identity_defs, resource_defs, [allow_grant], - base_batch, - execute, + batch, + execute ) + assert r['error'] is None + assert len(r['batch']) == 2 + assert r['batch'][0]['error'] is None + assert r['batch'][1]['error'] is not None -def test_batch_authorize_workflow_invalid_batch( +def test_batch_authorize_workflow_per_item_error( context_defs, identity_defs, resource_defs, allow_grant, + base_batch ): - assert batch_authorize_workflow( + """batch_authorize_workflow with a batch item that has a validation error.""" + batch = { + **base_batch, + "batch": [ + {}, + { + "identities": { + "Ghost": [ + {} + ] + } + } + ] + } + r = batch_authorize_workflow( context_defs, identity_defs, resource_defs, [allow_grant], - {}, - execute, - )['is_valid'] is False + batch, + execute + ) + assert r['error'] is None + assert len(r['batch']) == 2 + assert r['batch'][0]['error'] is None + assert r['batch'][1]['error'] is not None + assert r['batch'][1]['is_authorized'] is False diff --git a/tests/unit/test_shared_mem_latch.py b/tests/unit/test_shared_mem_latch.py index e65d20c..684dd6b 100644 --- a/tests/unit/test_shared_mem_latch.py +++ b/tests/unit/test_shared_mem_latch.py @@ -1,7 +1,5 @@ """Unit tests for authzee.compute.shared_mem_latch module.""" -import pytest - from multiprocessing.managers import SharedMemoryManager from authzee.compute.shared_mem_latch import SharedMemLatch