Skip to content

Commit

Permalink
feat: use sqlalchemy-utils PasswordType to store and hash user passwords
Browse files Browse the repository at this point in the history
  • Loading branch information
azmeuk committed Dec 1, 2023
1 parent 9463d0c commit a7e574f
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 13 deletions.
1 change: 1 addition & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Fixed
- Password reset and initialization mails were not sent at all the user
addresses if one email address could not be reached.
- Password comparision was too permissive on login.
- Encrypt passwords in the SQL backend.

[0.0.35] - 2023-11-25
=====================
Expand Down
10 changes: 8 additions & 2 deletions canaille/backends/sql/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,15 @@
from sqlalchemy.orm import reconstructor
from sqlalchemy.orm import relationship
from sqlalchemy_json import MutableJson
from sqlalchemy_utils import force_auto_coercion
from sqlalchemy_utils import PasswordType

from .backend import Backend
from .backend import Base
from .utils import TZDateTime

force_auto_coercion()


class SqlAlchemyModel(Model):
def __html__(self):
Expand Down Expand Up @@ -120,7 +124,9 @@ class User(canaille.core.models.User, Base, SqlAlchemyModel):
String, primary_key=True, default=lambda: str(uuid.uuid4())
)
user_name: Mapped[str] = mapped_column(String, unique=True, nullable=False)
password: Mapped[str] = mapped_column(String, nullable=True)
password: Mapped[str] = mapped_column(
PasswordType(schemes=["pbkdf2_sha512"]), nullable=True
)
preferred_language: Mapped[str] = mapped_column(String, nullable=True)
family_name: Mapped[str] = mapped_column(String, nullable=True)
given_name: Mapped[str] = mapped_column(String, nullable=True)
Expand Down Expand Up @@ -199,7 +205,7 @@ def get_from_login(cls, login=None, **kwargs):
return User.get(user_name=login)

def has_password(self):
return bool(self.password)
return self.password is not None

def check_password(self, password):
if password != self.password:
Expand Down
51 changes: 48 additions & 3 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,10 @@ python-ldap = {version = "^3.4.0", optional=true}
sentry-sdk = {version = "<2", optional=true, extras=["flask"]}

# extra : sql
sqlalchemy-json = {version = "^0.7.0", optional=true}
passlib = {version = "^1.7.4", optional=true}
sqlalchemy = {version = "^2.0.23", optional=true}
sqlalchemy-json = {version = "^0.7.0", optional=true}
sqlalchemy-utils = {version = "^0.41.1", optional=true}

[tool.poetry.group.doc]
optional = true
Expand Down Expand Up @@ -117,14 +119,17 @@ sentry = [
"sentry-sdk",
]
sql = [
"passlib",
"sqlalchemy",
"sqlalchemy-json",
"sqlalchemy-utils",
]
all = [
"click",
"email_validator",
"flask-babel",
"flask-themer",
"passlib",
"pycountry",
"pytz",
"toml",
Expand All @@ -133,6 +138,7 @@ all = [
"sentry-sdk",
"sqlalchemy",
"sqlalchemy-json",
"sqlalchemy-utils",
]

[tool.poetry.scripts]
Expand Down
16 changes: 9 additions & 7 deletions tests/core/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,24 @@ def test_user_get_from_login(testclient, user, backend):


def test_user_has_password(testclient, backend):
u = models.User(
user = models.User(
formatted_name="Temp User",
family_name="Temp",
user_name="temp",
emails=["[email protected]"],
)
u.save()
user.save()

assert not u.has_password()
assert user.password is None
assert not user.has_password()

u.password = "foobar"
u.save()
user.password = "foobar"
user.save()

assert u.has_password()
assert user.password is not None
assert user.has_password()

u.delete()
user.delete()


def test_user_set_and_check_password(testclient, user, backend):
Expand Down

0 comments on commit a7e574f

Please sign in to comment.