From 7ea36a5415b636c04df008726070aa042f5f6b67 Mon Sep 17 00:00:00 2001
From: Yohann D'ANELLO <ynerant@crans.org>
Date: Wed, 16 Jun 2021 00:01:35 +0200
Subject: [PATCH] [oauth2] Add view to generate authorization link per
 application with given scopes

Signed-off-by: Yohann D'ANELLO <ynerant@crans.org>
---
 .../templates/permission/scopes.html          | 74 +++++++++++++++++++
 apps/permission/urls.py                       | 11 ++-
 apps/permission/views.py                      | 25 ++++++-
 locale/fr/LC_MESSAGES/django.po               | 49 ++++++------
 .../templates/oauth2_provider/authorize.html  |  5 +-
 5 files changed, 137 insertions(+), 27 deletions(-)
 create mode 100644 apps/permission/templates/permission/scopes.html

diff --git a/apps/permission/templates/permission/scopes.html b/apps/permission/templates/permission/scopes.html
new file mode 100644
index 00000000..26a5feda
--- /dev/null
+++ b/apps/permission/templates/permission/scopes.html
@@ -0,0 +1,74 @@
+{% extends "base.html" %}
+
+{% load i18n %}
+
+{% block content %}
+    <div class="card">
+        <div class="card-header text-center">
+            <h2>{% trans "Available scopes" %}</h2>
+        </div>
+        <div class="card-body">
+            <div class="accordion" id="accordionApps">
+                {% for app, app_scopes in scopes.items %}
+                    <div class="card">
+                        <div class="card-header" id="app-{{ app.name.lower }}-title">
+                            <a class="text-decoration-none collapsed" href="#" data-toggle="collapse"
+                               data-target="#app-{{ app.name.lower }}" aria-expanded="false"
+                               aria-controls="app-{{ app.name.lower }}">
+                                {{ app.name }}
+                            </a>
+                        </div>
+                        <div class="collapse" id="app-{{ app.name.lower }}" aria-labelledby="app-{{ app.name.lower }}" data-target="#accordionApps">
+                            <div class="card-body">
+                                {% for scope_id, scope_desc in app_scopes.items %}
+                                    <div class="form-group">
+                                        <label class="form-check-label" for="scope-{{ app.name.lower }}-{{ scope_id }}">
+                                            <input type="checkbox" id="scope-{{ app.name.lower }}-{{ scope_id }}"
+                                                   name="scope-{{ app.name.lower }}" class="checkboxinput form-check-input" value="{{ scope_id }}">
+                                            {{ scope_desc }}
+                                        </label>
+                                    </div>
+                                {% endfor %}
+                                <p id="url-{{ app.name.lower }}">
+                                    <a href="{% url 'oauth2_provider:authorize' %}?client_id={{ app.client_id }}&response_type=code" target="_blank">
+                                        {{ request.scheme }}://{{ request.get_host }}{% url 'oauth2_provider:authorize' %}?client_id={{ app.client_id }}&response_type=code
+                                    </a>
+                                </p>
+                            </div>
+                        </div>
+                    </div>
+                {% empty %}
+                    <p>
+                        {% trans "No applications defined" %}.
+                        <a href="{% url 'oauth2_provider:register' %}">{% trans "Click here" %}</a> {% trans "if you want to register a new one" %}.
+                    </p>
+                {% endfor %}
+            </div>
+        </div>
+    </div>
+{% endblock %}
+
+{% block extrajavascript %}
+    <script>
+        {% for app in scopes.keys %}
+            let elements = document.getElementsByName("scope-{{ app.name.lower }}");
+            for (let element of elements) {
+                element.onchange = function (event) {
+                    let scope = ""
+                    for (let element of elements) {
+                        if (element.checked) {
+                            scope += element.value + "%20"
+                        }
+                    }
+
+                    scope = scope.substr(0, scope.length - 3)
+
+                    document.getElementById("url-{{ app.name.lower }}").innerHTML = 'Scopes : ' + scope
+                        + '<br><a href="{% url 'oauth2_provider:authorize' %}?client_id={{ app.client_id }}&response_type=code&scope='+ scope
+                        + '" target="_blank">{{ request.scheme }}://{{ request.get_host }}{% url 'oauth2_provider:authorize' %}?client_id={{ app.client_id }}&response_type=code&scope='
+                        + scope + '</a>'
+                }
+            }
+        {% endfor %}
+    </script>
+{% endblock %}
diff --git a/apps/permission/urls.py b/apps/permission/urls.py
index 0894ecf0..43eec1ef 100644
--- a/apps/permission/urls.py
+++ b/apps/permission/urls.py
@@ -1,10 +1,17 @@
 # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
 # SPDX-License-Identifier: GPL-3.0-or-later
 
