Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions admin/base/settings/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
'admin.meetings',
'admin.institutions',
'admin.preprint_providers',
'admin.loa',

# Additional addons
'addons.bitbucket',
Expand Down
1 change: 1 addition & 0 deletions admin/base/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
include('admin.user_identification_information_admin.urls', namespace='user_identification_information_admin')),
url(r'^project_limit_number/', include('admin.project_limit_number.urls', namespace='project_limit_number')),
url(r'^rdm_workflow/', include('admin.rdm_workflow.urls', namespace='rdm_workflow')),
url(r'^loa/', include('admin.loa.urls', namespace='loa')),
]),
),
]
Expand Down
Empty file added admin/loa/__init__.py
Empty file.
39 changes: 39 additions & 0 deletions admin/loa/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from django import forms
from osf.models import LoA
from django.utils.translation import ugettext_lazy as _


class LoAForm(forms.ModelForm):
CHOICES_AAL = [(0, _('NULL')), (1, _('AAL1')), (2, _('AAL2'))]
CHOICES_IAL = [(0, _('NULL')), (1, _('IAL1')), (2, _('IAL2'))]
CHOICES_MFA = (
(False, _('Hide')),
(True, _('Show')),
)
aal = forms.ChoiceField(
choices=CHOICES_AAL,
required=False,
)
ial = forms.ChoiceField(
choices=CHOICES_IAL,
required=False,
)
is_mfa = forms.ChoiceField(
label=_('Display MFA link button'),
choices=CHOICES_MFA,
initial=False,
required=False,
)

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.fields.values():
field.widget.attrs['class'] = 'form-control form-control-sm'

class Meta:
model = LoA
fields = (
'aal',
'ial',
'is_mfa',
)
9 changes: 9 additions & 0 deletions admin/loa/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from django.conf.urls import url
from . import views

app_name = 'admin'

urlpatterns = [
url(r'^$', views.ListLoA.as_view(), name='list'),
url(r'^bulk_add/$', views.BulkAddLoA.as_view(), name='bulk_add'),
]
132 changes: 132 additions & 0 deletions admin/loa/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
from __future__ import unicode_literals
from urllib.parse import urlencode
from django.core.exceptions import PermissionDenied
from django.shortcuts import redirect
from django.urls import reverse
from django.views.generic import View, TemplateView
from django.contrib import messages
from django.utils.translation import ugettext_lazy as _
from admin.rdm.utils import RdmPermissionMixin
from admin.loa.forms import LoAForm
from osf.models import Institution, LoA
from django.contrib.auth.mixins import UserPassesTestMixin
from django.http import Http404
from admin.base.utils import render_bad_request_response
import logging

logger = logging.getLogger(__name__)


class ListLoA(RdmPermissionMixin, UserPassesTestMixin, TemplateView):
template_name = 'loa/list.html'
raise_exception = True
institution_id = None
model = LoA

form_class = LoAForm

def dispatch(self, request, *args, **kwargs):

# login check
if not self.is_authenticated:
return self.handle_no_permission()
try:
self.institution_id = self.request.GET.get('institution_id')
if self.institution_id:
self.institution_id = int(self.institution_id)
return super(ListLoA, self).dispatch(request, *args, **kwargs)
except ValueError:
return render_bad_request_response(request=request, error_msgs='institution_id must be a integer')

def test_func(self):
"""check user permissions"""
if not self.institution_id:
# superuser or admin has an institution
return self.is_super_admin or self.is_institutional_admin
else:
# institution not exist
if not Institution.objects.filter(id=self.institution_id).exists():
raise Http404(
'Institution with id "{}" not found.'.format(
self.institution_id
))
# superuser or institutional admin has permission
return self.is_super_admin or \
(self.is_admin and self.is_affiliated_institution(self.institution_id))

def get_context_data(self, **kwargs):
user = self.request.user
# superuser
if self.is_super_admin:
institutions = Institution.objects.all().order_by('name')
# institution administrator
elif self.is_admin and user.affiliated_institutions.first():
institutions = Institution.objects.filter(pk__in=user.affiliated_institutions.all()).order_by('name')
else:
raise PermissionDenied('Not authorized to view the LoA.')

selected_id = institutions.first().id

institution_id = int(self.kwargs.get('institution_id', self.request.GET.get('institution_id', selected_id)))

formset_loa = LoAForm(instance=LoA.objects.get_or_none(institution_id=institution_id))
logger.info(formset_loa)
kwargs.setdefault('institutions', institutions)
kwargs.setdefault('institution_id', institution_id)
kwargs.setdefault('selected_id', institution_id)
kwargs.setdefault('formset_loa', formset_loa)

return super(ListLoA, self).get_context_data(**kwargs)


class BulkAddLoA(RdmPermissionMixin, UserPassesTestMixin, View):
raise_exception = True
institution_id = None

def dispatch(self, request, *args, **kwargs):
"""Initialize attributes shared by all view methods."""
# login check
if not self.is_authenticated:
return self.handle_no_permission()
try:
self.institution_id = self.request.POST.get('institution_id')
if self.institution_id:
self.institution_id = int(self.institution_id)
else:
return render_bad_request_response(request=request, error_msgs='institution_id is required')
return super(BulkAddLoA, self).dispatch(request, *args, **kwargs)
except ValueError:
return render_bad_request_response(request=request, error_msgs='institution_id must be a integer')

def test_func(self):
"""check user permissions"""
# institution not exist
if not Institution.objects.filter(id=self.institution_id, is_deleted=False).exists():
raise Http404(
'Institution with id "{}" not found.'.format(
self.institution_id
))
# superuser or institutional admin has permission
return self.is_super_admin or \
(self.is_admin and self.is_affiliated_institution(self.institution_id))

