django custom registration view, redirect fail - python

I am using Django-registration and I have subclassed the BaseRegistrationView to customised a backend.
However, the HttpResponseRedirect fail to redirect to a new page when the condition is met.
The code is listed below:
class CustomRegistrationView(BaseRegistrationView):
def register(self, request, **cleaned_data):
captchaMatch = False
#check captcha
captchaValue = request.session.get('captcha')
captchaValueSubmitted = cleaned_data['captcha'] + "123"
if captchaValue != captchaValueSubmitted:
return HttpResponseRedirect(reverse('InvitationRequired'))
else:
self.captchaMatch = True
username, email, password = cleaned_data['username'], cleaned_data['email'], cleaned_data['password1']
if Site._meta.installed:
site = Site.objects.get_current()
else:
site = RequestSite(request)
new_user = RegistrationProfile.objects.create_inactive_user(username, email,
password, site)
signals.user_registered.send(sender=self.__class__,
user=new_user,
request=request)
return new_user
def registration_allowed(self, request):
"""
Indicate whether account registration is currently permitted,
based on the value of the setting ``REGISTRATION_OPEN``. This
is determined as follows:
* If ``REGISTRATION_OPEN`` is not specified in settings, or is
set to ``True``, registration is permitted.
* If ``REGISTRATION_OPEN`` is both specified and set to
``False``, registration is not permitted.
"""
return getattr(settings, 'REGISTRATION_OPEN', True)
def get_success_url(self, request, user):
"""
Return the name of the URL to redirect to after successful
user registration.
"""
print "get successful url"
if self.captchaMatch:
return ('registration_complete', (), {})
else:
return HttpResponseRedirect(reverse('InvitationRequired'))
Can anyone help explain why the redirection is executed inside the function register ?

I guess the register method is supposed to return a user or None. Not an HttpRedirect object. Since I'm not familiar with this specific app, try to see how they handle failure on the original method and follow that policy (e.b. return None or raise an Exception).
Also, based on the comments on the get_success_url() method, I would expect the code to be like this:
def get_success_url(self, request, user):
"""
Return the name of the URL to redirect to after successful
user registration.
"""
if self.captchaMatch:
return ('registration_complete', (), {})
else:
return ('InvitationRequired', (), {})
I guess you should not return a HttpResponse instance, as the method is used to get the URL to be redirect to (not the redirection itself).

Related

Django - adding another check to my login

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

How to write tests for django form views?

