Skip to content

Commit

Permalink
Merge remote-tracking branch 'helenst/dev/shibboleth' into qa/1.x
Browse files Browse the repository at this point in the history
  • Loading branch information
sevein committed Aug 3, 2017
2 parents 9198655 + 8b21945 commit 473344c
Show file tree
Hide file tree
Showing 23 changed files with 440 additions and 61 deletions.
10 changes: 10 additions & 0 deletions src/dashboard/src/components/accounts/backends.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from shibboleth.backends import ShibbolethRemoteUserBackend
from tastypie.models import ApiKey


class CustomShibbolethRemoteUserBackend(ShibbolethRemoteUserBackend):
def configure_user(self, user):
api_key = ApiKey.objects.create(user=user)
api_key.key = api_key.generate_key()
api_key.save()
return user
7 changes: 7 additions & 0 deletions src/dashboard/src/components/accounts/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,10 @@ def save(self, commit=True):
if commit:
user.save()
return user


class ApiKeyForm(forms.Form):
regenerate_api_key = forms.CharField(
widget=forms.CheckboxInput,
label='Regenerate API key (shown below)?'
)
5 changes: 3 additions & 2 deletions src/dashboard/src/components/accounts/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@
url(r'add/$', views.add),
url(r'(?P<id>\d+)/delete/$', views.delete),
url(r'(?P<id>\d+)/edit/$', views.edit),
url(r'profile/$', views.edit),
url(r'list/$', views.list)
url(r'profile/$', views.profile, name='profile'),
url(r'list/$', views.list),
url(r'logged-out', views.logged_out, name='logged-out'),
]

urlpatterns += [
Expand Down
62 changes: 39 additions & 23 deletions src/dashboard/src/components/accounts/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,23 @@
# You should have received a copy of the GNU General Public License
# along with Archivematica. If not, see <http://www.gnu.org/licenses/>.

from django.conf import settings
from django.contrib.auth.decorators import user_passes_test
from django.contrib.auth.models import User
from django.contrib import messages
from django.core.urlresolvers import reverse
from django.http import Http404
from django.shortcuts import redirect, render
from django.shortcuts import get_object_or_404, redirect, render
from django.template import RequestContext
from django.utils.translation import ugettext as _

from tastypie.models import ApiKey

from components.accounts.forms import UserCreationForm
from components.accounts.forms import UserChangeForm
from components.accounts.forms import ApiKeyForm
import components.decorators as decorators
from components.helpers import generate_api_key


@user_passes_test(lambda u: u.is_superuser, login_url='/forbidden/')
Expand Down Expand Up @@ -61,6 +64,30 @@ def add(request):
})


def profile(request):
# If users are editable in this setup, go to the editable profile view
if settings.ALLOW_USER_EDITS:
return edit(request)

user = request.user
title = _('Your profile (%s)') % user

if request.method == 'POST':
form = ApiKeyForm(request.POST)
if form.is_valid():
if form['regenerate_api_key'] != '':
generate_api_key(user)

return redirect('profile')
else:
form = ApiKeyForm()

return render(request, 'accounts/profile.html', {
'form': form,
'title': title
})


def edit(request, id=None):
# Forbidden if user isn't an admin and is trying to edit another user
if str(request.user.id) != str(id) and id is not None:
Expand All @@ -72,11 +99,8 @@ def edit(request, id=None):
user = request.user
title = 'Edit your profile (%s)' % user
else:
try:
user = User.objects.get(pk=id)
title = 'Edit user %s' % user
except:
raise Http404
user = get_object_or_404(User, pk=id)
title = 'Edit user %s' % user

# Form
if request.method == 'POST':
Expand All @@ -98,18 +122,13 @@ def edit(request, id=None):
# regenerate API key if requested
regenerate_api_key = request.POST.get('regenerate_api_key', '')
if regenerate_api_key != '':
try:
api_key = ApiKey.objects.get(user_id=user.pk)
except ApiKey.DoesNotExist:
api_key = ApiKey.objects.create(user=user)
api_key.key = api_key.generate_key()
api_key.save()
generate_api_key(user)

# determine where to redirect to
if request.user.is_superuser is False:
return_view = 'components.accounts.views.edit'
else:
if request.user.is_superuser:
return_view = 'components.accounts.views.list'
else:
return_view = 'profile'

messages.info(request, _('Saved.'))
return redirect(return_view)
Expand All @@ -119,17 +138,9 @@ def edit(request, id=None):
suppress_administrator_toggle = False
form = UserChangeForm(instance=user, suppress_administrator_toggle=suppress_administrator_toggle)

# load API key for display
try:
api_key_data = ApiKey.objects.get(user_id=user.pk)
api_key = api_key_data.key
except:
api_key = '<no API key generated>'

