Skip to content

Commit

Permalink
Merge pull request #871 from WikipediaLibrary/jason-T294919
Browse files Browse the repository at this point in the history
Create a URL which initiates a library search
  • Loading branch information
suecarmol committed Nov 10, 2021
2 parents 13cf286 + 17a7462 commit fed2225
Show file tree
Hide file tree
Showing 12 changed files with 219 additions and 11 deletions.
59 changes: 59 additions & 0 deletions TWLight/forms.py
@@ -0,0 +1,59 @@
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Hidden, Submit, Layout

from django.conf import settings
from django.contrib.auth.models import User

from django import forms
from django.utils.translation import get_language, gettext as _


class EdsSearchForm(forms.Form):
""" """

lang = forms.ChoiceField(choices=settings.LANGUAGES)
schemaId = forms.CharField()
custid = forms.CharField()
groupid = forms.CharField()
profid = forms.CharField()
scope = forms.CharField()
site = forms.CharField()
direct = forms.CharField()
authtype = forms.CharField()
bquery = forms.CharField()

def __init__(self, *args, **kwargs):
language_code = get_language()
lang = language_code
bquery = kwargs.pop("bquery", None)

super().__init__(*args, **kwargs)
if language_code == "pt":
lang = "pt-pt"
elif language_code == "zh-hans":
lang = "zh-cn"
elif language_code == "zh-hant":
lang = "zh-tw"
self.helper = FormHelper()
self.helper.form_id = "search"
self.helper.form_action = "https://searchbox.ebsco.com/search/"
self.helper.form_method = "GET"
self.helper.label_class = "sr-only"
self.helper.layout = Layout(
Hidden("bquery", bquery),
Hidden("lang", lang),
Hidden("schemaId", "search"),
Hidden("custid", "ns253359"),
Hidden("groupid", "main"),
Hidden("profid", "eds"),
Hidden("scope", "site"),
Hidden("site", "eds-live"),
Hidden("direct", "true"),
Hidden("authtype", "url"),
Submit(
"submit",
# Translators: Shown in the search button.
_("Search"),
css_class="btn eds-search-button",
),
)
3 changes: 3 additions & 0 deletions TWLight/settings/base.py
Expand Up @@ -271,6 +271,9 @@ def show_toolbar(request):
# Overwrite django default SESSION_COOKIE_AGE
SESSION_COOKIE_AGE = 259200

# Add header to work with EDS
SECURE_REFERRER_POLICY = "origin"

# INTERNATIONALIZATION CONFIGURATION
# ------------------------------------------------------------------------------

Expand Down
9 changes: 9 additions & 0 deletions TWLight/static/css/new-local.css
Expand Up @@ -390,6 +390,15 @@ COMMON CONTENT BLOCK CSS
padding: 0.5rem 1rem;
}

/*
----------------------------------------------------------------------------
NOSCRIPT CSS
--------------------------------------------------------------------------
*/
#noscript {
color: red;
}

