Overview

This page describes how we used an existing MoinMoin installation to provide user authentication for a separate Django-based web application.

Our use case looks like this:

The login part requires Django to read the MoinMoin cookie and look up the session and user in MoinMoin. This is similar to HelpOnAuthentication/ExternalCookie--just in reverse.

Redirecting to the login page and back requires a small change to the MoinMoin code. When a Django page requires a logged-in user, it should redirect to MoinMoin's login action, passing the query string "?action=login&next=<url-of-django-page>". When the user clicks the login button, she should be redirected back to the Django page at "<url-of-django-page>".

MoinMoin Changes

Adding the query parameter "next" requires changes to the login action and to the login form. This is the modified LoginHandler.handle() method in the file MoinMoin/action/login.py (based on MoinMoin 1.9.3):

   1 # MoinMoin/action/login.py
   2 
   3 def handle(self):
   4     _ = self._
   5     request = self.request
   6     form = request.values
   7 
   8     islogin = form.get('login', '')
   9     display_login_form = not islogin
  10 
  11     if islogin: # user pressed login button
  12         if request._login_multistage:
  13             return self.handle_multistage()
  14         if hasattr(request, '_login_messages'):
  15             for msg in request._login_messages:
  16                 request.theme.add_msg(wikiutil.escape(msg), "error")
  17         if request.user.valid:
  18             if "next" in form:
  19                 request.theme.send_title(_("Login"), pagename=self.pagename,
  20                                          pi_refresh=(0, form["next"]))
  21                 request.write(
  22                     "<p>You will be redirected to <a href='%s'>%s</a>. "
  23                     % (form["next"], form["next"]))
  24                 request.write(" If not click the link.</p>")
  25                 request.theme.send_footer(self.pagename)
  26                 request.theme.send_closing_html()
  27                 return
  28             else:
  29                 return self.page.send_page()
  30         else:
  31             display_login_form = True
  32 
  33     if display_login_form:
  34         request.theme.send_title(_("Login"), pagename=self.pagename)
  35         # Start content (important for RTL support)
  36         request.write(request.formatter.startContent("content"))
  37 
  38         request.write(userform.getLogin(request))
  39 
  40         request.write(request.formatter.endContent())
  41         request.theme.send_footer(self.pagename)
  42         request.theme.send_closing_html()

The important part is passing the "pi_refresh" parameter to theme.send_title(). This results in a <meta http-equiv="refresh"> element being added to the page, same as with the "#REFRESH" processing instruction.

Note: What I really wanted to do is calling request.http_redirect() so that the redirection happens through an HTTP status code. However, request.http_redirect() is implemented inside the Werkzeug library by raising an exception. This aborts request processing before the login information is properly stored to the session. I asked about this on IRC, and the "pi_refresh" looks like the best solution with 1.9.3.

In addition to handling "next", I changed the method to stay on the login page when the user mistypes their password. (The original version goes to a wiki page, which we don't want when coming from a Django page.)

The second change is adding a hidden "next" form field to the login form in the asHTML() method in the file MoinMoin/userform/login.py (based on 1.9.3):

   1 # MoinMoin/userform/login.py
   2 
   3 def asHTML(self):
   4     ...
   5     self._form.append(html.INPUT(type="hidden", name="action", value="login"))
   6 
   7     if "next" in request.values:
   8         self._form.append(html.P().append(html.Text(
   9             "The page that you are trying to access requires you to log in"
  10             " with your wiki account. Once you have logged in, you will"
  11             " be redirected back.")))
  12         self._form.append(html.INPUT(type="hidden", name="next", value=request.values["next"]))
  13     ...

Django Changes

To sign in to Django using MoinMoin, the Django code needs to do several things:

The file wiki_auth.py contains the authentication middleware and backend that does this. The code accesses the MoinMoin session and user files directly. This makes the code independent of the MoinMoin classes, but it ties the code to the specific format of these files.

To enable it, add the class WikiAuthMiddleware to your MIDDLEWARE_CLASSES setting in Django.

In addition, set the LOGIN_URL setting to the URL of a Django view that redirects to MoinMoin's login action and adds the "next" query parameter. The view code might look something like this:

   1 from django.conf import settings
   2 from django.http import HttpResponseRedirect, QueryDict
   3 
   4 WIKI_URL = '/'
   5 
   6 def login(request):
   7     redirect_to = request.REQUEST.get('next', '')
   8     if not redirect_to:
   9         redirect_to = settings.LOGIN_REDIRECT_URL
  10     query = QueryDict('', mutable=True)
  11     query['action'] = 'login'
  12     query['next'] = redirect_to
  13     return HttpResponseRedirect(WIKI_URL + "?" + query.urlencode(safe='/'))

MoinMoin: HelpOnAuthentication/DjangoAuth (last edited 2011-10-27 15:30:45 by MichaelFoetsch)