def post(self, request):
institution_id = request.POST.get('institution_id')
aal = request.POST.get('aal')
ial = request.POST.get('ial')
is_mfa = request.POST.get('is_mfa')
existing_set = LoA.objects.get_or_none(institution_id=institution_id)
if not existing_set:
LoA.objects.create(institution_id=institution_id, aal=aal, ial=ial, is_mfa=is_mfa, modifier=request.user)
else:
existing_set.aal = aal
existing_set.ial = ial
existing_set.is_mfa = is_mfa
existing_set.modifier = request.user
existing_set.save()

base_url = reverse('loa:list')
query_string = urlencode({'institution_id': institution_id})
ctx = _('LoA update successful.')
messages.success(self.request, ctx)
return redirect('{}?{}'.format(base_url, query_string))
5 changes: 5 additions & 0 deletions admin/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,11 @@
<i class='fa fa-link'></i><span> {% trans "Login Availability Control" %}</span>
</a>
</li>
<li>
<a href="{% url 'loa:list' %}">
<i class='fa fa-link'></i><span> {% trans "Level of Assurance" %}</span>
</a>
</li>
{% endif %}
{% if perms.osf.view_node %}
<li><a role="button" data-toggle="collapse" href="#collapseNodes">
Expand Down
97 changes: 97 additions & 0 deletions admin/templates/loa/list.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
{% extends "base.html" %}
{% load i18n %}
{% load render_bundle from webpack_loader %}
{% load spam_extras %}
{% load static %}

{% block top_includes %}
<link rel="stylesheet" type="text/css" href="/static/css/institutions.css"/>
<style type="text/css">
.flex-container {
display: flex;
}

.flex-container .flex-item {
flex: 1;
}

.max-w-600 {
max-width: 600px;
}

.w-label {
min-width: 160px;
max-width: 200px;
}

@media (min-width: 768px) {
.xs-flex-container {
display: flex;
}

.xs-flex-container .xs-flex-item {
flex: 1;
}
}
</style>
{% endblock %}

{% block title %}
<title>{% trans "Level of Assurance" %}</title>
{% endblock title %}

{% block content %}
<h2>{% trans "Level of Assurance" %}</h2>

<div class="row">
{% if messages %}
{% for message in messages %}
<div class="col-md-12 alert alert-dismissible{% if message.tags %} {{ message.tags }}{% endif %}">
{{ message }}
</div>
{% endfor %}
{% endif %}
<div id="error_message" class="col-md-12">
{% if request.session.message %}
<div class="alert alert-danger">
{{ request.session.message }}
</div>
{% endif %}
</div>
</div>
<form action="{% url "loa:bulk_add" %}" method="post" class="form-horizontal"
id="entitlements_form">
{% csrf_token %}
<div class="form-group xs-flex-container">
<label for="institution_id" class="col-sm-2 control-label xs-flex-item w-label">{% trans 'Institutions' %}</label>
<div class="col-sm-9 max-w-600">
<select id="institution_id" name="institution_id" class="form-control">
{% for institution in institutions %}
<option value="{{ institution.id }}" {% if selected_id == institution.id %}selected="selected"{% endif %}>{{ institution.name }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="form-group xs-flex-container">
<label for="institution_id" class="col-sm-2 control-label xs-flex-item w-label">{% trans 'Level of Assurance' %}</label>
<div class="col-sm-9 max-w-600">
{{ formset_loa }}
</div>
</div>
<div class="form-group xs-flex-container">
<div class="col-sm-2 control-label xs-flex-item w-label"></div>
<div class="col-sm-9 max-w-600">
<button type="submit" class="btn btn-primary" style="width: 90px;">{% trans 'Save' %}</button>
</div>
</div>
</form>

{% endblock content %}

{% block bottom_js %}
<script type="application/javascript">
document.getElementById("institution_id").addEventListener("change", function change_institution() {
window.location = window.location.origin + "{% url 'loa:list' %}" + '?institution_id=' + document.getElementById("institution_id").value;
});
</script>
{% endblock %}
15 changes: 15 additions & 0 deletions admin/translations/django.pot
Original file line number Diff line number Diff line change
Expand Up @@ -3947,3 +3947,18 @@ msgstr ""

msgid "No@encrypt_uploads"
msgstr ""

msgid "LoA update successful."
msgstr ""

msgid "Show"
msgstr ""

msgid "Hide"
msgstr ""

msgid "Level of Assurance"
msgstr ""

msgid "Display MFA link button"
msgstr ""
15 changes: 15 additions & 0 deletions admin/translations/en/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -3999,3 +3999,18 @@ msgstr "Yes"

msgid "No@encrypt_uploads"
msgstr "No"

msgid "LoA update successful."
msgstr ""

msgid "Show"
msgstr ""

msgid "Hide"
msgstr ""

msgid "Level of Assurance"
msgstr ""

msgid "Display MFA link button"
msgstr ""
17 changes: 16 additions & 1 deletion admin/translations/ja/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -4390,4 +4390,19 @@ msgstr "関連するすべてのワークフローテンプレートとワーク

#: admin/templates/rdm_workflow/engine_list.html:196
msgid "If there are running workflow processes, deletion will fail."
msgstr "実行中のワークフロープロセスがある場合、エラーになります。"
msgstr "実行中のワークフロープロセスがある場合、エラーになります。"

msgid "LoA update successful."
msgstr "LoAの更新に成功しました。"

msgid "Show"
msgstr "表示する"

msgid "Hide"
msgstr "表示しない"

msgid "Level of Assurance"
msgstr "要求する保証レベルの設定"

msgid "Display MFA link button"
msgstr "多要素認証実行ボタンの表示"
Empty file added admin_tests/loa/__init__.py
Empty file.
Loading
Loading