Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ jobs:
strategy:
matrix:
python:
- "3.7"
- "3.8"
- "3.10"
platform:
- ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,4 @@ MANIFEST
.venv*/
.conda*/
.python-version
notebooks
21 changes: 10 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ pip install fastapi-rowsecurity

## Basic Usage

In your SQLAlchemy model, create a `classmethod` named `__rls_policies__` that returns a list of `Permissive` or `Restrictive` policies:
In your SQLAlchemy model, create an attribute named `__rls_policies__` that is a list of `Permissive` or `Restrictive` policies:

```py
from fastapi_rowsecurity import Permissive, set_rls_policies
from fastapi_rowsecurity import Permissive, register_rls
from fastapi_rowsecurity.principals import Authenticated, UserOwner

Base = declarative_base()
set_rls_policies(Base) # <- create all policies
register_rls(Base) # <- create all policies


class Item(Base):
Expand All @@ -48,18 +48,17 @@ class Item(Base):
owner_id = Column(Integer, ForeignKey("users.id"))
owner = relationship("User", back_populates="items")

@classmethod
def __rls_policies__(cls):
return [
Permissive(principal=Authenticated, policy="SELECT"),
Permissive(principal=UserOwner, policy=["INSERT", "UPDATE", "DELETE"]),

__rls_policies__ = [
Permissive(expr=Authenticated, cmd="SELECT"),
Permissive(expr=UserOwner, cmd=["INSERT", "UPDATE", "DELETE"]),
]
```

The above implies that any authenticated user can read all items; but can only insert, update or delete owned items.

- `principal`: any Boolean expression as a string;
- `policy`: any of `ALL`/`SELECT`/`INSERT`/`UPDATE`/`DELETE`.
- `expr`: any Boolean expression as a string;
- `cmd`: any command of `ALL`/`SELECT`/`INSERT`/`UPDATE`/`DELETE`.

Next, attach the `current_user_id` (or other [runtime parameters](https://www.postgresql.org/docs/current/sql-set.html) that you need) to the user session:

Expand All @@ -78,9 +77,9 @@ Find a simple example in the ![tests](./tests/simple_model.py).
then ...

- [ ] Support for Alembic
- [ ] How to deal with `BYPASSRLS` such as table owners?
- [ ] When item is tried to delete, no error is raised?
- [ ] Python 3.11
- [ ] Coverage report

## Final note

Expand Down
11 changes: 6 additions & 5 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@

[metadata]
name = fastapi-rowsecurity
description = Add a short description here!
description = Row-Level Security (RLS) in SQLAlchemy.
author = jwdobken
author_email = joost@dobken.nl
license = MIT
license_files = LICENSE.txt
long_description = file: README.md
long_description_content_type = text/markdown; charset=UTF-8; variant=GFM
url = https://github.com/pyscaffold/pyscaffold/
url = https://github.com/JWDobken/fastapi-rowsecurity
# Add here related links, for example:
project_urls =
Documentation = https://pyscaffold.org/
# Source = https://github.com/pyscaffold/pyscaffold/
Documentation = https://github.com/JWDobken/fastapi-rowsecurity
Source = https://github.com/JWDobken/fastapi-rowsecurity
# Changelog = https://pyscaffold.org/en/latest/changelog.html
# Tracker = https://github.com/pyscaffold/pyscaffold/issues
# Conda-Forge = https://anaconda.org/conda-forge/pyscaffold
Expand Down Expand Up @@ -49,8 +49,8 @@ package_dir =
# For more information, check out https://semver.org/.
install_requires =
importlib-metadata; python_version<"3.8"
alembic_utils>=0.8
pydantic>=2.5
sqlalchemy


[options.packages.find]
Expand All @@ -72,6 +72,7 @@ testing =
faker
asyncpg
greenlet
alembic

[options.entry_points]
# Add here console scripts like:
Expand Down
4 changes: 2 additions & 2 deletions src/fastapi_rowsecurity/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
finally:
del version, PackageNotFoundError

from .main import set_rls_policies
from .register_rls import register_rls
from .schemas import Permissive, Policy, Restrictive

__all__ = ["set_rls_policies", "Permissive", "Policy", "Restrictive"]
__all__ = ["register_rls", "Permissive", "Policy", "Restrictive"]
24 changes: 24 additions & 0 deletions src/fastapi_rowsecurity/create_policies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from typing import Type

from sqlalchemy import text
from sqlalchemy.engine import Connection
from sqlalchemy.ext.declarative import DeclarativeMeta


def create_policies(Base: Type[DeclarativeMeta], connection: Connection):
"""Create policies for `Base.metadata.create_all()`."""
for table, settings in Base.metadata.info["rls_policies"].items():
# enable
stmt = text(f"ALTER TABLE {table} ENABLE ROW LEVEL SECURITY;")
connection.execute(stmt)
# force by default
stmt = text(f"ALTER TABLE {table} FORCE ROW LEVEL SECURITY;")
connection.execute(stmt)
# policies
print("SETTINGS", settings)
for ix, policy in enumerate(settings):
for pol_stmt in policy.get_sql_policies(
table_name=table, name_suffix=str(ix)
):
connection.execute(pol_stmt)
connection.commit()
31 changes: 0 additions & 31 deletions src/fastapi_rowsecurity/functions.py

This file was deleted.

15 changes: 0 additions & 15 deletions src/fastapi_rowsecurity/main.py

This file was deleted.

61 changes: 0 additions & 61 deletions src/fastapi_rowsecurity/policies.py

This file was deleted.

Loading