{"id":22374418,"url":"https://github.com/kodemore/targe","last_synced_at":"2025-07-30T21:33:20.105Z","repository":{"id":45163386,"uuid":"399898370","full_name":"kodemore/targe","owner":"kodemore","description":"Policy based authorization library built for python.","archived":false,"fork":false,"pushed_at":"2023-04-08T12:40:21.000Z","size":587,"stargazers_count":10,"open_issues_count":0,"forks_count":2,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-04-06T15:02:40.611Z","etag":null,"topics":["acl","authorization","permissions","policy","python","roles"],"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/kodemore.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}},"created_at":"2021-08-25T17:10:21.000Z","updated_at":"2024-08-16T18:04:19.000Z","dependencies_parsed_at":"2022-08-28T13:11:25.552Z","dependency_job_id":null,"html_url":"https://github.com/kodemore/targe","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/kodemore/targe","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kodemore%2Ftarge","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kodemore%2Ftarge/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kodemore%2Ftarge/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kodemore%2Ftarge/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kodemore","download_url":"https://codeload.github.com/kodemore/targe/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kodemore%2Ftarge/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":267945168,"owners_count":24170215,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-30T02:00:09.044Z","response_time":70,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["acl","authorization","permissions","policy","python","roles"],"created_at":"2024-12-04T21:17:01.142Z","updated_at":"2025-07-30T21:33:19.811Z","avatar_url":"https://github.com/kodemore.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Targe\nA powerful and flexible authorization library utilizing a policy system that reflects your domain.\n\n\n## Installation\n\nWith pip:\n```\npip install targe\n```\n\nor with poetry\n\n```\npoetry add targe\n```\n\n## Quick start\n\n```python\nfrom targe import Auth, ActorProvider, Actor, Policy\nfrom targe.errors import AccessDeniedError\n\n\n# This will provide an actor for the auth mechanism\nclass MyActorProvider(ActorProvider):\n    def get_actor(self, actor_id: str) -\u003e Actor:\n        return Actor(actor_id)\n\n\n# Initialise auth class\nauth = Auth(MyActorProvider())\n\n# Retrieve and authorize actor by its id\nauth.authorize(\"actor_id\")\n\n\n# `auth.guard` decorator assigns auth scope to a function and\n# protects it from non-authorized access\n@auth.guard(scope=\"protected\")\ndef protect_this() -\u003e None:\n    ...  # code that should be protected by auth\n\n\ntry:\n    protect_this()\nexcept AccessDeniedError:\n    ...  # this will fail as actor has no access to scope `protected`\n\nauth.actor.policies.append(Policy.allow(\"protected\"))  # add `protected` scope to actor policies\nprotect_this()  # now this works\n```\n\n## Features\n\n### Customisable and flexible policy system\nThe policy system in **Targe** is not limited to specific keywords like `read`, `write`, `create`, etc. \nIt uses scopes instead, scopes can hold any value that makes sense in your application's domain \nlike `salads : eat`. To increase flexibility and control over your domain **Targe** allows you to define dynamic\nscopes that can point to specific data in your application.\n\n### Minimal, close to 0 learning curve\nIf you already have some experience with other `acl` or `authorization` libraries there is \nalmost 0 learning curve. In order to start using the library you only need to learn these 5 methods:\n- `Auth.guard`\n- `Auth.guard_after`\n- `Policy.allow`\n- `Policy.deny`\n- `ActorProvider.get_actor`\n\n### Built-in audit log\nEverytime a guarded function is executed, the library creates a log entry. These log entries can be persisted\nand used later on to understand who, when, how and what has changed within your application.\n\n### Elegant and easy to use interface\nYou don't have to write complex `if` statements asserting whether a user has a given role or policy. \nAll of that happens automatically in one small `@guard` decorator, which can be attached to \nany function / method within your codebase, and easily removed if needed. \n\n# Usage\n\n## Execution flow\n\nThe following diagram is a high level representation of the execution flow:\n\n![Execution Flow](./docs/targe@2x-2.png)\n\n\nWhen a protected function gets called, the `targe.Auth` class checks whether the `actor` is accessible \n(`targe.Auth.init` is responsible for providing a valid instance of `targe.Actor`). \n\nIf for some reason the `actor` is not accessible (`Auth.init` was not called, or `targe.ActorProvider` has failed), \nan `UnauthorizedError` exception is raised. \n\nWhen an `actor` is present, the library will attempt to resolve the `scope` and assert whether the `actor` has the required\npolicy or access to the given scope. Once this is done, the library will automatically generate an audit log \nwhich can be persisted to a database for future reference.\n\nFinally, if actor has sufficient rights guarded function is executed, otherwise `targe.AccessDeniedError` is raised.\n\n## Actor\nAn actor represents an authenticated user in your application. Other important characteristics of an actor are:\n- An actor aggregates permissions and roles\n- An actor encapsulates its state and may act upon its change  \n- An actor knows whether is can access given scope\n- An actor's id is referenced in audit log  \n- An actor can be extended further to encapsulate your application logic \n\n### Creating a new actor\n\n```python\nfrom targe import Actor\n\nmy_actor = Actor(\"actor_id\")\n```\n\n### Assigning policies\n\n```python\nfrom targe import Actor, Policy\n\nmy_actor = Actor(\"actor_id\")\n\n# assign policies \nmy_actor.policies.append(Policy.allow(\"articles : update\"))\n```\n\n\u003e Note: whitespaces in scope are irrelevant, both `articles:update`, `articles : update`\n\u003e are equal from the library points of view.\n\n### Assigning roles\n\n```python\nfrom targe import Actor, Policy, Role\n\nmy_actor = Actor(\"actor_id\")\n\n# simple role\nuser_manager = Role(\"user_manager\")\nuser_manager.policies.append(Policy.allow(\"user:*\"))\n\n# assign role\nmy_actor.roles.append(user_manager)\n```\n\n### Providing an actor to the auth system\nBy default, the auth system does not know who is your actor and what it can do. \n\nTo provide information about your actor, you have to implement the `targe.ActorProvider` protocol, \nplease consider the following example:\n\n```python\nfrom targe import ActorProvider, Actor, Auth\n\n\nclass MyActorProvider(ActorProvider):\n    def get_actor(self, actor_id: str) -\u003e Actor:\n        ...  # you can query your database or do other relevant task to factory your instance of `targe.Actor`\n        return Actor(actor_id)\n\n\n# now we have to just instantiate auth and pass an instance of our ActorProvider implementation\nauth = Auth(MyActorProvider())\n\n# The following line will cause the auth system to use `MyActorProvider.get_actor` method.\nauth.authorize(\"actor_id\")\n```\n\n## Policies\n\n**Policy** is an object representing a logical rule that can either allow or deny accessing\ncertain areas of your application. \nOnce policies are created they can ba attached to a role or a user, to ensure fine-grained\naccess control.\n\nPolicies contain `scopes` and `effect`. The first one holds information how data is \nbeing accessed within your application (`read`, `write`, `update`, `etc`), the latter \ntell whether the scope is accessible or not.\n\nYou can define a policy like in the example below:\n```python\nfrom targe import Policy\n\npolicy = Policy.allow(scope=\"articles : update\")\n```\n\n### Scopes\n\nScope is a string representing a logical boundary within your application in which data is being \naccessed and/or manipulated. This string must follow the pattern [a-zA-Z][a-zA-Z0-9-_]*, whitespaces are ignored.\nCharacters like: `:`, `,`, `*` are allowed in the scope, but they have specific meaning:\n\n- `:` to build namespaced scopes\n- `,` to match multiple scope sections\n- `*` for pattern matching expressions\n\nScopes can be used in policies and in guarded functions. Use scopes in policies to set rules for existing logical \nboundaries in our application. To define those boundaries, scopes must be provided in the `Auth.guard` decorator, which\ndecorates a given function or method.\n\nThe following is a list of valid policy scopes:\n```\narticles\narticles : update\narticles : update : article_id\narticles : create, upate, delete\narticles : *\narticles : meta : set-*\narticles : meta : *Name\narticles : update : * : tags\n```\n\n#### Pattern matching\n\nLet's review the following code snippet which defines multiple policies:\n\n```python\nfrom targe import Policy\n\nPolicy.allow(\"article : meta : setKeywords\")\nPolicy.allow(\"article : meta : setVersion\")\nPolicy.allow(\"article : meta : setCategory\")\nPolicy.allow(\"article : meta : getKeywords\")\nPolicy.allow(\"article : meta : getVersion\")\nPolicy.allow(\"article : meta : getCategory\")\n```\n\nIt is quite a repetitive task which can be simplified by using pattern matching in the policy's scopes:\n\n```python\nfrom targe import Policy\n\nPolicy.allow(\"article : meta : set*\")\nPolicy.allow(\"article : meta : get*\")\n```\n\nAn asterisk at the end of each scope tells **Targe** to use the pattern matching mechanism. \nThe first policy might be interpreted as \"match all the scopes which start with the `article` namespace, followed by the `meta` \nnamespace, followed by a namespace that starts with `set`\". The second scope is very similar but the last namespace \nhas to start with `get` instead. \n\nHere are some examples to help you understand how pattern matching works:\n```\narticle : *\n```\nMatch all scopes that start with article namespace.\n```\narticle : * : id\n```\nMatch all scopes that start with an `article`, has `any` namespace after that and ends with a `id`.\n```\narticle : *Name\n```\nMatch all scopes that start with an `article` namespace and are followed by a namespace that ends with `Name`\n\nLet's now go back to our last example that we simplified with pattern matching. We can simplify it  \neven further with grouping. Let's consider the following code snippet:\n\n```python\nfrom targe import Policy\n\nPolicy.allow(\"article : meta : set*, get*\")\n```\n\nNow with the above policy we can match all the scopes that were presented at the beginning of this chapter.\n\n## Roles\n\nRole is a collection of policies with a unique name. Roles can also be \nused to build Role-based access control (RBAC), which is a simplified mechanism\nfor regulating access to part of your application based on the roles of an individual actor.\n\nThe following is an example where the `user_manager` role is defined:\n\n```python\nfrom targe import Role, Policy\n\nrole = Role(\"user_manager\")\n\n# You can also attach policies to a role, but it is not needed in a RBAC scenario\nrole.policies.append(Policy.allow(\"user : create, update, delete, read\"))\n```\n\n\u003e Role names must follow the [a-z][a-z0-9_-]+ pattern. A role name is also its identifier, \n\u003e thus they should be unique across your application.\n\n## Guarding function\n\nProtecting a function from unauthorized access is one of the **Targe**'s main objectives.\n\nWe can protect a function from unauthorized execution using two styles:\n- ACL style\n- RBAC style\n\nUse the RBAC style in scenarios where you have to just assert if an actor has a given role, and use the ACL style in other cases.\nThe ACL style is not only giving you more control over your resources but also automatically enables the audit log. \n\n### Guarding function - RBAC style example\n\nTo protect a function from unauthorized execution use the `Auth.guard(rbac=[...])` decorator with `rbac` as the argument. The `rbac`\nargument accepts a list of strings where each string is a role name that is required to execute annotated function.\n\n\u003e If more than one role is passed in the `rbac` argument, this means the actor has to own all of the required roles\n\u003e to execute the annotated function.\n\n```python\nfrom targe import ActorProvider, Actor, Auth\nfrom targe.errors import AccessDeniedError\n\n\nclass MyActorProvider(ActorProvider):\n    def get_actor(self, actor_id: str) -\u003e Actor:\n        return Actor(actor_id)\n\n\nauth = Auth(MyActorProvider())\n\nauth.authorize(\"actor_id\")\n\n\n@auth.guard(roles=[\"user_manager\"])  # Here we use `Auth.guard` decorator to protect `create_user` function\ndef create_user() -\u003e None:\n    ...\n\n\ntry:\n    create_user()\nexcept AccessDeniedError:\n    print(\"`create_user` is protected from unauthorized access.\")\n```\n\n\u003e Keep in mind you can still take advantage of audit logs in RBAC mode, \n\u003e the only requirement is to provide a `scope` argument in the `Auth.guard` decorator.\n\n### Guarding function - ACL style example\n\n```python\nfrom targe import ActorProvider, Actor, Auth\nfrom targe.errors import AccessDeniedError\n\nclass MyActorProvider(ActorProvider):\n    def get_actor(self, actor_id: str) -\u003e Actor:\n        return Actor(actor_id)\n    \nauth = Auth(MyActorProvider())\n\nauth.authorize(\"actor_id\")\n\n@auth.guard(scope=\"user : create\") \ndef create_user() -\u003e None:\n    ...\n\ntry:\n    create_user()\nexcept AccessDeniedError:\n    print(\"`create_user` is protected from unauthorized access.\") \n```\n\n#### Dynamic scopes\n\nSometimes you might run into scenarios where you would like to limit access to a given entity\nor group of entities. In these scenarios, dynamic scopes may come in handy. \nDynamic scopes contain placeholders for values held by arguments passed to a guarded function.\nEverytime a function is called, placeholders are replaced with corresponding values.\n\n```python\nfrom targe import ActorProvider, Actor, Auth, Policy\nfrom targe.errors import AccessDeniedError\n\nclass MyActorProvider(ActorProvider):\n    def get_actor(self, actor_id: str) -\u003e Actor:\n        # lest initialize actor and allow access to `articles:update:allowed_id` scope.\n        actor = Actor(actor_id)\n        actor.policies.append(Policy.allow(\"articles:update:allowed_id\"))\n        \n        return actor\n    \nauth = Auth(MyActorProvider())\n\nauth.authorize(\"actor_id\")\n\nclass Article:\n    def __init__(self, article_id: str, body: str):\n        self.article_id = article_id\n        self.body = body\n\n# Here we define a dynamic scope that holds reference to the function's parameter `article` \n# and tries to access its property `article_id`\n@auth.guard(scope=\"article : update : { article.article_id }\") \ndef update_article(article: Article) -\u003e None:\n    print(\"article updated\")\n\n# the following attempt will fail as the actor has access only to the `article:update:allowed_id` scope\ntry:\n    update_article(Article(\"other_id\", \"Lorem Ipsum\"))\nexcept AccessDeniedError:\n    print(\"`update_article` is protected from unauthorized access.\") \n\n# this line will succeed\nupdate_article(Article(\"allowed_id\", \"Lorem Ipsum\"))\n```\n\n### Overriding function guarding mechanism\n\nYou can override the default behavior of the guard mechanism in scenarios when it denies access to a guarded\nfunction. In order to do that, pass a callable object to the `Auth` initializer, like below:\n\n```python\nfrom targe import ActorProvider, Actor, Auth\n\nclass MyActorProvider(ActorProvider):\n    def get_actor(self, actor_id: str) -\u003e Actor:\n        return Actor(actor_id)\n    \ndef on_guard(actor: Actor, scope: str) -\u003e bool:\n    if scope == \"user:create\":\n        return True\n    \n    return False\n    \nauth = Auth(MyActorProvider(), on_guard=on_guard)\nauth.authorize(\"actor_id\")\n\n@auth.guard(scope=\"user:create\") \ndef create_user() -\u003e None:\n    ...\n\ncreate_user()\n```\n\nThe callable object must return a `bool` value (`True` in order to allow access, `False` to deny access) and accept two parameters:\n- `actor: targe.Actor` - an actor that is currently authorized in the system\n- `scope: str` - the scope assigned to the guarded function\n\n\n## Audit log\n\nThe audit log might be useful if you need to track an actor's activities in your application.\nBy default, all actor's actions against guarded functions are automatically recorded and stored\nin memory as long as the `scope` attribute is provided in the `Auth.guard` decorator. \n\n\u003e The `InMemoryAuditStore` class is a default in-memory implementation of the `AuditStore` protocol, which\n\u003e is instantiated by the `Auth` class if no other implementation is provided.\n\n\n### AuditEntry structure\n\n`targe.AuditEntry` is a representation of a single actor's action against a guarded function in your application.\n\nEach audit entry contains the following information:\n- **`entry_id`**: `str` - unique identifier 16 characters long\n- **`actor_id`**: `str` - id of authenticated actor may reference to a user in your application\n- **`scope`**: `str` - scope in which the function was executed, defined in guard decorator\n- **`status`**: `targe.AuditStatus` - tells whether execution of a guarded function was successful or not\n- **`created_on`**: `datetime.datetime` - the time that the action was initialized\n\n### Persisting audit log\n\nAs we already discussed, by default the audit log is stored only in memory, this behaviour \ncan be simply amended by implementing the `targe.AuditStore` protocol and passing an instance\nof the new implementation into `targe.Auth`'s initializer:\n\n```python\nfrom targe import AuditStore, AuditEntry, Auth, ActorProvider, Actor\nimport sqlite3\n\n\nclass MyActorProvider(ActorProvider):\n    def get_actor(self, actor_id: str) -\u003e Actor:\n        return Actor(actor_id)\n\n    \nclass MyAuditStore(AuditStore):\n    def __init__(self, connection: sqlite3.Connection):\n        self.connection = connection\n        \n    def append(self, log: AuditEntry) -\u003e None:\n        query = self.connection.cursor()\n        query.execute(\n            \"INSERT INTO audit_log VALUES(?, ?, ?, ?, ?)\",\n            (\n                log.entry_id,\n                log.actor_id,\n                log.scope,\n                log.status,\n                log.created_on\n             )\n        )\n        \ndb_connection = sqlite3.connect(\"audit_log.db\")\nauth = Auth(actor_provider=MyActorProvider(), audit_store=MyAuditStore(db_connection))\nauth.authorize(\"actor_id\")\n...\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkodemore%2Ftarge","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkodemore%2Ftarge","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkodemore%2Ftarge/lists"}