From 9c64ea6a352b0bd565dc42589af936b461cfebbe Mon Sep 17 00:00:00 2001 From: Christian Kreuzberger Date: Fri, 2 Aug 2019 14:40:33 +0200 Subject: [PATCH] Allow nullable/blank fields, fix header values and add docu about those values --- README.md | 56 ++++++++++++++----- .../0003_allow_blank_and_null_fields.py | 25 +++++++++ django_rest_passwordreset/models.py | 7 ++- django_rest_passwordreset/views.py | 9 +-- setup.py | 2 +- 5 files changed, 77 insertions(+), 22 deletions(-) create mode 100644 django_rest_passwordreset/migrations/0003_allow_blank_and_null_fields.py diff --git a/README.md b/README.md index bee9d31..c09d108 100644 --- a/README.md +++ b/README.md @@ -61,21 +61,6 @@ The following endpoints are provided: where `${API_URL}/` is the url specified in your *urls.py* (e.g., `api/password_reset/`) - -### Configuration / Settings - -The following settings can be set in Djangos ``settings.py`` file: - -* `DJANGO_REST_MULTITOKENAUTH_RESET_TOKEN_EXPIRY_TIME` - time in hours about how long the token is active (Default: 24) - - **Please note**: expired tokens are automatically cleared based on this setting in every call of ``ResetPasswordRequestToken.post``. - -* `DJANGO_REST_PASSWORDRESET_NO_INFORMATION_LEAKAGE` - will cause a 200 to be returned on `POST ${API_URL}/reset_password/` - even if the user doesn't exist in the databse (Default: False) - -* `DJANGO_REST_MULTITOKENAUTH_REQUIRE_USABLE_PASSWORD` - allows password reset for a user that does not - [have a usable password](https://docs.djangoproject.com/en/2.2/ref/contrib/auth/#django.contrib.auth.models.User.has_usable_password) (Default: True) - ### Signals * ``reset_password_token_created(sender, instance, reset_password_token)`` Fired when a reset password token is generated @@ -140,6 +125,21 @@ def password_reset_token_created(sender, instance, reset_password_token, *args, If you want to test this locally, I recommend using some kind of fake mailserver (such as maildump). + +# Configuration / Settings + +The following settings can be set in Djangos ``settings.py`` file: + +* `DJANGO_REST_MULTITOKENAUTH_RESET_TOKEN_EXPIRY_TIME` - time in hours about how long the token is active (Default: 24) + + **Please note**: expired tokens are automatically cleared based on this setting in every call of ``ResetPasswordRequestToken.post``. + +* `DJANGO_REST_PASSWORDRESET_NO_INFORMATION_LEAKAGE` - will cause a 200 to be returned on `POST ${API_URL}/reset_password/` + even if the user doesn't exist in the databse (Default: False) + +* `DJANGO_REST_MULTITOKENAUTH_REQUIRE_USABLE_PASSWORD` - allows password reset for a user that does not + [have a usable password](https://docs.djangoproject.com/en/2.2/ref/contrib/auth/#django.contrib.auth.models.User.has_usable_password) (Default: True) + ## Custom Email Lookup By default, `email` lookup is used to find the user instance. You can change that by adding @@ -148,6 +148,19 @@ DJANGO_REST_LOOKUP_FIELD = 'custom_email_field' ``` into Django settings.py file. +## Custom Remote IP Address and User Agent Header Lookup + +If your setup demands that the IP adress of the user is in another header (e.g., 'X-Forwarded-For'), you can configure that (using Django Request Headers): + +```python +DJANGO_REST_PASSWORDRESET_IP_ADDRESS_HEADER = 'HTTP_X_FORWARDED_FOR' +``` + +The same is true for the user agent: + +```python +HTTP_USER_AGENT_HEADER = 'HTTP_USER_AGENT' +``` ## Custom Token Generator @@ -249,6 +262,7 @@ django-rest-passwordreset Version | Django Versions | Django Rest Framework Vers --------------------------------- | ----------------| ------------------------------ 0.9.7 | 1.8, 1.11, 2.0, 2.1 | 3.6 - 3.9 1.0 | 1.11, 2.0, 2.2 | 3.6 - 3.9 +1.1 | 1.11, 2.2 | 3.6 - 3.9 ## Documentation / Browsable API @@ -307,6 +321,18 @@ You need to make sure that the code with `@receiver(reset_password_token_created default_app_config = 'your_django_project.some_app.SomeAppConfig' ``` +### MongoDB not working + +Apparently, the following piece of code in the Django Model prevents MongodB from working: + +```python + id = models.AutoField( + primary_key=True + ) +``` + +See issue #49 for details. + ## Contributions This library tries to follow the unix philosophy of "do one thing and do it well" (which is providing a basic password reset endpoint for Django Rest Framework). Contributions are welcome in the form of pull requests and issues! If you create a pull request, please make sure that you are not introducing breaking changes. diff --git a/django_rest_passwordreset/migrations/0003_allow_blank_and_null_fields.py b/django_rest_passwordreset/migrations/0003_allow_blank_and_null_fields.py new file mode 100644 index 0000000..62aa48f --- /dev/null +++ b/django_rest_passwordreset/migrations/0003_allow_blank_and_null_fields.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.20 on 2019-08-02 12:39 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('django_rest_passwordreset', '0002_pk_migration'), + ] + + operations = [ + migrations.AlterField( + model_name='resetpasswordtoken', + name='ip_address', + field=models.GenericIPAddressField(blank=True, default='', null=True, verbose_name='The IP address of this session'), + ), + migrations.AlterField( + model_name='resetpasswordtoken', + name='user_agent', + field=models.CharField(blank=True, default='', max_length=256, verbose_name='HTTP User Agent'), + ), + ] diff --git a/django_rest_passwordreset/models.py b/django_rest_passwordreset/models.py index 940abb1..ac16611 100644 --- a/django_rest_passwordreset/models.py +++ b/django_rest_passwordreset/models.py @@ -61,12 +61,15 @@ def generate_key(): ip_address = models.GenericIPAddressField( _("The IP address of this session"), - default="127.0.0.1" + default="", + blank=True, + null=True, ) user_agent = models.CharField( max_length=256, verbose_name=_("HTTP User Agent"), - default="" + default="", + blank=True, ) def save(self, *args, **kwargs): diff --git a/django_rest_passwordreset/views.py b/django_rest_passwordreset/views.py index 23772da..f9ec8af 100644 --- a/django_rest_passwordreset/views.py +++ b/django_rest_passwordreset/views.py @@ -23,6 +23,9 @@ 'reset_password_request_token' ] +HTTP_USER_AGENT_HEADER = getattr(settings, 'DJANGO_REST_PASSWORDRESET_HTTP_USER_AGENT_HEADER', 'HTTP_USER_AGENT') +HTTP_IP_ADDRESS_HEADER = getattr(settings, 'DJANGO_REST_PASSWORDRESET_IP_ADDRESS_HEADER', 'REMOTE_ADDR') + class ResetPasswordConfirm(GenericAPIView): """ @@ -140,10 +143,8 @@ def post(self, request, *args, **kwargs): # no token exists, generate a new token token = ResetPasswordToken.objects.create( user=user, - user_agent=request.META.get('HTTP_USER_AGENT', - getattr(settings, 'DJANGO_REST_PASSWORDRESET_HTTP_USER_AGENT', '')), - ip_address=request.META.get('REMOTE_ADDR', - getattr(settings, 'DJANGO_REST_PASSWORDRESET_REMOTE_ADDR', '')) + user_agent=request.META.get(HTTP_USER_AGENT_HEADER, ''), + ip_address=request.META.get(HTTP_IP_ADDRESS_HEADER, ''), ) # send a signal that the password token was created # let whoever receives this signal handle sending the email for the password reset diff --git a/setup.py b/setup.py index efe7831..f78e9cc 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ setup( name='django-rest-passwordreset', - version='1.1.0rc2', + version='1.1.0rc3', packages=find_packages(), include_package_data=True, license='BSD License',