+from django.conf import settings
 from django.urls import path
-from permission.views import RightsView
+
+from .views import RightsView, ScopesView
 
 app_name = 'permission'
 urlpatterns = [
-    path('rights', RightsView.as_view(), name="rights"),
+    path('rights/', RightsView.as_view(), name="rights"),
 ]
+
+if "oauth2_provider" in settings.INSTALLED_APPS:
+    urlpatterns += [
+        path('scopes/', ScopesView.as_view(), name="scopes"),
+    ]
diff --git a/apps/permission/views.py b/apps/permission/views.py
index 25066731..9bee5295 100644
--- a/apps/permission/views.py
+++ b/apps/permission/views.py
@@ -1,6 +1,6 @@
 # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
 # SPDX-License-Identifier: GPL-3.0-or-later
-
+from collections import OrderedDict
 from datetime import date
 
 from django.contrib.auth.mixins import LoginRequiredMixin
@@ -143,3 +143,26 @@ class RightsView(TemplateView):
                                                    prefix="superusers-")
 
         return context
+
+
+class ScopesView(LoginRequiredMixin, TemplateView):
+    template_name = "permission/scopes.html"
+
+    def get_context_data(self, **kwargs):
+        context = super().get_context_data(**kwargs)
+
+        from oauth2_provider.models import Application
+        from .scopes import PermissionScopes
+
+        scopes = PermissionScopes()
+        context["scopes"] = {}
+        all_scopes = scopes.get_all_scopes()
+        for app in Application.objects.filter(Q(user=self.request.user) | Q(client_type='public')).all():
+            available_scopes = scopes.get_available_scopes(app)
+            context["scopes"][app] = OrderedDict()
+            items = [(k, v) for (k, v) in all_scopes.items() if k in available_scopes]
+            items.sort(key=lambda x: (int(x[0].split("_")[1]), int(x[0].split("_")[0])))
+            for k, v in items:
+                context["scopes"][app][k] = v
+
+        return context
diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po
index f5f26a89..3a6f2d3a 100644
--- a/locale/fr/LC_MESSAGES/django.po
+++ b/locale/fr/LC_MESSAGES/django.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2021-08-29 14:06+0200\n"
+"POT-Creation-Date: 2021-06-15 21:17+0200\n"
 "PO-Revision-Date: 2020-11-16 20:02+0000\n"
 "Last-Translator: Yohann D'ANELLO <ynerant@crans.org>\n"
 "Language-Team: French <http://translate.ynerant.fr/projects/nk20/nk20/fr/>\n"
@@ -956,8 +956,8 @@ msgstr "Verrouiller de force"
 msgid ""
 "Are you sure you want to unlock this note? Transactions will be re-enabled."
 msgstr ""
-"Êtes-vous sûr⋅e de vouloir déverrouiller cette note ? Les transactions seront "
-"à nouveau possible."
+"Êtes-vous sûr⋅e de vouloir déverrouiller cette note ? Les transactions "
+"seront à nouveau possible."
 
 #: apps/member/templates/member/club_alias.html:10
 #: apps/member/templates/member/profile_alias.html:10 apps/member/views.py:253
@@ -1803,6 +1803,25 @@ msgstr "Requête :"
 msgid "No associated permission"
 msgstr "Pas de permission associée"
 
