Skip to content

Commit

Permalink
Merge pull request rdmorganiser#641 from rdmorganiser/dev-1.11.0
Browse files Browse the repository at this point in the history
RDMO 1.11.0
  • Loading branch information
jochenklar authored Aug 1, 2023
2 parents cc454a3 + 2d9b75f commit 5f89662
Show file tree
Hide file tree
Showing 45 changed files with 1,765 additions and 742 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Changelog

## RDMO 1.11.0 (Aug 1, 2023)

* Refactor Shibboleth setup, add LOGIN_FORM, SHIBBOLETH_LOGIN_URL
* Add filter for catalogs to site_projects view
* Add API for project Invites
* Add catalog, site, and rdmo version to views
* Enable PROJECT_QUESTIONS_AUTOSAVE by default
* Remove skip button when PROJECT_QUESTIONS_AUTOSAVE is True, move back button
* Remove automatic replacement of missing translations, unless REPLACE_MISSING_TRANSLATION is True
* Hide html metadata tag in views
* Update django-allauth requirement

## RDMO 1.10.0 (Apr 27, 2023)

* Allow users to create API access tokens (if ACCOUNT_ALLOW_USER_TOKEN is set)
Expand Down
4 changes: 4 additions & 0 deletions rdmo/accounts/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,5 +133,9 @@
('projects', 'integration', 'change_integration'),
('projects', 'integration', 'delete_integration'),
('projects', 'integration', 'view_integration'),
('projects', 'invite', 'add_invite'),
('projects', 'invite', 'change_invite'),
('projects', 'invite', 'delete_invite'),
('projects', 'invite', 'view_invite'),
))
)
16 changes: 13 additions & 3 deletions rdmo/accounts/templates/account/login_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,23 @@

{% if settings.SHIBBOLETH %}

<a class="btn btn-success extend" href="{{ settings.LOGIN_URL }}">
{% if settings.SHIBBOLETH_LOGIN_URL %}
<p>
<a class="btn btn-success extend" href="{% url 'shibboleth_login' %}">
{% trans 'Login with Shibboleth' %}
</a>

</p>
{% else %}
<p>
<a class="btn btn-success extend" href="{% url 'account_login' %}">
{% trans 'Login with Shibboleth' %}
</a>
</p>
{% endif %}

{% endif %}

{% if settings.LOGIN_FORM %}
<form method="post" action="{% url 'account_login' %}">
{% csrf_token %}

Expand All @@ -21,7 +32,6 @@

<input type="submit" class="btn btn-primary" value="{% trans 'Login' %}" />
</form>

{% endif %}

{% if settings.ACCOUNT %}
Expand Down
120 changes: 119 additions & 1 deletion rdmo/accounts/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
from django.urls import reverse
from django.urls.exceptions import NoReverseMatch

from pytest_django.asserts import assertTemplateUsed
from pytest_django.asserts import assertTemplateUsed, assertRedirects, \
assertInHTML, assertContains, \
assertNotContains, assertURLEqual

from rdmo.accounts.tests.utils import reload_app_urlconf_in_testcase

Expand Down Expand Up @@ -595,3 +597,119 @@ def test_token_post_for_anonymous(db, client, settings, test_setting):
else:
with pytest.raises(NoReverseMatch):
reverse('account_token')


@pytest.mark.parametrize('LOGIN_FORM', boolean_toggle)
@pytest.mark.parametrize('username,password', users)
def test_home_login_form(db, client, settings, LOGIN_FORM, username, password):
settings.LOGIN_FORM = LOGIN_FORM
reload_app_urlconf_in_testcase('accounts')
# Anonymous user lands on home
client.login(username='anonymous', password=None)
url = reverse('home')
response = client.get(url)
response.status_code == 200
if LOGIN_FORM:
assertContains(response, f'<form method="post" action="{reverse("account_login")}"')
else:
assertNotContains(response, f'<form method="post" action="{reverse("account_login")}"')


@pytest.mark.parametrize('SHIBBOLETH', boolean_toggle)
@pytest.mark.parametrize('SHIBBOLETH_LOGIN_URL', (None, '/shibboleth/login'))
@pytest.mark.parametrize('username,password', users)
def test_shibboleth_for_home_url(db, client, settings, SHIBBOLETH, SHIBBOLETH_LOGIN_URL, username, password):
settings.SHIBBOLETH = SHIBBOLETH
settings.SHIBBOLETH_LOGIN_URL = SHIBBOLETH_LOGIN_URL
settings.ACCOUNT = False
reload_app_urlconf_in_testcase('accounts')
# Anonymous user lands on home
client.login(username='anonymous', password=None)
url = reverse('home')
response = client.get(url)

if SHIBBOLETH:
# Anyonymous user is redirected to login
response.status_code == 200

