Django, restrict access to registration success page - django

This is something I've wondered about in a couple of frameworks that I've messed around with. Assuming I don't want to automatically log a user in when they register (I want them to activate) how can I make it so a user can't just visit the "register-success" page? Right now, here's what I have:
def register(request):
if request.method == 'POST':
rf = forms.RegisterForm(request.POST)#register form
pf = forms.ProfileForm(request.POST)#profile form (additional info)
lf = forms.LoginForm()#login form is also on this page but is empty when registering
if rf.is_valid() and pf.is_valid():
newuser = User(username=rf.cleaned_data['username'],email=rf.cleaned_data['email'])
newuser.set_password(rf.cleaned_data['password'])
newuser.save()
#need to mark newuser as inactive still
profile = pf.save(commit=False)
profile.user = newuser
profile.save()
return HttpResponseRedirect("/register-success/")
return render_to_response("authentication/index.html", {'rform': rf, 'pform':pf,'lform':lf})
return main(request)
def register_success(request):
return render_to_response("authentication/register-success.html")
My url-conf:
(r'^register-success/$','register_success'),
The other way I thought to do it was to just render_to_response("authentication/register-success.html") and not do the redirect. The benefit is, no one can access the register-success.html page, the downside is if the user refreshes the page it will try and resubmit the POST. What's the best practice?

I would stick with the redirect, getting duplicate users is a fairly large risk. What is the risk of someone seeing your register success page who hasn't registered? If there is a risk, you could always generate a random token, put it in session and pass it to your register-success page and then in your view check that the token matches. But that seems like a lot of work for what typical success pages are.
My recommendation would be to not worry about people being able to get to that page without registering. If it is just static HTML, there can't be any risk with showing to to everybody, right?

You can set the cookie, a session key in register view that you can check for in the register_success view only on its presence render the page, else redirect to main register.

Related

Django CSRF Protection with user-based get request

I have the following "profile" view in my django app:
#login_required
def user_profile(request):
current_user = request.user
student_profile = get_object_or_404(Student, student_user = current_user)
reviews = StudentReview.objects.filter(target_student = student_profile).reverse()
for stu_review in reviews:
stu_review.review_seen = True
stu_review.save()
context = {
'user': current_user,
'profile': student_profile,
'reviews': reviews,
'is_logged_in': request.user.is_authenticated,
}
return render(request, 'polls/profile.html', context)
I was wondering whether or not this code is vulnerable to a CSRF attack. Since profile information is sensitive, and since profile information is displayed based on a user's identity, I wasn't sure whether someone could attempt a CSRF attack to display another user's profile information, or whether Django's middleware would take care of that. I have a number of views that behave similarly, so I want to make sure that this information is not at risk.
GET requests are not protected by Django's CSRF middleware. A CSRF attack is a possibility, but it would not disclose any information.
The way a CSRF attack works is that an attacker tricks a user (the victim) into visiting a specific page. Since the page is visited by the victim, any action on the page may be authorised using the victim's login session, rather than the attacker's login session. But, since the victim visits the page, the response is also send to the victim. The attacker has no way of reading the response (unless other vulnerabilities allow that, e.g. XSS).
In this specific example, a CSRF attack would only be able to mark reviews targeted at the victim as seen, without the victim actually seeing them. You need to decide whether that is harmless or an actual problem.

Prompting registration after creating content but before save

I am trying to implement a page using Django where the user is able to create content, hit submit, and then is prompted to register or login if they are not currently logged-in. One of the fields in the model for the content references the user, so they must be logged-in to create it, however I want them to be able to input all of the data before they are prompted with logging-in. I have used the #loginrequired decorator on the view, but that forced the user to be logged-in before they create the content. Here is the view currently:
def search(request):
form = LocationInput()
if request.method == "POST":
form = LocationInput(request.POST)
if form.is_valid():
t = Trip(trip_name = form.cleaned_data['city'])
t.user = request.user
t.save()
form.save()
l = Location.objects.get(google_id=form.cleaned_data['google_id'])
l.trips.add(t)
l.save()
return HttpResponseRedirect('/trips/')
return render(request, 'frontpage/search.html', {'form': form})
It loads the form, creates an object that needs a user associated with it so I need to register the user but keep the data from LocationInput(request.POST) until after the user has registered. Any help would be greatly appreciated!
I can see two options:
Allow NULL for user reference, save the content in a state "pending user login or sign up", and store the ID of the content object in a session. Vacuum "old" contents with no user reference on a regular basis.
Save the whole data of the form in a session.
I like the first one better since you, as a superuser, have access to the content even if the user didn't login or signup, and if the user contact you later telling you he had troubles signing in, you will be able to help and recover the content he submitted. While if everything is stored in session, once the session is deleted, it's all lost.

How do I set the "next" context variable in Django so I can redirect my user to the appropriate page?

