Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 3 years ago.
Improve this question
I want to send password reset link to only confirmed email address. And also user can request password reset link by searching by username or email address which is not available in default django. I have been trying and editing the default django password reset view and form for many days. But not working for me.
you can extend PasswordResetView from
rest_auth.views
and do more logic in it
as in example
from rest_auth.views import PasswordChangeView, PasswordResetView, PasswordResetConfirmView
sensitive_post_parameters_m = method_decorator(
sensitive_post_parameters(
'password', 'old_password', 'new_password1', 'new_password2'
)
)
class PasswordResetViewNew(PasswordResetView):
def post(self, request, *args, **kwargs):
email = request.data.get('email')
try:
if User.objects.get(email=email).active:
return super(PasswordResetViewNew, self).post(request, *args, **kwargs)
except:
# this for if the email is not in the db of the system
return super(PasswordResetViewNew, self).post(request, *args, **kwargs)
in urls
path('password/reset/', PasswordResetViewNew.as_view()),
Edit to answer "How to search by username for sending password reset link"
the default serializer for the PasswordResetView contains
email = serializers.EmailField()
this mean the end point will not accept any thing except email
so we will do some tricks to make it accept chars so we can send user name to it
firstly we would extend
PasswordResetSerializer from rest_auth.serializers
and do some maintains
from rest_auth.serializers import PasswordResetSerializer
from django.conf import settings
from django.contrib.auth.forms import PasswordResetForm
class TestSerializer(PasswordResetSerializer):
email = serializers.CharField()
# here changed the email to accept any chars
reset_form = PasswordResetForm()
def validate_email(self, value):
#here we will trick it be make email really have the email of the username entered
mutable = self.initial_data
self.initial_data._mutable = True
self.initial_data['email'] = User.objects.get(username=self.initial_data.get('email'))
# don't forget to handle exception for username that not in db
self.initial_data._mutable = mutable
return super(TestSerializer, self).validate_email(value)
and with a little bit changes in our view like this
from django.utils.translation import gettext_lazy as _
#don't forget to import PasswordResetView, TestSerializer
class PasswordResetViewNew(PasswordResetView):
serializer_class = TestSerializer
# here we changed the default serializer to our new serializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.data['email'] = User.objects.get(username=serializer.data.get('email'))
# here we trick it again and change the email with the email for the username entered
serializer.save()
# Return the success message with OK HTTP status
return Response(
{"detail": _("Password reset e-mail has been sent.")},
status=status.HTTP_200_OK
)
Related
Currently I've been using the generic login/register links (.../account/login, .../account/register etc) which lets all users (staff and non-staff) to login. I'm creating a separate app for only staff members and I'd like to have a separate endpoint link (.../acccount/staff-login) that would only allow staff members to get tokens. This seems pretty basic but I haven't been able to find anything for this.
Edit: MY SOLUTION : I simply reused the existing ObtainAuthToken view, and added a simple check for is_staff, if the user isn't staff I send an error status.
class StaffAuthToken(APIView):
throttle_classes = ()
permission_classes = ()
parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,)
renderer_classes = (renderers.JSONRenderer,)
serializer_class = AuthTokenSerializer
if coreapi_schema.is_enabled():
schema = ManualSchema(
fields=[
coreapi.Field(
name="username",
required=True,
location='form',
schema=coreschema.String(
title="Username",
description="Valid username for authentication",
),
),
coreapi.Field(
name="password",
required=True,
location='form',
schema=coreschema.String(
title="Password",
description="Valid password for authentication",
),
),
],
encoding="application/json",
)
def get_serializer_context(self):
return {
'request': self.request,
'format': self.format_kwarg,
'view': self
}
def get_serializer(self, *args, **kwargs):
kwargs['context'] = self.get_serializer_context()
return self.serializer_class(*args, **kwargs)
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
if (not user.is_staff): ## this is what I added
return Response(status = status.HTTP_403_FORBIDDEN)
token, created = Token.objects.get_or_create(user=user)
return Response({'token': token.key})
You can do this using two different link...
1.link_for_non-staf
2.link_for_staf
Now listen that request in two different function in views.py.
def link_for_staff(request):
if request.method=="POST":
staff_form=AuthenticationForm(request,data=request.POST)
if form.is_valid():
username=staff_form.cleaned_data.get('username')
passowrd=staff_form.cleaned_data.get('password')
user=authenticate(username=username,passowrd=password)
if user is not None:
login(request,user)
return redirect('home')
Similarly you can create a another function to listen non-staff login request.
In your staff link no any other can login.
Don't forgot to import all import modules and variable names may be differ according to your choice.
What you have to do is create another authentication and check the user that's trying to login.
you can simply use IF statement.
`
if User.object.filter(fieldname=value, is_staff=True).exists():
do the auth......
else:
return the user back with error messeage
`
This will allow only the staff on the staff app. there are many ways to do this but this is the simplest.
I'm creating a single page application that uses Django's session authentication on the backend. Django is using django-allauth for everything authentication-related.
I would like to add an additional step to my login where the user inputs a code and Django must verify that code too, other than password and username. How can i do that?
Note that i'm using Django as an API, so i don't need to edit the form and add another field, i only need to add another check to the authentication backend, so something very easy: if the code is right, than proceed to check username and password too, else return an error.
The problem is that i don't know where to add this check. I think i need to work on the authentication backend, but i'm stuck here.
Here is an example of the login data that django receives:
{'login': 'test', 'password': 'testPass12', 'testToken': '123abc'}
So basically, other than checking login and password like it already does now, it should check if testToken is equal to a specific value.
Here is the allauth authentication backend:
class AuthenticationBackend(ModelBackend):
def authenticate(self, request, **credentials):
ret = None
if app_settings.AUTHENTICATION_METHOD == AuthenticationMethod.EMAIL:
ret = self._authenticate_by_email(**credentials)
elif app_settings.AUTHENTICATION_METHOD == AuthenticationMethod.USERNAME_EMAIL:
ret = self._authenticate_by_email(**credentials)
if not ret:
ret = self._authenticate_by_username(**credentials)
else:
ret = self._authenticate_by_username(**credentials)
return ret
def _authenticate_by_username(self, **credentials):
username_field = app_settings.USER_MODEL_USERNAME_FIELD
username = credentials.get("username")
password = credentials.get("password")
User = get_user_model()
if not username_field or username is None or password is None:
return None
try:
# Username query is case insensitive
user = filter_users_by_username(username).get()
if self._check_password(user, password):
return user
except User.DoesNotExist:
return None
def _authenticate_by_email(self, **credentials):
# Even though allauth will pass along `email`, other apps may
# not respect this setting. For example, when using
# django-tastypie basic authentication, the login is always
# passed as `username`. So let's play nice with other apps
# and use username as fallback
email = credentials.get("email", credentials.get("username"))
if email:
for user in filter_users_by_email(email):
if self._check_password(user, credentials["password"]):
return user
return None
def _check_password(self, user, password):
ret = user.check_password(password)
if ret:
ret = self.user_can_authenticate(user)
if not ret:
self._stash_user(user)
return ret
#classmethod
def _stash_user(cls, user):
"""Now, be aware, the following is quite ugly, let me explain:
Even if the user credentials match, the authentication can fail because
Django's default ModelBackend calls user_can_authenticate(), which
checks `is_active`. Now, earlier versions of allauth did not do this
and simply returned the user as authenticated, even in case of
`is_active=False`. For allauth scope, this does not pose a problem, as
these users are properly redirected to an account inactive page.
This does pose a problem when the allauth backend is used in a
different context where allauth is not responsible for the login. Then,
by not checking on `user_can_authenticate()` users will allow to become
authenticated whereas according to Django logic this should not be
allowed.
In order to preserve the allauth behavior while respecting Django's
logic, we stash a user for which the password check succeeded but
`user_can_authenticate()` failed. In the allauth authentication logic,
we can then unstash this user and proceed pointing the user to the
account inactive page.
"""
global _stash
ret = getattr(_stash, "user", None)
_stash.user = user
return ret
#classmethod
def unstash_authenticated_user(cls):
return cls._stash_user(None)
And here is the allauth login view:
class LoginView(
RedirectAuthenticatedUserMixin, AjaxCapableProcessFormViewMixin, FormView
):
form_class = LoginForm
template_name = "account/login." + app_settings.TEMPLATE_EXTENSION
success_url = None
redirect_field_name = "next"
#sensitive_post_parameters_m
def dispatch(self, request, *args, **kwargs):
return super(LoginView, self).dispatch(request, *args, **kwargs)
def get_form_kwargs(self):
kwargs = super(LoginView, self).get_form_kwargs()
kwargs["request"] = self.request
return kwargs
def get_form_class(self):
return get_form_class(app_settings.FORMS, "login", self.form_class)
def form_valid(self, form):
success_url = self.get_success_url()
try:
return form.login(self.request, redirect_url=success_url)
except ImmediateHttpResponse as e:
return e.response
def get_success_url(self):
# Explicitly passed ?next= URL takes precedence
ret = (
get_next_redirect_url(self.request, self.redirect_field_name)
or self.success_url
)
return ret
def get_context_data(self, **kwargs):
ret = super(LoginView, self).get_context_data(**kwargs)
signup_url = passthrough_next_redirect_url(
self.request, reverse("account_signup"), self.redirect_field_name
)
redirect_field_value = get_request_param(self.request, self.redirect_field_name)
site = get_current_site(self.request)
ret.update(
{
"signup_url": signup_url,
"site": site,
"redirect_field_name": self.redirect_field_name,
"redirect_field_value": redirect_field_value,
}
)
return ret
I'm using dj_rest_auth to authenticate my users and there is a problem with registration. After I create a user with RegisterView that dj_rest_auth gives, django creates the user without a problem but password hashing is incorrect and thus I can not login with new created user.
This is my Register View:
registerview.py
class UserRegisterAPIView(RegisterView):
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
# user.set_password(make_password(request.data.get('password'))) Didn't work
user = self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(self.get_response_data(user),
status=status.HTTP_201_CREATED,
headers=headers)
def perform_create(self, serializer):
user = serializer.save(self.request)
create_token(self.token_model, user, serializer)
complete_signup(self.request._request, user,
allauth_settings.EMAIL_VERIFICATION,
None)
return user
EDIT: These are the same password but hashed differently(First one is dj_rest_auth's register view, second one is created in admin panel)
If I use the second password on other users, they successfully log in.
I think you have a typo in your create method.
Where you have the commented section:
user.set_password(make_password(request.data.get('password')))
It should actually be:
user.set_password(make_password(serializer.data.get('password')))
I'm not sure what the function make_password does there, but that should solve the problem.
sorry for my english. It is not good.
I work with rest framework django. I want to recover a user with his token. This Token must be sent via a post request
class GetUser(generics.ListCreateAPIView):
serializer_class = serializers.UserBasicSerializer
def get_queryset(self):
return models.Member.objects.filter()
def post(self, request, *args, **kwargs):
user = Token.objects.get(*args, **kwargs).user
i receive this error message
rest_framework.authtoken.models.MultipleObjectsReturned: get() returned more than one Token -- it returned 2!
thanks
Use:
user = Token.objects.filter(*args, **kwargs)
if user.exists():
user = user.last().user
The answer to your question is in the docs. Take a look here: http://www.django-rest-framework.org/api-guide/requests/#user
Basically, you just need to get from request the method user. For example:
def api_name_of_api(request):
user_data = request.user # Get username
user_data = request.user.id # Get user id
...
I've built a Django API that when given an email address via POST will respond with a boolean value indicating weather or not that email address already exists in my database:
class isEmailTaken(views.APIView):
permission_classes = [permissions.AllowAny,]
def post(self, request, *args, **kwargs):
try:
email = request.DATA['email']
except KeyError:
return HttpResponse(
'An email was not given with this request.',
status=status.HTTP_400_BAD_REQUEST,
)
return HttpResponse(
json.dumps(
User.objects.filter(email=email),
content_type="application/json",
status=status.HTTP_200_OK,
)
)
Now I would like to use the django-rest-swagger package to automatically generate documentation for this API. I installed the package and inserted the comments you see above between the triple-quotes. When I look at the documentation produced by django-rest-swagger for this API, I see the image below.
However, when I click the Try it out! button, I get the error shown below. Notably, it never gives me a chance to input the email argument that it should send via POST.
Why doesn't the Django-Swagger-Package create docs that allow me to properly the argument "email" via POST? How do I make this work?
I tested this with cigar_example which is made by django-rest-swagger and in that example they written one custom view which is also not rendering input parameters
Lastly i look into source code and found that django-rest-swagger needs get_serializer_class to build body parameters
So it worked with the following code:
class isEmailTaken(views.APIView):
permission_classes = [permissions.AllowAny,]
serializer_class = IsEmailTakenSerializer
def get_serializer_class(self):
return self.serializer_class
def post(self, request, *args, **kwargs):
try:
email = request.DATA['email']
except KeyError:
return HttpResponse(
'An email was not given with this request.',
status=status.HTTP_400_BAD_REQUEST,
)
return HttpResponse(
json.dumps(
User.objects.filter(email=email),
content_type="application/json",
status=status.HTTP_200_OK,
)
)
and IsEmailTakenSerializer:
from rest_framework import serializers
class IsEmailTakenSerializer(serializers.Serializer):
email = serializers.EmailField()
django-rest-swagger tries to send a POST request without some data.
First you have to fix your view like this:
from rest_framework import status
from django.http import HttpResponse
import json
def post(self, request, *args, **kwargs):
try:
email = request.DATA['email']
except KeyError:
return HttpResponse(
'An email was not given with this request.',
status=status.HTTP_400_BAD_REQUEST,
)
return HttpResponse(
json.dumps(
User.objects.filter(email=email),
content_type="application/json",
status=status.HTTP_200_OK,
)
)
If you try this, you should see your nice error message now.
Next step is to look at django-rest-swagger docs to find out what to do that it renders a html form field right above the "Try it out" button.