/*
----------------------------------------------------------------------------
HOMEPAGE CAROUSEL CSS
Expand Down
23 changes: 23 additions & 0 deletions TWLight/templates/eds_search_endpoint.html
@@ -0,0 +1,23 @@
{% extends "new_base.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% load twlight_perms %}
{% block content %}
{% comment %}Translators: On a special 'link to search' page, this message is shown if JavaScript is disabled.{% endcomment %}
<p id="noscript">{% trans "JavaScript is disabled; use the button below to continue." %}</p>
{% crispy form %}
{% endblock %}

{% block javascript %}
<script>
// On page load
document.addEventListener("DOMContentLoaded", function(event) {
// Delete noscript element.
// <noscript> text was not showing up in testing with the noscript extension.
var noscript = document.getElementById("noscript");
noscript.parentNode.removeChild(noscript);
// Submit the form.
document.createElement('form').submit.call(document.getElementById('search'));
});
</script>
{% endblock %}
65 changes: 65 additions & 0 deletions TWLight/tests.py
Expand Up @@ -34,6 +34,7 @@
PartnerCoordinatorOrSelf,
CoordinatorsOnly,
EditorsOnly,
EligibleEditorsOnly,
SelfOnly,
ToURequired,
EmailRequired,
Expand Down Expand Up @@ -81,6 +82,10 @@ class TestEditorsOnly(EditorsOnly, DispatchProvider):
pass


class TestEligibleEditorsOnly(EligibleEditorsOnly, DispatchProvider):
pass


class TestSelfOnly(SelfOnly, ObjGet, DispatchProvider):
pass

Expand Down Expand Up @@ -267,6 +272,66 @@ def test_editors_only_3(self):
with self.assertRaises(PermissionDenied):
test.dispatch(req)

def test_eligible_editors_only_1(self):
"""
EligibleEditorsOnly allows eligible editors.
"""
user = UserFactory()
_ = EditorFactory(user=user)
_.wp_bundle_eligible = True

req = RequestFactory()
req.user = user

test = TestEligibleEditorsOnly()
test.dispatch(req)

def test_eligible_editors_only_2(self):
"""
EligibleEditorsOnly does *not* allow superusers who aren't editors.
"""
user = UserFactory(is_superuser=True)
self.assertFalse(hasattr(user, "editor"))

req = RequestFactory()
req.user = user

test = TestEligibleEditorsOnly()
with self.assertRaises(PermissionDenied):
test.dispatch(req)

def test_eligible_editors_only_3(self):
"""
EligibleEditorsOnly does not allow non-superusers who aren't editors.
"""
user = UserFactory(is_superuser=False)
self.assertFalse(hasattr(user, "editor"))

req = RequestFactory()
req.user = user

test = TestEligibleEditorsOnly()
with self.assertRaises(PermissionDenied):
test.dispatch(req)

def test_eligible_editors_only_4(self):
"""
EligibleEditorsOnly redirects ineligible editors.
"""
user = UserFactory(is_superuser=False)
self.assertFalse(hasattr(user, "editor"))
_ = EditorFactory(user=user)
_.wp_bundle_eligible = False

req = RequestFactory()
req.user = user

test = TestEligibleEditorsOnly()

resp = test.dispatch(req)
# This test doesn't deny permission; it sends people to my_library.
self.assertTrue(isinstance(resp, HttpResponseRedirect))

def test_self_only_1(self):
"""
SelfOnly allows users who are also the object returned by get_object.
Expand Down
7 changes: 6 additions & 1 deletion TWLight/urls.py
Expand Up @@ -28,7 +28,7 @@
from TWLight.users.views import TermsView
from TWLight.ezproxy.urls import urlpatterns as ezproxy_urls

from .views import NewHomePageView
from .views import NewHomePageView, SearchEndpointFormView

handler400 = "TWLight.views.bad_request"

Expand Down Expand Up @@ -88,6 +88,11 @@
url(r"^contact/$", ContactUsView.as_view(), name="contact"),
url(r"^$", NewHomePageView.as_view(), name="homepage"),
url(r"^about/$", TemplateView.as_view(template_name="about.html"), name="about"),
url(
r"^search/$",
login_required(SearchEndpointFormView.as_view()),
name="search",
),
]

# Enable debug_toolbar if configured
Expand Down
13 changes: 10 additions & 3 deletions TWLight/users/oauth.py
Expand Up @@ -293,12 +293,15 @@ def get(self, request, *args, **kwargs):
next = query_dict.pop("next")
# Set the return url to the value of 'next'. Basic.
return_url = next[0]
# Pop the 'from_homepage' parameter out of the QueryDict.
# We don't need it here.
query_dict.pop("from_homepage", None)
# If there is anything left in the QueryDict after popping
# 'next', append it to the return url. This preserves state
# for filtered lists and redirected form submissions like
# the partner suggestion form.
if query_dict:
return_url += "?" + urlencode(query_dict)
return_url += "&" + urlencode(query_dict)
logger.info(
"User is already authenticated. Sending them on "
'for post-login redirection per "next" parameter.'
Expand Down Expand Up @@ -332,7 +335,8 @@ def get(self, request, *args, **kwargs):
next = query_dict.pop("next")
# Set the return url to the value of 'next'. Basic.
return_url = next[0]
from_homepage = query_dict.get("from_homepage", None)
# Pop the 'from_homepage' parameter out of the QueryDict.
from_homepage = query_dict.pop("from_homepage", None)

if from_homepage:
logger.info("Logging in from homepage, redirecting to Meta login")
Expand Down Expand Up @@ -513,12 +517,15 @@ def get(self, request, *args, **kwargs):
next = query_dict.pop("next")
# Set the return url to the value of 'next'. Basic.
return_url = next[0]
# Pop the 'from_homepage' parameter out of the QueryDict.
# We don't need it here.
query_dict.pop("from_homepage", None)
# If there is anything left in the QueryDict after popping
# 'next', append it to the return url. This preserves state
# for filtered lists and redirected form submissions like
# the partner suggestion form.
if query_dict:
return_url += "?" + urlencode(query_dict)
return_url += "&" + urlencode(query_dict)
logger.info(
"User authenticated. Sending them on for "
'post-login redirection per "next" parameter.'
Expand Down
25 changes: 25 additions & 0 deletions TWLight/view_mixins.py
Expand Up @@ -21,6 +21,7 @@
from TWLight.applications.models import Application
from TWLight.resources.models import Partner
from TWLight.users.models import Editor
from TWLight.users.helpers.editor_data import editor_bundle_eligible
from TWLight.users.groups import COORDINATOR_GROUP_NAME, RESTRICTED_GROUP_NAME

import logging
Expand Down Expand Up @@ -205,6 +206,30 @@ def dispatch(self, request, *args, **kwargs):
return super(EditorsOnly, self).dispatch(request, *args, **kwargs)


class EligibleEditorsOnly(object):
"""
Restricts visibility to:
* Eligible Editors.
Raises Permission denied for non-editors.
Redirects to my_library with message for ineligible editors.
"""

def dispatch(self, request, *args, **kwargs):
user = request.user
if not test_func_editors_only(user):
messages.add_message(
request,
messages.WARNING,
"You must be a coordinator or an editor to do that.",
)
raise PermissionDenied
elif not editor_bundle_eligible(user.editor):
# Send ineligible editors to my_library for info on eligibility.
return HttpResponseRedirect(reverse_lazy("users:my_library"))
return super().dispatch(request, *args, **kwargs)


def test_func_tou_required(user):
try:
return user.is_superuser or user.userprofile.terms_of_use
Expand Down
20 changes: 19 additions & 1 deletion TWLight/views.py
@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
import json

from django.views.generic import TemplateView
from django.views.generic import RedirectView, TemplateView
from django.views.generic.edit import FormView
from django.views import View
from django.conf import settings
from django.contrib.messages import get_messages
Expand All @@ -17,6 +18,9 @@
from TWLight.resources.models import Partner, PartnerLogo
from TWLight.resources.helpers import get_partner_description, get_tag_dict

from .forms import EdsSearchForm
from .view_mixins import EligibleEditorsOnly

import logging

from django.views.defaults import ERROR_400_TEMPLATE_NAME, ERROR_PAGE_TEMPLATE
Expand Down Expand Up @@ -125,6 +129,20 @@ def get(self, request):
return render(request, "homepage.html", context)


class SearchEndpointFormView(EligibleEditorsOnly, FormView):
"""
Allows persistent links to EDS searches with referring URL authentication.
"""

def get_form_kwargs(self, **kwargs):
kwargs = super().get_form_kwargs()
kwargs["bquery"] = self.request.GET.get("q")
return kwargs

template_name = "eds_search_endpoint.html"
form_class = EdsSearchForm


@sensitive_variables()
@requires_csrf_token
def bad_request(request, exception, template_name=ERROR_400_TEMPLATE_NAME):
Expand Down
2 changes: 0 additions & 2 deletions conf/local.nginx.conf
Expand Up @@ -53,8 +53,6 @@ server {
# redirects, we set the Host: header above already.
proxy_redirect off;
proxy_pass http://twlight;
# Add header to work with EDS
add_header Referrer-Policy 'origin';
}

error_page 500 502 503 504 /500.html;
Expand Down
2 changes: 0 additions & 2 deletions conf/production.nginx.conf
Expand Up @@ -94,8 +94,6 @@ server {
# redirects, we set the Host: header above already.
proxy_redirect off;
proxy_pass http://twlight;
# Add header to work with EDS
add_header Referrer-Policy 'origin';
}

proxy_intercept_errors on;
Expand Down
2 changes: 0 additions & 2 deletions conf/staging.nginx.conf
Expand Up @@ -95,8 +95,6 @@ server {
# redirects, we set the Host: header above already.
proxy_redirect off;
proxy_pass http://twlight;
# Add header to work with EDS
add_header Referrer-Policy 'origin';
}

proxy_intercept_errors on;
Expand Down

0 comments on commit fed2225

Please sign in to comment.