Metadata-Version: 2.0
Name: sqlalchemy-auth
Version: 1.2.0
Summary: Provides authorization mechanisms for SQLAlchemy
Home-page: https://github.com/dhiltonp/sqlalchemy_auth
Author: David P Hilton
Author-email: david.hilton.p@gmail.com
License: mit
Description-Content-Type: UNKNOWN
Platform: any
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Topic :: Database
Classifier: Operating System :: OS Independent
Requires-Dist: sqlalchemy

Overview
========

sqlalchemy\_auth provides authorization mechanisms for SQLAlchemy DB
access.

It is easy to use, and easy to bypass.

1. You set a ``badge`` on a session, and receive a ``badge`` within a
   query.
2. All mapped classes can add implicit filters on queries and implicit
   data on inserts.
3. All mapped classes can selectively block attribute access.

Your ``badge`` is shared between all queries and mapped class instances
within a session.

Getting Started
===============

Session
~~~~~~~

Create a session using the AuthSession and AuthQuery classes:

.. code:: python

    Session = sessionmaker(bind=engine, class_=AuthSession, query_cls=AuthQuery, badge=DENY)
    session = Session()

By default you don't need no stinking ``badge``. It is set to ``ALLOW``,
bypassing all auth mechanisms. Change ``badge`` from ``ALLOW`` to enable
authorization:

.. code:: python

    session.badge=badge

Temporarily switch ``badge``:

.. code:: python

    with session.switch_badge(badge):
        ...

``badge`` can be anything (the current user, their role, etc.), and will
be passed in to ``add_auth_filters`` or ``add_auth_insert_data`` (unless
it's ``ALLOW`` or ``DENY``).

Filters
~~~~~~~

To add filters, define ``add_auth_filters``:

.. code:: python

    class Data(Base):
        __tablename__ = "data"

        id = Column(Integer, primary_key=True)
        owner = Column(Integer)
        data = Column(String)

        @classmethod
        def add_auth_filters(cls, query, badge):
            return query.filter_by(owner=badge.user_id)

Inserts
~~~~~~~

To add data on insert, define ``add_auth_insert_data``

.. code:: python

    class Data(Base):
        __tablename__ = "data"

        id = Column(Integer, primary_key=True)
        owner = Column(Integer)
        data = Column(String)

        def add_auth_insert_data(self, badge):
            self.owner = badge.user_id

Default Filters and Inserts
~~~~~~~~~~~~~~~~~~~~~~~~~~~

If your ``Base`` inherits from ``AuthBase``, you will inherit no-op
``add_auth_filters`` and ``add_auth_insert_data`` methods.

Attribute Blocking
~~~~~~~~~~~~~~~~~~

To block attributes, inherit from the ``BlockBase`` class (you can also
use mixins instead of ``declarative_base(cls=BlockBase)``):

.. code:: python

    Base = declarative_base(cls=BlockBase)

    class AttributeCheck(Base):
        __tablename__ = "attributecheck"

        id = Column(Integer, primary_key=True)
        owner = Column(String)
        data = Column(String)
        secret = Column(String)

        def _blocked_read_attributes(self, badge):
            if self.owner == badge.user_id:
                return []
            return ["secret"]

        def _blocked_write_attributes(self, badge):
            return ["id", "owner"]

Four convenience methods are defined: ``readable_attrs()``,
``read_blocked_attrs()`` and ``writable_attrs()``,
``write_blocked_attrs()``. Only public attributes are returned.

Attribute blocking is only effective for instances of the mapped class.

Gotchas
=======

One Badge per Session/Query/Objects Group
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Only one badge exists between a session, its queries and returned
objects. For example:

.. code:: python

    session.badge = ALLOW
    query = session.query(Data)
    unfiltered = query.all()

    session.badge = badge
    filtered = query.all()

In this example, ``unfiltered`` will contain all Data objects, but the
same query later would return a ``filtered`` subset.

Scoped Session Usage
~~~~~~~~~~~~~~~~~~~~

To support ``scoped_session.query`` style syntax with ``badge`` and
``switch_badge``, you must run ``instrument_scoped_session`` on the
value returned by ``sqlalchemy.orm.scoped_session()``.

If you do not, setting ``badge`` will have no effect and calling
``switch_badge`` will raise
``AttributeError: 'scoped_session' object has no attribute 'switch_badge'``.

Attribute Blocking Limitations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Attribute blocking relies on the object being an instance of the class
with blocks. In the following example, ``add_auth_filters`` is applied,
but blocks are not:

.. code:: python

    obj = session.query(Class.attr, Class.blocked_attr).first()
    obj.blocked_attr = "foo"

Similarly, ``update`` bypasses attribute blocks:

.. code:: python

    query = session.query(Class.blocked).update({Class.blocked: "unchecked write"})

--------------

See auth\_query\_test.py for end-to-end examples.


