From 761bf8015c676448914b8eb9c2da2dd27e0b87ee Mon Sep 17 00:00:00 2001 From: Dan Braghis <dan.braghis@torchbox.com> Date: Thu, 11 Jan 2018 15:28:12 +0000 Subject: [PATCH] Add separate view for OAuth associations and allow disconnecting --- opentech/apply/users/decorators.py | 20 +++++++++ .../apply/users/templates/users/account.html | 9 +++- .../apply/users/templates/users/oauth.html | 41 +++++++++++++++++++ opentech/apply/users/templatetags/__init__.py | 0 .../apply/users/templatetags/users_tags.py | 40 ++++++++++++++++++ opentech/apply/users/urls.py | 3 +- opentech/apply/users/views.py | 13 ++++++ 7 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 opentech/apply/users/decorators.py create mode 100644 opentech/apply/users/templates/users/oauth.html create mode 100644 opentech/apply/users/templatetags/__init__.py create mode 100644 opentech/apply/users/templatetags/users_tags.py diff --git a/opentech/apply/users/decorators.py b/opentech/apply/users/decorators.py new file mode 100644 index 000000000..11539ef29 --- /dev/null +++ b/opentech/apply/users/decorators.py @@ -0,0 +1,20 @@ +from django.conf import settings +from django.core.exceptions import PermissionDenied + + +def require_oauth_whitelist(view_func): + """Simple decorator that limits the use of OAuth to the configure whitelisted domains""" + def decorated_view(request, *args, **kwargs): + user = request.user + + try: + if settings.SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS: + for domain in settings.SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS: + if user.email.endswith(f'@{domain}'): + return view_func(request, *args, **kwargs) + except AttributeError: + raise PermissionDenied + + raise PermissionDenied + + return decorated_view diff --git a/opentech/apply/users/templates/users/account.html b/opentech/apply/users/templates/users/account.html index 725d61531..5873285ed 100644 --- a/opentech/apply/users/templates/users/account.html +++ b/opentech/apply/users/templates/users/account.html @@ -1,12 +1,17 @@ {% extends 'base.html' %} -{% load i18n %} +{% load i18n users_tags %} {% block title %}Account{% endblock %} {% block content %} <h2>Welcome {{ user }}</h2> -{% if show_change_password %} +{% if show_change_password and not backends.associated %} <a href="{% url 'users:password_change' %}">{% trans "Change password" %}</a> {% endif %} + +{% can_use_oauth as show_oauth_link %} +{% if show_oauth_link %} + <a href="{% url 'users:oauth' %}">{% trans "Manage OAuth" %}</a> +{% endif %} {% endblock %} diff --git a/opentech/apply/users/templates/users/oauth.html b/opentech/apply/users/templates/users/oauth.html new file mode 100644 index 000000000..312003ddd --- /dev/null +++ b/opentech/apply/users/templates/users/oauth.html @@ -0,0 +1,41 @@ +{% extends 'base.html' %} +{% load users_tags %} + +{% block title %}OAuth{% endblock %} + +{% block content %} +<div class="social"> + {% if backends.associated %} + <h3>Providers</h3> + <ul> + {% for association in backends.associated %} + <li> + {% if user.password %} + <form id="{{ association.provider|backend_name }}-disconnect" class="disconnect-form col-md-2" action="{% url 'social:disconnect_individual' backend=association.provider association_id=association.id %}?next={% url "users:account" %}" + method="post"> + <input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token }}"> + <button class="btn btn-danger" name="{{ association.provider|backend_class }}"> + Disconnect {{ association.provider|backend_name }} + </button> + </form> + {% else %} + {{ association.provider|backend_name }} + {% endif %} + </li> + {% endfor %} + </ul> + {% endif %} + {% if backends.not_associated %} + <h3>Available OAuth providers</h3> + <ul> + {% for backend in backends.not_associated %} + <li> + <a id="{{ name }}-button" class="col-md-2 btn btn-default" name="{{ backend }}" href="{% url 'social:begin' backend=backend %}"> + Associate with {{ backend|backend_name }} + </a> + </li> + {% endfor %} + </ul> + {% endif %} +</div> +{% endblock %} diff --git a/opentech/apply/users/templatetags/__init__.py b/opentech/apply/users/templatetags/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/opentech/apply/users/templatetags/users_tags.py b/opentech/apply/users/templatetags/users_tags.py new file mode 100644 index 000000000..0125e89bd --- /dev/null +++ b/opentech/apply/users/templatetags/users_tags.py @@ -0,0 +1,40 @@ +from django import template +from django.conf import settings + +register = template.Library() + + +@register.filter +def backend_name(name): + return { + 'google-oauth': 'Google OAuth', + 'google-oauth2': 'Google OAuth', + 'google-openidconnect': 'Google OpenId', + 'facebook-app': 'Facebook', + 'stackoverflow': 'Stack Overflow', + 'yahoo-oauth': 'Yahoo', + 'vimeo': 'Vimeo', + 'linkedin-oauth2': 'LinkedIn OAuth', + 'vk-oauth2': 'VK OAuth', + 'live': 'Windows Live', + }.get(name, name) + + +@register.filter +def backend_class(backend): + return backend.replace('-', ' ') + + +@register.simple_tag(takes_context=True) +def can_use_oauth(context): + user = context.get('user') + + try: + if settings.SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS: + for domain in settings.SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS: + if user.email.endswith(f'@{domain}'): + return True + except AttributeError: + return False + + return False diff --git a/opentech/apply/users/urls.py b/opentech/apply/users/urls.py index 4942e2dad..1fc074d79 100644 --- a/opentech/apply/users/urls.py +++ b/opentech/apply/users/urls.py @@ -2,7 +2,7 @@ from django.conf.urls import url from django.contrib.auth import views as auth_views from django.urls import reverse_lazy -from opentech.apply.users.views import account +from opentech.apply.users.views import account, oauth urlpatterns = [ url(r'^$', account, name='account'), @@ -53,4 +53,5 @@ urlpatterns = [ auth_views.PasswordResetCompleteView.as_view(template_name='users/password_reset/complete.html'), name='password_reset_complete' ), + url(r'^oauth$', oauth, name='oauth'), ] diff --git a/opentech/apply/users/views.py b/opentech/apply/users/views.py index c531b183a..c9bad14df 100644 --- a/opentech/apply/users/views.py +++ b/opentech/apply/users/views.py @@ -1,9 +1,12 @@ from django.contrib.auth.decorators import login_required from django.shortcuts import render +from django.template.response import TemplateResponse from django.urls import reverse_lazy from wagtail.wagtailadmin.views.account import password_management_enabled +from .decorators import require_oauth_whitelist + @login_required(login_url=reverse_lazy('users:login')) def account(request): @@ -12,3 +15,13 @@ def account(request): return render(request, 'users/account.html', { 'show_change_password': password_management_enabled() and request.user.has_usable_password(), }) + + +@login_required(login_url=reverse_lazy('users:login')) +@require_oauth_whitelist +def oauth(request): + """ + Generic, empty view for the OAuth associations. + """ + + return TemplateResponse(request, 'users/oauth.html', {}) -- GitLab