{"id":21069779,"url":"https://github.com/jqhoogland/sqlalchemy_authorize","last_synced_at":"2025-05-16T04:34:33.745Z","repository":{"id":36991072,"uuid":"470732563","full_name":"jqhoogland/sqlalchemy_authorize","owner":"jqhoogland","description":"An unopinionated sqlalchemy extension to enforce role-/relation-/attribute-based access control.","archived":false,"fork":false,"pushed_at":"2024-12-07T02:44:56.000Z","size":75,"stargazers_count":4,"open_issues_count":13,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-22T04:08:16.281Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/jqhoogland.png","metadata":{"files":{"readme":"README.rst","changelog":"HISTORY.rst","contributing":"CONTRIBUTING.rst","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":"AUTHORS.rst","dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-03-16T19:55:48.000Z","updated_at":"2023-03-07T05:36:58.000Z","dependencies_parsed_at":"2023-02-12T05:45:19.490Z","dependency_job_id":"e1a55df8-2f77-4258-a725-3ff9cf3decde","html_url":"https://github.com/jqhoogland/sqlalchemy_authorize","commit_stats":{"total_commits":24,"total_committers":2,"mean_commits":12.0,"dds":"0.45833333333333337","last_synced_commit":"2257290907a7225019cc6d1e4e2b2dba7288a956"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jqhoogland%2Fsqlalchemy_authorize","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jqhoogland%2Fsqlalchemy_authorize/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jqhoogland%2Fsqlalchemy_authorize/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jqhoogland%2Fsqlalchemy_authorize/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jqhoogland","download_url":"https://codeload.github.com/jqhoogland/sqlalchemy_authorize/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254470302,"owners_count":22076566,"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","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":[],"created_at":"2024-11-19T18:37:09.730Z","updated_at":"2025-05-16T04:34:28.735Z","avatar_url":"https://github.com/jqhoogland.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"Disclaimer: I now think this is an absolutely awful idea and that you should run away from this as fast as possible because it introduces horribly tightly coupled garbage in your models that makes everything worse over time. I only leave it up as a warning to future generations of what not to do. Keep you sqlalchemy models lean. \n\n\n\n\n\n=====================\nSQL Alchemy Authorize\n=====================\n\n.. image:: https://img.shields.io/pypi/v/sqlalchemy_authorize.svg\n        :target: https://pypi.python.org/pypi/sqlalchemy_authorize\n\n.. image:: https://readthedocs.org/projects/sqlalchemy-authorize/badge/?version=latest\n        :target: https://sqlalchemy-authorize.readthedocs.io/en/latest/?version=latest\n        :alt: Documentation Status\n\n\n.. image:: https://pyup.io/repos/github/jqhoogland/sqlalchemy_authorize/shield.svg\n     :target: https://pyup.io/repos/github/jqhoogland/sqlalchemy_authorize/\n     :alt: Updates\n\n\nAn unopinionated extension to enforce field-level access control.\n\nFor Documentation: https://sqlalchemy-authorize.readthedocs.io.\n\nHow to use\n----------\n\nJust insert the appropriate ``PermissionsMixin`` in your model definition. For now,\nthat means the ``OsoPermissionsMixin`` (put it before ``db.Model`` / ``Base``).\n\nLet's look at an easy role-based example.\n\nIn your ``models.py``::\n\n    class User(OsoPermissionsMixin, db.Model):\n        __tablename__ = 'user'\n\n        # ``load_permissions`` is a convenience method for creating a\n        # permissions dictionary of the shape:\n        # {\"role_1\": {\"action_1\": [\"field_1\", \"field_2\", ...], ...}, ...}\n        __permissions__ = OsoPermissionsMixin.load_permissions(\n            read=[\"id\", \"username\"],\n            self=[\n                ([\"create\", \"update\"], [\"username\", \"fullname\"]),\n                \"read\",\n                \"delete\"\n            ],\n            admin=[\n                ([\"create\", \"update\", \"read\", \"delete\"], (\"id\", \"username\", \"fullname\", \"is_admin\")\n            ]\n        )\n\n        id = sa.Column(sa.String(128), primary_key=True)\n        username = sa.Column(sa.String(128), nullable=False)\n        fullname = sa.Column(sa.String(128), nullable=False)\n        ssn = sa.Column(sa.String(10), nullable=True)\n        is_admin = sa.Column(sa.Boolean, default=False)\n\nThen, in your `polar policy`_, write something like::\n\n    has_role(user: User, \"self\": other: User) if user.id == other.id;\n    has_role(user: User, \"admin\": _resource) if user.is_admin;\n\n    # OsoPermissionsMixin provides `.role` and `.authorized_fields`\n    allow_field(user: User, action, resource, field) if\n        role in resource.roles and\n        has_role(user, role, resource) and\n        (f in resource.authorized_fields(role, action) and\n        (f = \"*\" or f = field)); # to match a wildcard\n\n    # ...\n\nFor the full example, check out ``rbac.polar``.\n\nNow, we can start having fun::\n\n    admin = User(id=\"1\", username=\"root\", is_admin=True)\n\n    # This won't work because the current user is anonymous\n    # and has no create permissions on `User.username`\n    john_doe = User(username=\"john_doe\", check_create=True)  # oso.exceptions.ForbiddenError\n\n    with user_set(app, admin): # A helper context that sets `flask.g.user`\n        john_doe = User(username=\"john_doe\", check_create=True)\n        john_doe.id = \"2\"\n\n    john_doe.username, john_doe.id  # ('john_doe', '2')\n\n    with user_set(app, john_doe):\n        john_doe.username = \"doe_john\"\n\n        # This won't work because John only has update permissions on `username` and `fullname`\n        john_doe.id = \"3\"   # oso.exceptions.ForbiddenError\n\n    john_doe.username, john_doe.id # ('doe_john', '2')\n\nFor more details and options, check out ``BasePermissionsMixin`` and ``OsoPermissionsMixin``.\nRationale\n---------\n\n``sqlalchemy_authorize`` is a sqlalchemy extension designed to complement `sqlalchemy-oso`_.\nWhere `sqlalchemy-oso`_ provides authorization at the *row level* in the *data-access layer*\n(it modifies your queries so you pull only authorized entries from your database),\n``sqlalchemy_authorize`` operates at the *field level* in the `ORM layer` (it checks\nwhether users have permission before invoking ``__setattr__``, ``__getattribute__``,\nand ``__delattr__`` on your models).\n\nThe use I originally had in mind was to separate authorization from graphql in\n`Graphene-SQLAlchemy`_: to make it easier to create graphql-queryable models without\nsubstantial authorization boilerplate in the resolvers (`which is not recommended`_).\n\nReally though, the use is broader than both `Graphene-SQLAlchemy`_ and `sqlalchemy-oso`_.\nYes, there a bunch of other libraries for enforcing authorization with SQLAlchemy\n(and you should take a look at them before deciding to use this):\n\n* `Flask-Authorize \u003chttps://github.com/bprinty/Flask-Authorize\u003e`_\n* `Flask Principal \u003chttps://pythonhosted.org/Flask-Principal/\u003e`_\n* `Flask ACL \u003chttps://mikeboers.github.io/Flask-ACL/\u003e`_\n* `Flask RBAC \u003chttps://flask-rbac.readthedocs.io/en/latest/\u003e`_\n* `Flask Allows \u003chttps://github.com/justanr/flask-allows\u003e`_\n* `Flask Bouncer \u003chttps://github.com/bouncer-app/flask-bouncer\u003e`_\n\nStill, I decided to go ahead and throw together this library because:\n\n*    These options are Flask-specific and check permissions via decorators.\n     I wanted an option that isn't opt-in but opt-out, i.e., authorization by default.\n*    Many of these options assume you'll be authorizing at the *row level*, and (especially for the graphql use case) I needed field-level permissions.\n*    Many of the solutions are pretty opinionated about how you should be authorizing (and assume role-based access control).\n     I wanted a less opinionated \"real-world\" solution that lets me pick and choose from `role-, relation- and attribute-based access control`_.\n\nIf any of that resonates with you, glad you're here.\n\nThis is still a very early-stage library, and I discourage you from using it in production\nuntil I've tested in more thoroughly. Let me modify that: you're more than welcome to use it,\nsince, if there is one thing you should be testing anyway, it's authorization.\n\nGo ahead, just be very careful.\n\nMisc\n----\n\n* Free software: MIT license\n* Documentation: https://sqlalchemy-authorize.readthedocs.io.\n\n\nTimeline\n--------\n\n- [ ] More testing.\n- [ ] Flesh out the oso example.\n- [ ] Implement a non-oso role-based extension.\n- [ ] Check row-level create/delete permissions. (This is currently only on the field level).\n\nCredits\n-------\n\nThis package was created with Cookiecutter_ and the `audreyr/cookiecutter-pypackage`_ project template.\n\n.. _Cookiecutter: https://github.com/audreyr/cookiecutter\n.. _`audreyr/cookiecutter-pypackage`: https://github.com/audreyr/cookiecutter-pypackage\n.. _`sqlalchemy-oso`: https://github.com/osohq/oso/tree/main/languages/python/sqlalchemy-oso\n.. _`Graphene-SQLAlchemy`: https://docs.graphene-python.org/projects/sqlalchemy/en/latest/\n.. _`which is not recommended`: https://graphql.org/learn/authorization/\n.. _`role-, relation- and attribute-based access control`: https://www.osohq.com/academy\n.. _`polar policy`: https://docs.osohq.com/guides/policies.html\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjqhoogland%2Fsqlalchemy_authorize","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjqhoogland%2Fsqlalchemy_authorize","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjqhoogland%2Fsqlalchemy_authorize/lists"}