Projet

Général

Profil

0002-add-franceconnect-connector-53879.patch

Benjamin Dauvergne, 01 juin 2021 11:07

Télécharger (28,1 ko)

Voir les différences:

Subject: [PATCH 02/12] add franceconnect connector (#53879)

* follow the OAuth2 danse to get FranceConnect identite_pivot
* with ?mode=dgfip, also request an access_token to call DGFIP IR
  web-service
* call the IR web-service with two access tokens :
 * one from DGFIP
 * one from FC
 passerelle/apps/franceconnect/__init__.py     |   0
 passerelle/apps/franceconnect/fc.py           | 196 ++++++++++++++
 .../franceconnect/migrations/0001_initial.py  |  83 ++++++
 .../apps/franceconnect/migrations/__init__.py |   0
 passerelle/apps/franceconnect/models.py       | 241 ++++++++++++++++++
 .../templates/franceconnect/callback.html     |  39 +++
 .../templates/franceconnect/demo.html         |  75 ++++++
 .../franceconnect/resource_detail.html        |   8 +
 passerelle/settings.py                        |   1 +
 9 files changed, 643 insertions(+)
 create mode 100644 passerelle/apps/franceconnect/__init__.py
 create mode 100644 passerelle/apps/franceconnect/fc.py
 create mode 100644 passerelle/apps/franceconnect/migrations/0001_initial.py
 create mode 100644 passerelle/apps/franceconnect/migrations/__init__.py
 create mode 100644 passerelle/apps/franceconnect/models.py
 create mode 100644 passerelle/apps/franceconnect/templates/franceconnect/callback.html
 create mode 100644 passerelle/apps/franceconnect/templates/franceconnect/demo.html
 create mode 100644 passerelle/apps/franceconnect/templates/franceconnect/resource_detail.html
passerelle/apps/franceconnect/fc.py
1
# passerelle - uniform access to multiple data sources and services
2
# Copyright (C) 2021 Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

  
17
import base64
18
import json
19
import urllib.parse
20
import uuid
21

  
22
import requests
23
from django.utils.translation import ugettext_lazy as _
24

  
25

  
26
class FranceConnectError(Exception):
27
    def __init__(self, message, **kwargs):
28
        self.data = tuple(kwargs.items())
29
        super().__init__(message)
30

  
31

  
32
class Test:
33
    slug = 'test'
34
    name = _('Testing')
35
    authorize_url = 'https://fcp.integ01.dev-franceconnect.fr/api/v1/authorize'
36
    token_endpoint_url = 'https://fcp.integ01.dev-franceconnect.fr/api/v1/token'
37
    user_info_endpoint_url = 'https://fcp.integ01.dev-franceconnect.fr/api/v1/userinfo'
38
    logout_url = 'https://fcp.integ01.dev-franceconnect.fr/api/v1/logout'
39

  
40

  
41
class Prod:
42
    slug = 'prod'
43
    name = _('Production')
44
    authorize_url = 'https://app.franceconnect.gouv.fr/api/v1/authorize'
45
    token_endpoint_url = 'https://app.franceconnect.gouv.fr/api/v1/token'
46
    user_info_endpoint_url = 'https://app.franceconnect.gouv.fr/api/v1/userinfo'
47
    logout_url = 'https://app.franceconnect.gouv.fr/api/v1/logout'
48

  
49

  
50
PLATFORMS = [Test, Prod]
51
PLATFORMS_BY_SLUG = {platform.slug: platform for platform in PLATFORMS}
52

  
53

  
54
def base64url_decode(input):
55
    rem = len(input) % 4
56
    if rem > 0:
57
        input += b'=' * (4 - rem)
58
    return base64.urlsafe_b64decode(input)
59

  
60

  
61
class FranceConnect:
62
    def __init__(self, session, logger):
63
        self.session = session
64
        self.logger = logger
65
        self.items = []
66
        self.correlation_id = str(uuid.uuid4())
67

  
68
    def authorization_request(self, platform, client_id, scopes, redirect_uri, acr_values='eidas1'):
69
        '''Launch an authorization request to FranceConnect'''
70
        qs = urllib.parse.urlencode(
71
            {
72
                'response_type': 'code',
73
                'client_id': client_id,
74
                'redirect_uri': redirect_uri,
75
                'scope': 'openid ' + scopes,
76
                'state': str(uuid.uuid4()),
77
                'nonce': str(uuid.uuid4()),
78
                'acr_values': acr_values,
79
            }
80
        )
81
        return '%s?%s' % (platform.authorize_url, qs)
82

  
83
    def handle_authorization_response(
84
        self, platform, client_id, client_secret, redirect_uri, code, error, error_description
85
    ):
86
        if error:
87
            raise FranceConnectError(
88
                'No authorization code', error=error, error_description=error_description
89
            )
90

  
91
        data = {
92
            'grant_type': 'authorization_code',
93
            'redirect_uri': redirect_uri,
94
            'client_id': client_id,
95
            'client_secret': client_secret,
96
            'code': code,
97
        }
98

  
99
        response_content = self.request('token endpoint', 'POST', platform.token_endpoint_url, data=data)
100

  
101
        try:
102
            self.add('fc_token_endpoint_response', response_content)
103
            self.add('fc_access_token', response_content['access_token'])
104
            self.add('fc_id_token', response_content['id_token'])
105
            header, payload, signature = self.fc_id_token.split('.')
106
            self.add('fc_id_token_payload', json.loads(base64url_decode(payload.encode())))
107
        except Exception as e:
108
            raise FranceConnectError('Error in token endpoint response', sub_exception=e)
109

  
110
        fc_user_info = self.request(
111
            'user_info endpoint',
112
            'GET',
113
            platform.user_info_endpoint_url,
114
            headers={'Authorization': 'Bearer %s' % self.fc_access_token},
115
        )
116
        self.add('fc_user_info', fc_user_info)
117

  
118
    def request_dgfip_access_token(self, dgfip_username, dgfip_password, scope=None):
119
        data = {
120
            'grant_type': 'client_credentials',
121
        }
122
        if scope:
123
            data['scope'] = scope
124
        dgfip_response = self.request(
125
            'dgfip token endpoint',
126
            'POST',
127
            'https://gwfc.impots.gouv.fr/token',
128
            data=data,
129
            auth=(dgfip_username, dgfip_password),
130
        )
131

  
132
        self.add('dgfip_token_endpoint_response', dgfip_response)
133

  
134
        try:
135
            dgfip_access_token = dgfip_response['access_token']
136
        except (TypeError, KeyError) as e:
137
            raise FranceConnectError('dgfip token endpoint error %s' % e, response=dgfip_response)
138
        self.add('dgfip_access_token', dgfip_access_token)
139

  
140
    def request_dgfip_ir(self, annrev, id_teleservice=None):
141
        headers = {
142
            'Authorization': 'Bearer %s' % self.dgfip_access_token,
143
            'X-FranceConnect-OAuth': self.fc_access_token,
144
            'X-Correlation-ID': str(uuid.uuid4()),
145
            'Accept': 'application/prs.dgfip.part.situations.ir.assiettes.v1+json',
146
        }
147
        if id_teleservice:
148
            headers['ID_Teleservice'] = id_teleservice
149

  
150
        try:
151
            dgfip_ressource_ir_response = self.request(
152
                'ressource IR endpoint',
153
                'GET',
154
                'https://gwfc.impots.gouv.fr/impotparticulier/1.0/situations/ir/assiettes/annrev/%s' % annrev,
155
                headers=headers,
156
            )
157
        except FranceConnectError as e:
158
            dgfip_ressource_ir_response = {'error_desc': str(e), 'error': e.data}
159

  
160
        # accumulate data
161
        try:
162
            data = self.dgfip_ressource_ir_response
163
        except AttributeError:
164
            data = {}
165
        data[annrev] = dgfip_ressource_ir_response
166
        self.add('dgfip_ressource_ir_response', data)
167

  
168
    def __getattr__(self, name):
169
        try:
170
            return dict(self.items)[name]
171
        except KeyError:
172
            raise AttributeError(name)
173

  
174
    def add(self, key, value):
175
        self.items.append((key, value))
176

  
177
    def request(self, label, method, url, *args, **kwargs):
178
        self.logger.debug('request %s %s args:%s kwargs:%s', label, method, args, kwargs)
179
        self.add(label.replace(' ', '_') + '_request', [method, url, args, kwargs])
180
        try:
181
            response = getattr(self.session, method.lower())(url, *args, **kwargs)
182
            try:
183
                response_content = response.json()
184
            except ValueError:
185
                response_content = response.text[:1024]
186
                response.raise_for_status()
187
                raise
188
            else:
189
                response.raise_for_status()
190
        except requests.HTTPError as e:
191
            raise FranceConnectError('%s error %s' % (label, e), response=response_content)
192
        except requests.RequestException as e:
193
            raise FranceConnectError('%s error %s' % (label, e))
194
        except ValueError as e:
195
            raise FranceConnectError('%s error %s' % (label, e), response=response_content)
196
        return response_content
passerelle/apps/franceconnect/migrations/0001_initial.py
1
# Generated by Django 2.2.19 on 2021-05-17 11:43
2

  
3
from django.db import migrations, models
4

  
5

  
6
class Migration(migrations.Migration):
7

  
8
    initial = True
9

  
10
    dependencies = [
11
        ('base', '0029_auto_20210202_1627'),
12
    ]
13

  
14
    operations = [
15
        migrations.CreateModel(
16
            name='Resource',
17
            fields=[
18
                (
19
                    'id',
20
                    models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
21
                ),
22
                ('title', models.CharField(max_length=50, verbose_name='Title')),
23
                ('slug', models.SlugField(unique=True, verbose_name='Identifier')),
24
                ('description', models.TextField(verbose_name='Description')),
25
                (
26
                    'fc_platform_slug',
27
                    models.CharField(
28
                        choices=[('test', 'Testing'), ('prod', 'Production')],
29
                        max_length=4,
30
                        verbose_name='FranceConnect platform',
31
                    ),
32
                ),
33
                ('fc_client_id', models.CharField(max_length=64, verbose_name='FranceConnect client_id')),
34
                (
35
                    'fc_client_secret',
36
                    models.CharField(max_length=64, verbose_name='FranceConnect client_secret'),
37
                ),
38
                (
39
                    'fc_scopes',
40
                    models.TextField(default='identite_pivot', verbose_name='FranceConnect scopes'),
41
                ),
42
                (
43
                    'fc_text_template',
44
                    models.TextField(
45
                        default="{{ given_name }} {{ family_name }} {% if gender == 'male' %}né{% else %}née{% endif %} le {{ birthdate }} à {{ birthplace }}",
46
                        verbose_name='FranceConnect text template',
47
                    ),
48
                ),
49
                (
50
                    'dgfip_username',
51
                    models.CharField(
52
                        blank=True, max_length=64, null=True, verbose_name='api.impots.gouv.fr username'
53
                    ),
54
                ),
55
                (
56
                    'dgfip_password',
57
                    models.CharField(
58
                        blank=True, max_length=64, null=True, verbose_name='api.impots.gouv.fr password'
59
                    ),
60
                ),
61
                (
62
                    'dgfip_scopes',
63
                    models.TextField(blank=True, null=True, verbose_name='api.impots.gouv.fr scopes'),
64
                ),
65
                (
66
                    'dgfip_id_teleservice',
67
                    models.TextField(blank=True, null=True, verbose_name='api.impots.gouv.fr ID_Teleservice'),
68
                ),
69
                (
70
                    'users',
71
                    models.ManyToManyField(
72
                        blank=True,
73
                        related_name='_resource_users_+',
74
                        related_query_name='+',
75
                        to='base.ApiUser',
76
                    ),
77
                ),
78
            ],
79
            options={
80
                'verbose_name': 'FranceConnect',
81
            },
82
        ),
83
    ]
passerelle/apps/franceconnect/models.py
1
# passerelle - uniform access to multiple data sources and services
2
# Copyright (C) 2021 Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

  
17
import uuid
18

  
19
from django.core.cache import cache
20
from django.core.exceptions import PermissionDenied
21
from django.db import models
22
from django.http import HttpResponseBadRequest, HttpResponseRedirect
23
from django.template import Context, Template
24
from django.template.response import TemplateResponse
25
from django.urls import reverse
26
from django.utils.http import urlencode
27
from django.utils.timezone import now
28
from django.utils.translation import ugettext_lazy as _
29

  
30
from passerelle.base.models import BaseResource
31
from passerelle.utils import get_trusted_services
32
from passerelle.utils.api import endpoint
33
from passerelle.utils.origin import is_same_origin
34

  
35
from . import fc
36

  
37
# from passerelle.utils.jsonresponse import APIError
38

  
39

  
40
class Resource(BaseResource):
41
    category = _('Business Process Connectors')
42

  
43
    fc_platform_slug = models.CharField(
44
        _('FranceConnect platform'),
45
        max_length=4,
46
        choices=[(platform.slug, platform.name) for platform in fc.PLATFORMS],
47
    )
48

  
49
    fc_client_id = models.CharField(_('FranceConnect client_id'), max_length=64)
50

  
51
    fc_client_secret = models.CharField(_('FranceConnect client_secret'), max_length=64)
52

  
53
    fc_scopes = models.TextField(_('FranceConnect scopes'), default='identite_pivot')
54

  
55
    fc_text_template = models.TextField(
56
        _('FranceConnect text template'),
57
        default=(
58
            '''{{ given_name }} {{ family_name }} '''
59
            '''{% if gender == 'male' %}né{% else %}née{% endif %} le {{ birthdate }} '''
60
            '''à {{ birthplace }}'''
61
        ),
62
    )
63

  
64
    dgfip_username = models.CharField(_('api.impots.gouv.fr username'), max_length=64, blank=True, null=True)
65

  
66
    dgfip_password = models.CharField(_('api.impots.gouv.fr password'), max_length=64, blank=True, null=True)
67

  
68
    dgfip_scopes = models.TextField(_('api.impots.gouv.fr scopes'), blank=True, null=True)
69

  
70
    dgfip_id_teleservice = models.TextField(_('api.impots.gouv.fr ID_Teleservice'), blank=True, null=True)
71

  
72
    log_requests_errors = False
73

  
74
    class Meta:
75
        verbose_name = _('FranceConnect')
76

  
77
    @property
78
    def fc_platform(self):
79
        return fc.PLATFORMS_BY_SLUG[self.fc_platform_slug]
80

  
81
    def build_callback_url(self, request, **kwargs):
82
        redirect_uri = request.build_absolute_uri(
83
            reverse(
84
                'generic-endpoint',
85
                kwargs={'slug': self.slug, 'connector': self.get_connector_slug(), 'endpoint': 'callback'},
86
            )
87
        )
88
        if kwargs:
89
            redirect_uri += '?' + urlencode(
90
                {key: value for key, value in kwargs.items() if value is not None}
91
            )
92
        return redirect_uri
93

  
94
    def is_trusted_origin(self, request, origin):
95
        for service in get_trusted_services():
96
            if is_same_origin(origin, service['url']):
97
                return True
98

  
99
        if is_same_origin(request.build_absolute_uri(), origin):
100
            return True
101

  
102
        return False
103

  
104
    @endpoint(
105
        description=_('Init request'),
106
        parameters={
107
            'mode': {
108
                'description': _('What to retrieve, default to FranceConnect identity, can be "dgfip"'),
109
            },
110
            'origin': {
111
                'description': _('Origin for returning results through window.postMessage'),
112
            },
113
            'test': {
114
                'description': _('If set to one, activate the test callback view.'),
115
            },
116
        },
117
    )
118
    def init_request(self, request, origin, mode=None, test=None):
119
        if not request.user.is_superuser and not self.is_trusted_origin(request, origin):
120
            return HttpResponseBadRequest('Missing or invalid origin')
121

  
122
        redirect_uri = self.build_callback_url(request, origin=origin, mode=mode, test=test)
123
        franceconnect = fc.FranceConnect(session=self.requests, logger=self.logger)
124
        return HttpResponseRedirect(
125
            franceconnect.authorization_request(
126
                platform=self.fc_platform,
127
                client_id=self.fc_client_id,
128
                scopes=self.fc_scopes,
129
                redirect_uri=redirect_uri,
130
            )
131
        )
132

  
133
    @endpoint(
134
        description=_('FranceConnect callback (internal use)'),
135
        parameters={
136
            'origin': {
137
                'description': _('HTTP Origin, needed to secure window.postMessage'),
138
            },
139
            'mode': {
140
                'description': _('Mode'),
141
            },
142
            'test': {
143
                'description': _('Use test mode (to see exchanges)'),
144
            },
145
        },
146
    )
147
    def callback(self, request, origin, mode=None, test=None, **kwargs):
148
        if not request.user.is_superuser and not self.is_trusted_origin(request, origin):
149
            return HttpResponseBadRequest('Missing or invalid origin.')
150

  
151
        if test and not request.user.is_superuser:
152
            return HttpResponseBadRequest('Only admin can use test mode.')
153

  
154
        franceconnect = fc.FranceConnect(session=self.requests, logger=self.logger)
155
        redirect_uri = self.build_callback_url(request, origin=origin, mode=mode, test=test)
156
        context = {
157
            'origin': origin,
158
            'franceconnect': franceconnect,
159
            'redirect_uri': redirect_uri,
160
            'test': test,
161
        }
162
        try:
163
            franceconnect.handle_authorization_response(
164
                platform=self.fc_platform,
165
                client_id=self.fc_client_id,
166
                client_secret=self.fc_client_secret,
167
                redirect_uri=redirect_uri,
168
                code=request.GET.get('code'),
169
                error=request.GET.get('error'),
170
                error_description=request.GET.get('error_description'),
171
            )
172
            token = {'franceconnect': franceconnect.fc_user_info}
173
            if mode == 'dgfip':
174
                franceconnect.request_dgfip_access_token(
175
                    self.dgfip_username, self.dgfip_password, scope=self.dgfip_scopes
176
                )
177
                current_year = now().year
178
                for year in range(current_year - 3, current_year):
179
                    franceconnect.request_dgfip_ir(str(year), id_teleservice=self.dgfip_id_teleservice)
180
                token['dgfip_ir'] = franceconnect.dgfip_ressource_ir_response
181
            try:
182
                template = Template(self.fc_text_template)
183
                token['text'] = template.render(Context(franceconnect.fc_user_info))
184
            except Exception:
185
                token['text'] = '<failed to render>'
186
            context['data'] = {'id': self.store(token), 'text': token['text']}
187
        except fc.FranceConnectError as e:
188
            context['error'] = e
189
        return TemplateResponse(request, 'franceconnect/callback.html', context=context)
190

  
191
    @endpoint(
192
        description=_('Demo page (to check your configuration)'),
193
    )
194
    def demo(self, request, **kwargs):
195
        if not request.user.is_superuser:
196
            return PermissionDenied
197
        return TemplateResponse(
198
            request,
199
            'franceconnect/demo.html',
200
            context={'origin': request.build_absolute_uri('/'), 'resource': self},
201
        )
202

  
203
    @endpoint(
204
        description=_('Data source'),
205
    )
206
    def data_source(self, request, id=None, mode=None, **kwargs):
207
        if id:
208
            return {
209
                'data': [
210
                    dict(self.retrieve(id), id=id),
211
                ]
212
            }
213
        url = request.build_absolute_uri(
214
            reverse(
215
                'generic-endpoint',
216
                kwargs={
217
                    'slug': self.slug,
218
                    'connector': self.get_connector_slug(),
219
                    'endpoint': 'init_request',
220
                },
221
            )
222
        )
223
        if mode == 'dgfip':
224
            url += '?mode=dgfip'
225
        return {
226
            'data': [
227
                {
228
                    'id': '',
229
                    'text': '',
230
                    'init_request_url': url,
231
                }
232
            ]
233
        }
234

  
235
    def store(self, data):
236
        ref = str(uuid.uuid4().hex)
237
        cache.set(ref, data)
238
        return ref
239

  
240
    def retrieve(self, ref):
241
        return cache.get(ref)
passerelle/apps/franceconnect/templates/franceconnect/callback.html
1
<html>
2
    <head>
3
    </head>
4
    <body>
5
        {% if test %}<button id="continue">Continue</button>
6
        {{ data|json_script:"data" }}
7
        <p>redirect_uri: <pre>{{ redirect_uri|pprint }}</pre></p>
8
        <p>correlation_id: <pre>{{ franceconnect.correlation_id }}</pre></p>
9
        {% if error %}
10
            <p>{{ error }}<p>
11
            {% if error.data %}
12
                <dl>
13
                    {% for key, value in error.data %}
14
                        <dt>{{ key }}</td>
15
                        <dd><pre>{{ value|pprint }}</pre></dd>
16
                    {% endfor %}
17
                </dl>
18
            {% endif %}
19
        {% endif %}
20
        <ul>
21
        {% for key, value in franceconnect.items reversed %}
22
            <li>{{ key }}&nbsp;: <pre>{{ value|pprint }}</pre></li>
23
        {% endfor %}
24
        </ul>
25
        {% endif %}
26
        <script>
27
            (function () {
28
                const data = JSON.parse(document.getElementById('data').textContent);
29
                const continue_button = document.getElementById('continue');
30
                const post_message = function () {
31
                    window.opener.postMessage(data, "{{ origin }}");
32
                }
33
                {% if test %}continue_button.addEventListener('click', function () { post_message(); });
34
                {% else %}post_message(){% endif %}
35
            })();
36
        </script>
37
    </body>
38
</html>
39

  
passerelle/apps/franceconnect/templates/franceconnect/demo.html
1
{% extends "passerelle/manage.html" %}
2
{% load i18n %}
3

  
4
{% block breadcrumb %}
5
{{ block.super }}
6
<a href="{{ resource.get_absolute_url }}">{{ resource.title }}</a>
7
<a href="#">{% trans "Demo view" %}</a>
8
{% endblock %}
9

  
10
{% block appbar %}
11
{% endblock %}
12

  
13
{% block content %}
14
<h1>{% trans "Demo view" %}</h1>
15
<p>
16
  <button id="start">{% trans "Get FranceConnect data" %}</button>
17
</p>
18
<p>
19
  <label for="dgfip">{% trans "Get DGFIP data" %}</label>
20
  <input type="checkbox" id="dgfip"/>
21
</p>
22
        <script>
23
            var popup = null;
24
const popupCenter = function(url, title, w, h) {
25
    // Fixes dual-screen position                             Most browsers      Firefox
26
    const dualScreenLeft = window.screenLeft !==  undefined ? window.screenLeft : window.screenX;
27
    const dualScreenTop = window.screenTop !==  undefined   ? window.screenTop  : window.screenY;
28

  
29
    const width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width;
30
    const height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height;
31

  
32
    const systemZoom = width / window.screen.availWidth;
33
    const left = (width - w) / 2 / systemZoom + dualScreenLeft
34
    const top = (height - h) / 2 / systemZoom + dualScreenTop
35
    popup = window.open(url, title,
36
      `
37
      scrollbars=yes,
38
      width=${w / systemZoom},
39
      height=${h / systemZoom},
40
      top=${top},
41
      left=${left}
42
      `
43
    )
44

  
45
    if (window.focus) popup.focus();
46
};
47

  
48
            $('#start').on('click', function () {
49
                $('#user-info').hide();
50
                if (popup) { popup.close(); popup = null; };
51
                var url = 'init_request?test=1&origin={{ origin }}';
52
                var checkbox = document.getElementById('dgfip');
53
                if (checkbox && checkbox.checked) {
54
                    url += '&mode=dgfip';
55
                };
56
                popupCenter(url, 'FranceConnect', 1000, 670);
57
            });
58
            $(window).on('message', function(event) {
59
                var data = event.originalEvent.data;
60
                var origin = event.originalEvent.origin;
61
                if (origin != window.location.origin) {
62
                    return;
63
                }
64
                $.getJSON("data_source?id=" + data.id, function(result) {
65
                    $('#user-info').show();
66
                    $('#user-info-preview').text(JSON.stringify(result, null, 2));
67
                });
68
                popup.close();
69
            });
70
        </script>
71
        <div id="user-info" style="display: none; position: relative;">
72
            <h3>{% trans "Data-source data" %}</h3>
73
            <pre id="user-info-preview" style="background: white; border: 0.2ex solid black; color: black; padding: 1em; width: calc(100% - 5em);"></pre>
74
        </div>
75
{% endblock %}
passerelle/apps/franceconnect/templates/franceconnect/resource_detail.html
1
{% extends "passerelle/manage/service_view.html" %}
2
{% load i18n passerelle %}
3

  
4
{% block description %}
5
{{ block.super }}
6
{% url "generic-endpoint" connector="franceconnect" slug=object.slug endpoint="callback"  as callback_url %}
7
<p>URL de callback pour FranceConnect: <a href="{{ request.scheme }}://{{ request.get_host }}{{ callback_url }}">{{ request.scheme }}://{{ request.get_host }}{{ callback_url }}</a></p>
8
{% endblock %}
passerelle/settings.py
141 141
    'passerelle.apps.esirius',
142 142
    'passerelle.apps.family',
143 143
    'passerelle.apps.feeds',
144
    'passerelle.apps.franceconnect',
144 145
    'passerelle.apps.gdc',
145 146
    'passerelle.apps.gesbac',
146 147
    'passerelle.apps.jsondatastore',
147
-