4

Dear omnoscient beings at Stackoverflow,

In Django 1.3 I am making a process_request middleware that gets a token from an url, logs the user in (if it's correct) and removes the token from the url. However:

I) Django recommends against accessing POST/GET data in middleware, I'm not really sure why so... Does the same apply to request.path ? https://docs.djangoproject.com/en/dev/topics/http/middleware/#process-view

II) I want to remove the token from the URL, so /album3/pic42/~53Cr3t70K3n/like/ -> /album3/pic42/like/. Changing request.path however does not work. The page will not be found, while

  • The middleware does process correctly (verified by print)

  • Directly entering /album3/pic42/like/ does work

  • The error (with token) shows Request URL: http://www.site.com/album3/pic42/like/

Is there a fix for this, or am I approaching this from the wrong angle entirely?

Thanks in advance!

I just realized that to change it client side, obviously I need a redirect (why didn't I think of that...). However, it would still be useful to be able to rewrite it just server-side without a new request, for example for accessing a personalized image.


P.s.: more details if needed, feel free to skip

I am working on a site that (will) sends personalized emails to users. I would like users to be able to click links in the email and be logged in automatically, by means of a token in the email link. This is in addition to normal login. (I know it's a less secure because people might forward an e-mail, but it's good enough for my site). The url would look something like this: /album3/pic42/~53Cr3t70K3n/like/ (with http://www.site.com stripped, Django does that)

I am writing a middleware to match this and log the user in when appropriate, a authentication backend for accepting a token as valid credentials and a token model.


Middleware process_request function: def process_request(self, request):

    if '/~' in request.path:
        result = re.search('(.*)/~(.+?)/(.*)', request.path)
        (uidb36, token) = result.group(2).split('-', 2)
        user = authenticate(uidb36 = uidb36, token = token)
        if user: login(request, user)
        return HttpResponseRedirect('%s/%s' % (result.group(1), result.group(3)) + ('?' + '&'.join('='.join(item) for item in request.GET.items()) if request.GET else ''))
    return None

Right now it works with redirects, I'd like to also be able to do it internally.

Mark
  • 18,730
  • 7
  • 107
  • 130

2 Answers2

2

If you don't want to mess up with upload handlers, I have a better solution for you:

  1. Create a rule in your urls.py to specifically catch visits with tokens

    Put it at the start of the urlpatterns to make sure that it will be evaluated first. Something like this will do:

    (r'/~', 'my_app_name.my_redirect_view'),
    
  2. Create a view:

    def my_redirect_view(request):
        #Compiled regular expressions work much faster
        beloved_tokens = re.compile(r'(.*)/~(.+?)/(.*)')
        result = beloved_tokens.search(request.path)
        try:
            (uidb36, token) = result.group(2).split('-', 2)
            path_end = result.group(3)
        # We use "try" to be sure that no one had
        # messed with our beloved tokens:
        except AttributeError: 
            raise Http404
        else:
            user = authenticate(uidb36 = uidb36, token = token)
            if user: 
                login(request, user)
                return HttpResponseRedirect('%s/%s' % (result.group(1), result.group(3)) + ('?' + '&'.join('='.join(item) for item in request.GET.items()) if request.GET else ''))
            else:
                raise Http404
    
Community
  • 1
  • 1
Ivan Kharlamov
  • 1,889
  • 2
  • 24
  • 33
  • Of course, you can add some additional processing in order to strip token from request.GET. But the whole concept remains the same: **we don't need middleware** to solve the issue. – Ivan Kharlamov Nov 06 '11 at 21:02
  • Using url match is a good idea, I hadn't thought of that! (Also adding try except is a good idea).But is there a way to go on to match the url stripped of token after this, because it'd also like a method that doesn't need redirect... – Mark Nov 06 '11 at 21:24
  • @Mark, why don't you add a token at the end of the url? Is there a specific reason to put it in the middle? – Ivan Kharlamov Nov 06 '11 at 21:26
  • You mean to match them with the normal views instead, and read the token either at the start of the view or with middleware? r'^album/' instead of r'^album/$', I guess that'd work if I put things like r'^album2' before r'^album' (or it'd match the first one)... – Mark Nov 06 '11 at 21:33
  • In any case, if you use middleware or normal views, having the token at the end of an url is much better. You should probably use token as a GET parameter like `?token=...` and do as @Anurag suggested. – Ivan Kharlamov Nov 06 '11 at 22:00
1

IMO changing request.path is more bad(if it can be called bad) compared to accessing GET/POST parameters, so just pass token as GET parameter and login based on token and do not redirect or do request.path modifications

I see token as a added attribute to a valid url, hence a middleware acknowledges that and does something with that token, but url still is handled by the correct view, so middleware to me seems a very logical fit here.

Anurag Uniyal
  • 85,954
  • 40
  • 175
  • 219
  • I don't see any reason to use middleware at all. – Ivan Kharlamov Nov 06 '11 at 21:21
  • Yes I can see how GET would be more appropriate for this than request.path, but it apparently gives trouble with upload handlers (which I don't know yet if I will need later). But I guess I can use GET at least for the redirect-way, in which case that problem doesn't exist! – Mark Nov 06 '11 at 21:27
  • @Ivan Kharlamov, but middleware makes thing easier to handle and can avoid a redirect – Anurag Uniyal Nov 06 '11 at 21:34
  • 1
    @Mark, if you are modifying upload handlers, you can do them before token middleware and everything will be fine, csrf middleware also accesses POST params – Anurag Uniyal Nov 06 '11 at 21:35
  • @AnuragUniyal, you are probably right, it is better to use middleware. And it is [DRY](https://docs.djangoproject.com/en/dev/misc/design-philosophies/?from=olddocs#don-t-repeat-yourself-dry). – Ivan Kharlamov Nov 06 '11 at 21:53