summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--askbot/conf/external_keys.py72
-rw-r--r--askbot/conf/site_settings.py1
-rw-r--r--askbot/conf/skin_general_settings.py35
-rw-r--r--askbot/const/__init__.py5
-rw-r--r--askbot/deps/django_authopenid/backends.py169
-rw-r--r--askbot/deps/django_authopenid/decorators.py27
-rw-r--r--askbot/deps/django_authopenid/forms.py383
-rw-r--r--askbot/deps/django_authopenid/models.py2
-rw-r--r--askbot/deps/django_authopenid/tests.py22
-rw-r--r--askbot/deps/django_authopenid/urls.py36
-rw-r--r--askbot/deps/django_authopenid/util.py431
-rw-r--r--askbot/deps/django_authopenid/views.py1079
-rw-r--r--askbot/deps/recaptcha_django/__init__.py4
-rw-r--r--askbot/doc/source/change-password.pngbin0 -> 43123 bytes
-rw-r--r--askbot/doc/source/connect-aol.pngbin0 -> 43088 bytes
-rw-r--r--askbot/doc/source/initialize-database-tables.rst1
-rw-r--r--askbot/doc/source/manage-logins1.pngbin0 -> 43472 bytes
-rw-r--r--askbot/doc/source/password-signin.pngbin0 -> 45493 bytes
-rw-r--r--askbot/doc/source/pw-register.pngbin0 -> 41318 bytes
-rw-r--r--askbot/doc/source/recover-account.pngbin0 -> 43429 bytes
-rw-r--r--askbot/doc/source/signin-with-aol.pngbin0 -> 44138 bytes
-rw-r--r--askbot/management/commands/send_email_alerts.py2
-rw-r--r--askbot/middleware/spaceless.py17
-rw-r--r--askbot/models/meta.py2
-rw-r--r--askbot/setup_templates/settings.py2
-rw-r--r--askbot/skins/default/media/images/pw-login.gifbin0 -> 1818 bytes
-rwxr-xr-xaskbot/skins/default/media/jquery-openid/images/aol.gifbin2205 -> 1872 bytes
-rwxr-xr-xaskbot/skins/default/media/jquery-openid/images/facebook.gifbin2075 -> 1737 bytes
-rwxr-xr-xaskbot/skins/default/media/jquery-openid/images/google.gifbin1596 -> 1528 bytes
-rw-r--r--askbot/skins/default/media/jquery-openid/images/linkedin.gifbin0 -> 1530 bytes
-rwxr-xr-xaskbot/skins/default/media/jquery-openid/images/openid.gifbin740 -> 1473 bytes
-rw-r--r--askbot/skins/default/media/jquery-openid/images/twitter.gifbin0 -> 1913 bytes
-rwxr-xr-xaskbot/skins/default/media/jquery-openid/images/yahoo.gifbin1682 -> 1607 bytes
-rwxr-xr-xaskbot/skins/default/media/jquery-openid/jquery.openid.js476
-rwxr-xr-xaskbot/skins/default/media/jquery-openid/openid.css93
-rwxr-xr-xaskbot/skins/default/media/style/style.css1
-rw-r--r--askbot/skins/default/templates/authopenid/complete.html39
-rw-r--r--askbot/skins/default/templates/authopenid/sendpw.html26
-rwxr-xr-xaskbot/skins/default/templates/authopenid/signin.html412
-rw-r--r--askbot/skins/default/templates/authopenid/signup_with_password.html (renamed from askbot/skins/default/templates/authopenid/signup.html)3
-rw-r--r--askbot/skins/default/templates/logout.html28
-rw-r--r--askbot/views/users.py6
42 files changed, 2172 insertions, 1202 deletions
diff --git a/askbot/conf/external_keys.py b/askbot/conf/external_keys.py
index 667a997f..acf637f8 100644
--- a/askbot/conf/external_keys.py
+++ b/askbot/conf/external_keys.py
@@ -45,9 +45,16 @@ settings.register(
settings.register(
StringValue(
EXTERNAL_KEYS,
- 'RECAPTCHA_PRIVATE_KEY',
- description=_('Recaptcha private key') + ' - does not work yet',
- hidden=True,
+ 'RECAPTCHA_KEY',
+ description=_('Recaptcha public key')
+ )
+)
+
+settings.register(
+ StringValue(
+ EXTERNAL_KEYS,
+ 'RECAPTCHA_SECRET',
+ description=_('Recaptcha private key'),
help_text=_(
'Recaptcha is a tool that helps distinguish '
'real people from annoying spam robots. '
@@ -57,21 +64,12 @@ settings.register(
)
)
-settings.register(
- StringValue(
- EXTERNAL_KEYS,
- 'RECAPTCHA_PUBLIC_KEY',
- hidden=True,
- description=_('Recaptcha public key') + ' - does not work yet'
- )
-)
settings.register(
StringValue(
EXTERNAL_KEYS,
- 'FB_API_KEY',
- description=_('Facebook public API key') + ' - does not work yet',
- hidden=True,
+ 'FACEBOOK_KEY',
+ description=_('Facebook public API key'),
help_text=_(
'Facebook API key and Facebook secret '
'allow to use Facebook Connect login method '
@@ -80,14 +78,54 @@ settings.register(
'facebook create app</a> site'
)
)
+)
+
+settings.register(
+ StringValue(
+ EXTERNAL_KEYS,
+ 'FACEBOOK_SECRET',
+ description=_('Facebook secret key')
+ )
+)
+
+settings.register(
+ StringValue(
+ EXTERNAL_KEYS,
+ 'TWITTER_KEY',
+ description=_('Twitter consumer key'),
+ help_text=_(
+ 'Please register your forum at <a href="http://dev.twitter.com/apps/">'
+ 'twitter applications site</a>'
+ ),
+
+ )
+)
+settings.register(
+ StringValue(
+ EXTERNAL_KEYS,
+ 'TWITTER_SECRET',
+ description=_('Twitter consumer secret'),
+ )
+)
+
+settings.register(
+ StringValue(
+ EXTERNAL_KEYS,
+ 'LINKEDIN_KEY',
+ description=_('LinkedIn consumer key'),
+ help_text=_(
+ 'Please register your forum at <a href="http://dev.twitter.com/apps/">'
+ 'twitter applications site</a>'
+ ),
+
+ )
)
settings.register(
StringValue(
EXTERNAL_KEYS,
- 'FB_SECRET',
- hidden=True,
- description=_('Facebook secret key') + ' - does not work yet'
+ 'LINKEDIN_SECRET',
+ description=_('LinkedIn consumer secret'),
)
)
diff --git a/askbot/conf/site_settings.py b/askbot/conf/site_settings.py
index e2991fad..a206144f 100644
--- a/askbot/conf/site_settings.py
+++ b/askbot/conf/site_settings.py
@@ -56,7 +56,6 @@ settings.register(
QA_SITE_SETTINGS,
'APP_SHORT_NAME',
default=_('Askbot'),
- hidden=True,
description=_('Short name for your Q&A forum')
)
)
diff --git a/askbot/conf/skin_general_settings.py b/askbot/conf/skin_general_settings.py
index be605fd3..a3b22a0a 100644
--- a/askbot/conf/skin_general_settings.py
+++ b/askbot/conf/skin_general_settings.py
@@ -8,6 +8,7 @@ from askbot.deps.livesettings import ImageValue
from django.utils.translation import ugettext as _
from django.conf import settings as django_settings
from askbot.skins import utils as skin_utils
+from askbot import const
GENERAL_SKIN_SETTINGS = ConfigurationGroup(
'GENERAL_SKIN_SETTINGS',
@@ -31,6 +32,40 @@ settings.register(
)
settings.register(
+ ImageValue(
+ GENERAL_SKIN_SETTINGS,
+ 'SITE_FAVICON',
+ description = _('Site favicon'),
+ help_text = _(
+ 'A small 16x16 or 32x32 pixel icon image '
+ 'used to distinguish your site in the browser '
+ 'user interface. Please find more information about favicon '
+ 'at <a href="%(favicon_info_url)s">this page</a>.'
+ ) % {'favicon_info_url': const.DEPENDENCY_URLS['favicon']},
+ upload_directory = django_settings.ASKBOT_FILE_UPLOAD_DIR,
+ upload_url = '/' + django_settings.ASKBOT_UPLOADED_FILES_URL,
+ default = '/images/favicon.gif',
+ url_resolver = skin_utils.get_media_url
+ )
+)
+
+settings.register(
+ ImageValue(
+ GENERAL_SKIN_SETTINGS,
+ 'LOCAL_LOGIN_ICON',
+ description = _('Password login button'),
+ help_text = _(
+ 'An 88x38 pixel image that is used on the login screen '
+ 'for the password login button.'
+ ),
+ upload_directory = django_settings.ASKBOT_FILE_UPLOAD_DIR,
+ upload_url = '/' + django_settings.ASKBOT_UPLOADED_FILES_URL,
+ default = '/images/pw-login.gif',
+ url_resolver = skin_utils.get_media_url
+ )
+)
+
+settings.register(
BooleanValue(
GENERAL_SKIN_SETTINGS,
'ALWAYS_SHOW_ALL_UI_FUNCTIONS',
diff --git a/askbot/const/__init__.py b/askbot/const/__init__.py
index 06c7e497..98b8e49c 100644
--- a/askbot/const/__init__.py
+++ b/askbot/const/__init__.py
@@ -205,7 +205,7 @@ USERS_PAGE_SIZE = 28#todo: move it to settings?
USERNAME_REGEX_STRING = r'^[\w \-]+$'
#chars that can go before or after @mention
-TWITTER_STYLE_MENTION_TERMINATION_CHARS = '\n ;,.!?<>'
+TWITTER_STYLE_MENTION_TERMINATION_CHARS = '\n ;:,.!?<>'
COMMENT_HARD_MAX_LENGTH = 2048
@@ -226,7 +226,10 @@ USER_VIEW_DATA_SIZE = 50
DEPENDENCY_URLS = {
'mathjax': 'http://www.mathjax.org/resources/docs/?installation.html',
+ 'favicon': 'http://en.wikipedia.org/wiki/Favicon'
}
+PASSWORD_MIN_LENGTH = 8
+
#an exception import * because that file has only strings
from askbot.const.message_keys import *
diff --git a/askbot/deps/django_authopenid/backends.py b/askbot/deps/django_authopenid/backends.py
new file mode 100644
index 00000000..664307d4
--- /dev/null
+++ b/askbot/deps/django_authopenid/backends.py
@@ -0,0 +1,169 @@
+"""authentication backend that takes care of the
+multiple login methods supported by the authenticator
+application
+"""
+import logging
+import datetime
+from django.contrib.auth.models import User
+from django.core.exceptions import ImproperlyConfigured
+from askbot.deps.django_authopenid.models import UserAssociation
+from askbot.deps.django_authopenid import util
+
+class AuthBackend(object):
+ """Authenticator's authentication backend class
+ for more info, see django doc page:
+ http://docs.djangoproject.com/en/dev/topics/auth/#writing-an-authentication-backend
+
+ the reason there is only one class - for simplicity of
+ adding this application to a django project - users only need
+ to extend the AUTHENTICATION_BACKENDS with a single line
+ """
+
+ def authenticate(
+ self,
+ username = None,#for 'password'
+ password = None,#for 'password'
+ user_id = None,#for 'force'
+ provider_name = None,#required with all except email_key
+ openid_url = None,
+ email_key = None,
+ oauth_user_id = None,#used with oauth
+ facebook_user_id = None,#user with facebook
+ method = None,#requried parameter
+ ):
+ """this authentication function supports many login methods
+ just which method it is going to use it determined
+ from the signature of the function call
+ """
+ login_providers = util.get_login_providers()
+ if method == 'password':
+ if login_providers[provider_name]['type'] != 'password':
+ raise ImproperlyConfigured('login provider must use password')
+ if provider_name == 'local':
+ try:
+ user = User.objects.get(username=username)
+ if not user.check_password(password):
+ return None
+ except User.DoesNotExist:
+ return None
+ else:
+ #todo there must be a call to some sort of
+ #an external "check_password" function
+ raise NotImplementedError('do not support external passwords')
+
+ #this is a catch - make login token a little more unique
+ #for the cases when passwords are the same for two users
+ #from the same provider
+ try:
+ assoc = UserAssociation.objects.get(
+ user = user,
+ provider_name = provider_name
+ )
+ except UserAssociation.DoesNotExist:
+ assoc = UserAssociation(
+ user = user,
+ provider_name = provider_name
+ )
+ assoc.openid_url = user.password + str(user.id)
+
+ elif method == 'openid':
+ provider_name = util.get_provider_name(openid_url)
+ if login_providers[provider_name]['type'].startswith('openid'):
+ try:
+ assoc = UserAssociation.objects.get(
+ openid_url = openid_url,
+ provider_name = provider_name
+ )
+ user = assoc.user
+ except UserAssociation.DoesNotExist:
+ return None
+ else:
+ return None
+
+ elif method == 'email':
+ #with this method we do no use user association
+ try:
+ #todo: add email_key_timestamp field
+ #and check key age
+ user = User.objects.get(email_key = email_key)
+ user.email_key = None #one time key so delete it
+ user.email_isvalid = True
+ user.save()
+ return user
+ except User.DoesNotExist:
+ return None
+
+ elif method == 'oauth':
+ if login_providers[provider_name]['type'] == 'oauth':
+ try:
+ assoc = UserAssociation.objects.get(
+ openid_url = oauth_user_id,
+ provider_name = provider_name
+ )
+ user = assoc.user
+ except UserAssociation.DoesNotExist:
+ return None
+ else:
+ return None
+
+ elif method == 'facebook':
+ try:
+ assoc = UserAssociation.objects.get(
+ openid_url = facebook_user_id,
+ provider_name = 'facebook'
+ )
+ user = assoc.user
+ except UserAssociation.DoesNotExist:
+ return None
+
+ elif method == 'force':
+ return self.get_user(user_id)
+ else:
+ raise TypeError('only openid and password supported')
+
+ #update last used time
+ assoc.last_used_timestamp = datetime.datetime.now()
+ assoc.save()
+ return user
+
+ def get_user(self, user_id):
+ try:
+ return User.objects.get(id=user_id)
+ except User.DoesNotExist:
+ return None
+
+ @classmethod
+ def set_password(cls,
+ user=None,
+ password=None,
+ provider_name=None
+ ):
+ """generic method to change password of
+ any for any login provider that uses password
+ and allows the password change function
+ """
+ login_providers = util.get_login_providers()
+ if login_providers[provider_name]['type'] != 'password':
+ raise ImproperlyConfigured('login provider must use password')
+
+ if provider_name == 'local':
+ user.set_password(password)
+ user.save()
+ scrambled_password = user.password + str(user.id)
+ else:
+ raise NotImplementedError('external passwords not supported')
+
+ try:
+ assoc = UserAssociation.objects.get(
+ user = user,
+ provider_name = provider_name
+ )
+ except UserAssociation.DoesNotExist:
+ assoc = UserAssociation(
+ user = user,
+ provider_name = provider_name
+ )
+
+ assoc.openid_url = scrambled_password
+ assoc.last_used_timestamp = datetime.datetime.now()
+ assoc.save()
diff --git a/askbot/deps/django_authopenid/decorators.py b/askbot/deps/django_authopenid/decorators.py
new file mode 100644
index 00000000..8338aa9d
--- /dev/null
+++ b/askbot/deps/django_authopenid/decorators.py
@@ -0,0 +1,27 @@
+import functools
+from django.forms import ValidationError
+from django.core.urlresolvers import reverse
+from django.http import HttpResponseRedirect
+from askbot.deps.django_authopenid import forms
+from askbot.utils.forms import get_next_url
+
+def valid_password_login_provider_required(view_func):
+ """decorator for a view function which will
+ redirect to signin page with the next url parameter
+ set unless request (either GET or POST has parameter
+ 'login_provider' and the provider uses password
+ authentication method
+ """
+ @functools.wraps(view_func)
+ def decorated_function(request):
+ login_provider = request.REQUEST.get('login_provider', '').strip()
+ try:
+ forms.PasswordLoginProviderField().clean(login_provider)
+ return view_func(request)
+ except ValidationError:
+ redirect_url = reverse('user_signin')
+ next = get_next_url(request)
+ if next:
+ redirect_url += '?next=%s' % next
+ return HttpResponseRedirect(redirect_url)
+ return decorated_function
diff --git a/askbot/deps/django_authopenid/forms.py b/askbot/deps/django_authopenid/forms.py
index d89796b7..4eb69bc9 100644
--- a/askbot/deps/django_authopenid/forms.py
+++ b/askbot/deps/django_authopenid/forms.py
@@ -30,14 +30,16 @@
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+import types
+import re
+import logging
from django import forms
from django.contrib.auth.models import User
from django.contrib.auth import authenticate
from django.utils.translation import ugettext as _
from django.conf import settings
from askbot.conf import settings as askbot_settings
-import types
-import re
+from askbot import const as askbot_const
from django.utils.safestring import mark_safe
from askbot.deps.recaptcha_django import ReCaptchaField
from askbot.utils.forms import NextUrlField, UserNameField, UserEmailField, SetPasswordForm
@@ -50,12 +52,55 @@ except ImportError:
from yadis import xri
from askbot.utils.forms import clean_next
+from askbot.deps.django_authopenid import util
from askbot.deps.django_authopenid.models import ExternalLoginData
-__all__ = ['OpenidSigninForm', 'ClassicLoginForm', 'OpenidVerifyForm',
- 'OpenidRegisterForm', 'ClassicRegisterForm', 'ChangePasswordForm',
- 'ChangeEmailForm', 'EmailPasswordForm', 'DeleteForm',
- 'ChangeOpenidForm']
+__all__ = [
+ 'OpenidSigninForm','OpenidRegisterForm',
+ 'ClassicRegisterForm', 'ChangePasswordForm',
+ 'ChangeEmailForm', 'EmailPasswordForm', 'DeleteForm',
+ 'ChangeOpenidForm'
+]
+
+class LoginProviderField(forms.CharField):
+ """char field where value must
+ be one of login providers
+ """
+ widget = forms.widgets.HiddenInput()
+
+ def __init__(self, *args, **kwargs):
+ kwargs['max_length'] = 64
+ super(LoginProviderField, self).__init__(*args, **kwargs)
+
+ def clean(self, value):
+ """makes sure that login provider name
+ exists is in the list of accepted providers
+ """
+ providers = util.get_login_providers()
+ if value in providers:
+ return value
+ else:
+ error_message = 'unknown provider name %s' % value
+ logging.critical(error_message)
+ raise forms.ValidationError(error_message)
+
+class PasswordLoginProviderField(LoginProviderField):
+ """char field where value must
+ be one of login providers using username/password
+ method for authentication
+ """
+ def clean(self, value):
+ """make sure that value is name of
+ one of the known password login providers
+ """
+ value = super(PasswordLoginProviderField, self).clean(value)
+ providers = util.get_login_providers()
+ if providers[value]['type'] != 'password':
+ raise forms.ValidationError(
+ 'provider %s must accept password' % value
+ )
+ return value
+
class OpenidSigninForm(forms.Form):
""" signin form """
@@ -72,109 +117,201 @@ class OpenidSigninForm(forms.Form):
raise forms.ValidationError(_('i-names are not supported'))
return self.cleaned_data['openid_url']
-class ClassicLoginForm(forms.Form):
- """ legacy account signin form """
- next = NextUrlField()
- username = UserNameField(required=False,skip_clean=True)
- password = forms.CharField(max_length=128,
- widget=forms.widgets.PasswordInput(attrs={'class':'required login'}),
- required=False)
+class LoginForm(forms.Form):
+ """All-inclusive login form.
- def __init__(self, data=None, files=None, auto_id='id_%s',
- prefix=None, initial=None):
- super(ClassicLoginForm, self).__init__(data, files, auto_id,
- prefix, initial)
- self.user_cache = None
+ handles the following:
- def _clean_nonempty_field(self,field):
- value = None
- if field in self.cleaned_data:
- value = str(self.cleaned_data[field]).strip()
- if value == '':
- value = None
- self.cleaned_data[field] = value
- return value
+ * password login
+ * change of password
+ * openid login (of all types - direct, usename, generic url-based)
+ * oauth login
+ * facebook login (javascript-based facebook's sdk)
+ """
+ next = NextUrlField()
+ login_provider_name = LoginProviderField()
+ openid_login_token = forms.CharField(
+ max_length=256,
+ required = False,
+ )
+ username = UserNameField(required=False, skip_clean=True)
+ password = forms.CharField(
+ max_length=128,
+ widget=forms.widgets.PasswordInput(
+ attrs={'class':'required login'}
+ ),
+ required=False
+ )
+ password_action = forms.CharField(
+ max_length=32,
+ required=False,
+ widget=forms.widgets.HiddenInput()
+ )
+ new_password = forms.CharField(
+ max_length=128,
+ widget=forms.widgets.PasswordInput(
+ attrs={'class':'required login'}
+ ),
+ required=False
+ )
+ new_password_retyped = forms.CharField(
+ max_length=128,
+ widget=forms.widgets.PasswordInput(
+ attrs={'class':'required login'}
+ ),
+ required=False
+ )
+
+ def set_error_if_missing(self, field_name, error_message):
+ """set's error message on a field
+ if the field is not present in the cleaned_data dictionary
+ """
+ if field_name not in self.cleaned_data:
+ self._errors[field_name] = self.error_class([error_message])
- def clean_username(self):
- return self._clean_nonempty_field('username')
+ def set_password_login_error(self):
+ """sets a parameter flagging that login with
+ password had failed
+ """
+ #add monkey-patch parameter
+ #this is used in the signin.html template
+ self.password_login_failed = True
+
+ def set_password_change_error(self):
+ """sets a parameter flagging that
+ password change failed
+ """
+ #add monkey-patch parameter
+ #this is used in the signin.html template
+ self.password_change_failed = True
- def clean_password(self):
- return self._clean_nonempty_field('password')
def clean(self):
- """
- this clean function actually cleans username and password
-
- test if password is valid for this username
- this is really the "authenticate" function
- also openid_auth is not an authentication backend
- since it's written in a way that does not comply with
- the Django convention
+ """besides input data takes data from the
+ login provider settings
+ and stores final digested data into
+ the cleaned_data
+
+ the idea is that cleaned data can be used directly
+ to enact the signin action, without post-processing
+ of the data
+
+ contents of cleaned_data depends on the type
+ of login
"""
+ providers = util.get_login_providers()
- error_list = []
- username = self.cleaned_data['username']
- password = self.cleaned_data['password']
+ if 'login_provider_name' in self.cleaned_data:
+ provider_name = self.cleaned_data['login_provider_name']
+ else:
+ raise forms.ValidationError('no login provider specified')
+
+ provider_data = providers[provider_name]
+
+ provider_type = provider_data['type']
+
+ if provider_type == 'password':
+ self.do_clean_password_fields()
+ self.cleaned_data['login_type'] = 'password'
+ elif provider_type.startswith('openid'):
+ self.do_clean_openid_fields(provider_data)
+ self.cleaned_data['login_type'] = 'openid'
+ elif provider_type == 'oauth':
+ self.cleaned_data['login_type'] = 'oauth'
+ pass
+ elif provider_type == 'facebook':
+ self.cleaned_data['login_type'] = 'facebook'
+ #self.do_clean_oauth_fields()
+
+ print 'returning cleaned data'
- self.user_cache = None
- if username and password:
- if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
- pw_ok = False
- try:
- pw_ok = EXTERNAL_LOGIN_APP.api.check_password(username,password)
- except forms.ValidationError, e:
- error_list.extend(e.messages)
- if pw_ok:
- external_user = ExternalLoginData.objects.get(external_username=username)
- if external_user.user == None:
- return self.cleaned_data
- user = external_user.user
- openid_logins = user.userassociation_set.all()
-
- if len(openid_logins) > 0:
- msg1 = _('Account with this name already exists on the forum')
- msg2 = _('can\'t have two logins to the same account yet, sorry.')
- error_list.append(msg1)
- error_list.append(msg2)
- self._errors['__all__'] = forms.util.ErrorList(error_list)
- return self.cleaned_data
- else:
- #synchronize password with external login
- user.set_password(password)
- user.save()
- #this auth will always succeed
- self.user_cache = authenticate(username=user.username,\
- password=password)
- else:
- #keep self.user_cache == None
- #nothing to do, error message will be set below
- pass
- else:
- self.user_cache = authenticate(username=username, password=password)
-
- if self.user_cache is None:
- del self.cleaned_data['username']
- del self.cleaned_data['password']
- error_list.insert(0,(_("Please enter valid username and password "
- "(both are case-sensitive).")))
- elif self.user_cache.is_active == False:
- error_list.append(_("This account is inactive."))
- if len(error_list) > 0:
- error_list.insert(0,_('Login failed.'))
- elif password == None and username == None:
- error_list.append(_('Please enter username and password'))
- elif password == None:
- error_list.append(_('Please enter your password'))
- elif username == None:
- error_list.append(_('Please enter user name'))
- if len(error_list) > 0:
- self._errors['__all__'] = forms.util.ErrorList(error_list)
return self.cleaned_data
- def get_user(self):
- """ get authenticated user """
- return self.user_cache
-
+ def do_clean_openid_fields(self, provider_data):
+ """returns fake openid_url value
+ created based on provider_type (subtype of openid)
+ and the
+ """
+ openid_endpoint = provider_data['openid_endpoint']
+ openid_type = provider_data['type']
+ if openid_type == 'openid-direct':
+ openid_url = openid_endpoint
+ else:
+ error_message = _('Please enter your %(username_token)s') % \
+ {'username_token': provider_data['extra_token_name']}
+ self.set_error_if_missing('openid_login_token', error_message)
+ if 'openid_login_token' in self.cleaned_data:
+ openid_login_token = self.cleaned_data['openid_login_token']
+
+ if openid_type == 'openid-username':
+ openid_url = openid_endpoint % {'username': openid_login_token}
+ elif openid_type == 'openid-generic':
+ openid_url = openid_login_token
+ else:
+ raise ValueError('unknown openid type %s' % openid_type)
+
+ self.cleaned_data['openid_url'] = openid_url
+
+ def do_clean_password_fields(self):
+ """cleans password fields appropriate for
+ the selected password_action, which can be either
+ "login" or "change_password"
+ new password is checked for minimum length and match to initial entry
+ """
+ password_action = self.cleaned_data.get('password_action', None)
+ if password_action == 'login':
+ #if it's login with password - password and user name are required
+ self.set_error_if_missing(
+ 'username',
+ _('Please, enter your user name')
+ )
+ self.set_error_if_missing(
+ 'password',
+ _('Please, enter your password')
+ )
+
+ elif password_action == 'change_password':
+ #if it's change password - new_password and new_password_retyped
+ self.set_error_if_missing(
+ 'new_password',
+ _('Please, enter your new password')
+ )
+ self.set_error_if_missing(
+ 'new_password_retyped',
+ _('Please, enter your new password')
+ )
+ field_set = set(('new_password', 'new_password_retyped'))
+ if field_set.issubset(self.cleaned_data.keys()):
+ new_password = self.cleaned_data[
+ 'new_password'
+ ].strip()
+ new_password_retyped = self.cleaned_data[
+ 'new_password_retyped'
+ ].strip()
+ if new_password != new_password_retyped:
+ error_message = _('Passwords did not match')
+ error = self.error_class([error_message])
+ self._errors['new_password_retyped'] = error
+ self.set_password_change_error()
+ del self.cleaned_data['new_password']
+ del self.cleaned_data['new_password_retyped']
+ else:
+ #validate password
+ if len(new_password) < askbot_const.PASSWORD_MIN_LENGTH:
+ del self.cleaned_data['new_password']
+ del self.cleaned_data['new_password_retyped']
+ error_message = _(
+ 'Please choose password > %(len)s characters'
+ ) % {'len': askbot_const.PASSWORD_MIN_LENGTH}
+ error = self.error_class([error_message])
+ self._errors['new_password'] = error
+ self.set_password_change_error()
+ else:
+ error_message = 'unknown password action'
+ logging.critical(error_message)
+ self._errors['password_action'] = self.error_class([error_message])
+ raise forms.ValidationError(error_message)
+ print 'password fields were cleaned'
class OpenidRegisterForm(forms.Form):
""" openid signin form """
@@ -182,45 +319,13 @@ class OpenidRegisterForm(forms.Form):
username = UserNameField()
email = UserEmailField()
-class OpenidVerifyForm(forms.Form):
- """ openid verify form (associate an openid with an account) """
- next = NextUrlField()
- username = UserNameField(must_exist=True)
- password = forms.CharField(max_length=128,
- widget=forms.widgets.PasswordInput(attrs={'class':'required login'}))
-
- def __init__(self, data=None, files=None, auto_id='id_%s',
- prefix=None, initial=None):
- super(OpenidVerifyForm, self).__init__(data, files, auto_id,
- prefix, initial)
- self.user_cache = None
-
- def clean_password(self):
- """ test if password is valid for this user """
- if 'username' in self.cleaned_data and \
- 'password' in self.cleaned_data:
- self.user_cache = authenticate(
- username = self.cleaned_data['username'],
- password = self.cleaned_data['password']
- )
- if self.user_cache is None:
- raise forms.ValidationError(_("Please enter a valid \
- username and password. Note that both fields are \
- case-sensitive."))
- elif self.user_cache.is_active == False:
- raise forms.ValidationError(_("This account is inactive."))
- return self.cleaned_data['password']
-
- def get_user(self):
- """ get authenticated user """
- return self.user_cache
-
class ClassicRegisterForm(SetPasswordForm):
""" legacy registration form """
next = NextUrlField()
username = UserNameField()
email = UserEmailField()
+ login_provider = PasswordLoginProviderField()
#fields password1 and password2 are inherited
recaptcha = ReCaptchaField()
@@ -278,6 +383,22 @@ class AccountRecoveryForm(forms.Form):
this form merely checks that entered email
"""
email = forms.EmailField()
+
+ def clean_email(self):
+ """check if email exists in the database
+ and if so, populate 'user' field in the cleaned data
+ with the user object
+ """
+ if 'email' in self.cleaned_data:
+ email = self.cleaned_data['email']
+ print 'got email "%s"' % email
+ try:
+ user = User.objects.get(email=email)
+ self.cleaned_data['user'] = user
+ except User.DoesNotExist:
+ del self.cleaned_data['email']
+ message = _('Sorry, we don\'t have this email address in the database')
+ raise forms.ValidationError(message)
class ChangeopenidForm(forms.Form):
""" change openid form """
diff --git a/askbot/deps/django_authopenid/models.py b/askbot/deps/django_authopenid/models.py
index b5162064..8a1fa118 100644
--- a/askbot/deps/django_authopenid/models.py
+++ b/askbot/deps/django_authopenid/models.py
@@ -34,6 +34,8 @@ class UserAssociation(models.Model):
"""
model to manage association between openid and user
"""
+ #todo: rename this field so that it sounds good for other methods
+ #for exaple, for password provider this will hold password
openid_url = models.CharField(blank=False, max_length=255)
user = models.ForeignKey(User)
#in the future this must be turned into an
diff --git a/askbot/deps/django_authopenid/tests.py b/askbot/deps/django_authopenid/tests.py
new file mode 100644
index 00000000..28c7ffd6
--- /dev/null
+++ b/askbot/deps/django_authopenid/tests.py
@@ -0,0 +1,22 @@
+from unittest import TestCase
+from askbot.deps.django_authopenid.forms import LoginForm
+from askbot import const
+
+class LoginFormTests(TestCase):
+
+ def test_fail_change_to_short_password(self):
+ new_pass = '1'
+ data = {
+ 'login_provider_name': 'local',
+ 'password_action': 'change_password',
+ 'new_password': new_pass,
+ 'new_password_retyped': new_pass
+ }
+ assert(len(new_pass) < const.PASSWORD_MIN_LENGTH)
+ form = LoginForm(data)
+ result = form.is_valid()
+ #print form.errors
+ self.assertFalse(result)
+ self.assertEquals(form.initial['new_password'], None)
+ #self.assertFalse('new_password_retyped' in form.cleaned_data)
+
diff --git a/askbot/deps/django_authopenid/urls.py b/askbot/deps/django_authopenid/urls.py
index faa4569d..bbd0f2a2 100644
--- a/askbot/deps/django_authopenid/urls.py
+++ b/askbot/deps/django_authopenid/urls.py
@@ -26,15 +26,19 @@ urlpatterns = patterns('askbot.deps.django_authopenid.views',
url(r'^%s$' % _('signout/'), 'signout', name='user_signout'),
url(r'^%s%s$' % (_('signin/'), _('complete/')), 'complete_signin',
name='user_complete_signin'),
+ url(
+ r'^%s%s$' % (_('signin/'), _('complete-oauth/')),
+ 'complete_oauth_signin',
+ name='user_complete_oauth_signin'
+ ),
url(r'^%s$' % _('register/'), 'register', name='user_register'),
- #url(r'^%s$' % _('signup/'), 'signup', name='user_signup'),
- #disable current sendpw function
- #url(r'^%s$' % _('sendpw/'), 'sendpw', name='user_sendpw'),
- #url(r'^%s%s$' % (_('password/'), _('confirm/')), 'confirmchangepw', name='user_confirmchangepw'),
-
+ url(
+ r'^%s$' % _('signup/'),
+ 'signup_with_password',
+ name='user_signup_with_password'
+ ),
# manage account settings
#url(r'^$', 'account_settings', name='user_account_settings'),
- #url(r'^%s$' % _('password/'), 'changepw', name='user_changepw'),
#url(r'^%s%s$' % (_('email/'),_('validate/')), 'changeemail', name='user_validateemail',kwargs = {'action':'validate'}),
#url(r'^%s%s$' % (_('email/'), _('change/')), 'changeemail', name='user_changeemail'),
#url(r'^%s%s$' % (_('email/'), _('sendkey/')), 'send_email_key', name='send_email_key'),
@@ -43,26 +47,8 @@ urlpatterns = patterns('askbot.deps.django_authopenid.views',
#url(r'^%s$' % _('openid/'), 'changeopenid', name='user_changeopenid'),
#url(r'^%s$' % _('delete/'), 'delete', name='user_delete'),
url(
- r'^%s$' % _('delete_login_method/'),#thid method is ajax only
+ r'^delete_login_method/$',#this method is ajax only
'delete_login_method',
name ='delete_login_method'
),
)
-
-#todo move these out of this file completely
-if settings.USE_EXTERNAL_LEGACY_LOGIN:
- from askbot.forms import NotARobotForm
- EXTERNAL_LOGIN_APP = settings.LOAD_EXTERNAL_LOGIN_APP()
- urlpatterns += patterns('',
- url('^%s$' % _('external-login/forgot-password/'),\
- 'askbot.deps.django_authopenid.views.external_legacy_login_info', \
- name='user_external_legacy_login_issues'),
- url('^%s$' % _('external-login/signup/'), \
- EXTERNAL_LOGIN_APP.views.signup,\
- name='user_external_legacy_login_signup'),
-# url('^%s$' % _('external-login/signup/'), \
-# EXTERNAL_LOGIN_APP.forms.RegisterFormWizard( \
-# [EXTERNAL_LOGIN_APP.forms.RegisterForm, \
-# NotARobotForm]),\
-# name='user_external_legacy_login_signup'),
- )
diff --git a/askbot/deps/django_authopenid/util.py b/askbot/deps/django_authopenid/util.py
index 977311ae..594c70d1 100644
--- a/askbot/deps/django_authopenid/util.py
+++ b/askbot/deps/django_authopenid/util.py
@@ -1,12 +1,25 @@
# -*- coding: utf-8 -*-
+import urlparse
+import urllib
from openid.store.interface import OpenIDStore
from openid.association import Association as OIDAssociation
from openid.extensions import sreg
import openid.store
+import oauth2 as oauth
from django.db.models.query import Q
from django.conf import settings
from django.core.urlresolvers import reverse
+from django.utils.datastructures import SortedDict
+from django.utils.translation import ugettext as _
+from django.core.exceptions import ImproperlyConfigured
+
+try:
+ from hashlib import md5
+except:
+ from md5 import md5
+
+from askbot.conf import settings as askbot_settings
# needed for some linux distributions like debian
try:
@@ -142,3 +155,421 @@ def get_provider_name(openid_url):
base_url = bits[2] #assume this is base url
url_bits = base_url.split('.')
return url_bits[-2].lower()
+
+def get_major_login_providers():
+ """returns a tuple with data about login providers
+ whose icons are to be shown in large format
+
+ items of tuple are dictionaries with keys:
+
+ * name
+ * display_name
+ * icon_media_path (relative to /media directory)
+ * type (oauth|openid-direct|openid-generic|openid-username|password)
+
+ Fields dependent on type:
+
+ * openid_endpoint (required for type=openid|openid-username)
+ for type openid-username - the string must have %(username)s
+ format variable, plain string url otherwise
+ * extra_token_name - required for type=openid-username
+ describes name of required extra token - e.g. "XYZ user name"
+ """
+ data = SortedDict()
+ if askbot_settings.RECAPTCHA_KEY and askbot_settings.RECAPTCHA_SECRET:
+ data['local'] = {
+ 'name': 'local',
+ 'display_name': askbot_settings.APP_SHORT_NAME,
+ 'extra_token_name': 'Askbot user name and password',
+ 'type': 'password',
+ 'create_password_prompt': _('Create a password-protected account'),
+ 'change_password_prompt': _('Change your password'),
+ 'icon_media_path': askbot_settings.LOCAL_LOGIN_ICON,
+ }
+ if askbot_settings.FACEBOOK_KEY and askbot_settings.FACEBOOK_SECRET:
+ data['facebook'] = {
+ 'name': 'facebook',
+ 'display_name': 'Facebook',
+ 'type': 'facebook',
+ 'icon_media_path': '/jquery-openid/images/facebook.gif',
+ }
+ if askbot_settings.TWITTER_KEY and askbot_settings.TWITTER_SECRET:
+ data['twitter'] = {
+ 'name': 'twitter',
+ 'display_name': 'Twitter',
+ 'type': 'oauth',
+ 'request_token_url': 'https://api.twitter.com/oauth/request_token',
+ 'access_token_url': 'https://api.twitter.com/oauth/access_token',
+ 'authorize_url': 'https://api.twitter.com/oauth/authorize',
+ 'authenticate_url': 'https://api.twitter.com/oauth/authenticate',
+ 'get_user_id_url': 'https://twitter.com/account/verify_credentials.json',
+ 'icon_media_path': '/jquery-openid/images/twitter.gif',
+ 'get_user_id_function': lambda data: data['user_id'],
+ }
+ def get_linked_in_user_id(data):
+ consumer = oauth.Consumer(data['consumer_key'], data['consumer_secret'])
+ token = oauth.Token(data['oauth_token'], data['oauth_token_secret'])
+ client = oauth.Client(consumer, token=token)
+ url = 'https://api.linkedin.com/v1/people/~:(first-name,last-name,id)'
+ response, content = client.request(url, 'GET')
+ if response['status'] == '200':
+ import re
+ id_re = re.compile(r'<id>([^<]+)</id>')
+ matches = id_re.search(content)
+ if matches:
+ return matches.group(1)
+ raise OAuthError()
+
+ if askbot_settings.LINKEDIN_KEY and askbot_settings.LINKEDIN_SECRET:
+ data['linkedin'] = {
+ 'name': 'linkedin',
+ 'display_name': 'LinkedIn',
+ 'type': 'oauth',
+ 'request_token_url': 'https://api.linkedin.com/uas/oauth/requestToken',
+ 'access_token_url': 'https://api.linkedin.com/uas/oauth/accessToken',
+ 'authorize_url': 'https://www.linkedin.com/uas/oauth/authorize',
+ 'authenticate_url': 'https://www.linkedin.com/uas/oauth/authenticate',
+ 'icon_media_path': '/jquery-openid/images/linkedin.gif',
+ 'get_user_id_function': get_linked_in_user_id
+ }
+ data['google'] = {
+ 'name': 'google',
+ 'display_name': 'Google',
+ 'type': 'openid-direct',
+ 'icon_media_path': '/jquery-openid/images/google.gif',
+ 'openid_endpoint': 'https://www.google.com/accounts/o8/id',
+ }
+ data['yahoo'] = {
+ 'name': 'yahoo',
+ 'display_name': 'Yahoo',
+ 'type': 'openid-direct',
+ 'icon_media_path': '/jquery-openid/images/yahoo.gif',
+ 'tooltip_text': _('Sign in with Yahoo'),
+ 'openid_endpoint': 'http://yahoo.com',
+ }
+ data['aol'] = {
+ 'name': 'aol',
+ 'display_name': 'AOL',
+ 'type': 'openid-username',
+ 'extra_token_name': _('AOL screen name'),
+ 'icon_media_path': '/jquery-openid/images/aol.gif',
+ 'openid_endpoint': 'http://openid.aol.com/%(username)s'
+ }
+ data['openid'] = {
+ 'name': 'openid',
+ 'display_name': 'OpenID',
+ 'type': 'openid-generic',
+ 'extra_token_name': _('OpenID url'),
+ 'icon_media_path': '/jquery-openid/images/openid.gif',
+ 'openid_endpoint': None,
+ }
+ return data
+
+def get_minor_login_providers():
+ """same as get_major_login_providers
+ but those that are to be displayed with small buttons
+
+ structure of dictionary values is the same as in get_major_login_providers
+ """
+ data = SortedDict()
+ data['myopenid'] = {
+ 'name': 'myopenid',
+ 'display_name': 'MyOpenid',
+ 'type': 'openid-username',
+ 'extra_token_name': _('MyOpenid user name'),
+ 'icon_media_path': '/jquery-openid/images/myopenid-2.png',
+ 'openid_endpoint': 'http://%(username)s.myopenid.com'
+ }
+ data['flickr'] = {
+ 'name': 'flickr',
+ 'display_name': 'Flickr',
+ 'type': 'openid-username',
+ 'extra_token_name': _('Flickr user name'),
+ 'icon_media_path': '/jquery-openid/images/flickr.png',
+ 'openid_endpoint': 'http://flickr.com/%(username)s/'
+ }
+ data['flickr'] = {
+ 'name': 'flickr',
+ 'display_name': 'Flickr',
+ 'type': 'openid-username',
+ 'extra_token_name': _('Flickr user name'),
+ 'icon_media_path': '/jquery-openid/images/flickr.png',
+ 'openid_endpoint': 'http://flickr.com/%(username)s/'
+ }
+ data['technorati'] = {
+ 'name': 'technorati',
+ 'display_name': 'Technorati',
+ 'type': 'openid-username',
+ 'extra_token_name': _('Technorati user name'),
+ 'icon_media_path': '/jquery-openid/images/technorati-1.png',
+ 'openid_endpoint': 'http://technorati.com/people/technorati/%(username)s/'
+ }
+ data['wordpress'] = {
+ 'name': 'wordpress',
+ 'display_name': 'WordPress',
+ 'type': 'openid-username',
+ 'extra_token_name': _('WordPress blog name'),
+ 'icon_media_path': '/jquery-openid/images/wordpress.png',
+ 'openid_endpoint': 'http://%(username)s.wordpress.com'
+ }
+ data['blogger'] = {
+ 'name': 'blogger',
+ 'display_name': 'Blogger',
+ 'type': 'openid-username',
+ 'extra_token_name': _('Blogger blog name'),
+ 'icon_media_path': '/jquery-openid/images/blogger-1.png',
+ 'openid_endpoint': 'http://%(username)s.blogspot.com'
+ }
+ data['livejournal'] = {
+ 'name': 'livejournal',
+ 'display_name': 'LiveJournal',
+ 'type': 'openid-username',
+ 'extra_token_name': _('LiveJournal blog name'),
+ 'icon_media_path': '/jquery-openid/images/livejournal-1.png',
+ 'openid_endpoint': 'http://%(username)s.livejournal.com'
+ }
+ data['claimid'] = {
+ 'name': 'claimid',
+ 'display_name': 'ClaimID',
+ 'type': 'openid-username',
+ 'extra_token_name': _('ClaimID user name'),
+ 'icon_media_path': '/jquery-openid/images/claimid-0.png',
+ 'openid_endpoint': 'http://%(username)s.livejournal.com'
+ }
+ data['vidoop'] = {
+ 'name': 'vidoop',
+ 'display_name': 'Vidoop',
+ 'type': 'openid-username',
+ 'extra_token_name': _('Vidoop user name'),
+ 'icon_media_path': '/jquery-openid/images/vidoop.png',
+ 'openid_endpoint': 'http://%(username)s.myvidoop.com/'
+ }
+ data['verisign'] = {
+ 'name': 'verisign',
+ 'display_name': 'Verisign',
+ 'type': 'openid-username',
+ 'extra_token_name': _('Verisign user name'),
+ 'icon_media_path': '/jquery-openid/images/verisign-2.png',
+ 'openid_endpoint': 'http://%(username)s.pip.verisignlabs.com/'
+ }
+ return data
+
+def get_login_providers():
+ """return all login providers in one sorted dict
+ """
+ data = get_major_login_providers()
+ data.update(get_minor_login_providers())
+ return data
+
+def set_login_provider_tooltips(provider_dict, active_provider_names = None):
+ """adds appropriate tooltip_text field to each provider
+ record, if second argument is None, then tooltip is of type
+ signin with ..., otherwise it's more elaborate -
+ depending on the type of provider and whether or not it's one of
+ currently used
+ """
+ for provider in provider_dict.values():
+ if active_provider_names:
+ if provider['name'] in active_provider_names:
+ if provider['type'] == 'password':
+ tooltip = _('Change your %(provider)s password') % \
+ {'provider': provider['display_name']}
+ else:
+ tooltip = _(
+ 'Click to see if your %(provider)s '
+ 'signin still works for %(site_name)s'
+ ) % {
+ 'provider': provider['display_name'],
+ 'site_name': askbot_settings.APP_SHORT_NAME
+ }
+ else:
+ if provider['type'] == 'password':
+ tooltip = _(
+ 'Create password for %(provider)s'
+ ) % {'provider': provider['display_name']}
+ else:
+ tooltip = _(
+ 'Connect your %(provider)s account '
+ 'to %(site_name)s'
+ ) % {
+ 'provider': provider['display_name'],
+ 'site_name': askbot_settings.APP_SHORT_NAME
+ }
+ else:
+ if provider['type'] == 'password':
+ tooltip = _(
+ 'Signin with %(provider)s user name and password'
+ ) % {
+ 'provider': provider['display_name'],
+ 'site_name': askbot_settings.APP_SHORT_NAME
+ }
+ else:
+ tooltip = _(
+ 'Sign in with your %(provider)s account'
+ ) % {'provider': provider['display_name']}
+ provider['tooltip_text'] = tooltip
+
+
+def get_oauth_parameters(provider_name):
+ """retrieves OAuth protocol parameters
+ from hardcoded settings and adds some
+ from the livesettings
+
+ because this function uses livesettings
+ it should not be called at compile time
+ otherwise there may be strange errors
+ """
+ providers = get_login_providers()
+ data = providers[provider_name]
+ if data['type'] != 'oauth':
+ raise ValueError('oauth provider expected, %s found' % data['type'])
+
+ if provider_name == 'twitter':
+ consumer_key = askbot_settings.TWITTER_KEY
+ consumer_secret = askbot_settings.TWITTER_SECRET
+ elif provider_name == 'linkedin':
+ consumer_key = askbot_settings.LINKEDIN_KEY
+ consumer_secret = askbot_settings.LINKEDIN_SECRET
+ else:
+ raise ValueError('sorry, only linkedin and twitter oauth for now')
+
+ data['consumer_key'] = consumer_key
+ data['consumer_secret'] = consumer_secret
+
+ return data
+
+
+class OAuthError(Exception):
+ """Error raised by the OAuthConnection class
+ """
+ pass
+
+
+class OAuthConnection(object):
+ """a simple class wrapping oauth2 library
+ """
+
+ def __init__(self, provider_name, callback_url = None):
+ """initializes oauth connection
+ """
+ self.provider_name = provider_name
+ self.parameters = get_oauth_parameters(provider_name)
+ self.callback_url = callback_url
+ self.consumer = oauth.Consumer(
+ self.parameters['consumer_key'],
+ self.parameters['consumer_secret'],
+ )
+
+ def start(self, callback_url = None):
+
+ if callback_url is None:
+ callback_url = self.callback_url
+
+ client = oauth.Client(self.consumer)
+ request_url = self.parameters['request_token_url']
+
+ if callback_url:
+ callback_url = '%s%s' % (askbot_settings.APP_URL, callback_url)
+ request_body = urllib.urlencode(dict(oauth_callback=callback_url))
+
+ self.request_token = self.send_request(
+ client = client,
+ url = request_url,
+ method = 'POST',
+ body = request_body
+ )
+ else:
+ self.request_token = self.send_request(
+ client,
+ request_url,
+ 'GET'
+ )
+
+ def send_request(self, client=None, url=None, method='GET', **kwargs):
+
+ response, content = client.request(url, method, **kwargs)
+ if response['status'] == '200':
+ return dict(urlparse.parse_qsl(content))
+ else:
+ raise OAuthError('response is %s' % response)
+
+ def get_token(self):
+ return self.request_token
+
+ def get_user_id(self, oauth_token = None, oauth_verifier = None):
+
+ token = oauth.Token(
+ oauth_token['oauth_token'],
+ oauth_token['oauth_token_secret']
+ )
+ token.set_verifier(oauth_verifier)
+ client = oauth.Client(self.consumer, token = token)
+ url = self.parameters['access_token_url']
+ #there must be some provider-specific post-processing
+ data = self.send_request(client = client, url=url, method='GET')
+ data['consumer_key'] = self.parameters['consumer_key']
+ data['consumer_secret'] = self.parameters['consumer_secret']
+ return self.parameters['get_user_id_function'](data)
+
+ def get_auth_url(self, login_only = False):
+ """returns OAuth redirect url.
+ callback_url is the redirect that will be used by the
+ provider server, if login_only is True, authentication
+ endpoint will be used, if available, otherwise authorization
+ url (potentially granting full access to the server) will
+ be used.
+
+ Typically, authentication-only endpoint simplifies the
+ signin process, but does not allow advanced access to the
+ content on the OAuth-enabled server
+ """
+
+ endpoint_url = self.parameters.get('authorize_url', None)
+ if login_only == True:
+ endpoint_url = self.parameters.get(
+ 'authenticate_url',
+ endpoint_url
+ )
+ if endpoint_url is None:
+ raise ImproperlyConfigured('oauth parameters are incorrect')
+
+ auth_url = '%s?oauth_token=%s' % \
+ (
+ endpoint_url,
+ self.request_token['oauth_token'],
+ )
+
+ return auth_url
+
+class FacebookError(Exception):
+ """Raised when there's something not right
+ with FacebookConnect
+ """
+ pass
+
+def get_facebook_user_id(request):
+ try:
+ key = askbot_settings.FACEBOOK_KEY
+ secret = askbot_settings.FACEBOOK_SECRET
+
+ fb_cookie = request.COOKIES['fbs_%s' % key]
+ fb_response = dict(urlparse.parse_qsl(fb_cookie))
+
+ signature = None
+ payload = ''
+ for key in sorted(fb_response.keys()):
+ if key != 'sig':
+ payload += '%s=%s' % (key, fb_response[key])
+
+ if 'sig' in fb_response:
+ if md5(payload + secret).hexdigest() != fb_response['sig']:
+ raise ValueError('signature does not match')
+ else:
+ raise ValueError('no signature in facebook response')
+
+ if 'uid' not in fb_response:
+ raise ValueError('no user id in facebook response')
+
+ return fb_response['uid']
+ except Exception, e:
+ raise FacebookError(e)
diff --git a/askbot/deps/django_authopenid/views.py b/askbot/deps/django_authopenid/views.py
index 4e8e5720..f9f02f8f 100644
--- a/askbot/deps/django_authopenid/views.py
+++ b/askbot/deps/django_authopenid/views.py
@@ -66,8 +66,11 @@ import urllib
from askbot import forms as askbot_forms
from askbot.deps.django_authopenid import util
+from askbot.deps.django_authopenid import decorators
from askbot.deps.django_authopenid.models import UserAssociation, UserPasswordQueue, ExternalLoginData
from askbot.deps.django_authopenid import forms
+from askbot.deps.django_authopenid.backends import AuthBackend
+from django import forms as django_forms
import logging
from askbot.utils.forms import get_next_url
@@ -81,9 +84,6 @@ def login(request,user):
if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
EXTERNAL_LOGIN_APP.api.login(request,user)
- if not hasattr(user, 'backend'):
- user.backend = "django.contrib.auth.backends.ModelBackend"
-
#1) get old session key
session_key = request.session.session_key
#2) get old search state
@@ -109,8 +109,6 @@ def logout(request):
request.session['search_state'].set_logged_out()
request.session.modified = True
_logout(request)
- if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
- EXTERNAL_LOGIN_APP.api.logout(request)
def get_url_host(request):
if request.is_secure():
@@ -170,11 +168,19 @@ def complete(request, on_success=None, on_failure=None, return_to=None):
# make sure params are encoded in utf8
params = dict((k,smart_unicode(v)) for k, v in request.GET.items())
openid_response = consumer.complete(params, return_to)
+
+ try:
+ logging.debug(u'returned openid parameters were: %s' % unicode(params))
+ except Exception, e:
+ logging.critical(u'fix logging statement above ' + unicode(e))
if openid_response.status == SUCCESS:
- logging.debug('SUCCESS')
- return on_success(request, openid_response.identity_url,
- openid_response)
+ logging.debug('openid response status is SUCCESS')
+ return on_success(
+ request,
+ openid_response.identity_url,
+ openid_response
+ )
elif openid_response.status == CANCEL:
logging.debug('CANCEL')
return on_failure(request, 'The request was canceled')
@@ -205,13 +211,75 @@ def default_on_failure(request, message):
def not_authenticated(func):
""" decorator that redirect user to next page if
- he is already logged."""
+ he/she is already logged in."""
def decorated(request, *args, **kwargs):
if request.user.is_authenticated():
return HttpResponseRedirect(get_next_url(request))
return func(request, *args, **kwargs)
return decorated
+def complete_oauth_signin(request):
+ if 'next_url' in request.session:
+ next_url = request.session['next_url']
+ del request.session['next_url']
+ else:
+ next_url = reverse('index')
+
+ if 'denied' in request.GET:
+ return HttpResponseRedirect(next_url)
+ if 'oauth_problem' in request.GET:
+ return HttpResponseRedirect(next_url)
+
+ try:
+ oauth_token = request.GET['oauth_token']
+ logging.debug('have token %s' % oauth_token)
+ oauth_verifier = request.GET['oauth_verifier']
+ logging.debug('have verifier %s' % oauth_verifier)
+ session_oauth_token = request.session['oauth_token']
+ logging.debug('have token from session')
+ assert(oauth_token == session_oauth_token['oauth_token'])
+
+ oauth_provider_name = request.session['oauth_provider_name']
+ logging.debug('have saved provider name')
+
+ oauth = util.OAuthConnection(oauth_provider_name)
+
+ user_id = oauth.get_user_id(
+ oauth_token = session_oauth_token,
+ oauth_verifier = oauth_verifier
+ )
+ logging.debug('have %s user id=%s' % (oauth_provider_name, user_id))
+
+ user = authenticate(
+ oauth_user_id = user_id,
+ provider_name = oauth_provider_name,
+ method = 'oauth'
+ )
+
+ logging.debug('finalizing oauth signin')
+
+ request.session['email'] = ''#todo: pull from profile
+ request.session['username'] = ''#todo: pull from profile
+ request.session['user_identifier'] = user_id
+ request.session['login_provider_name'] = oauth_provider_name
+
+ return finalize_generic_signin(
+ request = request,
+ user = user,
+ user_identifier = user_id,
+ login_provider_name = oauth_provider_name,
+ redirect_url = next_url
+ )
+
+ except Exception, e:
+ logging.critical(e)
+ msg = _('Unfortunately, there was some problem when '
+ 'connecting to %(provider)s, please try again '
+ 'or use another provider'
+ ) % {'provider': oauth_provider_name}
+ request.user.message_set.create(message = msg)
+ return HttpResponseRedirect(next_url)
+
#@not_authenticated
def signin(
request,
@@ -230,141 +298,140 @@ def signin(
request.encoding = 'UTF-8'
on_failure = signin_failure
email_feeds_form = askbot_forms.SimpleEmailSubscribeForm()
- initial_data = {'next': get_next_url(request)}
- logging.debug('next url is %s' % get_next_url(request))
- openid_login_form = forms.OpenidSigninForm(initial = initial_data)
- password_login_form = forms.ClassicLoginForm(initial = initial_data)
+ next_url = get_next_url(request)
+ logging.debug('next url is %s' % next_url)
+
+ if next_url == reverse('user_signin'):
+ next_url = '%(next)s?next=%(next)s' % {'next': next_url}
+
+ login_form = forms.LoginForm(initial = {'next': next_url})
+
+ #todo: get next url make it sticky if next is 'user_signin'
if request.method == 'POST':
- #'blogin' - password login
- if 'blogin' in request.POST:
- logging.debug('processing classic login form submission')
- password_login_form = forms.ClassicLoginForm(request.POST)
- if password_login_form.is_valid():
- #have login and password and need to login through external website
- if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
- username = password_login_form.cleaned_data['username']
- password = password_login_form.cleaned_data['password']
- next = password_login_form.cleaned_data['next']
- if password_login_form.get_user() == None:
- #need to create internal user
-
- #1) save login and password temporarily in session
- request.session['external_username'] = username
- request.session['external_password'] = password
-
- #2) try to extract user email and nickname from external service
- email = EXTERNAL_LOGIN_APP.api.get_email(username,password)
- screen_name = EXTERNAL_LOGIN_APP.api.get_screen_name(username,password)
-
- #3) see if username clashes with some existing user
- #if so, we have to prompt the user to pick a different name
- username_taken = User.is_username_taken(screen_name)
-
- email_feeds_form = askbot_forms.SimpleEmailSubscribeForm()
- form_data = {'username':screen_name,'email':email,'next':next}
- form = forms.OpenidRegisterForm(initial=form_data)
- template_data = {'form1':form,'username':screen_name,\
- 'email_feeds_form':email_feeds_form,\
- 'provider':mark_safe(settings.EXTERNAL_LEGACY_LOGIN_PROVIDER_NAME),\
- 'login_type':'legacy',\
- 'gravatar_faq_url':reverse('faq') + '#gravatar',\
- 'external_login_name_is_taken':username_taken}
- return render_to_response('authopenid/complete.html',template_data,\
- context_instance=RequestContext(request))
- else:
- #user existed, external password is ok
- user = password_login_form.get_user()
- login(request,user)
- response = HttpResponseRedirect(get_next_url(request))
- EXTERNAL_LOGIN_APP.api.set_login_cookies(response,user)
- return response
- else:
- #regular password authentication
- user = password_login_form.get_user()
- login(request, user)
- return HttpResponseRedirect(get_next_url(request))
-
- elif 'bnewaccount' in request.POST:
- logging.debug('processing classic (login/password) create account form submission')
- #register externally logged in password user with a new local account
- if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
- form = forms.OpenidRegisterForm(request.POST)
- email_feeds_form = askbot_forms.SimpleEmailSubscribeForm(request.POST)
- form1_is_valid = form.is_valid()
- form2_is_valid = email_feeds_form.is_valid()
- if form1_is_valid and form2_is_valid:
- #create the user
- username = form.cleaned_data['username']
- password = request.session.get('external_password',None)
- email = form.cleaned_data['email']
- if password and username:
- User.objects.create_user(username,email,password)
- user = authenticate(username=username,password=password)
- EXTERNAL_LOGIN_APP.api.connect_local_user_to_external_user(user,username,password)
- external_username = request.session['external_username']
- eld = ExternalLoginData.objects.get(external_username=external_username)
- eld.user = user
- eld.save()
- login(request,user)
- email_feeds_form.save(user)
- del request.session['external_username']
- del request.session['external_password']
- response = HttpResponseRedirect(reverse('index'))
- EXTERNAL_LOGIN_APP.api.set_login_cookies(response, user)
- return response
+
+ login_form = forms.LoginForm(request.POST)
+ if login_form.is_valid():
+
+ provider_name = login_form.cleaned_data['login_provider_name']
+ if login_form.cleaned_data['login_type'] == 'password':
+
+ password_action = login_form.cleaned_data['password_action']
+ if password_action == 'login':
+ user = authenticate(
+ username = login_form.cleaned_data['username'],
+ password = login_form.cleaned_data['password'],
+ provider_name = provider_name,
+ method = 'password'
+ )
+ if user is None:
+ login_form.set_password_login_error()
else:
- if password:
- del request.session['external_username']
- if username:
- del request.session['external_password']
- return HttpResponseServerError()
+ login(request, user)
+ #todo: here we might need to set cookies
+ #for external login sites
+ return HttpResponseRedirect(next_url)
+ elif password_action == 'change_password':
+ if request.user.is_authenticated():
+ new_password = login_form.cleaned_data['new_password']
+ AuthBackend.set_password(
+ user=request.user,
+ password=new_password,
+ provider_name=provider_name
+ )
+ request.user.message_set.create(
+ message = _('Your new password saved')
+ )
+ print 'password changed'
+ return HttpResponseRedirect(next_url)
else:
- #register after authenticating with external login
- #provider
- username = request.POST.get('username',None)
- provider = mark_safe(settings.EXTERNAL_LEGACY_LOGIN_PROVIDER_NAME)
- username_taken = User.is_username_taken(username)
- data = {'login_type':'legacy','form1':form,'username':username,\
- 'email_feeds_form':email_feeds_form,'provider':provider,\
- 'gravatar_faq_url':reverse('faq') + '#gravatar',\
- 'external_login_name_is_taken':username_taken}
- return render_to_response('authopenid/complete.html',data,
- context_instance=RequestContext(request))
- else:
- raise Http404
-
- elif 'bsignin' in request.POST or 'openid_username' in request.POST:
- logging.debug('processing signin with openid submission')
- openid_login_form = forms.OpenidSigninForm(request.POST)
- if openid_login_form.is_valid():
- logging.debug('OpenidSigninForm is valid')
- next = openid_login_form.cleaned_data['next']
- print next
- sreg_req = sreg.SRegRequest(optional=['nickname', 'email'])
+ logging.critical(
+ 'unknown password action %s' % password_action
+ )
+ raise Http404
- if next == reverse('user_signin'):
- #if next was explicitly set to the url of this function
- #that means user wants to stay on this page indefinitely
- #so we need to double-up the "next"
- #alternatively, next parameter must be set explicitly everywhere
- #to the url of the referer page (except the signin page)
- url_encoded_next_url = urllib.urlencode({'next':'%s?next=%s' % (next, next)})
- else:
- url_encoded_next_url = urllib.urlencode({'next': next})
+ elif login_form.cleaned_data['login_type'] == 'openid':
+ #initiate communication process
+ logging.debug('processing signin with openid submission')
+
+ #todo: make a simple-use wrapper for openid protocol
+ sreg_req = sreg.SRegRequest(optional=['nickname', 'email'])
redirect_to = "%s%s?%s" % (
get_url_host(request),
reverse('user_complete_signin'),
- url_encoded_next_url
+ urllib.urlencode({'next':next_url})
)
- return ask_openid(request,
- openid_login_form.cleaned_data['openid_url'],
- redirect_to,
- on_failure=signin_failure,
- sreg_request=sreg_req)
+ return ask_openid(
+ request,
+ login_form.cleaned_data['openid_url'],
+ redirect_to,
+ on_failure=signin_failure,
+ sreg_request=sreg_req
+ )
+
+ elif login_form.cleaned_data['login_type'] == 'oauth':
+ try:
+ #this url may need to have "next" piggibacked onto
+ callback_url = reverse('user_complete_oauth_signin')
+
+ connection = util.OAuthConnection(
+ provider_name,
+ callback_url = callback_url
+ )
+
+ connection.start()
+
+ request.session['oauth_token'] = connection.get_token()
+ request.session['oauth_provider_name'] = provider_name
+ request.session['next_url'] = next_url#special case for oauth
+
+ oauth_url = connection.get_auth_url(login_only = False)
+ return HttpResponseRedirect(oauth_url)
+
+ except util.OAuthError, e:
+ logging.critical(unicode(e))
+ msg = _('Unfortunately, there was some problem when '
+ 'connecting to %(provider)s, please try again '
+ 'or use another provider'
+ ) % {'provider': provider_name}
+ request.user.message_set.create(message = msg)
+
+ elif login_form.cleaned_data['login_type'] == 'facebook':
+ #have to redirect for consistency
+ #there is a requirement that 'complete_signin'
+ try:
+ #this call may raise FacebookError
+ user_id = util.get_facebook_user_id(request)
+
+ user = authenticate(
+ method = 'facebook',
+ facebook_user_id = user_id
+ )
+
+ return finalize_generic_signin(
+ request = request,
+ user = user,
+ user_identifier = user_id,
+ login_provider_name = 'facebook',
+ redirect_url = next_url
+ )
+
+ except util.FacebookError, e:
+ logging.critical(unicode(e))
+ msg = _('Unfortunately, there was some problem when '
+ 'connecting to %(provider)s, please try again '
+ 'or use another provider'
+ ) % {'provider': 'Facebook'}
+ request.user.message_set.create(message = msg)
+
else:
- logging.debug('OpenidSigninForm is NOT valid! -> redisplay login view')
+ #raise 500 error - unknown login type
+ pass
+ else:
+ logging.debug('login form is not valid')
+ logging.debug(login_form.errors)
+ logging.debug(request.REQUEST)
if request.method == 'GET' and request.user.is_authenticated():
view_subtype = 'change_openid'
@@ -373,17 +440,16 @@ def signin(
return show_signin_view(
request,
- password_login_form = password_login_form,
- openid_login_form = openid_login_form,
+ login_form = login_form,
view_subtype = view_subtype
)
def show_signin_view(
request,
- password_login_form = None,
- openid_login_form = None,
+ login_form = None,
account_recovery_form = None,
account_recovery_message = None,
+ sticky = False,
view_subtype = 'default'
):
"""url-less utility function that populates
@@ -391,15 +457,21 @@ def show_signin_view(
and returns its rendered output
"""
- allowed_subtypes = ('default', 'add_openid', 'email_sent', 'change_openid')
+ allowed_subtypes = (
+ 'default', 'add_openid',
+ 'email_sent', 'change_openid',
+ 'bad_key'
+ )
+
assert(view_subtype in allowed_subtypes)
- initial_data = {'next': get_next_url(request)}
+ if sticky:
+ next_url = reverse('user_signin')
+ else:
+ next_url = get_next_url(request)
- if password_login_form is None:
- password_login_form = forms.ClassicLoginForm(initial = initial_data)
- if openid_login_form is None:
- openid_login_form = forms.OpenidSigninForm(initial = initial_data)
+ if login_form is None:
+ login_form = forms.LoginForm(initial = {'next': next_url})
if account_recovery_form is None:
account_recovery_form = forms.AccountRecoveryForm()#initial = initial_data)
@@ -427,17 +499,23 @@ def show_signin_view(
else:
answer = None
- if view_subtype == 'default' or view_subtype == 'email_sent':
+ if request.user.is_authenticated():
+ existing_login_methods = UserAssociation.objects.filter(user = request.user)
+
+ if view_subtype == 'default':
page_title = _('Please click any of the icons below to sign in')
+ elif view_subtype == 'email_sent':
+ page_title = _('Account recovery email sent')
elif view_subtype == 'change_openid':
- existing_login_methods = UserAssociation.objects.filter(user = request.user)
if len(existing_login_methods) == 0:
page_title = _('Please add one or more login methods.')
else:
page_title = _('If you wish, please add, remove or re-validate your login methods')
elif view_subtype == 'add_openid':
page_title = _('Please wait a second! Your account is recovered, but ...')
-
+ elif view_subtype == 'bad_key':
+ page_title = _('Sorry, this account recovery key has expired or is invalid')
+
logging.debug('showing signin view')
data = {
'page_class': 'openid-signin',
@@ -445,15 +523,33 @@ def show_signin_view(
'page_title': page_title,
'question':question,
'answer':answer,
- 'password_login_form': password_login_form,
- 'openid_login_form': openid_login_form,
+ 'login_form': login_form,
'account_recovery_form': account_recovery_form,
'openid_error_message': request.REQUEST.get('msg',''),
'account_recovery_message': account_recovery_message,
}
- if view_subtype == 'change_openid' and request.user.is_authenticated():
+ major_login_providers = util.get_major_login_providers()
+ minor_login_providers = util.get_minor_login_providers()
+
+ active_provider_names = None
+ if request.user.is_authenticated():
data['existing_login_methods'] = existing_login_methods
+ active_provider_names = [
+ item.provider_name for item in existing_login_methods
+ ]
+
+ util.set_login_provider_tooltips(
+ major_login_providers,
+ active_provider_names = active_provider_names
+ )
+ util.set_login_provider_tooltips(
+ minor_login_providers,
+ active_provider_names = active_provider_names
+ )
+
+ data['major_login_providers'] = major_login_providers.values()
+ data['minor_login_providers'] = minor_login_providers.values()
return render_to_response(
'authopenid/signin.html',
@@ -488,101 +584,105 @@ def delete_login_method(request):
def complete_signin(request):
""" in case of complete signin with openid """
logging.debug('')#blank log just for the trace
- return complete(request, signin_success, signin_failure,
- get_url_host(request) + reverse('user_complete_signin'))
+ return complete(
+ request,
+ on_success = signin_success,
+ on_failure = signin_failure,
+ return_to = get_url_host(request) + reverse('user_complete_signin')
+ )
def signin_success(request, identity_url, openid_response):
"""
- openid signin success.
-
- If the openid is already registered, the user is redirected to
- url set par next or in settings with OPENID_REDIRECT_NEXT variable.
- If none of these urls are set user is redirectd to /.
-
- if openid isn't registered user is redirected to register page.
+ this is not a view, has no url pointing to this
+
+ this function is called when OpenID provider returns
+ successful response to user authentication
+
+ Does actual authentication in Django site and
+ redirects to the registration page, if necessary
+ or adds another login method.
"""
logging.debug('')
- openid_ = util.from_openid_response(openid_response) #create janrain OpenID object
- request.session['openid'] = openid_
- try:
- logging.debug('trying to get user associated with this openid...')
- user_assoc = UserAssociation.objects.get(openid_url__exact = str(openid_))
- logging.debug('have openid, and logged in - success')
- if request.user.is_authenticated() and user_assoc.user != request.user:
- print 'stealing openid'
+ openid_data = util.from_openid_response(openid_response) #create janrain OpenID object
+ request.session['openid'] = openid_data
+
+ openid_url = str(openid_data)
+ user = authenticate(
+ openid_url = openid_url,
+ method = 'openid'
+ )
+
+ next_url = get_next_url(request)
+ provider_name = util.get_provider_name(openid_url)
+
+ request.session['email'] = openid_data.sreg.get('email', '')
+ request.session['username'] = openid_data.sreg.get('username', '')
+ request.session['user_identifier'] = openid_url
+ request.session['login_provider_name'] = provider_name
+
+ return finalize_generic_signin(
+ request = request,
+ user = user,
+ user_identifier = openid_url,
+ login_provider_name = provider_name,
+ redirect_url = next_url
+ )
+
+def finalize_generic_signin(
+ request = None,
+ user = None,
+ login_provider_name = None,
+ user_identifier = None,
+ redirect_url = None
+ ):
+ """non-view function
+ generic signin, run after all protocol-dependent details
+ have been resolved
+ """
+
+ if request.user.is_authenticated():
+ #this branch is for adding a new association
+ if user is None:
+ #register new association
+ UserAssociation(
+ user = request.user,
+ provider_name = login_provider_name,
+ openid_url = user_identifier,
+ last_used_timestamp = datetime.datetime.now()
+ ).save()
+ return HttpResponseRedirect(redirect_url)
+
+ elif user != request.user:
+ #prevent theft of account by another pre-existing user
logging.critical(
'possible account theft attempt by %s,%d to %s %d' % \
(
request.user.username,
request.user.id,
- user_assoc.user.username,
- user_assoc.user.id
+ user.username,
+ user.id
)
)
- raise Http404
+ logout(request)#log out current user
+ login(request, user)#login freshly authenticated user
+ return HttpResponseRedirect(redirect_url)
else:
- logging.debug('success')
- except UserAssociation.DoesNotExist:
- logging.debug('openid %s not found' % str(openid_))
- if request.user.is_anonymous():
- logging.debug('failed --> try to register brand new user')
- # try to register this new user
+ #user just checks if another login still works
+ msg = _('Your %(provider)s login works fine') % \
+ {'provider': login_provider_name}
+ request.user.message_set.create(message = msg)
+ return HttpResponseRedirect(redirect_url)
+ else:
+ if user is None:
+ #need to register
return register(request)
else:
- #store openid association
- provider_name = util.get_provider_name(str(openid_))
- try:
- logging.debug('recovering association via user and provider name')
- user_assoc = UserAssociation.objects.get(
- user = request.user,
- provider_name = provider_name
- )
- logging.debug('have %s replacing with new' % user_assoc.openid_url)
- user_assoc.openid_url = str(openid_)
- #association changed
- except UserAssociation.DoesNotExist:
- print 'creating a brand new association with this openid'
- user_assoc = UserAssociation(
- user = request.user,
- openid_url = str(openid_),
- provider_name = provider_name
- )
- #new association created
- except UserAssociation.MultipleObjectsReturned, error:
- msg = 'user %s (id=%d) has > 1 %s logins' % \
- (request.user.username, request.user.id, provider_name)
- logging.critical(message)
- raise error
-
- user_assoc.last_used_timestamp = datetime.datetime.now()
- user_assoc.save()
- message = _('Your %(provider)s login method saved.') % {'provider': provider_name}
- request.user.message_set.create(message = message)
- #set "account recovered" message
- logging.debug('redirecting to %s' % get_next_url(request))
- logging.debug('redirecting to %s' % get_next_url(request))
- return HttpResponseRedirect(get_next_url(request))
- except Exception, e:
- logging.critical(unicode(e))
-
- user_assoc.last_used_timestamp = datetime.datetime.now()
- if user_assoc.user.is_active:
- #this is a substitute for the "authenticate" function
- #todo - build a recular authenticate func and backend
- user_assoc.user.backend = "django.contrib.auth.backends.ModelBackend"
- logging.debug('user is active --> attached django auth ModelBackend --> calling login')
- login(request, user_assoc.user)
- logging.debug('success')
- else:
- msg = _( 'Sorry, your account is inactive and you '
- 'cannot sign in - please contact the site '
- 'administrator.')
- logging.debug('user is inactive, do not log them in')
- request.user.message_set.create(message=msg)
+ #authentication branch
+ login(request, user)
+ logging.debug('login success')
+ return HttpResponseRedirect(redirect_url)
- logging.debug('redirecting to %s' % get_next_url(request))
- return HttpResponseRedirect(get_next_url(request))
def is_association_exist(openid_url):
""" test if an openid is already in database """
@@ -595,9 +695,11 @@ def is_association_exist(openid_url):
return is_exist
@not_authenticated
-def register(request):
+def register(request, provider_name = None):
"""
- register an openid.
+ register view that processes
+ new user registratioins using openid or oauth
+ this function is also called directly from "signin_succes"
If user is already a member he can associate their openid with
their account.
@@ -611,102 +713,96 @@ def register(request):
"""
logging.debug('')
- openid_ = request.session.get('openid', None)
- next = get_next_url(request)
- if not openid_:
- logging.debug('oops, no openid in session --> go back to signin')
- return HttpResponseRedirect(reverse('user_signin') + '?next=%s' % next)
-
- nickname = openid_.sreg.get('nickname', '')
- email = openid_.sreg.get('email', '')
- openid_register_form = forms.OpenidRegisterForm(initial={
- 'next': next,
- 'username': nickname,
- 'email': email,
- })
- openid_verify_form = forms.OpenidVerifyForm(initial={
- 'next': next,
- 'username': nickname,
- })
- email_feeds_form = askbot_forms.SimpleEmailSubscribeForm()
-
- user_ = None
+
+ next_url = get_next_url(request)
+
+ if 'login_provider_name' not in request.session \
+ or 'user_identifier' not in request.session:
+ logging.critical('illegal attempt to register')
+ return HttpResponseRedirect(reverse('user_signin'))
+
+ user = None
is_redirect = False
+ username = request.session.get('username', '')
+ email = request.session.get('email', '')
logging.debug('request method is %s' % request.method)
+
+ register_form = forms.OpenidRegisterForm(
+ initial={
+ 'next': next_url,
+ 'username': request.session.get('username', ''),
+ 'email': request.session.get('email', ''),
+ }
+ )
+ email_feeds_form = askbot_forms.SimpleEmailSubscribeForm()
+
if request.method == 'POST':
- if 'bnewaccount' in request.POST.keys():
- logging.debug('trying to create new account associated with openid')
- openid_register_form = forms.OpenidRegisterForm(request.POST)
- email_feeds_form = askbot_forms.SimpleEmailSubscribeForm(request.POST)
- if not openid_register_form.is_valid():
- logging.debug('OpenidRegisterForm is INVALID')
- elif not email_feeds_form.is_valid():
- logging.debug('SimpleEmailSubscribeForm is INVALID')
- else:
- logging.debug('OpenidRegisterForm and SimpleEmailSubscribeForm are valid')
- next = openid_register_form.cleaned_data['next']
- is_redirect = True
- logging.debug('creatng new django user %s ...' % \
- openid_register_form.cleaned_data['username'])
- tmp_pwd = User.objects.make_random_password()
- user_ = User.objects.create_user(openid_register_form.cleaned_data['username'],
- openid_register_form.cleaned_data['email'], tmp_pwd)
-
- user_.set_unusable_password()
- # make association with openid
- logging.debug('creating new openid user association %s <--> %s' \
- % (user_.username, str(openid_)))
- uassoc = UserAssociation(openid_url=str(openid_), user_id=user_.id)
- uassoc.save()
-
- # login
- user_.backend = "django.contrib.auth.backends.ModelBackend"
- logging.debug('logging the user in')
- login(request, user_)
- logging.debug('saving email feed settings')
- email_feeds_form.save(user_)
- elif 'bverify' in request.POST.keys():
- logging.debug('processing OpenidVerify form')
- openid_verify_form = forms.OpenidVerifyForm(request.POST)
- if openid_verify_form.is_valid():
- logging.debug('form is valid')
- is_redirect = True
- next = openid_verify_form.cleaned_data['next']
- user_ = openid_verify_form.get_user()
- logging.debug('creating new openid user association %s <--> %s' \
- % (user_.username, str(openid_)))
- uassoc = UserAssociation(openid_url=str(openid_),
- user_id=user_.id)
- uassoc.save()
- logging.debug('logging the user in')
- login(request, user_)
-
+
+ logging.debug('trying to create new account associated with openid')
+ register_form = forms.OpenidRegisterForm(request.POST)
+ email_feeds_form = askbot_forms.SimpleEmailSubscribeForm(request.POST)
+ if not register_form.is_valid():
+ logging.debug('OpenidRegisterForm is INVALID')
+ elif not email_feeds_form.is_valid():
+ logging.debug('SimpleEmailSubscribeForm is INVALID')
+ else:
+ logging.debug('OpenidRegisterForm and SimpleEmailSubscribeForm are valid')
+ is_redirect = True
+ username = register_form.cleaned_data['username']
+ email = register_form.cleaned_data['email']
+
+ user = User.objects.create_user(username, email)
+
+ logging.debug('creating new openid user association for %s')
+
+ UserAssociation(
+ openid_url = request.session['user_identifier'],
+ user = user,
+ provider_name = request.session['login_provider_name'],
+ last_used_timestamp = datetime.datetime.now()
+ ).save()
+
+ del request.session['user_identifier']
+ del request.session['login_provider_name']
+
+ # login
+ logging.debug('logging the user in')
+
+ user = authenticate(method = 'force', user_id = user.id)
+ if user is None:
+ raise Exception('this does not make any sense')
+
+ user.backend = 'askbot.deps.django_authopenid.backends.AuthBackend'
+ login(request, user)
+
+ logging.debug('saving email feed settings')
+ email_feeds_form.save(user)
+
#check if we need to post a question that was added anonymously
#this needs to be a function call becase this is also done
#if user just logged in and did not need to create the new account
- if user_ != None:
+ if user != None:
if askbot_settings.EMAIL_VALIDATION == True:
logging.debug('sending email validation')
- send_new_email_key(user_,nomessage=True)
+ send_new_email_key(user, nomessage=True)
output = validation_email_sent(request)
- set_email_validation_message(user_) #message set after generating view
+ set_email_validation_message(user) #message set after generating view
return output
- if user_.is_authenticated():
+ if user.is_authenticated():
logging.debug('success, send user to main page')
return HttpResponseRedirect(reverse('index'))
else:
logging.debug('have really strange error')
raise Exception('openid login failed')#should not ever get here
- provider_name = util.get_provider_name(str(openid_))
-
- providers = {'yahoo':'<font color="purple">Yahoo!</font>',
- 'flickr':'<font color="#0063dc">flick</font><font color="#ff0084">r</font>&trade;',
- 'google':'Google&trade;',
- 'aol':'<font color="#31658e">AOL</font>',
- 'myopenid':'MyOpenID',
- }
+ providers = {
+ 'yahoo':'<font color="purple">Yahoo!</font>',
+ 'flickr':'<font color="#0063dc">flick</font><font color="#ff0084">r</font>&trade;',
+ 'google':'Google&trade;',
+ 'aol':'<font color="#31658e">AOL</font>',
+ 'myopenid':'MyOpenID',
+ }
if provider_name not in providers:
provider_logo = provider_name
logging.error('openid provider named "%s" has no pretty customized logo' % provider_name)
@@ -714,48 +810,38 @@ def register(request):
provider_logo = providers[provider_name]
logging.debug('printing authopenid/complete.html output')
- return render_to_response('authopenid/complete.html', {
- 'openid_register_form': openid_register_form,
- 'openid_verify_form': openid_verify_form,
- 'email_feeds_form': email_feeds_form,
- 'provider':mark_safe(provider_logo),
- 'username': nickname,
- 'email': email,
- 'login_type':'openid',
- 'gravatar_faq_url':reverse('faq') + '#gravatar',
- }, context_instance=RequestContext(request))
+ return render_to_response(
+ 'authopenid/complete.html',
+ {
+ 'openid_register_form': register_form,
+ 'email_feeds_form': email_feeds_form,
+ 'provider':mark_safe(provider_logo),
+ 'username': username,
+ 'email': email,
+ 'login_type':'openid',
+ 'gravatar_faq_url':reverse('faq') + '#gravatar',
+ },
+ context_instance=RequestContext(request)
+ )
def signin_failure(request, message):
"""
falure with openid signin. Go back to signin page.
-
- template : "authopenid/signin.html"
"""
- logging.debug('')
- next = get_next_url(request)
- openid_login_form = forms.OpenidSigninForm(initial={'next': next})
- password_login_form = forms.ClassicLoginForm(initial={'next': next})
-
- return render_to_response('authopenid/signin.html', {
- 'msg': message,
- 'password_login_form': password_login_form,
- 'openid_login_form': openid_login_form,
- }, context_instance=RequestContext(request))
+ request.user.message_set.create(message = message)
+ return show_signin_view(request)
@not_authenticated
-def signup(request):
+@decorators.valid_password_login_provider_required
+def signup_with_password(request):
+ """Create a password-protected account
+ template: authopenid/signup_with_password.html
"""
- signup page. Create a legacy account
-
- url : /signup/"
-
- templates: authopenid/signup.html, authopenid/confirm_email.txt
- """
- logging.debug('')
- if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
- logging.debug('handling external legacy login registration')
- return HttpResponseRedirect(reverse('user_external_legacy_login_signup'))
+
next = get_next_url(request)
+ #this is safe because second decorator cleans this field
+ provider_name = request.REQUEST['login_provider']
+
logging.debug('request method was %s' % request.method)
if request.method == 'POST':
form = forms.ClassicRegisterForm(request.POST)
@@ -764,52 +850,77 @@ def signup(request):
#validation outside if to remember form values
logging.debug('validating classic register form')
form1_is_valid = form.is_valid()
- logging.debug('classic register form validated')
+ if form1_is_valid:
+ logging.debug('classic register form validated')
+ else:
+ logging.debug('classic register form is not valid')
form2_is_valid = email_feeds_form.is_valid()
- logging.debug('email feeds form validated')
+ if form2_is_valid:
+ logging.debug('email feeds form validated')
+ else:
+ logging.debug('email feeds form is not valid')
if form1_is_valid and form2_is_valid:
logging.debug('both forms are valid')
next = form.cleaned_data['next']
username = form.cleaned_data['username']
password = form.cleaned_data['password1']
email = form.cleaned_data['email']
+ provider_name = form.cleaned_data['login_provider']
- user_ = User.objects.create_user( username,email,password )
+ User.objects.create_user(username, email, password)
logging.debug('new user %s created' % username)
- if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
- EXTERNAL_LOGIN_APP.api.create_user(username,email,password)
-
- user_.backend = "django.contrib.auth.backends.ModelBackend"
- login(request, user_)
+ if provider_name != 'local':
+ raise NotImplementedError('must run create external user code')
+
+ user = authenticate(
+ username = username,
+ password = password,
+ provider_name = provider_name,
+ method = 'password'
+ )
+
+ login(request, user)
logging.debug('new user logged in')
- email_feeds_form.save(user_)
+ email_feeds_form.save(user)
logging.debug('email feeds form saved')
# send email
- subject = _("Welcome email subject line")
- message_template = loader.get_template(
- 'authopenid/confirm_email.txt'
- )
- message_context = Context({
- 'signup_url': askbot_settings.APP_URL + reverse('user_signin'),
- 'username': username,
- 'password': password,
- })
- message = message_template.render(message_context)
- send_mail(subject, message, settings.DEFAULT_FROM_EMAIL,
- [user_.email])
- logging.debug('new user with login and password created, confirmation email sent!')
+ #subject = _("Welcome email subject line")
+ #message_template = loader.get_template(
+ # 'authopenid/confirm_email.txt'
+ #)
+ #message_context = Context({
+ # 'signup_url': askbot_settings.APP_URL + reverse('user_signin'),
+ # 'username': username,
+ # 'password': password,
+ #})
+ #message = message_template.render(message_context)
+ #send_mail(subject, message, settings.DEFAULT_FROM_EMAIL,
+ # [user.email])
+ #logging.debug('new password acct created, confirmation email sent!')
return HttpResponseRedirect(next)
else:
+ #todo: this can be solved with a decorator, maybe
+ form.initial['login_provider'] = provider_name
logging.debug('create classic account forms were invalid')
else:
- form = forms.ClassicRegisterForm(initial={'next':next})
+ #todo: here we have duplication of get_password_login_provider...
+ form = forms.ClassicRegisterForm(
+ initial={
+ 'next':next,
+ 'login_provider': provider_name
+ }
+ )
email_feeds_form = askbot_forms.SimpleEmailSubscribeForm()
logging.debug('printing legacy signup form')
- return render_to_response('authopenid/signup.html', {
- 'form': form,
- 'email_feeds_form': email_feeds_form
- }, context_instance=RequestContext(request))
+ context_data = {
+ 'form': form,
+ 'email_feeds_form': email_feeds_form
+ }
+ return render_to_response(
+ 'authopenid/signup_with_password.html',
+ context_data,
+ context_instance=RequestContext(request))
#what if request is not posted?
@login_required
@@ -870,39 +981,6 @@ def account_settings(request):
'is_openid': is_openid
}, context_instance=RequestContext(request))
-@login_required
-def changepw(request):
- """
- change password view.
-
- url : /changepw/
- template: authopenid/changepw.html
- """
- logging.debug('')
- user_ = request.user
-
- if user_.has_usable_password():
- if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
- return HttpResponseRedirect(reverse('user_external_legacy_login_issues'))
- else:
- raise Http404
-
- if request.POST:
- form = forms.ChangePasswordForm(request.POST, user=user_)
- if form.is_valid():
- user_.set_password(form.cleaned_data['password1'])
- user_.save()
- msg = _("Password changed.")
- redirect = "%s?msg=%s" % (
- reverse('user_account_settings'),
- urlquote_plus(msg))
- return HttpResponseRedirect(redirect)
- else:
- form = forms.ChangePasswordForm(user=user_)
-
- return render_to_response('authopenid/changepw.html', {'form': form },
- context_instance=RequestContext(request))
-
def find_email_validation_messages(user):
msg_text = _('your email needs to be validated see %(details_url)s') \
% {'details_url':reverse('faq') + '#validate'}
@@ -992,55 +1070,41 @@ def account_recover(request, key = None):
if request.method == 'POST':
form = forms.AccountRecoveryForm(request.POST)
if form.is_valid():
- email = form.cleaned_data['email']
- try:
- user = User.objects.get(email = email)
- send_new_email_key(user, nomessage = True)
- message = _(
- 'Please check your email and visit the enclosed link.'
- )
- return show_signin_view(
- request,
- account_recovery_message = message,
- view_subtype = 'email_sent'
- )
- except User.DoesNotExist:
- message = _(
- 'Sorry we cound not find this email it our database. '
- 'If you think that this is an error - please contact '
- 'the site administrator'
- )
- return show_signin_view(
- request,
- account_recovery_message = message,
- account_recovery_form = form
- )
- else:
- message = _('Please enter a valid email address')
+ user = form.cleaned_data['user']
+ send_new_email_key(user, nomessage = True)
+ message = _(
+ 'Please check your email and visit the enclosed link.'
+ )
return show_signin_view(
request,
account_recovery_message = message,
+ view_subtype = 'email_sent'
+ )
+ else:
+ return show_signin_view(
+ request,
account_recovery_form = form
)
else:
if key is None:
- raise Http404
- try:
- user = User.objects.get(email_key = key)
- user.email_key = None #delete the key so that nobody could use it again
- #todo: add email_key_timestamp field
- #and check key age
- login(request, user)
- return show_signin_view(request, view_subtype = 'add_openid')
+ return HttpResponseRedirect(reverse('user_signin'))
- except User.DoesNotExist:
- message = _('Sorry this account recovery key has '
- 'expired or is invalid, please request a new one'
- )
+ user = authenticate(email_key = key, method = 'email')
+ if user:
+ if request.user.is_authenticated():
+ if user != request.user:
+ logout(request.user)
+ login(request, user)
+ else:
+ login(request, user)
+ #need to show "sticky" signin view here
return show_signin_view(
- request,
- account_recovery_message = message,
- )
+ request,
+ view_subtype = 'add_openid',
+ sticky = True
+ )
+ else:
+ return show_signin_view(request, view_subtype = 'bad_key')
#internal server view used as return value by other views
@@ -1105,8 +1169,12 @@ def changeemail(request, action='change'):
elif not request.POST and 'openid.mode' in request.GET:
redirect_to = get_url_host(request) + reverse('user_changeemail')
- return complete(request, emailopenid_success,
- emailopenid_failure, redirect_to)
+ return complete(
+ request,
+ on_success = emailopenid_success,
+ on_failure = emailopenid_failure,
+ return_to = redirect_to
+ )
else:
form = forms.ChangeEmailForm(initial={'email': user_.email},
user=user_)
@@ -1194,8 +1262,12 @@ def changeopenid(request):
redirect_to, on_failure=changeopenid_failure)
elif not request.POST and has_openid:
if 'openid.mode' in request.GET:
- return complete(request, changeopenid_success,
- changeopenid_failure, redirect_to)
+ return complete(
+ request,
+ on_success = changeopenid_success,
+ on_failure = changeopenid_failure,
+ return_to = redirect_to
+ )
form = forms.ChangeopenidForm(initial={'openid_url': openid_url }, user=user_)
return render_to_response('authopenid/changeopenid.html', {
@@ -1272,8 +1344,12 @@ def delete(request):
return ask_openid(request, form.cleaned_data['password'],
redirect_to, on_failure=deleteopenid_failure)
elif not request.POST and 'openid.mode' in request.GET:
- return complete(request, deleteopenid_success, deleteopenid_failure,
- redirect_to)
+ return complete(
+ request,
+ on_success = deleteopenid_success,
+ on_failure = deleteopenid_failure,
+ return_to = redirect_to
+ )
form = forms.DeleteForm(user=user_)
@@ -1320,112 +1396,3 @@ def external_legacy_login_info(request):
return render_to_response('authopenid/external_legacy_login_info.html',
{'feedback_url':feedback_url},
context_instance=RequestContext(request))
-
-def sendpw(request):
- """
- send a new password to the user. It return a mail with
- a new pasword and a confirm link in. To activate the
- new password, the user should click on confirm link.
-
- url : /sendpw/
-
- templates : authopenid/sendpw_email.txt, authopenid/sendpw.html
- """
- logging.debug('')
- if settings.USE_EXTERNAL_LEGACY_LOGIN == True:
- logging.debug('delegating to view dealing with external password recovery')
- return HttpResponseRedirect(reverse('user_external_legacy_login_issues'))
-
- msg = request.GET.get('msg','')
- logging.debug('request method is %s' % request.method)
- if request.method == 'POST':
- form = forms.EmailPasswordForm(request.POST)
- if form.is_valid():
- logging.debug('EmailPasswordForm is valid')
- new_pw = User.objects.make_random_password()
- confirm_key = UserPasswordQueue.objects.get_new_confirm_key()
- try:
- uqueue = UserPasswordQueue.objects.get(
- user=form.user_cache
- )
- except:
- uqueue = UserPasswordQueue(
- user=form.user_cache
- )
- uqueue.new_password = new_pw
- uqueue.confirm_key = confirm_key
- uqueue.save()
- # send email
- subject = _("Request for new password")
- message_template = loader.get_template(
- 'authopenid/sendpw_email.txt')
- key_link = askbot_settings.APP_URL + reverse('user_confirmchangepw') + '?key=' + confirm_key
- logging.debug('emailing new password for %s' % form.user_cache.username)
- message_context = Context({
- 'site_url': askbot_settings.APP_URL + reverse('index'),
- 'key_link': key_link,
- 'username': form.user_cache.username,
- 'password': new_pw,
- })
- message = message_template.render(message_context)
- send_mail(subject, message, settings.DEFAULT_FROM_EMAIL,
- [form.user_cache.email])
- msg = _("A new password and the activation link were sent to your email address.")
- else:
- form = forms.EmailPasswordForm()
-
- logging.debug('showing reset password form')
- return render_to_response('authopenid/sendpw.html', {
- 'form': form,
- 'msg': msg
- }, context_instance=RequestContext(request))
-
-def confirmchangepw(request):
- """
- view to set new password when the user click on confirm link
- in its mail. Basically it check if the confirm key exist, then
- replace old password with new password and remove confirm
- ley from the queue. Then it redirect the user to signin
- page.
-
- url : /sendpw/confirm/?key
-
- """
- logging.debug('')
- confirm_key = request.GET.get('key', '')
- if not confirm_key:
- logging.error('someone called confirm password without a key!')
- return HttpResponseRedirect(reverse('index'))
-
- try:
- uqueue = UserPasswordQueue.objects.get(
- confirm_key__exact=confirm_key
- )
- except:
- msg = _("Could not change password. Confirmation key '%s'\
- is not registered." % confirm_key)
- logging.error(msg)
- redirect = "%s?msg=%s" % (
- reverse('user_sendpw'), urlquote_plus(msg))
- return HttpResponseRedirect(redirect)
-
- try:
- user_ = User.objects.get(id=uqueue.user.id)
- except:
- msg = _("Can not change password. User don't exist anymore \
- in our database.")
- logging.error(msg)
- redirect = "%s?msg=%s" % (reverse('user_sendpw'),
- urlquote_plus(msg))
- return HttpResponseRedirect(redirect)
-
- user_.set_password(uqueue.new_password)
- user_.save()
- uqueue.delete()
- msg = _("Password changed for %s. You may now sign in." %
- user_.username)
- logging.debug(msg)
- redirect = "%s?msg=%s" % (reverse('user_signin'),
- urlquote_plus(msg))
-
- return HttpResponseRedirect(redirect)
diff --git a/askbot/deps/recaptcha_django/__init__.py b/askbot/deps/recaptcha_django/__init__.py
index 3423a106..518c214b 100644
--- a/askbot/deps/recaptcha_django/__init__.py
+++ b/askbot/deps/recaptcha_django/__init__.py
@@ -37,7 +37,7 @@ class ReCaptchaWidget(Widget):
final_attrs = self.build_attrs(attrs)
error = final_attrs.get('error', None)
html = captcha.displayhtml(
- askbot_settings.RECAPTCHA_PUBLIC_KEY,
+ askbot_settings.RECAPTCHA_KEY,
error=error
)
options = u',\n'.join([u'%s: "%s"' % (k, conditional_escape(v)) \
@@ -73,7 +73,7 @@ class ReCaptchaField(Field):
raise ValidationError(_('Invalid request'))
resp = captcha.submit(value.get('challenge', None),
value.get('response', None),
- askbot_settings.RECAPTCHA_PRIVATE_KEY,
+ askbot_settings.RECAPTCHA_SECRET,
value.get('ip', None))
if not resp.is_valid:
self.widget.attrs['error'] = resp.error_code
diff --git a/askbot/doc/source/change-password.png b/askbot/doc/source/change-password.png
new file mode 100644
index 00000000..bb2f5cf0
--- /dev/null
+++ b/askbot/doc/source/change-password.png
Binary files differ
diff --git a/askbot/doc/source/connect-aol.png b/askbot/doc/source/connect-aol.png
new file mode 100644
index 00000000..e7961f09
--- /dev/null
+++ b/askbot/doc/source/connect-aol.png
Binary files differ
diff --git a/askbot/doc/source/initialize-database-tables.rst b/askbot/doc/source/initialize-database-tables.rst
index 83e70c14..8a37549d 100644
--- a/askbot/doc/source/initialize-database-tables.rst
+++ b/askbot/doc/source/initialize-database-tables.rst
@@ -13,6 +13,7 @@ When you will be suggested to create a superuser, answer **no**.
Then run::
python manage.py migrate askbot
+ python manage.py migrate django_authopenid #embedded login application
.. note::
diff --git a/askbot/doc/source/manage-logins1.png b/askbot/doc/source/manage-logins1.png
new file mode 100644
index 00000000..e28676a6
--- /dev/null
+++ b/askbot/doc/source/manage-logins1.png
Binary files differ
diff --git a/askbot/doc/source/password-signin.png b/askbot/doc/source/password-signin.png
new file mode 100644
index 00000000..0e9a75f1
--- /dev/null
+++ b/askbot/doc/source/password-signin.png
Binary files differ
diff --git a/askbot/doc/source/pw-register.png b/askbot/doc/source/pw-register.png
new file mode 100644
index 00000000..1e7ecd43
--- /dev/null
+++ b/askbot/doc/source/pw-register.png
Binary files differ
diff --git a/askbot/doc/source/recover-account.png b/askbot/doc/source/recover-account.png
new file mode 100644
index 00000000..8ee05ede
--- /dev/null
+++ b/askbot/doc/source/recover-account.png
Binary files differ
diff --git a/askbot/doc/source/signin-with-aol.png b/askbot/doc/source/signin-with-aol.png
new file mode 100644
index 00000000..4f769b77
--- /dev/null
+++ b/askbot/doc/source/signin-with-aol.png
Binary files differ
diff --git a/askbot/management/commands/send_email_alerts.py b/askbot/management/commands/send_email_alerts.py
index fcdac21e..10aec596 100644
--- a/askbot/management/commands/send_email_alerts.py
+++ b/askbot/management/commands/send_email_alerts.py
@@ -11,7 +11,7 @@ from django.utils.translation import ugettext as _
from django.utils.translation import ungettext
from django.conf import settings
from askbot.conf import settings as askbot_settings
-from django.utils.datastructures import SortedDict
+from django.utils.datastructures import SortedDict
from django.contrib.contenttypes.models import ContentType
from askbot import const
diff --git a/askbot/middleware/spaceless.py b/askbot/middleware/spaceless.py
index 16b11522..9e6b5503 100644
--- a/askbot/middleware/spaceless.py
+++ b/askbot/middleware/spaceless.py
@@ -3,13 +3,24 @@ Middleware that strips whitespace between html tags
copied from David Cramer's blog
http://www.davidcramer.net/code/369/spaceless-html-in-django.html
"""
-from django.utils.html import strip_spaces_between_tags as short
-
+import re
+from django.utils.functional import allow_lazy
+from django.utils.encoding import force_unicode
+
+def reduce_spaces_between_tags(value):
+ """Returns the given HTML with all spaces between tags removed.
+ ,but one. One space is left so that consecutive links and other things
+ do not appear glued together
+ slight mod of django.utils.html import strip_spaces_between_tags
+ """
+ return re.sub(r'>\s+<', '> <', force_unicode(value))
+reduce_spaces_between_tags = allow_lazy(reduce_spaces_between_tags, unicode)
+
class SpacelessMiddleware(object):
def process_response(self, request, response):
"""strips whitespace from all documents
whose content type is text/html
"""
if 'text/html' in response['Content-Type']:
- response.content = short(response.content)
+ response.content = reduce_spaces_between_tags(response.content)
return response
diff --git a/askbot/models/meta.py b/askbot/models/meta.py
index 83e82933..0da701bc 100644
--- a/askbot/models/meta.py
+++ b/askbot/models/meta.py
@@ -156,7 +156,7 @@ class Comment(base.MetaContent, base.UserContent):
exclude_list = None
):
"""get list of users who want instant notifications
- about this post
+ about this comment
argument potential_subscribers is required as it saves on db hits
"""
diff --git a/askbot/setup_templates/settings.py b/askbot/setup_templates/settings.py
index 78e0ab71..1f01dadd 100644
--- a/askbot/setup_templates/settings.py
+++ b/askbot/setup_templates/settings.py
@@ -93,7 +93,7 @@ MIDDLEWARE_CLASSES = (
'askbot.middleware.anon_user.ConnectToSessionMessagesMiddleware',
'askbot.middleware.pagesize.QuestionsPageSizeMiddleware',
'askbot.middleware.cancel.CancelActionMiddleware',
- #'askbot.deps.recaptcha_django.middleware.ReCaptchaMiddleware',
+ 'askbot.deps.recaptcha_django.middleware.ReCaptchaMiddleware',
'django.middleware.transaction.TransactionMiddleware',
#'debug_toolbar.middleware.DebugToolbarMiddleware',
'askbot.middleware.view_log.ViewLogMiddleware',
diff --git a/askbot/skins/default/media/images/pw-login.gif b/askbot/skins/default/media/images/pw-login.gif
new file mode 100644
index 00000000..f88b1bcf
--- /dev/null
+++ b/askbot/skins/default/media/images/pw-login.gif
Binary files differ
diff --git a/askbot/skins/default/media/jquery-openid/images/aol.gif b/askbot/skins/default/media/jquery-openid/images/aol.gif
index decc4f12..24d1e152 100755
--- a/askbot/skins/default/media/jquery-openid/images/aol.gif
+++ b/askbot/skins/default/media/jquery-openid/images/aol.gif
Binary files differ
diff --git a/askbot/skins/default/media/jquery-openid/images/facebook.gif b/askbot/skins/default/media/jquery-openid/images/facebook.gif
index b997b358..c5586455 100755
--- a/askbot/skins/default/media/jquery-openid/images/facebook.gif
+++ b/askbot/skins/default/media/jquery-openid/images/facebook.gif
Binary files differ
diff --git a/askbot/skins/default/media/jquery-openid/images/google.gif b/askbot/skins/default/media/jquery-openid/images/google.gif
index 1b6cd07b..65395365 100755
--- a/askbot/skins/default/media/jquery-openid/images/google.gif
+++ b/askbot/skins/default/media/jquery-openid/images/google.gif
Binary files differ
diff --git a/askbot/skins/default/media/jquery-openid/images/linkedin.gif b/askbot/skins/default/media/jquery-openid/images/linkedin.gif
new file mode 100644
index 00000000..36e049ac
--- /dev/null
+++ b/askbot/skins/default/media/jquery-openid/images/linkedin.gif
Binary files differ
diff --git a/askbot/skins/default/media/jquery-openid/images/openid.gif b/askbot/skins/default/media/jquery-openid/images/openid.gif
index c718b0e6..19eb7c6f 100755
--- a/askbot/skins/default/media/jquery-openid/images/openid.gif
+++ b/askbot/skins/default/media/jquery-openid/images/openid.gif
Binary files differ
diff --git a/askbot/skins/default/media/jquery-openid/images/twitter.gif b/askbot/skins/default/media/jquery-openid/images/twitter.gif
new file mode 100644
index 00000000..173cace1
--- /dev/null
+++ b/askbot/skins/default/media/jquery-openid/images/twitter.gif
Binary files differ
diff --git a/askbot/skins/default/media/jquery-openid/images/yahoo.gif b/askbot/skins/default/media/jquery-openid/images/yahoo.gif
index 42adbfa5..614910a9 100755
--- a/askbot/skins/default/media/jquery-openid/images/yahoo.gif
+++ b/askbot/skins/default/media/jquery-openid/images/yahoo.gif
Binary files differ
diff --git a/askbot/skins/default/media/jquery-openid/jquery.openid.js b/askbot/skins/default/media/jquery-openid/jquery.openid.js
index 120b034b..3efc8172 100755
--- a/askbot/skins/default/media/jquery-openid/jquery.openid.js
+++ b/askbot/skins/default/media/jquery-openid/jquery.openid.js
@@ -1,111 +1,383 @@
//jQuery OpenID Plugin 1.1 Copyright 2009 Jarrett Vance http://jvance.com/pages/jQueryOpenIdPlugin.xhtml
-$.fn.openid = function() {
- var $this = $(this);
+$.fn.authenticator = function() {
+ var signin_page = $(this);
+ var signin_form = $('#signin-form');
+ var openid_login_token_input = $('input[name=openid_login_token]');
+ var openid_login_token_input_fields = $('#openid-fs');
+ var provider_name_input = $('input[name=login_provider_name]');
+ var email_input_fields = $('#email-input-fs');
+ var account_recovery_heading = $('#account-recovery-heading');
+ var account_recovery_hint = $('#account-recovery-form>.hint');
+ var account_recovery_link = $('#account-recovery-form>.hint>span.link');
+ var account_recovery_text_span = $('#account-recovery-form>.hint>span.text');
+ var password_input_fields = $('#password-fs');
+ var existing_login_methods_div = $('#existing-login-methods');
+ var openid_submit_button = $('input[name=openid_login_with_extra_token]');
+ var existing_login_methods = {};
- //name input value - needed for name based OpenID
- var $usr = $this.find('input[name=openid_username]');
+ var account_recovery_question_text = account_recovery_heading.html();
+ var account_recovery_prompt_text = account_recovery_text_span.html();
- //final url input value
- var $id = $this.find('input[name=openid_url]');
+ var setup_event_handlers = function(elements, handler_function){
+ elements.unbind('click').click(handler_function);
+ elements.unbind('keypress').keypress(
+ function(e){
+ if ((e.which && e.which == 13)||(e.keyCode && e.keyCode == 13)){
+ return handler_function();
+ }
+ }
+ );
+ };
- //beginning and end of name OpenID url (name being the middle)
- var $front = $this.find('p:has(input[name=openid_username])>span:eq(0)');
- var $end = $this.find('p:has(input[name=openid_username])>span:eq(1)');
+ var read_existing_login_methods = function(){
+ $('.ab-provider-row').each(
+ function(i, provider_row){
+ var provider_name = $(
+ provider_row
+ ).find(
+ '.ab-provider-name'
+ ).html().trim();
+ existing_login_methods[provider_name] = true;
+ }
+ );
+ };
- //needed for special effects only
- var $localfs = $this.find('fieldset:has(input[name=username])');
- var $usrfs = $this.find('fieldset:has(input[name=openid_username])');
- var $idfs = $this.find('fieldset:has(input[name=openid_url])');
+ var setup_login_method_deleters = function(){
+ $('.ab-provider-row').each(
+ function(i, provider_row){
+ var provider_name = $(
+ provider_row
+ ).find(
+ '.ab-provider-name'
+ ).html().trim();
+ var remove_button = $(
+ provider_row
+ ).find('button');
+ remove_button.click(
+ function(){
+ var message = $.i18n._(
+ 'Are you sure you want to remove ' +
+ 'your {provider} login?'
+ ).replace(
+ '{provider}',
+ provider_name
+ );
+ if (confirm(message)){
+ $.ajax({
+ type: 'POST',
+ url: authUrl + 'delete_login_method/',//url!!!
+ data: {provider_name: provider_name},
+ success: function(data, text_status, xhr){
+ $(provider_row).remove();
+ delete existing_login_methods[provider_name];
+ provider_count -=1;
+ if (provider_count < 0){
+ provider_count === 0;
+ }
+ if (provider_count === 0){
+ $('#ab-existing-login-methods').remove();
+ $('#ab-show-login-methods').remove();
+ $('h1').html(
+ $.i18n._("Please add one or more login methods.")
+ );
+ $('#login-intro').html(
+ $.i18n._("You don\'t have a method to log in right now, please add one or more by clicking any of the icons below.")
+ );
+ existing_login_methods = null;
+ }
+ },
- var submitusr = function() {
- if ($usr.val().length < 1) {
- $usr.focus();
- return false;
+ });
+ }
+ }
+ );
+ }
+ );
}
- $id.val($front.text() + $usr.val() + $end.text());
- return true;
- };
-
- var submitid = function() {
- if ($id.val().length < 1) {
- $id.focus();
- return false;
- }
- return true;
-
- };
- var local = function() {
- var $li = $(this);
- $('#openid_form .providers td').removeClass('highlight');
- $li.addClass('highlight');
- $usrfs.hide();
- $idfs.hide();
- $localfs.show();
- $this.unbind('submit').submit(submitid);
- return false;
- };
-
- var direct = function() {
- var $li = $(this);
- $('#openid_form .providers td').removeClass('highlight');
- $li.addClass('highlight');
- $usrfs.fadeOut('slow');
- $localfs.fadeOut('slow');
- $idfs.fadeOut('slow');
- $id.val($this.find("td.highlight span").text());
- setTimeout(function(){$('#bsignin').click();},1000);
- return false;
- };
-
- var openid = function() {
- var $li = $(this);
- $('#openid_form .providers td').removeClass('highlight');
- $li.addClass('highlight');
- $usrfs.hide();
- $localfs.hide();
- $idfs.show();
- $id.focus();
- $this.unbind('submit').submit(submitid);
- return false;
- };
-
- var username = function() {
- var $li = $(this);
- $('#openid_form .providers td').removeClass('highlight');
- $li.addClass('highlight');
- $idfs.hide();
- $localfs.hide();
- $usrfs.show();
- $this.find('#enter_your_what').text($li.attr("title"));
- $front.text($li.find("span").text().split("username")[0]);
- $end.text("").text($li.find("span").text().split("username")[1]);
- $id.focus();
- $this.unbind('submit').submit(submitusr);
- return false;
- };
-
- $this.find('td.local').click(local);
- $this.find('td.direct').click(direct);
- $this.find('td.openid').click(openid);
- $this.find('td.username').click(username);
- $id.keypress(function(e) {
- if ((e.which && e.which == 13) || (e.keyCode && e.keyCode == 13)) {
- return submitid();
- }
- });
- $usr.keypress(function(e) {
- if ((e.which && e.which == 13) || (e.keyCode && e.keyCode == 13)) {
- return submitusr();
+
+ var submit_login_with_password = function(){
+ var username = $('#id_username');
+ var password = $('#id_password');
+ if (username.val().length < 1){
+ username.focus();
+ return false;
+ }
+ if (password.val().length < 1){
+ password.focus();
+ return false;
+ }
+ return true;
+ };
+
+ var submit_change_password = function(){
+ var newpass = $('#id_new_password');
+ var newpass_retyped = $('#id_new_password_retyped');
+ if (newpass.val().length < 1){
+ newpass.focus();
+ return false
+ }
+ if (newpass_retyped.val().length < 1){
+ newpass_retyped.focus();
+ return false;
+ }
+ if (newpass.val() !== newpass_retyped.val()){
+ newpass_retyped.after(
+ '<span class="error">' +
+ $.i18n._('passwords do not match') +
+ '</span>'
+ );
+ newpass.val('').focus();
+ newpass_retyped.val('');
+ return false;
+ }
+ return true;
+ };
+
+ //validator, may be extended to check url for openid
+ var submit_with_extra_openid_token = function() {
+ if (openid_login_token_input.val().length < 1) {
+ openid_login_token_input.focus();
+ return false;
+ }
+ return true;
+ };
+
+ var insert_login_list_enabler = function(){
+ var enabler = $('#login-list-enabler');
+ if (enabler.is('p#login-list-enabler')){
+ enabler.show();
+ }
+ else {
+ enabler = $(
+ '<p id="login-list-enabler"><a href="#">' +
+ $.i18n._('Show/change current login methods') +
+ '</a></p>');
+ setup_event_handlers(
+ enabler,
+ function(){
+ password_input_fields.hide();
+ openid_login_token_input_fields.hide();
+ enabler.hide();
+ existing_login_methods_div.show();
+ }
+ );
+ existing_login_methods_div.after(enabler);
+ }
+ };
+
+ var reset_password_input_fields = function(){
+ if (userIsAuthenticated){
+ $('#id_new_password').val('');
+ $('#id_new_password_retyped').val('');
+ }
+ else {
+ $('#id_username').val('');
+ $('#id_password').val('');
+ }
+ };
+
+ var reset_form = function(){
+ openid_login_token_input_fields.hide();
+ password_input_fields.hide();
+ reset_password_input_fields();
+ $('.error').remove();
+ if (userIsAuthenticated === false){
+ email_input_fields.hide();
+ account_recovery_heading.hide();
+ account_recovery_link.show();
+ account_recovery_hint.show();
+ $('#account-recovery-form>p.hint').css('margin-top','10px');
+ account_recovery_text_span.html(account_recovery_question_text).show();
+ }
+ else {
+ if (existing_login_methods !== null){
+ existing_login_methods_div.hide();
+ insert_login_list_enabler();
+ }
+ }
+ };
+
+ var set_provider_name = function(element){
+ var provider_name = element.attr('name');
+ provider_name_input.val(provider_name);
+ };
+
+ var show_openid_input_fields = function(provider_name){
+ reset_form();
+ var token_name = extra_token_name[provider_name]
+ if (userIsAuthenticated){
+ var heading_text = $.i18n._(
+ 'Please enter your {token_name}, then proceed'
+ );
+ $('#openid-heading').html(
+ heading_text.replace('{token_name}', token_name)
+ );
+ var button_text = $.i18n._('Connect your {provider_name} account to {site}');
+ button_text = button_text.replace(
+ '{provider_name}', provider_name
+ ).replace(
+ '{site}', siteName
+ );
+ openid_submit_button.val(button_text);
+ }
+ else {
+ $('#openid-heading>span').html(token_name);
+ }
+ openid_login_token_input_fields.show();
+ openid_login_token_input.focus();
+ };
+
+ var start_simple_login = function() {
+ //$('#openid_form .providers td').removeClass('highlight');
+ //$li.addClass('highlight');
+ set_provider_name($(this));
+ signin_form.submit();
+ return true;
+ };
+
+ var start_login_with_extra_openid_token = function() {
+ show_openid_input_fields($(this).attr('name'));
+ set_provider_name($(this));
+ setup_event_handlers(
+ openid_submit_button,
+ function(){
+ signin_form.unbind(
+ 'submit'
+ ).submit(
+ submit_with_extra_openid_token
+ );
+ }
+ );
+ return false;
+ };
+
+ var start_facebook_login = function(){
+ set_provider_name($(this));
+ if (typeof FB != 'undefined'){
+ FB.login();
+ //FB.getLoginStatus(function(response){
+ // if (response.session){
+ // alert('you are logged in');
+ // }
+ // else {
+ // }
+ //});
+ }
+ return false;
+ };
+
+ var start_password_login_or_change = function(){
+ reset_form();
+ set_provider_name($(this));
+ var provider_name = $(this).attr('name');
+ var token_name = extra_token_name[provider_name]
+ var password_action_input = $('input[name=password_action]');
+ if (userIsAuthenticated === true){
+ var password_button = $('input[name=change_password]');
+ var submit_action = submit_change_password;
+ if (existing_login_methods && existing_login_methods[provider_name]){
+ var change_pw_heading = 'Change your {provider} password';
+ var password_heading_text = $.i18n._(change_pw_heading);
+ var password_button_text = $.i18n._('Change password');
+ }
+ else {
+ var create_pw_heading = 'Create a password for {provider}';
+ var password_heading_text = $.i18n._(create_pw_heading);
+ var password_button_text = $.i18n._('Create password');
+ }
+ if (provider_name === 'local'){
+ var provider_cleaned_name = siteName;
+ }
+ else {
+ var provider_cleaned_name = provider_name;
+ }
+ $('#password-heading').html(
+ password_heading_text.replace('{provider}', provider_cleaned_name)
+ )
+ password_button.val(password_button_text);
+ password_action_input.val('change_password');
+ }
+ else{
+ $('#password-heading>span').html(token_name);
+ var password_button = $('input[name=login_with_password]');
+ var submit_action = submit_login_with_password;
+ var create_pw_link = $('a.create-password-account')
+ create_pw_link.html($.i18n._('Create a password-protected account'));
+ var url = create_pw_link.attr('href');
+ if (url.indexOf('?') !== -1){
+ url = url.replace(/\?.*$/,'?login_provider=' + provider_name);
+ }
+ else{
+ url += '?login_provider=' + provider_name;
+ }
+ create_pw_link.attr('href', url);
+ password_action_input.val('login');
+ }
+ password_input_fields.show();
+ setup_event_handlers(
+ password_button,
+ function(){
+ signin_form.unbind(
+ 'submit'
+ ).submit(
+ submit_action
+ );
+ }
+ );
+ return false;
+ };
+
+ var start_account_recovery = function(){
+ reset_form();
+ account_recovery_hint.hide();
+ account_recovery_heading.css('margin-bottom', '0px');
+ account_recovery_heading.html(account_recovery_prompt_text).show();
+ email_input_fields.show();
+ $('#id_email').focus();
+ };
+
+ var clear_password_fields = function(){
+ $('#id_password').val('');
+ $('#id_new_password').val('');
+ $('#id_new_password_retyped').val('');
+ };
+
+ setup_event_handlers(
+ signin_page.find('input.openid-direct'),
+ start_simple_login
+ );
+
+ setup_event_handlers(
+ signin_page.find('input.openid-username'),
+ start_login_with_extra_openid_token
+ );
+
+ setup_event_handlers(
+ signin_page.find('input.openid-generic'),
+ start_login_with_extra_openid_token
+ );
+
+ setup_event_handlers(
+ signin_page.find('input.facebook'),
+ start_facebook_login
+ );
+
+ setup_event_handlers(
+ signin_page.find('input.oauth'),
+ start_simple_login
+ );
+
+ setup_event_handlers(
+ signin_page.find('input.password'),
+ start_password_login_or_change
+ );
+
+ setup_event_handlers(account_recovery_link, start_account_recovery);
+ if (userIsAuthenticated){
+ read_existing_login_methods();
+ setup_login_method_deleters();
}
- });
- $this.find('td span').hide();
- $this.find('td').css('line-height', 0).css('cursor', 'pointer');
- $usrfs.hide();
- $idfs.hide();
- $localfs.hide();
- //$this.find('td:eq(0)').click();
-
- return this;
+
+ clear_password_fields();
+ return this;
};
-// submitting next=%2F&openid_username=&openid_url=http%3A%2F%2Fyahoo.com%2F
-// submitting next=%2F&openid_username=&openid_url=http%3A%2F%2Fyahoo.com%2F
diff --git a/askbot/skins/default/media/jquery-openid/openid.css b/askbot/skins/default/media/jquery-openid/openid.css
index 795d1ddf..fa9d47ee 100755
--- a/askbot/skins/default/media/jquery-openid/openid.css
+++ b/askbot/skins/default/media/jquery-openid/openid.css
@@ -1,80 +1,27 @@
-fieldset { border-style:none; }
-img {border-style:none;}
+div#login-icons {margin:10px 0 0 0;padding:0}
+ul.login-icons {width: 450px; margin:0;padding:0;text-align:left; list-style-type:none; display:block;}
+ul.login-icons li {display:inline;}
+ul.large input {height: 40px; width: 90px;border:1px solid #ccc;margin:0 5px 5px 0;}
+.openid-signin h2 {margin-top:15px;}
+.openid-signin h2#account-recovery-heading {margin-bottom:2px;}
+#account-recovery-form p.hint a {color:blue; text-decoration: underline;}
+.openid-signin fieldset { border-style:none;margin:0;padding:0;}
+.openid-signin p {margin:0;padding:0};
+.openid-signin p.hint {color: #555;}
+.openid-signin #password-fs label {float:left; width:100px;margin-top:5px;text-align:left;}
+.openid-signin #password-fs .hint {margin-bottom:5px}
+#password-fs a {padding-left:5px;}
+/*#signin-form #account-recovery-form input {cursor:pointer;}
+#signin-form #account-recovery-form input.text {cursor:default;}*/
-.logo_box {width:90px;height:40px;background:white;border:1px solid #dddddd;}
-.openid_box img {margin:6px 0 0 3px;}
-.aol_box img {margin:6px 0 0 5px;}
-.yahoo_box img {margin:13px 0 0 4px;}
-.google_box img {margin:6px 0 0 6px;}
-.local_login_box img {margin-top:2px;margin-left:-3px;}
-
-form.openid ul{ margin:0;padding:0;text-align:center; list-style-type:none; display:block;}
-form.openid ul li {padding:4px;}
-form.openid ul li span {padding:0 1em 0 3px}
-form.openid ul li.first_tiny_li {clear:left;}
-form.openid fieldset {clear:both;padding:10px 0px 0px 0px;}
-form.openid div+fieldset {display:none}
-input[name=openid_username] {width:8em}
-input[name=openid_identifier] {width:18em}
-form.openid ul li.highlight { -moz-border-radius:4px; -webkit-border-radius:4px; background-color: #FD6}
-form.openid fieldset div {
- /*-moz-border-radius:4px; */
- /*-webkit-border-radius:4px; */
- /*background: #DCDCDC;*/
- padding:10px;
- float:left;
-}
-.openid-signin h2 {margin-top:10px;}
-form.openid p {margin-bottom:4px;}
-form.openid fieldset div p {padding:0px;margin:0px;}
-form.openid fieldset div p.login {padding:0px;margin:0 0 10px 0;}
-form.openid label {
- display:inline-block;
- font-weight:normal;
- width:6em;
- text-align:right;
-}
-#local_login_fs div {
- padding-bottom:4px;
-}
-#local_login_buttons {
- text-align:center;
- line-height:1.8em;
- margin-top:3px;
-}
-/*form.openid input[type='submit'] {margin-left:1em;}*/
-#openid_username {background:#ffffa0;}
-#openid_url {background:#ffffa0;}
-
-.openid_logo{color:#F7931E;padding:6px 0px 8px 28px;
-background: url(images/openidico.png) no-repeat;
+.openid-signin .submit-b {
+ cursor: pointer; /*letter-spacing:1px;*/
+ margin: 0 0 2px 0;
+ vertical-align: middle;
}
-#openid_login {float:left; width:30%; margin:2em 1em; text-align:center}
-#openid_login div{margin-top:0.5em}
+.openid-signin .highlight { -moz-border-radius:4px; -webkit-border-radius:4px; background-color: #FD6}
-form.openid ul.errorlist {
- border: none;
- list-style-position:inside;
- list-style-type: disc;
- margin-bottom:5px;
-}
-form.openid ul.errorlist li {
- text-align: left;
- margin: 5px;
- float: none;
- color:blue;
-}
-ul.providers {
- clear:left;
- float:left;
-}
-#openid_small_providers li {
- margin-top:4px;
-}
-#openid_small_providers li.facebook {
- margin-top:0px;
-}
ul.providers {
display: block;
}
diff --git a/askbot/skins/default/media/style/style.css b/askbot/skins/default/media/style/style.css
index 44276798..71cec370 100755
--- a/askbot/skins/default/media/style/style.css
+++ b/askbot/skins/default/media/style/style.css
@@ -44,6 +44,7 @@ p {
a {
color: #333333;
text-decoration: none;
+ cursor: pointer;
}
.badges a {
diff --git a/askbot/skins/default/templates/authopenid/complete.html b/askbot/skins/default/templates/authopenid/complete.html
index 8e668ab7..ad53cd9a 100644
--- a/askbot/skins/default/templates/authopenid/complete.html
+++ b/askbot/skins/default/templates/authopenid/complete.html
@@ -54,22 +54,6 @@ parameters:
{% endif %}
</ul>
{% endif %}
- {% comment %}
- {% if openid_verify_form.errors %}<!--openid_verify_form is dysfunctional so commented out -->
- <div class="errors">
- <span class="big">{% trans "Sorry, looks like we have some errors:" %}</span><br/>
- <ul class="error-list">
- {% if openid_verify_form.username.errors %}
- <li><span class="error">{{ openid_verify_form.username.errors|join:", " }}</span></li>
- {% endif %}
- {% if openid_verify_form.password.errors %}
- <li><span class="error">{{ openid_verify_form.password.errors|join:", " }}</span></li>
- {% endif %}
- </ul>
- </div>
- {% endif %}
- {% endcomment %}
-
<div class="login">
{% ifequal login_type 'openid' %}
<form name="fregister" action="{% url user_register %}" method="POST">
@@ -106,28 +90,5 @@ parameters:
<div class="submit-row"><input type="submit" class="submit" name="bnewaccount" value="{% trans "create account" %}"/></div>
</form>
</div>
- {% comment %}<!-- this form associates openID with an existing password-protected account, not yet functional -->
- {% if openid_verify_form %}
- <div class="login" style="display:none">
- <form name="fverify" action="{% url user_register %}" method="POST">
- {{ openid_verify_form.next }}
- <fieldset style="padding:10px">
- <legend class="big">{% trans "Existing account" %}</legend>
- <div class="form-row"><label for="id_username">{% trans "user name" %}</label><br/>{{ openid_verify_form.username }}</div>
- <div class="form-row"><label for="id_passwordl">{% trans "password" %}</label><br/>{{ openid_verify_form.password }}</div>
- <p><span class='big strong'>(Optional) receive updates by email</span> - only sent when there are any.</p>
- <div class='simple-subscribe-options'>
- {{email_feeds_form.subscribe}}
- </div>
- <!--todo double check translation from chinese 确认 = "Register" -->
- <div class="submit-row">
- <input type="submit" class="submit" name="bverify" value="{% trans "Register" %}"/>
- <a href="{% url user_sendpw %}">{% trans "Forgot your password?" %}</a>
- </div>
- </fieldset>
- </form>
- </div>
- {% endif %}
- {% endcomment %}
{% endblock %}
<!-- end complete.html -->
diff --git a/askbot/skins/default/templates/authopenid/sendpw.html b/askbot/skins/default/templates/authopenid/sendpw.html
deleted file mode 100644
index 6241c811..00000000
--- a/askbot/skins/default/templates/authopenid/sendpw.html
+++ /dev/null
@@ -1,26 +0,0 @@
-{% extends "base.html" %}
-<!-- sendpw.html -->
-{% load i18n %}
-{% block title %}{% spaceless %}{% trans "Send new password" %}{% endspaceless %}{% endblock %}
-{% block content %}
-<div class="headNormal">
- {% trans "Send new password" %}
-</div>
-<p class="message">
-{% trans "password recovery information" %}
-</p>
-{% if msg %}
- <p class="action-status"><span>{{msg}}</span><p>
-{% endif %}
-
-<div class="aligned">
- <form action="." method="post" accept-charset="utf-8">
- <ul id="emailpw-form" class="form-horizontal-rows">
- {{form.as_ul}}
- </ul>
- <p style="padding-top:10px"><input type="submit" class="submit" value="{% trans "Reset password" %}" />
- <a href="{% url user_signin %}"><span class="strong">{% trans "return to login" %}</span></a></p>
- </form>
-</div>
-{% endblock %}
-<!-- end sendpw.html -->
diff --git a/askbot/skins/default/templates/authopenid/signin.html b/askbot/skins/default/templates/authopenid/signin.html
index e700d189..f3e76bdd 100755
--- a/askbot/skins/default/templates/authopenid/signin.html
+++ b/askbot/skins/default/templates/authopenid/signin.html
@@ -5,17 +5,41 @@
{% load smart_if %}
{% block title %}{% spaceless %}{% trans "User login" %}{% endspaceless %}{% endblock %}
{% block forejs %}
+ <link rel="stylesheet" type="text/css" media="screen" href="{% media "/jquery-openid/openid.css" %}"/>
<script type='text/javascript' src='{% media "/js/jquery.validate.pack.js" %}'></script>
-
- <link rel="stylesheet" type="text/css" media="screen" href="{% media "/jquery-openid/openid.css" %}"/>
- <script type="text/javascript" src="{% media "/jquery-openid/jquery.openid.js" %}"></script>
- <script type="text/javascript"> $().ready( function() { $("form.openid:eq(0)").openid(); })</script>
- <!--<script type="text/javascript">
- $().ready(function(){
- openid.init('id_openid_url');
- setupFormValidation("#openid_form", {bsignin:{required: true}});
- });
- </script>-->
+ <script type="text/javascript" src="{% media "/jquery-openid/jquery.openid.js" %}"></script>
+ <script type="text/javascript">
+ $(document).ready( function() { $("body").authenticator(); })
+ var extra_token_name = {};
+ var create_pw_text = {};
+ var change_pw_text = {};
+ var authUrl = '/{% trans "account/" %}';
+ var siteName = '{{settings.APP_SHORT_NAME}}';
+ var provider_count = {{existing_login_methods|length}};
+ {% for login_provider in major_login_providers %}
+ {% if login_provider.extra_token_name %}
+ extra_token_name['{{login_provider.name}}'] = '{{login_provider.extra_token_name}}';
+ {% endif %}
+ {% if login_provider.type == 'password' %}
+ create_pw_text['{{login_provider.name}}'] = '{{login_provider.create_password_prompt}}';
+ change_pw_text['{{login_provider.name}}'] = '{{login_provider.change_password_prompt}}';
+ {% endif %}
+ {% endfor %}
+ {% for login_provider in minor_login_providers %}
+ {% if login_provider.extra_token_name %}
+ extra_token_name['{{login_provider.name}}'] = '{{login_provider.extra_token_name}}';
+ {% endif %}
+ {% if login_provider.type == 'password' %}
+ create_pw_text['{{login_provider.name}}'] = '{{login_provider.create_password_prompt}}';
+ change_pw_text['{{login_provider.name}}'] = '{{login_provider.change_password_prompt}}';
+ {% endif %}
+ {% endfor %}
+ {% if user.is_authenticated %}
+ var userIsAuthenticated = true;
+ {% else %}
+ var userIsAuthenticated = false;
+ {% endif %}
+ </script>
{% endblock %}
{% block content %}
<h1>{{page_title}}</h1>
@@ -33,13 +57,16 @@
{% endblocktrans %}
</div>
{% endif %}
- <form id="openid_form" name="openid_form" class="openid" method="post" action="{% url user_signin %}">
- <p id='ab-login-method-prompt'>
- {% if view_subtype == 'default' or view_subtype == 'email_sent' %}
- {% trans "You can use your favorite service from those listed below to sign in using secure OpenID or similar technology. Your external service password always stays confidential and you don't have to rememeber or create another one." %}
+ <p id='login-intro'>
+ {% if view_subtype == 'default' %}
+ {% trans "Take a pick of your favorite service below to sign in using secure OpenID or similar technology. Your external service password always stays confidential and you don't have to rememeber or create another one." %}
{% endif %}
{% if view_subtype == 'add_openid' %}
- {% trans "Please add a more permanent login method by clicking one of the icons below, to avoid logging in via email each time. All of the services listed below provide a secure sign-in method based on OpenID or similar technology." %}
+ {% if existing_login_methods %}
+ {% trans "It's a good idea to make sure that your existing login methods still work, or add a new one. Please click any of the icons below to check/change or add new login methods." %}
+ {% else %}
+ {% trans "Please add a more permanent login method by clicking one of the icons below, to avoid logging in via email each time." %}
+ {% endif %}
{% endif %}
{% if view_subtype == 'change_openid' %}
{% if existing_login_methods %}
@@ -48,165 +75,120 @@
{% trans "You don't have a method to log in right now, please add one or more by clicking any of the icons below." %}
{% endif %}
{% endif %}
+ {% if view_subtype == 'email_sent' %}
+ {% trans "Please check your email and visit the enclosed link to re-connect to your account" %}
+ {% endif %}
</p>
{% if openid_error_message %}
<p class="warning">{{ openid_error_message }}</p>
{% endif %}
- <table>
- {% comment %}
- <li class="local" title="Local login">
- <div class="logo_box local_login_box">
- <img src="{% media "/jquery-openid/images/logo-small.gif" %}"
- alt="your icon here" />
- </div>
- <span></span>
- </li>
- {% endcomment %}
- <tr class="providers">
- <td class="username" title="OpenID URL">
- <div class="logo_box openid_box">
- <img src="{% media "/jquery-openid/images/openid.gif" %}" alt="icon" />
- <span>http://<strong>username</strong></span>
- </div>
- </td>
- <td class="direct" title="Google">
- <div class="logo_box google_box">
- <img src="{% media "/jquery-openid/images/google.gif" %}" alt="icon" />
- <span>https://www.google.com/accounts/o8/id</span>
- </div>
- </td>
- <td class="direct" title="Yahoo">
- <div class="logo_box yahoo_box">
- <img src="{% media "/jquery-openid/images/yahoo.gif" %}" alt="icon" />
- <span>http://yahoo.com/</span>
- </div>
- </td>
- <td class="username" title="AOL screen name">
- <div class="logo_box aol_box">
- <img src="{% media "/jquery-openid/images/aol.gif" %}" alt="icon" />
- <span>http://openid.aol.com/<strong>username</strong></span>
- </div>
- </td>
- </tr>
- </table>
- <table>
- <tr id="openid_small_providers" class="providers">
- {% comment %}
- <li class="openid" title="OpenID">
- <div class="logo_box openid_box">
- <img src="/jquery-openid/images/openid.gif" alt="icon" />
- </div>
- <span><strong>http://{your-openid-url}</strong></span>
+ {% if view_subtype != 'email_sent' and view_subtype != 'bad_key' %}
+ <form id="signin-form" method="post" action="{% url user_signin %}">
+ {{login_form.login_provider_name}}
+ <div id="login-icons">
+ <ul class="login-icons large">
+ {% for login_provider in major_login_providers %}
+ <li>
+ <input
+ name="{{login_provider.name}}"
+ type="image"
+ class="{{login_provider.type}}"
+ src="{% media login_provider.icon_media_path %}"
+ alt="{{login_provider.tooltip_text}}"
+ title="{{login_provider.tooltip_text}}"
+ />
</li>
- <li class="first_tiny_li facebook" title="Facebook Connect">
- {% if question %}
- <fb:login-button onlogin="window.location = '{% url fb_signin_new_question %}'"></fb:login-button>
- {% else %}
- {% if answer %}
- <fb:login-button onlogin="window.location = '{% url fb_signin_new_answer %}'"></fb:login-button>
- {% else %}
- <fb:login-button onlogin="window.location = '{% url fb_signin %}'"></fb:login-button>
- {% endif %}
- {% endif %}
+ {% endfor %}
+ </ul>
+ <ul class="login-icons small">
+ {% for login_provider in minor_login_providers %}
+ <li>
+ <input
+ name="{{login_provider.name}}"
+ type="image"
+ class="{{login_provider.type}}"
+ src="{% media login_provider.icon_media_path %}"
+ alt="{{login_provider.tooltip_text}}"
+ title="{{login_provider.tooltip_text}}"
+ />
</li>
- <li class="openid first_tiny_li" title="OpenID URL">
- <img src="{% media "/jquery-openid/images/openidico16.png" %}" alt="icon" />
- <span>http://{your-openid-url}
- </li>
- {% endcomment %}
- <td class="username first_tiny_li" title="MyOpenID user name">
- <img src="{% media "/jquery-openid/images/myopenid-2.png" %}" alt="icon" />
- <span>http://<strong>username</strong>.myopenid.com/</span>
- </td>
- <td class="username" title="Flickr user name">
- <img src="{% media "/jquery-openid/images/flickr.png" %}" alt="icon" />
- <span>http://flickr.com/<strong>username</strong>/</span>
- </td>
- <td class="username" title="Technorati user name">
- <img src="{% media "/jquery-openid/images/technorati-1.png" %}" alt="icon" />
- <span>http://technorati.com/people/technorati/<strong>username</strong>/</span>
- </td>
- <td class="username" title="Wordpress blog name">
- <img src="{% media "/jquery-openid/images/wordpress.png" %}" alt="icon" />
- <span>http://<strong>username</strong>.wordpress.com</span>
- </td>
- <td class="username" title="Blogger blog name">
- <img src="{% media "/jquery-openid/images/blogger-1.png" %}" alt="icon" />
- <span>http://<strong>username</strong>.blogspot.com/</span>
- </td>
- <td class="username" title="LiveJournal blog name">
- <img src="{% media "/jquery-openid/images/livejournal-1.png" %}" alt="icon" />
- <span>http://<strong>username</strong>.livejournal.com</span>
- </td>
- <td class="username" title="ClaimID user name">
- <img src="{% media "/jquery-openid/images/claimid-0.png" %}" alt="icon" />
- <span>http://claimid.com/<strong>username</strong></span>
- </td>
- <td class="username" title="Vidoop user name">
- <img src="{% media "/jquery-openid/images/vidoop.png" %}" alt="icon" />
- <span>http://<strong>username</strong>.myvidoop.com/</span>
- </td>
- <td class="username" title="Verisign user name">
- <img src="{% media "/jquery-openid/images/verisign-2.png" %}" alt="icon" />
- <span>http://<strong>username</strong>.pip.verisignlabs.com/</span>
- </td>
- </tr>
- </table>
- {{ openid_login_form.next }}
- <fieldset style="display:none;">
- <p id="provider_name_slot">{% trans 'Enter your <span id="enter_your_what">Provider user name</span>' %}</p>
- <div><p><span></span>
- <input id="openid_username" type="text" name="openid_username" /><span></span>
- <input type="submit" value="{% trans "Login" %}" />
- </p></div>
- </fieldset>
- <fieldset style="display:none">
- <p>{% trans 'Enter your <a class="openid_logo" href="http://openid.net">OpenID</a> web address' %}</p>
- <div><p><input id="openid_url" type="text" value="http://" name="openid_url" />
- <input id="bsignin" name="bsignin" type="submit" value="{% trans "Login" %}" /></p></div>
+ {% endfor %}
+ </ul>
+ </div>
+ {{ login_form.next }}
+ <fieldset
+ id="openid-fs"
+ {% if not login_form.openid_login_token.errors %}
+ style="display:none;"
+ {% endif %}
+ >
+ <h2 id="openid-heading">{% trans "Please enter your <span>user name</span>, then sign in" %}</h2>
+ <p class="hint">{% trans "(or select another login method above)" %}</p>
+ <input type="text" name="openid_login_token" />
+ <input class="submit-b" type="submit" name="openid_login_with_extra_token" value="{% trans "Sign in" %}"/>
</fieldset>
- {% comment %}
- <fieldset id='local_login_fs'>
- <p>{% trans 'Enter your login name and password' %}</p>
- {% if password_login_form.errors %}
- {{password_login_form.non_field_errors.as_ul}}
+ {% if settings.RECAPTCHA_KEY and settings.RECAPTCHA_SECRET %}
+ <fieldset
+ id="password-fs"
+ {% if user.is_anonymous %}
+ {% if not login_form.username.errors and not login_form.password_login_failed %}
+ style="display:none;"
+ {% endif %}
+ {% else %}
+ {% if not login_form.new_password.errors and not login_form.new_password_retyped.errors %}
+ style="display:none;"
+ {% endif %}
{% endif %}
- <div>
- <p class="login">
+ >
+ {{login_form.password_action}}
+ {% if user.is_anonymous %}
+ <h2 id="password-heading">
+ {% trans "Please enter your <span>user name and password</span>, then sign in" %}
+ </h2>
+ <p class="hint">{% trans "(or select another login method above)" %}</p>
+ {% if login_form.password_login_failed %}
+ <p class="error">{% trans "Login failed, please try again" %}</p>
+ {% endif %}
+ <p>
<label for="id_username">{% trans "Login name" %}</label>
- {{password_login_form.username}}
+ {{login_form.username}}
</p>
- <p class="login">
+ <p>
<label for="id_password">{% trans "Password" %}</label>
- {{password_login_form.password}}
+ {{login_form.password}}
+ </p>
+ <p id="local_login_buttons">
+ <input class="submit-b" name="login_with_password" type="submit" value="{% trans "Login" %}" />
+ <a class="create-password-account" style="vertical-align:middle" href="{% url user_signup_with_password %}">{% trans "Create a password-protected account" %}</a>
+ </p>
+ {% else %}
+ <h2 id="password-heading">
+ {% trans "To change your password - please enter the new one twice, then submit" %}
+ </h2>
+ <p>
+ <label for="id_new_password">{% trans "New password" %}</label>
+ {{login_form.new_password}}
+ <span class="error">{{login_form.new_password.errors.0}}</span>
+ </p>
+ <p>
+ <label for="id_new_password_retyped">{% trans "Please, retype" %}</label>
+ {{login_form.new_password_retyped}}
+ <span class="error">{{login_form.new_password_retyped.errors.0}}</span>
</p>
<p id="local_login_buttons">
- <input id="blogin" name="blogin" type="submit" value="{% trans "Login" %}" />
- <a href="{% url user_signup %}">{% trans "Create account" %}</a><br/>
- <a href="{% url user_sendpw %}">{% trans "Forgot your password?" %}</a>
+ <input class="submit-b" name="change_password" type="submit" value="{% trans "Change password" %}" />
</p>
- </div>
+ {% endif %}
</fieldset>
- {% endcomment %}
- </form>
- {% if view_subtype == 'default' or view_subtype == 'email_sent' %}
- <form action="{% url user_account_recover %}" method="post">
- {% if view_subtype == 'email_sent' %}
- <h2>{% trans "Account recovery email sent" %}</h2>
- {% else %}
- <h2>{% trans "Still have trouble accessing your account?" %}</h2>
{% endif %}
- {% if account_recovery_message %}
- <p>{{account_recovery_message}}</p>
- {% else %}
- <p>{% trans "Please, enter your email address below to recover" %}</p>
- {% endif %}
- {{ account_recovery_form.email }}
- <input type="submit" value="{% trans "Recover your account via email" %}"/>
- </form>
- {% endif %}
- {% if view_subtype == 'change_openid' %}
- {% if existing_login_methods %}
+ </form>
+ {% if user.is_authenticated and existing_login_methods %}
+ <div
+ id='existing-login-methods'
+ {% if login_form.password_change_failed %}
+ style="display:none";
+ {% endif %}
+ >
<h2 id='ab-show-login-methods'>
{% trans "Here are your current login methods" %}
</h2>
@@ -232,62 +214,41 @@
</tr>
{% endfor %}
</table>
- <script type="text/javascript">
- $(document).ready(
- function(){
- var provider_count = {{existing_login_methods|length}};
- $('.ab-provider-row').each(
- function(i, provider_row){
- var provider_name = $(
- provider_row
- ).find(
- '.ab-provider-name'
- ).html().trim();
- var remove_button = $(
- provider_row
- ).find('button');
- remove_button.click(
- function(){
- var message = $.i18n._(
- 'Are you sure you want to remove ' +
- 'your {provider} login?'
- ).replace(
- '{provider}',
- provider_name
- );
- if (confirm(message)){
- $.ajax({
- type: 'POST',
- url: '{% url delete_login_method %}',
- data: {provider_name: provider_name},
- success: function(data, text_status, xhr){
- $(provider_row).remove();
- provider_count -=1;
- if (provider_count < 0){
- provider_count === 0;
- }
- if (provider_count === 0){
- $('#ab-existing-login-methods').remove();
- $('#ab-show-login-methods').remove();
- $('h1').html(
- $.i18n._("Please add one or more login methods.")
- );
- $('#ab-login-method-prompt').html(
- $.i18n._("You don\'t have a method to log in right now, please add one or more by clicking any of the icons below.")
- );
- alert($.i18n._('You have deleted all login methods, please add at least one'));
- }
- },
-
- });
- }
- }
- );
- }
- );
- }
- );
- </script>
+ </div>
+ {% endif %}
+ {% endif %}
+ {% if view_subtype != 'email_sent' or view_subtype == 'bad_key' %}
+ {% if user.is_anonymous %}
+ <form id="account-recovery-form" action="{% url user_account_recover %}" method="post">
+ {% if view_subtype != 'bad_key' %}
+ <h2 id='account-recovery-heading'>{% trans "Still have trouble signing in?" %}</h2>
+ {% endif %}
+ <p class="hint">
+ <span class="text">
+ {% if view_subtype == 'bad_key' %}
+ {% trans "Please, enter your email address below and obtain a new key" %}
+ {% else %}
+ {% trans "Please, enter your email address below to recover your account" %}
+ {% endif %}
+ </span>
+ <span style="display:none" class="link"> - <a href="#">{% trans "recover your account via email" %}</a></span>
+ </p>
+ <fieldset id='email-input-fs'>
+ {% if account_recovery_form.email.errors %}
+ <p class="error">{{account_recovery_form.email.errors.0}}</p>
+ {% endif %}
+ {{ account_recovery_form.email }}
+ <input
+ class="submit-b"
+ type="submit"
+ {% if view_subtype == 'bad_key' %}
+ value="{% trans "Send a new recovery key" %}"
+ {% else %}
+ value="{% trans "Recover your account via email" %}"
+ {% endif %}
+ />
+ </fieldset>
+ </form>
{% endif %}
{% endif %}
{% endblock %}
@@ -315,11 +276,22 @@
<a href="http://openid.net/get/" target="_blank">{% trans "Get OpenID" %} »</a>
</p>
</div>
-{% comment %}
-<script type="text/javascript" src="http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php"></script>
-<script type="text/javascript"> FB.init("{{ settings.FB_API_KEY }}","{% url xd_receiver %}");</script>
-{% endcomment %}
+{% if settings.FACEBOOK_KEY and settings.FACEBOOK_SECRET %}
+<div id="fb-root"></div>
+<script src="http://connect.facebook.net/en_US/all.js"></script>
+<script>
+ $(document).ready(function(){
+ FB.init({appId: '{{settings.FACEBOOK_KEY}}', status: true, cookie: true, xfbml: true});
+ FB.Event.subscribe('auth.sessionChange', function(response){
+ if (response.session) {
+ // A user has logged in, and a new cookie has been saved
+ $('#signin-form').submit();
+ } else {
+ // The user has logged out, and the cookie has been cleared
+ }
+ });
+ });
+</script>
+{% endif %}
{% endblock%}
-
-<script type="text/javascript"> $( function() { $("form.openid:eq(0)").openid(); })</script>
<!-- end signin.html -->
diff --git a/askbot/skins/default/templates/authopenid/signup.html b/askbot/skins/default/templates/authopenid/signup_with_password.html
index fdb236c2..009c7592 100644
--- a/askbot/skins/default/templates/authopenid/signup.html
+++ b/askbot/skins/default/templates/authopenid/signup_with_password.html
@@ -8,7 +8,8 @@
{% trans "Create login name and password" %}
</div>
<p class="message">{% trans "Traditional signup info" %}</p>
-<form action="{% url user_signup %}" method="post" accept-charset="utf-8">
+<form action="{% url user_signup_with_password %}" method="post" accept-charset="utf-8">
+ {{form.login_provider}}
<ul class="form-horizontal-rows">
<li><label for="usename_id">{{form.username.label}}</label>{{form.username}}{{form.username.errors}}</li>
<li><label for="email_id">{{form.email.label}}</label>{{form.email}}{{form.email.errors}}</li>
diff --git a/askbot/skins/default/templates/logout.html b/askbot/skins/default/templates/logout.html
index 650ba044..c8273a39 100644
--- a/askbot/skins/default/templates/logout.html
+++ b/askbot/skins/default/templates/logout.html
@@ -6,8 +6,27 @@
{% block title %}{% spaceless %}{% trans "Logout" %}{% endspaceless %}{% endblock %}
{% block forejs %}
<script type="text/javascript">
+ var sign_out = function(){
+ window.location.href='{% url user_signout %}?next={{ next }}';
+ }
$().ready(function(){
- $('#btLogout').bind('click', function(){ window.location.href='{% url user_signout %}?next={{ next }}'; });
+ $('#btLogout').bind('click', function(){
+ if (typeof FB != 'undefined'){
+ FB.getLoginStatus(function(response){
+ if (response.session){
+ FB.logout(function(response){
+ sign_out();
+ });
+ }
+ else {
+ sign_out();
+ }
+ });
+ }
+ else {
+ sign_out();
+ }
+ });
});
</script>
{% endblock %}
@@ -19,5 +38,12 @@
<p>{% trans "As a registered user you can login with your OpenID, log out of the site or permanently remove your account." %}</p>
<input id="btLogout" type="button" class="submit" value="{% trans "Logout now" %}"><!-- style="width:150px">-->
</div>
+{% if settings.FACEBOOK_KEY and settings.FACEBOOK_SECRET %}
+<div id="fb-root"></div>
+<script src="http://connect.facebook.net/en_US/all.js"></script>
+<script>
+ FB.init({appId: '{{settings.FACEBOOK_KEY}}', status: true, cookie: true, xfbml: true});
+</script>
+{% endif %}
{% endblock %}
<!-- end logout.html -->
diff --git a/askbot/views/users.py b/askbot/views/users.py
index 9b130fd2..aa7815a0 100644
--- a/askbot/views/users.py
+++ b/askbot/views/users.py
@@ -375,7 +375,11 @@ def user_stats(request, user):
total_awards = awards.count()
awards = awards.annotate(count = Count('badge__id'))
- user_tags = user_tags.annotate(user_tag_usage_count=Count('name'))
+ user_tags = user_tags.annotate(
+ user_tag_usage_count=Count('name')
+ ).order_by(
+ '-user_tag_usage_count'
+ )
except ImportError:
#todo: remove all old django stuff, e.g. with '.group_by = ' pattern