{"id":50109047,"url":"https://github.com/mgourlis/stateful-abac-policy-engine","last_synced_at":"2026-05-23T12:06:02.283Z","repository":{"id":333875060,"uuid":"1139034632","full_name":"mgourlis/stateful-abac-policy-engine","owner":"mgourlis","description":"Stateful ABAC Policy Engine is a production-ready, multi-tenant authorization system implementing Attribute-Based Access Control (ABAC) with support for spatial conditions (PostGIS), hierarchical ACL evaluation, and both HTTP and direct-database access modes. Includes a React admin UI, async Python SDK, and Keycloak IdP integration.","archived":false,"fork":false,"pushed_at":"2026-03-10T07:45:39.000Z","size":503,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-10T14:56:04.085Z","etag":null,"topics":["abac","access-control","acl","attribute-based-access-control","authorization","fastapi","geospatial","jwt","keycloak","multi-tenant","oauth2","policy-engine","postgis","postgresql","python","rbac","react","redis","sdk","spatial"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mgourlis.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-21T12:43:21.000Z","updated_at":"2026-03-10T07:45:43.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/mgourlis/stateful-abac-policy-engine","commit_stats":null,"previous_names":["mgourlis/stateful-abac-policy-engine"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/mgourlis/stateful-abac-policy-engine","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mgourlis%2Fstateful-abac-policy-engine","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mgourlis%2Fstateful-abac-policy-engine/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mgourlis%2Fstateful-abac-policy-engine/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mgourlis%2Fstateful-abac-policy-engine/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mgourlis","download_url":"https://codeload.github.com/mgourlis/stateful-abac-policy-engine/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mgourlis%2Fstateful-abac-policy-engine/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33394703,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-23T04:15:53.637Z","status":"ssl_error","status_checked_at":"2026-05-23T04:15:53.242Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["abac","access-control","acl","attribute-based-access-control","authorization","fastapi","geospatial","jwt","keycloak","multi-tenant","oauth2","policy-engine","postgis","postgresql","python","rbac","react","redis","sdk","spatial"],"created_at":"2026-05-23T12:06:01.206Z","updated_at":"2026-05-23T12:06:02.264Z","avatar_url":"https://github.com/mgourlis.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Stateful ABAC Policy Engine - High-Performance ABAC Authorization Engine\n\n## 1. Overview\nThe **Stateful ABAC Policy Engine** is a next-generation authorization service designed for high-scale, geo-spatial, and attribute-based access control (ABAC) systems. Built on **FastAPI**, **PostgreSQL 16**, and **Redis**, it leverages advanced database engineering techniques—including table partitioning, dynamic SQL compilation, and PL/pgSQL runners—to deliver sub-millisecond compiled authorization checks.\n\nThis system moves beyond traditional \"Check permissions in Application Logic\" by compiling complex JSON policies directly into efficient SQL `WHERE` clauses, allowing the database to filter millions of rows natively.\n\n---\n\n## 2. Key Advanced Features\n\n### 2.1. Hybrid ABAC / RBAC Engine\nThe core differentiator is the **JIT (Just-In-Time) Policy Compiler**.\n- **Dynamic Rules**: Policies are stored as JSONB in the `acl` table.\n- **SQL Compilation**: A trigger (`trg_compile_acl_conditions`) automatically parses JSON rules into raw SQL logic upon insertion using a recursive PL/pgSQL compiler.\n- **Supported Logic**:\n  - **Logical Operators**: Nested `AND`, `OR` groups, plus `NOT` for negation.\n  - **Comparators**: `=`, `!=`, `\u003e`, `\u003e=`, `\u003c`, `\u003c=`.\n  - **Set Operations**: `IN`, `NOT_IN` (e.g., `status NOT IN ['deleted', 'archived']`), `ALL` (array containment).\n  - **Type Safety**: Automatic type casting for `::numeric`, `::boolean`, and `::text`.\n\n### 2.2. Context-Aware Authorization\nPolicies can reference dynamic runtime context, not just static attributes.\n- **Context Placeholders**: Access request data dynamically.\n  - `$context.ip`: The IP address of the requester.\n  - `$principal.dept`: The department of the logged-in user.\n- **Example Rule**:\n  ```json\n  {\n    \"op\": \"and\",\n    \"conditions\": [\n      { \"op\": \"=\", \"source\": \"resource\", \"attr\": \"classification\", \"val\": \"confidential\" },\n      { \"op\": \"=\", \"source\": \"context\", \"attr\": \"ip\", \"val\": \"10.0.0.1\" }\n    ]\n  }\n  ```\n  *Compiled SQL*: `(resource.attributes-\u003e\u003e'classification' = 'confidential' AND p_ctx-\u003e'context'-\u003e\u003e'ip' = '10.0.0.1')`\n\n- **NOT Operator Example** (Negation):\n  ```json\n  {\n    \"op\": \"not\",\n    \"conditions\": [\n      { \"op\": \"=\", \"source\": \"resource\", \"attr\": \"deleted\", \"val\": true }\n    ]\n  }\n  ```\n  *Compiled SQL*: `NOT (resource.attributes-\u003e\u003e'deleted' = 'true')`\n\n- **NOT_IN Operator Example** (Set Exclusion):\n  ```json\n  { \"op\": \"not_in\", \"source\": \"resource\", \"attr\": \"status\", \"val\": [\"deleted\", \"archived\"] }\n  ```\n  *Compiled SQL*: `NOT (resource.attributes-\u003e\u003e'status' = ANY(ARRAY['deleted', 'archived']))`\n\n### 2.3. Database Partitioning \u0026 Sharding Strategy\nTo handle massive scale, the system uses **Declarative Table Partitioning**.\n- **Realm-Based Sharding**: Data is physically partitioned by `realm_id`.\n  - `resource` -\u003e `resource_realmname_10`, `resource_realmname_11`...\n  - `acl` -\u003e `acl_realmname_10`...\n\n### 2.4. Authorization Runner (The \"Check\")\nInstead of fetching all Access Control Lists (ACLs) to Python, the system pushes the check down to the database kernel.\n- **Function**: `get_authorized_resources(realm_id, principal_id, ...)`\n- **Mechanism**:\n  1. Finds the matching compiled SQL string for the user/role.\n  2. Dynamically binds context variables (`$1`, `$2`...).\n  3. Executes `RETURN QUERY EXECUTE ...` to fetch only valid Resource IDs.\n- **Performance**: Authorization becomes a single indexed Index Scan.\n\n### 2.5. Audit \u0026 Compliance\n- **Zero-Latency Logging**: Audit logs are dispatched via `BackgroundTasks` to avoid blocking the API response.\n- **Full Traceability**: Every check logs `principal_id`, `decision`, `compiled_sql_hash`, and `execution_time`.\n\n### 2.6. Spatial Authorization (Geo-fencing \u0026 Proximity)\nLeveraging **PostGIS**, the system supports location-based rules natively.\n- **Native Geometry**: Resources have a `geometry` column (Geometry, 3857).\n- **Spatial Operators**:\n  - `st_dwithin`: \"Allow if User is within X meters of Resource\".\n  - `st_contains` / `st_intersects`: \"Allow if Resource Polygon contains User Point\".\n- **Supported Formats**: The compiler auto-detects and supports:\n  - **WKT** (Well-Known Text): `POINT(30 10)`\n  - **GeoJSON**: `{\"type\":\"Point\",\"coordinates\":[30,10]}`\n  - **EWKT** (Extended WKT): `SRID=3857;POINT(30 10)`\n- **SRID Support**: Native support for configurable input SRIDs (e.g., 4326, 3857) via API parameters. The system automatically transforms all inputs to the storage projection (3857) for consistent spatial operations.\n- **Example Rule**:\n  ```json\n  {\n    \"op\": \"st_dwithin\",\n    \"attr\": \"geometry\",\n    \"val\": \"$context.location\",\n    \"args\": 5000\n  }\n  ```\n  *Result*: Sub-millisecond spatial join verification.\n\n### 2.7. External Identity Integration\nThe engine can map external system IDs (e.g., specific file IDs `IMG-001`, `DOC-999`) to internal integer IDs automatically.\n- **Mapping Table**: `external_ids` table partitions map `(realm, resource_type, external_id)` -\u003e `internal_id`.\n- **Resource Type Scoping**: External IDs are scoped by resource type. The same external ID (e.g., `\"EXT-001\"`) can exist for multiple resource types within the same realm.\n- **Multiple External IDs**: A single resource can be associated with multiple unique External IDs (aliases). The API returns all associated external IDs for a resource.\n- **Disambiguation**: API endpoints for external ID operations require the resource type (ID or name) to be specified: `/resources/external/{type_id_or_name}/{external_id}`.\n- **Request Flow**: Application sends `IMG-001` with type `\"Image\"`. Engine resolves it to internal `ID: 55`, checks permissions, and if allowed, returns `IMG-001`.\n- **Reverse Mapping (Default)**: If an ID list is requested without logical filters, the system automatically looks up and returns **External IDs** instead of internal integers. Resources explicitly missing an external mapping are excluded from the response.\n\n### 2.8. Active Roles (RBAC Override)\nSupports checking permissions for a specific subset of roles, rather than all assigned roles.\n- **Use Case**: \"Check if the 'Manager' role has access to this document\" (even if the user is also an Admin).\n- **Override**: Pass `role_names=[\"Manager\"]` in the API request. The system resolves these to Role IDs and restricts the check to the matching roles.\n\n### 2.9. Keycloak Integration \u0026 Sync\nThe system includes a robust **Keycloak Adapter** and **Sync Service** to mirror Keycloak state locally for high-performance checks.\n- **Realm-Specific Sync**: Each Realm can be linked to a Keycloak Realm via `realm_keycloak_config`.\n- **Periodic Synchronization**: `APScheduler` runs background tasks (configurable interval) to sync:\n  - **Roles**: Keycloak Realm Roles -\u003e Apps `AuthRole`.\n  - **Principals**: Keycloak Users -\u003e App `Principal`.\n  - **User-Role Mappings**: Updates local assignments.\n- **API Trigger**: Sync can be triggered manually via `POST /api/v1/realms/{id}/sync`.\n\n### 2.10. Comprehensive Entity CRUD \u0026 Batch Operations\nBeyond generic policy checks, the system provides full lifecycle management for authorization entities.\n- **Realm Management**: Create, Configure (Keycloak), and Manage Realms.\n- **Entity CRUD**: Standard `POST`, `GET`, `PUT`, `DELETE` for Actions, ResourceTypes, Roles, Resources, and ACLs.\n- **Batch Operations**: High-performance batch endpoints (e.g., `POST /realms/{id}/resources/batch`) support:\n  - **Bulk Create**: Insert multiple items in one transaction.\n  - **Bulk Update/Delete**: Update or Delete items using internal ID or **External ID**.\n    - *Example*: Update resource geometry by providing `{\"external_id\": \"IMG-001\", \"srid\": 4326, \"geometry\": \"POINT(23 38)\"}`.\n\n\u003e **Note (Upsert Behavior)**: \n\u003e - **Create Operations** (Single \u0026 Batch) behave as **Upserts**. If the entity exists, the system will **Update** the existing record instead of returning a duplicate error.\n\n### 2.11. Single-Query Authorization (SearchQuery Integration)\nFor applications that need to combine authorization with existing database queries (e.g., search, filtering, pagination), the system provides a **Single-Query Authorization** feature that returns authorization conditions as JSON DSL.\n\n- **Endpoint**: `POST /api/v1/get-authorization-conditions`\n- **Purpose**: Get authorization conditions that can be converted to SQL WHERE clauses and merged with application queries.\n- **Use Case**: \"Show me all documents I can access that match my search criteria\" - in a single optimized query.\n\n#### How It Works\n1. **Request**: Specify resource type, action, and optional context\n2. **Processing**: \n   - PostgreSQL function aggregates all applicable ACLs (type-level + resource-level)\n   - Conditions with `source='principal'` or `source='context'` are evaluated server-side\n   - `$context.*` and `$principal.*` references are resolved to actual values\n3. **Response**: Returns one of three filter types:\n   - `granted_all`: User has unconditional access - no filtering needed\n   - `denied_all`: User has no access - reject the query\n   - `conditions`: JSON DSL that must be applied to the query\n\n#### Response Schema\n| Field | Type | Description |\n|-------|------|-------------|\n| `filter_type` | string | `'granted_all'`, `'denied_all'`, or `'conditions'` |\n| `conditions_dsl` | object | JSON condition DSL (only when `filter_type='conditions'`) |\n| `has_context_refs` | boolean | Whether conditions originally had `$context.*` or `$principal.*` references |\n\n#### Condition Evaluation \u0026 Simplification\nThe system intelligently evaluates and simplifies conditions:\n- **Principal/Context conditions**: Evaluated server-side (e.g., `$principal.department = 'Sales'`)\n- **Resource conditions**: Returned in DSL for database-side evaluation\n- **Short-circuit logic**: AND returns `denied_all` on first false; OR returns `granted_all` on first true\n- **Simplification**: Removes evaluated `true` from AND, `false` from OR\n\n#### Integration with SearchQuery DSL\nThe returned `conditions_dsl` is compatible with `search_query_dsl` library:\n```python\nfrom search_query_dsl import SearchQuery, ABACConditionConverter\n\n# 1. Get authorization conditions\nauth_result = await client.auth.get_authorization_conditions(\n    resource_type_name=\"Document\",\n    action_name=\"read\"\n)\n\nif auth_result.filter_type == \"denied_all\":\n    return []  # No access\nelif auth_result.filter_type == \"granted_all\":\n    # Execute query without auth filter\n    results = await execute_query(user_query)\nelse:\n    # Convert and merge with user query\n    auth_query = ABACConditionConverter.convert(auth_result.conditions_dsl)\n    merged_query = user_query.merge(auth_query)\n    results = await execute_query(merged_query)\n```\n\n### 2.12. Public Access Strategy (3-Level Authorization)\nThe system implements a high-performance **3-Level Waterfall** strategy to handle public vs. private resources efficiently:\n\n1.  **Level 1: Floodgate (Public Flag)**\n    -   **Mechanism**: If a *Resource Type* is marked `is_public=True`, **ALL** resources of that type are instantly accessible to **everyone** (including anonymous users).\n    -   **Performance**: Zero DB lookups for ACLs. Immediate `RETURN TRUE`.\n\n2.  **Level 2: Pattern (Type-Level ACL)**\n    -   **Mechanism**: An ACL defined with `resource_id=NULL` applies to **ALL** resources of that type.\n    -   **Use Case**: \"All *InternalDocs* are viewable by *Employees*.\"\n    -   **Performance**: Single ACL row evaluation.\n\n3.  **Level 3: Exception (Resource-Level ACL)**\n    -   **Mechanism**: An ACL targeting a specific `resource_id`.\n    -   **Use Case**: \"This specific *SecretDoc* is visible ONLY to *Alice* (even if the type is private).\"\n    -   **Performance**: Specific indexed lookup using the Resource ID.\n\n---\n\n## 3. Performance Optimizations\n\nThe `/check-access` endpoint is optimized for per-request authorization with minimal latency.\n\n### 3.1. Multi-Layer Caching (Redis)\n\n| Cache Layer | Data | TTL |\n|-------------|------|-----|\n| **Realm Map** | Action/Type/Role IDs, `is_public` flags | 1 hour |\n| **Principal Cache** | User attributes + role IDs | 1 hour |\n| **Principal Roles** | Role IDs for a principal | 1 hour |\n| **External ID Mapping** | `external_id → resource_id` | 1 hour |\n| **Type Level Decision** | Type-level authorization cache (principal + type + action + roles) | 5 minutes |\n\n### 3.2. Database Indexes\n\nOptimized indexes for common query patterns:\n- `idx_external_ids_lookup` - External ID resolution\n- `idx_external_ids_reverse` - Reverse mapping (internal → external)\n- `idx_acl_matching` - ACL query optimization\n- `idx_acl_principal` / `idx_acl_role` - Partial indexes for specific ACL types\n\n### 3.3. Batch \u0026 Parallel Processing\n\n- **Batch External ID Resolution**: All external IDs across items resolved in a single query upfront\n- **Parallel Authorization**: Multiple access items processed concurrently via `asyncio.gather()`\n- **Resource ID Filter**: DB function accepts optional resource filter to avoid full table scans\n\n### 3.4. Connection Pool Tuning\n\nOptimized SQLAlchemy pool for high concurrency:\n```python\npool_size=20, max_overflow=30, pool_pre_ping=True, pool_recycle=300\n```\n\n### 3.5. Async Audit Queue\n\nProduction audit logs are pushed to Redis queue and processed asynchronously, adding zero latency to API responses. In test mode, logs write directly to DB for immediate verification.\n\n---\n\n## 4. Technology Stack\n\n| Component | Technology | Role |\n|-----------|------------|------|\n| **API** | FastAPI (Python 3.12) | High-performance Async REST Interface |\n| **Database** | PostgreSQL 16 + PostGIS | Relational Data, JSONB, Spatial Indexing |\n| **Cache** | Redis 7 | Name-to-ID Resolution (Realm/Action/Type lookups) |\n| **ORM** | SQLAlchemy 2.0 (Async) | Schema definition and Migration management |\n| **Identity** | Keycloak + python-keycloak | External Identity Provider \u0026 Sync |\n| **Scheduler** | APScheduler | Periodic Background Synchronization |\n| **Migrations** | Alembic | Version control for DB Schema \u0026 PL/pgSQL functions |\n| **Testing** | Pytest + AnyIO | Async Integration Testing |\n\n---\n\n## 5. Installation \u0026 Setup\n\n### Prerequisites\n- Docker \u0026 Docker Compose\n- Python 3.12+\n- Poetry\n\n### 5.1. Start Infrastructure\nDepending on your needs, you can start the infrastructure in several ways:\n\n**API Only (Default)**\n```bash\ndocker compose up -d\n```\nStarts PostgreSQL on port `5432` and Redis on `6379`.\n\n**Full Stack Development (with Hot-Reload UI)**\n```bash\ndocker compose --profile ui up\n```\nStarts backend (8000), DB, Redis, AND the UI dev server (5173).\n\n**Full Stack (Bundled UI)**\n```bash\ndocker compose up app-bundled\n```\nRuns the backend alongside a pre-built UI served directly on port `8000`.\n\n### 5.2. Install Dependencies\n```bash\npoetry install\n```\n\n### 5.3. Run Migrations\nApply the schema, triggers, and stored procedures.\n```bash\npoetry run alembic upgrade head\n```\n\n### 5.4. Run the API\n```bash\npoetry run uvicorn app.main:app --reload\n```\nSwagger UI available at: `http://localhost:8000/docs`\n\n---\n\n## 6. API Reference\n\nFull API documentation including the **Realm Management API** is available via the interactive **Swagger UI**.\n- **URL**: `http://localhost:8000/docs`\n- **Key Resources**:\n  - `Auth`: Token generation and access checks.\n  - `Realms`: Realm CRUD, Sync Trigger, Entity Management.\n  - `Manifest`: Declarative configuration import/export.\n\n### Manifest Endpoints\n\n#### Export Realm Configuration\n**GET** `/api/v1/realms/{realm_name}/manifest`\n\nExports the complete realm configuration as a JSON manifest, including all entities (resource types, actions, roles, principals, resources, and ACLs).\n\n#### Apply Manifest (Server-Side)\n**POST** `/api/v1/manifest/apply?mode=update`\n\nUpload and apply a manifest file on the server. Useful for large manifests.\n\n**Query Parameters**:\n- `mode`: Application mode (`replace`, `create`, or `update`)\n\n**Request**: Multipart file upload with JSON manifest.\n\n---\n\n### Check Access Endpoint\n**POST** `/api/v1/check-access`\n\n#### Request Schema\n| Field | Type | Description |\n|-------|------|-------------|\n| `realm_name` | string | **Required**. The Realm to check against (e.g., \"DefenseRealm\"). |\n| `req_access` | array | **Required**. List of requested permissions. |\n| `req_access[].action_name` | string | The action being performed (e.g., \"view\", \"edit\"). |\n| `req_access[].resource_type_name` | string | The type of resource (e.g., \"Document\", \"Zone\"). |\n| `req_access[].return_type` | string | \"id_list\" (default) or \"decision\" (boolean). |\n| `req_access[].external_resource_ids` | array | **Optional**. List of external strings (e.g., `[\"EXT-1\"]`). |\n| `role_names` | array | **Optional**. Override/Switch active roles (e.g., `[\"Manager\"]`). |\n| `auth_context` | object | **Optional**. Dynamic context variables (e.g., `ip`, `location`). |\n\n#### Curl Example\n```bash\ncurl -X POST \"http://localhost:8000/api/v1/check-access\" \\\n  -H \"Authorization: Bearer \u003cYOUR_JWT_TOKEN\u003e\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"realm_name\": \"DefenseRealm\",\n    \"req_access\": [\n      { \n        \"resource_type_name\": \"Document\", \n        \"action_name\": \"view\",\n        \"return_type\": \"id_list\"\n      }\n    ],\n    \"role_names\": [\"Manager\"],\n    \"auth_context\": {\n      \"ip\": \"192.168.1.50\",\n      \"location\": \"POINT(5 5)\"\n    }\n  }'\n```\n\n#### Response Example\n```json\n{\n  \"results\": [\n    {\n      \"action_name\": \"view\",\n      \"resource_type_name\": \"Document\",\n      \"answer\": [\"DOC-001\"] \n    }\n  ]\n}\n```\n\n---\n\n### Get Authorization Conditions Endpoint\n**POST** `/api/v1/get-authorization-conditions`\n\nReturns authorization conditions as JSON DSL for single-query authorization patterns.\n\n#### Request Schema\n| Field | Type | Description |\n|-------|------|-------------|\n| `realm_name` | string | **Required**. The Realm to check against. |\n| `resource_type_name` | string | **Required**. The type of resource (e.g., \"Document\"). |\n| `action_name` | string | **Required**. The action being performed (e.g., \"read\"). |\n| `role_names` | array | **Optional**. Override active roles. |\n| `auth_context` | object | **Optional**. Runtime context for `$context.*` resolution. |\n\n#### Curl Example\n```bash\ncurl -X POST \"http://localhost:8000/api/v1/get-authorization-conditions\" \\\n  -H \"Authorization: Bearer \u003cYOUR_JWT_TOKEN\u003e\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"realm_name\": \"DefenseRealm\",\n    \"resource_type_name\": \"Document\",\n    \"action_name\": \"read\",\n    \"auth_context\": {\n      \"ip\": \"10.0.0.5\",\n      \"department\": \"Engineering\"\n    }\n  }'\n```\n\n#### Response Examples\n\n**Granted All** (unconditional access):\n```json\n{\n  \"filter_type\": \"granted_all\",\n  \"conditions_dsl\": null,\n  \"has_context_refs\": false\n}\n```\n\n**Denied All** (no access):\n```json\n{\n  \"filter_type\": \"denied_all\",\n  \"conditions_dsl\": null,\n  \"has_context_refs\": false\n}\n```\n\n**Conditions** (apply to query):\n```json\n{\n  \"filter_type\": \"conditions\",\n  \"conditions_dsl\": {\n    \"op\": \"or\",\n    \"conditions\": [\n      {\n        \"op\": \"and\",\n        \"conditions\": [\n          { \"op\": \"=\", \"source\": \"resource\", \"attr\": \"external_id\", \"val\": \"DOC-001\" },\n          { \"op\": \"=\", \"source\": \"resource\", \"attr\": \"status\", \"val\": \"active\" }\n        ]\n      },\n      { \"op\": \"=\", \"source\": \"resource\", \"attr\": \"classification\", \"val\": \"public\" }\n    ]\n  },\n  \"has_context_refs\": true\n}\n```\n\n---\n\n## Python SDK\n\nFor programmatic access, a full-featured **async Python SDK** is available with support for both HTTP and direct database modes.\n\n### Installation\n\n**From PyPI** (when published):\n```bash\npip install stateful-abac-sdk\n# or with DB mode support\npip install stateful-abac-sdk[db]\n```\n\n**From GitHub**:\n```bash\npip install \"stateful-abac-sdk @ git+https://github.com/mgourlis/stateful-abac-policy-engine.git#subdirectory=python-sdk\"\n# or with DB mode support\npip install \"stateful-abac-sdk[db] @ git+https://github.com/mgourlis/stateful-abac-policy-engine.git#subdirectory=python-sdk\"\n```\nThe SDK depends on `stateful-abac-common`, which is installed automatically from the same repo. To install only the common package:\n```bash\npip install git+https://github.com/mgourlis/stateful-abac-policy-engine.git#subdirectory=common\n```\n\n### Quick Example\n```python\nfrom stateful_abac_sdk import StatefulABACClient\nfrom stateful_abac_sdk.models import CheckAccessItem\n\nasync with StatefulABACClient(\"http://localhost:8000/api/v1\", realm=\"my_realm\").connect(token=\"...\") as client:\n    result = await client.auth.check_access(\n        resources=[\n            CheckAccessItem(resource_type_name=\"Document\", action_name=\"view\")\n        ]\n    )\n    print(f\"Authorized IDs: {result.results[0].answer}\")\n```\n\n### Key Features\n- **Single Realm Scope**: Realm specified once at client creation - no `realm_id` in API calls\n- **Auto-Provisioning**: Realms auto-created on connect with optional Keycloak config\n- **Dual Mode**: HTTP mode (REST API) or DB mode (10-100x faster direct database access)\n- **Unified Interface**: Both modes implement `IStatefulABACClient` for consistent API\n- **Manifest Management**: Programmatic import/export of realm configurations\n\n📖 **Full SDK Documentation**: [`python-sdk/README.md`](python-sdk/README.md)\n\n---\n\n## 7. Testing\n\nThe project includes a comprehensive test suite covering standard flows and advanced edge cases.\n\n### Run All Tests\n```bash\npoetry run pytest\n```\n\n### Test Scope\n- **`tests/test_abac_flow.py`**: End-to-end integration test (Setup Realm -\u003e Create Rule -\u003e Verify Access).\n- **`tests/test_advanced_scenarios.py`**:\n  - Validates `AND`/`OR` logic depth.\n  - Verifies RBAC inheritance (User -\u003e Role -\u003e Rule).\n  - Checks Context Variable substitution.\n  - Verifies `IN` operator logic.\n- **`tests/test_spatial_scenarios.py`**:\n  - **Proximity**: Validates `st_dwithin` (e.g., finding resources within 11km).\n  - **Geofencing**: Validates `st_contains` (User entering a Resource Polygon).\n- **`tests/test_external_integration.py`**:\n  - **External IDs**: Validates ID resolution and mapping.\n  - **Active Roles**: Validates role override logic.\n\n---\n\n## 8. Database Schema Summary\n\n```mermaid\nerDiagram\n    REALM ||--o{ RESOURCE_TYPE : has\n    REALM ||--o{ ACTION : has\n    REALM ||--o{ PRINCIPAL : contains\n    REALM ||--o{ AUTH_ROLE : contains\n    REALM ||--|| REALM_KEYCLOAK_CONFIG : configures\n    \n    RESOURCE_TYPE ||--o{ RESOURCE : classifies\n    RESOURCE_TYPE ||--o{ ACL : scopes\n    RESOURCE_TYPE ||--o{ EXTERNAL_IDS : scopes\n\n    ACTION ||--o{ ACL : scopes\n\n    PRINCIPAL ||--o{ PRINCIPAL_ROLES : assigned\n    AUTH_ROLE ||--o{ PRINCIPAL_ROLES : assigned\n    \n    RESOURCE ||--o{ EXTERNAL_IDS : maps_to\n    \n    REALM {\n        int id PK\n        string name\n        string description\n    }\n    \n    REALM_KEYCLOAK_CONFIG {\n        int id PK\n        int realm_id FK\n        string server_url\n        string keycloak_realm\n        string client_id\n        string client_secret\n        boolean verify_ssl\n        string public_key\n        string algorithm\n        jsonb settings\n        string sync_cron\n    }\n\n    RESOURCE_TYPE {\n        int id PK\n        string name\n        int realm_id FK\n    }\n\n    ACTION {\n        int id PK\n        string name\n        int realm_id FK\n    }\n    \n    PRINCIPAL {\n        int id PK\n        string username\n        int realm_id FK\n        jsonb attributes\n    }\n\n    AUTH_ROLE {\n        int id PK\n        string name\n        int realm_id FK\n        jsonb attributes\n    }\n\n    PRINCIPAL_ROLES {\n        int principal_id FK\n        int role_id FK\n    }\n    \n    EXTERNAL_IDS {\n        int resource_id FK\n        int realm_id FK\n        int resource_type_id FK\n        text external_id PK\n    }\n    \n    RESOURCE {\n        int id PK\n        int realm_id FK\n        int resource_type_id FK\n        geometry geometry\n        jsonb attributes\n        int owner_id\n    }\n    \n    ACL {\n        int principal_id FK \"Nullable\"\n        int role_id FK \"Nullable\"\n        int realm_id PK\n        int resource_type_id PK\n        int action_id PK\n        int resource_id \"Nullable\"\n        jsonb conditions\n        text compiled_sql\n    }\n\n    AUTHORIZATION_LOG {\n        int id PK\n        timestamp timestamp\n        int realm_id\n        int principal_id\n        string action_name\n        string resource_type_name\n        boolean decision\n        jsonb resource_ids\n    }\n```\n\n---\n\n## 9. ACL Examples\n\nThis section provides concrete examples of creating Access Control Lists (ACLs) and verifying them using the `/check-access` endpoint.\n\n### 9.1. Simple Attribute Match\n*Scenario*: Allow access if the resource's `status` attribute is \"active\".\n\n**ACL Condition SQL** `(resource.attributes-\u003e\u003e'status' = 'active')`:\n```json\n{\n  \"op\": \"=\",\n  \"source\": \"resource\",\n  \"attr\": \"status\",\n  \"val\": \"active\"\n}\n```\n\n**Verification Check**:\n```bash\ncurl -X POST \"http://localhost:8000/api/v1/check-access\" \\\n  -H \"Authorization: Bearer \u003cTOKEN\u003e\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"realm_name\": \"DemoRealm\",\n    \"req_access\": [\n      {\n        \"resource_type_name\": \"Document\",\n        \"action_name\": \"view\",\n        \"return_type\": \"id_list\"\n      }\n    ],\n    \"auth_context\": {}\n  }'\n```\n\n### 9.2. Advanced / Nested Logic\n*Scenario*: Allow access if the resource is \"public\" OR (the user is in the \"Finance\" department AND the resource is \"internal\").\n\n**ACL Condition SQL**:\n```json\n{\n  \"op\": \"or\",\n  \"conditions\": [\n    {\n      \"op\": \"=\",\n      \"source\": \"resource\",\n      \"attr\": \"classification\",\n      \"val\": \"public\"\n    },\n    {\n      \"op\": \"and\",\n      \"conditions\": [\n        {\n          \"op\": \"=\",\n          \"source\": \"principal\",\n          \"attr\": \"department\",\n          \"val\": \"Finance\"\n        },\n        {\n          \"op\": \"=\",\n          \"source\": \"resource\",\n          \"attr\": \"classification\",\n          \"val\": \"internal\"\n        }\n      ]\n    }\n  ]\n}\n```\n\n**Verification Check**:\n```bash\ncurl -X POST \"http://localhost:8000/api/v1/check-access\" \\\n  -H \"Authorization: Bearer \u003cTOKEN\u003e\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"realm_name\": \"DemoRealm\",\n    \"req_access\": [\n      { \"resource_type_name\": \"Document\", \"action_name\": \"view\" }\n    ],\n    \"auth_context\": {}\n  }'\n```\n*Note*: The system automatically injects principal attributes from the authenticated user's profile.\n\n### 9.3. Geospatial Control (PostGIS)\n*Scenario*: Allow access only if the user is within 5km (5000m) of the resource's location.\n\n**ACL Condition SQL** `ST_DWithin(resource.geometry, ST_SetSRID(ST_GeomFromGeoJSON($context.location), 3857), 5000)`:\n```json\n{\n  \"op\": \"st_dwithin\",\n  \"attr\": \"geometry\",\n  \"val\": \"$context.location\",\n  \"args\": 5000\n}\n```\n\n**Verification Check** (User at `[23.7, 37.9]` checking against resources near them):\n```bash\ncurl -X POST \"http://localhost:8000/api/v1/check-access\" \\\n  -H \"Authorization: Bearer \u003cTOKEN\u003e\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"realm_name\": \"DemoRealm\",\n    \"req_access\": [\n      { \"resource_type_name\": \"Zone\", \"action_name\": \"enter\" }\n    ],\n    \"auth_context\": {\n      \"location\": {\n        \"type\": \"Point\",\n        \"coordinates\": [23.7, 37.9]\n      }\n    }\n  }'\n```\n\n### 9.4. Context, Resource, and Principal Sources\n*Scenario*: Allow access if:\n1. The **Context** IP is \"10.0.0.5\"\n2. The **Resource** owner ID matches the **Principal** ID (Ownership Check).\n\n**ACL Condition SQL**:\n```json\n{\n  \"op\": \"and\",\n  \"conditions\": [\n    {\n      \"op\": \"=\",\n      \"source\": \"context\",\n      \"attr\": \"ip\",\n      \"val\": \"10.0.0.5\"\n    },\n    {\n      \"op\": \"=\",\n      \"source\": \"resource\",\n      \"attr\": \"owner_id\",\n      \"val\": \"$principal.id\"\n    }\n  ]\n}\n```\n\n**Verification Check**:\n```bash\ncurl -X POST \"http://localhost:8000/api/v1/check-access\" \\\n  -H \"Authorization: Bearer \u003cTOKEN\u003e\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"realm_name\": \"DemoRealm\",\n    \"req_access\": [\n      { \"resource_type_name\": \"File\", \"action_name\": \"delete\" }\n    ],\n    }\n  }'\n```\n\n### 9.5. Geospatial: Geofencing (Polygon)\n*Scenario*: Allow access only if the user's location is **inside** the Resource's defined polygon (e.g., a \"Restricted Zone\").\n\n**ACL Condition SQL** `ST_Intersects(resource.geometry, ST_SetSRID(ST_GeomFromGeoJSON($context.user_point), 3857))`:\n```json\n{\n  \"op\": \"st_intersects\",\n  \"attr\": \"geometry\",\n  \"val\": \"$context.user_point\"\n}\n```\n\n**Verification Check**:\n```bash\ncurl -X POST \"http://localhost:8000/api/v1/check-access\" \\\n  -H \"Authorization: Bearer \u003cTOKEN\u003e\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"realm_name\": \"DemoRealm\",\n    \"req_access\": [\n      { \"resource_type_name\": \"Zone\", \"action_name\": \"enter\" }\n    ],\n    \"auth_context\": {\n      \"user_point\": {\n        \"type\": \"Point\",\n        \"coordinates\": [23.7, 37.9]\n      }\n    }\n  }'\n```\n\n### 9.6. Time-Based Context\n*Scenario*: Allow access only after a specific time (e.g., \"Shift Start\").\n*Note*: This relies on ISO 8601 string comparison.\n\n**ACL Condition SQL**:\n```json\n{\n  \"op\": \"\u003e=\",\n  \"source\": \"context\",\n  \"attr\": \"current_time\",\n  \"val\": \"2023-01-01T09:00:00Z\"\n}\n```\n\n**Verification Check**:\n```bash\ncurl -X POST \"http://localhost:8000/api/v1/check-access\" \\\n  -H \"Authorization: Bearer \u003cTOKEN\u003e\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"realm_name\": \"DemoRealm\",\n    \"req_access\": [ { \"resource_type_name\": \"ShiftReport\", \"action_name\": \"read\" } ],\n    \"auth_context\": {\n      \"current_time\": \"2023-01-01T09:30:00Z\"\n    }\n  }'\n```\n\n### 9.7. Nested Principal Attributes (Hierarchical ABAC)\n*Scenario*: Allow access if the user's `security_profile.clearance.level` is greater than or equal to 5.\n\n**ACL Condition SQL** `(p_ctx-\u003e'principal'-\u003e'security_profile'-\u003e'clearance'-\u003e\u003e'level')::numeric \u003e= 5`:\n```json\n{\n  \"op\": \"\u003e=\",\n  \"source\": \"principal\",\n  \"attr\": \"security_profile.clearance.level\",\n  \"val\": 5\n}\n```\n\n*Note*: The compiler automatically handles the nested JSON pathing (`-\u003e` vs `-\u003e\u003e`) based on the dot notation.\n\n**Verification Check**:\n```bash\n# Assuming the Principal has attributes:\n# { \"security_profile\": { \"clearance\": { \"level\": 7 } } }\n\ncurl -X POST \"http://localhost:8000/api/v1/check-access\" \\\n  -H \"Authorization: Bearer \u003cTOKEN\u003e\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"realm_name\": \"DemoRealm\",\n    \"req_access\": [ { \"resource_type_name\": \"ClassifiedDoc\", \"action_name\": \"read\" } ],\n    \"auth_context\": {}\n  }'\n```\n\n---\n\n## 📄 License\n\nThis project is licensed under the [MIT License](LICENSE).\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmgourlis%2Fstateful-abac-policy-engine","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmgourlis%2Fstateful-abac-policy-engine","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmgourlis%2Fstateful-abac-policy-engine/lists"}