Session Variables with Cached Pages in Django - python

I'm building an app using someone else's API allowing the user to browse content from their site. The user can log into my project with their credentials from this other site in order to "favorite" content from their site.
When they log in, I get a user_token for them. To avoid having to create a user file for them, I just store this token as a session variable:
# Set session
request.session.set_expiry(60 * 60)
# Save token in session
request.session['user_token'] = unicode(auth.Message)
I'm also using file caching for the content from the site:
CACHE_BACKEND = 'file:///..../cache/'
And using the #cache_page command before certain views to cache the data:
#cache_page(CACHE_TIME)
def listings_by_cat_page(request, category_id):
# view stuff here
The problem when I'm running into is when a user views the home screen (which is cached), clicks log in, logs in, and then returns to the home screen, the Login/Logout button doesn't know to switch. I'm assuming it's because since it's a cached page, it doesn't see the user_token session variable.
{% if not request.session.user_token %}
Login
{% else %}
Logout
{% endif %}
Ideally, I'd like to cache the content, but have the page recognize the change in the request.session variables.
Any ideas?

Modify the Vary header and django will generate a new cache key.
Update: I think you should go for the cookie method, as the SessionMiddleware already sets the appropriate Vary headers which is why with Auth, the caching works correctly.
Since you only want to set this once, the cookie method is the way to go I think.
Whichever view handles the logging in of the API method should set a cookie that is a secure hash of the user token and the rest should work, if my thinking is correct.

use {% cache %} tag to cache only parts of the page that are user-independent.

Related

Finding the source of Django login page message

I have created a working Django application. I am using django-allauth for implementing social-account authentication.
Now, suppose I am logged-in inside my application using an e-mail id whose user does not have a staff access and, if I open admin login page directly, the admin login page is displayed as follows:
My question is: how can I stop Django from displaying the message "Successfully signed in as ... "? Where is the source of this message present?
The Successfully signed in message is a message from django-allauth. It uses the messages framework.
I suggest that you add the template code to display messages to your base template. That way, the message will be displayed immediately after you have logged in, rather than when you go directly to the admin login (which does include the template code to display messages).
The text you are referring to (You are authenticated as ...) can be found in this template:
python3.6/site-packages/django/contrib/admin/templates/admin/login.html
You can override this template to remove the messsage. For example, look into this question for how to override djangos default templates.
To override this you need to inherit the template by {% extends "admin/login.html" %}
Then you need to override the block with the name.
{% blocktrans trimmed %} You are authenticated as {{ username }}, but are not authorized to access this page. Would you like to login to a different account? {% endblocktrans %}
Now you can customize this particular line and then point your function to load your custom html file instead of the standard admin ones or you can directly edit the login.html in your django package(Not a good idea). To know where it is fetching from you can do the following...
$python
>>>import sys
>>>sys.path = sys.path[1:]
>>>import django
>>>print(django.__path__)
And then go into contrib\admin\templates\admin and edit the login.html manually.

Django - Show message in template if this user has not been on the site before

I was wondering how might I show a new user a message such as 'First time here?' in a Django template using conditionals?
e.g.
{% if this_website_has_not_been_accessed_by_user %}
<h3>First time here?</h3>
<h5>Follow these steps...</h5>
{% endif %}
What might be the best way to do this? Thanks
Assuming you only want to show that banner at the top of the page for logged in users (those that have a User instance in the current request), you could create a Model (or, if you have your own user profile model, add a field to it) to save that.
class Access(models.Model):
user = models.ForeignKey(User)
has_been_here_before = models.BooleanField(default=False)
In your view you would have to check that Model to see if the user is registered there and has the flag set.
See this post for detecting first login.
See also this similar post for detecting a visit to each individual page or view.
EDIT: according to your comments you want to show the welcome banner to anonymous users (not logged in) if they visit for the first time.
You could achieve that using djangos session framework. You can use request.session just like a simple dict.
In your home page view (or through a custom middleware so it affects all pages not just the home page) you could check if a user has already dismissed the welcome banner or not.
def my_home_view(request):
...
# as default 'show_welcome_banner' will be True
context['show_welcome_banner'] = request.session.get('show_welcome_banner', True)
...
Then, in your template use:
{% if show_welcome_banner %}
<h3>First time here?</h3>
<h5>Follow these steps...</h5>
{% endif %}
Your banner could have a 'dismiss' button that posts a hidden to a different url (maybe even through ajax). This url would alter the users session info:
def dismiss_welcome_banner(request):
...
if request.method == 'POST':
request.session['show_welcome_banner'] = False
...
Notes:
if a user access the site through incognito mode (the session cockie gets deleted when he closes the browser) he will need to click the dismiss button every time he opens the incognito browser.
You could additionally check that loggen in users do not get to see that banner, depending of how you prefer it.