return render(request, 'accounts/edit.html', {
'form': form,
'user': user,
'api_key': api_key,
'title': title
})

Expand Down Expand Up @@ -160,3 +171,8 @@ def delete(request, id):
return redirect('components.accounts.views.list')
except:
raise Http404


def logged_out(request):
# Display a post-logout message
return render(request, 'accounts/logged_out.html')
13 changes: 13 additions & 0 deletions src/dashboard/src/components/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from django.shortcuts import render
from django.utils.translation import ugettext as _
from main import models
from tastypie.models import ApiKey

logger = logging.getLogger('archivematica.dashboard')

Expand Down Expand Up @@ -359,3 +360,15 @@ def completed_units_efficient(unit_type='transfer', include_failed=True):
'failed' in ms_group.lower()))):
completed.add(uuid)
return list(completed)


def generate_api_key(user):
"""
Generate API key for a user
"""
try:
api_key = ApiKey.objects.get(user_id=user.pk)
except ApiKey.DoesNotExist:
api_key = ApiKey.objects.create(user=user)
api_key.key = api_key.generate_key()
api_key.save()
14 changes: 11 additions & 3 deletions src/dashboard/src/installer/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
from django.forms.widgets import TextInput
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy as _l


class SuperUserCreationForm(UserCreationForm):
email = forms.EmailField(required=True)
org_name = forms.CharField(label=_('Organization name'), help_text=_('PREMIS agent name'), required=False, widget=TextInput(attrs=settings.INPUT_ATTRS))
org_identifier = forms.CharField(label=_('Organization identifier'), help_text=_('PREMIS agent identifier'), required=False, widget=TextInput(attrs=settings.INPUT_ATTRS))
org_name = forms.CharField(label=_l('Organization name'), help_text=_l('PREMIS agent name'), required=False, widget=TextInput(attrs=settings.INPUT_ATTRS))
org_identifier = forms.CharField(label=_l('Organization identifier'), help_text=_l('PREMIS agent identifier'), required=False, widget=TextInput(attrs=settings.INPUT_ATTRS))

class Meta:
model = User
Expand All @@ -44,5 +44,13 @@ def save(self, commit=True):
return user


class OrganizationForm(forms.Form):
"""
Simplified version of the superuser form - simply ask for organisation info
"""
org_name = forms.CharField(label=_l('Organization name'), help_text=_l('PREMIS agent name'), required=False, widget=TextInput(attrs=settings.INPUT_ATTRS))
org_identifier = forms.CharField(label=_l('Organization identifier'), help_text=_l('PREMIS agent identifier'), required=False, widget=TextInput(attrs=settings.INPUT_ATTRS))


class FPRConnectForm(forms.Form):
comments = forms.CharField(required=False, widget=TextInput(attrs=settings.INPUT_ATTRS))
17 changes: 10 additions & 7 deletions src/dashboard/src/installer/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
# along with Archivematica. If not, see <http://www.gnu.org/licenses/>.

from django.conf import settings
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from django.shortcuts import redirect

import components.helpers as helpers
from re import compile as re_compile


Expand All @@ -30,11 +30,14 @@

class ConfigurationCheckMiddleware:
def process_request(self, request):
if User.objects.count() == 0:
# The presence of the UUID is an indicator of whether we've already set up.
dashboard_uuid = helpers.get_setting('dashboard_uuid')
if not dashboard_uuid:
# Start off the installer
if reverse('installer.views.welcome') != request.path_info:
return redirect('installer.views.welcome')
else:
if not request.user.is_authenticated():
path = request.path_info.lstrip('/')
if not any(m.match(path) for m in EXEMPT_URLS):
return redirect(settings.LOGIN_URL)
elif not request.user.is_authenticated():
# Installation already happened - make sure the user is logged in.
path = request.path_info.lstrip('/')
if not any(m.match(path) for m in EXEMPT_URLS):
return redirect(settings.LOGIN_URL)
40 changes: 24 additions & 16 deletions src/dashboard/src/installer/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,35 +24,43 @@

import components.helpers as helpers
from components.administration.forms import StorageSettingsForm
from installer.forms import SuperUserCreationForm
from installer.forms import OrganizationForm, SuperUserCreationForm
from installer.steps import download_fpr_rules, setup_pipeline, setup_pipeline_in_ss, submit_fpr_agent


def welcome(request):
# This form will be only accessible when the database has no users
if 0 < User.objects.count():
# This form will be only accessible when there is no uuid
dashboard_uuid = helpers.get_setting('dashboard_uuid')
if dashboard_uuid:
return redirect('main.views.home')

# Do we need to set up a user?
set_up_user = not User.objects.exists()