if SHIBBOLETH_LOGIN_URL:
assertContains(response, 'href="' + reverse('shibboleth_login'))
else:
assertContains(response, 'href="' + reverse('account_login'))


@pytest.mark.parametrize('username,password', users)
def test_shibboleth_login_view(db, client, settings, username, password):
settings.SHIBBOLETH = True
settings.SHIBBOLETH_LOGIN_URL = '/shibboleth/login'
reload_app_urlconf_in_testcase('accounts')
# Anonymous user lands on home
client.login(username='anonymous', password=None)

if settings.SHIBBOLETH and settings.SHIBBOLETH_LOGIN_URL:
url = reverse('shibboleth_login')
response = client.get(url)

# Anyonymous user is redirected to login
assertRedirects(response,
settings.SHIBBOLETH_LOGIN_URL + f'?target={response.request["PATH_INFO"]}',
target_status_code=404)

# Redirected user logs in
if password:
client.login(username=username, password=password)
response = client.get(url)
assertRedirects(response, reverse('projects'))


@pytest.mark.parametrize('SHIBBOLETH', boolean_toggle)
@pytest.mark.parametrize('SHIBBOLETH_LOGIN_URL', (None, '/shibboleth/login'))
@pytest.mark.parametrize('username,password', users)
def test_shibboleth_for_projects_url(db, client, settings, SHIBBOLETH, SHIBBOLETH_LOGIN_URL, username, password):
settings.SHIBBOLETH = SHIBBOLETH
settings.SHIBBOLETH_LOGIN_URL = SHIBBOLETH_LOGIN_URL
settings.ACCOUNT = False
reload_app_urlconf_in_testcase('accounts')
client.login(username='anonymous', password=None)
# Try to access projects
url = reverse('projects')
response = client.get(url)

if SHIBBOLETH and SHIBBOLETH_LOGIN_URL:
# Anyonymous user is redirected to login
response.status_code == 302

assertRedirects(response, reverse('account_login') + '?next=' + reverse('projects'))

response = client.get(response.url)
# Shibboleth login is shown
response.status_code == 200
assertContains(response, 'href="/account/shibboleth/login/">')

# Redirected user logs in
client.login(username=username, password=password)
response = client.get(response)

if password:
# Logged in user lands on projects
response.status_code == 200
else:
# Anonymous user is redirected to shibboleth login
response.status_code == 404


@pytest.mark.parametrize('SHIBBOLETH', boolean_toggle)
@pytest.mark.parametrize('username,password', users)
def test_shibboleth_logout_username_pattern(db, client, settings, SHIBBOLETH, username, password):
settings.SHIBBOLETH = SHIBBOLETH
settings.SHIBBOLETH_USERNAME_PATTERN = username
reload_app_urlconf_in_testcase('accounts')

client.login(username=username, password=password)
if SHIBBOLETH:
url = reverse('shibboleth_logout')
response = client.get(url)
if password is not None:
assertURLEqual(response.url, reverse('account_logout') + f'?next={settings.SHIBBOLETH_LOGOUT_URL}')
else:
assertURLEqual(response.url, reverse('account_logout'))
18 changes: 13 additions & 5 deletions rdmo/accounts/urls/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
from django.contrib.auth import views as auth_views
from django.urls import include, re_path

from ..views import profile_update, remove_user, terms_of_use, token
from ..views import (profile_update, remove_user, shibboleth_login,
shibboleth_logout, terms_of_use, token)