Django v1.10
FormView code:
class PasswordResetConfirmView(FormView):
template_name = "dashboard/account/reset_password_form.html"
success_url = '/dashboard/'
form_class = SetPasswordForm
def authenticate_password_token(self, request, uidb64=None, token=None, encodedtimestring=None):
try:
uid = force_text(urlsafe_base64_decode(uidb64))
user = User.objects.get(pk=uid)
timestring = force_text(urlsafe_base64_decode(encodedtimestring))
timestamp = timeparse(timestring)
timediff = timezone.now() - timestamp
except (TypeError, ValueError, OverflowError, User.DoesNotExist):
user = None
timediff = None
if timediff is None or timediff.days < 0 or timediff.days > PASSWORD_RESET_TIMEOUT_DAYS:
messages.error(request, _(
'The reset password link is no longer valid.'))
return None
if user is None or not default_token_generator.check_token(user, token):
messages.error(request, _('The reset password link is not valid.'))
return None
return user
def get(self, request, uidb64=None, token=None, encodedtimestring=None, *arg, **kwargs):
form = self.form_class()
assert uidb64 is not None and token is not None and encodedtimestring is not None
user = self.authenticate_password_token(
request, uidb64, token, encodedtimestring)
if user is None:
return redirect(reverse('dashboard-login'))
return self.render_to_response(self.get_context_data(form=form))
def post(self, request, uidb64=None, token=None, encodedtimestring=None, *arg, **kwargs):
form = self.form_class(request.POST)
assert uidb64 is not None and token is not None and encodedtimestring is not None
user = self.authenticate_password_token(
request, uidb64, token, encodedtimestring)
if user is None:
return redirect(reverse('dashboard-login'))
if not form.is_valid():
return self.form_invalid(form)
new_password = form.cleaned_data['new_password2']
try:
with transaction.atomic():
user.auth_token.delete()
Token.objects.create(user=user)
user.set_password(new_password)
user.save()
except:
messages.error(request, _('Password reset was unsuccessful.'))
return redirect(reverse('dashboard-login'))
messages.success(request, _('Password has been reset.'))
return redirect(reverse('dashboard-login'))
urls.py:
url(r'^(?i)recover/password/(?P<uidb64>[0-9A-Za-z]+)/(?P<token>.+)/(?P<encodedtimestring>.+)/$',
views.PasswordResetConfirmView.as_view(), name='reset-password-confirm'),
testclass parent:
class BaseApiTest(TestCase):
def setUp(self):
superuser = User.objects.create_superuser(
'test', 'test#api.com', 'testpassword')
self.factory = RequestFactory()
self.user = superuser
self.client.login(username=superuser.username, password='testpassword')
My attempt at writing the test case:
class ResetPasswordEmailTest(BaseApiTest):
def test_password_reset_form(self):
"""
Ensure that the authenticate token works
"""
self.client.logout()
token = default_token_generator.make_token(self.user)
uidb64 = force_bytes(self.user.id)
timenow = force_bytes(timezone.now())
response = self.client.get(
reverse('reset-password-confirm',
args=[urlsafe_base64_encode(uidb64), token,
urlsafe_base64_encode(timenow)]))
self.assertEqual(response.status_code, status.HTTP_200_OK)
Error message that I got:
tests/password_tests.py", line 129, in test_password_reset_form
self.assertEqual(response.status_code, status.HTTP_200_OK)
AssertionError: 302 != 200
Am not sure how to write a test class to test all 3 methods of the formview. My attempt was just to test the get method
UPDATE:
The real reason for the failure has nothing to do with the user login but somehow the check_token method inherent in the PasswordTokenGenerator was failing my tests.
And as I do more research, I think it would be better that I upgrade Django v1.10 to v1.11 where I need to rewrite this whole thing which may end up invalidating the need for this question.
Your get method of PasswordResetConfirmView is responding with 2 different responses.
Redirect
When user is None it is responding with a Redirect URI
if user is None:
return redirect(reverse('dashboard-login'))
So in that case the status code of response will be HTTP 302 means
that you are redirected to some other URL.
Render to response
When there is user, then the information returned with the response is the method used in the request.
return self.render_to_response(self.get_context_data(form=form))
So in that case the status code of response will be HTTP 200 means
the request has succeeded.
Solution:
In order to make your test case pass you can use assertRedirects
def test_password_reset_form(self):
...
# A URL that redirects can be followed to termination.
response = self.client.get(reverse('reset-password-confirm', args=[...]), follow=True)
self.assertRedirects(response, reverse('dashboard-login'), status_code=302, target_status_code=200)
self.assertEqual(len(response.redirect_chain), 2)
If your request used the follow argument, the expected_url and target_status_code will be the url and status code for the final point of the redirect chain.
Reference:
https://github.com/django/django/blob/master/tests/test_client/tests.py
Alternatively you can create two separate test cases (when user is logged-in, when user is not logged-in) and use assertEqual
# Test for logged in user.
def test_password_reset_form_for_logged_in_user(self):
...
# do stuff
self.assertEqual(response.status_code, status.HTTP_200_OK)
# Test if user not logged in.
def test_password_reset_form_if_user_not_logged_in(self):
...
# do stuff
self.assertEqual(response.status_code, status.HTTP_302_FOUND)
Problem (Post Mortem):
Let's start with defining the problem:
Status 302:
The requested resource resides temporarily under a different URI. -source
So that leads us to assume that you are getting redirected thus the test fails.
Where this happens:
In your get() method we find this code:
if user is None:
return redirect(reverse('dashboard-login'))
So if the user is Anonymous (Unauthenticated) the view redirects him to the login page.
From the above, we can state that the problem occurs when you make an unauthenticated request to PasswordResetConfirmView.get().
Solution:
You need to authenticate (login) your user before you make the request.
You can do this by utilizing the force_login() method:
If your site uses Django’s authentication system, you can use the force_login() method to simulate the effect of a user logging into the site. Use this method instead of login() when a test requires a user be logged in and the details of how a user logged in aren’t important.
def my_test_case():
... Do stuff ...
# Login
self.client.force_login(self.user)
# Make the request
response = self.client.get(...)