if request.method == 'POST':
# save organization PREMIS agent if supplied
setup_pipeline(
org_name=request.POST.get('org_name', ''),
org_identifier=request.POST.get('org_identifier', '')
)

# Save user and set cookie to indicate this is the first login
form = SuperUserCreationForm(request.POST)
if form.is_valid():
user = form.save()
api_key = ApiKey.objects.create(user=user)
api_key.key = api_key.generate_key()
api_key.save()
user = authenticate(username=user.username, password=form.cleaned_data['password1'])
if user is not None:
login(request, user)
request.session['first_login'] = True
return redirect('installer.views.fprconnect')
if set_up_user:
form = SuperUserCreationForm(request.POST)
if form.is_valid():
user = form.save()
api_key = ApiKey.objects.create(user=user)
api_key.key = api_key.generate_key()
api_key.save()
user = authenticate(username=user.username, password=form.cleaned_data['password1'])
if user is not None:
login(request, user)
request.session['first_login'] = True
return redirect('installer.views.fprconnect')
else:
request.session['first_login'] = True
return redirect('installer.views.fprconnect')
else:
form = SuperUserCreationForm()
form = SuperUserCreationForm() if set_up_user else OrganizationForm()

return render(request, 'installer/welcome.html', {
'form': form,
Expand Down
22 changes: 22 additions & 0 deletions src/dashboard/src/main/templatetags/user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from django import template
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from tastypie.models import ApiKey

register = template.Library()


@register.filter
def api_key(user):
try:
return user.api_key.key
except ApiKey.DoesNotExist:
return _('<no API key generated>')


@register.simple_tag(takes_context=True)
def logout_link(context):
if context.get('logout_link'):
return context['logout_link']
else:
return reverse('django.contrib.auth.views.logout_then_login')
8 changes: 7 additions & 1 deletion src/dashboard/src/main/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with Archivematica. If not, see <http://www.gnu.org/licenses/>.

from django.conf.urls import url
from django.conf.urls import include, url
from django.conf import settings

from main import views
Expand Down Expand Up @@ -43,4 +43,10 @@
url(r'status/$', views.status),
url(r'formdata/(?P<type>\w+)/(?P<parent_id>\d+)/(?P<delete_id>\d+)/$', views.formdata_delete),
url(r'formdata/(?P<type>\w+)/(?P<parent_id>\d+)/$', views.formdata),

]

if 'shibboleth' in settings.INSTALLED_APPS:
urlpatterns += [
url(r'^shib/', include('shibboleth.urls', namespace='shibboleth')),
]
26 changes: 26 additions & 0 deletions src/dashboard/src/middleware/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from django.http import HttpResponseServerError
from django.shortcuts import render
from django.template.base import TemplateDoesNotExist
from shibboleth.middleware import ShibbolethRemoteUserMiddleware

import elasticsearch

Expand Down Expand Up @@ -65,3 +66,28 @@ def process_exception(self, request, exception):
return
if isinstance(exception, self.EXCEPTIONS):
return render(request, 'elasticsearch_error.html', {'exception_type': str(type(exception))})


SHIBBOLETH_REMOTE_USER_HEADER = getattr(
settings, 'SHIBBOLETH_REMOTE_USER_HEADER', 'REMOTE_USER'
)


class CustomShibbolethRemoteUserMiddleware(ShibbolethRemoteUserMiddleware):
"""
Custom version of Shibboleth remote user middleware
THe aim of this is to provide a custom header name that is expected
to identify the remote Shibboleth user
"""
header = SHIBBOLETH_REMOTE_USER_HEADER

def make_profile(self, user, shib_meta):
"""
Customize the user based on shib_meta mappings (anything that's not
already covered by the attribute map)
"""
# Make the user an administrator if they are in the designated admin group
entitlements = shib_meta['entitlement'].split(';')
user.is_superuser = settings.SHIBBOLETH_ADMIN_ENTITLEMENT in entitlements
user.save()
5 changes: 5 additions & 0 deletions src/dashboard/src/requirements/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,8 @@ ndg-httpsclient
pyasn1
requests==2.7.0
whitenoise==3.3.0
git+git://github.com/Brown-University-Library/django-shibboleth-remoteuser.git@67d270c65c201606fb86d548493d4b3fd8cc7a76#egg=django-shibboleth-remoteuser

# Support for longer (>30 characters) usernames
# Using a fork of the main package because this one provides Django (rather than South) migrations
git+git://github.com/seatme/django-longer-username.git@seatme#egg=longerusername
1 change: 1 addition & 0 deletions src/dashboard/src/requirements/dev.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
-r test.txt

ipython
ipdb
Loading

0 comments on commit 473344c

Please sign in to comment.