Django default login view always populates the next field

I'm using the default django authentication system with little customization. The core functionality to login and logout is working as expected. The problem is with the following snippet in my login form template:
{% if next %}
<p>Please login to see this page.</p>
{% endif %}
This is adapted from the example login view provided in the official documentation. The idea is that if the user tried to access a protected page without logging in, he/she would be redirected to the login page and the next parameter is set to the protected page's url. This is working fine.
However, when the user clicks on the login url and navigates directly to the login url, the above error message should not be displayed. But in this case, the next parameter is being set to the LOGIN_REDIRECT_URL from settings.py and we see this error message.
I tried to debug to find where the problem is, and found it in django.contrib.auth.views.LoginView class. This class has a method get_success_url which gets the redirect url either from the next parameter or from the LOGIN_REDIRECT_URL. This method is being used to populate the context for the login form in the method get_context_data, which, in my opinion is incorrect. The dispatch method also uses the get_success_url to get the redirect url, which is correct because the purpose is to actually redirect.
I'm not sure if my explanation is clear, let me know if it isn't. Is there a workaround for this? Should I submit a bug report for this?
Using:
Python: 3.6 (Anaconda3, 64 bit)
Django: 1.11.1
django-registration: 2.2
Raised Django bug https://code.djangoproject.com/ticket/28229.
This is not a solution but more like a workaround:
Assuming that your LOGIN_REDIRECT_URL is set to /, you could write {% if next != "/" %} instead of {% if next %}.

How to have specific users see different webpages in Django?

I was wondering how one would handle a website that, once a user logged in, would be able to show a completely different page to each user?
Think of a master login/registration page.
Once a user registers, depending on their permissions, they would be redirected to the appropriate page for their user type (whilst not being able to access those of any other type).
So would this be an individual user/group permissions thing (using Django auth to do so)? Or would this be able to be implemented using models (i.e. have models of each type of user account and just hold instances in the database)?
This is my first week or so with Django (and web dev), so I am not at all familiar with this stuff.
Redirecting users based on permissions
On the After a user logs in, let's say they land on a URL set by settings.LOGIN_REDIRECT_URL that can you can change to e.g./enter/ URL , and you can map that /enter/ URL to be backed by below view called enter_view()
from django.shortcuts import redirect
def enter_view(request):
if request.user.has_perm('app_label.permission_codename'):
return redirect('/page-A')
elif request.user.has_perm('app_label.another_permission'):
return redirect('/page-B')
else:
return redirect('/page-C')
To change your LOGIN_REDIRECT_URL simply put in settings.py
LOGIN_REDIRECT_URL = '/enter/' # or any URL you want
References
https://docs.djangoproject.com/en/1.8/ref/settings/#login-redirect-url
https://docs.djangoproject.com/en/1.8/ref/contrib/auth/#django.contrib.auth.models.User.has_perm
https://docs.djangoproject.com/en/1.8/topics/http/shortcuts/#redirect
Checking permissions in your template
Further more if you need to customize small parts of a template, you can also check user permissions in similar manner using the {{ perms }} object
{% if perms.foo.can_vote %}
<p>You can vote!</p>
{% endif %}
https://docs.djangoproject.com/en/1.8/topics/auth/default/#permissions

Django user impersonation by admin