I'm aware that there is a context variable called "next" which allows you to specify the URL to redirect to after a page, which is commonly used when you want to redirect a user to a login page, then redirect them back to the page they were at before the login page once they've logged in.
In my views I've resorted to manually setting it via the URL such as redirecting to /login/?next=/someurl, but this isn't a clean way. I've tried googling and such, but there is surprisingly little information about it online.
How exactly do you set the context variable "next"? My site has a form that anyone can see, but only logged in users can submit. Right now if the user isn't logged in, they will get redirected to the login page with the "?next=/someurl/" attached to it so they get sent back to the original form once they log in.
However, from the login page there is a link to the sign up page for users who don't have an account, and I want to set "next" to be the original form page so that after they sign up they are redirected back to the original form. Any ideas or advice?
It sounds like you want to not simply use next for one redirect, but persist it across two redirects:
Some form page -> login -> signup -> Get back to some form
For the login page by itself, Django provides some automatic help for this (if you use the built-in auth views). But the second hop to the signup page requires some custom code explained below.
If you are using Django's built-in login view, and have created your own template for it, then Django will pass the current value of next to the template as a context variable called (appropriately) next. You can use that in the link to your sign-up page like this:
Sign me up!
Consequently, in the view you create to handle your user signup page, the value of next will be accessible as a GET param:
def signup(request):
if request.method == 'GET':
next = request.GET.get('next', None)
if next:
# Add it as a hidden input in your signup form
# Or re-append it as a GET param
# Or stick it in the user's session, which is
# what this example does:
request.session['next'] = next
Finally, in the same signup view, when you respond to the POSTed signup form, retrieve the next value in whichever way you chose to propogate it in the GET request, and redirect to it's value.
def signup(request):
....
# POST handling section
if signup_form.is_valid():
next = request.session.get('next', None)
if next:
# See caution note below!
return redirect(next)
Caution:
Be aware that you should check and sanitize the next value before you redirect to it after processing the signup form, to prevent browser-side tampering. For example, it's common to validate that the URL belongs to your own domain (if that's appropriate) and/or that Django's resolve function is able to successfully resolve it.

How to add fields to a request?

I have a login page. Upon submission if 'webmail' is selected, the request
should be redirected to the webmail server, with the credentials submitted but
under different keys. Here's what I'm trying now:
if form.validate_on_submit():
if form.destination.data == 'webmail':
form.rc_user.data = form.email.data
form.rc_password.data = form.password.data
return redirect('https://example.com/webmail/', code=307)
This almost works: the POST is redirected to webmail. However the values
submitted are the default values, not the assigned values.
I have some more issues though:
the keys should be _user and _pass, but Flask seems to blow up with
leading-underscore field names.
I do not want to add these fields to the original class. I want to subclass
upon submission, somewhat as follows:
if form.validate_on_submit():
if form.destination.data == 'webmail':
class WebmailLoginForm(LoginForm):
rc_user = EmailField('user', default=form.email.data)
form = WebmailLoginForm(request.form)
return redirect('https://example.com/webmail/', code=307)
When I do this, the added fields show up as UnboundField and are not
submitted.
When issuing a redirect, the browser is simply told to resubmit to another server. I.e. it's too late for the server to influence the request.
So either: start a new request, or use javascript to change the submit target.
Thanks to my colleague Johan for kickstarting my brain.

Django: any way to avoid querying for request.user on every request?

For my website pretty much every page has a header bar displaying "Welcome, ABC" where "ABC" is the username. That means request.user will be called for every single request resulting in database hits over and over again.
But once a user is logged in, I should be able to store his user instance in his cookie and encrypt it. That way I can avoid hitting the database repeatedly and just retrieve request.user from the cookie instead.
How would you modify Django to do this? Is there any Django plugins that does what I need?
Thanks
You want to use the session middleware, and you'll want to read the documentation. The session middleware supports multiple session engines. Ideally you'd use memcached or redis, but you could write your own session engine to store all the data in the user's cookie. Once you enable the middleware, it's available as part of the request object. You interact with request.session, which acts like a dict, making it easy to use. Here are a couple of examples from the docs:
This simplistic view sets a has_commented variable to True after a user posts a comment. It doesn’t let a user post a comment more than once:
def post_comment(request, new_comment):
if request.session.get('has_commented', False):
return HttpResponse("You've already commented.")
c = comments.Comment(comment=new_comment)
c.save()
request.session['has_commented'] = True
return HttpResponse('Thanks for your comment!')
This simplistic view logs in a "member" of the site:
def login(request):
m = Member.objects.get(username=request.POST['username'])
if m.password == request.POST['password']:
request.session['member_id'] = m.id
return HttpResponse("You're logged in.")
else:
return HttpResponse("Your username and password didn't match.")
This smells of over-optimisation. Getting a user from the db is a single hit per request, or possibly two if you use a Profile model as well. If your site is such that an extra two queries makes a big difference to performance, you may have bigger problems.
The user is attached to the request object using the Authentication Middleware provided by django (django.contrib.auth.middleware). It users a function the get_user function in django.contrib.auth.init to get the user from the backend you are using. You can easily change this function to look for the user in another location (e.g. cookie).
When a user is logged in, django puts the userid in the session (request.session[SESSION_KEY]=user.id). When a user logs off, it erases the user's id from the session. You can override these login and logoff functions to also store a user object in the browsers cookie / erase user object from cookie in the browser. Both of these functions are also in django.contrib.auth.init
See here for settting cookies: Django Cookies, how can I set them?
Once you have proper caching the number of database hits should be reduced significantly - then again I'm not really and expert on caching. I think it would be a bad idea to modify request.user to solve your problem. I think a better solution would be to create some utility, method or custom template tag that attempts to load your require user data from the cookie, and return the result. If the user data is not found in the cookie, then a call to request.user should be made, save the data to the cookie, and then return the result. You could possibly use a post_save signal to check for changes to the user data, so that you can make update to the cookie as required.