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