diff --git a/auth/__init__.py b/auth/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..5b4536cfc0c300cd323cf043495a605c3c77d1d0
--- /dev/null
+++ b/auth/__init__.py
@@ -0,0 +1,10 @@
+
+from django.conf import settings
+
+TEMPLATE_BASE = settings.AUTH_TEMPLATE_BASE or "auth/templates/base.html"
+
+# enabled auth systems
+import auth_systems
+ENABLED_AUTH_SYSTEMS = settings.AUTH_ENABLED_AUTH_SYSTEMS or auth_systems.AUTH_SYSTEMS.keys()
+DEFAULT_AUTH_SYSTEM = settings.AUTH_DEFAULT_AUTH_SYSTEM or None
+
diff --git a/auth/auth_systems/.gitignore b/auth/auth_systems/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..85c334c60e41768560181d92cd78ba65f2982762
--- /dev/null
+++ b/auth/auth_systems/.gitignore
@@ -0,0 +1,2 @@
+twitterconfig.py
+facebookconfig.py
diff --git a/auth/auth_systems/__init__.py b/auth/auth_systems/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..8f13235e021eb205dbbafb5892c20322cb03bd79
--- /dev/null
+++ b/auth/auth_systems/__init__.py
@@ -0,0 +1,14 @@
+
+AUTH_SYSTEMS = {}
+
+import twitter, password, cas, facebook, google, yahoo
+AUTH_SYSTEMS['twitter'] = twitter
+AUTH_SYSTEMS['password'] = password
+AUTH_SYSTEMS['cas'] = cas
+AUTH_SYSTEMS['facebook'] = facebook
+AUTH_SYSTEMS['google'] = google
+AUTH_SYSTEMS['yahoo'] = yahoo
+
+# not ready
+#import live
+#AUTH_SYSTEMS['live'] = live
diff --git a/auth/auth_systems/cas.py b/auth/auth_systems/cas.py
new file mode 100644
index 0000000000000000000000000000000000000000..24935e99dcdd4f05819d0971283e9f8905132fb3
--- /dev/null
+++ b/auth/auth_systems/cas.py
@@ -0,0 +1,154 @@
+"""
+CAS (Princeton) Authentication
+
+Some code borrowed from
+https://sp.princeton.edu/oit/sdp/CAS/Wiki%20Pages/Python.aspx
+"""
+
+from django.http import *
+from django.core.mail import send_mail
+from django.conf import settings
+
+import sys, os, cgi, urllib, urllib2, re
+from xml.etree import ElementTree
+
+CAS_EMAIL_DOMAIN = "princeton.edu"
+CAS_URL= 'https://fed.princeton.edu/cas/'
+CAS_LOGOUT_URL = 'https://fed.princeton.edu/cas/logout?service=%s'
+
+# eligibility checking
+if hasattr(settings, 'CAS_USERNAME'):
+  CAS_USERNAME = settings.CAS_USERNAME
+  CAS_PASSWORD = settings.CAS_PASSWORD
+  CAS_ELIGIBILITY_URL = settings.CAS_ELIGIBILITY_URL
+  CAS_ELIGIBILITY_REALM = settings.CAS_ELIGIBILITY_REALM
+
+# display tweaks
+LOGIN_MESSAGE = "Log in with my NetID"
+
+def _get_service_url():
+  # FIXME current URL
+  from auth.views import after
+  from django.conf import settings
+  from django.core.urlresolvers import reverse
+  
+  return settings.URL_HOST + reverse(after)
+  
+def get_auth_url(request):
+  return CAS_URL + 'login?service=' + urllib.quote(_get_service_url())
+
+def get_user_category(user_id):
+  theurl = CAS_ELIGIBILITY_URL % user_id
+
+  auth_handler = urllib2.HTTPBasicAuthHandler()
+  auth_handler.add_password(realm=CAS_ELIGIBILITY_REALM, uri= theurl, user= CAS_USERNAME, passwd = CAS_PASSWORD)
+  opener = urllib2.build_opener(auth_handler)
+  urllib2.install_opener(opener)
+  
+  result = urllib2.urlopen(CAS_ELIGIBILITY_URL % user_id).read().strip()
+  parsed_result = ElementTree.fromstring(result)
+  return parsed_result.text
+  
+  
+def get_user_info(user_id):
+  url = 'http://dsml.princeton.edu/'
+  headers = {'SOAPAction': "#searchRequest", 'Content-Type': 'text/xml'}
+  
+  request_body = """<?xml version='1.0' encoding='UTF-8'?> 
+  <soap-env:Envelope 
+     xmlns:xsd='http://www.w3.org/2001/XMLSchema'
+     xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
+     xmlns:soap-env='http://schemas.xmlsoap.org/soap/envelope/'> 
+     <soap-env:Body> 
+        <batchRequest xmlns='urn:oasis:names:tc:DSML:2:0:core'
+  requestID='searching'>
+        <searchRequest 
+           dn='o=Princeton University, c=US'
+           scope='wholeSubtree'
+           derefAliases='neverDerefAliases'
+           sizeLimit='200'> 
+              <filter>
+                   <equalityMatch name='uid'>
+                            <value>%s</value>
+                    </equalityMatch>
+               </filter>
+               <attributes>
+                       <attribute name="displayName"/>
+                       <attribute name="pustatus"/>
+               </attributes>
+        </searchRequest>
+       </batchRequest>
+     </soap-env:Body> 
+  </soap-env:Envelope>
+""" % user_id
+
+  req = urllib2.Request(url, request_body, headers)
+  response = urllib2.urlopen(req).read()
+  
+  # parse the result
+  from xml.dom.minidom import parseString
+  
+  response_doc = parseString(response)
+  
+  # get the value elements (a bit of a hack but no big deal)
+  values = response_doc.getElementsByTagName('value')
+  
+  return {'name' : values[0].firstChild.wholeText, 'category' : values[1].firstChild.wholeText}
+  
+def get_user_info_after_auth(request):
+  ticket = request.GET.get('ticket', None)
+  
+  # if no ticket, this is a logout
+  if not ticket:
+    return None
+
+  # fetch the information from the CAS server
+  val_url = CAS_URL + "validate" + \
+     '?service=' + urllib.quote(_get_service_url()) + \
+     '&ticket=' + urllib.quote(ticket)
+  r = urllib.urlopen(val_url).readlines()   # returns 2 lines
+
+  # success
+  if len(r) == 2 and re.match("yes", r[0]) != None:
+    netid = r[1].strip()
+    
+    category = get_user_category(netid)
+    info = {'name': netid, 'category': category}
+      
+    return {'type': 'cas', 'user_id': netid, 'name': netid, 'info': info, 'token': None}
+  else:
+    return None
+    
+def do_logout(user):
+  """
+  Perform logout of CAS by redirecting to the CAS logout URL
+  """
+  return HttpResponseRedirect(CAS_LOGOUT_URL % _get_service_url())
+  
+def update_status(token, message):
+  """
+  simple update
+  """
+  pass
+
+def send_message(user_id, name, user_info, subject, body):
+  """
+  send email, for now just to Princeton
+  """
+  # if the user_id contains an @ sign already
+  if "@" in user_id:
+    email = user_id
+  else:
+    email = "%s@%s" % (user_id, CAS_EMAIL_DOMAIN)
+    
+  if user_info.has_key('name'):
+    name = user_info["name"]
+  else:
+    name = email
+    
+  send_mail(subject, body, settings.SERVER_EMAIL, ["%s <%s>" % (name, email)], fail_silently=False)
+  
+def check_constraint(constraint, user_info):
+  if not user_info.has_key('category'):
+    return False
+  return constraint['year'] == user_info['category']
diff --git a/auth/auth_systems/facebook.py b/auth/auth_systems/facebook.py
new file mode 100644
index 0000000000000000000000000000000000000000..4dc1e7406331c224f6b422bd06bb32b9325961c0
--- /dev/null
+++ b/auth/auth_systems/facebook.py
@@ -0,0 +1,69 @@
+"""
+Facebook Authentication
+"""
+
+import logging
+
+from django.conf import settings
+from django.core.mail import send_mail
+
+APP_ID = settings.FACEBOOK_APP_ID
+API_KEY = settings.FACEBOOK_API_KEY
+API_SECRET = settings.FACEBOOK_API_SECRET
+  
+#from facebookclient import Facebook
+import urllib, urllib2, cgi
+
+# some parameters to indicate that status updating is possible
+STATUS_UPDATES = True
+STATUS_UPDATE_WORDING_TEMPLATE = "Send %s to your facebook status"
+
+from auth import utils
+
+def facebook_url(url, params):
+  if params:
+    return "https://graph.facebook.com%s?%s" % (url, urllib.urlencode(params))
+  else:
+    return "https://graph.facebook.com%s" % url
+
+def facebook_get(url, params):
+  full_url = facebook_url(url,params)
+  return urllib2.urlopen(full_url).read()
+
+def facebook_post(url, params):
+  full_url = facebook_url(url, None)
+  return urllib2.urlopen(full_url, urllib.urlencode(params)).read()
+
+def get_auth_url(request, redirect_url):
+  request.session['fb_redirect_uri'] = redirect_url
+  return facebook_url('/oauth/authorize', {
+      'client_id': APP_ID,
+      'redirect_uri': redirect_url,
+      'scope': 'publish_stream,email'})
+    
+def get_user_info_after_auth(request):
+  args = facebook_get('/oauth/access_token', {
+      'client_id' : APP_ID,
+      'redirect_uri' : request.session['fb_redirect_uri'],
+      'client_secret' : API_SECRET,
+      'code' : request.GET['code']
+      })
+
+  access_token = cgi.parse_qs(args)['access_token'][0]
+
+  info = utils.from_json(facebook_get('/me', {'access_token':access_token}))
+
+  return {'type': 'facebook', 'user_id' : info['id'], 'name': info['name'], 'email': info['email'], 'info': info, 'token': {'access_token': access_token}}
+    
+def update_status(user_id, user_info, token, message):
+  """
+  post a message to the auth system's update stream, e.g. twitter stream
+  """
+  result = facebook_post('/me/feed', {
+      'access_token': token['access_token'],
+      'message': message
+      })
+
+def send_message(user_id, user_name, user_info, subject, body):
+  if user_info.has_key('email'):
+    send_mail(subject, body, settings.SERVER_EMAIL, ["%s <%s>" % (user_name, user_info['email'])], fail_silently=False)    
diff --git a/auth/auth_systems/facebookclient/__init__.py b/auth/auth_systems/facebookclient/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..7ee9e0c37c4bf1aef85d0e1b11bb6c1dec3dfb09
--- /dev/null
+++ b/auth/auth_systems/facebookclient/__init__.py
@@ -0,0 +1,1431 @@
+#! /usr/bin/env python
+#
+# pyfacebook - Python bindings for the Facebook API
+#
+# Copyright (c) 2008, Samuel Cormier-Iijima
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in the
+#       documentation and/or other materials provided with the distribution.
+#     * Neither the name of the author nor the names of its contributors may
+#       be used to endorse or promote products derived from this software
+#       without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS``AS IS'' AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""
+Python bindings for the Facebook API (pyfacebook - http://code.google.com/p/pyfacebook)
+
+PyFacebook is a client library that wraps the Facebook API.
+
+For more information, see
+
+Home Page: http://code.google.com/p/pyfacebook
+Developer Wiki: http://wiki.developers.facebook.com/index.php/Python
+Facebook IRC Channel: #facebook on irc.freenode.net
+
+PyFacebook can use simplejson if it is installed, which
+is much faster than XML and also uses less bandwith. Go to
+http://undefined.org/python/#simplejson to download it, or do
+apt-get install python-simplejson on a Debian-like system.
+"""
+
+import sys
+import time
+import struct
+import urllib
+import urllib2
+import httplib
+try:
+    import hashlib
+except ImportError:
+    import md5 as hashlib
+import binascii
+import urlparse
+import mimetypes
+
+# try to use simplejson first, otherwise fallback to XML
+RESPONSE_FORMAT = 'JSON'
+try:
+    import json as simplejson
+except ImportError:
+    try:
+        import simplejson
+    except ImportError:
+        try:
+            from django.utils import simplejson
+        except ImportError:
+            try:
+                import jsonlib as simplejson
+                simplejson.loads
+            except (ImportError, AttributeError):
+                from xml.dom import minidom
+                RESPONSE_FORMAT = 'XML'
+
+# support Google App Engine.  GAE does not have a working urllib.urlopen.
+try:
+    from google.appengine.api import urlfetch
+
+    def urlread(url, data=None, headers=None):
+        if data is not None:
+            if headers is None:
+                headers = {"Content-type": "application/x-www-form-urlencoded"}
+            method = urlfetch.POST
+        else:
+            if headers is None:
+                headers = {}
+            method = urlfetch.GET
+
+        result = urlfetch.fetch(url, method=method,
+                                payload=data, headers=headers)
+
+        if result.status_code == 200:
+            return result.content
+        else:
+            raise urllib2.URLError("fetch error url=%s, code=%d" % (url, result.status_code))
+
+except ImportError:
+    def urlread(url, data=None):
+        res = urllib2.urlopen(url, data=data)
+        return res.read()
+
+__all__ = ['Facebook']
+
+VERSION = '0.1'
+
+FACEBOOK_URL = 'http://api.facebook.com/restserver.php'
+FACEBOOK_SECURE_URL = 'https://api.facebook.com/restserver.php'
+
+class json(object): pass
+
+# simple IDL for the Facebook API
+METHODS = {
+    'application': {
+        'getPublicInfo': [
+            ('application_id', int, ['optional']),
+            ('application_api_key', str, ['optional']),
+            ('application_canvas_name', str,['optional']),
+        ],
+    },
+
+    # admin methods
+    'admin': {
+        'getAllocation': [
+            ('integration_point_name', str, []),
+        ],
+    },
+
+    # auth methods
+    'auth': {
+        'revokeAuthorization': [
+            ('uid', int, ['optional']),
+        ],
+    },
+
+    # feed methods
+    'feed': {
+        'publishStoryToUser': [
+            ('title', str, []),
+            ('body', str, ['optional']),
+            ('image_1', str, ['optional']),
+            ('image_1_link', str, ['optional']),
+            ('image_2', str, ['optional']),
+            ('image_2_link', str, ['optional']),
+            ('image_3', str, ['optional']),
+            ('image_3_link', str, ['optional']),
+            ('image_4', str, ['optional']),
+            ('image_4_link', str, ['optional']),
+            ('priority', int, ['optional']),
+        ],
+
+        'publishActionOfUser': [
+            ('title', str, []),
+            ('body', str, ['optional']),
+            ('image_1', str, ['optional']),
+            ('image_1_link', str, ['optional']),
+            ('image_2', str, ['optional']),
+            ('image_2_link', str, ['optional']),
+            ('image_3', str, ['optional']),
+            ('image_3_link', str, ['optional']),
+            ('image_4', str, ['optional']),
+            ('image_4_link', str, ['optional']),
+            ('priority', int, ['optional']),
+        ],
+
+        'publishTemplatizedAction': [
+            ('title_template', str, []),
+            ('page_actor_id', int, ['optional']),
+            ('title_data', json, ['optional']),
+            ('body_template', str, ['optional']),
+            ('body_data', json, ['optional']),
+            ('body_general', str, ['optional']),
+            ('image_1', str, ['optional']),
+            ('image_1_link', str, ['optional']),
+            ('image_2', str, ['optional']),
+            ('image_2_link', str, ['optional']),
+            ('image_3', str, ['optional']),
+            ('image_3_link', str, ['optional']),
+            ('image_4', str, ['optional']),
+            ('image_4_link', str, ['optional']),
+            ('target_ids', list, ['optional']),
+        ],
+
+        'registerTemplateBundle': [
+            ('one_line_story_templates', json, []),
+            ('short_story_templates', json, ['optional']),
+            ('full_story_template', json, ['optional']),
+            ('action_links', json, ['optional']),
+        ],
+
+        'deactivateTemplateBundleByID': [
+            ('template_bundle_id', int, []),
+        ],
+
+        'getRegisteredTemplateBundles': [],
+
+        'getRegisteredTemplateBundleByID': [
+            ('template_bundle_id', str, []),
+        ],
+
+        'publishUserAction': [
+            ('template_bundle_id', int, []),
+            ('template_data', json, ['optional']),
+            ('target_ids', list, ['optional']),
+            ('body_general', str, ['optional']),
+            ('story_size', int, ['optional']),
+        ],
+    },
+
+    # fql methods
+    'fql': {
+        'query': [
+            ('query', str, []),
+        ],
+    },
+
+    # friends methods
+    'friends': {
+        'areFriends': [
+            ('uids1', list, []),
+            ('uids2', list, []),
+        ],
+
+        'get': [
+            ('flid', int, ['optional']),
+        ],
+
+        'getLists': [],
+
+        'getAppUsers': [],
+    },
+
+    # notifications methods
+    'notifications': {
+        'get': [],
+
+        'send': [
+            ('to_ids', list, []),
+            ('notification', str, []),
+            ('email', str, ['optional']),
+            ('type', str, ['optional']),
+        ],
+
+        'sendRequest': [
+            ('to_ids', list, []),
+            ('type', str, []),
+            ('content', str, []),
+            ('image', str, []),
+            ('invite', bool, []),
+        ],
+
+        'sendEmail': [
+            ('recipients', list, []),
+            ('subject', str, []),
+            ('text', str, ['optional']),
+            ('fbml', str, ['optional']),
+        ]
+    },
+
+    # profile methods
+    'profile': {
+        'setFBML': [
+            ('markup', str, ['optional']),
+            ('uid', int, ['optional']),
+            ('profile', str, ['optional']),
+            ('profile_action', str, ['optional']),
+            ('mobile_fbml', str, ['optional']),
+            ('profile_main', str, ['optional']),
+        ],
+
+        'getFBML': [
+            ('uid', int, ['optional']),
+            ('type', int, ['optional']),
+        ],
+
+        'setInfo': [
+            ('title', str, []),
+            ('type', int, []),
+            ('info_fields', json, []),
+            ('uid', int, []),
+        ],
+
+        'getInfo': [
+            ('uid', int, []),
+        ],
+
+        'setInfoOptions': [
+            ('field', str, []),
+            ('options', json, []),
+        ],
+
+        'getInfoOptions': [
+            ('field', str, []),
+        ],
+    },
+
+    # users methods
+    'users': {
+        'getInfo': [
+            ('uids', list, []),
+            ('fields', list, [('default', ['name'])]),
+        ],
+
+        'getStandardInfo': [
+            ('uids', list, []),
+            ('fields', list, [('default', ['uid'])]),
+        ],
+
+        'getLoggedInUser': [],
+
+        'isAppAdded': [],
+
+        'hasAppPermission': [
+            ('ext_perm', str, []),
+            ('uid', int, ['optional']),
+        ],
+
+        'setStatus': [
+            ('status', str, []),
+            ('clear', bool, []),
+            ('status_includes_verb', bool, ['optional']),
+            ('uid', int, ['optional']),
+        ],
+    },
+
+    # events methods
+    'events': {
+        'get': [
+            ('uid', int, ['optional']),
+            ('eids', list, ['optional']),
+            ('start_time', int, ['optional']),
+            ('end_time', int, ['optional']),
+            ('rsvp_status', str, ['optional']),
+        ],
+
+        'getMembers': [
+            ('eid', int, []),
+        ],
+
+        'create': [
+            ('event_info', json, []),
+        ],
+    },
+
+    # update methods
+    'update': {
+        'decodeIDs': [
+            ('ids', list, []),
+        ],
+    },
+
+    # groups methods
+    'groups': {
+        'get': [
+            ('uid', int, ['optional']),
+            ('gids', list, ['optional']),
+        ],
+
+        'getMembers': [
+            ('gid', int, []),
+        ],
+    },
+
+    # marketplace methods
+    'marketplace': {
+        'createListing': [
+            ('listing_id', int, []),
+            ('show_on_profile', bool, []),
+            ('listing_attrs', str, []),
+        ],
+
+        'getCategories': [],
+
+        'getListings': [
+            ('listing_ids', list, []),
+            ('uids', list, []),
+        ],
+
+        'getSubCategories': [
+            ('category', str, []),
+        ],
+
+        'removeListing': [
+            ('listing_id', int, []),
+            ('status', str, []),
+        ],
+
+        'search': [
+            ('category', str, ['optional']),
+            ('subcategory', str, ['optional']),
+            ('query', str, ['optional']),
+        ],
+    },
+
+    # pages methods
+    'pages': {
+        'getInfo': [
+            ('fields', list, [('default', ['page_id', 'name'])]),
+            ('page_ids', list, ['optional']),
+            ('uid', int, ['optional']),
+        ],
+
+        'isAdmin': [
+            ('page_id', int, []),
+        ],
+
+        'isAppAdded': [
+            ('page_id', int, []),
+        ],
+
+        'isFan': [
+            ('page_id', int, []),
+            ('uid', int, []),
+        ],
+    },
+
+    # photos methods
+    'photos': {
+        'addTag': [
+            ('pid', int, []),
+            ('tag_uid', int, [('default', 0)]),
+            ('tag_text', str, [('default', '')]),
+            ('x', float, [('default', 50)]),
+            ('y', float, [('default', 50)]),
+            ('tags', str, ['optional']),
+        ],
+
+        'createAlbum': [
+            ('name', str, []),
+            ('location', str, ['optional']),
+            ('description', str, ['optional']),
+        ],
+
+        'get': [
+            ('subj_id', int, ['optional']),
+            ('aid', int, ['optional']),
+            ('pids', list, ['optional']),
+        ],
+
+        'getAlbums': [
+            ('uid', int, ['optional']),
+            ('aids', list, ['optional']),
+        ],
+
+        'getTags': [
+            ('pids', list, []),
+        ],
+    },
+
+    # status methods
+    'status': {
+        'get': [
+            ('uid', int, ['optional']),
+            ('limit', int, ['optional']),
+        ],
+        'set': [
+            ('status', str, ['optional']),
+            ('uid', int, ['optional']),
+        ],
+    },
+
+    # fbml methods
+    'fbml': {
+        'refreshImgSrc': [
+            ('url', str, []),
+        ],
+
+        'refreshRefUrl': [
+            ('url', str, []),
+        ],
+
+        'setRefHandle': [
+            ('handle', str, []),
+            ('fbml', str, []),
+        ],
+    },
+
+    # SMS Methods
+    'sms' : {
+        'canSend' : [
+            ('uid', int, []),
+        ],
+
+        'send' : [
+            ('uid', int, []),
+            ('message', str, []),
+            ('session_id', int, []),
+            ('req_session', bool, []),
+        ],
+    },
+
+    'data': {
+        'getCookies': [
+            ('uid', int, []),
+            ('string', str, ['optional']),
+        ],
+
+        'setCookie': [
+            ('uid', int, []),
+            ('name', str, []),
+            ('value', str, []),
+            ('expires', int, ['optional']),
+            ('path', str, ['optional']),
+        ],
+    },
+
+    # connect methods
+    'connect': {
+        'registerUsers': [
+            ('accounts', json, []),
+        ],
+
+        'unregisterUsers': [
+            ('email_hashes', json, []),
+        ],
+
+        'getUnconnectedFriendsCount': [
+        ],
+    },
+
+    #stream methods (beta)
+    'stream' : {
+        'addComment' : [
+            ('post_id', int, []),
+            ('comment', str, []),
+            ('uid', int, ['optional']),
+        ],
+
+        'addLike': [
+            ('uid', int, ['optional']),
+            ('post_id', int, ['optional']),
+        ],
+
+        'get' : [
+            ('viewer_id', int, ['optional']),
+            ('source_ids', list, ['optional']),
+            ('start_time', int, ['optional']),
+            ('end_time', int, ['optional']),
+            ('limit', int, ['optional']),
+            ('filter_key', str, ['optional']),
+        ],
+
+        'getComments' : [
+            ('post_id', int, []),
+        ],
+
+        'getFilters' : [
+            ('uid', int, ['optional']),
+        ],
+
+        'publish' : [
+            ('message', str, ['optional']),
+            ('attachment', json, ['optional']),
+            ('action_links', json, ['optional']),
+            ('target_id', str, ['optional']),
+            ('uid', str, ['optional']),
+        ],
+
+        'remove' : [
+            ('post_id', int, []),
+            ('uid', int, ['optional']),
+        ],
+
+        'removeComment' : [
+            ('comment_id', int, []),
+            ('uid', int, ['optional']),
+        ],
+
+        'removeLike' : [
+            ('uid', int, ['optional']),
+            ('post_id', int, ['optional']),
+        ],
+    }
+}
+
+class Proxy(object):
+    """Represents a "namespace" of Facebook API calls."""
+
+    def __init__(self, client, name):
+        self._client = client
+        self._name = name
+
+    def __call__(self, method=None, args=None, add_session_args=True):
+        # for Django templates
+        if method is None:
+            return self
+
+        if add_session_args:
+            self._client._add_session_args(args)
+
+        return self._client('%s.%s' % (self._name, method), args)
+
+
+# generate the Facebook proxies
+def __generate_proxies():
+    for namespace in METHODS:
+        methods = {}
+
+        for method in METHODS[namespace]:
+            params = ['self']
+            body = ['args = {}']
+
+            for param_name, param_type, param_options in METHODS[namespace][method]:
+                param = param_name
+
+                for option in param_options:
+                    if isinstance(option, tuple) and option[0] == 'default':
+                        if param_type == list:
+                            param = '%s=None' % param_name
+                            body.append('if %s is None: %s = %s' % (param_name, param_name, repr(option[1])))
+                        else:
+                            param = '%s=%s' % (param_name, repr(option[1]))
+
+                if param_type == json:
+                    # we only jsonify the argument if it's a list or a dict, for compatibility
+                    body.append('if isinstance(%s, list) or isinstance(%s, dict): %s = simplejson.dumps(%s)' % ((param_name,) * 4))
+
+                if 'optional' in param_options:
+                    param = '%s=None' % param_name
+                    body.append('if %s is not None: args[\'%s\'] = %s' % (param_name, param_name, param_name))
+                else:
+                    body.append('args[\'%s\'] = %s' % (param_name, param_name))
+
+                params.append(param)
+
+            # simple docstring to refer them to Facebook API docs
+            body.insert(0, '"""Facebook API call. See http://developers.facebook.com/documentation.php?v=1.0&method=%s.%s"""' % (namespace, method))
+
+            body.insert(0, 'def %s(%s):' % (method, ', '.join(params)))
+
+            body.append('return self(\'%s\', args)' % method)
+
+            exec('\n    '.join(body))
+
+            methods[method] = eval(method)
+
+        proxy = type('%sProxy' % namespace.title(), (Proxy, ), methods)
+
+        globals()[proxy.__name__] = proxy
+
+
+__generate_proxies()
+
+
+class FacebookError(Exception):
+    """Exception class for errors received from Facebook."""
+
+    def __init__(self, code, msg, args=None):
+        self.code = code
+        self.msg = msg
+        self.args = args
+
+    def __str__(self):
+        return 'Error %s: %s' % (self.code, self.msg)
+
+
+class AuthProxy(AuthProxy):
+    """Special proxy for facebook.auth."""
+
+    def getSession(self):
+        """Facebook API call. See http://developers.facebook.com/documentation.php?v=1.0&method=auth.getSession"""
+        args = {}
+        try:
+            args['auth_token'] = self._client.auth_token
+        except AttributeError:
+            raise RuntimeError('Client does not have auth_token set.')
+        result = self._client('%s.getSession' % self._name, args)
+        self._client.session_key = result['session_key']
+        self._client.uid = result['uid']
+        self._client.secret = result.get('secret')
+        self._client.session_key_expires = result['expires']
+        return result
+
+    def createToken(self):
+        """Facebook API call. See http://developers.facebook.com/documentation.php?v=1.0&method=auth.createToken"""
+        token = self._client('%s.createToken' % self._name)
+        self._client.auth_token = token
+        return token
+
+
+class FriendsProxy(FriendsProxy):
+    """Special proxy for facebook.friends."""
+
+    def get(self, **kwargs):
+        """Facebook API call. See http://developers.facebook.com/documentation.php?v=1.0&method=friends.get"""
+        if not kwargs.get('flid') and self._client._friends:
+            return self._client._friends
+        return super(FriendsProxy, self).get(**kwargs)
+
+
+class PhotosProxy(PhotosProxy):
+    """Special proxy for facebook.photos."""
+
+    def upload(self, image, aid=None, caption=None, size=(604, 1024), filename=None, callback=None):
+        """Facebook API call. See http://developers.facebook.com/documentation.php?v=1.0&method=photos.upload
+
+        size -- an optional size (width, height) to resize the image to before uploading. Resizes by default
+                to Facebook's maximum display width of 604.
+        """
+        args = {}
+
+        if aid is not None:
+            args['aid'] = aid
+
+        if caption is not None:
+            args['caption'] = caption
+
+        args = self._client._build_post_args('facebook.photos.upload', self._client._add_session_args(args))
+
+        try:
+            import cStringIO as StringIO
+        except ImportError:
+            import StringIO
+
+        # check for a filename specified...if the user is passing binary data in
+        # image then a filename will be specified
+        if filename is None:
+            try:
+                import Image
+            except ImportError:
+                data = StringIO.StringIO(open(image, 'rb').read())
+            else:
+                img = Image.open(image)
+                if size:
+                    img.thumbnail(size, Image.ANTIALIAS)
+                data = StringIO.StringIO()
+                img.save(data, img.format)
+        else:
+            # there was a filename specified, which indicates that image was not
+            # the path to an image file but rather the binary data of a file
+            data = StringIO.StringIO(image)
+            image = filename
+
+        content_type, body = self.__encode_multipart_formdata(list(args.iteritems()), [(image, data)])
+        urlinfo = urlparse.urlsplit(self._client.facebook_url)
+        try:
+            content_length = len(body)
+            chunk_size = 4096
+
+            h = httplib.HTTPConnection(urlinfo[1])
+            h.putrequest('POST', urlinfo[2])
+            h.putheader('Content-Type', content_type)
+            h.putheader('Content-Length', str(content_length))
+            h.putheader('MIME-Version', '1.0')
+            h.putheader('User-Agent', 'PyFacebook Client Library')
+            h.endheaders()
+
+            if callback:
+                count = 0
+                while len(body) > 0:
+                    if len(body) < chunk_size:
+                        data = body
+                        body = ''
+                    else:
+                        data = body[0:chunk_size]
+                        body = body[chunk_size:]
+
+                    h.send(data)
+                    count += 1
+                    callback(count, chunk_size, content_length)
+            else:
+                h.send(body)
+
+            response = h.getresponse()
+
+            if response.status != 200:
+                raise Exception('Error uploading photo: Facebook returned HTTP %s (%s)' % (response.status, response.reason))
+            response = response.read()
+        except:
+            # sending the photo failed, perhaps we are using GAE
+            try:
+                from google.appengine.api import urlfetch
+
+                try:
+                    response = urlread(url=self._client.facebook_url,data=body,headers={'POST':urlinfo[2],'Content-Type':content_type,'MIME-Version':'1.0'})
+                except urllib2.URLError:
+                    raise Exception('Error uploading photo: Facebook returned %s' % (response))
+            except ImportError:
+                # could not import from google.appengine.api, so we are not running in GAE
+                raise Exception('Error uploading photo.')
+
+        return self._client._parse_response(response, 'facebook.photos.upload')
+
+
+    def __encode_multipart_formdata(self, fields, files):
+        """Encodes a multipart/form-data message to upload an image."""
+        boundary = '-------tHISiStheMulTIFoRMbOUNDaRY'
+        crlf = '\r\n'
+        l = []
+
+        for (key, value) in fields:
+            l.append('--' + boundary)
+            l.append('Content-Disposition: form-data; name="%s"' % str(key))
+            l.append('')
+            l.append(str(value))
+        for (filename, value) in files:
+            l.append('--' + boundary)
+            l.append('Content-Disposition: form-data; filename="%s"' % (str(filename), ))
+            l.append('Content-Type: %s' % self.__get_content_type(filename))
+            l.append('')
+            l.append(value.getvalue())
+        l.append('--' + boundary + '--')
+        l.append('')
+        body = crlf.join(l)
+        content_type = 'multipart/form-data; boundary=%s' % boundary
+        return content_type, body
+
+
+    def __get_content_type(self, filename):
+        """Returns a guess at the MIME type of the file from the filename."""
+        return str(mimetypes.guess_type(filename)[0]) or 'application/octet-stream'
+
+
+class Facebook(object):
+    """
+    Provides access to the Facebook API.
+
+    Instance Variables:
+
+    added
+        True if the user has added this application.
+
+    api_key
+        Your API key, as set in the constructor.
+
+    app_name
+        Your application's name, i.e. the APP_NAME in http://apps.facebook.com/APP_NAME/ if
+        this is for an internal web application. Optional, but useful for automatic redirects
+        to canvas pages.
+
+    auth_token
+        The auth token that Facebook gives you, either with facebook.auth.createToken,
+        or through a GET parameter.
+
+    callback_path
+        The path of the callback set in the Facebook app settings. If your callback is set
+        to http://www.example.com/facebook/callback/, this should be '/facebook/callback/'.
+        Optional, but useful for automatic redirects back to the same page after login.
+
+    desktop
+        True if this is a desktop app, False otherwise. Used for determining how to
+        authenticate.
+
+    ext_perms
+        Any extended permissions that the user has granted to your application.
+        This parameter is set only if the user has granted any.
+
+    facebook_url
+        The url to use for Facebook requests.
+
+    facebook_secure_url
+        The url to use for secure Facebook requests.
+
+    in_canvas
+        True if the current request is for a canvas page.
+
+    in_iframe
+        True if the current request is for an HTML page to embed in Facebook inside an iframe.
+
+    is_session_from_cookie
+        True if the current request session comes from a session cookie.
+
+    in_profile_tab
+        True if the current request is for a user's tab for your application.
+
+    internal
+        True if this Facebook object is for an internal application (one that can be added on Facebook)
+
+    locale
+        The user's locale. Default: 'en_US'
+
+    page_id
+        Set to the page_id of the current page (if any)
+
+    profile_update_time
+        The time when this user's profile was last updated. This is a UNIX timestamp. Default: None if unknown.
+
+    secret
+        Secret that is used after getSession for desktop apps.
+
+    secret_key
+        Your application's secret key, as set in the constructor.
+
+    session_key
+        The current session key. Set automatically by auth.getSession, but can be set
+        manually for doing infinite sessions.
+
+    session_key_expires
+        The UNIX time of when this session key expires, or 0 if it never expires.
+
+    uid
+        After a session is created, you can get the user's UID with this variable. Set
+        automatically by auth.getSession.
+
+    ----------------------------------------------------------------------
+
+    """
+
+    def __init__(self, api_key, secret_key, auth_token=None, app_name=None, callback_path=None, internal=None, proxy=None, facebook_url=None, facebook_secure_url=None):
+        """
+        Initializes a new Facebook object which provides wrappers for the Facebook API.
+
+        If this is a desktop application, the next couple of steps you might want to take are:
+
+        facebook.auth.createToken() # create an auth token
+        facebook.login()            # show a browser window
+        wait_login()                # somehow wait for the user to log in
+        facebook.auth.getSession()  # get a session key
+
+        For web apps, if you are passed an auth_token from Facebook, pass that in as a named parameter.
+        Then call:
+
+        facebook.auth.getSession()
+
+        """
+        self.api_key = api_key
+        self.secret_key = secret_key
+        self.session_key = None
+        self.session_key_expires = None
+        self.auth_token = auth_token
+        self.secret = None
+        self.uid = None
+        self.page_id = None
+        self.in_canvas = False
+        self.in_iframe = False
+        self.is_session_from_cookie = False
+        self.in_profile_tab = False
+        self.added = False
+        self.app_name = app_name
+        self.callback_path = callback_path
+        self.internal = internal
+        self._friends = None
+        self.locale = 'en_US'
+        self.profile_update_time = None
+        self.ext_perms = None
+        self.proxy = proxy
+        if facebook_url is None:
+            self.facebook_url = FACEBOOK_URL
+        else:
+            self.facebook_url = facebook_url
+        if facebook_secure_url is None:
+            self.facebook_secure_url = FACEBOOK_SECURE_URL
+        else:
+            self.facebook_secure_url = facebook_secure_url
+
+        for namespace in METHODS:
+            self.__dict__[namespace] = eval('%sProxy(self, \'%s\')' % (namespace.title(), 'facebook.%s' % namespace))
+
+
+    def _hash_args(self, args, secret=None):
+        """Hashes arguments by joining key=value pairs, appending a secret, and then taking the MD5 hex digest."""
+        # @author: houyr
+        # fix for UnicodeEncodeError
+        hasher = hashlib.md5(''.join(['%s=%s' % (isinstance(x, unicode) and x.encode("utf-8") or x, isinstance(args[x], unicode) and args[x].encode("utf-8") or args[x]) for x in sorted(args.keys())]))
+        if secret:
+            hasher.update(secret)
+        elif self.secret:
+            hasher.update(self.secret)
+        else:
+            hasher.update(self.secret_key)
+        return hasher.hexdigest()
+
+
+    def _parse_response_item(self, node):
+        """Parses an XML response node from Facebook."""
+        if node.nodeType == node.DOCUMENT_NODE and \
+            node.childNodes[0].hasAttributes() and \
+            node.childNodes[0].hasAttribute('list') and \
+            node.childNodes[0].getAttribute('list') == "true":
+            return {node.childNodes[0].nodeName: self._parse_response_list(node.childNodes[0])}
+        elif node.nodeType == node.ELEMENT_NODE and \
+            node.hasAttributes() and \
+            node.hasAttribute('list') and \
+            node.getAttribute('list')=="true":
+            return self._parse_response_list(node)
+        elif len(filter(lambda x: x.nodeType == x.ELEMENT_NODE, node.childNodes)) > 0:
+            return self._parse_response_dict(node)
+        else:
+            return ''.join(node.data for node in node.childNodes if node.nodeType == node.TEXT_NODE)
+
+
+    def _parse_response_dict(self, node):
+        """Parses an XML dictionary response node from Facebook."""
+        result = {}
+        for item in filter(lambda x: x.nodeType == x.ELEMENT_NODE, node.childNodes):
+            result[item.nodeName] = self._parse_response_item(item)
+        if node.nodeType == node.ELEMENT_NODE and node.hasAttributes():
+            if node.hasAttribute('id'):
+                result['id'] = node.getAttribute('id')
+        return result
+
+
+    def _parse_response_list(self, node):
+        """Parses an XML list response node from Facebook."""
+        result = []
+        for item in filter(lambda x: x.nodeType == x.ELEMENT_NODE, node.childNodes):
+            result.append(self._parse_response_item(item))
+        return result
+
+
+    def _check_error(self, response):
+        """Checks if the given Facebook response is an error, and then raises the appropriate exception."""
+        if type(response) is dict and response.has_key('error_code'):
+            raise FacebookError(response['error_code'], response['error_msg'], response['request_args'])
+
+
+    def _build_post_args(self, method, args=None):
+        """Adds to args parameters that are necessary for every call to the API."""
+        if args is None:
+            args = {}
+
+        for arg in args.items():
+            if type(arg[1]) == list:
+                args[arg[0]] = ','.join(str(a) for a in arg[1])
+            elif type(arg[1]) == unicode:
+                args[arg[0]] = arg[1].encode("UTF-8")
+            elif type(arg[1]) == bool:
+                args[arg[0]] = str(arg[1]).lower()
+
+        args['method'] = method
+        args['api_key'] = self.api_key
+        args['v'] = '1.0'
+        args['format'] = RESPONSE_FORMAT
+        args['sig'] = self._hash_args(args)
+
+        return args
+
+
+    def _add_session_args(self, args=None):
+        """Adds 'session_key' and 'call_id' to args, which are used for API calls that need sessions."""
+        if args is None:
+            args = {}
+
+        if not self.session_key:
+            return args
+            #some calls don't need a session anymore. this might be better done in the markup
+            #raise RuntimeError('Session key not set. Make sure auth.getSession has been called.')
+
+        args['session_key'] = self.session_key
+        args['call_id'] = str(int(time.time() * 1000))
+
+        return args
+
+
+    def _parse_response(self, response, method, format=None):
+        """Parses the response according to the given (optional) format, which should be either 'JSON' or 'XML'."""
+        if not format:
+            format = RESPONSE_FORMAT
+
+        if format == 'JSON':
+            result = simplejson.loads(response)
+
+            self._check_error(result)
+        elif format == 'XML':
+            dom = minidom.parseString(response)
+            result = self._parse_response_item(dom)
+            dom.unlink()
+
+            if 'error_response' in result:
+                self._check_error(result['error_response'])
+
+            result = result[method[9:].replace('.', '_') + '_response']
+        else:
+            raise RuntimeError('Invalid format specified.')
+
+        return result
+
+
+    def hash_email(self, email):
+        """
+        Hash an email address in a format suitable for Facebook Connect.
+
+        """
+        email = email.lower().strip()
+        return "%s_%s" % (
+            struct.unpack("I", struct.pack("i", binascii.crc32(email)))[0],
+            hashlib.md5(email).hexdigest(),
+        )
+
+
+    def unicode_urlencode(self, params):
+        """
+        @author: houyr
+        A unicode aware version of urllib.urlencode.
+        """
+        if isinstance(params, dict):
+            params = params.items()
+        return urllib.urlencode([(k, isinstance(v, unicode) and v.encode('utf-8') or v)
+                          for k, v in params])
+
+
+    def __call__(self, method=None, args=None, secure=False):
+        """Make a call to Facebook's REST server."""
+        # for Django templates, if this object is called without any arguments
+        # return the object itself
+        if method is None:
+            return self
+
+        # __init__ hard-codes into en_US
+        if args is not None and not args.has_key('locale'):
+            args['locale'] = self.locale
+
+        # @author: houyr
+        # fix for bug of UnicodeEncodeError
+        post_data = self.unicode_urlencode(self._build_post_args(method, args))
+
+        if self.proxy:
+            proxy_handler = urllib2.ProxyHandler(self.proxy)
+            opener = urllib2.build_opener(proxy_handler)
+            if secure:
+                response = opener.open(self.facebook_secure_url, post_data).read()
+            else:
+                response = opener.open(self.facebook_url, post_data).read()
+        else:
+            if secure:
+                response = urlread(self.facebook_secure_url, post_data)
+            else:
+                response = urlread(self.facebook_url, post_data)
+
+        return self._parse_response(response, method)
+
+
+    # URL helpers
+    def get_url(self, page, **args):
+        """
+        Returns one of the Facebook URLs (www.facebook.com/SOMEPAGE.php).
+        Named arguments are passed as GET query string parameters.
+
+        """
+        return 'http://www.facebook.com/%s.php?%s' % (page, urllib.urlencode(args))
+
+
+    def get_app_url(self, path=''):
+        """
+        Returns the URL for this app's canvas page, according to app_name.
+
+        """
+        return 'http://apps.facebook.com/%s/%s' % (self.app_name, path)
+
+
+    def get_add_url(self, next=None):
+        """
+        Returns the URL that the user should be redirected to in order to add the application.
+
+        """
+        args = {'api_key': self.api_key, 'v': '1.0'}
+
+        if next is not None:
+            args['next'] = next
+
+        return self.get_url('install', **args)
+
+
+    def get_authorize_url(self, next=None, next_cancel=None):
+        """
+        Returns the URL that the user should be redirected to in order to
+        authorize certain actions for application.
+
+        """
+        args = {'api_key': self.api_key, 'v': '1.0'}
+
+        if next is not None:
+            args['next'] = next
+
+        if next_cancel is not None:
+            args['next_cancel'] = next_cancel
+
+        return self.get_url('authorize', **args)
+
+
+    def get_login_url(self, next=None, popup=False, canvas=True):
+        """
+        Returns the URL that the user should be redirected to in order to login.
+
+        next -- the URL that Facebook should redirect to after login
+
+        """
+        args = {'api_key': self.api_key, 'v': '1.0'}
+
+        if next is not None:
+            args['next'] = next
+
+        if canvas is True:
+            args['canvas'] = 1
+
+        if popup is True:
+            args['popup'] = 1
+
+        if self.auth_token is not None:
+            args['auth_token'] = self.auth_token
+
+        return self.get_url('login', **args)
+
+
+    def login(self, popup=False):
+        """Open a web browser telling the user to login to Facebook."""
+        import webbrowser
+        webbrowser.open(self.get_login_url(popup=popup))
+
+
+    def get_ext_perm_url(self, ext_perm, next=None, popup=False):
+        """
+        Returns the URL that the user should be redirected to in order to grant an extended permission.
+
+        ext_perm -- the name of the extended permission to request
+        next     -- the URL that Facebook should redirect to after login
+
+        """
+        args = {'ext_perm': ext_perm, 'api_key': self.api_key, 'v': '1.0'}
+
+        if next is not None:
+            args['next'] = next
+
+        if popup is True:
+            args['popup'] = 1
+
+        return self.get_url('authorize', **args)
+
+
+    def request_extended_permission(self, ext_perm, popup=False):
+        """Open a web browser telling the user to grant an extended permission."""
+        import webbrowser
+        webbrowser.open(self.get_ext_perm_url(ext_perm, popup=popup))
+
+
+    def check_session(self, request):
+        """
+        Checks the given Django HttpRequest for Facebook parameters such as
+        POST variables or an auth token. If the session is valid, returns True
+        and this object can now be used to access the Facebook API. Otherwise,
+        it returns False, and the application should take the appropriate action
+        (either log the user in or have him add the application).
+
+        """
+        self.in_canvas = (request.POST.get('fb_sig_in_canvas') == '1')
+
+        if self.session_key and (self.uid or self.page_id):
+            return True
+
+
+        if request.method == 'POST':
+            params = self.validate_signature(request.POST)
+        else:
+            if 'installed' in request.GET:
+                self.added = True
+
+            if 'fb_page_id' in request.GET:
+                self.page_id = request.GET['fb_page_id']
+
+            if 'auth_token' in request.GET:
+                self.auth_token = request.GET['auth_token']
+
+                try:
+                    self.auth.getSession()
+                except FacebookError, e:
+                    self.auth_token = None
+                    return False
+
+                return True
+
+            params = self.validate_signature(request.GET)
+
+        if not params:
+            # first check if we are in django - to check cookies
+            if hasattr(request, 'COOKIES'):
+                params = self.validate_cookie_signature(request.COOKIES)
+                self.is_session_from_cookie = True
+            else:
+                # if not, then we might be on GoogleAppEngine, check their request object cookies
+                if hasattr(request,'cookies'):
+                    params = self.validate_cookie_signature(request.cookies)
+                    self.is_session_from_cookie = True
+
+        if not params:
+            return False
+
+        if params.get('in_canvas') == '1':
+            self.in_canvas = True
+
+        if params.get('in_iframe') == '1':
+            self.in_iframe = True
+
+        if params.get('in_profile_tab') == '1':
+            self.in_profile_tab = True
+
+        if params.get('added') == '1':
+            self.added = True
+
+        if params.get('expires'):
+            self.session_key_expires = int(params['expires'])
+
+        if 'locale' in params:
+            self.locale = params['locale']
+
+        if 'profile_update_time' in params:
+            try:
+                self.profile_update_time = int(params['profile_update_time'])
+            except ValueError:
+                pass
+
+        if 'ext_perms' in params:
+            self.ext_perms = params['ext_perms']
+
+        if 'friends' in params:
+            if params['friends']:
+                self._friends = params['friends'].split(',')
+            else:
+                self._friends = []
+
+        if 'session_key' in params:
+            self.session_key = params['session_key']
+            if 'user' in params:
+                self.uid = params['user']
+            elif 'page_id' in params:
+                self.page_id = params['page_id']
+            else:
+                return False
+        elif 'profile_session_key' in params:
+            self.session_key = params['profile_session_key']
+            if 'profile_user' in params:
+                self.uid = params['profile_user']
+            else:
+                return False
+        elif 'canvas_user' in params:
+            self.uid = params['canvas_user']
+        elif 'uninstall' in params:
+            self.uid = params['user']
+        else:
+            return False
+
+        return True
+
+
+    def validate_signature(self, post, prefix='fb_sig', timeout=None):
+        """
+        Validate parameters passed to an internal Facebook app from Facebook.
+
+        """
+        args = post.copy()
+
+        if prefix not in args:
+            return None
+
+        del args[prefix]
+
+        if timeout and '%s_time' % prefix in post and time.time() - float(post['%s_time' % prefix]) > timeout:
+            return None
+
+        args = dict([(key[len(prefix + '_'):], value) for key, value in args.items() if key.startswith(prefix)])
+
+        hash = self._hash_args(args)
+
+        if hash == post[prefix]:
+            return args
+        else:
+            return None
+
+    def validate_cookie_signature(self, cookies):
+        """
+        Validate parameters passed by cookies, namely facebookconnect or js api.
+        """
+
+        api_key = self.api_key
+        if api_key not in cookies:
+            return None
+
+        prefix = api_key + "_"
+       
+        params = {} 
+        vals = ''
+        for k in sorted(cookies):
+            if k.startswith(prefix):
+                key = k.replace(prefix,"")
+                value = cookies[k]
+                params[key] = value
+                vals += '%s=%s' % (key, value)
+                
+        hasher = hashlib.md5(vals)
+
+        hasher.update(self.secret_key)
+        digest = hasher.hexdigest()
+        if digest == cookies[api_key]:
+            params['is_session_from_cookie'] = True
+            return params
+        else:
+            return False
+
+
+
+
+if __name__ == '__main__':
+    # sample desktop application
+
+    api_key = ''
+    secret_key = ''
+
+    facebook = Facebook(api_key, secret_key)
+
+    facebook.auth.createToken()
+
+    # Show login window
+    # Set popup=True if you want login without navigational elements
+    facebook.login()
+
+    # Login to the window, then press enter
+    print 'After logging in, press enter...'
+    raw_input()
+
+    facebook.auth.getSession()
+    print 'Session Key:   ', facebook.session_key
+    print 'Your UID:      ', facebook.uid
+
+    info = facebook.users.getInfo([facebook.uid], ['name', 'birthday', 'affiliations', 'sex'])[0]
+
+    print 'Your Name:     ', info['name']
+    print 'Your Birthday: ', info['birthday']
+    print 'Your Gender:   ', info['sex']
+
+    friends = facebook.friends.get()
+    friends = facebook.users.getInfo(friends[0:5], ['name', 'birthday', 'relationship_status'])
+
+    for friend in friends:
+        print friend['name'], 'has a birthday on', friend['birthday'], 'and is', friend['relationship_status']
+
+    arefriends = facebook.friends.areFriends([friends[0]['uid']], [friends[1]['uid']])
+
+    photos = facebook.photos.getAlbums(facebook.uid)
+
diff --git a/auth/auth_systems/facebookclient/djangofb/__init__.py b/auth/auth_systems/facebookclient/djangofb/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..68b1b27c37d19e1372369837006947b4f60ef7c5
--- /dev/null
+++ b/auth/auth_systems/facebookclient/djangofb/__init__.py
@@ -0,0 +1,248 @@
+import re
+import datetime
+import facebook
+
+from django.http import HttpResponse, HttpResponseRedirect
+from django.core.exceptions import ImproperlyConfigured
+from django.conf import settings
+from datetime import datetime
+
+try:
+    from threading import local
+except ImportError:
+    from django.utils._threading_local import local
+
+__all__ = ['Facebook', 'FacebookMiddleware', 'get_facebook_client', 'require_login', 'require_add']
+
+_thread_locals = local()
+
+class Facebook(facebook.Facebook):
+    def redirect(self, url):
+        """
+        Helper for Django which redirects to another page. If inside a
+        canvas page, writes a <fb:redirect> instead to achieve the same effect.
+
+        """
+        if self.in_canvas:
+            return HttpResponse('<fb:redirect url="%s" />' % (url, ))
+        elif re.search("^https?:\/\/([^\/]*\.)?facebook\.com(:\d+)?", url.lower()):
+            return HttpResponse('<script type="text/javascript">\ntop.location.href = "%s";\n</script>' % url)
+        else:
+            return HttpResponseRedirect(url)
+
+
+def get_facebook_client():
+    """
+    Get the current Facebook object for the calling thread.
+
+    """
+    try:
+        return _thread_locals.facebook
+    except AttributeError:
+        raise ImproperlyConfigured('Make sure you have the Facebook middleware installed.')
+
+
+def require_login(next=None, internal=None):
+    """
+    Decorator for Django views that requires the user to be logged in.
+    The FacebookMiddleware must be installed.
+
+    Standard usage:
+        @require_login()
+        def some_view(request):
+            ...
+
+    Redirecting after login:
+        To use the 'next' parameter to redirect to a specific page after login, a callable should
+        return a path relative to the Post-add URL. 'next' can also be an integer specifying how many
+        parts of request.path to strip to find the relative URL of the canvas page. If 'next' is None,
+        settings.callback_path and settings.app_name are checked to redirect to the same page after logging
+        in. (This is the default behavior.)
+        @require_login(next=some_callable)
+        def some_view(request):
+            ...
+    """
+    def decorator(view):
+        def newview(request, *args, **kwargs):
+            next = newview.next
+            internal = newview.internal
+
+            try:
+                fb = request.facebook
+            except:
+                raise ImproperlyConfigured('Make sure you have the Facebook middleware installed.')
+
+            if internal is None:
+                internal = request.facebook.internal
+
+            if callable(next):
+                next = next(request.path)
+            elif isinstance(next, int):
+                next = '/'.join(request.path.split('/')[next + 1:])
+            elif next is None and fb.callback_path and request.path.startswith(fb.callback_path):
+                next = request.path[len(fb.callback_path):]
+            elif not isinstance(next, str):
+                next = ''
+
+            if not fb.check_session(request):
+                #If user has never logged in before, the get_login_url will redirect to the TOS page
+                return fb.redirect(fb.get_login_url(next=next))
+
+            if internal and request.method == 'GET' and fb.app_name:
+                return fb.redirect('%s%s' % (fb.get_app_url(), next))
+
+            return view(request, *args, **kwargs)
+        newview.next = next
+        newview.internal = internal
+        return newview
+    return decorator
+
+
+def require_add(next=None, internal=None, on_install=None):
+    """
+    Decorator for Django views that requires application installation.
+    The FacebookMiddleware must be installed.
+    
+    Standard usage:
+        @require_add()
+        def some_view(request):
+            ...
+
+    Redirecting after installation:
+        To use the 'next' parameter to redirect to a specific page after login, a callable should
+        return a path relative to the Post-add URL. 'next' can also be an integer specifying how many
+        parts of request.path to strip to find the relative URL of the canvas page. If 'next' is None,
+        settings.callback_path and settings.app_name are checked to redirect to the same page after logging
+        in. (This is the default behavior.)
+        @require_add(next=some_callable)
+        def some_view(request):
+            ...
+
+    Post-install processing:
+        Set the on_install parameter to a callable in order to handle special post-install processing.
+        The callable should take a request object as the parameter.
+        @require_add(on_install=some_callable)
+        def some_view(request):
+            ...
+    """
+    def decorator(view):
+        def newview(request, *args, **kwargs):
+            next = newview.next
+            internal = newview.internal
+
+            try:
+                fb = request.facebook
+            except:
+                raise ImproperlyConfigured('Make sure you have the Facebook middleware installed.')
+
+            if internal is None:
+                internal = request.facebook.internal
+
+            if callable(next):
+                next = next(request.path)
+            elif isinstance(next, int):
+                next = '/'.join(request.path.split('/')[next + 1:])
+            elif next is None and fb.callback_path and request.path.startswith(fb.callback_path):
+                next = request.path[len(fb.callback_path):]
+            else:
+                next = ''
+
+            if not fb.check_session(request):
+                if fb.added:
+                    if request.method == 'GET' and fb.app_name:
+                        return fb.redirect('%s%s' % (fb.get_app_url(), next))
+                    return fb.redirect(fb.get_login_url(next=next))
+                else:
+                    return fb.redirect(fb.get_add_url(next=next))
+
+            if not fb.added:
+                return fb.redirect(fb.get_add_url(next=next))
+
+            if 'installed' in request.GET and callable(on_install):
+                on_install(request)
+
+            if internal and request.method == 'GET' and fb.app_name:
+                return fb.redirect('%s%s' % (fb.get_app_url(), next))
+
+            return view(request, *args, **kwargs)
+        newview.next = next
+        newview.internal = internal
+        return newview
+    return decorator
+
+# try to preserve the argspecs
+try:
+    import decorator
+except ImportError:
+    pass
+else:
+    def updater(f):
+        def updated(*args, **kwargs):
+            original = f(*args, **kwargs)
+            def newdecorator(view):
+                return decorator.new_wrapper(original(view), view)
+            return decorator.new_wrapper(newdecorator, original)
+        return decorator.new_wrapper(updated, f)
+    require_login = updater(require_login)
+    require_add = updater(require_add)
+
+class FacebookMiddleware(object):
+    """
+    Middleware that attaches a Facebook object to every incoming request.
+    The Facebook object created can also be accessed from models for the
+    current thread by using get_facebook_client().
+
+    """
+
+    def __init__(self, api_key=None, secret_key=None, app_name=None, callback_path=None, internal=None):
+        self.api_key = api_key or settings.FACEBOOK_API_KEY
+        self.secret_key = secret_key or settings.FACEBOOK_SECRET_KEY
+        self.app_name = app_name or getattr(settings, 'FACEBOOK_APP_NAME', None)
+        self.callback_path = callback_path or getattr(settings, 'FACEBOOK_CALLBACK_PATH', None)
+        self.internal = internal or getattr(settings, 'FACEBOOK_INTERNAL', True)
+        self.proxy = None
+        if getattr(settings, 'USE_HTTP_PROXY', False):
+            self.proxy = settings.HTTP_PROXY
+
+    def process_request(self, request):
+        _thread_locals.facebook = request.facebook = Facebook(self.api_key, self.secret_key, app_name=self.app_name, callback_path=self.callback_path, internal=self.internal, proxy=self.proxy)
+        if not self.internal:
+            if 'fb_sig_session_key' in request.GET and 'fb_sig_user' in request.GET:
+                request.facebook.session_key = request.session['facebook_session_key'] = request.GET['fb_sig_session_key']
+                request.facebook.uid = request.session['fb_sig_user'] = request.GET['fb_sig_user']
+            elif request.session.get('facebook_session_key', None) and request.session.get('facebook_user_id', None):
+                request.facebook.session_key = request.session['facebook_session_key']
+                request.facebook.uid = request.session['facebook_user_id']
+
+    def process_response(self, request, response):
+        if not self.internal and request.facebook.session_key and request.facebook.uid:
+            request.session['facebook_session_key'] = request.facebook.session_key
+            request.session['facebook_user_id'] = request.facebook.uid
+
+            if request.facebook.session_key_expires:
+                expiry = datetime.datetime.fromtimestamp(request.facebook.session_key_expires)
+                request.session.set_expiry(expiry)
+
+        try:
+            fb = request.facebook
+        except:
+            return response
+
+        if not fb.is_session_from_cookie:
+            # Make sure the browser accepts our session cookies inside an Iframe
+            response['P3P'] = 'CP="NOI DSP COR NID ADMa OPTa OUR NOR"'
+            fb_cookies = {
+                'expires': fb.session_key_expires,
+                'session_key': fb.session_key,
+                'user': fb.uid,
+            }
+
+            expire_time = None
+            if fb.session_key_expires:
+                expire_time = datetime.utcfromtimestamp(fb.session_key_expires)
+
+            for k in fb_cookies:
+                response.set_cookie(self.api_key + '_' + k, fb_cookies[k], expires=expire_time)
+            response.set_cookie(self.api_key , fb._hash_args(fb_cookies), expires=expire_time)
+
+        return response
diff --git a/auth/auth_systems/facebookclient/djangofb/context_processors.py b/auth/auth_systems/facebookclient/djangofb/context_processors.py
new file mode 100644
index 0000000000000000000000000000000000000000..6f954397308f7af525d9fa600fb29e8cf6902c33
--- /dev/null
+++ b/auth/auth_systems/facebookclient/djangofb/context_processors.py
@@ -0,0 +1,6 @@
+def messages(request):
+    """Returns messages similar to ``django.core.context_processors.auth``."""
+    if hasattr(request, 'facebook') and request.facebook.uid is not None:
+        from models import Message
+        messages = Message.objects.get_and_delete_all(uid=request.facebook.uid)
+    return {'messages': messages}
\ No newline at end of file
diff --git a/auth/auth_systems/facebookclient/djangofb/default_app/__init__.py b/auth/auth_systems/facebookclient/djangofb/default_app/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/auth/auth_systems/facebookclient/djangofb/default_app/models.py b/auth/auth_systems/facebookclient/djangofb/default_app/models.py
new file mode 100644
index 0000000000000000000000000000000000000000..666ccd3f39403df207fac99cee88c6ca00789b8f
--- /dev/null
+++ b/auth/auth_systems/facebookclient/djangofb/default_app/models.py
@@ -0,0 +1,31 @@
+from django.db import models
+
+# get_facebook_client lets us get the current Facebook object
+# from outside of a view, which lets us have cleaner code
+from facebook.djangofb import get_facebook_client
+
+class UserManager(models.Manager):
+    """Custom manager for a Facebook User."""
+    
+    def get_current(self):
+        """Gets a User object for the logged-in Facebook user."""
+        facebook = get_facebook_client()
+        user, created = self.get_or_create(id=int(facebook.uid))
+        if created:
+            # we could do some custom actions for new users here...
+            pass
+        return user
+
+class User(models.Model):
+    """A simple User model for Facebook users."""
+
+    # We use the user's UID as the primary key in our database.
+    id = models.IntegerField(primary_key=True)
+
+    # TODO: The data that you want to store for each user would go here.
+    # For this sample, we let users let people know their favorite progamming
+    # language, in the spirit of Extended Info.
+    language = models.CharField(maxlength=64, default='Python')
+
+    # Add the custom manager
+    objects = UserManager()
diff --git a/auth/auth_systems/facebookclient/djangofb/default_app/templates/canvas.fbml b/auth/auth_systems/facebookclient/djangofb/default_app/templates/canvas.fbml
new file mode 100644
index 0000000000000000000000000000000000000000..6734dd17caa138540fb10d0fcb750d70c8600d33
--- /dev/null
+++ b/auth/auth_systems/facebookclient/djangofb/default_app/templates/canvas.fbml
@@ -0,0 +1,22 @@
+<fb:header>
+  {% comment %}
+    We can use {{ fbuser }} to get at the current user.
+    {{ fbuser.id }} will be the user's UID, and {{ fbuser.language }}
+    is his/her favorite language (Python :-).
+  {% endcomment %}
+  Welcome, <fb:name uid="{{ fbuser.id }}" firstnameonly="true" useyou="false" />!
+</fb:header>
+
+<div class="clearfix" style="float: left; border: 1px #d8dfea solid; padding: 10px 10px 10px 10px; margin-left: 30px; margin-bottom: 30px; width: 500px;">
+  Your favorite language is {{ fbuser.language|escape }}.
+  <br /><br />
+
+  <div class="grayheader clearfix">
+    <br /><br />
+
+    <form action="." method="POST">
+      <input type="text" name="language" value="{{ fbuser.language|escape }}" />
+      <input type="submit" value="Change" />
+    </form>
+  </div>
+</div>
diff --git a/auth/auth_systems/facebookclient/djangofb/default_app/urls.py b/auth/auth_systems/facebookclient/djangofb/default_app/urls.py
new file mode 100644
index 0000000000000000000000000000000000000000..850184440c5fc153df12bf71e792cdccfe9baa57
--- /dev/null
+++ b/auth/auth_systems/facebookclient/djangofb/default_app/urls.py
@@ -0,0 +1,7 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('{{ project }}.{{ app }}.views',
+    (r'^$', 'canvas'),
+    # Define other pages you want to create here
+)
+
diff --git a/auth/auth_systems/facebookclient/djangofb/default_app/views.py b/auth/auth_systems/facebookclient/djangofb/default_app/views.py
new file mode 100644
index 0000000000000000000000000000000000000000..931d6216a8f689167f7c5d22facf9b69551daeff
--- /dev/null
+++ b/auth/auth_systems/facebookclient/djangofb/default_app/views.py
@@ -0,0 +1,37 @@
+from django.http import HttpResponse
+from django.views.generic.simple import direct_to_template
+#uncomment the following two lines and the one below
+#if you dont want to use a decorator instead of the middleware
+#from django.utils.decorators import decorator_from_middleware
+#from facebook.djangofb import FacebookMiddleware
+
+# Import the Django helpers
+import facebook.djangofb as facebook
+
+# The User model defined in models.py
+from models import User
+
+# We'll require login for our canvas page. This
+# isn't necessarily a good idea, as we might want
+# to let users see the page without granting our app
+# access to their info. See the wiki for details on how
+# to do this.
+#@decorator_from_middleware(FacebookMiddleware)
+@facebook.require_login()
+def canvas(request):
+    # Get the User object for the currently logged in user
+    user = User.objects.get_current()
+
+    # Check if we were POSTed the user's new language of choice
+    if 'language' in request.POST:
+        user.language = request.POST['language'][:64]
+        user.save()
+
+    # User is guaranteed to be logged in, so pass canvas.fbml
+    # an extra 'fbuser' parameter that is the User object for
+    # the currently logged in user.
+    return direct_to_template(request, 'canvas.fbml', extra_context={'fbuser': user})
+
+@facebook.require_login()
+def ajax(request):
+    return HttpResponse('hello world')
diff --git a/auth/auth_systems/facebookclient/djangofb/models.py b/auth/auth_systems/facebookclient/djangofb/models.py
new file mode 100644
index 0000000000000000000000000000000000000000..b5d2c62221e9926f7ab4b57cb95fb71ab22be2da
--- /dev/null
+++ b/auth/auth_systems/facebookclient/djangofb/models.py
@@ -0,0 +1,36 @@
+from django.db import models
+from django.utils.html import escape
+from django.utils.safestring import mark_safe
+
+FB_MESSAGE_STATUS = (
+    (0, 'Explanation'),
+    (1, 'Error'),
+    (2, 'Success'),
+)
+
+class MessageManager(models.Manager):
+    def get_and_delete_all(self, uid):
+        messages = []
+        for m in self.filter(uid=uid):
+            messages.append(m)
+            m.delete()
+        return messages
+
+class Message(models.Model):
+    """Represents a message for a Facebook user."""
+    uid = models.CharField(max_length=25)
+    status = models.IntegerField(choices=FB_MESSAGE_STATUS)
+    message = models.CharField(max_length=300)
+    objects = MessageManager()
+
+    def __unicode__(self):
+        return self.message
+
+    def _fb_tag(self):
+        return self.get_status_display().lower()
+
+    def as_fbml(self):
+        return mark_safe(u'<fb:%s message="%s" />' % (
+            self._fb_tag(),
+            escape(self.message),
+        ))
diff --git a/auth/auth_systems/facebookclient/webappfb.py b/auth/auth_systems/facebookclient/webappfb.py
new file mode 100644
index 0000000000000000000000000000000000000000..5fdf77af5c05ce29ccd56b6326ca4b8f64a08294
--- /dev/null
+++ b/auth/auth_systems/facebookclient/webappfb.py
@@ -0,0 +1,170 @@
+#
+# webappfb - Facebook tools for Google's AppEngine "webapp" Framework
+#
+# Copyright (c) 2009, Max Battcher
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in the
+#       documentation and/or other materials provided with the distribution.
+#     * Neither the name of the author nor the names of its contributors may
+#       be used to endorse or promote products derived from this software
+#       without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS``AS IS'' AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+from google.appengine.api import memcache
+from google.appengine.ext.webapp import RequestHandler
+from facebook import Facebook
+import yaml
+
+"""
+Facebook tools for Google AppEngine's object-oriented "webapp" framework.
+"""
+
+# This global configuration dictionary is for configuration variables
+# for Facebook requests such as the application's API key and secret
+# key. Defaults to loading a 'facebook.yaml' YAML file. This should be
+# useful and familiar for most AppEngine development.
+FACEBOOK_CONFIG = yaml.load(file('facebook.yaml', 'r'))
+
+class FacebookRequestHandler(RequestHandler):
+    """
+    Base class for request handlers for Facebook apps, providing useful
+    Facebook-related tools: a local 
+    """
+
+    def _fbconfig_value(self, name, default=None):
+        """
+        Checks the global config dictionary and then for a class/instance
+        variable, using a provided default if no value is found.
+        """
+        if name in FACEBOOK_CONFIG:
+            default = FACEBOOK_CONFIG[name]
+            
+        return getattr(self, name, default)
+
+    def initialize(self, request, response):
+        """
+        Initialize's this request's Facebook client.
+        """
+        super(FacebookRequestHandler, self).initialize(request, response)
+
+        app_name = self._fbconfig_value('app_name', '')
+        api_key = self._fbconfig_value('api_key', None)
+        secret_key = self._fbconfig_value('secret_key', None)
+
+        self.facebook = Facebook(api_key, secret_key,
+            app_name=app_name)
+
+        require_app = self._fbconfig_value('require_app', False)
+        require_login = self._fbconfig_value('require_login', False)
+        need_session = self._fbconfig_value('need_session', False)
+        check_session = self._fbconfig_value('check_session', True)
+
+        self._messages = None
+        self.redirecting = False
+
+        if require_app or require_login:
+            if not self.facebook.check_session(request):
+                self.redirect(self.facebook.get_login_url(next=request.path))
+                self.redirecting = True
+                return
+        elif check_session:
+            self.facebook.check_session(request) # ignore response
+
+        # NOTE: require_app is deprecated according to modern Facebook login
+        #       policies. Included for completeness, but unnecessary.
+        if require_app and not self.facebook.added:
+            self.redirect(self.facebook.get_add_url(next=request.path))
+            self.redirecting = True
+            return
+
+        if not (require_app or require_login) and need_session:
+            self.facebook.auth.getSession()
+
+    def redirect(self, url, **kwargs):
+        """
+        For Facebook canvas pages we should use <fb:redirect /> instead of
+        a normal redirect.
+        """
+        if self.facebook.in_canvas:
+            self.response.clear()
+            self.response.out.write('<fb:redirect url="%s" />' % (url, ))
+        else:
+            super(FacebookRequestHandler, self).redirect(url, **kwargs)
+
+    def add_user_message(self, kind, msg, detail='', time=15 * 60):
+        """
+        Add a message to the current user to memcache.
+        """
+        if self.facebook.uid:
+            key = 'messages:%s' % self.facebook.uid
+            self._messages = memcache.get(key)
+            message = {
+                'kind': kind,
+                'message': msg,
+                'detail': detail,
+            }
+            if self._messages is not None:
+                self._messages.append(message)
+            else:
+                self._messages = [message]
+            memcache.set(key, self._messages, time=time)
+
+    def get_and_delete_user_messages(self):
+        """
+        Get all of the messages for the current user; removing them.
+        """
+        if self.facebook.uid:
+            key = 'messages:%s' % self.facebook.uid
+            if not hasattr(self, '_messages') or self._messages is None:
+                self._messages = memcache.get(key)
+            memcache.delete(key)
+            return self._messages
+        return None
+
+class FacebookCanvasHandler(FacebookRequestHandler):
+    """
+    Request handler for Facebook canvas (FBML application) requests.
+    """
+
+    def canvas(self, *args, **kwargs):
+        """
+        This will be your handler to deal with Canvas requests.
+        """
+        raise NotImplementedError()
+
+    def get(self, *args):
+        """
+        All valid canvas views are POSTS.
+        """
+        # TODO: Attempt to auto-redirect to Facebook canvas?
+        self.error(404)
+
+    def post(self, *args, **kwargs):
+        """
+        Check a couple of simple safety checks and then call the canvas
+        handler.
+        """
+        if self.redirecting: return
+
+        if not self.facebook.in_canvas:
+            self.error(404)
+            return
+
+        self.canvas(*args, **kwargs)
+
+# vim: ai et ts=4 sts=4 sw=4
diff --git a/auth/auth_systems/facebookclient/wsgi.py b/auth/auth_systems/facebookclient/wsgi.py
new file mode 100644
index 0000000000000000000000000000000000000000..f6a790db14858d762159478a4aa51e7323144b5d
--- /dev/null
+++ b/auth/auth_systems/facebookclient/wsgi.py
@@ -0,0 +1,129 @@
+"""This is some simple helper code to bridge the Pylons / PyFacebook gap.
+
+There's some generic WSGI middleware, some Paste stuff, and some Pylons
+stuff.  Once you put FacebookWSGIMiddleware into your middleware stack,
+you'll have access to ``environ["pyfacebook.facebook"]``, which is a
+``facebook.Facebook`` object.  If you're using Paste (which includes
+Pylons users), you can also access this directly using the facebook
+global in this module.
+
+"""
+
+# Be careful what you import.  Don't expect everyone to have Pylons,
+# Paste, etc. installed.  Degrade gracefully.
+
+from facebook import Facebook
+
+__docformat__ = "restructuredtext"
+
+
+# Setup Paste, if available.  This needs to stay in the same module as
+# FacebookWSGIMiddleware below.
+
+try:
+    from paste.registry import StackedObjectProxy
+    from webob.exc import _HTTPMove
+    from paste.util.quoting import strip_html, html_quote, no_quote
+except ImportError:
+    pass
+else:
+    facebook = StackedObjectProxy(name="PyFacebook Facebook Connection")
+
+
+    class CanvasRedirect(_HTTPMove):
+
+        """This is for canvas redirects."""
+
+        title = "See Other"
+        code = 200
+        template = '<fb:redirect url="%(location)s" />'
+
+        def html(self, environ):
+            """ text/html representation of the exception """
+            body = self.make_body(environ, self.template, html_quote, no_quote)
+            return body
+
+class FacebookWSGIMiddleware(object):
+
+    """This is WSGI middleware for Facebook."""
+
+    def __init__(self, app, config, facebook_class=Facebook):
+        """Initialize the Facebook middleware.
+
+        ``app``
+            This is the WSGI application being wrapped.
+
+        ``config``
+            This is a dict containing the keys "pyfacebook.apikey" and
+            "pyfacebook.secret".
+
+        ``facebook_class``
+            If you want to subclass the Facebook class, you can pass in
+            your replacement here.  Pylons users will want to use
+            PylonsFacebook.
+
+        """
+        self.app = app
+        self.config = config
+        self.facebook_class = facebook_class
+
+    def __call__(self, environ, start_response):
+        config = self.config
+        real_facebook = self.facebook_class(config["pyfacebook.apikey"],
+                                            config["pyfacebook.secret"])
+        registry = environ.get('paste.registry')
+        if registry:
+            registry.register(facebook, real_facebook)
+        environ['pyfacebook.facebook'] = real_facebook
+        return self.app(environ, start_response)
+
+
+# The remainder is Pylons specific.
+
+try:
+    import pylons
+    from pylons.controllers.util import redirect_to as pylons_redirect_to
+    from routes import url_for
+except ImportError:
+    pass
+else:
+
+
+    class PylonsFacebook(Facebook):
+
+        """Subclass Facebook to add Pylons goodies."""
+
+        def check_session(self, request=None):
+            """The request parameter is now optional."""
+            if request is None:
+                request = pylons.request
+            return Facebook.check_session(self, request)
+
+        # The Django request object is similar enough to the Paste
+        # request object that check_session and validate_signature
+        # should *just work*.
+
+        def redirect_to(self, url):
+            """Wrap Pylons' redirect_to function so that it works in_canvas.
+
+            By the way, this won't work until after you call
+            check_session().
+
+            """
+            if self.in_canvas:
+                raise CanvasRedirect(url)
+            pylons_redirect_to(url)
+
+        def apps_url_for(self, *args, **kargs):
+            """Like url_for, but starts with "http://apps.facebook.com"."""
+            return "http://apps.facebook.com" + url_for(*args, **kargs)
+
+
+    def create_pylons_facebook_middleware(app, config):
+        """This is a simple wrapper for FacebookWSGIMiddleware.
+
+        It passes the correct facebook_class.
+
+        """
+        return FacebookWSGIMiddleware(app, config,
+                                      facebook_class=PylonsFacebook)
diff --git a/auth/auth_systems/google.py b/auth/auth_systems/google.py
new file mode 100644
index 0000000000000000000000000000000000000000..847530be56dcc644a8053607226c7b77c8139565
--- /dev/null
+++ b/auth/auth_systems/google.py
@@ -0,0 +1,59 @@
+"""
+Google Authentication
+
+"""
+
+from django.http import *
+from django.core.mail import send_mail
+from django.conf import settings
+
+import sys, os, cgi, urllib, urllib2, re
+from xml.etree import ElementTree
+
+from openid import view_helpers
+
+# some parameters to indicate that status updating is not possible
+STATUS_UPDATES = False
+
+# display tweaks
+LOGIN_MESSAGE = "Log in with my Google Account"
+OPENID_ENDPOINT = 'https://www.google.com/accounts/o8/id'
+
+# FIXME!
+# TRUST_ROOT = 'http://localhost:8000'
+# RETURN_TO = 'http://localhost:8000/auth/after'
+
+def get_auth_url(request, redirect_url):
+  # FIXME?? TRUST_ROOT should be diff than return_url?
+  request.session['google_redirect_url'] = redirect_url
+  url = view_helpers.start_openid(request.session, OPENID_ENDPOINT, redirect_url, redirect_url)
+  return url
+
+def get_user_info_after_auth(request):
+  data = view_helpers.finish_openid(request.session, request.GET, request.session['google_redirect_url'])
+
+  return {'type' : 'google', 'user_id': data['ax']['email'][0], 'name': "%s %s" % (data['ax']['firstname'][0], data['ax']['lastname'][0]), 'info': {}, 'token':{}}
+    
+def do_logout(user):
+  """
+  logout of Google
+  """
+  return None
+  
+def update_status(token, message):
+  """
+  simple update
+  """
+  pass
+
+def send_message(user_id, name, user_info, subject, body):
+  """
+  send email to google users. user_id is the email for google.
+  """
+  send_mail(subject, body, settings.SERVER_EMAIL, ["%s <%s>" % (name, user_id)], fail_silently=False)
+  
+def check_constraint(constraint, user_info):
+  """
+  for eligibility
+  """
+  pass
diff --git a/auth/auth_systems/live.py b/auth/auth_systems/live.py
new file mode 100644
index 0000000000000000000000000000000000000000..21eaf7c6dce0fa264c5fb41c6d156f537ef73eca
--- /dev/null
+++ b/auth/auth_systems/live.py
@@ -0,0 +1,67 @@
+"""
+Windows Live Authentication using oAuth WRAP,
+so much like Facebook
+
+# NOT WORKING YET because Windows Live documentation and status is unclear. Is it in beta? I think it is.
+"""
+
+import logging
+
+from django.conf import settings
+APP_ID = settings.LIVE_APP_ID
+APP_SECRET = settings.LIVE_APP_SECRET
+  
+import urllib, urllib2, cgi
+
+# some parameters to indicate that status updating is possible
+STATUS_UPDATES = False
+# STATUS_UPDATE_WORDING_TEMPLATE = "Send %s to your facebook status"
+
+from auth import utils
+
+def live_url(url, params):
+  if params:
+    return "https://graph.facebook.com%s?%s" % (url, urllib.urlencode(params))
+  else:
+    return "https://graph.facebook.com%s" % url
+
+def live_get(url, params):
+  full_url = live_url(url,params)
+  return urllib2.urlopen(full_url).read()
+
+def live_post(url, params):
+  full_url = live_url(url, None)
+  return urllib2.urlopen(full_url, urllib.urlencode(params)).read()
+
+def get_auth_url(request, redirect_url):
+  request.session['live_redirect_uri'] = redirect_url
+  return live_url('/oauth/authorize', {
+      'client_id': APP_ID,
+      'redirect_uri': redirect_url,
+      'scope': 'publish_stream'})
+    
+def get_user_info_after_auth(request):
+  args = facebook_get('/oauth/access_token', {
+      'client_id' : APP_ID,
+      'redirect_uri' : request.session['fb_redirect_uri'],
+      'client_secret' : API_SECRET,
+      'code' : request.GET['code']
+      })
+
+  access_token = cgi.parse_qs(args)['access_token'][0]
+
+  info = utils.from_json(facebook_get('/me', {'access_token':access_token}))
+
+  return {'type': 'facebook', 'user_id' : info['id'], 'name': info['name'], 'info': info, 'token': {'access_token': access_token}}
+    
+def update_status(user_id, user_info, token, message):
+  """
+  post a message to the auth system's update stream, e.g. twitter stream
+  """
+  result = facebook_post('/me/feed', {
+      'access_token': token['access_token'],
+      'message': message
+      })
+
+def send_message(user_id, user_name, user_info, subject, body):
+  pass
diff --git a/auth/auth_systems/oauthclient/README b/auth/auth_systems/oauthclient/README
new file mode 100644
index 0000000000000000000000000000000000000000..10deb937724ed4b3e9cded775b224739ab005fce
--- /dev/null
+++ b/auth/auth_systems/oauthclient/README
@@ -0,0 +1,56 @@
+Python Oauth client for Twitter
+---------
+
+I built this so that i didn't have to keep looking for an oauth client for twitter to use in python. 
+
+It is based off of the PHP work from abrah.am (http://github.com/poseurtech/twitteroauth/tree/master). 
+It was very helpful. 
+
+I am using the OAuth lib that is from google gdata. I figure it is a working client and is in production use - so it should be solid. You can find it at:
+http://gdata-python-client.googlecode.com/svn/trunk/src/gdata/oauth
+
+With a bit of modification this client should work with other publishers. 
+
+btw, i am a python n00b. so feel free to help out. 
+
+Thanks,
+harper - harper@nata2.org (email and xmpp)
+
+
+-----------
+Links:
+
+Google Code Project: http://code.google.com/p/twitteroauth-python/
+Issue Tracker: http://code.google.com/p/twitteroauth-python/issues/list
+Wiki: http://wiki.github.com/harperreed/twitteroauth-python
+
+-----------
+
+The example client is included in the client.py. It is:
+
+if __name__ == '__main__':
+    consumer_key = ''
+    consumer_secret = ''
+    while not consumer_key:
+        consumer_key = raw_input('Please enter consumer key: ')
+    while not consumer_secret:
+        consumer_secret = raw_input('Please enter consumer secret: ')
+    auth_client = TwitterOAuthClient(consumer_key,consumer_secret)
+    tok = auth_client.get_request_token()
+    token = tok['oauth_token']
+    token_secret = tok['oauth_token_secret']
+    url = auth_client.get_authorize_url(token) 
+    webbrowser.open(url)
+    print "Visit this URL to authorize your app: " + url
+    response_token = raw_input('What is the oauth_token from twitter: ')
+    response_client = TwitterOAuthClient(consumer_key, consumer_secret,token, token_secret) 
+    tok = response_client.get_access_token()
+    print "Making signed request"
+    #verify user access
+    content = response_client.oauth_request('https://twitter.com/account/verify_credentials.json', method='POST')
+    #make an update
+    #content = response_client.oauth_request('https://twitter.com/statuses/update.xml', {'status':'Updated from a python oauth client. awesome.'}, method='POST')
+    print content
+   
+    print 'Done.'
+
diff --git a/auth/auth_systems/oauthclient/__init__.py b/auth/auth_systems/oauthclient/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/auth/auth_systems/oauthclient/client.py b/auth/auth_systems/oauthclient/client.py
new file mode 100644
index 0000000000000000000000000000000000000000..e1a4e18b7ccc2c84d6cccec0f0ab21f27ba120ad
--- /dev/null
+++ b/auth/auth_systems/oauthclient/client.py
@@ -0,0 +1,147 @@
+'''
+Python Oauth client for Twitter
+
+Used the SampleClient from the OAUTH.org example python client as basis.
+
+props to leahculver for making a very hard to use but in the end usable oauth lib.
+
+'''
+import httplib
+import urllib, urllib2
+import time
+import webbrowser
+import oauth as oauth
+from urlparse import urlparse
+
+class TwitterOAuthClient(oauth.OAuthClient):
+    api_root_url = 'https://twitter.com' #for testing 'http://term.ie'
+    api_root_port = "80"
+
+    #set api urls
+    def request_token_url(self):
+        return self.api_root_url + '/oauth/request_token'
+    def authorize_url(self):
+        return self.api_root_url + '/oauth/authorize'
+    def authenticate_url(self):
+      return self.api_root_url + '/oauth/authenticate'
+    def access_token_url(self):
+        return self.api_root_url + '/oauth/access_token'
+
+    #oauth object
+    def __init__(self, consumer_key, consumer_secret, oauth_token=None, oauth_token_secret=None):
+        self.sha1_method = oauth.OAuthSignatureMethod_HMAC_SHA1()
+        self.consumer = oauth.OAuthConsumer(consumer_key, consumer_secret)
+        if ((oauth_token != None) and (oauth_token_secret!=None)):
+            self.token = oauth.OAuthConsumer(oauth_token, oauth_token_secret)
+        else:
+            self.token = None
+
+    def oauth_request(self,url, args = {}, method=None):
+        if (method==None):
+            if args=={}:
+                method = "GET"
+            else:
+                method = "POST"
+        req = oauth.OAuthRequest.from_consumer_and_token(self.consumer, self.token, method, url, args)
+        req.sign_request(self.sha1_method, self.consumer,self.token)
+        if (method=="GET"):
+            return self.http_wrapper(req.to_url())
+        elif (method == "POST"):
+            return self.http_wrapper(req.get_normalized_http_url(),req.to_postdata())
+
+    #this is barely working. (i think. mostly it is that everyone else is using httplib) 
+    def http_wrapper(self, url, postdata={}): 
+        try:
+            if (postdata != {}): 
+                f = urllib.urlopen(url, postdata) 
+            else: 
+                f = urllib.urlopen(url) 
+            response = f.read()
+        except:
+            import traceback
+            import logging, sys
+            cla, exc, tb = sys.exc_info()
+            logging.error(url)
+            if postdata:
+              logging.error("with post data")
+            else:
+              logging.error("without post data")
+            logging.error(exc.args)
+            logging.error(traceback.format_tb(tb))
+            response = ""
+        return response 
+    
+
+    def get_request_token(self):
+        response = self.oauth_request(self.request_token_url())
+        token = self.oauth_parse_response(response)
+        try:
+            self.token = oauth.OAuthConsumer(token['oauth_token'],token['oauth_token_secret'])
+            return token
+        except:
+            raise oauth.OAuthError('Invalid oauth_token')
+
+    def oauth_parse_response(self, response_string):
+        r = {}
+        for param in response_string.split("&"):
+            pair = param.split("=")
+            if (len(pair)!=2):
+                break
+                
+            r[pair[0]]=pair[1]
+        return r
+
+    def get_authorize_url(self, token):
+        return self.authorize_url() + '?oauth_token=' +token
+
+    def get_authenticate_url(self, token):
+        return self.authenticate_url() + '?oauth_token=' +token
+
+    def get_access_token(self,token=None):
+        r = self.oauth_request(self.access_token_url())
+        token = self.oauth_parse_response(r)
+        self.token = oauth.OAuthConsumer(token['oauth_token'],token['oauth_token_secret'])
+        return token
+
+    def oauth_request(self, url, args={}, method=None):
+        if (method==None):
+            if args=={}:
+                method = "GET"
+            else:
+                method = "POST"
+        req = oauth.OAuthRequest.from_consumer_and_token(self.consumer, self.token, method, url, args)
+        req.sign_request(self.sha1_method, self.consumer,self.token)
+        if (method=="GET"):
+            return self.http_wrapper(req.to_url())
+        elif (method == "POST"):
+            return self.http_wrapper(req.get_normalized_http_url(),req.to_postdata())
+
+        
+
+if __name__ == '__main__':
+    consumer_key = ''
+    consumer_secret = ''
+    while not consumer_key:
+        consumer_key = raw_input('Please enter consumer key: ')
+    while not consumer_secret:
+        consumer_secret = raw_input('Please enter consumer secret: ')
+    auth_client = TwitterOAuthClient(consumer_key,consumer_secret)
+    tok = auth_client.get_request_token()
+    token = tok['oauth_token']
+    token_secret = tok['oauth_token_secret']
+    url = auth_client.get_authorize_url(token) 
+    webbrowser.open(url)
+    print "Visit this URL to authorize your app: " + url
+    response_token = raw_input('What is the oauth_token from twitter: ')
+    response_client = TwitterOAuthClient(consumer_key, consumer_secret,token, token_secret) 
+    tok = response_client.get_access_token()
+    print "Making signed request"
+    #verify user access
+    content = response_client.oauth_request('https://twitter.com/account/verify_credentials.json', method='POST')
+    #make an update
+    #content = response_client.oauth_request('https://twitter.com/statuses/update.xml', {'status':'Updated from a python oauth client. awesome.'}, method='POST')
+    print content
+   
+    print 'Done.'
+
+
diff --git a/auth/auth_systems/oauthclient/oauth/CHANGES.txt b/auth/auth_systems/oauthclient/oauth/CHANGES.txt
new file mode 100755
index 0000000000000000000000000000000000000000..7c2b92cd943df997919904c965f480268e47b701
--- /dev/null
+++ b/auth/auth_systems/oauthclient/oauth/CHANGES.txt
@@ -0,0 +1,17 @@
+1. Moved oauth.py to __init__.py
+
+2. Refactored __init__.py for compatibility with python 2.2 (Issue 59)
+
+3. Refactored rsa.py for compatibility with python 2.2 (Issue 59)
+
+4. Refactored OAuthRequest.from_token_and_callback since the callback url was
+getting double url-encoding the callback url in place of single. (Issue 43)
+
+5. Added build_signature_base_string method to rsa.py since it used the
+implementation of this method from oauth.OAuthSignatureMethod_HMAC_SHA1 which
+was incorrect since it enforced the presence of a consumer secret and a token
+secret. Also, changed its super class from oauth.OAuthSignatureMethod_HMAC_SHA1
+to oauth.OAuthSignatureMethod (Issue 64)
+
+6. Refactored <OAuthRequest>.to_header method since it returned non-oauth params
+as well which was incorrect. (Issue 31)
\ No newline at end of file
diff --git a/auth/auth_systems/oauthclient/oauth/__init__.py b/auth/auth_systems/oauthclient/oauth/__init__.py
new file mode 100755
index 0000000000000000000000000000000000000000..baf543ed4a8db09d92ee91b925d18bc6f5d09f93
--- /dev/null
+++ b/auth/auth_systems/oauthclient/oauth/__init__.py
@@ -0,0 +1,524 @@
+import cgi
+import urllib
+import time
+import random
+import urlparse
+import hmac
+import binascii
+
+VERSION = '1.0' # Hi Blaine!
+HTTP_METHOD = 'GET'
+SIGNATURE_METHOD = 'PLAINTEXT'
+
+# Generic exception class
+class OAuthError(RuntimeError):
+    def __init__(self, message='OAuth error occured.'):
+        self.message = message
+
+# optional WWW-Authenticate header (401 error)
+def build_authenticate_header(realm=''):
+    return {'WWW-Authenticate': 'OAuth realm="%s"' % realm}
+
+# url escape
+def escape(s):
+    # escape '/' too
+    return urllib.quote(s, safe='~')
+
+# util function: current timestamp
+# seconds since epoch (UTC)
+def generate_timestamp():
+    return int(time.time())
+
+# util function: nonce
+# pseudorandom number
+def generate_nonce(length=8):
+    return ''.join([str(random.randint(0, 9)) for i in range(length)])
+
+# OAuthConsumer is a data type that represents the identity of the Consumer
+# via its shared secret with the Service Provider.
+class OAuthConsumer(object):
+    key = None
+    secret = None
+
+    def __init__(self, key, secret):
+        self.key = key
+        self.secret = secret
+
+# OAuthToken is a data type that represents an End User via either an access
+# or request token.     
+class OAuthToken(object):
+    # access tokens and request tokens
+    key = None
+    secret = None
+
+    '''
+    key = the token
+    secret = the token secret
+    '''
+    def __init__(self, key, secret):
+        self.key = key
+        self.secret = secret
+
+    def to_string(self):
+        return urllib.urlencode({'oauth_token': self.key, 'oauth_token_secret': self.secret})
+
+    # return a token from something like:
+    # oauth_token_secret=digg&oauth_token=digg
+    def from_string(s):
+        params = cgi.parse_qs(s, keep_blank_values=False)
+        key = params['oauth_token'][0]
+        secret = params['oauth_token_secret'][0]
+        return OAuthToken(key, secret)
+    from_string = staticmethod(from_string)
+
+    def __str__(self):
+        return self.to_string()
+
+# OAuthRequest represents the request and can be serialized
+class OAuthRequest(object):
+    '''
+    OAuth parameters:
+        - oauth_consumer_key 
+        - oauth_token
+        - oauth_signature_method
+        - oauth_signature 
+        - oauth_timestamp 
+        - oauth_nonce
+        - oauth_version
+        ... any additional parameters, as defined by the Service Provider.
+    '''
+    parameters = None # oauth parameters
+    http_method = HTTP_METHOD
+    http_url = None
+    version = VERSION
+
+    def __init__(self, http_method=HTTP_METHOD, http_url=None, parameters=None):
+        self.http_method = http_method
+        self.http_url = http_url
+        self.parameters = parameters or {}
+
+    def set_parameter(self, parameter, value):
+        self.parameters[parameter] = value
+
+    def get_parameter(self, parameter):
+        try:
+            return self.parameters[parameter]
+        except:
+            raise OAuthError('Parameter not found: %s' % parameter)
+
+    def _get_timestamp_nonce(self):
+        return self.get_parameter('oauth_timestamp'), self.get_parameter('oauth_nonce')
+
+    # get any non-oauth parameters
+    def get_nonoauth_parameters(self):
+        parameters = {}
+        for k, v in self.parameters.iteritems():
+            # ignore oauth parameters
+            if k.find('oauth_') < 0:
+                parameters[k] = v
+        return parameters
+
+    # serialize as a header for an HTTPAuth request
+    def to_header(self, realm=''):
+        auth_header = 'OAuth realm="%s"' % realm
+        # add the oauth parameters
+        if self.parameters:
+            for k, v in self.parameters.iteritems():
+                if k[:6] == 'oauth_':
+                    auth_header += ', %s="%s"' % (k, escape(str(v)))
+        return {'Authorization': auth_header}
+
+    # serialize as post data for a POST request
+    def to_postdata(self):
+        return '&'.join(['%s=%s' % (escape(str(k)), escape(str(v))) for k, v in self.parameters.iteritems()])
+
+    # serialize as a url for a GET request
+    def to_url(self):
+        return '%s?%s' % (self.get_normalized_http_url(), self.to_postdata())
+
+    # return a string that consists of all the parameters that need to be signed
+    def get_normalized_parameters(self):
+        params = self.parameters
+        try:
+            # exclude the signature if it exists
+            del params['oauth_signature']
+        except:
+            pass
+        key_values = params.items()
+        # sort lexicographically, first after key, then after value
+        key_values.sort()
+        # combine key value pairs in string and escape
+        return '&'.join(['%s=%s' % (escape(str(k)), escape(str(v))) for k, v in key_values])
+
+    # just uppercases the http method
+    def get_normalized_http_method(self):
+        return self.http_method.upper()
+
+    # parses the url and rebuilds it to be scheme://host/path
+    def get_normalized_http_url(self):
+        parts = urlparse.urlparse(self.http_url)
+        url_string = '%s://%s%s' % (parts[0], parts[1], parts[2]) # scheme, netloc, path
+        return url_string
+        
+    # set the signature parameter to the result of build_signature
+    def sign_request(self, signature_method, consumer, token):
+        # set the signature method
+        self.set_parameter('oauth_signature_method', signature_method.get_name())
+        # set the signature
+        self.set_parameter('oauth_signature', self.build_signature(signature_method, consumer, token))
+
+    def build_signature(self, signature_method, consumer, token):
+        # call the build signature method within the signature method
+        return signature_method.build_signature(self, consumer, token)
+
+    def from_request(http_method, http_url, headers=None, parameters=None, query_string=None):
+        # combine multiple parameter sources
+        if parameters is None:
+            parameters = {}
+
+        # headers
+        if headers and 'Authorization' in headers:
+            auth_header = headers['Authorization']
+            # check that the authorization header is OAuth
+            if auth_header.index('OAuth') > -1:
+                try:
+                    # get the parameters from the header
+                    header_params = OAuthRequest._split_header(auth_header)
+                    parameters.update(header_params)
+                except:
+                    raise OAuthError('Unable to parse OAuth parameters from Authorization header.')
+
+        # GET or POST query string
+        if query_string:
+            query_params = OAuthRequest._split_url_string(query_string)
+            parameters.update(query_params)
+
+        # URL parameters
+        param_str = urlparse.urlparse(http_url)[4] # query
+        url_params = OAuthRequest._split_url_string(param_str)
+        parameters.update(url_params)
+
+        if parameters:
+            return OAuthRequest(http_method, http_url, parameters)
+
+        return None
+    from_request = staticmethod(from_request)
+
+    def from_consumer_and_token(oauth_consumer, token=None, http_method=HTTP_METHOD, http_url=None, parameters=None):
+        if not parameters:
+            parameters = {}
+
+        defaults = {
+            'oauth_consumer_key': oauth_consumer.key,
+            'oauth_timestamp': generate_timestamp(),
+            'oauth_nonce': generate_nonce(),
+            'oauth_version': OAuthRequest.version,
+        }
+
+        defaults.update(parameters)
+        parameters = defaults
+
+        if token:
+            parameters['oauth_token'] = token.key
+
+        return OAuthRequest(http_method, http_url, parameters)
+    from_consumer_and_token = staticmethod(from_consumer_and_token)
+
+    def from_token_and_callback(token, callback=None, http_method=HTTP_METHOD, http_url=None, parameters=None):
+        if not parameters:
+            parameters = {}
+
+        parameters['oauth_token'] = token.key
+
+        if callback:
+            parameters['oauth_callback'] = callback
+
+        return OAuthRequest(http_method, http_url, parameters)
+    from_token_and_callback = staticmethod(from_token_and_callback)
+
+    # util function: turn Authorization: header into parameters, has to do some unescaping
+    def _split_header(header):
+        params = {}
+        parts = header.split(',')
+        for param in parts:
+            # ignore realm parameter
+            if param.find('OAuth realm') > -1:
+                continue
+            # remove whitespace
+            param = param.strip()
+            # split key-value
+            param_parts = param.split('=', 1)
+            # remove quotes and unescape the value
+            params[param_parts[0]] = urllib.unquote(param_parts[1].strip('\"'))
+        return params
+    _split_header = staticmethod(_split_header)
+    
+    # util function: turn url string into parameters, has to do some unescaping
+    def _split_url_string(param_str):
+        parameters = cgi.parse_qs(param_str, keep_blank_values=False)
+        for k, v in parameters.iteritems():
+            parameters[k] = urllib.unquote(v[0])
+        return parameters
+    _split_url_string = staticmethod(_split_url_string)
+
+# OAuthServer is a worker to check a requests validity against a data store
+class OAuthServer(object):
+    timestamp_threshold = 300 # in seconds, five minutes
+    version = VERSION
+    signature_methods = None
+    data_store = None
+
+    def __init__(self, data_store=None, signature_methods=None):
+        self.data_store = data_store
+        self.signature_methods = signature_methods or {}
+
+    def set_data_store(self, oauth_data_store):
+        self.data_store = data_store
+
+    def get_data_store(self):
+        return self.data_store
+
+    def add_signature_method(self, signature_method):
+        self.signature_methods[signature_method.get_name()] = signature_method
+        return self.signature_methods
+
+    # process a request_token request
+    # returns the request token on success
+    def fetch_request_token(self, oauth_request):
+        try:
+            # get the request token for authorization
+            token = self._get_token(oauth_request, 'request')
+        except OAuthError:
+            # no token required for the initial token request
+            version = self._get_version(oauth_request)
+            consumer = self._get_consumer(oauth_request)
+            self._check_signature(oauth_request, consumer, None)
+            # fetch a new token
+            token = self.data_store.fetch_request_token(consumer)
+        return token
+
+    # process an access_token request
+    # returns the access token on success
+    def fetch_access_token(self, oauth_request):
+        version = self._get_version(oauth_request)
+        consumer = self._get_consumer(oauth_request)
+        # get the request token
+        token = self._get_token(oauth_request, 'request')
+        self._check_signature(oauth_request, consumer, token)
+        new_token = self.data_store.fetch_access_token(consumer, token)
+        return new_token
+
+    # verify an api call, checks all the parameters
+    def verify_request(self, oauth_request):
+        # -> consumer and token
+        version = self._get_version(oauth_request)
+        consumer = self._get_consumer(oauth_request)
+        # get the access token
+        token = self._get_token(oauth_request, 'access')
+        self._check_signature(oauth_request, consumer, token)
+        parameters = oauth_request.get_nonoauth_parameters()
+        return consumer, token, parameters
+
+    # authorize a request token
+    def authorize_token(self, token, user):
+        return self.data_store.authorize_request_token(token, user)
+    
+    # get the callback url
+    def get_callback(self, oauth_request):
+        return oauth_request.get_parameter('oauth_callback')
+
+    # optional support for the authenticate header   
+    def build_authenticate_header(self, realm=''):
+        return {'WWW-Authenticate': 'OAuth realm="%s"' % realm}
+
+    # verify the correct version request for this server
+    def _get_version(self, oauth_request):
+        try:
+            version = oauth_request.get_parameter('oauth_version')
+        except:
+            version = VERSION
+        if version and version != self.version:
+            raise OAuthError('OAuth version %s not supported.' % str(version))
+        return version
+
+    # figure out the signature with some defaults
+    def _get_signature_method(self, oauth_request):
+        try:
+            signature_method = oauth_request.get_parameter('oauth_signature_method')
+        except:
+            signature_method = SIGNATURE_METHOD
+        try:
+            # get the signature method object
+            signature_method = self.signature_methods[signature_method]
+        except:
+            signature_method_names = ', '.join(self.signature_methods.keys())
+            raise OAuthError('Signature method %s not supported try one of the following: %s' % (signature_method, signature_method_names))
+
+        return signature_method
+
+    def _get_consumer(self, oauth_request):
+        consumer_key = oauth_request.get_parameter('oauth_consumer_key')
+        if not consumer_key:
+            raise OAuthError('Invalid consumer key.')
+        consumer = self.data_store.lookup_consumer(consumer_key)
+        if not consumer:
+            raise OAuthError('Invalid consumer.')
+        return consumer
+
+    # try to find the token for the provided request token key
+    def _get_token(self, oauth_request, token_type='access'):
+        token_field = oauth_request.get_parameter('oauth_token')
+        token = self.data_store.lookup_token(token_type, token_field)
+        if not token:
+            raise OAuthError('Invalid %s token: %s' % (token_type, token_field))
+        return token
+
+    def _check_signature(self, oauth_request, consumer, token):
+        timestamp, nonce = oauth_request._get_timestamp_nonce()
+        self._check_timestamp(timestamp)
+        self._check_nonce(consumer, token, nonce)
+        signature_method = self._get_signature_method(oauth_request)
+        try:
+            signature = oauth_request.get_parameter('oauth_signature')
+        except:
+            raise OAuthError('Missing signature.')
+        # validate the signature
+        valid_sig = signature_method.check_signature(oauth_request, consumer, token, signature)
+        if not valid_sig:
+            key, base = signature_method.build_signature_base_string(oauth_request, consumer, token)
+            raise OAuthError('Invalid signature. Expected signature base string: %s' % base)
+        built = signature_method.build_signature(oauth_request, consumer, token)
+
+    def _check_timestamp(self, timestamp):
+        # verify that timestamp is recentish
+        timestamp = int(timestamp)
+        now = int(time.time())
+        lapsed = now - timestamp
+        if lapsed > self.timestamp_threshold:
+            raise OAuthError('Expired timestamp: given %d and now %s has a greater difference than threshold %d' % (timestamp, now, self.timestamp_threshold))
+
+    def _check_nonce(self, consumer, token, nonce):
+        # verify that the nonce is uniqueish
+        nonce = self.data_store.lookup_nonce(consumer, token, nonce)
+        if nonce:
+            raise OAuthError('Nonce already used: %s' % str(nonce))
+
+# OAuthClient is a worker to attempt to execute a request
+class OAuthClient(object):
+    consumer = None
+    token = None
+
+    def __init__(self, oauth_consumer, oauth_token):
+        self.consumer = oauth_consumer
+        self.token = oauth_token
+
+    def get_consumer(self):
+        return self.consumer
+
+    def get_token(self):
+        return self.token
+
+    def fetch_request_token(self, oauth_request):
+        # -> OAuthToken
+        raise NotImplementedError
+
+    def fetch_access_token(self, oauth_request):
+        # -> OAuthToken
+        raise NotImplementedError
+
+    def access_resource(self, oauth_request):
+        # -> some protected resource
+        raise NotImplementedError
+
+# OAuthDataStore is a database abstraction used to lookup consumers and tokens
+class OAuthDataStore(object):
+
+    def lookup_consumer(self, key):
+        # -> OAuthConsumer
+        raise NotImplementedError
+
+    def lookup_token(self, oauth_consumer, token_type, token_token):
+        # -> OAuthToken
+        raise NotImplementedError
+
+    def lookup_nonce(self, oauth_consumer, oauth_token, nonce, timestamp):
+        # -> OAuthToken
+        raise NotImplementedError
+
+    def fetch_request_token(self, oauth_consumer):
+        # -> OAuthToken
+        raise NotImplementedError
+
+    def fetch_access_token(self, oauth_consumer, oauth_token):
+        # -> OAuthToken
+        raise NotImplementedError
+
+    def authorize_request_token(self, oauth_token, user):
+        # -> OAuthToken
+        raise NotImplementedError
+
+# OAuthSignatureMethod is a strategy class that implements a signature method
+class OAuthSignatureMethod(object):
+    def get_name(self):
+        # -> str
+        raise NotImplementedError
+
+    def build_signature_base_string(self, oauth_request, oauth_consumer, oauth_token):
+        # -> str key, str raw
+        raise NotImplementedError
+
+    def build_signature(self, oauth_request, oauth_consumer, oauth_token):
+        # -> str
+        raise NotImplementedError
+
+    def check_signature(self, oauth_request, consumer, token, signature):
+        built = self.build_signature(oauth_request, consumer, token)
+        return built == signature
+
+class OAuthSignatureMethod_HMAC_SHA1(OAuthSignatureMethod):
+
+    def get_name(self):
+        return 'HMAC-SHA1'
+        
+    def build_signature_base_string(self, oauth_request, consumer, token):
+        sig = (
+            escape(oauth_request.get_normalized_http_method()),
+            escape(oauth_request.get_normalized_http_url()),
+            escape(oauth_request.get_normalized_parameters()),
+        )
+
+        key = '%s&' % escape(consumer.secret)
+        if token:
+            key += escape(token.secret)
+        raw = '&'.join(sig)
+        return key, raw
+
+    def build_signature(self, oauth_request, consumer, token):
+        # build the base signature string
+        key, raw = self.build_signature_base_string(oauth_request, consumer, token)
+
+        # hmac object
+        try:
+            import hashlib # 2.5
+            hashed = hmac.new(key, raw, hashlib.sha1)
+        except:
+            import sha # deprecated
+            hashed = hmac.new(key, raw, sha)
+
+        # calculate the digest base 64
+        return binascii.b2a_base64(hashed.digest())[:-1]
+
+class OAuthSignatureMethod_PLAINTEXT(OAuthSignatureMethod):
+
+    def get_name(self):
+        return 'PLAINTEXT'
+
+    def build_signature_base_string(self, oauth_request, consumer, token):
+        # concatenate the consumer key and secret
+        sig = escape(consumer.secret) + '&'
+        if token:
+            sig = sig + escape(token.secret)
+        return sig
+
+    def build_signature(self, oauth_request, consumer, token):
+        return self.build_signature_base_string(oauth_request, consumer, token)
diff --git a/auth/auth_systems/oauthclient/oauth/rsa.py b/auth/auth_systems/oauthclient/oauth/rsa.py
new file mode 100755
index 0000000000000000000000000000000000000000..f8d9b8503f7a35aa57cfef1fe75445baabae596a
--- /dev/null
+++ b/auth/auth_systems/oauthclient/oauth/rsa.py
@@ -0,0 +1,120 @@
+#!/usr/bin/python
+
+"""
+requires tlslite - http://trevp.net/tlslite/
+
+"""
+
+import binascii
+
+from gdata.tlslite.utils import keyfactory
+from gdata.tlslite.utils import cryptomath
+
+# XXX andy: ugly local import due to module name, oauth.oauth
+import gdata.oauth as oauth
+
+class OAuthSignatureMethod_RSA_SHA1(oauth.OAuthSignatureMethod):
+  def get_name(self):
+    return "RSA-SHA1"
+
+  def _fetch_public_cert(self, oauth_request):
+    # not implemented yet, ideas are:
+    # (1) do a lookup in a table of trusted certs keyed off of consumer
+    # (2) fetch via http using a url provided by the requester
+    # (3) some sort of specific discovery code based on request
+    #
+    # either way should return a string representation of the certificate
+    raise NotImplementedError
+
+  def _fetch_private_cert(self, oauth_request):
+    # not implemented yet, ideas are:
+    # (1) do a lookup in a table of trusted certs keyed off of consumer
+    #
+    # either way should return a string representation of the certificate
+    raise NotImplementedError
+
+  def build_signature_base_string(self, oauth_request, consumer, token):
+      sig = (
+          oauth.escape(oauth_request.get_normalized_http_method()),
+          oauth.escape(oauth_request.get_normalized_http_url()),
+          oauth.escape(oauth_request.get_normalized_parameters()),
+      )
+      key = ''
+      raw = '&'.join(sig)
+      return key, raw
+
+  def build_signature(self, oauth_request, consumer, token):
+    key, base_string = self.build_signature_base_string(oauth_request,
+                                                        consumer,
+                                                        token)
+
+    # Fetch the private key cert based on the request
+    cert = self._fetch_private_cert(oauth_request)
+
+    # Pull the private key from the certificate
+    privatekey = keyfactory.parsePrivateKey(cert)
+    
+    # Convert base_string to bytes
+    #base_string_bytes = cryptomath.createByteArraySequence(base_string)
+    
+    # Sign using the key
+    signed = privatekey.hashAndSign(base_string)
+  
+    return binascii.b2a_base64(signed)[:-1]
+  
+  def check_signature(self, oauth_request, consumer, token, signature):
+    decoded_sig = base64.b64decode(signature);
+
+    key, base_string = self.build_signature_base_string(oauth_request,
+                                                        consumer,
+                                                        token)
+
+    # Fetch the public key cert based on the request
+    cert = self._fetch_public_cert(oauth_request)
+
+    # Pull the public key from the certificate
+    publickey = keyfactory.parsePEMKey(cert, public=True)
+
+    # Check the signature
+    ok = publickey.hashAndVerify(decoded_sig, base_string)
+
+    return ok
+
+
+class TestOAuthSignatureMethod_RSA_SHA1(OAuthSignatureMethod_RSA_SHA1):
+  def _fetch_public_cert(self, oauth_request):
+    cert = """
+-----BEGIN CERTIFICATE-----
+MIIBpjCCAQ+gAwIBAgIBATANBgkqhkiG9w0BAQUFADAZMRcwFQYDVQQDDA5UZXN0
+IFByaW5jaXBhbDAeFw03MDAxMDEwODAwMDBaFw0zODEyMzEwODAwMDBaMBkxFzAV
+BgNVBAMMDlRlc3QgUHJpbmNpcGFsMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
+gQC0YjCwIfYoprq/FQO6lb3asXrxLlJFuCvtinTF5p0GxvQGu5O3gYytUvtC2JlY
+zypSRjVxwxrsuRcP3e641SdASwfrmzyvIgP08N4S0IFzEURkV1wp/IpH7kH41Etb
+mUmrXSwfNZsnQRE5SYSOhh+LcK2wyQkdgcMv11l4KoBkcwIDAQABMA0GCSqGSIb3
+DQEBBQUAA4GBAGZLPEuJ5SiJ2ryq+CmEGOXfvlTtEL2nuGtr9PewxkgnOjZpUy+d
+4TvuXJbNQc8f4AMWL/tO9w0Fk80rWKp9ea8/df4qMq5qlFWlx6yOLQxumNOmECKb
+WpkUQDIDJEoFUzKMVuJf4KO/FJ345+BNLGgbJ6WujreoM1X/gYfdnJ/J
+-----END CERTIFICATE-----
+"""
+    return cert
+
+  def _fetch_private_cert(self, oauth_request):
+    cert = """
+-----BEGIN PRIVATE KEY-----
+MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALRiMLAh9iimur8V
+A7qVvdqxevEuUkW4K+2KdMXmnQbG9Aa7k7eBjK1S+0LYmVjPKlJGNXHDGuy5Fw/d
+7rjVJ0BLB+ubPK8iA/Tw3hLQgXMRRGRXXCn8ikfuQfjUS1uZSatdLB81mydBETlJ
+hI6GH4twrbDJCR2Bwy/XWXgqgGRzAgMBAAECgYBYWVtleUzavkbrPjy0T5FMou8H
+X9u2AC2ry8vD/l7cqedtwMPp9k7TubgNFo+NGvKsl2ynyprOZR1xjQ7WgrgVB+mm
+uScOM/5HVceFuGRDhYTCObE+y1kxRloNYXnx3ei1zbeYLPCHdhxRYW7T0qcynNmw
+rn05/KO2RLjgQNalsQJBANeA3Q4Nugqy4QBUCEC09SqylT2K9FrrItqL2QKc9v0Z
+zO2uwllCbg0dwpVuYPYXYvikNHHg+aCWF+VXsb9rpPsCQQDWR9TT4ORdzoj+Nccn
+qkMsDmzt0EfNaAOwHOmVJ2RVBspPcxt5iN4HI7HNeG6U5YsFBb+/GZbgfBT3kpNG
+WPTpAkBI+gFhjfJvRw38n3g/+UeAkwMI2TJQS4n8+hid0uus3/zOjDySH3XHCUno
+cn1xOJAyZODBo47E+67R4jV1/gzbAkEAklJaspRPXP877NssM5nAZMU0/O/NGCZ+
+3jPgDUno6WbJn5cqm8MqWhW1xGkImgRk+fkDBquiq4gPiT898jusgQJAd5Zrr6Q8
+AO/0isr/3aa6O6NLQxISLKcPDk2NOccAfS/xOtfOz4sJYM3+Bs4Io9+dZGSDCA54
+Lw03eHTNQghS0A==
+-----END PRIVATE KEY-----
+"""
+    return cert
diff --git a/auth/auth_systems/openid/__init__.py b/auth/auth_systems/openid/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..d3a0cfc8d0a0a636124277f21d568061ecaf11ec
--- /dev/null
+++ b/auth/auth_systems/openid/__init__.py
@@ -0,0 +1,7 @@
+"""
+OpenID utils
+
+some copied from http://github.com/openid/python-openid/examples/djopenid
+
+ben@adida.net
+"""
diff --git a/auth/auth_systems/openid/util.py b/auth/auth_systems/openid/util.py
new file mode 100644
index 0000000000000000000000000000000000000000..265cedb93f16a0298b42591d4b6655c28fbdccfb
--- /dev/null
+++ b/auth/auth_systems/openid/util.py
@@ -0,0 +1,148 @@
+
+"""
+Utility code for the Django example consumer and server.
+"""
+
+from urlparse import urljoin
+
+from django.db import connection
+from django.template.context import RequestContext
+from django.template import loader
+from django import http
+from django.core.exceptions import ImproperlyConfigured
+from django.core.urlresolvers import reverse as reverseURL
+from django.views.generic.simple import direct_to_template
+
+from django.conf import settings
+
+from openid.store.filestore import FileOpenIDStore
+from openid.store import sqlstore
+from openid.yadis.constants import YADIS_CONTENT_TYPE
+
+def getOpenIDStore(filestore_path, table_prefix):
+    """
+    Returns an OpenID association store object based on the database
+    engine chosen for this Django application.
+
+    * If no database engine is chosen, a filesystem-based store will
+      be used whose path is filestore_path.
+
+    * If a database engine is chosen, a store object for that database
+      type will be returned.
+
+    * If the chosen engine is not supported by the OpenID library,
+      raise ImproperlyConfigured.
+
+    * If a database store is used, this will create the tables
+      necessary to use it.  The table names will be prefixed with
+      table_prefix.  DO NOT use the same table prefix for both an
+      OpenID consumer and an OpenID server in the same database.
+
+    The result of this function should be passed to the Consumer
+    constructor as the store parameter.
+    """
+    if not settings.DATABASE_ENGINE:
+        return FileOpenIDStore(filestore_path)
+
+    # Possible side-effect: create a database connection if one isn't
+    # already open.
+    connection.cursor()
+
+    # Create table names to specify for SQL-backed stores.
+    tablenames = {
+        'associations_table': table_prefix + 'openid_associations',
+        'nonces_table': table_prefix + 'openid_nonces',
+        }
+
+    types = {
+        'postgresql': sqlstore.PostgreSQLStore,
+        'postgresql_psycopg2': sqlstore.PostgreSQLStore,
+        'mysql': sqlstore.MySQLStore,
+        'sqlite3': sqlstore.SQLiteStore,
+        }
+
+    try:
+        s = types[settings.DATABASE_ENGINE](connection.connection,
+                                            **tablenames)
+    except KeyError:
+        raise ImproperlyConfigured, \
+              "Database engine %s not supported by OpenID library" % \
+              (settings.DATABASE_ENGINE,)
+
+    try:
+        s.createTables()
+    except (SystemExit, KeyboardInterrupt, MemoryError), e:
+        raise
+    except:
+        # XXX This is not the Right Way to do this, but because the
+        # underlying database implementation might differ in behavior
+        # at this point, we can't reliably catch the right
+        # exception(s) here.  Ideally, the SQL store in the OpenID
+        # library would catch exceptions that it expects and fail
+        # silently, but that could be bad, too.  More ideally, the SQL
+        # store would not attempt to create tables it knows already
+        # exists.
+        pass
+
+    return s
+
+def getViewURL(req, view_name_or_obj, args=None, kwargs=None):
+    relative_url = reverseURL(view_name_or_obj, args=args, kwargs=kwargs)
+    full_path = req.META.get('SCRIPT_NAME', '') + relative_url
+    return urljoin(getBaseURL(req), full_path)
+
+def getBaseURL(req):
+    """
+    Given a Django web request object, returns the OpenID 'trust root'
+    for that request; namely, the absolute URL to the site root which
+    is serving the Django request.  The trust root will include the
+    proper scheme and authority.  It will lack a port if the port is
+    standard (80, 443).
+    """
+    name = req.META['HTTP_HOST']
+    try:
+        name = name[:name.index(':')]
+    except:
+        pass
+
+    try:
+        port = int(req.META['SERVER_PORT'])
+    except:
+        port = 80
+
+    proto = req.META['SERVER_PROTOCOL']
+
+    if 'HTTPS' in proto:
+        proto = 'https'
+    else:
+        proto = 'http'
+
+    if port in [80, 443] or not port:
+        port = ''
+    else:
+        port = ':%s' % (port,)
+
+    url = "%s://%s%s/" % (proto, name, port)
+    return url
+
+def normalDict(request_data):
+    """
+    Converts a django request MutliValueDict (e.g., request.GET,
+    request.POST) into a standard python dict whose values are the
+    first value from each of the MultiValueDict's value lists.  This
+    avoids the OpenID library's refusal to deal with dicts whose
+    values are lists, because in OpenID, each key in the query arg set
+    can have at most one value.
+    """
+    return dict((k, v[0]) for k, v in request_data.iteritems())
+
+def renderXRDS(request, type_uris, endpoint_urls):
+    """Render an XRDS page with the specified type URIs and endpoint
+    URLs in one service block, and return a response with the
+    appropriate content-type.
+    """
+    response = direct_to_template(
+        request, 'xrds.xml',
+        {'type_uris':type_uris, 'endpoint_urls':endpoint_urls,})
+    response['Content-Type'] = YADIS_CONTENT_TYPE
+    return response
diff --git a/auth/auth_systems/openid/view_helpers.py b/auth/auth_systems/openid/view_helpers.py
new file mode 100644
index 0000000000000000000000000000000000000000..d5ce72cd81290c2541b5c1cd31f7b1f94da911a8
--- /dev/null
+++ b/auth/auth_systems/openid/view_helpers.py
@@ -0,0 +1,160 @@
+
+from django import http
+from django.http import HttpResponseRedirect
+from django.views.generic.simple import direct_to_template
+
+from openid.consumer import consumer
+from openid.consumer.discover import DiscoveryFailure
+from openid.extensions import ax, pape, sreg
+from openid.yadis.constants import YADIS_HEADER_NAME, YADIS_CONTENT_TYPE
+from openid.server.trustroot import RP_RETURN_TO_URL_TYPE
+
+import util
+
+PAPE_POLICIES = [
+    'AUTH_PHISHING_RESISTANT',
+    'AUTH_MULTI_FACTOR',
+    'AUTH_MULTI_FACTOR_PHYSICAL',
+    ]
+
+AX_REQUIRED_FIELDS = {
+    'firstname' : 'http://axschema.org/namePerson/first',
+    'lastname' : 'http://axschema.org/namePerson/last',
+    'fullname' : 'http://axschema.org/namePerson',
+    'email' : 'http://axschema.org/contact/email'
+}
+
+# List of (name, uri) for use in generating the request form.
+POLICY_PAIRS = [(p, getattr(pape, p))
+                for p in PAPE_POLICIES]
+
+def getOpenIDStore():
+    """
+    Return an OpenID store object fit for the currently-chosen
+    database backend, if any.
+    """
+    return util.getOpenIDStore('/tmp/djopenid_c_store', 'c_')
+
+def get_consumer(session):
+    """
+    Get a Consumer object to perform OpenID authentication.
+    """
+    return consumer.Consumer(session, getOpenIDStore())
+
+def start_openid(session, openid_url, trust_root, return_to):
+    """
+    Start the OpenID authentication process.
+
+    * Requests some Simple Registration data using the OpenID
+      library's Simple Registration machinery
+
+    * Generates the appropriate trust root and return URL values for
+      this application (tweak where appropriate)
+
+    * Generates the appropriate redirect based on the OpenID protocol
+      version.
+    """
+
+    # Start OpenID authentication.
+    c = get_consumer(session)
+    error = None
+
+    try:
+        auth_request = c.begin(openid_url)
+    except DiscoveryFailure, e:
+        # Some other protocol-level failure occurred.
+        error = "OpenID discovery error: %s" % (str(e),)
+
+    if error:
+        import pdb; pdb.set_trace()
+        raise Exception("error in openid")
+
+    # Add Simple Registration request information.  Some fields
+    # are optional, some are required.  It's possible that the
+    # server doesn't support sreg or won't return any of the
+    # fields.
+    sreg_request = sreg.SRegRequest(required=['email'],
+                                    optional=[])
+    auth_request.addExtension(sreg_request)
+
+    # Add Attribute Exchange request information.
+    ax_request = ax.FetchRequest()
+    # XXX - uses myOpenID-compatible schema values, which are
+    # not those listed at axschema.org.
+
+    for k, v in AX_REQUIRED_FIELDS.iteritems():
+        ax_request.add(ax.AttrInfo(v, required=True))
+
+    auth_request.addExtension(ax_request)
+                
+    # Compute the trust root and return URL values to build the
+    # redirect information.
+    # trust_root = util.getViewURL(request, startOpenID)
+    # return_to = util.getViewURL(request, finishOpenID)
+
+    # Send the browser to the server either by sending a redirect
+    # URL or by generating a POST form.
+    url = auth_request.redirectURL(trust_root, return_to)
+    return url
+
+def finish_openid(session, request_args, return_to):
+    """
+    Finish the OpenID authentication process.  Invoke the OpenID
+    library with the response from the OpenID server and render a page
+    detailing the result.
+    """
+    result = {}
+
+    # Because the object containing the query parameters is a
+    # MultiValueDict and the OpenID library doesn't allow that, we'll
+    # convert it to a normal dict.
+
+    if request_args:
+        c = get_consumer(session)
+
+        # Get a response object indicating the result of the OpenID
+        # protocol.
+        response = c.complete(request_args, return_to)
+
+        # Get a Simple Registration response object if response
+        # information was included in the OpenID response.
+        sreg_response = {}
+        ax_items = {}
+        if response.status == consumer.SUCCESS:
+            sreg_response = sreg.SRegResponse.fromSuccessResponse(response)
+
+            ax_response = ax.FetchResponse.fromSuccessResponse(response)
+            if ax_response:
+                for k, v in AX_REQUIRED_FIELDS.iteritems():
+                    """
+                    the values are the URIs, they are the key into the data
+                    the key is the shortname
+                    """
+                    if ax_response.data.has_key(v):
+                        ax_items[k] = ax_response.get(v)
+
+        # Map different consumer status codes to template contexts.
+        results = {
+            consumer.CANCEL:
+            {'message': 'OpenID authentication cancelled.'},
+
+            consumer.FAILURE:
+            {'error': 'OpenID authentication failed.'},
+
+            consumer.SUCCESS:
+            {'url': response.getDisplayIdentifier(),
+             'sreg': sreg_response and sreg_response.items(),
+             'ax': ax_items}
+            }
+
+        result = results[response.status]
+
+        if isinstance(response, consumer.FailureResponse):
+            # In a real application, this information should be
+            # written to a log for debugging/tracking OpenID
+            # authentication failures. In general, the messages are
+            # not user-friendly, but intended for developers.
+            result['failure_reason'] = response.message
+
+    return result
+
diff --git a/auth/auth_systems/password.py b/auth/auth_systems/password.py
new file mode 100644
index 0000000000000000000000000000000000000000..744012f4e49984241c28007c5edc7edfe9a12408
--- /dev/null
+++ b/auth/auth_systems/password.py
@@ -0,0 +1,116 @@
+"""
+Username/Password Authentication
+"""
+
+from django.core.urlresolvers import reverse
+from django import forms
+from django.core.mail import send_mail
+from django.conf import settings
+from django.http import HttpResponseRedirect
+
+import logging
+
+# some parameters to indicate that status updating is possible
+STATUS_UPDATES = False
+
+
+def create_user(username, password, name = None):
+  from auth.models import User
+  
+  user = User.get_by_type_and_id('password', username)
+  if user:
+    raise Exception('user exists')
+  
+  info = {'password' : password, 'name': name}
+  user = User.update_or_create(user_type='password', user_id=username, info = info)
+  user.save()
+
+class LoginForm(forms.Form):
+  username = forms.CharField(max_length=50)
+  password = forms.CharField(widget=forms.PasswordInput(), max_length=100)
+
+def password_check(user, password):
+  return (user and user.info['password'] == password)
+  
+# the view for logging in
+def password_login_view(request):
+  from auth.view_utils import render_template
+  from auth.views import after
+  from auth.models import User
+
+  error = None
+  
+  if request.method == "GET":
+    form = LoginForm()
+  else:
+    form = LoginForm(request.POST)
+
+    if form.is_valid():
+      username = form.cleaned_data['username'].strip()
+      password = form.cleaned_data['password'].strip()
+      try:
+        user = User.get_by_type_and_id('password', username)
+        if password_check(user, password):
+          # set this in case we came here from another location than 
+          # the normal login process
+          request.session['auth_system_name'] = 'password'
+          if request.POST.has_key('return_url'):
+            request.session['auth_return_url'] = request.POST.get('return_url')
+
+          request.session['user'] = user
+          return HttpResponseRedirect(reverse(after))
+      except User.DoesNotExist:
+        pass
+      error = 'Bad Username or Password'
+  
+  return render_template(request, 'password/login', {'form': form, 'error': error})
+    
+def password_forgotten_view(request):
+  """
+  forgotten password view and submit.
+  includes return_url
+  """
+  from auth.view_utils import render_template
+  from auth.models import User
+
+  if request.method == "GET":
+    return render_template(request, 'password/forgot', {'return_url': request.GET.get('return_url', '')})
+  else:
+    username = request.POST['username']
+    return_url = request.POST['return_url']
+    
+    user = User.get_by_type_and_id('password', username)
+    
+    body = """
+
+This is a password reminder:
+
+Your username: %s
+Your password: %s
+
+--
+%s
+""" % (user.user_id, user.info['password'], settings.SITE_TITLE)
+
+    # FIXME: make this a task
+    send_mail('password reminder', body, settings.SERVER_EMAIL, ["%s <%s>" % (user.info['name'], user.info['email'])], fail_silently=False)
+    
+    return HttpResponseRedirect(return_url)
+  
+def get_auth_url(request, redirect_url = None):
+  return reverse(password_login_view)
+    
+def get_user_info_after_auth(request):
+  user = request.session['user']
+  user_info = user.info
+  
+  return {'type': 'password', 'user_id' : user.user_id, 'name': user.name, 'info': user.info, 'token': None}
+    
+def update_status(token, message):
+  pass
+  
+def send_message(user_id, user_name, user_info, subject, body):
+  if user_info.has_key('email'):
+    email = user_info['email']
+    name = user_info.get('name', email)
+    send_mail(subject, body, settings.SERVER_EMAIL, ["%s <%s>" % (name, email)], fail_silently=False)    
diff --git a/auth/auth_systems/twitter.py b/auth/auth_systems/twitter.py
new file mode 100644
index 0000000000000000000000000000000000000000..73f8bfe21b623025c89eeeebd1991d098b0d7ec6
--- /dev/null
+++ b/auth/auth_systems/twitter.py
@@ -0,0 +1,109 @@
+"""
+Twitter Authentication
+"""
+
+from oauthclient import client
+
+from django.core.urlresolvers import reverse
+from django.http import HttpResponseRedirect
+
+from auth import utils
+
+import logging
+
+from django.conf import settings
+API_KEY = settings.TWITTER_API_KEY
+API_SECRET = settings.TWITTER_API_SECRET
+USER_TO_FOLLOW = settings.TWITTER_USER_TO_FOLLOW
+REASON_TO_FOLLOW = settings.TWITTER_REASON_TO_FOLLOW
+DM_TOKEN = settings.TWITTER_DM_TOKEN
+
+# some parameters to indicate that status updating is possible
+STATUS_UPDATES = True
+STATUS_UPDATE_WORDING_TEMPLATE = "Tweet %s"
+
+def _get_new_client(token=None, token_secret=None):
+  if token:
+    return client.TwitterOAuthClient(API_KEY, API_SECRET, token, token_secret)
+  else:
+    return client.TwitterOAuthClient(API_KEY, API_SECRET)
+
+def _get_client_by_token(token):
+  return _get_new_client(token['oauth_token'], token['oauth_token_secret'])
+
+def get_auth_url(request, redirect_url):
+  client = _get_new_client()
+  try:
+    tok = client.get_request_token()
+  except:
+    return None
+  
+  request.session['request_token'] = tok
+  url = client.get_authenticate_url(tok['oauth_token']) 
+  return url
+    
+def get_user_info_after_auth(request):
+  tok = request.session['request_token']
+  twitter_client = _get_client_by_token(tok)
+  access_token = twitter_client.get_access_token()
+  request.session['access_token'] = access_token
+    
+  user_info = utils.from_json(twitter_client.oauth_request('http://api.twitter.com/1/account/verify_credentials.json', args={}, method='GET'))
+  
+  return {'type': 'twitter', 'user_id' : user_info['screen_name'], 'name': user_info['name'], 'info': user_info, 'token': access_token}
+    
+
+def user_needs_intervention(user_id, user_info, token):
+  """
+  check to see if user is following the users we need
+  """
+  twitter_client = _get_client_by_token(token)
+  friendship = utils.from_json(twitter_client.oauth_request('http://api.twitter.com/1/friendships/exists.json', args={'user_a': user_id, 'user_b': USER_TO_FOLLOW}, method='GET'))
+  if friendship:
+    return None
+
+  return HttpResponseRedirect(reverse(follow_view))
+
+def _get_client_by_request(request):
+  access_token = request.session['access_token']
+  return _get_client_by_token(access_token)
+  
+def update_status(user_id, user_info, token, message):
+  """
+  post a message to the auth system's update stream, e.g. twitter stream
+  """
+  twitter_client = _get_client_by_token(token)
+  result = twitter_client.oauth_request('http://api.twitter.com/1/statuses/update.json', args={'status': message}, method='POST')
+
+def send_message(user_id, user_name, user_info, subject, body):
+  pass
+
+def send_notification(user_id, user_info, message):
+  twitter_client = _get_client_by_token(DM_TOKEN)
+  result = twitter_client.oauth_request('http://api.twitter.com/1/direct_messages/new.json', args={'screen_name': user_id, 'text': message}, method='POST')
+
+##
+## views
+##
+
+def follow_view(request):
+  if request.method == "GET":
+    from auth.view_utils import render_template
+    from auth.views import after
+    
+    return render_template(request, 'twitter/follow', {'user_to_follow': USER_TO_FOLLOW, 'reason_to_follow' : REASON_TO_FOLLOW})
+
+  if request.method == "POST":
+    follow_p = bool(request.POST.get('follow_p',False))
+    
+    if follow_p:
+      from auth.security import get_user
+
+      user = get_user(request)
+      twitter_client = _get_client_by_token(user.token)
+      result = twitter_client.oauth_request('http://api.twitter.com/1/friendships/create.json', args={'screen_name': USER_TO_FOLLOW}, method='POST')
+
+    from auth.views import after_intervention
+    return HttpResponseRedirect(reverse(after_intervention))
+
+
diff --git a/auth/auth_systems/yahoo.py b/auth/auth_systems/yahoo.py
new file mode 100644
index 0000000000000000000000000000000000000000..48dd2464ddd7c0b57da09b839cd4983bd3c15cdc
--- /dev/null
+++ b/auth/auth_systems/yahoo.py
@@ -0,0 +1,54 @@
+"""
+Yahoo Authentication
+
+"""
+
+from django.http import *
+from django.core.mail import send_mail
+from django.conf import settings
+
+import sys, os, cgi, urllib, urllib2, re
+from xml.etree import ElementTree
+
+from openid import view_helpers
+
+# some parameters to indicate that status updating is not possible
+STATUS_UPDATES = False
+
+# display tweaks
+LOGIN_MESSAGE = "Log in with my Yahoo Account"
+OPENID_ENDPOINT = 'yahoo.com'
+
+def get_auth_url(request, redirect_url):
+  request.session['yahoo_redirect_url'] = redirect_url
+  url = view_helpers.start_openid(request.session, OPENID_ENDPOINT, redirect_url, redirect_url)
+  return url
+
+def get_user_info_after_auth(request):
+  data = view_helpers.finish_openid(request.session, request.GET, request.session['yahoo_redirect_url'])
+
+  return {'type' : 'yahoo', 'user_id': data['ax']['email'][0], 'name': data['ax']['fullname'][0], 'info': {}, 'token':{}}
+    
+def do_logout(user):
+  """
+  logout of Yahoo
+  """
+  return None
+  
+def update_status(token, message):
+  """
+  simple update
+  """
+  pass
+
+def send_message(user_id, user_name, user_info, subject, body):
+  """
+  send email to yahoo user, user_id is email for yahoo and other openID logins.
+  """
+  send_mail(subject, body, settings.SERVER_EMAIL, ["%s <%s>" % (user_name, user_id)], fail_silently=False)
+  
+def check_constraint(constraint, user_info):
+  """
+  for eligibility
+  """
+  pass
diff --git a/auth/bbtests.py b/auth/bbtests.py
new file mode 100644
index 0000000000000000000000000000000000000000..5c3da268257764389155da838d04506cc62282a1
--- /dev/null
+++ b/auth/bbtests.py
@@ -0,0 +1,5 @@
+"""
+Black-box tests for Auth Systems
+
+2010-08-11
+"""
diff --git a/auth/jsonfield.py b/auth/jsonfield.py
new file mode 100644
index 0000000000000000000000000000000000000000..54311acd90e769779d42d589d5df18029bba9806
--- /dev/null
+++ b/auth/jsonfield.py
@@ -0,0 +1,55 @@
+"""
+taken from
+
+http://www.djangosnippets.org/snippets/377/
+"""
+
+import datetime
+from django.db import models
+from django.db.models import signals
+from django.conf import settings
+from django.utils import simplejson as json
+from django.core.serializers.json import DjangoJSONEncoder
+
+class JSONField(models.TextField):
+    """JSONField is a generic textfield that neatly serializes/unserializes
+    JSON objects seamlessly"""
+
+    # Used so to_python() is called
+    __metaclass__ = models.SubfieldBase
+
+    def __init__(self, json_type=None, **kwargs):
+        self.json_type = json_type
+        super(JSONField, self).__init__(**kwargs)
+
+    def to_python(self, value):
+        """Convert our string value to JSON after we load it from the DB"""
+
+        if value == "":
+            return None
+
+        try:
+            if isinstance(value, basestring):
+                parsed_value = json.loads(value)
+                if self.json_type and parsed_value:
+                    parsed_value = self.json_type.fromJSONDict(parsed_value)
+
+                return parsed_value
+        except ValueError:
+            pass
+
+        return value
+
+    def get_db_prep_save(self, value):
+        """Convert our JSON object to a string before we save"""
+
+        if value == "" or value == None:
+            return None
+
+        if value and (self.json_type or hasattr(value, 'toJSONDict')):
+            value = value.toJSONDict()
+
+        # if isinstance(value, dict):
+        value = json.dumps(value, cls=DjangoJSONEncoder)
+
+        return super(JSONField, self).get_db_prep_save(value)
diff --git a/auth/media/login-icons/facebook.png b/auth/media/login-icons/facebook.png
new file mode 100644
index 0000000000000000000000000000000000000000..156bfa141d47d4776a4394884a6b6b5c94774771
Binary files /dev/null and b/auth/media/login-icons/facebook.png differ
diff --git a/auth/media/login-icons/google.png b/auth/media/login-icons/google.png
new file mode 100644
index 0000000000000000000000000000000000000000..f6fbb5aeacd86436b8bf18c7fa0c9d5b9f160331
Binary files /dev/null and b/auth/media/login-icons/google.png differ
diff --git a/auth/media/login-icons/password.png b/auth/media/login-icons/password.png
new file mode 100644
index 0000000000000000000000000000000000000000..86f7807cbcf9f8d5add2c9450a1cf74b30ff1cb6
Binary files /dev/null and b/auth/media/login-icons/password.png differ
diff --git a/auth/media/login-icons/twitter.png b/auth/media/login-icons/twitter.png
new file mode 100644
index 0000000000000000000000000000000000000000..930edf2555535ae8a68896ddfbe6ece9aa4d00eb
Binary files /dev/null and b/auth/media/login-icons/twitter.png differ
diff --git a/auth/media/login-icons/yahoo.png b/auth/media/login-icons/yahoo.png
new file mode 100644
index 0000000000000000000000000000000000000000..cfd329ced6d69e4acf16dd4be651f3b094946405
Binary files /dev/null and b/auth/media/login-icons/yahoo.png differ
diff --git a/auth/media/main.css b/auth/media/main.css
new file mode 100644
index 0000000000000000000000000000000000000000..54140e793681b951a169de216bafe604e8f9a665
--- /dev/null
+++ b/auth/media/main.css
@@ -0,0 +1,55 @@
+
+body {
+  font-family: 'Lucida Grande',sans-serif;
+  background: white;
+  padding: 0px;
+  margin: 0px;
+}
+
+#content {
+  position: absolute;
+  padding: 20px 30px 20px 30px;
+  top: 0px;
+  margin-left: 100px;
+  margin-top: 0px;
+  width: 860px;
+  background: #eee;
+  border-left: 1px solid #666;
+  border-right: 1px solid #666;
+  border-bottom: 1px solid #666;
+}
+
+#header {
+  padding-top: 0px;
+  text-align: center;
+  padding-bottom: 20px;
+}
+
+#footer {
+  border-top: 1px solid #666;
+  bottom: 0px;
+  margin: auto;
+  width: 860px;
+  text-align: center;
+  color: #666;
+  clear: both;
+}
+
+#footer a, #footer a:visited {
+  color: black;
+  text-decoration: none;
+}
+
+#footer a:hover {
+  text-decoration: underline;
+}
+
+#page h2 {
+  background: #fc9;
+  border-bottom: 1px solid #666;
+  padding: 5px 0px 2px 5px;
+}
+
+#election_info {
+  font-size: 16pt;
+}
\ No newline at end of file
diff --git a/auth/media/signin-with-twitter.png b/auth/media/signin-with-twitter.png
new file mode 100644
index 0000000000000000000000000000000000000000..746b6b9f80c71049f2a4eb84ff72d5d1bf77c62c
Binary files /dev/null and b/auth/media/signin-with-twitter.png differ
diff --git a/auth/media/twitter.png b/auth/media/twitter.png
new file mode 100644
index 0000000000000000000000000000000000000000..62cf0036372324603b7fa238692b22d21f5dac26
Binary files /dev/null and b/auth/media/twitter.png differ
diff --git a/auth/models.py b/auth/models.py
new file mode 100644
index 0000000000000000000000000000000000000000..5b39a03f6df4c908409fd44b042add1ca2d66ad7
--- /dev/null
+++ b/auth/models.py
@@ -0,0 +1,147 @@
+"""
+Data Objects for user authentication
+
+GAE
+
+Ben Adida
+(ben@adida.net)
+"""
+
+from django.db import models
+from jsonfield import JSONField
+
+import datetime, logging
+
+from auth_systems import AUTH_SYSTEMS
+
+
+class User(models.Model):
+  user_type = models.CharField(max_length=50)
+  user_id = models.CharField(max_length=100)
+    
+  name = models.CharField(max_length=200, null=True)
+  
+  # other properties
+  info = JSONField()
+  
+  # access token information
+  token = JSONField(null = True)
+  
+  # administrator
+  admin_p = models.BooleanField(default=False)
+
+  class Meta:
+    unique_together = (('user_type', 'user_id'),)
+    
+  @classmethod
+  def _get_type_and_id(cls, user_type, user_id):
+    return "%s:%s" % (user_type, user_id)    
+    
+  @property
+  def type_and_id(self):
+    return self._get_type_and_id(self.user_type, self.user_id)
+    
+  @classmethod
+  def get_by_type_and_id(cls, user_type, user_id):
+    return cls.objects.get(user_type = user_type, user_id = user_id)
+  
+  @classmethod
+  def update_or_create(cls, user_type, user_id, name=None, info=None, token=None):
+    obj, created_p = cls.objects.get_or_create(user_type = user_type, user_id = user_id, defaults = {'name': name, 'info':info, 'token':token})
+    
+    if not created_p:
+      # special case the password: don't replace it if it exists
+      if obj.info.has_key('password'):
+        info['password'] = obj.info['password']
+
+      obj.info = info
+      obj.name = name
+      obj.token = token
+      obj.save()
+
+    return obj
+    
+  def can_update_status(self):
+    if not AUTH_SYSTEMS.has_key(self.user_type):
+      return False
+
+    return AUTH_SYSTEMS[self.user_type].STATUS_UPDATES
+
+  def update_status_template(self):
+    if not self.can_update_status():
+      return None
+
+    return AUTH_SYSTEMS[self.user_type].STATUS_UPDATE_WORDING_TEMPLATE
+
+  def update_status(self, status):
+    if AUTH_SYSTEMS.has_key(self.user_type):
+      AUTH_SYSTEMS[self.user_type].update_status(self.user_id, self.info, self.token, status)
+      
+  def send_message(self, subject, body):
+    if AUTH_SYSTEMS.has_key(self.user_type):
+      subject = subject.split("\n")[0]
+      AUTH_SYSTEMS[self.user_type].send_message(self.user_id, self.name, self.info, subject, body)
+
+  def send_notification(self, message):
+    if AUTH_SYSTEMS.has_key(self.user_type):
+      if hasattr(AUTH_SYSTEMS[self.user_type], 'send_notification'):
+        AUTH_SYSTEMS[self.user_type].send_notification(self.user_id, self.info, message)
+  
+  def is_eligible_for(self, eligibility_case):
+    """
+    Check if this user is eligible for this particular eligibility case, which looks like
+    {'auth_system': 'cas', 'constraint': [{}, {}, {}]}
+    and the constraints are OR'ed together
+    """
+    
+    if eligibility_case['auth_system'] != self.user_type:
+      return False
+      
+    # no constraint? Then eligible!
+    if not eligibility_case.has_key('constraint'):
+      return True
+    
+    # from here on we know we match the auth system, but do we match one of the constraints?  
+
+    auth_system = AUTH_SYSTEMS[self.user_type]
+
+    # does the auth system allow for checking a constraint?
+    if not hasattr(auth_system, 'check_constraint'):
+      return False
+      
+    for constraint in eligibility_case['constraint']:
+      # do we match on this constraint?
+      if auth_system.check_constraint(constraint=constraint, user_info = self.info):
+        return True
+  
+    # no luck
+    return False
+    
+  def __eq__(self, other):
+    if other:
+      return self.type_and_id == other.type_and_id
+    else:
+      return False
+  
+
+  @property
+  def pretty_name(self):
+    if self.name:
+      return self.name
+
+    if self.info.has_key('name'):
+      return self.info['name']
+
+    return self.user_id
+  
+  def _display_html(self, size):
+    return """<img border="0" height="%s" src="/static/auth/login-icons/%s.png" alt="%s" /> %s""" % (
+      str(int(size)), self.user_type, self.user_type, self.pretty_name)
+
+  @property
+  def display_html_small(self):
+    return self._display_html(15)
+
+  @property
+  def display_html_big(self):
+    return self._display_html(25)
diff --git a/auth/security/__init__.py b/auth/security/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..1b3911cf4db0834dbb1cc991cf27607ac3a936d6
--- /dev/null
+++ b/auth/security/__init__.py
@@ -0,0 +1,123 @@
+"""
+Generic Security -- for the auth system
+
+Ben Adida (ben@adida.net)
+"""
+
+# nicely update the wrapper function
+from functools import update_wrapper
+
+from django.http import HttpResponse, Http404, HttpResponseRedirect
+from django.core.exceptions import *
+from django.conf import settings
+
+import oauth
+
+import uuid
+
+from auth.models import *
+
+FIELDS_TO_SAVE = 'FIELDS_TO_SAVE'
+
+## FIXME: oauth is NOT working right now
+
+##
+## OAuth and API clients
+##
+
+class OAuthDataStore(oauth.OAuthDataStore):
+  def __init__(self):
+    pass
+      
+  def lookup_consumer(self, key):
+    c = APIClient.objects.get(consumer_key = key)
+    return oauth.OAuthConsumer(c.consumer_key, c.consumer_secret)
+
+  def lookup_token(self, oauth_consumer, token_type, token):
+    if token_type != 'access':
+      raise NotImplementedError
+
+    c = APIClient.objects.get(consumer_key = oauth_consumer.key)
+    return oauth.OAuthToken(c.consumer_key, c.consumer_secret)
+
+  def lookup_nonce(self, oauth_consumer, oauth_token, nonce):
+    """
+    FIXME this to actually check for nonces
+    """
+    return None
+
+# create the oauth server
+OAUTH_SERVER = oauth.OAuthServer(OAuthDataStore())
+OAUTH_SERVER.add_signature_method(oauth.OAuthSignatureMethod_HMAC_SHA1())
+    
+def get_api_client(request):
+  parameters = request.POST.copy()
+  parameters.update(request.GET)
+  
+  full_url = request.get_full_path()
+    
+  oauth_request = oauth.OAuthRequest.from_request(request.method, full_url, headers= request.META,
+                                                  parameters=parameters, query_string=None)
+                                                  
+  if not oauth_request:
+    return None
+    
+  try:
+    consumer, token, params = OAUTH_SERVER.verify_request(oauth_request)
+    return APIClient.objects.get(consumer_key = consumer.key)
+  except oauth.OAuthError:
+    return None
+  
+# decorator for login required
+def login_required(func):
+  def login_required_wrapper(request, *args, **kw):
+    if not (get_user(request) or get_api_client(request)):
+      return HttpResponseRedirect(settings.LOGIN_URL + "?return_url=" + request.get_full_path())
+  
+    return func(request, *args, **kw)
+
+  return update_wrapper(login_required_wrapper, func)
+  
+# decorator for admin required
+def admin_required(func):
+  def admin_required_wrapper(request, *args, **kw):
+    user = get_user(request)
+    if not user or not user.is_staff:
+      raise PermissionDenied()
+      
+    return func(request, *args, **kw)
+
+  return update_wrapper(admin_required_wrapper, func)
+
+# get the user
+def get_user(request):
+  # push the expiration of the session back
+  request.session.set_expiry(settings.SESSION_COOKIE_AGE)
+  
+  # set up CSRF protection if needed
+  if not request.session.has_key('csrf_token') or type(request.session['csrf_token']) != str:
+    request.session['csrf_token'] = str(uuid.uuid4())
+
+  if request.session.has_key('user'):
+    user = request.session['user']
+
+    # find the user
+    user_obj = User.get_by_type_and_id(user['type'], user['user_id'])
+    return user_obj
+  else:
+    return None  
+
+def check_csrf(request):
+  if request.method != "POST":
+    return HttpResponseNotAllowed("only a POST for this URL")
+    
+  if (not request.POST.has_key('csrf_token')) or (request.POST['csrf_token'] != request.session['csrf_token']):
+    raise Exception("A CSRF problem was detected")
+
+def save_in_session_across_logouts(request, field_name, field_value):
+  fields_to_save = request.session.get(FIELDS_TO_SAVE, [])
+  if field_name not in fields_to_save:
+    fields_to_save.append(field_name)
+    request.session[FIELDS_TO_SAVE] = fields_to_save
+
+  request.session[field_name] = field_value
diff --git a/auth/security/oauth.py b/auth/security/oauth.py
new file mode 100644
index 0000000000000000000000000000000000000000..04cc4bb2ba294c9f50a1eb91fe9ba9ef3e3aaa15
--- /dev/null
+++ b/auth/security/oauth.py
@@ -0,0 +1,539 @@
+"""
+Initially downlaoded from
+http://oauth.googlecode.com/svn/code/python/oauth/
+
+Hacked a bit by Ben Adida (ben@adida.net) so that:
+- access tokens are looked up with an extra param of consumer
+"""
+
+import cgi
+import urllib
+import time
+import random
+import urlparse
+import hmac
+import base64
+import logging
+
+VERSION = '1.0' # Hi Blaine!
+HTTP_METHOD = 'GET'
+SIGNATURE_METHOD = 'PLAINTEXT'
+
+# Generic exception class
+class OAuthError(RuntimeError):
+    def __init__(self, message='OAuth error occured.'):
+        self.message = message
+
+# optional WWW-Authenticate header (401 error)
+def build_authenticate_header(realm=''):
+    return {'WWW-Authenticate': 'OAuth realm="%s"' % realm}
+
+# url escape
+def escape(s):
+    # escape '/' too
+    return urllib.quote(s, safe='~')
+
+# util function: current timestamp
+# seconds since epoch (UTC)
+def generate_timestamp():
+    return int(time.time())
+
+# util function: nonce
+# pseudorandom number
+def generate_nonce(length=8):
+    return ''.join(str(random.randint(0, 9)) for i in range(length))
+
+# OAuthConsumer is a data type that represents the identity of the Consumer
+# via its shared secret with the Service Provider.
+class OAuthConsumer(object):
+    key = None
+    secret = None
+
+    def __init__(self, key, secret):
+        self.key = key
+        self.secret = secret
+
+# OAuthToken is a data type that represents an End User via either an access
+# or request token.     
+class OAuthToken(object):
+    # access tokens and request tokens
+    key = None
+    secret = None
+
+    '''
+    key = the token
+    secret = the token secret
+    '''
+    def __init__(self, key, secret):
+        self.key = key
+        self.secret = secret
+
+    def to_string(self):
+        return urllib.urlencode({'oauth_token': self.key, 'oauth_token_secret': self.secret})
+
+    # return a token from something like:
+    # oauth_token_secret=digg&oauth_token=digg
+    @staticmethod   
+    def from_string(s):
+        params = cgi.parse_qs(s, keep_blank_values=False)
+        key = params['oauth_token'][0]
+        secret = params['oauth_token_secret'][0]
+        return OAuthToken(key, secret)
+
+    def __str__(self):
+        return self.to_string()
+
+# OAuthRequest represents the request and can be serialized
+class OAuthRequest(object):
+    '''
+    OAuth parameters:
+        - oauth_consumer_key 
+        - oauth_token
+        - oauth_signature_method
+        - oauth_signature 
+        - oauth_timestamp 
+        - oauth_nonce
+        - oauth_version
+        ... any additional parameters, as defined by the Service Provider.
+    '''
+    parameters = None # oauth parameters
+    http_method = HTTP_METHOD
+    http_url = None
+    version = VERSION
+    
+    # added by Ben to filter out extra params from header
+    OAUTH_PARAMS = ['oauth_consumer_key', 'oauth_token', 'oauth_signature_method', 'oauth_signature', 'oauth_timestamp', 'oauth_nonce', 'oauth_version']
+
+    def __init__(self, http_method=HTTP_METHOD, http_url=None, parameters=None):
+        self.http_method = http_method
+        self.http_url = http_url
+        self.parameters = parameters or {}
+
+    def set_parameter(self, parameter, value):
+        self.parameters[parameter] = value
+
+    def get_parameter(self, parameter):
+        try:
+            return self.parameters[parameter]
+        except:
+            raise OAuthError('Parameter not found: %s' % parameter)
+
+    def _get_timestamp_nonce(self):
+        return self.get_parameter('oauth_timestamp'), self.get_parameter('oauth_nonce')
+
+    # get any non-oauth parameters
+    def get_nonoauth_parameters(self):
+        parameters = {}
+        for k, v in self.parameters.iteritems():
+            # ignore oauth parameters
+            if k.find('oauth_') < 0:
+                parameters[k] = v
+        return parameters
+
+    # serialize as a header for an HTTPAuth request
+    def to_header(self, realm=''):
+        auth_header = 'OAuth realm="%s"' % realm
+        # add the oauth parameters
+        if self.parameters:
+            for k, v in self.parameters.iteritems():
+              # only if it's a standard OAUTH param (Ben)
+              if k in self.OAUTH_PARAMS:
+                auth_header += ', %s="%s"' % (k, escape(str(v)))
+        return {'Authorization': auth_header}
+
+    # serialize as post data for a POST request
+    def to_postdata(self):
+        return '&'.join('%s=%s' % (escape(str(k)), escape(str(v))) for k, v in self.parameters.iteritems())
+
+    # serialize as a url for a GET request
+    def to_url(self):
+        return '%s?%s' % (self.get_normalized_http_url(), self.to_postdata())
+
+    # return a string that consists of all the parameters that need to be signed
+    def get_normalized_parameters(self):
+        params = self.parameters
+        try:
+            # exclude the signature if it exists
+            del params['oauth_signature']
+        except:
+            pass
+        key_values = params.items()
+        # sort lexicographically, first after key, then after value
+        key_values.sort()
+        # combine key value pairs in string and escape
+        return '&'.join('%s=%s' % (escape(str(k)), escape(str(v))) for k, v in key_values)
+
+    # just uppercases the http method
+    def get_normalized_http_method(self):
+        return self.http_method.upper()
+
+    # parses the url and rebuilds it to be scheme://host/path
+    def get_normalized_http_url(self):
+        parts = urlparse.urlparse(self.http_url)
+        url_string = '%s://%s%s' % (parts[0], parts[1], parts[2]) # scheme, netloc, path
+        return url_string
+        
+    # set the signature parameter to the result of build_signature
+    def sign_request(self, signature_method, consumer, token):
+        # set the signature method
+        self.set_parameter('oauth_signature_method', signature_method.get_name())
+        # set the signature
+        self.set_parameter('oauth_signature', self.build_signature(signature_method, consumer, token))
+
+    def build_signature(self, signature_method, consumer, token):
+        # call the build signature method within the signature method
+        return signature_method.build_signature(self, consumer, token)
+
+    @staticmethod
+    def from_request(http_method, http_url, headers=None, parameters=None, query_string=None):
+        # combine multiple parameter sources
+        if parameters is None:
+            parameters = {}
+
+        # headers
+        if headers and 'HTTP_AUTHORIZATION' in headers:
+            auth_header = headers['HTTP_AUTHORIZATION']
+            # check that the authorization header is OAuth
+            if auth_header.index('OAuth') > -1:
+                try:
+                    # get the parameters from the header
+                    header_params = OAuthRequest._split_header(auth_header)
+                    parameters.update(header_params)
+                except:
+                    raise OAuthError('Unable to parse OAuth parameters from Authorization header.')
+
+        # GET or POST query string
+        if query_string:
+            query_params = OAuthRequest._split_url_string(query_string)
+            parameters.update(query_params)
+
+        # URL parameters
+        param_str = urlparse.urlparse(http_url)[4] # query
+        url_params = OAuthRequest._split_url_string(param_str)
+        parameters.update(url_params)
+
+        if parameters:
+            return OAuthRequest(http_method, http_url, parameters)
+
+        return None
+
+    @staticmethod
+    def from_consumer_and_token(oauth_consumer, token=None, http_method=HTTP_METHOD, http_url=None, parameters=None):
+        if not parameters:
+            parameters = {}
+
+        defaults = {
+            'oauth_consumer_key': oauth_consumer.key,
+            'oauth_timestamp': generate_timestamp(),
+            'oauth_nonce': generate_nonce(),
+            'oauth_version': OAuthRequest.version,
+        }
+
+        defaults.update(parameters)
+        parameters = defaults
+
+        if token:
+            parameters['oauth_token'] = token.key
+
+        return OAuthRequest(http_method, http_url, parameters)
+
+    @staticmethod
+    def from_token_and_callback(token, callback=None, http_method=HTTP_METHOD, http_url=None, parameters=None):
+        if not parameters:
+            parameters = {}
+
+        parameters['oauth_token'] = token.key
+
+        if callback:
+            parameters['oauth_callback'] = escape(callback)
+
+        return OAuthRequest(http_method, http_url, parameters)
+
+    # util function: turn Authorization: header into parameters, has to do some unescaping
+    @staticmethod
+    def _split_header(header):
+        params = {}
+        parts = header.split(',')
+        for param in parts:
+            # ignore realm parameter
+            if param.find('OAuth realm') > -1:
+                continue
+            # remove whitespace
+            param = param.strip()
+            # split key-value
+            param_parts = param.split('=', 1)
+            # remove quotes and unescape the value
+            params[param_parts[0]] = urllib.unquote(param_parts[1].strip('\"'))
+        return params
+    
+    # util function: turn url string into parameters, has to do some unescaping
+    @staticmethod
+    def _split_url_string(param_str):
+        parameters = cgi.parse_qs(param_str, keep_blank_values=False)
+        for k, v in parameters.iteritems():
+            parameters[k] = urllib.unquote(v[0])
+        return parameters
+
+# OAuthServer is a worker to check a requests validity against a data store
+class OAuthServer(object):
+    timestamp_threshold = 300 # in seconds, five minutes
+    version = VERSION
+    signature_methods = None
+    data_store = None
+
+    def __init__(self, data_store=None, signature_methods=None):
+        self.data_store = data_store
+        self.signature_methods = signature_methods or {}
+
+    def set_data_store(self, oauth_data_store):
+        self.data_store = data_store
+
+    def get_data_store(self):
+        return self.data_store
+
+    def add_signature_method(self, signature_method):
+        self.signature_methods[signature_method.get_name()] = signature_method
+        return self.signature_methods
+
+    # process a request_token request
+    # returns the request token on success
+    def fetch_request_token(self, oauth_request):
+        try:
+            # get the request token for authorization
+            token = self._get_token(oauth_request, 'request')
+        except OAuthError:
+            # no token required for the initial token request
+            version = self._get_version(oauth_request)
+            consumer = self._get_consumer(oauth_request)
+            self._check_signature(oauth_request, consumer, None)
+            # fetch a new token
+            token = self.data_store.fetch_request_token(consumer)
+        return token
+
+    # process an access_token request
+    # returns the access token on success
+    def fetch_access_token(self, oauth_request):
+        version = self._get_version(oauth_request)
+        consumer = self._get_consumer(oauth_request)
+        # get the request token
+        token = self._get_token(oauth_request, 'request')
+        self._check_signature(oauth_request, consumer, token)
+        new_token = self.data_store.fetch_access_token(consumer, token)
+        return new_token
+
+    # verify an api call, checks all the parameters
+    def verify_request(self, oauth_request):
+        # -> consumer and token
+        version = self._get_version(oauth_request)
+        consumer = self._get_consumer(oauth_request)
+        # get the access token
+        token = self._get_token(oauth_request, consumer, 'access')
+        self._check_signature(oauth_request, consumer, token)
+        parameters = oauth_request.get_nonoauth_parameters()
+        return consumer, token, parameters
+
+    # authorize a request token
+    def authorize_token(self, token, user):
+        return self.data_store.authorize_request_token(token, user)
+    
+    # get the callback url
+    def get_callback(self, oauth_request):
+        return oauth_request.get_parameter('oauth_callback')
+
+    # optional support for the authenticate header   
+    def build_authenticate_header(self, realm=''):
+        return {'WWW-Authenticate': 'OAuth realm="%s"' % realm}
+
+    # verify the correct version request for this server
+    def _get_version(self, oauth_request):
+        try:
+            version = oauth_request.get_parameter('oauth_version')
+        except:
+            version = VERSION
+        if version and version != self.version:
+            raise OAuthError('OAuth version %s not supported.' % str(version))
+        return version
+
+    # figure out the signature with some defaults
+    def _get_signature_method(self, oauth_request):
+        try:
+            signature_method = oauth_request.get_parameter('oauth_signature_method')
+        except:
+            signature_method = SIGNATURE_METHOD
+        try:
+            # get the signature method object
+            signature_method = self.signature_methods[signature_method]
+        except:
+            signature_method_names = ', '.join(self.signature_methods.keys())
+            raise OAuthError('Signature method %s not supported try one of the following: %s' % (signature_method, signature_method_names))
+
+        return signature_method
+
+    def _get_consumer(self, oauth_request):
+        consumer_key = oauth_request.get_parameter('oauth_consumer_key')
+        if not consumer_key:
+            raise OAuthError('Invalid consumer key.')
+        consumer = self.data_store.lookup_consumer(consumer_key)
+        if not consumer:
+            raise OAuthError('Invalid consumer.')
+        return consumer
+
+    # try to find the token for the provided request token key
+    def _get_token(self, oauth_request, consumer, token_type='access'):
+        token_field = oauth_request.get_parameter('oauth_token')
+        token = self.data_store.lookup_token(consumer, token_type, token_field)
+        if not token:
+            raise OAuthError('Invalid %s token: %s' % (token_type, token_field))
+        return token
+
+    def _check_signature(self, oauth_request, consumer, token):
+        timestamp, nonce = oauth_request._get_timestamp_nonce()
+        self._check_timestamp(timestamp)
+        self._check_nonce(consumer, token, nonce)
+        signature_method = self._get_signature_method(oauth_request)
+        try:
+            signature = oauth_request.get_parameter('oauth_signature')
+        except:
+            raise OAuthError('Missing signature.')
+        # validate the signature
+        valid_sig = signature_method.check_signature(oauth_request, consumer, token, signature)
+        if not valid_sig:
+            key, base = signature_method.build_signature_base_string(oauth_request, consumer, token)
+            raise OAuthError('Invalid signature. Expected signature base string: %s' % base)
+        built = signature_method.build_signature(oauth_request, consumer, token)
+
+    def _check_timestamp(self, timestamp):
+        # verify that timestamp is recentish
+        timestamp = int(timestamp)
+        now = int(time.time())
+        lapsed = now - timestamp
+        if lapsed > self.timestamp_threshold:
+            raise OAuthError('Expired timestamp: given %d and now %s has a greater difference than threshold %d' % (timestamp, now, self.timestamp_threshold))
+
+    def _check_nonce(self, consumer, token, nonce):
+        # verify that the nonce is uniqueish
+        nonce = self.data_store.lookup_nonce(consumer, token, nonce)
+        if nonce:
+            raise OAuthError('Nonce already used: %s' % str(nonce))
+
+# OAuthClient is a worker to attempt to execute a request
+class OAuthClient(object):
+    consumer = None
+    token = None
+
+    def __init__(self, oauth_consumer, oauth_token):
+        self.consumer = oauth_consumer
+        self.token = oauth_token
+
+    def get_consumer(self):
+        return self.consumer
+
+    def get_token(self):
+        return self.token
+
+    def fetch_request_token(self, oauth_request):
+        # -> OAuthToken
+        raise NotImplementedError
+
+    def fetch_access_token(self, oauth_request):
+        # -> OAuthToken
+        raise NotImplementedError
+
+    def access_resource(self, oauth_request):
+        # -> some protected resource
+        raise NotImplementedError
+
+# OAuthDataStore is a database abstraction used to lookup consumers and tokens
+class OAuthDataStore(object):
+
+    def lookup_consumer(self, key):
+        # -> OAuthConsumer
+        raise NotImplementedError
+
+    def lookup_token(self, oauth_consumer, token_type, token_token):
+        # -> OAuthToken
+        raise NotImplementedError
+
+    def lookup_nonce(self, oauth_consumer, oauth_token, nonce, timestamp):
+        # -> OAuthToken
+        raise NotImplementedError
+
+    def fetch_request_token(self, oauth_consumer):
+        # -> OAuthToken
+        raise NotImplementedError
+
+    def fetch_access_token(self, oauth_consumer, oauth_token):
+        # -> OAuthToken
+        raise NotImplementedError
+
+    def authorize_request_token(self, oauth_token, user):
+        # -> OAuthToken
+        raise NotImplementedError
+
+# OAuthSignatureMethod is a strategy class that implements a signature method
+class OAuthSignatureMethod(object):
+    def get_name(self):
+        # -> str
+        raise NotImplementedError
+
+    def build_signature_base_string(self, oauth_request, oauth_consumer, oauth_token):
+        # -> str key, str raw
+        raise NotImplementedError
+
+    def build_signature(self, oauth_request, oauth_consumer, oauth_token):
+        # -> str
+        raise NotImplementedError
+
+    def check_signature(self, oauth_request, consumer, token, signature):
+        built = self.build_signature(oauth_request, consumer, token)
+        logging.info("built %s" % built)
+        logging.info("signature %s" % signature)
+        return built == signature
+
+class OAuthSignatureMethod_HMAC_SHA1(OAuthSignatureMethod):
+
+    def get_name(self):
+        return 'HMAC-SHA1'
+        
+    def build_signature_base_string(self, oauth_request, consumer, token):
+        sig = (
+            escape(oauth_request.get_normalized_http_method()),
+            escape(oauth_request.get_normalized_http_url()),
+            escape(oauth_request.get_normalized_parameters()),
+        )
+
+        key = '%s&' % escape(consumer.secret)
+        if token:
+            key += escape(token.secret)
+        raw = '&'.join(sig)
+        return key, raw
+
+    def build_signature(self, oauth_request, consumer, token):
+        # build the base signature string
+        key, raw = self.build_signature_base_string(oauth_request, consumer, token)
+
+        # hmac object
+        try:
+            import hashlib # 2.5
+            hashed = hmac.new(key, raw, hashlib.sha1)
+        except:
+            import sha # deprecated
+            hashed = hmac.new(key, raw, sha)
+
+        # calculate the digest base 64
+        return base64.b64encode(hashed.digest())
+
+class OAuthSignatureMethod_PLAINTEXT(OAuthSignatureMethod):
+
+    def get_name(self):
+        return 'PLAINTEXT'
+
+    def build_signature_base_string(self, oauth_request, consumer, token):
+        # concatenate the consumer key and secret
+        sig = escape(consumer.secret) + '&'
+        if token:
+            sig = sig + escape(token.secret)
+        return sig
+
+    def build_signature(self, oauth_request, consumer, token):
+        return self.build_signature_base_string(oauth_request, consumer, token)
diff --git a/auth/templates/base.html b/auth/templates/base.html
new file mode 100644
index 0000000000000000000000000000000000000000..cd838d1cc5ef96312762a9c3cf5ad2a4d7e00bf1
--- /dev/null
+++ b/auth/templates/base.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html 
+     PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml"
+    dir="{% if LANGUAGE_BIDI %}rtl{% else %}ltr{% endif %}"
+    xml:lang="{% firstof LANGUAGE_CODE 'en' %}"
+    lang="{% firstof LANGUAGE_CODE 'en' %}">
+  <head>
+    <title>{% block title %}Authentication{% endblock %} - IACR</title>
+    {% block css %}
+      <!--
+          <link rel="stylesheet" type="text/css"  media="screen, projection" href="{{ MEDIA_URL }}combined-{% if LANGUAGE_BIDI %}rtl{% else %}ltr{% endif %}.css" />
+      -->
+      <link rel="stylesheet" type="text/css" media="screen" href="/static/main.css">
+
+      <!--[if IE]>
+        <link rel="stylesheet" type="text/css" media="screen, projection" href="{{ MEDIA_URL }}ie.css">
+      <![endif]-->
+    {% endblock %}
+
+    {% block js %}
+    {% endblock %}
+
+    {% block extra-head %}{% endblock %}
+  </head>
+
+  <body>
+    <div id="content">
+        <div id="header">
+        {% block header %}
+        {% endblock %}
+        </div>
+      {% block content %}{% endblock %}
+      <div id="footer">
+      </div>
+    </div>
+    
+  </body>
+</html>
diff --git a/auth/templates/index.html b/auth/templates/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..3af9a06542be6ab3ce4c432b8e35058c0b215307
--- /dev/null
+++ b/auth/templates/index.html
@@ -0,0 +1,17 @@
+{% extends TEMPLATE_BASE %}
+
+{% block content %}
+<h1>Authentication</h1>
+
+{% if user %}
+<p style="font-size: 1.4em;">
+    You are currently logged in as<br /><b>{{user.user_id}}</b> via <b>{{user.user_type}}</b>.
+</p>
+<p>
+    <a href="{% url auth.views.logout %}">logout</a>
+</p>
+
+{% else %}
+{% include "login_box.html" %}
+{% endif %}
+{% endblock %}
diff --git a/auth/templates/login_box.html b/auth/templates/login_box.html
new file mode 100644
index 0000000000000000000000000000000000000000..4fb7a6eba40ef78e3b8ce7fa5778333b1bedd14d
--- /dev/null
+++ b/auth/templates/login_box.html
@@ -0,0 +1,26 @@
+{% if default_auth_system %}
+<p><a href="{% url auth.views.start system_name=default_auth_system %}?return_url={{return_url}}">
+{{default_auth_system_obj.LOGIN_MESSAGE}}
+</a></p>
+{% else %}
+{% for auth_system in enabled_auth_systems %}
+{% ifequal auth_system "password" %}
+<form method="post" action="{% url auth.auth_systems.password.password_login_view %}">
+<input type="hidden" name="election_uuid" value="{{election.uuid}}" />
+<input type="hidden" name="csrf_token" value="{{csrf_token}}" />
+<input type="hidden" name="return_url" value="{{return_url}}" />
+<table>
+    {{form.as_table}}
+</table>
+<input type="submit" value="log in" />
+<a style="font-size: 0.8em;" href="{% url auth.auth_systems.password.password_forgotten_view %}?return_url={{return_url|urlencode}}">forgot password?</a>
+</form>
+{% else %}
+<p>
+    <a href="{% url auth.views.start system_name=auth_system %}?return_url={{return_url}}" style="font-size: 1.4em;">
+<img border="0" height="35" src="/static/auth/login-icons/{{auth_system}}.png" alt="{{auth_system}}" /> {{auth_system}}
+{% endifequal %}
+</a>
+</p>
+{% endfor %}
+{% endif %}
diff --git a/auth/templates/password/forgot.html b/auth/templates/password/forgot.html
new file mode 100644
index 0000000000000000000000000000000000000000..043a66789411c0aa0cd16923b8dfac3c19054ecb
--- /dev/null
+++ b/auth/templates/password/forgot.html
@@ -0,0 +1,20 @@
+{% extends TEMPLATE_BASE %}
+
+{% block header %}
+<h2>{{ settings.SITE_TITLE }} &mdash; Password Forgotten</h2>
+{% endblock %}
+
+{% block content %}
+
+{% if error %}
+<p style="border: 1px solid black; padding: 5px;">
+    {{error}}
+</p>
+{% endif %}
+
+<form class="prettyform" action="" method="POST">
+    <input type="hidden" name="csrf_token" value="{{csrf_token}}" />
+    <input type="hidden" name="return_url" value="{{return_url}}" />
+Your Username : <input type="text" name="username" /> <input type="submit" value="Send Reminder" />
+</form>
+{% endblock %}
diff --git a/auth/templates/password/login.html b/auth/templates/password/login.html
new file mode 100644
index 0000000000000000000000000000000000000000..9b038893d71d966c6a94a68eff78b0e9e4652a4e
--- /dev/null
+++ b/auth/templates/password/login.html
@@ -0,0 +1,27 @@
+{% extends TEMPLATE_BASE %}
+
+{% block header %}
+<h2>{{ settings.SITE_TITLE }} &mdash; Login</h2>
+{% endblock %}
+
+
+{% block content %}
+
+{% if error %}
+<p style="border: 1px solid black; padding: 5px;">
+    {{error}}
+</p>
+{% endif %}
+
+<p>
+<form class="prettyform" action="" method="POST" id="create_election_form">
+    <input type="hidden" name="csrf_token" value="{{csrf_token}}" />
+<table>
+    {{form.as_table}}
+</table>
+<div>
+<label for="">&nbsp;</label><input type="submit" value="Login" />
+</div>
+</form>
+</p>
+{% endblock %}
diff --git a/auth/templates/twitter/follow.html b/auth/templates/twitter/follow.html
new file mode 100644
index 0000000000000000000000000000000000000000..bbda5da3db04fc18f9e2d26986e0e9dcafa2991e
--- /dev/null
+++ b/auth/templates/twitter/follow.html
@@ -0,0 +1,25 @@
+{% extends TEMPLATE_BASE %}
+
+{% block header %}
+<h2>{{ settings.SITE_TITLE }} &mdash; Twitter Follow?</h2>
+{% endblock %}
+
+
+{% block content %}
+
+<div style="font-size: 1.2em;">
+We recommend that you follow <b>@{{user_to_follow}}</b>.<br /><br />
+That way, {{reason_to_follow}}. We will not abuse of this friendship: only messages pertinent to you will be sent to you directly, and general updates happen only sporadically.
+</div>
+<br /><br />
+<form method="post" action="">
+<input type="hidden" name="csrf_token" value="{{csrf_token}}" />
+<span style="font-size: 1.3em;">
+<input type="checkbox" name="follow_p" value="1" checked>
+follow @{{user_to_follow}}
+<br /><br />
+<input type="submit" value="continue" />
+</span>
+</form>
+
+{% endblock %}
diff --git a/auth/tests.py b/auth/tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..072e0701241a53ea6cf85d83c80bb316dbd082e9
--- /dev/null
+++ b/auth/tests.py
@@ -0,0 +1,74 @@
+"""
+Unit Tests for Auth Systems
+"""
+
+import unittest
+import models
+
+from django.db import IntegrityError, transaction
+
+from auth_systems import AUTH_SYSTEMS
+
+class UserTests(unittest.TestCase):
+
+    def setUp(self):
+        pass
+
+    def test_unique_users(self):
+        """
+        there should not be two users with the same user_type and user_id
+        """
+        for auth_system, auth_system_module in AUTH_SYSTEMS.iteritems():
+            models.User.objects.create(user_type = auth_system, user_id = 'foobar', info={'name':'Foo Bar'})
+            
+            def double_insert():
+                models.User.objects.create(user_type = auth_system, user_id = 'foobar', info={'name': 'Foo2 Bar'})
+                
+            self.assertRaises(IntegrityError, double_insert)
+            transaction.rollback()
+
+    def test_create_or_update(self):
+        """
+        shouldn't create two users, and should reset the password
+        """
+        for auth_system, auth_system_module in AUTH_SYSTEMS.iteritems():
+            u = models.User.update_or_create(user_type = auth_system, user_id = 'foobar_cou', info={'name':'Foo Bar'})
+
+            def double_update_or_create():
+                new_name = 'Foo2 Bar'
+                u2 = models.User.update_or_create(user_type = auth_system, user_id = 'foobar_cou', info={'name': new_name})
+
+                self.assertEquals(u.id, u2.id)
+                self.assertEquals(u2.info['name'], new_name)
+
+
+    def test_status_update(self):
+        """
+        check that a user set up with status update ability reports it as such,
+        and otherwise does not report it
+        """
+        for auth_system, auth_system_module in AUTH_SYSTEMS.iteritems():
+            u = models.User.update_or_create(user_type = auth_system, user_id = 'foobar_status_update', info={'name':'Foo Bar Status Update'})
+
+            if hasattr(auth_system_module, 'send_message'):
+                self.assertNotEquals(u.update_status_template, None)
+            else:
+                self.assertEquals(u.update_status_template, None)
+
+    def test_eligibility(self):
+        """
+        test that users are reported as eligible for something
+
+        FIXME: also test constraints on eligibility
+        """
+        for auth_system, auth_system_module in AUTH_SYSTEMS.iteritems():
+            u = models.User.update_or_create(user_type = auth_system, user_id = 'foobar_status_update', info={'name':'Foo Bar Status Update'})
+
+            self.assertTrue(u.is_eligible_for({'auth_system': auth_system}))
+
+    def test_eq(self):
+        for auth_system, auth_system_module in AUTH_SYSTEMS.iteritems():
+            u = models.User.update_or_create(user_type = auth_system, user_id = 'foobar_eq', info={'name':'Foo Bar Status Update'})
+            u2 = models.User.update_or_create(user_type = auth_system, user_id = 'foobar_eq', info={'name':'Foo Bar Status Update'})
+
+            self.assertEquals(u, u2)
diff --git a/auth/urls.py b/auth/urls.py
new file mode 100644
index 0000000000000000000000000000000000000000..cedf65e3d0d47548c898aae5ebc5cbcb23db83d5
--- /dev/null
+++ b/auth/urls.py
@@ -0,0 +1,29 @@
+"""
+Authentication URLs
+
+Ben Adida (ben@adida.net)
+"""
+
+from django.conf.urls.defaults import *
+
+from views import *
+from auth_systems.password import password_login_view, password_forgotten_view
+from auth_systems.twitter import follow_view
+
+urlpatterns = patterns('',
+    # basic static stuff
+    (r'^$', index),
+    (r'^logout$', logout),
+    (r'^start/(?P<system_name>.*)$', start),
+    (r'^after$', after),
+    (r'^after_intervention$', after_intervention),
+    
+    ## should make the following modular
+
+    # password auth
+    (r'^password/login', password_login_view),
+    (r'^password/forgot', password_forgotten_view),
+
+    # twitter
+    (r'^twitter/follow', follow_view),
+)
diff --git a/auth/utils.py b/auth/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..78a613a596f0b1deef8b3a3610269df038793e0f
--- /dev/null
+++ b/auth/utils.py
@@ -0,0 +1,29 @@
+"""
+Some basic utils 
+(previously were in helios module, but making things less interdependent
+
+2010-08-17
+"""
+
+from django.utils import simplejson
+
+## JSON
+def to_json(d):
+  return simplejson.dumps(d, sort_keys=True)
+  
+def from_json(json_str):
+  if not json_str: return None
+  return simplejson.loads(json_str)
+  
+def JSONtoDict(json):
+    x=simplejson.loads(json)
+    return x
+    
+def JSONFiletoDict(filename):
+  f = open(filename, 'r')
+  content = f.read()
+  f.close()
+  return JSONtoDict(content)
+    
+
+
diff --git a/auth/view_utils.py b/auth/view_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..e4bb20306a8a5f6840349449b5995e61c31cc4aa
--- /dev/null
+++ b/auth/view_utils.py
@@ -0,0 +1,60 @@
+"""
+Utilities for all views
+
+Ben Adida (12-30-2008)
+"""
+
+from django.template import Context, Template, loader
+from django.http import HttpResponse, Http404
+from django.shortcuts import render_to_response
+
+from auth.security import get_user
+
+import auth
+
+from django.conf import settings
+
+##
+## BASICS
+##
+
+SUCCESS = HttpResponse("SUCCESS")
+
+##
+## template abstraction
+##
+
+def prepare_vars(request, vars):
+  vars_with_user = vars.copy()
+  
+  if request:
+    vars_with_user['user'] = get_user(request)
+    vars_with_user['csrf_token'] = request.session['csrf_token']
+    vars_with_user['SECURE_URL_HOST'] = settings.SECURE_URL_HOST
+    
+  vars_with_user['STATIC'] = '/static/auth'
+  vars_with_user['MEDIA_URL'] = '/static/auth/'
+  vars_with_user['TEMPLATE_BASE'] = auth.TEMPLATE_BASE
+  
+  vars_with_user['settings'] = settings
+  
+  return vars_with_user
+  
+def render_template(request, template_name, vars = {}):
+  t = loader.get_template(template_name + '.html')
+  
+  vars_with_user = prepare_vars(request, vars)
+  
+  return render_to_response('auth/templates/%s.html' % template_name, vars_with_user)
+
+def render_template_raw(request, template_name, vars={}):
+  t = loader.get_template(template_name + '.html')
+  
+  vars_with_user = prepare_vars(request, vars)
+  c = Context(vars_with_user)  
+  return t.render(c)
+
+def render_json(json_txt):
+  return HttpResponse(json_txt)
+
+
diff --git a/auth/views.py b/auth/views.py
new file mode 100644
index 0000000000000000000000000000000000000000..7aef37c3d9b55498e87e106e22d631655b6af2c5
--- /dev/null
+++ b/auth/views.py
@@ -0,0 +1,183 @@
+"""
+Views for authentication
+
+Ben Adida
+2009-07-05
+"""
+
+from django.http import *
+from django.core.urlresolvers import reverse
+
+from view_utils import *
+from auth.security import get_user
+
+import auth_systems
+from auth_systems import AUTH_SYSTEMS
+from auth_systems import password
+import auth
+
+import copy
+
+from models import User
+
+from security import FIELDS_TO_SAVE
+
+def index(request):
+  """
+  the page from which one chooses how to log in.
+  """
+  
+  user = get_user(request)
+
+  # single auth system?
+  if len(auth.ENABLED_AUTH_SYSTEMS) == 1 and not user:
+    return HttpResponseRedirect(reverse(start, args=[auth.ENABLED_AUTH_SYSTEMS[0]])+ '?return_url=' + request.GET.get('return_url', ''))
+
+  #if auth.DEFAULT_AUTH_SYSTEM and not user:
+  #  return HttpResponseRedirect(reverse(start, args=[auth.DEFAULT_AUTH_SYSTEM])+ '?return_url=' + request.GET.get('return_url', ''))
+  
+  default_auth_system_obj = None
+  if auth.DEFAULT_AUTH_SYSTEM:
+    default_auth_system_obj = auth_systems.AUTH_SYSTEMS[auth.DEFAULT_AUTH_SYSTEM]
+
+  form = password.LoginForm()
+
+  return render_template(request,'index', {'return_url' : request.GET.get('return_url', '/'),
+                                           'enabled_auth_systems' : auth.ENABLED_AUTH_SYSTEMS,
+                                           'default_auth_system': auth.DEFAULT_AUTH_SYSTEM,
+                                           'default_auth_system_obj': default_auth_system_obj,
+                                           'form' : form})
+
+def login_box_raw(request, return_url='/', auth_systems = None):
+  """
+  a chunk of HTML that shows the various login options
+  """
+  default_auth_system_obj = None
+  if auth.DEFAULT_AUTH_SYSTEM:
+    default_auth_system_obj = auth_systems.AUTH_SYSTEMS[auth.DEFAULT_AUTH_SYSTEM]
+  
+  enabled_auth_systems = auth_systems or auth.ENABLED_AUTH_SYSTEMS
+
+  form = password.LoginForm()
+
+  return render_template_raw(request, 'login_box', {
+      'enabled_auth_systems': enabled_auth_systems, 'return_url': return_url,
+      'default_auth_system': auth.DEFAULT_AUTH_SYSTEM, 'default_auth_system_obj': default_auth_system_obj,
+      'form' : form})
+  
+def do_local_logout(request):
+  """
+  if there is a logged-in user, it is saved in the new session's "user_for_remote_logout"
+  variable.
+  """
+
+  user = None
+
+  if request.session.has_key('user'):
+    user = request.session['user']
+    
+  # 2010-08-14 be much more aggressive here
+  # we save a few fields across session renewals,
+  # but we definitely kill the session and renew
+  # the cookie
+  field_names_to_save = request.session.get(FIELDS_TO_SAVE, [])
+  fields_to_save = dict([(name, request.session.get(name, None)) for name in field_names_to_save])
+
+  # let's not forget to save the list of fields to save
+  field_names_to_save.append(FIELDS_TO_SAVE)
+  fields_to_save[FIELDS_TO_SAVE] = field_names_to_save
+
+  request.session.flush()
+
+  for name in field_names_to_save:
+    request.session[name] = fields_to_save[name]
+
+  request.session['user_for_remote_logout'] = user
+
+def do_remote_logout(request, user, return_url="/"):
+  # FIXME: do something with return_url
+  auth_system = AUTH_SYSTEMS[user['type']]
+  
+  # does the auth system have a special logout procedure?
+  if hasattr(auth_system, 'do_logout'):
+    response = auth_system.do_logout(request.session.get('user_for_remote_logout', None))
+    return response
+
+def do_complete_logout(request, return_url="/"):
+  do_local_logout(request)
+  user_for_remote_logout = request.session.get('user_for_remote_logout', None)
+  if user_for_remote_logout:
+    response = do_remote_logout(request, user_for_remote_logout, return_url)
+    return response
+  return None
+  
+def logout(request):
+  """
+  logout
+  """
+
+  return_url = request.GET.get('return_url',"/")
+  response = do_complete_logout(request, return_url)
+  if response:
+    return response
+  
+  return HttpResponseRedirect(return_url)
+  
+def start(request, system_name):
+  if not (system_name in auth.ENABLED_AUTH_SYSTEMS):
+    return HttpResponseRedirect(reverse(index))
+    
+  request.session.save()
+  
+  # store in the session the name of the system used for auth
+  request.session['auth_system_name'] = system_name
+  
+  # where to return to when done
+  request.session['auth_return_url'] = request.GET.get('return_url', '/')
+
+  # get the system
+  system = AUTH_SYSTEMS[system_name]  
+  
+  # where to send the user to?
+  redirect_url = "%s%s" % (settings.URL_HOST,reverse(after))
+  auth_url = system.get_auth_url(request, redirect_url=redirect_url)
+  
+  if auth_url:
+    return HttpResponseRedirect(auth_url)
+  else:
+    return HttpResponse("an error occurred trying to contact " + system_name +", try again later")
+
+def after(request):
+  # which auth system were we using?
+  if not request.session.has_key('auth_system_name'):
+    do_local_logout(request)
+    return HttpResponseRedirect("/")
+    
+  system = AUTH_SYSTEMS[request.session['auth_system_name']]
+  
+  # get the user info
+  user = system.get_user_info_after_auth(request)
+
+  if user:
+    # get the user and store any new data about him
+    user_obj = User.update_or_create(user['type'], user['user_id'], user['name'], user['info'], user['token'])
+    
+    request.session['user'] = user
+  else:
+    # we were logging out
+    pass
+
+  # does the auth system want to present an additional view?
+  # this is, for example, to prompt the user to follow @heliosvoting
+  # so they can hear about election results
+  if hasattr(system, 'user_needs_intervention'):
+    intervention_response = system.user_needs_intervention(user['user_id'], user['info'], user['token'])
+    if intervention_response:
+      return intervention_response
+
+  # go to the after intervention page. This is for modularity
+  return HttpResponseRedirect(reverse(after_intervention))
+
+def after_intervention(request):
+  return HttpResponseRedirect(request.session['auth_return_url'] or "/")
+