python/flask find unique ids for each website visitor [duplicate]

I want to build a simple webapp as part of my learning activity. Webapp is supposed to ask for user to input their email_id if it encounters a first time visitor else it remembers the user through cookie and automatically logs him/her in to carry out the functions.
This is my first time with creating a user based web app. I have a blue print in my mind but I am unable to figure out how to implement it. Primarily I am confused with respect to the way of collecting user cookie. I have looked into various tutorials and flask_login but I think what I want to implement is much simpler as compared to what flask_login is implementing.
I also tried using flask.session but it was a bit difficult to understand and I ended up with a flawed implementation.
Here is what I have so far (it is rudimentary and meant to communicate my use case):
from flask import render_template, request, redirect, url_for
#app.route("/", methods= ["GET"])
def first_page():
cookie = response.headers['cookie']
if database.lookup(cookie):
user = database.get(cookie) # it returns user_email related to that cookie id
else:
return redirect_url(url_for('login'))
data = generateSomeData() # some function
return redirect(url_for('do_that'), user_id, data, stats)
#app.route('/do_that', methods =['GET'])
def do_that(user_id):
return render_template('interface.html', user_id, stats,data) # it uses Jinja template
#app.route('/submit', methods =["GET"])
def submit():
# i want to get all the information here
user_id = request.form['user_id']# some data
answer = request.form['answer'] # some response to be recorded
data = request.form['data'] # same data that I passed in do_that to keep
database.update(data,answer,user_id)
return redirect(url_for('/do_that'))
#app.route('/login', methods=['GET'])
def login():
return render_template('login.html')
#app.route('/loggedIn', methods =['GET'])
def loggedIn():
cookie = response.headers['cookie']
user_email = response.form['user_email']
database.insert(cookie, user_email)
return redirect(url_for('first_page'))
You can access request cookies through the request.cookies dictionary and set cookies by using either make_response or just storing the result of calling render_template in a variable and then calling set_cookie on the response object:
#app.route("/")
def home():
user_id = request.cookies.get('YourSessionCookie')
if user_id:
user = database.get(user_id)
if user:
# Success!
return render_template('welcome.html', user=user)
else:
return redirect(url_for('login'))
else:
return redirect(url_for('login'))
#app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
# You should really validate that these fields
# are provided, rather than displaying an ugly
# error message, but for the sake of a simple
# example we'll just assume they are provided
user_name = request.form["name"]
password = request.form["password"]
user = db.find_by_name_and_password(user_name, password)
if not user:
# Again, throwing an error is not a user-friendly
# way of handling this, but this is just an example
raise ValueError("Invalid username or password supplied")
# Note we don't *return* the response immediately
response = redirect(url_for("do_that"))
response.set_cookie('YourSessionCookie', user.id)
return response
#app.route("/do-that")
def do_that():
user_id = request.cookies.get('YourSessionCookie')
if user_id:
user = database.get(user_id)
if user:
# Success!
return render_template('do_that.html', user=user)
else:
return redirect(url_for('login'))
else:
return redirect(url_for('login'))
DRYing up the code
Now, you'll note there is a lot of boilerplate in the home and do_that methods, all related to login. You can avoid that by writing your own decorator (see What is a decorator if you want to learn more about them):
from functools import wraps
from flask import flash
def login_required(function_to_protect):
#wraps(function_to_protect)
def wrapper(*args, **kwargs):
user_id = request.cookies.get('YourSessionCookie')
if user_id:
user = database.get(user_id)
if user:
# Success!
return function_to_protect(*args, **kwargs)
else:
flash("Session exists, but user does not exist (anymore)")
return redirect(url_for('login'))
else:
flash("Please log in")
return redirect(url_for('login'))
return wrapper
Then your home and do_that methods get much shorter:
# Note that login_required needs to come before app.route
# Because decorators are applied from closest to furthest
# and we don't want to route and then check login status
#app.route("/")
#login_required
def home():
# For bonus points we *could* store the user
# in a thread-local so we don't have to hit
# the database again (and we get rid of *this* boilerplate too).
user = database.get(request.cookies['YourSessionCookie'])
return render_template('welcome.html', user=user)
#app.route("/do-that")
#login_required
def do_that():
user = database.get(request.cookies['YourSessionCookie'])
return render_template('welcome.html', user=user)
Using what's provided
If you don't need your cookie to have a particular name, I would recommend using flask.session as it already has a lot of niceties built into it (it's signed so it can't be tampered with, can be set to be HTTP only, etc.). That DRYs up our login_required decorator even more:
# You have to set the secret key for sessions to work
# Make sure you keep this secret
app.secret_key = 'something simple for now'
from flask import flash, session
def login_required(function_to_protect):
#wraps(function_to_protect)
def wrapper(*args, **kwargs):
user_id = session.get('user_id')
if user_id:
user = database.get(user_id)
if user:
# Success!
return function_to_protect(*args, **kwargs)
else:
flash("Session exists, but user does not exist (anymore)")
return redirect(url_for('login'))
else:
flash("Please log in")
return redirect(url_for('login'))
And then your individual methods can get the user via:
user = database.get(session['user_id'])

