From dc480301663518ac86647de8406464b8f323fc01 Mon Sep 17 00:00:00 2001 From: Tanner Prestegard <tanner.prestegard@ligo.org> Date: Wed, 13 Feb 2019 12:01:22 -0600 Subject: [PATCH] Reorganize alerts index and manage password views Small fixes to the main "index" view for alerts. Move the manage_password page for LV-EM observers to the ligoauth app and show a link to it from the navbar to those users. --- config/urls.py | 7 +- gracedb/alerts/tests/test_access.py | 26 +------- gracedb/alerts/urls.py | 5 -- gracedb/alerts/views.py | 66 +++---------------- gracedb/ligoauth/tests/test_access.py | 25 +++++++ gracedb/ligoauth/views.py | 46 ++++++++++++- .../notifications.html => alerts/index.html} | 11 ---- .../templates/ligoauth/manage_password.html | 4 +- gracedb/templates/navbar_frag.html | 8 ++- 9 files changed, 91 insertions(+), 107 deletions(-) create mode 100644 gracedb/ligoauth/tests/test_access.py rename gracedb/templates/{profile/notifications.html => alerts/index.html} (79%) diff --git a/config/urls.py b/config/urls.py index 0129d1191..f88027805 100644 --- a/config/urls.py +++ b/config/urls.py @@ -10,7 +10,7 @@ import core.views from events.feeds import EventFeed, feedview import events.reports import events.views -from ligoauth.views import pre_login, post_login, shib_logout +from ligoauth.views import pre_login, post_login, shib_logout, manage_password import search.views # Django admin auto-discover @@ -33,7 +33,7 @@ urlpatterns = [ template_name='discovery.html'), name="discovery"), url(r'^events/', include('events.urls')), url(r'^superevents/', include('superevents.urls')), - url(r'^options/', include('alerts.urls')), + url(r'^alerts/', include('alerts.urls')), url(r'^feeds/(?P<url>.*)/$', EventFeed()), url(r'^feeds/$', feedview, name="feeds"), @@ -51,6 +51,9 @@ urlpatterns = [ url(r'^post-login/$', post_login, name='post-login'), url(r'^logout/$', shib_logout, name='logout'), + # Password management + url('^manage-password/$', manage_password, name='manage-password'), + # API URLs url(r'^api/', include('api.urls')), # Legacy API URLs - must be maintained! diff --git a/gracedb/alerts/tests/test_access.py b/gracedb/alerts/tests/test_access.py index 554cd92de..a00ea4b0b 100644 --- a/gracedb/alerts/tests/test_access.py +++ b/gracedb/alerts/tests/test_access.py @@ -21,35 +21,13 @@ class TestIndexView(GraceDbTestBase): """LV-EM user can get to index view""" url = reverse('alerts:index') response = self.request_as_user(url, "GET", self.lvem_user) - self.assertEqual(response.status_code, 200) + self.assertEqual(response.status_code, 403) def test_public_user_get(self): - """Public user can get to index view""" + """Public user can't get to index view""" url = reverse('alerts:index') response = self.request_as_user(url, "GET") # Should be redirected to login page - self.assertEqual(response.status_code, 302) - - -class TestManagePasswordView(GraceDbTestBase): - """Test user access to page for generating basic auth password for API""" - - def test_internal_user_get(self): - """Internal user *can't* get to manage password page""" - url = reverse('alerts:manage-password') - response = self.request_as_user(url, "GET", self.internal_user) - self.assertEqual(response.status_code, 403) - - def test_lvem_user_get(self): - """LV-EM user can get to manage password page""" - url = reverse('alerts:manage-password') - response = self.request_as_user(url, "GET", self.lvem_user) - self.assertEqual(response.status_code, 200) - - def test_public_user_get(self): - """Public user can't get to manage password page""" - url = reverse('alerts:manage-password') - response = self.request_as_user(url, "GET") self.assertEqual(response.status_code, 403) diff --git a/gracedb/alerts/urls.py b/gracedb/alerts/urls.py index 41430b368..069b6cd77 100644 --- a/gracedb/alerts/urls.py +++ b/gracedb/alerts/urls.py @@ -31,9 +31,4 @@ urlpatterns = [ views.EditNotificationView.as_view(), name="edit-notification"), url(r'^notification/(?P<pk>\d+)/delete/$', views.DeleteNotificationView.as_view(), name="delete-notification"), - - # Manage password - url(r'^manage_password/$', views.managePassword, - name="manage-password"), - ] diff --git a/gracedb/alerts/views.py b/gracedb/alerts/views.py index 2d0c93724..52ea08164 100644 --- a/gracedb/alerts/views.py +++ b/gracedb/alerts/views.py @@ -2,35 +2,24 @@ import logging from django.conf import settings from django.contrib import messages -from django.contrib.auth.decorators import login_required -from django.contrib.auth.models import User from django.core.mail import EmailMessage -from django.http import ( - HttpResponse, HttpResponseRedirect, HttpResponseNotFound, - Http404, HttpResponseForbidden, HttpResponseBadRequest -) -from django.template import RequestContext +from django.http import HttpResponseRedirect from django.shortcuts import render from django.urls import reverse, reverse_lazy from django.utils import timezone from django.utils.decorators import method_decorator -from django.utils.http import urlencode -from django.utils.safestring import mark_safe -from django.views.generic.edit import FormView, DeleteView, UpdateView -from django.views.generic.base import ContextMixin -from django.views.generic.detail import SingleObjectMixin, DetailView +from django.views.generic.edit import DeleteView, UpdateView +from django.views.generic.detail import DetailView from django_twilio.client import twilio_client from core.views import MultipleFormView -from events.permission_utils import lvem_user_required, is_external -from events.models import Label from ligoauth.decorators import internal_user_required from .forms import ( PhoneContactForm, EmailContactForm, VerifyContactForm, EventNotificationForm, SupereventNotificationForm, ) -from .models import Notification, Contact +from .models import Contact, Notification from .phone import get_twilio_from @@ -38,51 +27,18 @@ from .phone import get_twilio_from logger = logging.getLogger(__name__) -@login_required +############################################################################### +# Generic views ############################################################### +############################################################################### +@internal_user_required def index(request): context = { 'notifications': request.user.notification_set.all(), 'contacts': request.user.contact_set.all(), } - return render(request, 'profile/notifications.html', context=context) - - -@lvem_user_required -def managePassword(request): - # lvem_user_required only checks for LVEM group membership, - # not the absence of LVC membership. We want this page to be - # forbidden to LVC members - they don't need passwords since they - # have certificate-based access to the API. - if not is_external(request.user): - return HttpResponseForbidden("Forbidden") - - # Set up context dictionary - d = { 'username': request.user.username } - - if request.method == "POST": - password = User.objects.make_random_password(length=20) - d['password'] = password - request.user.set_password(password) - request.user.date_joined = timezone.now() - request.user.save() - - if request.user.has_usable_password(): - d['has_password'] = True - # Check if password is expired - # NOTE: This is super hacky because we are using date_joined to store - # the date when the password was set. - password_expiry = request.user.date_joined + \ - settings.PASSWORD_EXPIRATION_TIME - timezone.now() - if (password_expiry.total_seconds() < 0): - d['expired'] = True - else: - d['expired'] = False - d['expiration_days'] = password_expiry.days - else: - d['has_password'] = False + return render(request, 'alerts/index.html', context=context) - return render(request, 'profile/manage_password.html', context=d) ############################################################################### # Notification views ########################################################## @@ -154,7 +110,6 @@ class EditNotificationView(UpdateView): @method_decorator(internal_user_required, name='dispatch') class DeleteNotificationView(DeleteView): """Delete a notification""" - model = Notification success_url = reverse_lazy('alerts:index') def get(self, request, *args, **kwargs): @@ -243,7 +198,6 @@ class EditContactView(UpdateView): @method_decorator(internal_user_required, name='dispatch') class DeleteContactView(DeleteView): """Delete a contact""" - model = Contact success_url = reverse_lazy('alerts:index') def get(self, request, *args, **kwargs): @@ -267,7 +221,6 @@ class DeleteContactView(DeleteView): class TestContactView(DetailView): """Test a contact (must be verified already)""" # Send alerts to all contact methods - model = Contact success_url = reverse_lazy('alerts:index') def get_queryset(self): @@ -352,7 +305,6 @@ class VerifyContactView(UpdateView): @method_decorator(internal_user_required, name='dispatch') class RequestVerificationCodeView(DetailView): """Redirect view for requesting a contact verification code""" - model = Contact def get_queryset(self): return self.request.user.contact_set.all() diff --git a/gracedb/ligoauth/tests/test_access.py b/gracedb/ligoauth/tests/test_access.py new file mode 100644 index 000000000..64bd3c4d6 --- /dev/null +++ b/gracedb/ligoauth/tests/test_access.py @@ -0,0 +1,25 @@ +from django.urls import reverse + +from core.tests.utils import GraceDbTestBase + + +class TestManagePasswordView(GraceDbTestBase): + """Test user access to page for generating basic auth password for API""" + + def test_internal_user_get(self): + """Internal user *can't* get to manage password page""" + url = reverse('manage-password') + response = self.request_as_user(url, "GET", self.internal_user) + self.assertEqual(response.status_code, 403) + + def test_lvem_user_get(self): + """LV-EM user can get to manage password page""" + url = reverse('manage-password') + response = self.request_as_user(url, "GET", self.lvem_user) + self.assertEqual(response.status_code, 200) + + def test_public_user_get(self): + """Public user can't get to manage password page""" + url = reverse('manage-password') + response = self.request_as_user(url, "GET") + self.assertEqual(response.status_code, 403) diff --git a/gracedb/ligoauth/views.py b/gracedb/ligoauth/views.py index 5a638689d..9d6084a35 100644 --- a/gracedb/ligoauth/views.py +++ b/gracedb/ligoauth/views.py @@ -1,10 +1,19 @@ import logging from django.conf import settings -from django.contrib.auth import logout -from django.http import HttpResponseRedirect -from django.shortcuts import resolve_url +from django.contrib.auth import ( + logout, get_user_model, update_session_auth_hash, +) +from django.http import HttpResponseRedirect, HttpResponseForbidden +from django.shortcuts import resolve_url, render from django.urls import reverse +from django.utils import timezone + +from .decorators import lvem_observers_only + + +# Set up user model +UserModel = get_user_model() # Set up logger logger = logging.getLogger(__name__) @@ -85,3 +94,34 @@ def shib_logout(request): resolve_url(settings.LOGOUT_REDIRECT_URL)) return HttpResponseRedirect(original_url) + + +@lvem_observers_only(superuser_allowed=True) +def manage_password(request): + # Set up context dictionary + d = {} + + if request.method == "POST": + password = UserModel.objects.make_random_password(length=20) + d['password'] = password + request.user.set_password(password) + request.user.date_joined = timezone.now() + request.user.save() + update_session_auth_hash(request, request.user) + + if request.user.has_usable_password(): + d['has_password'] = True + # Check if password is expired + # NOTE: This is super hacky because we are using date_joined to store + # the date when the password was set. + password_expiry = request.user.date_joined + \ + settings.PASSWORD_EXPIRATION_TIME - timezone.now() + if (password_expiry.total_seconds() < 0): + d['expired'] = True + else: + d['expired'] = False + d['expiration_days'] = password_expiry.days + else: + d['has_password'] = False + + return render(request, 'ligoauth/manage_password.html', context=d) diff --git a/gracedb/templates/profile/notifications.html b/gracedb/templates/alerts/index.html similarity index 79% rename from gracedb/templates/profile/notifications.html rename to gracedb/templates/alerts/index.html index 0425a6454..535b7532b 100644 --- a/gracedb/templates/profile/notifications.html +++ b/gracedb/templates/alerts/index.html @@ -69,17 +69,6 @@ {% endif %} <a class="btn btn-success" href="{% url "alerts:create-notification" %}">Create new notification</a> -<br/><br/> -{% endif %} - -{# Show password page only to LV-EM users who are not also internal #} -{# But let the admins (superusers) see it with some additions #} -{% if user_is_lvem and not user_is_internal or user.is_superuser %} - {% if user.is_superuser %} - <h2 style="border: 2px solid red; color: red; display: inline-block;"><b>The following section is only visible to non-internal LV-EM members. You can see it (and this message) because you are a superuser.</b></h2> - {% endif %} -<h1>Passwords for Scripted Access</h1> -<a class="btn btn-dark" href="{% url "alerts:manage-password" %}">Manage password</a> {% endif %} {% endblock %} diff --git a/gracedb/templates/ligoauth/manage_password.html b/gracedb/templates/ligoauth/manage_password.html index ee5e5b913..c780a4757 100644 --- a/gracedb/templates/ligoauth/manage_password.html +++ b/gracedb/templates/ligoauth/manage_password.html @@ -8,7 +8,7 @@ <p>Passwords generated here are intended only for scripted access to the GraceDB <a href={% url "legacy_apiweb:default:root" %}>REST API</a> by LV-EM users.</p> -<p> Your username is: <b>{{ username }}</b></p> +<p> Your username is: <b>{{ user.username }}</b></p> {% if has_password %} {% if password %} <p>Your password is: <b>{{ password }}</b></p> @@ -26,7 +26,7 @@ <p>Press the button here to get a new password (or change your existing one):</p> -<form action={% url "alerts:manage-password" %} method="post"> +<form action={% url "manage-password" %} method="post"> <input type="submit" value="Get me a password!"> </form> diff --git a/gracedb/templates/navbar_frag.html b/gracedb/templates/navbar_frag.html index 82c48b9a1..12d307037 100644 --- a/gracedb/templates/navbar_frag.html +++ b/gracedb/templates/navbar_frag.html @@ -8,12 +8,14 @@ <li id="nav-feeds"><a href="{% url "feeds" %}">RSS</a></li> {% endif %} <li id="nav-latest"><a href="{% url "latest" %}">Latest</a></li> - {% if user.is_authenticated %} - <li id="nav-userprofile"><a href="{% url "alerts:index" %}">Options</a></li> + {% if user_is_internal %} + <li id="nav-userprofile"><a href="{% url "alerts:index" %}">Alerts</a></li> + {% elif user_is_lvem %} + <li id="nav-userprofile"><a href="{% url "manage-password" %}">Manage Password</a></li> {% endif %} <li id="nav-docs"><a href="{% url "home" %}documentation/">Documentation</a></li> {% if user %} - {% if user.is_staff %} + {% if user.is_superuser %} <li id="nav-admin-docs"><a href="{% url "home" %}admin_docs/">Admin docs</a></li> {% endif %} {% endif %} -- GitLab