summaryrefslogtreecommitdiffstats
path: root/forum_modules/openidauth
diff options
context:
space:
mode:
authorhrcerqueira <hrcerqueira@gmail.com>2010-03-01 17:37:32 +0000
committerhrcerqueira <hrcerqueira@gmail.com>2010-03-01 17:37:32 +0000
commitd01a3ff31d96aaedd9ce8ba38cb5d0cde53584fc (patch)
treebc5cd0632fdb2edbde77a5ea0db2be4cdeaedc59 /forum_modules/openidauth
parent0d29fc79deba22027187ae4627a7c38b3fdef2e4 (diff)
downloadaskbot-d01a3ff31d96aaedd9ce8ba38cb5d0cde53584fc.tar.gz
askbot-d01a3ff31d96aaedd9ce8ba38cb5d0cde53584fc.tar.bz2
askbot-d01a3ff31d96aaedd9ce8ba38cb5d0cde53584fc.zip
New auth system, see the wiki for details.
Diffstat (limited to 'forum_modules/openidauth')
-rwxr-xr-xforum_modules/openidauth/__init__.py0
-rwxr-xr-xforum_modules/openidauth/authentication.py196
-rwxr-xr-xforum_modules/openidauth/consumer.py112
-rwxr-xr-xforum_modules/openidauth/models.py26
-rwxr-xr-xforum_modules/openidauth/settings.py9
-rwxr-xr-xforum_modules/openidauth/store.py79
-rwxr-xr-xforum_modules/openidauth/templates/openidurl.html20
7 files changed, 442 insertions, 0 deletions
diff --git a/forum_modules/openidauth/__init__.py b/forum_modules/openidauth/__init__.py
new file mode 100755
index 00000000..e69de29b
--- /dev/null
+++ b/forum_modules/openidauth/__init__.py
diff --git a/forum_modules/openidauth/authentication.py b/forum_modules/openidauth/authentication.py
new file mode 100755
index 00000000..d34591e2
--- /dev/null
+++ b/forum_modules/openidauth/authentication.py
@@ -0,0 +1,196 @@
+from consumer import OpenIdAbstractAuthConsumer
+from forum.authentication.base import ConsumerTemplateContext
+
+class GoogleAuthConsumer(OpenIdAbstractAuthConsumer):
+ def get_user_url(self, request):
+ return 'https://www.google.com/accounts/o8/id'
+
+class GoogleAuthContext(ConsumerTemplateContext):
+ mode = 'BIGICON'
+ type = 'DIRECT'
+ weight = 200
+ human_name = 'Google'
+ icon = '/media/images/openid/google.gif'
+
+
+
+class YahooAuthConsumer(OpenIdAbstractAuthConsumer):
+ def get_user_url(self, request):
+ return 'http://yahoo.com/'
+
+class YahooAuthContext(ConsumerTemplateContext):
+ mode = 'BIGICON'
+ type = 'DIRECT'
+ weight = 300
+ human_name = 'Yahoo'
+ icon = '/media/images/openid/yahoo.gif'
+
+
+
+class AolAuthConsumer(OpenIdAbstractAuthConsumer):
+ def get_user_url(self, request):
+ uname = request.POST['input_field']
+ return 'http://openid.aol.com/' + uname
+
+class AolAuthContext(ConsumerTemplateContext):
+ mode = 'BIGICON'
+ type = 'SIMPLE_FORM'
+ simple_form_context = {
+ 'your_what': 'AOL screen name'
+ }
+ weight = 400
+ human_name = 'AOL'
+ icon = '/media/images/openid/aol.gif'
+
+
+class MyOpenIdAuthConsumer(OpenIdAbstractAuthConsumer):
+ def get_user_url(self, request):
+ blog_name = request.POST['input_field']
+ return "http://%s.myopenid.com/" % blog_name
+
+class MyOpenIdAuthContext(ConsumerTemplateContext):
+ mode = 'SMALLICON'
+ type = 'SIMPLE_FORM'
+ simple_form_context = {
+ 'your_what': 'MyOpenID user name'
+ }
+ weight = 200
+ human_name = 'MyOpenID'
+ icon = '/media/images/openid/myopenid.ico'
+
+
+class FlickrAuthConsumer(OpenIdAbstractAuthConsumer):
+ def get_user_url(self, request):
+ blog_name = request.POST['input_field']
+ return "http://flickr.com/%s/" % blog_name
+
+class FlickrAuthContext(ConsumerTemplateContext):
+ mode = 'SMALLICON'
+ type = 'SIMPLE_FORM'
+ simple_form_context = {
+ 'your_what': 'Flickr user name'
+ }
+ weight = 250
+ human_name = 'Flickr'
+ icon = '/media/images/openid/flickr.ico'
+
+
+class TechnoratiAuthConsumer(OpenIdAbstractAuthConsumer):
+ def get_user_url(self, request):
+ blog_name = request.POST['input_field']
+ return "http://technorati.com/people/technorati/%s/" % blog_name
+
+class TechnoratiAuthContext(ConsumerTemplateContext):
+ mode = 'SMALLICON'
+ type = 'SIMPLE_FORM'
+ simple_form_context = {
+ 'your_what': 'Technorati user name'
+ }
+ weight = 260
+ human_name = 'Technorati'
+ icon = '/media/images/openid/technorati.ico'
+
+
+class WordpressAuthConsumer(OpenIdAbstractAuthConsumer):
+ def get_user_url(self, request):
+ blog_name = request.POST['input_field']
+ return "http://%s.wordpress.com/" % blog_name
+
+class WordpressAuthContext(ConsumerTemplateContext):
+ mode = 'SMALLICON'
+ type = 'SIMPLE_FORM'
+ simple_form_context = {
+ 'your_what': 'Wordpress blog name'
+ }
+ weight = 270
+ human_name = 'Wordpress'
+ icon = '/media/images/openid/wordpress.ico'
+
+
+class BloggerAuthConsumer(OpenIdAbstractAuthConsumer):
+ def get_user_url(self, request):
+ blog_name = request.POST['input_field']
+ return "http://%s.blogspot.com/" % blog_name
+
+class BloggerAuthContext(ConsumerTemplateContext):
+ mode = 'SMALLICON'
+ type = 'SIMPLE_FORM'
+ simple_form_context = {
+ 'your_what': 'Blogger blog name'
+ }
+ weight = 300
+ human_name = 'Blogger'
+ icon = '/media/images/openid/blogger.ico'
+
+
+class LiveJournalAuthConsumer(OpenIdAbstractAuthConsumer):
+ def get_user_url(self, request):
+ blog_name = request.POST['input_field']
+ return "http://%s.livejournal.com/" % blog_name
+
+class LiveJournalAuthContext(ConsumerTemplateContext):
+ mode = 'SMALLICON'
+ type = 'SIMPLE_FORM'
+ simple_form_context = {
+ 'your_what': 'LiveJournal blog name'
+ }
+ weight = 310
+ human_name = 'LiveJournal'
+ icon = '/media/images/openid/livejournal.ico'
+
+
+class ClaimIdAuthConsumer(OpenIdAbstractAuthConsumer):
+ def get_user_url(self, request):
+ blog_name = request.POST['input_field']
+ return "http://claimid.com/%s" % blog_name
+
+class ClaimIdAuthContext(ConsumerTemplateContext):
+ mode = 'SMALLICON'
+ type = 'SIMPLE_FORM'
+ simple_form_context = {
+ 'your_what': 'ClaimID user name'
+ }
+ weight = 320
+ human_name = 'ClaimID'
+ icon = '/media/images/openid/claimid.ico'
+
+class VidoopAuthConsumer(OpenIdAbstractAuthConsumer):
+ def get_user_url(self, request):
+ blog_name = request.POST['input_field']
+ return "http://%s.myvidoop.com/" % blog_name
+
+class VidoopAuthContext(ConsumerTemplateContext):
+ mode = 'SMALLICON'
+ type = 'SIMPLE_FORM'
+ simple_form_context = {
+ 'your_what': 'Vidoop user name'
+ }
+ weight = 330
+ human_name = 'Vidoop'
+ icon = '/media/images/openid/vidoop.ico'
+
+class VerisignAuthConsumer(OpenIdAbstractAuthConsumer):
+ def get_user_url(self, request):
+ blog_name = request.POST['input_field']
+ return "http://%s.pip.verisignlabs.com/" % blog_name
+
+class VerisignAuthContext(ConsumerTemplateContext):
+ mode = 'SMALLICON'
+ type = 'SIMPLE_FORM'
+ simple_form_context = {
+ 'your_what': 'Verisign user name'
+ }
+ weight = 340
+ human_name = 'Verisign'
+ icon = '/media/images/openid/verisign.ico'
+
+
+class OpenIdUrlAuthConsumer(OpenIdAbstractAuthConsumer):
+ pass
+
+class OpenIdUrlAuthContext(ConsumerTemplateContext):
+ mode = 'STACK_ITEM'
+ weight = 300
+ human_name = 'OpenId url'
+ stack_item_template = 'modules/openidauth/openidurl.html'
+ icon = '/media/images/openid/openid-inputicon.gif' \ No newline at end of file
diff --git a/forum_modules/openidauth/consumer.py b/forum_modules/openidauth/consumer.py
new file mode 100755
index 00000000..17d1378f
--- /dev/null
+++ b/forum_modules/openidauth/consumer.py
@@ -0,0 +1,112 @@
+from django.utils.html import escape
+from django.http import get_host
+
+from forum.authentication.base import AuthenticationConsumer, InvalidAuthentication
+import settings
+
+from openid.yadis import xri
+from openid.consumer.consumer import Consumer, SUCCESS, CANCEL, FAILURE, SETUP_NEEDED
+from openid.consumer.discover import DiscoveryFailure
+from openid.extensions.sreg import SRegRequest, SRegResponse
+from openid.extensions.ax import FetchRequest as AXFetchRequest, AttrInfo, FetchResponse as AXFetchResponse
+from django.utils.translation import ugettext as _
+
+from store import OsqaOpenIDStore
+
+class OpenIdAbstractAuthConsumer(AuthenticationConsumer):
+
+ def get_user_url(self, request):
+ try:
+ return request.POST['openid_identifier']
+ except:
+ raise NotImplementedError()
+
+ def prepare_authentication_request(self, request, redirect_to):
+ if not redirect_to.startswith('http://') or redirect_to.startswith('https://'):
+ redirect_to = get_url_host(request) + redirect_to
+
+ user_url = self.get_user_url(request)
+
+ if xri.identifierScheme(user_url) == 'XRI' and getattr(
+ settings, 'OPENID_DISALLOW_INAMES', False
+ ):
+ raise InvalidAuthentication('i-names are not supported')
+
+ consumer = Consumer(request.session, OsqaOpenIDStore())
+
+ try:
+ auth_request = consumer.begin(user_url)
+ except DiscoveryFailure:
+ raise InvalidAuthentication(_('Sorry, but your input is not a valid OpenId'))
+
+ #sreg = getattr(settings, 'OPENID_SREG', False)
+
+ #if sreg:
+ # s = SRegRequest()
+ # for sarg in sreg:
+ # if sarg.lower().lstrip() == "policy_url":
+ # s.policy_url = sreg[sarg]
+ # else:
+ # for v in sreg[sarg].split(','):
+ # s.requestField(field_name=v.lower().lstrip(), required=(sarg.lower().lstrip() == "required"))
+ # auth_request.addExtension(s)
+
+ #auth_request.addExtension(SRegRequest(required=['email']))
+
+ if request.session.get('force_email_request', True):
+ axr = AXFetchRequest()
+ axr.add(AttrInfo("http://axschema.org/contact/email", 1, True, "email"))
+ auth_request.addExtension(axr)
+
+ trust_root = getattr(
+ settings, 'OPENID_TRUST_ROOT', get_url_host(request) + '/'
+ )
+
+
+ return auth_request.redirectURL(trust_root, redirect_to)
+
+ def process_authentication_request(self, request):
+ consumer = Consumer(request.session, OsqaOpenIDStore())
+
+ query_dict = dict([
+ (k.encode('utf8'), v.encode('utf8')) for k, v in request.GET.items()
+ ])
+
+ #for i in query_dict.items():
+ # print "%s : %s" % i
+
+ url = get_url_host(request) + request.path
+ openid_response = consumer.complete(query_dict, url)
+
+ if openid_response.status == SUCCESS:
+ if request.session.get('force_email_request', True):
+ try:
+ ax = AXFetchResponse.fromSuccessResponse(openid_response)
+ email = ax.getExtensionArgs()['value.ext0.1']
+ request.session['auth_email_request'] = email
+ except Exception, e:
+ pass
+
+ return request.GET['openid.identity']
+ elif openid_response.status == CANCEL:
+ raise InvalidAuthentication(_('The OpenId authentication request was canceled'))
+ elif openid_response.status == FAILURE:
+ raise InvalidAuthentication(_('The OpenId authentication failed: ') + openid_response.message)
+ elif openid_response.status == SETUP_NEEDED:
+ raise InvalidAuthentication(_('Setup needed'))
+ else:
+ raise InvalidAuthentication(_('The OpenId authentication failed with an unknown status: ') + openid_response.status)
+
+ def get_user_data(self, key):
+ return {}
+
+def get_url_host(request):
+ if request.is_secure():
+ protocol = 'https'
+ else:
+ protocol = 'http'
+ host = escape(get_host(request))
+ return '%s://%s' % (protocol, host)
+
+def get_full_url(request):
+ return get_url_host(request) + request.get_full_path() \ No newline at end of file
diff --git a/forum_modules/openidauth/models.py b/forum_modules/openidauth/models.py
new file mode 100755
index 00000000..d6cc991e
--- /dev/null
+++ b/forum_modules/openidauth/models.py
@@ -0,0 +1,26 @@
+from django.db import models
+
+class OpenIdNonce(models.Model):
+ server_url = models.URLField()
+ timestamp = models.IntegerField()
+ salt = models.CharField( max_length=50 )
+
+ def __unicode__(self):
+ return "Nonce: %s" % self.nonce
+
+ class Meta:
+ app_label = 'forum'
+
+class OpenIdAssociation(models.Model):
+ server_url = models.TextField(max_length=2047)
+ handle = models.CharField(max_length=255)
+ secret = models.TextField(max_length=255) # Stored base64 encoded
+ issued = models.IntegerField()
+ lifetime = models.IntegerField()
+ assoc_type = models.TextField(max_length=64)
+
+ def __unicode__(self):
+ return "Association: %s, %s" % (self.server_url, self.handle)
+
+ class Meta:
+ app_label = 'forum'
diff --git a/forum_modules/openidauth/settings.py b/forum_modules/openidauth/settings.py
new file mode 100755
index 00000000..3b1c2eec
--- /dev/null
+++ b/forum_modules/openidauth/settings.py
@@ -0,0 +1,9 @@
+OPENID_SREG = {
+ "required": "nickname, email",
+ "optional": "postcode, country",
+ "policy_url": ""
+}
+OPENID_AX = [
+ {"type_uri": "http://axschema.org/contact/email", "count": 1, "required": True, "alias": "email"},
+ {"type_uri": "fullname", "count":1 , "required": False, "alias": "fullname"}
+ ] \ No newline at end of file
diff --git a/forum_modules/openidauth/store.py b/forum_modules/openidauth/store.py
new file mode 100755
index 00000000..93d38a5b
--- /dev/null
+++ b/forum_modules/openidauth/store.py
@@ -0,0 +1,79 @@
+import time, base64, md5
+
+from openid.store import nonce as oid_nonce
+from openid.store.interface import OpenIDStore
+from openid.association import Association as OIDAssociation
+from django.conf import settings
+
+from models import OpenIdNonce as Nonce, OpenIdAssociation as Association
+
+class OsqaOpenIDStore(OpenIDStore):
+ def __init__(self):
+ self.max_nonce_age = 6 * 60 * 60 # Six hours
+
+ def storeAssociation(self, server_url, association):
+ assoc = Association(
+ server_url = server_url,
+ handle = association.handle,
+ secret = base64.encodestring(association.secret),
+ issued = association.issued,
+ lifetime = association.issued,
+ assoc_type = association.assoc_type
+ )
+ assoc.save()
+
+ def getAssociation(self, server_url, handle=None):
+ assocs = []
+ if handle is not None:
+ assocs = Association.objects.filter(
+ server_url = server_url, handle = handle
+ )
+ else:
+ assocs = Association.objects.filter(
+ server_url = server_url
+ )
+ if not assocs:
+ return None
+ associations = []
+ for assoc in assocs:
+ association = OIDAssociation(
+ assoc.handle, base64.decodestring(assoc.secret), assoc.issued,
+ assoc.lifetime, assoc.assoc_type
+ )
+ if association.getExpiresIn() == 0:
+ self.removeAssociation(server_url, assoc.handle)
+ else:
+ associations.append((association.issued, association))
+ if not associations:
+ return None
+ return associations[-1][1]
+
+ def removeAssociation(self, server_url, handle):
+ assocs = list(Association.objects.filter(
+ server_url = server_url, handle = handle
+ ))
+ assocs_exist = len(assocs) > 0
+ for assoc in assocs:
+ assoc.delete()
+ return assocs_exist
+
+ def storeNonce(self, nonce):
+ nonce, created = Nonce.objects.get_or_create(
+ nonce = nonce, defaults={'expires': int(time.time())}
+ )
+
+ def useNonce(self, server_url, timestamp, salt):
+ if abs(timestamp - time.time()) > oid_nonce.SKEW:
+ return False
+
+ try:
+ nonce = Nonce( server_url=server_url, timestamp=timestamp, salt=salt)
+ nonce.save()
+ except:
+ raise
+ else:
+ return 1
+
+ def getAuthKey(self):
+ # Use first AUTH_KEY_LEN characters of md5 hash of SECRET_KEY
+ return md5.new(settings.SECRET_KEY).hexdigest()[:self.AUTH_KEY_LEN]
diff --git a/forum_modules/openidauth/templates/openidurl.html b/forum_modules/openidauth/templates/openidurl.html
new file mode 100755
index 00000000..cd4e77dc
--- /dev/null
+++ b/forum_modules/openidauth/templates/openidurl.html
@@ -0,0 +1,20 @@
+{% load i18n %}
+{% load extra_tags %}
+
+<fieldset>
+ <table>
+ <tr>
+ <td><p id="provider_name_slot">{% trans 'Enter your OpenId Url' %}</p></td>
+ </tr>
+ <tr>
+ <td>
+ <input id="openid_identifier" class="icon_input" name="openid_identifier" type="text"
+ style="width: 500px; background: url('{% media provider.icon %}') no-repeat left center" />
+ </td>
+ <td>
+ <input type="submit" name="ssignin" value="Login" />
+ </td>
+ </tr>
+ </table>
+</fieldset>
+