+#: apps/permission/templates/permission/scopes.html:8
+msgid "Available scopes"
+msgstr "Scopes disponibles"
+
+#: apps/permission/templates/permission/scopes.html:42
+#: note_kfet/templates/oauth2_provider/application_list.html:18
+msgid "No applications defined"
+msgstr "Pas d'application définie"
+
+#: apps/permission/templates/permission/scopes.html:43
+#: note_kfet/templates/oauth2_provider/application_list.html:19
+msgid "Click here"
+msgstr "Cliquez ici"
+
+#: apps/permission/templates/permission/scopes.html:43
+#: note_kfet/templates/oauth2_provider/application_list.html:19
+msgid "if you want to register a new one"
+msgstr "si vous voulez en enregistrer une nouvelle"
+
 #: apps/permission/views.py:72
 #, python-brace-format
 msgid ""
@@ -3133,11 +3152,10 @@ msgstr "Il n'y a pas de résultat."
 
 #: note_kfet/templates/oauth2_provider/application_confirm_delete.html:8
 msgid "Are you sure to delete the application"
-msgstr ""
-"Êtes-vous sûr⋅e de vouloir supprimer l'application"
+msgstr "Êtes-vous sûr⋅e de vouloir supprimer l'application"
 
 #: note_kfet/templates/oauth2_provider/application_confirm_delete.html:17
-#: note_kfet/templates/oauth2_provider/authorize.html:27
+#: note_kfet/templates/oauth2_provider/authorize.html:28
 msgid "Cancel"
 msgstr "Annuler"
 
@@ -3174,18 +3192,6 @@ msgstr "Modifier l'application"
 msgid "Your applications"
 msgstr "Vos applications"
 
-#: note_kfet/templates/oauth2_provider/application_list.html:18
-msgid "No applications defined"
-msgstr "Pas d'application définie"
-
-#: note_kfet/templates/oauth2_provider/application_list.html:19
-msgid "Click here"
-msgstr "Cliquez ici"
-
-#: note_kfet/templates/oauth2_provider/application_list.html:19
-msgid "if you want to register a new one"
-msgstr "si vous voulez en enregistrer une nouvelle"
-
 #: note_kfet/templates/oauth2_provider/application_list.html:24
 msgid "New Application"
 msgstr "Nouvelle application"
@@ -3195,7 +3201,7 @@ msgid "Register a new application"
 msgstr "Enregistrer une nouvelle application"
 
 #: note_kfet/templates/oauth2_provider/authorize.html:9
-#: note_kfet/templates/oauth2_provider/authorize.html:28
+#: note_kfet/templates/oauth2_provider/authorize.html:29
 msgid "Authorize"
 msgstr "Autoriser"
 
@@ -3203,7 +3209,7 @@ msgstr "Autoriser"
 msgid "Application requires following permissions:"
 msgstr "L'application requiert les permissions suivantes :"
 
-#: note_kfet/templates/oauth2_provider/authorize.html:35
+#: note_kfet/templates/oauth2_provider/authorize.html:36
 #: note_kfet/templates/oauth2_provider/authorized-oob.html:15
 msgid "Error:"
 msgstr "Erreur :"
@@ -3218,8 +3224,7 @@ msgstr "Merci de retourner à votre application et entrez ce code :"
 
 #: note_kfet/templates/oauth2_provider/authorized-token-delete.html:9
 msgid "Are you sure you want to delete this token?"
-msgstr ""
-"Êtes-vous sûr⋅e de vouloir supprimer ce jeton ?"
+msgstr "Êtes-vous sûr⋅e de vouloir supprimer ce jeton ?"
 
 #: note_kfet/templates/oauth2_provider/authorized-tokens.html:7
 msgid "Tokens"
diff --git a/note_kfet/templates/oauth2_provider/authorize.html b/note_kfet/templates/oauth2_provider/authorize.html
index 61348a5d..16c9f3b6 100644
--- a/note_kfet/templates/oauth2_provider/authorize.html
+++ b/note_kfet/templates/oauth2_provider/authorize.html
@@ -12,14 +12,15 @@
             <form id="authorizationForm" method="post">
                 <div class="card-body">
                     <p>{% trans "Application requires following permissions:" %}</p>
-                    {% csrf_token %}
-                    {{ form|crispy }}
 
                     <ul>
                         {% for scope in scopes_descriptions %}
                             <li>{{ scope }}</li>
                         {% endfor %}
                     </ul>
+
+                    {% csrf_token %}
+                    {{ form|crispy }}
                 </div>
                 <div class="card-footer text-center">
                     <div class="control-group">
-- 
GitLab