From f4e9bb93b6d6e8b57ca7875a25a5c283f511b0a0 Mon Sep 17 00:00:00 2001
From: Benjamin Dauvergne <bdauvergne@entrouvert.com>
Date: Thu, 13 Nov 2014 16:50:29 +0100
Subject: [PATCH 4/5] Add federation management to profile page (fixes #5925)

---
 authentic2/idp/saml/backend.py                   |   36 ++++++++++++++++++++++
 authentic2/idp/saml/urls.py                      |   15 ++++++++-
 authentic2/idp/saml/views.py                     |   36 ++++++++++++++++++++++
 authentic2/templates/idp/account_management.html |   21 +++++++++++++
 authentic2/views.py                              |    8 +++++
 5 files changed, 115 insertions(+), 1 deletion(-)
 create mode 100644 authentic2/idp/saml/views.py

diff --git a/authentic2/idp/saml/backend.py b/authentic2/idp/saml/backend.py
index 2c58b6e..995ecde 100644
--- a/authentic2/idp/saml/backend.py
+++ b/authentic2/idp/saml/backend.py
@@ -1,6 +1,7 @@
 import logging
 import urllib
 
+from django.utils.translation import ugettext as _
 from django.core.urlresolvers import reverse
 from django.template.loader import render_to_string
 
@@ -8,6 +9,7 @@ import authentic2.saml.models as models
 import authentic2.idp.saml.saml2_endpoints as saml2_endpoints
 import authentic2.saml.common as common
 
+from authentic2.decorators import to_list
 from authentic2.utils import Service
 
 
@@ -92,3 +94,37 @@ class SamlBackend(object):
 
     def can_synchronous_logout(self, django_sessions_keys):
         return True
+
+    @to_list
+    def federation_management(self, request):
+        qs = models.LibertyFederation.objects
+        qs = qs.filter(sp__users_can_manage_federations=True)
+        qs = qs.filter(user=request.user)
+        federations = qs.select_related()
+        next_url = request.get_full_path()
+        for federation in  federations:
+            url = reverse('a2-idp-saml2-federation-delete',
+                    kwargs={'pk': federation.pk})
+            yield {
+                    'name': federation.sp.liberty_provider.name,
+                    'hidden_inputs': {
+                        'next': next_url,
+                    },
+                    'buttons': (('delete', _('Delete')),),
+                    'url': url,
+                  }
+        qs = models.LibertyProvider.objects
+        qs = qs.filter(service_provider__users_can_manage_federations=True)
+        qs = qs.exclude(service_provider__libertyfederation__in=federations)
+        qs = qs.select_related()
+        for liberty_provider in qs:
+            url = reverse('a2-idp-saml2-idp-sso')
+            yield {
+                    'name': liberty_provider.name,
+                    'hidden_inputs': {
+                        'provider_id': liberty_provider.entity_id,
+                        'next': next_url,
+                    },
+                    'buttons': (('create', _('Create')),),
+                    'url': url,
+                  }
diff --git a/authentic2/idp/saml/urls.py b/authentic2/idp/saml/urls.py
index df5a506..2e55fc6 100644
--- a/authentic2/idp/saml/urls.py
+++ b/authentic2/idp/saml/urls.py
@@ -1,5 +1,7 @@
 from django.conf.urls import patterns, url
 
+from . import views
+
 urlpatterns = patterns('authentic2.idp.saml.saml2_endpoints',
     url(r'^metadata$', 'metadata'),
     url(r'^sso$', 'sso'),
@@ -10,5 +12,16 @@ urlpatterns = patterns('authentic2.idp.saml.saml2_endpoints',
     url(r'^slo_return$', 'slo_return'),
     url(r'^finish_slo$', 'finish_slo'),
     url(r'^artifact$', 'artifact'),
-    url(r'^idp_sso/(.*)$', 'idp_sso'),
+    # legacy endpoint, now it's prefered to pass the entity_id in a parameter
+    url(r'^idp_sso/(.+)$',
+        'idp_sso'),
+    url(r'^idp_sso/$',
+        'idp_sso',
+        name='a2-idp-saml2-idp-sso'),
+    url(r'^federations/create/(?P<pk>\d+)/$',
+        views.create_federation,
+        name='a2-idp-saml2-federation-create'),
+    url(r'^federations/(?P<pk>\d+)/delete/$',
+        views.delete_federation,
+        name='a2-idp-saml2-federation-delete'),
 )
diff --git a/authentic2/idp/saml/views.py b/authentic2/idp/saml/views.py
new file mode 100644
index 0000000..c3dec18
--- /dev/null
+++ b/authentic2/idp/saml/views.py
@@ -0,0 +1,36 @@
+from django.utils.translation import ugettext as _
+from django.core.urlresolvers import reverse
+from django.views.generic import DeleteView, View
+from django.http import HttpResponseRedirect
+
+from django.contrib.auth import REDIRECT_FIELD_NAME
+from django.contrib import messages
+
+from authentic2.saml.models import LibertyFederation
+
+class FederationCreateView(View):
+    pass
+
+class FederationDeleteView(DeleteView):
+    model = LibertyFederation
+
+    def get_queryset(self):
+        qs = super(FederationDeleteView, self).get_queryset()
+        return qs.filter(user=self.request.user)
+
+    def delete(self, request, *args, **kwargs):
+        # check current user owns this federation
+        self.object = self.get_object()
+        self.object.user = None
+        self.object.save()
+        messages.info(request, _('Federation to {0} deleted').format(
+            self.object.sp.liberty_provider.name))
+        return HttpResponseRedirect(self.get_success_url())
+
+    def get_success_url(self):
+        return self.request.POST.get(REDIRECT_FIELD_NAME,
+                reverse('auth_homepage'))
+
+
+delete_federation = FederationDeleteView.as_view()
+create_federation = FederationCreateView.as_view()
diff --git a/authentic2/templates/idp/account_management.html b/authentic2/templates/idp/account_management.html
index 6369a32..d600287 100644
--- a/authentic2/templates/idp/account_management.html
+++ b/authentic2/templates/idp/account_management.html
@@ -39,5 +39,26 @@
   {% for html_block in frontends_block %}
     {{ html_block|safe }}
   {% endfor %}
+{% if federation_management %}
+<div id="federation-management">
+  <h3>{% trans "Federation management" %}
+  <ul>
+    {% for federation in federation_management %}
+      <li>
+        <form method="post" action="{{ federation.url }}">
+          {% csrf_token %}
+          <span class="name">{{ federation.name }}</span>
+          {% for key, value in federation.hidden_inputs.iteritems %}
+            <input type="hidden" name="{{ key }}" value="{{ value }}"/>
+          {% endfor %}
+          {% for button_nane, button_label in federation.buttons %}
+            <button name="{{ button_name }}">{{ button_label }}</button>
+          {% endfor %}
+        </form>
+      </li>
+    {% endfor %}
+  </ul>
+</div>
+{% endif %}
 <p><a href="/">{% trans "Back" %}</a></p>
 {% endblock %}
diff --git a/authentic2/views.py b/authentic2/views.py
index 5edd3e9..78620e1 100644
--- a/authentic2/views.py
+++ b/authentic2/views.py
@@ -366,12 +366,20 @@ class ProfileView(TemplateView):
         # Credentials management
         blocks = [ frontend.profile(request) for frontend in frontends \
                 if hasattr(frontend, 'profile') and frontend.enabled() ]
+        idp_backends = utils.get_backends()
+        # Get actions for federation management
+        federation_management = []
+        if app_settings.A2_PROFILE_CAN_MANAGE_FEDERATION:
+            for idp_backend in idp_backends:
+                if hasattr(idp_backend, 'federation_management'):
+                    federation_management.extend(idp_backend.federation_management(request))
         ctx.update({
             'frontends_block': blocks,
             'profile': profile,
             'allow_account_deletion': app_settings.A2_REGISTRATION_CAN_DELETE_ACCOUNT,
             'allow_profile_edit': app_settings.A2_PROFILE_CAN_EDIT_PROFILE,
             'allow_email_change': app_settings.A2_PROFILE_CAN_CHANGE_EMAIL,
+            'federation_management': federation_management,
         })
         return ctx
 
-- 
1.7.10.4