urlpatterns = [
# edit own profile
Expand All @@ -20,16 +21,23 @@
urlpatterns += [
re_path(r'^', include('allauth.urls'))
]
elif settings.SHIBBOLETH:
urlpatterns += [
re_path('^logout/', auth_views.LogoutView.as_view(next_page=settings.SHIBBOLETH_LOGOUT_URL), name='account_logout'),
]
else:
urlpatterns += [
re_path('^login/', auth_views.LoginView.as_view(template_name='account/login.html'), name='account_login'),
re_path('^logout/', auth_views.LogoutView.as_view(next_page=settings.LOGIN_REDIRECT_URL), name='account_logout'),
]

if settings.SHIBBOLETH:
if settings.SHIBBOLETH_LOGIN_URL:
urlpatterns += [
re_path('^shibboleth/login/', shibboleth_login, name='shibboleth_login'),
re_path('^shibboleth/logout/', shibboleth_logout, name='shibboleth_logout'),
]
else:
urlpatterns += [
re_path('^logout/', auth_views.LogoutView.as_view(next_page=settings.SHIBBOLETH_LOGOUT_URL), name='account_logout'),
]

if settings.ACCOUNT_ALLOW_USER_TOKEN:
urlpatterns += [
re_path(r'^token/$', token, name='account_token')
Expand Down
25 changes: 22 additions & 3 deletions rdmo/accounts/views.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import logging
import re

from django.conf import settings
from django.contrib.auth import logout
from django.contrib.auth.decorators import login_required
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.urls import reverse
from rest_framework.authtoken.models import Token

from rdmo.core.utils import get_next, get_referer_path_info
Expand Down Expand Up @@ -46,7 +48,7 @@ def remove_user(request):
return render(request, 'profile/profile_remove_closed.html')
form = RemoveForm(request.POST or None, request=request)
log.debug('Remove user form initialized for "%s"' % request.user.username)

if request.method == 'POST':
if 'cancel' in request.POST:
log.info('User %s removal cancelled', str(request.user))
Expand All @@ -58,8 +60,8 @@ def remove_user(request):

if form.is_valid():
user_is_deleted = delete_user(user=request.user,
email=request.POST['email'],
password=request.POST.get('password', None))
email=request.POST['email'],
password=request.POST.get('password', None))

if user_is_deleted:
logout(request)
Expand All @@ -77,6 +79,7 @@ def remove_user(request):
def terms_of_use(request):
return render(request, 'account/terms_of_use.html')


@login_required()
def token(request):
if request.method == 'POST':
Expand All @@ -89,3 +92,19 @@ def token(request):
return render(request, 'account/account_token.html', {
'token': token
})


def shibboleth_login(request):
if request.user.is_authenticated:
return HttpResponseRedirect(reverse('projects'))
else:
login_url = settings.SHIBBOLETH_LOGIN_URL + f'?target={request.path}'
return HttpResponseRedirect(login_url)


def shibboleth_logout(request):
logout_url = reverse('account_logout')
if settings.SHIBBOLETH_USERNAME_PATTERN is None \
or re.search(settings.SHIBBOLETH_USERNAME_PATTERN, request.user.username):
logout_url += f'?next={settings.SHIBBOLETH_LOGOUT_URL}'
return HttpResponseRedirect(logout_url)
3 changes: 2 additions & 1 deletion rdmo/core/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging

from django.conf import settings
from django.db import models
from django.utils.timezone import now
from django.utils.translation import get_language
Expand Down Expand Up @@ -38,7 +39,7 @@ def trans(self, field):
r = getattr(self, '%s_%s' % (field, lang_field)) or None
if r is not None:
return r
else:
elif settings.REPLACE_MISSING_TRANSLATION:
for i in range(1, 6):
r = getattr(self, '%s_%s' % (field, 'lang' + str(i))) or None
if r is not None:
Expand Down
10 changes: 9 additions & 1 deletion rdmo/core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@

MULTISITE = False

LOGIN_FORM = True

PROFILE_UPDATE = True
PROFILE_DELETE = True

Expand All @@ -95,7 +97,9 @@
SOCIALACCOUNT = False

SHIBBOLETH = False
SHIBBOLETH_LOGIN_URL = '/Shibboleth.sso/Login'
SHIBBOLETH_LOGOUT_URL = '/Shibboleth.sso/Logout'
SHIBBOLETH_USERNAME_PATTERN = None

ACCOUNT_SIGNUP_FORM_CLASS = 'rdmo.accounts.forms.SignupForm'
ACCOUNT_USER_DISPLAY = 'rdmo.accounts.utils.get_full_name'
Expand Down Expand Up @@ -176,6 +180,7 @@
'SITE_ID',
'LOGIN_URL',
'LOGOUT_URL',
'LOGIN_FORM',
'ACCOUNT',
'ACCOUNT_SIGNUP',
'ACCOUNT_TERMS_OF_USE',
Expand All @@ -184,6 +189,7 @@
'PROFILE_UPDATE',
'PROFILE_DELETE',
'SHIBBOLETH',
'SHIBBOLETH_LOGIN_URL',
'MULTISITE',
'EXPORT_FORMATS',
'PROJECT_ISSUES',
Expand Down Expand Up @@ -274,7 +280,7 @@

PROJECT_IMPORTS_LIST = []

PROJECT_QUESTIONS_AUTOSAVE = False
PROJECT_QUESTIONS_AUTOSAVE = True

PROJECT_QUESTIONS_CYCLE_SETS = False

Expand Down Expand Up @@ -307,6 +313,8 @@

DEFAULT_URI_PREFIX = 'http://example.com/terms'

REPLACE_MISSING_TRANSLATION = False

VENDOR_CDN = True

VENDOR = {
Expand Down
4 changes: 4 additions & 0 deletions rdmo/core/static/core/css/base.scss
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ summary {
cursor: pointer;
}

metadata {
display: none;
}

/* navbar */

.navbar-default {
Expand Down
Loading

0 comments on commit 5f89662

Please sign in to comment.