From e0c6a56263d4e6b1034d9bfe42b14f04624cbdfe Mon Sep 17 00:00:00 2001 From: Matthew Smith Date: Fri, 17 Jan 2020 13:13:16 +0000 Subject: [PATCH 1/2] Disable password reset as temporary fix to vulnerability (#396) Disabled password reset and left message notifying user of problem. In response to CVE-2019-19844 --- RIGS/templates/RIGS/password_reset_disable.html | 9 +++++++++ RIGS/urls.py | 2 +- RIGS/views.py | 4 ++++ 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 RIGS/templates/RIGS/password_reset_disable.html diff --git a/RIGS/templates/RIGS/password_reset_disable.html b/RIGS/templates/RIGS/password_reset_disable.html new file mode 100644 index 00000000..eec6e791 --- /dev/null +++ b/RIGS/templates/RIGS/password_reset_disable.html @@ -0,0 +1,9 @@ +{% extends 'base_rigs.html' %} + +{% block title %}Password Reset Disabled{% endblock %} + +{% block content %} +

Password reset is disabled

+

We are very sorry for the inconvenience, but due to a security vulnerability, password reset is currently disabled until the vulnerability can be patched.

+

If you are locked out of your account, please contact an administrator and we can manually perform a reset

+{% endblock %} \ No newline at end of file diff --git a/RIGS/urls.py b/RIGS/urls.py index 3630f7d0..46e70f10 100644 --- a/RIGS/urls.py +++ b/RIGS/urls.py @@ -19,7 +19,7 @@ urlpatterns = [ url('^user/login/$', views.login, name='login'), url('^user/login/embed/$', xframe_options_exempt(views.login_embed), name='login_embed'), - url(r'^user/password_reset/$', password_reset, {'password_reset_form': forms.PasswordReset}), + url(r'^user/password_reset/$', views.PasswordResetDisabled.as_view()), # People url(r'^people/$', permission_required_with_403('RIGS.view_person')(views.PersonList.as_view()), diff --git a/RIGS/views.py b/RIGS/views.py index 023f0089..f8494e25 100644 --- a/RIGS/views.py +++ b/RIGS/views.py @@ -392,3 +392,7 @@ class ResetApiKey(generic.RedirectView): self.request.user.save() return reverse_lazy('profile_detail') + + +class PasswordResetDisabled(generic.TemplateView): + template_name = "RIGS/password_reset_disable.html" From 630011aff7ce365e579343b0cfa7ef44d718c753 Mon Sep 17 00:00:00 2001 From: Arona Jones Date: Fri, 17 Jan 2020 15:28:29 +0000 Subject: [PATCH 2/2] FEAT: Add oembed for assets (#393) * FEAT: Add oembed for assets Don't see the worth in doing supplier currently...we don't OEmbed Org/Venue etc after all... * FIX Copy paste error ;D * Fix embeds not actually working for unauthenticated users This is why I should have written tests... --- PyRIGS/decorators.py | 34 +++++++++++++++++++---- assets/templates/asset_embed.html | 45 +++++++++++++++++++++++++++++++ assets/urls.py | 14 ++++++++-- assets/views.py | 25 +++++++++++++++++ 4 files changed, 111 insertions(+), 7 deletions(-) create mode 100644 assets/templates/asset_embed.html diff --git a/PyRIGS/decorators.py b/PyRIGS/decorators.py index 448839c8..6d48e5e1 100644 --- a/PyRIGS/decorators.py +++ b/PyRIGS/decorators.py @@ -6,6 +6,34 @@ from django.urls import reverse from RIGS import models +def get_oembed(login_url, request, oembed_view, kwargs): + context = {} + context['oembed_url'] = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], reverse(oembed_view, kwargs=kwargs)) + context['login_url'] = "{0}?{1}={2}".format(login_url, REDIRECT_FIELD_NAME, request.get_full_path()) + resp = render(request, 'login_redirect.html', context=context) + return resp + + +def has_oembed(oembed_view, login_url=None): + if not login_url: + from django.conf import settings + login_url = settings.LOGIN_URL + + def _dec(view_func): + def _checklogin(request, *args, **kwargs): + if request.user.is_authenticated: + return view_func(request, *args, **kwargs) + else: + if oembed_view is not None: + return get_oembed(login_url, request, oembed_view, kwargs) + else: + return HttpResponseRedirect('%s?%s=%s' % (login_url, REDIRECT_FIELD_NAME, request.get_full_path())) + _checklogin.__doc__ = view_func.__doc__ + _checklogin.__dict__ = view_func.__dict__ + return _checklogin + return _dec + + def user_passes_test_with_403(test_func, login_url=None, oembed_view=None): """ Decorator for views that checks that the user passes the given test. @@ -25,11 +53,7 @@ def user_passes_test_with_403(test_func, login_url=None, oembed_view=None): return view_func(request, *args, **kwargs) elif not request.user.is_authenticated: if oembed_view is not None: - context = {} - context['oembed_url'] = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], reverse(oembed_view, kwargs=kwargs)) - context['login_url'] = "{0}?{1}={2}".format(login_url, REDIRECT_FIELD_NAME, request.get_full_path()) - resp = render(request, 'login_redirect.html', context=context) - return resp + return get_oembed(login_url, request, oembed_view, kwargs) else: return HttpResponseRedirect('%s?%s=%s' % (login_url, REDIRECT_FIELD_NAME, request.get_full_path())) else: diff --git a/assets/templates/asset_embed.html b/assets/templates/asset_embed.html new file mode 100644 index 00000000..e6b37173 --- /dev/null +++ b/assets/templates/asset_embed.html @@ -0,0 +1,45 @@ +{% extends 'base_embed.html' %} +{% load static from staticfiles %} + +{% block content %} + +
+ + +
+

+ Asset: {{ object.asset_id }} | {{ object.description }} + + Category: + {{ object.category }} + +   + + Status: + {{ object.status }} + +

+
+ {% if object.serial_number %} +

+ Serial Number: + {{ object.serial_number }} +

+ {% endif %} + {% if object.comments %} +

+ Comments: + {{ object.comments|linebreaksbr }} +

+ {% endif %} + + +
+
+ + +{% endblock %} diff --git a/assets/urls.py b/assets/urls.py index dcfb5d24..78e60592 100644 --- a/assets/urls.py +++ b/assets/urls.py @@ -3,12 +3,15 @@ from django.urls import path from assets import views, models from RIGS import versioning -from PyRIGS.decorators import permission_required_with_403 +from django.contrib.auth.decorators import login_required +from django.views.decorators.clickjacking import xframe_options_exempt +from PyRIGS.decorators import has_oembed, permission_required_with_403 urlpatterns = [ path('', views.AssetList.as_view(), name='asset_index'), path('asset/list/', views.AssetList.as_view(), name='asset_list'), - path('asset/id//', views.AssetDetail.as_view(), name='asset_detail'), + # Lazy way to enable the oembed redirect... + path('asset/id//', has_oembed(oembed_view="asset_oembed")(views.AssetDetail.as_view()), name='asset_detail'), path('asset/create/', permission_required_with_403('assets.add_asset') (views.AssetCreate.as_view()), name='asset_create'), path('asset/id//edit/', permission_required_with_403('assets.change_asset') @@ -21,6 +24,13 @@ urlpatterns = [ (views.ActivityTable.as_view()), name='asset_activity_table'), path('asset/search/', views.AssetSearch.as_view(), name='asset_search_json'), + path('asset/id//embed/', + xframe_options_exempt( + login_required(login_url='/user/login/embed/')(views.AssetEmbed.as_view())), + name='asset_embed'), + path('asset/id//oembed_json/', + views.AssetOembed.as_view(), + name='asset_oembed'), path('supplier/list', views.SupplierList.as_view(), name='supplier_list'), path('supplier/', views.SupplierDetail.as_view(), name='supplier_detail'), diff --git a/assets/views.py b/assets/views.py index d0e5135e..96e8940d 100644 --- a/assets/views.py +++ b/assets/views.py @@ -1,5 +1,6 @@ from django.contrib.auth.mixins import LoginRequiredMixin from django.http import JsonResponse +from django.http import HttpResponse from django.views import generic from django.views.decorators.csrf import csrf_exempt from django.utils.decorators import method_decorator @@ -9,6 +10,8 @@ from django.shortcuts import get_object_or_404 from assets import models, forms from RIGS import versioning +import simplejson + @method_decorator(csrf_exempt, name='dispatch') class AssetList(LoginRequiredMixin, generic.ListView): @@ -149,6 +152,28 @@ class AssetDuplicate(DuplicateMixin, AssetIDUrlMixin, AssetCreate): return context +class AssetOembed(generic.View): + model = models.Asset + + def get(self, request, pk=None): + embed_url = reverse('asset_embed', args=[pk]) + full_url = "{0}://{1}{2}".format(request.scheme, request.META['HTTP_HOST'], embed_url) + + data = { + 'html': ''.format(full_url), + 'version': '1.0', + 'type': 'rich', + 'height': '250' + } + + json = simplejson.JSONEncoderForHTML().encode(data) + return HttpResponse(json, content_type="application/json") + + +class AssetEmbed(AssetDetail): + template_name = 'asset_embed.html' + + class SupplierList(generic.ListView): model = models.Supplier template_name = 'supplier_list.html'