#!/usr/bin/env python # -*- coding: utf-8 -*- """ This file is part of web2py Web Framework (Copyrighted, 2007-2009). Developed by Massimo Di Pierro . License: LGPL v3 Tinkered by Szabolcs Gyuris < szimszo n @ o regpreshaz dot eu> """ import xml.dom.minidom as dom import xml.parsers.expat as expat from gluon import current, redirect, URL from gluon._compat import urlopen, to_native class CasAuth(object): """ Login will be done via Web2py's CAS application, instead of web2py's login form. Include in your model (eg db.py):: from gluon.contrib.login_methods.cas_auth import CasAuth auth.define_tables(username=True) auth.settings.login_form=CasAuth( urlbase = "https://[your CAS provider]/app/default/user/cas", actions=['login','validate','logout']) where urlbase is the actual CAS server url without the login,logout... Enjoy. ###UPDATE### if you want to connect to a CAS version 2 JASIG Server use this: auth.settings.login_form=CasAuth( urlbase = "https://[Your CAS server]/cas", actions = ['login','serviceValidate','logout'], casversion = 2, casusername = "cas:user") where casusername is the xml node returned by CAS server which contains user's username. """ def __init__(self, g=None, # g for backward compatibility ### urlbase="https://web2py.com/cas/cas", actions=['login', 'validate', 'logout'], maps=dict(username=lambda v: v.get('username', v['user']), email=lambda v: v.get('email', None), user_id=lambda v: v['user']), casversion=1, casusername='cas:user', change_password_url=None ): self.urlbase = urlbase self.cas_login_url = "%s/%s" % (self.urlbase, actions[0]) self.cas_check_url = "%s/%s" % (self.urlbase, actions[1]) self.cas_logout_url = "%s/%s" % (self.urlbase, actions[2]) self.maps = maps self.casversion = casversion self.casusername = casusername # vars commented because of # https://code.google.com/p/web2py/issues/detail?id=1774 self.cas_my_url = URL(args=current.request.args, #vars=current.request.vars, scheme=True) # URL to let users change their password in the IDP system self.cas_change_password_url = change_password_url def login_url(self, next="/"): current.session.token = self._CAS_login() return next def logout_url(self, next="/"): current.session.token = None current.session.auth = None self._CAS_logout() return next def change_password_url(self, next="/"): self._CAS_change_password() return next def get_user(self): user = current.session.token if user: d = {'source': 'web2py cas'} for key in self.maps: d[key] = self.maps[key](user) return d return None def _CAS_login(self): """ exposed as CAS.login(request) returns a token on success, None on failed authentication """ self.ticket = current.request.vars.ticket if not current.request.vars.ticket: redirect("%s?service=%s" % (self.cas_login_url, self.cas_my_url)) else: url = "%s?service=%s&ticket=%s" % (self.cas_check_url, self.cas_my_url, self.ticket) data = to_native(urlopen(url).read()) if data.startswith('yes') or data.startswith('no'): data = data.split('\n') if data[0] == 'yes': if ':' in data[1]: # for Compatibility with Custom CAS items = data[1].split(':') a = items[0] b = len(items) > 1 and items[1] or a c = len(items) > 2 and items[2] or b else: a = b = c = data[1] return dict(user=a, email=b, username=c) return None try: dxml = dom.parseString(data) envelop = dxml.getElementsByTagName("cas:authenticationSuccess") if len(envelop) > 0: res = dict() for x in envelop[0].childNodes: if x.nodeName.startswith('cas:') and len(x.childNodes): key = to_native(x.nodeName[4:]) value = to_native(x.childNodes[0].nodeValue) if key not in res: res[key] = value else: if not isinstance(res[key], list): res[key] = [res[key]] res[key].append(value) return res except expat.ExpatError: pass return None # fallback def _CAS_logout(self): """ exposed CAS.logout() redirects to the CAS logout page """ redirect("%s?service=%s" % (self.cas_logout_url, self.cas_my_url)) def _CAS_change_password(self): redirect(self.cas_change_password_url)