Browse Source

Social auth error handler

Rafał Pitoń 7 years ago
parent
commit
c7bb9c8a61

+ 3 - 3
docs/settings/Core.md

@@ -251,11 +251,11 @@ Items in Misago are usually indexed in search engine on save or update. If you c
 Path to function or callable used by Misago to generate slugs. Defaults to `misago.core.slugify.default`. Use this function if you want to customize slugs generation on your community.
 
 
-## `MISAGO_SOCIAL_AUTH_PROVIDERS_NAMES`
+## `MISAGO_SOCIAL_AUTH_BACKENDS_NAMES`
 
-This setting allows you to override social auth provider's name displayed in Misago interface to be more descriptive. For example, to rename "Facebook" to "Facebook Connect" and "Google Plus" to "Google+" at same time, add following code to your `settings.py`:
+This setting allows you to override social auth backend name displayed in Misago interface to be more descriptive. For example, to rename "Facebook" to "Facebook Connect" and "Google Plus" to "Google+" at same time, add following code to your `settings.py`:
 
-    MISAGO_SOCIAL_AUTH_PROVIDERS_NAMES = {
+    MISAGO_SOCIAL_AUTH_BACKENDS_NAMES = {
         'facebook': "Facebook Connect",
         'google-plus': "Google+",
     }

+ 3 - 3
misago/conf/defaults.py

@@ -129,10 +129,10 @@ MISAGO_USE_STOP_FORUM_SPAM = True
 MISAGO_STOP_FORUM_SPAM_MIN_CONFIDENCE = 80
 
 
-# Social Auth Providers Names Overrides
-# Allows you to customize 
+# Social Auth Backends Names Overrides
+# This seeting may be used to customise auth backends names displayed in the UI
 
-MISAGO_SOCIAL_AUTH_PROVIDERS_NAMES = {} 
+MISAGO_SOCIAL_AUTH_BACKENDS_NAMES = {} 
 
 
 # Login API URL

+ 44 - 0
misago/core/errorpages.py

@@ -1,8 +1,10 @@
 from django.http import JsonResponse
 from django.shortcuts import render
 from django.utils.translation import ugettext as _
+from social_core import exceptions as social_exceptions
 
 from misago.admin.views.errorpages import admin_csrf_failure, admin_error_page
+from misago.users.social.utils import get_social_auth_backend_name
 
 from .utils import get_exception_message, is_request_to_misago
 
@@ -55,6 +57,48 @@ def page_not_found(request, exception):
         return _error_page(request, 404, exception)
 
 
+def social_auth_failed(request, exception):
+    backend_name = None
+    message = None
+    help_text = None
+
+    try:
+        backend_name = exception.backend_name
+    except AttributeError:
+        pass
+    try:
+        exception_backend = exception.backend
+        backend_name = get_social_auth_backend_name(exception_backend.name)
+    except AttributeError:
+        pass
+
+    if isinstance(exception, social_exceptions.NotAllowedToDisconnect):
+        message = _(
+            "A problem was encountered when disconnecting your account from the remote site."
+        )
+        help_text = _(
+            "You are not allowed to disconnect your account from the other site, "
+            "because currently it's the only way to sign in to your account."
+        )
+    elif backend_name:
+        message = _("A problem was encountered when signing you in using %(backend)s.") % {
+            'backend': backend_name
+        }
+
+        if isinstance(exception, social_exceptions.AuthCanceled):
+            help_text = _("The sign in process has been canceled by user.")
+        if isinstance(exception, social_exceptions.AuthUnreachableProvider):
+            help_text = _("The other service could not be reached.")
+    else:
+        message = _("Unexpected problem has been encountered during sign in process.")
+
+    return render(request, 'misago/errorpages/social.html', {
+        'backend_name': backend_name,
+        'message': message,
+        'help_text': help_text,
+    }, status=400)
+
+
 @admin_csrf_failure
 def csrf_failure(request, reason=""):
     if request.is_ajax():

+ 12 - 3
misago/core/exceptionhandler.py

@@ -4,23 +4,26 @@ from django.core.exceptions import PermissionDenied
 from django.http import Http404, HttpResponsePermanentRedirect, JsonResponse
 from django.urls import reverse
 from django.utils import six
+from social_core.exceptions import SocialAuthBaseException
+from social_core.utils import social_logger
 
 from . import errorpages
 from .exceptions import AjaxError, Banned, ExplicitFirstPage, OutdatedSlug
 
 
-HANDLED_EXCEPTIONS = [
+HANDLED_EXCEPTIONS = (
     AjaxError,
     Banned,
     ExplicitFirstPage,
     Http404,
     OutdatedSlug,
     PermissionDenied,
-]
+    SocialAuthBaseException,
+)
 
 
 def is_misago_exception(exception):
-    return exception.__class__ in HANDLED_EXCEPTIONS
+    return isinstance(exception, HANDLED_EXCEPTIONS)
 
 
 def handle_ajax_error(request, exception):
@@ -66,6 +69,11 @@ def handle_permission_denied_exception(request, exception):
     return errorpages.permission_denied(request, exception)
 
 
+def handle_social_auth_exception(request, exception):
+    social_logger.error(exception)
+    return errorpages.social_auth_failed(request, exception)
+
+
 EXCEPTION_HANDLERS = [
     (AjaxError, handle_ajax_error),
     (Banned, handle_banned_exception),
@@ -73,6 +81,7 @@ EXCEPTION_HANDLERS = [
     (ExplicitFirstPage, handle_explicit_first_page_exception),
     (OutdatedSlug, handle_outdated_slug_exception),
     (PermissionDenied, handle_permission_denied_exception),
+    (SocialAuthBaseException, handle_social_auth_exception),
 ]
 
 

+ 3 - 0
misago/core/testproject/urls.py

@@ -70,6 +70,9 @@ urlpatterns = [
     url(r'^forum/test-403/$', views.raise_misago_403, name='raise-misago-403'),
     url(r'^forum/test-404/$', views.raise_misago_404, name='raise-misago-404'),
     url(r'^forum/test-405/$', views.raise_misago_405, name='raise-misago-405'),
+    url(r'^forum/social-auth-failed/$', views.raise_social_auth_failed, name='raise-social-auth-failed'),
+    url(r'^forum/social-wrong-backend/$', views.raise_social_wrong_backend, name='raise-social-wrong-backend'),
+    url(r'^forum/social-not-allowed-to-disconnect/$', views.raise_social_not_allowed_to_disconnect, name='raise-social-not-allowed-to-disconnect'),
     url(r'^test-403/$', views.raise_403, name='raise-403'),
     url(r'^test-404/$', views.raise_404, name='raise-404'),
     url(r'^test-redirect/$', views.test_redirect, name='test-redirect'),

+ 14 - 0
misago/core/testproject/views.py

@@ -3,6 +3,8 @@ from rest_framework.decorators import api_view
 from django.contrib.auth import get_user_model
 from django.core.exceptions import PermissionDenied
 from django.http import Http404, HttpResponse
+from social_core.exceptions import AuthFailed, NotAllowedToDisconnect, WrongBackend
+from social_core.backends.github import GithubOAuth2
 
 from misago.core import errorpages, mail
 from misago.core.decorators import require_POST
@@ -121,6 +123,18 @@ def raise_404(request):
     raise Http404()
 
 
+def raise_social_auth_failed(require_POST):
+    raise AuthFailed(GithubOAuth2)
+
+
+def raise_social_wrong_backend(request):
+    raise WrongBackend('facebook')
+
+
+def raise_social_not_allowed_to_disconnect(request):
+    raise NotAllowedToDisconnect()
+
+
 def test_redirect(request):
     return home_redirect(request)
 

+ 16 - 0
misago/core/tests/test_errorpages.py

@@ -43,6 +43,22 @@ class ErrorPageViewsTests(TestCase):
         self.assertContains(response, "misago:error-405", status_code=405)
         self.assertContains(response, "Wrong way", status_code=405)
 
+    def test_social_auth_failed_returns_400(self):
+        """social auth's failed error returns 400"""
+        response = self.client.get(reverse('raise-social-auth-failed'))
+        self.assertContains(response, "page-error-social", status_code=400)
+        self.assertContains(response, "GitHub", status_code=400)
+
+    def test_social_wrong_backend_returns_400(self):
+        """social auth's wrong backend error returns 400"""
+        response = self.client.get(reverse('raise-social-wrong-backend'))
+        self.assertContains(response, "page-error-social", status_code=400)
+
+    def test_social_not_allowed_to_disconnect_returns_400(self):
+        """social auth's not allowed to disconnect error returns 400"""
+        response = self.client.get(reverse('raise-social-not-allowed-to-disconnect'))
+        self.assertContains(response, "page-error-social", status_code=400)
+
 
 @override_settings(ROOT_URLCONF='misago.core.testproject.urlswitherrorhandlers')
 class CustomErrorPagesTests(TestCase):

+ 45 - 0
misago/templates/misago/errorpages/social.html

@@ -0,0 +1,45 @@
+{% extends "misago/base.html" %}
+{% load i18n %}
+
+
+{% block title %}{% trans "Problem with sign in" %} | {{ block.super }}{% endblock %}
+
+
+{% block meta-description %}
+{{ message }}
+{% endblock meta-description %}
+
+
+{% block og-title %}{% trans "Problem with sign in" %}{% endblock %}
+
+
+{% block og-description %}
+{{ message }}
+{% endblock %}
+
+
+{% block content %}
+<div class="page page-error page-error-social">
+  <div class="page-header-bg"></div>
+  <div class="container">
+    <div class="message-panel">
+
+      <div class="message-icon">
+        <span class="material-icon">sync_problem</span>
+      </div>
+
+      <div class="message-body">
+        <p class="lead">{{ message }}</p>
+        <p>
+          {% if help_text %}
+            {{ help_text }}
+          {% else %}
+            {% trans "Please try again or use another method to sign in if the problem persists." %}
+          {% endif %}
+        </p>
+      </div>
+
+    </div>
+  </div>
+</div>
+{% endblock content %}

+ 1 - 1
misago/users/social/providersnames.py → misago/users/social/backendsnames.py

@@ -3,7 +3,7 @@ Python Social Auth doesn't provide information about proper names for OAuth site
 so we are using this file for overrides whenever name is different than `provider.name.title`
 """
 
-PROVIDERS_NAMES = {
+BACKENDS_NAMES = {
     'angel': "AngelList",
     'aol': "AOL",
     'arcgis': "ArcGIS",

+ 14 - 0
misago/users/social/pipeline.py

@@ -0,0 +1,14 @@
+def validate_ip_not_banned(strategy, details, backend, user=None, *args, **kwargs):
+    pass
+
+
+def associate_by_email(strategy, details, backend, user=None, *args, **kwargs):
+    pass
+
+
+def create_user(strategy, details, backend, user=None, *args, **kwargs):
+    pass
+
+
+def create_user_with_form(strategy, details, backend, user=None, *args, **kwargs):
+    pass

+ 14 - 8
misago/users/social/utils.py

@@ -3,20 +3,26 @@ from social_core.backends.utils import load_backends
 
 from misago.conf import settings
 
-from .providersnames import PROVIDERS_NAMES
+from .backendsnames import BACKENDS_NAMES
 
 
 def get_enabled_social_auth_sites_list():
     social_auth_backends = load_backends(settings.AUTHENTICATION_BACKENDS)
     providers_list = []
-    for provider_id in social_auth_backends:
-        provider_name = settings.MISAGO_SOCIAL_AUTH_PROVIDERS_NAMES.get(provider_id)
-        if not provider_name:
-            provider_name = PROVIDERS_NAMES.get(provider_id, provider_id.title())
+    for backend_id in social_auth_backends:
+        backend_name = get_social_auth_backend_name(backend_id)
             
         providers_list.append({
-            'id': provider_id,
-            'name': provider_name,
-            'url': reverse('social:begin', kwargs={'backend': provider_id}),
+            'id': backend_id,
+            'name': backend_name,
+            'url': reverse('social:begin', kwargs={'backend': backend_id}),
         })
     return providers_list
+
+
+def get_social_auth_backend_name(backend_id):
+    if backend_id in settings.MISAGO_SOCIAL_AUTH_BACKENDS_NAMES:
+        return settings.MISAGO_SOCIAL_AUTH_BACKENDS_NAMES[backend_id]
+    if backend_id in BACKENDS_NAMES:
+        return BACKENDS_NAMES[backend_id]
+    return backend_id.title()

+ 2 - 2
misago/users/tests/test_social_utils.py

@@ -30,7 +30,7 @@ class SocialUtilsTests(TestCase):
             'social_core.backends.facebook.FacebookOAuth2',
             'social_core.backends.github.GithubOAuth2',
         ],
-        MISAGO_SOCIAL_AUTH_PROVIDERS_NAMES={
+        MISAGO_SOCIAL_AUTH_BACKENDS_NAMES={
             'facebook': "Facebook Connect",
         }
     )
@@ -46,4 +46,4 @@ class SocialUtilsTests(TestCase):
                 'name': 'GitHub',
                 'url': reverse('social:begin', kwargs={'backend': 'github'}),
             }
-        ])
+        ])