Difference between self.request and request in Django class-based view

In django, for class-based view like ListView and DetailView, methods like get() or post() or other functions defined by developer take parameters include self and request. I learnt that in self these is actually a self.request field, so wha's the difference between self.request and request?
Example, this is the function in a class based view and used to handle user's login requirement:
def login(self, request):
name = request.POST['name']
pwd = request.POST['password']
user = authenticate(username=name, password=pwd)
if user is not None:
request.session.set_expiry(0)
login(request, user)
log_message = 'Login successfully.'
else:
log_message = 'Fail to login.'
return HttpResponseRedirect(reverse('blog:testindex'))
This is the function used to handle user's register:
def register(self, request):
user_name = self.request.POST['username']
firstname = self.request.POST['firstname']
lastname = self.request.POST['lastname']
pwd = self.request.POST['password']
e_mail = self.request.POST['email']
user = User.objects.create(username=user_name, first_name=firstname, last_name=lastname, email=e_mail)
user.set_password(pwd)
try:
user.save()
user = authenticate(username=user_name, password=pwd)
login(self.request, user)
except Exception:
pass
else:
return HttpResponseRedirect(reverse('blog:testindex'))
In the first function, it used data stored in request and in the second one, it used self.request, both work functionally. What's the difference?
For a subclass of View, they're the same object. self.request = request is set in view function that as_view() returns. I looked into the history, but only found setting self.request and then immediately passing request into the view function.

Check if Flask-HTTPAuth is authenticated inside view

I'm using Flask-HTTPAuth for authentication. I want to display different data from a view depending on if the request was authenticated or not. Decorating the view with auth.login_required only shows it to authenticated users. How can I test if the request was authenticated with Flask-HTTPAuth?
auth = HTTPBasicAuth()
#app.route("/clothesInfo")
#auth.login_required
def show_info():
return jsonify(blah blah blah)
What you want is actually very easy to implement. In your verify_password callback, you will get username and password set to '' when the user does not provide credentials. You can still return True from that function, and that will allow the anonymous user to access the endpoint.
The following example demonstrates this technique:
auth = HTTPBasicAuth()
#auth.verify_password
def verify_password(username, password):
if username == '' or password == '':
# anonymous user, we still let them in
g.current_user = None
return True
g.current_user = my_verify_function(username, password)
return g.current_user is not None
#app.route("/clothesInfo")
#auth.login_required
def show_info():
if g.current_user:
# prepare data for authenticated users here
pass
else:
# prepare data for anonymous users here
pass
return jsonify(data)
You can decorate a function that returns None with login_required. Calling it while not authenticated will return an error response, calling it while authenticated will return None.
# a dummy callable to execute the login_required logic
login_required_dummy_view = auth.login_required(lambda: None)
def is_authenticated():
try:
# default implementation returns a string error
return login_required_dummy_view() is None
except HTTPException:
# in case auth_error_callback raises a real error
return False
#app.route('/info')
def info():
if is_authenticated():
# logged in view
else:
# basic view
See also Default login_required rather than adding decorator everywhere.
Things are simpler since April 2020.
Flask-HTTPAuth 4.0.0 added optional argument to login required to do exactly this.
From the docs:
An optional optional argument can be set to True to allow the route to execute also when authentication is not included with the request, in which case auth.current_user() will be set to None. Example:
#app.route('/private')
#auth.login_required(optional=True)
def private_page():
user = auth.current_user()
return "Hello {}!".format(user.name if user is not None else 'anonymous')

Categories

Resources