How I Got Grok Talking To CAS ============================= :Author: Brandon Craig Rhodes :Version: unkown One user's experience connecting his Grok application to a CAS authentication web server, which he probably did the wrong way around, but which he's sharing because at least it worked. Might be outdated! This document hasn't been reviewed for Grok 1.0 and may be outdated. If you would like to review the document, please read this post. Purpose ------- Our campus uses a CAS server to provide single-sign-on across web applications for our users. How can a Grok application send its users to a CAS server to make them log in? Well, knowing almost nothing about Grok or Zope, I had to put together something simple fairly quickly. In particular, this solution does not integrate with the standard and pluggable Zope authentication mechanism; instead, it does its own thing. Perhaps wiser and more experienced Zope folks can write another HOWTO explaining how to do this right! But until then, people were asking for something — anything — that would give them information with which they could get started. The Three Steps --------------- There were three steps necessary to get Grok working with CAS. They each wound up getting their own .py file the way I did it. - auth.py This file contains a primitive method for recording, in a user's cookie-based session, which CAS user they've authenticated themselves as (if any), and for doling that information back out when various parts of Zope call for an IAuthentication local utility. - login.py This file, and its accompanying template named login_templates/login.pt, creates a very simple (and, to be frank, fairly ugly) login screen. The screen consists of a single button that sends you to Webauth. - app.py Finally, you'll need to add one or two statements to your main app.py file in order to register the above-mentioned adapter as a local utility. Wow, that really sounds clunky, doesn't it? And it occurs to me that, the way I've done it, the login screen doesn't remember where you were when you asked to log in, so once you're logged in you've got to navigate back to the page you came from all by yourself. I guess we'd better improve this scheme soon. Remembering Who Has Authenticated --------------------------------- How can Grok remember who is authenticated to your site? The most convenient way to store this information is through a wonderful mechanism Zope 3 provides called ISession. The magic of this adapter is that Grok, without even being asked, takes it upon itself to mark with a cookie every user that visits your site, and then keep up with them as they move from page to page. At any time you can invoke the ISession utility in order to access a bundle of behind-the- scenes information that Grok will remember for you between the user's page visits. And so, here is my auth.py file. It provides one or two simple functions for getting and setting a username in the user's session where they'll be safely stored, and then an IAuthentication utility that can spring into action when any part of the frameworks wants to know who is connected while the user is browsing inside of your Grok application:: import grok from zope.session.interfaces import ISession from zope.app.security.interfaces import IAuthentication from zope.app.security.principalregistry import Principal def get_auth_data(request): return ISession(request)['MyGrokApp.auth'] def get_user(request): return get_auth_data(request).get('user', None) def set_user(request, user): get_auth_data(request)['user'] = user class MyAuthentication(grok.LocalUtility): grok.implements(IAuthentication) grok.provides(IAuthentication) def unauthenticatedPrincipal(self): """We implement no unauthenticated principal of our own.""" def authenticate(self, request): user = get_user(request) if user: title = 'User ' + user description = 'The user ' + user return Principal(user, title, description, 'aaaaa', 'bbbbb') You can ignore that aaaaa and bbbbb stuff in that last Principal constructor, or put in something of your own devising. They're nonsense strings that I added because I never expected to use those last two values that you have to provide with a Principal — they're called the login and the pw — but if they ever pop up somewhere, hopefully I'll recognize those strings and know to come back here to set them to something more reasonable! The Actual CAS Login Part ------------------------- That previous section of this HOWTO might disappear at any time, because someone wiser will doubtless come along and point out some much easier way of keeping up with the username of who's logged in; Zope probably has one or more such ways already built in that I just didn't run across while browsing and in a hurry. But this next bit is more important, because knowing how to "talk CAS" is the key to what we're doing here that's new. Fortunately CAS is a very easy protocol, and a complete implementation of a login page can look exactly like this:: import grok from urllib import urlopen, urlencode from mywebapp.app import MyWebApp from mywebapp.auth import get_user, set_user class Login(grok.View): grok.context(MyWebApp) def update(self, redirect=None, ticket=None, logout=None): if logout: set_user(self.request, None) elif ticket: data = urlencode({ 'service': self.url(), 'ticket': ticket }) socket = urlopen('https://your.cas.server/validate', data) parts = socket.read().split() if parts and parts[0] == 'yes': set_user(self.request, parts[1]) self.redirect(self.url(grok.getSite())) elif redirect: data = urlencode({ 'service': self.url() }) self.redirect('https://your.cas.server/login?' + data) self.user = get_user(self.request) This is very straightforward! If the user arrives with the logout parameter set, then we wipe out our knowledge of who they are. If, instead, they look like they're returning from having just logged in to CAS and have a ticket purporting to prove their identity to us, then we ask CAS whether it will vouch that the ticket is valid; if so, then we set the current user to the one returned during ticket validation and then redirect the user back to the home page. (This is where one big improvement could take place: if the redirection was back to where the user was before they logged in.) Else, if they have shown up with redirect set, then this means they've asked to go log in to CAS, so we redirect them there. And if all else fails, we just show them the login page. "But wait! What does the login page look like?!" I hear you cry. It just so happens that I have it right here (it lives, per the usual Grok conventions, in login_templates/login.pt)::
You are already logged in.
Your name is username.
You are currently not logged in.
Press the button below to log in using CAS.