Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into send_notification_e…
Browse files Browse the repository at this point in the history
…mail
  • Loading branch information
varun kumar committed Dec 3, 2023
2 parents 2b8455b + df41f28 commit 810fb47
Show file tree
Hide file tree
Showing 63 changed files with 5,835 additions and 3,564 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Updating package list
run: sudo apt-get update
- name: Install xmlsec
run: sudo apt-get install -y xmlsec1 libxmlsec1-dev
- name: Install dependencies
Expand Down Expand Up @@ -86,6 +88,8 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Updating package list
run: sudo apt-get update
- name: Install xmlsec
run: sudo apt-get install -y xmlsec1 libxmlsec1-dev
- name: Install gettext
Expand Down
8 changes: 6 additions & 2 deletions ChangeLog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ Note worthy changes
- The MFA authenticator model now features "created at" an "last used "at"
timestamps.

- The MFA authenticator model is now registed with the Django admin.
- The MFA authenticator model is now registered with the Django admin.

- Added MFA signals emitted when authenticators are added, removed or (in case
of recovery codes) reset.

- There is now an MFA adapter method ``can_delete_authenticator(authenticator)``
available that can be used to prevent users from deactivating e.g. their TOTP
authenticator.


0.58.2 (2023-11-06)
*******************
Expand All @@ -38,7 +42,7 @@ Fixes
Note worthy changes
-------------------

- The ``SocialAccount.exra_data`` field was a custom JSON field that used
- The ``SocialAccount.extra_data`` field was a custom JSON field that used
``TextField`` as the underlying implementation. It was once needed because
Django had no ``JSONField`` support. Now, this field is changed to use the
official ``JSONField()``. Migrations are in place.
Expand Down
19 changes: 19 additions & 0 deletions allauth/account/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,25 @@ def authenticate(self, request, **credentials):
def authentication_failed(self, request, **credentials):
pass

def reauthenticate(self, user, password):
from allauth.account.models import EmailAddress
from allauth.account.utils import user_email, user_username

credentials = {"password": password}
username = user_username(user)
if username:
credentials["username"] = username
email = None
primary = EmailAddress.objects.get_primary(user)
if primary:
email = primary.email
else:
email = user_email(user)
if email:
credentials["email"] = email
reauth_user = self.authenticate(context.request, **credentials)
return reauth_user is not None and reauth_user.pk == user.pk

def is_ajax(self, request):
return any(
[
Expand Down
3 changes: 2 additions & 1 deletion allauth/account/admin.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from django.contrib import admin
from django.utils.translation import gettext_lazy as _

from . import app_settings
from .adapter import get_adapter
Expand All @@ -19,7 +20,7 @@ def get_search_fields(self, request):
def make_verified(self, request, queryset):
queryset.update(verified=True)

make_verified.short_description = "Mark selected email addresses as verified"
make_verified.short_description = _("Mark selected email addresses as verified")


class EmailConfirmationAdmin(admin.ModelAdmin):
Expand Down
40 changes: 40 additions & 0 deletions allauth/account/authentication.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import time


AUTHENTICATION_METHODS_SESSION_KEY = "account_authentication_methods"


def record_authentication(request, method, **extra_data):
"""Here we keep a log of all authentication methods used within the current
session. Important to note is that having entries here does not imply that
a user is fully signed in. For example, consider a case where a user
authenticates using a password, but fails to complete the 2FA challenge.
Or, a user successfully signs in into an inactive account or one that still
needs verification. In such cases, ``request.user`` is still anonymous, yet,
we do have an entry here.
Example data::
{'method': 'password',
'at': 1701423602.7184925,
'username': 'john.doe'}
{'method': 'socialaccount',
'at': 1701423567.6368647,
'provider': 'amazon',
'uid': 'amzn1.account.K2LI23KL2LK2'}
{'method': 'mfa',
'at': 1701423602.6392953,
'id': 1,
'type': 'totp'}
"""
methods = request.session.get(AUTHENTICATION_METHODS_SESSION_KEY, [])
data = {
"method": method,
"at": time.time(),
**extra_data,
}
methods.append(data)
request.session[AUTHENTICATION_METHODS_SESSION_KEY] = methods
14 changes: 11 additions & 3 deletions allauth/account/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from django.utils.safestring import mark_safe
from django.utils.translation import gettext, gettext_lazy as _, pgettext

from allauth.account.authentication import record_authentication

from ..utils import (
build_absolute_uri,
get_username_max_length,
Expand Down Expand Up @@ -200,13 +202,19 @@ def clean(self):
return self.cleaned_data

def login(self, request, redirect_url=None):
email = self.user_credentials().get("email")
credentials = self.user_credentials()
extra_data = {
field: credentials.get(field)
for field in ["email", "username"]
if field in credentials
}
record_authentication(request, method="password", **extra_data)
ret = perform_login(
request,
self.user,
email_verification=app_settings.EMAIL_VERIFICATION,
redirect_url=redirect_url,
email=email,
email=credentials.get("email"),
)
remember = app_settings.SESSION_REMEMBER
if remember is None:
Expand Down Expand Up @@ -689,7 +697,7 @@ def __init__(self, *args, **kwargs):

def clean_password(self):
password = self.cleaned_data.get("password")
if not self.user.check_password(password):
if not get_adapter().reauthenticate(self.user, password):
raise forms.ValidationError(
get_adapter().error_messages["incorrect_password"]
)
Expand Down
2 changes: 2 additions & 0 deletions allauth/account/reauthentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ def resume_request(request):


def record_authentication(request, user):
# TODO: This is a different/independent mechanism from
# ``authentication.record_authentication()``. We need to unify this.
request.session[AUTHENTICATED_AT_SESSION_KEY] = time.time()


Expand Down
13 changes: 12 additions & 1 deletion allauth/account/tests/test_login.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import json
from unittest.mock import patch
from unittest.mock import ANY, patch

import django
from django.conf import settings
Expand All @@ -9,6 +9,7 @@
from django.urls import NoReverseMatch, reverse

from allauth.account import app_settings
from allauth.account.authentication import AUTHENTICATION_METHODS_SESSION_KEY
from allauth.account.forms import LoginForm
from allauth.account.models import EmailAddress
from allauth.tests import TestCase
Expand Down Expand Up @@ -46,6 +47,16 @@ def test_username_containing_at(self):
self.assertRedirects(
resp, settings.LOGIN_REDIRECT_URL, fetch_redirect_response=False
)
self.assertEqual(
self.client.session[AUTHENTICATION_METHODS_SESSION_KEY],
[
{
"at": ANY,
"username": "@raymond.penners",
"method": "password",
}
],
)

def _create_user(self, username="john", password="doe", **kwargs):
user = get_user_model().objects.create(
Expand Down
Loading

0 comments on commit 810fb47

Please sign in to comment.