From 4f37fb7a6735e758149d4cb434d4087cf953df57 Mon Sep 17 00:00:00 2001 From: Mad Price Ball Date: Tue, 21 Jan 2020 14:30:24 -0800 Subject: [PATCH] Revert "remove discourse" This reverts commit 0032eaf84c1a6e1e20da4a7c47fae5b115be94a4. --- discourse/__init__.py | 0 discourse/urls.py | 5 +++ discourse/views.py | 79 +++++++++++++++++++++++++++++++++++++++++ env.example | 4 +++ open_humans/settings.py | 5 +++ open_humans/urls.py | 3 ++ 6 files changed, 96 insertions(+) create mode 100644 discourse/__init__.py create mode 100644 discourse/urls.py create mode 100644 discourse/views.py diff --git a/discourse/__init__.py b/discourse/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/discourse/urls.py b/discourse/urls.py new file mode 100644 index 000000000..b69e18a97 --- /dev/null +++ b/discourse/urls.py @@ -0,0 +1,5 @@ +from django.conf.urls import url + +from .views import single_sign_on + +urlpatterns = [url(r"^sso/$", single_sign_on)] diff --git a/discourse/views.py b/discourse/views.py new file mode 100644 index 000000000..ed9a944dd --- /dev/null +++ b/discourse/views.py @@ -0,0 +1,79 @@ +import base64 +import hmac +import hashlib +import urllib + +from urlparse import parse_qs + +from django.contrib.auth.decorators import login_required +from django.http import HttpResponseBadRequest, HttpResponseRedirect +from django.conf import settings + + +@login_required +def single_sign_on(request): + """ + Support Discourse single sign-on. + """ + payload = request.GET.get("sso") + signature = request.GET.get("sig") + + if None in [payload, signature]: + return HttpResponseBadRequest( + "No SSO payload or signature. Please " + "contact support if this problem " + "persists." + ) + + # Validate the payload + try: + payload = urllib.unquote(payload) + decoded = base64.decodestring(payload) + + assert "nonce" in decoded + assert len(payload) > 0 + except AssertionError: + return HttpResponseBadRequest( + "Invalid payload. Please contact " "support if this problem persists." + ) + + key = str(settings.DISCOURSE_SSO_SECRET) + h = hmac.new(key, payload, digestmod=hashlib.sha256) + this_signature = h.hexdigest() + + if this_signature != signature: + return HttpResponseBadRequest( + "Invalid payload. Please contact " "support if this problem persists." + ) + + # Build the return payload + qs = parse_qs(decoded) + + if not request.user.member.primary_email.verified: + return HttpResponseBadRequest( + "Please verify your Open Humans email " "address." + ) + + params = { + "nonce": qs["nonce"][0], + "name": request.user.member.name, + "email": request.user.member.primary_email.email, + "external_id": request.user.id, + "username": request.user.username, + } + + try: + params["avatar_url"] = request.user.member.profile_image.url + params["avatar_force_update"] = "true" + except ValueError: + pass + + return_payload = base64.encodestring(urllib.urlencode(params)) + h = hmac.new(key, return_payload, digestmod=hashlib.sha256) + + query_string = urllib.urlencode({"sso": return_payload, "sig": h.hexdigest()}) + + # Redirect back to Discourse + url = "%s/session/sso_login" % settings.DISCOURSE_BASE_URL + + return HttpResponseRedirect("%s?%s" % (url, query_string)) diff --git a/env.example b/env.example index 187800d25..aa78aad3c 100644 --- a/env.example +++ b/env.example @@ -53,5 +53,9 @@ PORT= RECAPTCHA_PUBLIC_KEY="" RECAPTCHA_PRIVATE_KEY="" +# The SSO secret from +# http://forum.openhumans.org/admin/site_settings/category/login +DISCOURSE_SSO_SECRET="" + # A key used to communicate with data-processing; must be set in both sites PRE_SHARED_KEY="" diff --git a/open_humans/settings.py b/open_humans/settings.py index 1eb9b9413..c35f79d55 100644 --- a/open_humans/settings.py +++ b/open_humans/settings.py @@ -156,6 +156,7 @@ def to_bool(env, default="false"): "data_import", "private_sharing", "public_data", + "discourse", # gulp integration "django_gulp", # Django built-ins @@ -490,6 +491,10 @@ def to_bool(env, default="false"): ZAPIER_WEBHOOK_URL = os.getenv("ZAPIER_WEBHOOK_URL") +DISCOURSE_BASE_URL = os.getenv("DISCOURSE_BASE_URL", "https://forums.openhumans.org") + +DISCOURSE_SSO_SECRET = os.getenv("DISCOURSE_SSO_SECRET") + MAX_UNAPPROVED_MEMBERS = int(os.getenv("MAX_UNAPPROVED_MEMBERS", "20")) # Highlighted projects diff --git a/open_humans/urls.py b/open_humans/urls.py index 9ac437fce..a8e9448aa 100644 --- a/open_humans/urls.py +++ b/open_humans/urls.py @@ -6,6 +6,7 @@ from django.views.generic import RedirectView, TemplateView import data_import.urls +import discourse.urls import private_sharing.api_urls import private_sharing.urls import public_data.urls @@ -21,6 +22,8 @@ "admin/", include((admin.site.urls[0], admin.site.urls[1]), namespace=admin.site.urls[2]), ), + # Include Discourse SSO + path(r"discourse/", include(discourse.urls)), # Include the various APIs here path("api/", include(api_urls)), path("api/direct-sharing/", include(private_sharing.api_urls)),