Access session / request information outside of views in Django - django

I need to store a special cookies that is coming from some non-django applications. I can do this in views
request.session[special_cookies'] = special_cookies
But in the non-views py files, I need to access this special cookies.
According to docs, I can do this
>>> from django.contrib.sessions.backends.db import SessionStore
>>> import datetime
>>> s = SessionStore(session_key='2b1189a188b44ad18c35e113ac6ceead')
>>> s['last_login'] = datetime.datetime(2005, 8, 20, 13, 35, 10)
>>> s['last_login']
datetime.datetime(2005, 8, 20, 13, 35, 0)
>>> s.save()
If I don't supply the session key, Django will generate one for me.
I am concerned about the effect of getting many new session keys. (I don't think this is good when you have multiple users, right...?)
I want a user to have this special cookies binded to a user's session. However, I do not want to save this in a user profile, because for security reason. This cookie is generated when we login (our application will send in this special cookies). We want to send this cookie back and forth throughout the browsing session.
How should I go about solving this?
Thank you very much!
#views.py
request.session['special_cookies'] = library.get_special(user, pwd)
#library.py
def get_special_cookies(user, pwd):
res = get_special_cookies("http://foobar.com/api/get_special_cookies", user, pwd)
#foobar.py (also non-views)
def do_this(user, special_cookies)
I am pretty sure this is fine....
#views_2.py
def dummy_views(request):
foobar.do_this(request.user, request.session['special_cookies'])
But there are instances where I don't want to get my special cookies through views / calling get_sepcial_cookies. I want it to last throughout. Or am I overthinking..?

In order to explain why you are in a dangerous path, we have to remember why server side sessions where invented in the first place:
HTTP is a stateless protocol. A stateless protocol does not require the server to retain information or status about each user for the duration of multiple requests. For example, when a web server is required to customize the content of a web page for a user, the web application may have to track the user's progress from page to page. A common solution is the use of HTTP cookies. Other methods include server side sessions, hidden variables (when the current page contains a form), and URL-rewriting using URI-encoded parameters.
Django is a very mature framework; if some goal seems hard to accomplish in Django, probably you are taking the wrong approach to the problem. Even if you can store server side session information directly at the session backend, it seems like bad design for me, because session data is not relevant outside requests.
IMHO, if you need to share authentication/authorization data among applications, you should really consider something like OAuth, otherwise you will end up with something insecure, fragile, ugly and hard to support.
(sorry if I sound condescending, English is not my native idiom).
[update]
Hi Paulo. Thank you very much. I believe my team doesn't want to introduce OAuth or any sort of extra layer of authetication mechaicism. But are you against inserting this special cookies into HttpResponse.COOKIES?
A few remarks if you you really want to go this way:
you will be constrained by the "same domain" restriction (the other application should reside in the same TLD)
you should use some sort of signing to avoid tampering with the cookies
Is that a better solution than request.session?
There are some mechanisms to deal with this kind of problem at a higher level. For example:
if you want to make a variable present at every template context based on the value of some cookie, you can write a custom context processor.
if you want to reroute views depending on the presence of a cookie, you should write a custom middleware.
I can't provide a more specific solution without further details about your goals, but using these hooks you can avoid repeating code to test for the external cookie in every view - note however that everything concerning cookies is tied to the request/response context and makes no sense outside it.

Related

Users for multiple sites in Django

I am trying to get multiple sites to use the same database and code but in a way which forces each user to have their own login to each site.
I have seen a few suggestions as to how to make this work but I'm not sure which way to go.
I am using the Sites Framework, using the subdomain to identify the current site so I'm not using SITE_ID at all.
Use the sites framework - This answer (https://stackoverflow.com/a/1405902/1180442) suggests using the sites framework to do it, but I'm having trouble with the get_user() method, as it doesn't have access to the request and that's where my site info is stored.
Use separate databases for users - I'm really not sure about this one but I think it might cause bigger problems down the line.
Change to using SITE_ID - I want to try and avoid this if possible as it will mean having to run many different instances of my app, one for each site, which uses it's own settings.py. This will quickly turn into a nightmare, I think.
Permissions - I'm wondering if this should be something that I get the permissions framework to use? So one set of users for all sites but each user can have permissions to see each site, as long as they've registered with that site?
Can anyone help with this?
I quite like the idea of number 1 but I just need to get the request in the get_user() method so I can do this
def get_user(self, user_id):
try:
# I can't do this because there is no request available here
return User.objects.get(pk=user_id, site=request.site)
except User.DoesNotExist:
return None
to prevent people logged in to one site being able to log into another using the same session.
How I actually do it, not for users but for common databases, Is to design a main, hidden app with a REST API architecture. My other apps, naturally have their own DB and exchange their data via batch or stream process depending on the need. I use django-rest-framework.
For your case what I would do is that whenever a user makes a Log In request I would send it via HTTPS to my main database and get it authenticated in my main app. Whenever I would need to validate the user status I would simply make a get request to the main app.
This architecture is not that different from the one that many mobile apps have.
I hope it helps.

How does viewset aligns with rest methods

I am relatively new to DRF, but found viewsets an amazing abstraction technique for writing RESTful API. I am having a hard time correlating Viewsets with REST methods though. Let's say I have a viewset for Userprofiles and somebody new creates a profile on client.
Should this send a PUT or a POST ?
Which url should this request go to, http://user or http://user/new_id ?
If I want this profile only accessible to the user or admin(all CRUD operations), then where should I handle the code for making it inaccessible to others ?
Should I create a new permission ? If yes, should I handle rest methods in has_permission/has_object_permission ?
I have gone through the tutorial, and know how permissions/mixins works, but I am not able to connect these dots here.
1/ In general, POST is for creating new, PUT is for updating. See the docs on the SimpleRouter to show how the various types of Http methods align with various actions in your Django backend.
2/ You'll find that different situations call for different routing methods. If yours is standard, you may want to use a SimpleRouter like the example above. In that case, creating a new user would be a POST request to /user/ and updating a user would be a PUT request to /user/{{user_id}}/.
3/ To limit access to various API methods, you want to use Permissions. It's possible that you could use one of DRF's Custom Permissions. I've found that in general, if you want only the user to access his/her own profile, it's easier to either use conditional logic within a view (i.e., return a DRF PermissionDenied exception in the view logic if the request.user.pk is not the pk of that REST url. More often than not, I've used the UserPassesTestMixin from Django Braces, that works pretty well to filter user permissions.
4/ I guess the Django Braces mixin above answers this question. You should include a test_func method if you're using Django Braces that returns True if you want to grant the user access, and False otherwise.
Hope this helps! I agree that these are difficult parts of DRF and perhaps some of these could more effectively get incorporated into the source. Good luck!

How to POST data to remote server and display result

I would like to submit some post request from my django app to payment service.
Is there a simple way to do so from within a view and then display the
remote service instead of my own view (something like being redirected to remote
page with post data)
Currently I am messing with some weird approach of displaying a hidden form and
ovveridng it's javascript submit call to implement some of my own stuff, but I think
it is overcomplicated.
I need something like this:
def view(request, **kwargs):
do_my_own_business()
post_data = create_post_dada(**kwargs)
return post_remote_page("https://example.com", post_data)
You can use python's urllib2 to to make a request to a 3rd party website. This is essentially emunlating how you or I would visit the website via code - here's a good tutorial.
If you want to make life easier, there are some libraries build upon urllib2 which make doing these external request easier - namely requests and mechanize. If you are from a PHP background you can also use pyCurl (Not based on urllib2)
Be sure that the payment service you are using doesn't have it's own API or library that you could use instead, as this would be much easier and safer then the above approach which is essentially screen scraping.
Furthermore, be aware that for every request your user makes to your server, your server needs to make to an other external server which means you could potentially time out etc. introducing another level of complexity in managing the connections.

Django: preventing external query string requests

I've been doing web development for a few months now and keep having this nagging problem. It is typical for pages to request content with a query string which usually contains meaningful data such as an id in the database. An example would be a link such as:
http://www.example.com/posts?id=5
I've been trying to think of a good strategy to prevent users from manually entering a value for the id without having accessed it from a link--I'd only wish to acknowledge requests that were made by links presented on my website. Also, the website may not have an authentication system and allows for anonymous browsing; that being said, the information isn't particularly sensitive but still I don't like the idea of not being able to control access to certain information. One option, I suppose, would be to use HTTP POST requests for these kind of pages -- I don't believe a user can simulate a post request but I may be wrong.
Furthermore, the user could place any arbitrary number for the id and end up requesting a record that doesn't exist in the database. Of course, I could validate the requested id but then I would be wasting resources to accommodate this check.
Any thoughts? I'm working with django but a general strategy for any programming language would be good. Thanks.
First, choosing between GET and POST: A user can simulate any kind of request, so POST will not help you there. When choosing between the two it is best to decide based on the action the user is taking or how they are interacting with your content. Are they getting a page or sending you data (a form is the obvious example)? For your case of retrieving some sort of post, GET is appropriate.
Also worth noting, GET is the correct choice if the content is appropriate for bookmarking. Serving a URL based solely on the referrer -- as you say, "prevent users from manually entering a value for the id without having accessed it from a link" -- is a terrible idea. This will cause you innumerable headaches and it is probably not a nice experience for the user.
As general principle, avoid relying on the primary key of a database record. That key (id=5 in your case) should be treated purely as an auto-increment field to prevent record collisions, i.e. you are guaranteed to always have a unique field for all records in the table. That ID field is a backend utility. Don't expose it to your users and don't rely on it yourself.
If you can't use ID, what do you use? A common idiom is using the date of the record, a slug or both. If you are dealing with posts, use the published/created date. Then add a text field that will hold URL friendly and descriptive words. Call it a slug and read about Django's models.SlugField for more information. Also, see the URL of an article on basically any news site. Your final URL will look something like http://www.example.com/posts/2012/01/19/this-is-cool/
Now your URL is friendly on the eyes, has Google-fu SEO benefits, is bookmark-able and isn't guessable. Because you aren't relying on a back-end database fixed arbitrary ID, you have the freedom to...restore a backup db dump, move databases, change the auto-increment number ID to a UUID hash, whatever. Only your database will care, not you as a programmer and not your users.
Oh and don't over-worry about a user "requesting a record that doesn't exist" or "validating the requested id"...you have to do that anyway. It isn't consuming unnecessary resources. It is how a database-backed website works. You have to connect the request to the data. If the request is incorrect, you 404. Your webserver does it for non-existent URLs and you'll need to do it for non-existent data. Checkout Django's get_object_or_404() for ideas/implementation.
There are two ways I know of to do this effectively, since there is basically no way to stop someone from forging any request.
The first is not to use bare IDs in the query parameters. Instead, generate a large random number, and make the link out of that. You will have to keep a table in your database mapping your random numbers to the actual IDs they represent, and you will have to clean the table eventually. This is fairly simple to implement, but requires some storage space, and some management of the stored data occasionally.
The second method is to sign the data when you make a link. By appending a cryptographic signature to the data, and verifying the signature when a request is made, you ensure that only your web service could possibly have created the link. Even if the request itself is 'forged' -- perhaps bookmarked, written down, copy-and-pasted into another browser -- you know that your site has already authorized that URL.
To do this, you need to create a Message Authentication Code (MAC) with the data that you are signing (say, just the 'id' value, or possibly the id and the time that you signed the data) and with a secret key that you keep only on your server.
In your view, then, you take the id value (or id and timestamp, if that's what you're using) and you construct the MAC again, and see if they match. If there's any difference, you reject the request as having been tampered with.
Look at the python docs for the hmac module, as well as the hashlib module for all of the details.
You could generate a link in python like this:
settings.py:
hmac_secret_key = '12345'
views.py:
import time, hmac, hashlib
from django.conf import settings
def some_view(request):
...
id = 5
time = int(time.time())
mac = hmac.new(
settings.hmac_secret_key,
'%d/%d' % (id, time),
hashlib.sha1)
url = 'http://www.example.com/posts/id=%d&ts=%d&mac=%s' % (
id, time, mac.hexdigest())
# Now return a template with that url in it somewhere
To verify it in another view, you would use code like this: (warning, warning, not robust, lots of error checking still to do)
def posts_view(request):
id = int(request.GET['id'])
ts = int(request.GET['ts'])
mac_from_url = request.GET['mac']
computed_mac = hmac.new(
settings.hmac_secret_key,
'%d/%d' % (id, time),
hashlib.sha1)
if mac_from_url <> computed_mac:
raise SomeSecurityException()
# Now you know that the request is legit.
# You can check the timestamp here, too, if you like.
I don't know if this is the correct way of doing it but maybe you can save the url he is going to be redirected after the GET request into the session, and write a decorator so that if the session has that url take him to that page. else give a 404 error or something.

User permissions Django for serving media

I want to set up a Django server that allows certain users to access certain media. I'm sure this can't be that hard to do and I'm just being a little bit silly.
For example I want USER1 to be able to access JPEG1, JPEG2 and JPEG3 but not JPEG4, and USER2 to be able to access JPEG3 and JPEG 4.
[I know I should be burnt with fire for using Django to serve up media, but that's what I'm doing at the moment, I'll change it over when I start actually running on gas.]
You can send a file using django by returning the file in the request as shown in Vazquez-Abrams link.
However, you would probably do best by using mod_xsendfile in apache (or similar settings in lighttpd) due to efficiency. Django is not as fast at sending it, one way to do so while keeping the option of using the dev server's static function would be http://pypi.python.org/pypi/django-xsendfile/1.0
As to what user should be able to access what jpeg, you will probably have to implement this yourself. A simple way would be to create an Image model with a many-to-many field to users with access and a function to check if the current user is among those users. Something along the line of:
if image.users_with_access.filter(pk=request.user.id).exists():
return HttpResponse(image.get_file())
With lots of other code of course and only as an example. I actually use a modified mod_xsend in my own project for this very purpose.
You just need to frob the response appropriately.
You can put the media in http://foo.com/media/blah.jpg and set up a media/(?P<file>.*) in urls.py to point to a view blahview that checks the user and their permissions within:
from you_shouldve_made_one_anyways import handler404
def blahview(request,*args,**kwargs):
if cannot_use( request.user, kwargs['username'] ): return handler404(request)
...
Though just to be clear, I do not recommend serving media through Django.