Skip to content

Commit

Permalink
[fix] Support phone numbers with leading zero in auth backend
Browse files Browse the repository at this point in the history
This fix allows users of countries which use
leading zeros in their national phone numbers
to authenticate successfully by using the version
of their number with the leading zero, which
without this patch fails to be recognized.
  • Loading branch information
nemesifier committed May 21, 2024
1 parent 8a39e48 commit af6214e
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 37 deletions.
39 changes: 21 additions & 18 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -606,7 +606,7 @@ Create User
POST /api/v1/users/user/
**Note**: Passing ``true`` to the optional
**Note**: Passing ``true`` to the optional
``is_verified`` field allows creating users with
their email address flagged as verified. This will
also skip sending the verification link to their
Expand Down Expand Up @@ -803,27 +803,30 @@ Usage example:
Authentication Backend
----------------------

The authentication backend in ``openwisp_users.backends.UsersAuthenticationBackend``
allows users to authenticate using their
``email`` or ``phone_number`` instead of their ``username``.
Authenticating with the ``username`` is still allowed,
but ``email`` has precedence.
The authentication backend in
``openwisp_users.backends.UsersAuthenticationBackend``
allows users to authenticate using their ``email`` or ``phone_number``
instead of their ``username``. Authenticating with the ``username`` is
still supported, but ``email`` takes precedence.

If the username string passed is parsed as a valid phone number, then
``phone_number`` has precedence.
If the provided username string is parsed as a valid phone number, then
``phone_number`` takes precedence.

Phone numbers are parsed using the ``phonenumbers`` library, which means
that even if the user adds characters like spaces, dots or dashes, the number
will be recognized anyway.
Phone numbers are parsed using the
`phonenumbers <https://github.com/daviddrysdale/python-phonenumbers>`_
library, which ensures that numbers are recognized even if users
include characters like spaces, dots, or dashes.

When parsing phone numbers, the
`OPENWISP_USERS_AUTH_BACKEND_AUTO_PREFIXES
<#openwisp_users_auth_backend_auto_prefixes>`_
setting allows to specify a list of international prefixes that can
be prepended to the username string automatically in order to allow
users to log in without having to type the international prefix.
The ``OPENWISP_USERS_AUTH_BACKEND_AUTO_PREFIXES`` setting allows specifying
a list of international prefixes that can be automatically prepended to the
username string, enabling users to log in without typing the international
prefix.

The authentication backend can also be used as follows:
Additionally, the backend supports phone numbers with a leading zero, which
is common in some countries for local numbers. This ensures users can
authenticate successfully even if they include the leading zero.

The authentication backend can also be used programmatically, for example:

.. code-block:: python
Expand Down
38 changes: 19 additions & 19 deletions openwisp_users/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,31 @@

class UsersAuthenticationBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
queryset = self.get_users(username)
try:
# can not use queryset.first() because it orders the queryset
# by pk before returning the first object which is not what we want
user = queryset[0]
except IndexError:
return None
if user.check_password(password) and self.user_can_authenticate(user):
return user
for user in self.get_users(username):
if user.check_password(password) and self.user_can_authenticate(user):
return user

def get_users(self, identifier):
conditions = Q(email=identifier) | Q(username=identifier)
# if the identifier is a phone number, use the phone number as primary condition
phone_number = self._get_phone_number(identifier)
if phone_number:
for phone_number in self._get_phone_numbers(identifier):
conditions = Q(phone_number=phone_number) | conditions
return User.objects.filter(conditions)

def _get_phone_number(self, identifier):
def _get_phone_numbers(self, identifier):
prefixes = [''] + list(app_settings.AUTH_BACKEND_AUTO_PREFIXES)
numbers = [identifier]
found = []
# support those countries which use
# leading zeros for their local numbers
if str(identifier).startswith('0'):
numbers.append(identifier[1:])
for prefix in prefixes:
value = f'{prefix}{identifier}'
try:
phonenumbers.parse(value)
return value
except NumberParseException:
pass
return False
for number in numbers:
value = f'{prefix}{number}'
try:
phonenumbers.parse(value)
found.append(value)
except NumberParseException:
continue
return found
4 changes: 4 additions & 0 deletions openwisp_users/tests/test_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@ def test_partial_phone_number(self):
self.assertEqual(auth_backend.get_users('3665243702').count(), 1)
self.assertEqual(auth_backend.get_users('3665243702').first(), user1)

with self.subTest('test with leading zero'):
self.assertEqual(auth_backend.get_users('03665243702').count(), 1)
self.assertEqual(auth_backend.get_users('03665243702').first(), user1)

with self.subTest('test different prefix which is not enabled'):
self._create_user(
username='tester2',
Expand Down

0 comments on commit af6214e

Please sign in to comment.