From d4d5d5084048b733e507d35e70cc704021e7db59 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= <fpeters@entrouvert.com>
Date: Thu, 10 Jan 2019 15:31:11 +0100
Subject: [PATCH] misc: add a page to configure FranceConnect (#...)

---
 MANIFEST.in                                   |   1 +
 hobo/franceconnect/__init__.py                |   0
 hobo/franceconnect/forms.py                   |  39 ++++++
 .../templates/hobo/franceconnect_disable.html |  20 +++
 .../templates/hobo/franceconnect_enable.html  |  20 +++
 .../templates/hobo/franceconnect_home.html    |  47 +++++++
 hobo/franceconnect/urls.py                    |  25 ++++
 hobo/franceconnect/views.py                   | 120 ++++++++++++++++++
 hobo/settings.py                              |   1 +
 hobo/templates/hobo/home.html                 |   1 +
 hobo/urls.py                                  |   3 +
 11 files changed, 277 insertions(+)
 create mode 100644 hobo/franceconnect/__init__.py
 create mode 100644 hobo/franceconnect/forms.py
 create mode 100644 hobo/franceconnect/templates/hobo/franceconnect_disable.html
 create mode 100644 hobo/franceconnect/templates/hobo/franceconnect_enable.html
 create mode 100644 hobo/franceconnect/templates/hobo/franceconnect_home.html
 create mode 100644 hobo/franceconnect/urls.py
 create mode 100644 hobo/franceconnect/views.py

diff --git a/MANIFEST.in b/MANIFEST.in
index 6227631..eaf06d2 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,5 +1,6 @@
 recursive-include hobo/static *.css *.png *.js
 recursive-include hobo/templates *.html *.txt
+recursive-include hobo/franceconnect/templates *.html *.txt
 recursive-include hobo/profile/templates *.html *.txt
 recursive-include hobo/theme/templates *.html *.txt
 recursive-include hobo/environment/templates *.html *.txt
diff --git a/hobo/franceconnect/__init__.py b/hobo/franceconnect/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/hobo/franceconnect/forms.py b/hobo/franceconnect/forms.py
new file mode 100644
index 0000000..b5d7aa4
--- /dev/null
+++ b/hobo/franceconnect/forms.py
@@ -0,0 +1,39 @@
+# hobo - portal to configure and deploy applications
+# Copyright (C) 2015-2019  Entr'ouvert
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from django import forms
+from django.utils.translation import ugettext_lazy as _
+
+
+class SettingsForm(forms.Form):
+    platform = forms.ChoiceField(
+            label=_('Platform'),
+            choices=[
+                ('prod', _('Production')),
+                ('test', _('Integration')),
+            ])
+    client_id = forms.CharField(
+            label=_('Client ID'),
+            help_text=_('See <a href="https://franceconnect.gouv.fr/fournisseur-service">'
+                        'FranceConnect partners site</a> for getting client ID and secret.'),
+            widget=forms.TextInput(attrs={'size': 64}))
+    client_secret = forms.CharField(
+            label=_('Client Secret'),
+            widget=forms.TextInput(attrs={'size': 64}))
+
+
+class EnableForm(forms.Form):
+    pass
diff --git a/hobo/franceconnect/templates/hobo/franceconnect_disable.html b/hobo/franceconnect/templates/hobo/franceconnect_disable.html
new file mode 100644
index 0000000..eea69e3
--- /dev/null
+++ b/hobo/franceconnect/templates/hobo/franceconnect_disable.html
@@ -0,0 +1,20 @@
+{% extends "hobo/franceconnect_home.html" %}
+{% load i18n %}
+
+{% block appbar %}
+  <h2>FranceConnect</h2>
+{% endblock %}
+
+{% block content %}
+<form method="post">
+{% csrf_token %}
+<p>
+{% trans "Are you sure you want to disable FranceConnect support?" %}
+{{ form.as_p }}
+<div class="buttons">
+<button class="submit-button">{% trans "Disable" %}</button>
+<a class="cancel" href="{% url 'franceconnect-home' %}">{% trans "Cancel" %}</a>
+</div>
+</form>
+
+{% endblock %}
diff --git a/hobo/franceconnect/templates/hobo/franceconnect_enable.html b/hobo/franceconnect/templates/hobo/franceconnect_enable.html
new file mode 100644
index 0000000..b59f4ff
--- /dev/null
+++ b/hobo/franceconnect/templates/hobo/franceconnect_enable.html
@@ -0,0 +1,20 @@
+{% extends "hobo/franceconnect_home.html" %}
+{% load i18n %}
+
+{% block appbar %}
+  <h2>FranceConnect</h2>
+{% endblock %}
+
+{% block content %}
+<form method="post">
+{% csrf_token %}
+<p>
+{% trans "Are you sure you want to enable FranceConnect support?" %}
+{{ form.as_p }}
+<div class="buttons">
+<button class="submit-button">{% trans "Enable" %}</button>
+<a class="cancel" href="{% url 'franceconnect-home' %}">{% trans "Cancel" %}</a>
+</div>
+</form>
+
+{% endblock %}
diff --git a/hobo/franceconnect/templates/hobo/franceconnect_home.html b/hobo/franceconnect/templates/hobo/franceconnect_home.html
new file mode 100644
index 0000000..cead5e9
--- /dev/null
+++ b/hobo/franceconnect/templates/hobo/franceconnect_home.html
@@ -0,0 +1,47 @@
+{% extends "hobo/base.html" %}
+{% load i18n %}
+
+{% block breadcrumb %}
+{{ block.super }}
+<a href="{% url 'franceconnect-home' %}">FranceConnect</a>
+{% endblock %}
+
+{% block appbar %}
+  <h2>{% trans 'FranceConnect' %}</h2>
+  {% if enabled %}
+  <span class="actions">
+  <a rel="popup" href="{% url 'franceconnect-disable' %}">{% trans 'Disable' %}</a>
+  </span>
+  {% endif %}
+{% endblock %}
+
+{% block content %}
+
+<div class="infonotice">
+  {% blocktrans %}
+  FranceConnect is the solution proposed by the French state to streamline
+  logging in online services.
+ {% endblocktrans %}
+</div>
+
+{% if not enabled %}
+<p>
+ {% trans "Support is currently disabled." %}
+</p>
+<p>
+ <a class="button" rel="popup" href="{% url 'franceconnect-enable' %}">Enable</a>
+</p>
+{% else %}
+
+<form method="post">
+{% csrf_token %}
+{{ form.as_p }}
+
+<div class="buttons">
+<button class="submit-button">{% trans "Submit" %}</button>
+</div>
+</form>
+
+{% endif %}
+
+{% endblock %}
diff --git a/hobo/franceconnect/urls.py b/hobo/franceconnect/urls.py
new file mode 100644
index 0000000..b0bc813
--- /dev/null
+++ b/hobo/franceconnect/urls.py
@@ -0,0 +1,25 @@
+# hobo - portal to configure and deploy applications
+# Copyright (C) 2015-2019  Entr'ouvert
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from django.conf.urls import url
+
+from . import views
+
+urlpatterns = [
+    url(r'^$', views.home, name='franceconnect-home'),
+    url(r'^enable$', views.enable, name='franceconnect-enable'),
+    url(r'^disable$', views.disable, name='franceconnect-disable'),
+]
diff --git a/hobo/franceconnect/views.py b/hobo/franceconnect/views.py
new file mode 100644
index 0000000..216e112
--- /dev/null
+++ b/hobo/franceconnect/views.py
@@ -0,0 +1,120 @@
+# hobo - portal to configure and deploy applications
+# Copyright (C) 2015-2019  Entr'ouvert
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from django.core.urlresolvers import reverse_lazy
+from django.views.generic import RedirectView, FormView
+
+from hobo.environment.models import Variable, Authentic
+from .forms import SettingsForm, EnableForm
+
+
+def get_variable(name):
+    variable, created = Variable.objects.get_or_create(
+            name='SETTING_' + name,
+            defaults={
+                'auto': True,
+                'service': Authentic.objects.get(secondary=False),
+            })
+    return variable
+
+
+class HomeView(FormView):
+    template_name = 'hobo/franceconnect_home.html'
+    form_class = SettingsForm
+    success_url = reverse_lazy('franceconnect-home')
+
+    def get_initial(self):
+        initial = super(HomeView, self).get_initial()
+        authorize_url = get_variable('A2_FC_AUTHORIZE_URL').value
+        if authorize_url == 'https://fcp.integ01.dev-franceconnect.fr/api/v1/authorize':
+            initial['platform'] = 'test'
+        elif authorize_url == 'https://app.franceconnect.gouv.fr/api/v1/authorize':
+            initial['platform'] = 'prod'
+
+        initial['client_id'] = get_variable('A2_FC_CLIENT_ID').value
+        initial['client_secret'] = get_variable('A2_FC_CLIENT_SECRET').value
+
+        return initial
+
+    def form_valid(self, form):
+        platforms = {
+            'test': {
+                'A2_FC_AUTHORIZE_URL': 'https://fcp.integ01.dev-franceconnect.fr/api/v1/authorize',
+                'A2_FC_TOKEN_URL': 'https://fcp.integ01.dev-franceconnect.fr/api/v1/token',
+                'A2_FC_USERINFO_URL': 'https://fcp.integ01.dev-franceconnect.fr/api/v1/userinfo',
+                'A2_FC_LOGOUT_URL': 'https://fcp.integ01.dev-franceconnect.fr/api/v1/logout',
+            },
+            'prod': {
+                'A2_FC_AUTHORIZE_URL': 'https://app.franceconnect.gouv.fr/api/v1/authorize',
+                'A2_FC_TOKEN_URL': 'https://app.franceconnect.gouv.fr/api/v1/token',
+                'A2_FC_USERINFO_URL': 'https://app.franceconnect.gouv.fr/api/v1/userinfo',
+                'A2_FC_LOGOUT_URL': 'https://app.franceconnect.gouv.fr/api/v1/logout',
+            }
+        }
+
+        for key, value in platforms[form.cleaned_data['platform']].items():
+            variable = get_variable(key)
+            variable.value = value
+            variable.save()
+
+        variable = get_variable('A2_FC_CLIENT_ID')
+        variable.value = form.cleaned_data['client_id']
+        variable.save()
+
+        variable = get_variable('A2_FC_CLIENT_SECRET')
+        variable.value = form.cleaned_data['client_secret']
+        variable.save()
+
+        variable = get_variable('A2_FC_VERIFY_CERTIFICATE')
+        variable.value = 'true'
+        variable.save()
+
+        return super(HomeView, self).form_valid(form)
+
+    def get_context_data(self, **kwargs):
+        context = super(HomeView, self).get_context_data(**kwargs)
+        context['enabled'] = bool(get_variable('A2_FC_ENABLE').json)
+        return context
+
+home = HomeView.as_view()
+
+
+class EnableView(FormView):
+    form_class = EnableForm
+    template_name = 'hobo/franceconnect_enable.html'
+    success_url = reverse_lazy('franceconnect-home')
+
+    def form_valid(self, form):
+        variable = get_variable('A2_FC_ENABLE')
+        variable.value = 'true'
+        variable.save()
+        return super(EnableView, self).form_valid(form)
+
+enable = EnableView.as_view()
+
+
+class DisableView(FormView):
+    form_class = EnableForm
+    template_name = 'hobo/franceconnect_disable.html'
+    success_url = reverse_lazy('franceconnect-home')
+
+    def form_valid(self, form):
+        variable = get_variable('A2_FC_ENABLE')
+        variable.value = 'false'
+        variable.save()
+        return super(DisableView, self).form_valid(form)
+
+disable = DisableView.as_view()
diff --git a/hobo/settings.py b/hobo/settings.py
index 071985e..0086f68 100644
--- a/hobo/settings.py
+++ b/hobo/settings.py
@@ -40,6 +40,7 @@ INSTALLED_APPS = (
     'mellon',
     'gadjo',
     'hobo.environment',
+    'hobo.franceconnect',
     'hobo.profile',
     'hobo.theme',
     'hobo.emails',
diff --git a/hobo/templates/hobo/home.html b/hobo/templates/hobo/home.html
index 8a9b5d3..3e957f8 100644
--- a/hobo/templates/hobo/home.html
+++ b/hobo/templates/hobo/home.html
@@ -9,6 +9,7 @@
     <li><a href="{% url 'profile-home' %}">{% trans 'User Profile' %}</a></li>
     <li><a href="{% url 'theme-home' %}">{% trans 'Theme' %}</a></li>
     <li><a href="{% url 'emails-home' %}">{% trans 'Emails' %}</a></li>
+    <li><a href="{% url 'franceconnect-home' %}">FranceConnect</a></li>
     <li><a href="{% url 'environment-home' %}">{% trans 'Services' %}</a></li>
     <li><a href="{% url 'environment-variables' %}">{% trans 'Variables' %}</a></li>
   </ul>
diff --git a/hobo/urls.py b/hobo/urls.py
index 08b9d42..d0f0d52 100644
--- a/hobo/urls.py
+++ b/hobo/urls.py
@@ -7,6 +7,7 @@ admin.autodiscover()
 from .views import admin_required, login, login_local, logout, home, health_json, menu_json, hobo
 from .urls_utils import decorated_includes
 from .environment.urls import urlpatterns as environment_urls
+from .franceconnect.urls import urlpatterns as franceconnect_urls
 from .profile.urls import urlpatterns as profile_urls
 from .theme.urls import urlpatterns as theme_urls
 from .emails.urls import urlpatterns as emails_urls
@@ -17,6 +18,8 @@ urlpatterns = [
                                        include(environment_urls))),
     url(r'^profile/', decorated_includes(admin_required,
                                              include(profile_urls))),
+    url(r'^franceconnect/',
+        decorated_includes(admin_required, include(franceconnect_urls))),
     url(r'^theme/', decorated_includes(admin_required,
                                              include(theme_urls))),
     url(r'^emails/', decorated_includes(admin_required, include(emails_urls))),
-- 
2.20.1

