from django import newforms as forms from openid.consumer.discover import discover, DiscoveryFailure from openid.fetchers import HTTPFetchingError from openid.yadis import xri def identifierScheme(identifier): """Wrap openid.yadis.xri.identifierScheme to keep URI/URL consistent.""" scheme = xri.identifierScheme(identifier) if scheme == 'XRI': return scheme return 'URL' class OpenIDField(forms.CharField): """Field to normalize and validate an OpenID. Adds 'http://' prefix to URLs that don't have a scheme, appends trailing '/' if no path is provided, strips URL fragments and follows HTTP redirects. Strips 'xri://' prefix from XRIs. http://openid.net/specs/openid-authentication-2_0.html#normalization """ default_error_messages = { 'discovery': u"This %s doesn't appear to be an OpenID.", 'invalid': u'Enter a valid OpenID.', 'invalid_link': u'This URL appears to be a broken link.', 'required': u'Enter an OpenID.' } def __init__(self, max_length=255, *args, **kwargs): super(OpenIDField, self).__init__(max_length, *args, **kwargs) def clean(self, value): value = super(OpenIDField, self).clean(value) if value in forms.fields.EMPTY_VALUES: return u'' scheme = identifierScheme(value) if scheme == 'XRI' and value.startswith('xri://'): value = value[6:] try: claimed_id, endpoints = discover(value) except DiscoveryFailure: # resource not found raise forms.ValidationError(self.error_messages['invalid_link']) except HTTPFetchingError: # server not found or invalid URL raise forms.ValidationError(self.error_messages['invalid']) else: # valid URL/XRI with no OpenID endpoints if not endpoints: raise forms.ValidationError( self.error_messages['discovery'] % scheme ) self.endpoints = endpoints return claimed_id def widget_attrs(self, widget): # CSS hook for http://openid.net/logos/ return {'class': 'openid'}