I have a Django app. When logged in as an admin user, I want to be able to pass a secret parameter in the URL and have the whole site behave as if I were another user.
Let's say I have the URL /my-profile/ which shows the currently logged in user's profile. I want to be able to do something like /my-profile/?__user_id=123 and have the underlying view believe that I am actually the user with ID 123 (thus render that user's profile).
Why do I want that?
Simply because it's much easier to reproduce certain bugs that only appear in a single user's account.
My questions:
What would be the easiest way to implement something like this?
Is there any security concern I should have in mind when doing this? Note that I (obviously) only want to have this feature for admin users, and our admin users have full access to the source code, database, etc. anyway, so it's not really a "backdoor"; it just makes it easier to access a user's account.
I don't have enough reputation to edit or reply yet (I think), but I found that although ionaut's solution worked in simple cases, a more robust solution for me was to use a session variable. That way, even AJAX requests are served correctly without modifying the request URL to include a GET impersonation parameter.
class ImpersonateMiddleware(object):
def process_request(self, request):
if request.user.is_superuser and "__impersonate" in request.GET:
request.session['impersonate_id'] = int(request.GET["__impersonate"])
elif "__unimpersonate" in request.GET:
del request.session['impersonate_id']
if request.user.is_superuser and 'impersonate_id' in request.session:
request.user = User.objects.get(id=request.session['impersonate_id'])
Usage:
log in: http://localhost/?__impersonate=[USERID]
log out (back to admin): http://localhost/?__unimpersonate=True
It looks like quite a few other people have had this problem and have written re-usable apps to do this and at least some are listed on the django packages page for user switching. The most active at time of writing appear to be:
django-hijack puts a "hijack" button in the user list in the admin, along with a bit at the top of page for while you've hijacked an account.
impostor means you can login with username "me as other" and your own password
django-impersonate sets up URLs to start impersonating a user, stop, search etc
I solved this with a simple middleware. It also handles redirects (that is, the GET parameter is preserved during a redirect). Here it is:
class ImpersonateMiddleware(object):
def process_request(self, request):
if request.user.is_superuser and "__impersonate" in request.GET:
request.user = models.User.objects.get(id=int(request.GET["__impersonate"]))
def process_response(self, request, response):
if request.user.is_superuser and "__impersonate" in request.GET:
if isinstance(response, http.HttpResponseRedirect):
location = response["Location"]
if "?" in location:
location += "&"
else:
location += "?"
location += "__impersonate=%s" % request.GET["__impersonate"]
response["Location"] = location
return response
#Charles Offenbacher's answer is great for impersonating users who are not being authenticated via tokens. However, it will not work with clients side apps that use token authentication. To get user impersonation to work with apps using tokens, one has to directly set the HTTP_AUTHORIZATION header in the Impersonate Middleware. My answer basically plagiarizes Charles's answer and adds lines for manually setting said header.
class ImpersonateMiddleware(object):
def process_request(self, request):
if request.user.is_superuser and "__impersonate" in request.GET:
request.session['impersonate_id'] = int(request.GET["__impersonate"])
elif "__unimpersonate" in request.GET:
del request.session['impersonate_id']
if request.user.is_superuser and 'impersonate_id' in request.session:
request.user = User.objects.get(id=request.session['impersonate_id'])
# retrieve user's token
token = Token.objects.get(user=request.user)
# manually set authorization header to user's token as it will be set to that of the admin's (assuming the admin has one, of course)
request.META['HTTP_AUTHORIZATION'] = 'Token {0}'.format(token.key)
i don't see how that is a security hole any more than using su - someuser as root on a a unix machine. root or an django-admin with root/admin access to the database can fake anything if he/she wants to. the risk is only in the django-admin account being cracked at which point the cracker could hide tracks by becoming another user and then faking actions as the user.
yes, it may be called a backdoor, but as ibz says, admins have access to the database anyways. being able to make changes to the database in that light is also a backdoor.
Set up so you have two different host names to the same server. If you are doing it locally, you can connect with 127.0.0.1, or localhost, for example. Your browser will see this as three different sites, and you can be logged in with different users. The same works for your site.
So in addition to www.mysite.com you can set up test.mysite.com, and log in with the user there. I often set up sites (with Plone) so I have both www.mysite.com and admin.mysite.com, and only allow access to the admin pages from there, meaning I can log in to the normal site with the username that has the problems.

Categories

Resources