Projet

Général

Profil

0001-astregs-add-initial-connector-33424.patch

Serghei Mihai, 25 juin 2019 15:50

Télécharger (22 ko)

Voir les différences:

Subject: [PATCH] astregs: add initial connector (#33424)

 passerelle/apps/astregs/__init__.py           |   0
 .../apps/astregs/migrations/0001_initial.py   |  64 +++++
 .../apps/astregs/migrations/__init__.py       |   0
 passerelle/apps/astregs/models.py             | 264 ++++++++++++++++++
 passerelle/settings.py                        |   1 +
 tests/test_astregs.py                         | 144 ++++++++++
 6 files changed, 473 insertions(+)
 create mode 100644 passerelle/apps/astregs/__init__.py
 create mode 100644 passerelle/apps/astregs/migrations/0001_initial.py
 create mode 100644 passerelle/apps/astregs/migrations/__init__.py
 create mode 100644 passerelle/apps/astregs/models.py
 create mode 100644 tests/test_astregs.py
passerelle/apps/astregs/migrations/0001_initial.py
1
# -*- coding: utf-8 -*-
2
# Generated by Django 1.11.20 on 2019-06-19 10:24
3
from __future__ import unicode_literals
4

  
5
from django.db import migrations, models
6
import django.db.models.deletion
7

  
8

  
9
class Migration(migrations.Migration):
10

  
11
    initial = True
12

  
13
    dependencies = [
14
        ('base', '0012_job'),
15
    ]
16

  
17
    operations = [
18
        migrations.CreateModel(
19
            name='AstreGS',
20
            fields=[
21
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
22
                ('title', models.CharField(max_length=50, verbose_name='Title')),
23
                ('description', models.TextField(verbose_name='Description')),
24
                ('slug', models.SlugField(unique=True, verbose_name='Identifier')),
25
                ('wsdl_base_url', models.URLField(verbose_name='Webservices base URL')),
26
                ('username', models.CharField(max_length=32, verbose_name='Username')),
27
                ('password', models.CharField(max_length=32, verbose_name='Password')),
28
                ('organism', models.CharField(max_length=32, verbose_name='Organism')),
29
                ('budget', models.CharField(max_length=32, verbose_name='Budget')),
30
                ('exercice', models.CharField(max_length=32, verbose_name='Exercice')),
31
                ('users', models.ManyToManyField(blank=True, related_name='_astregs_users_+', related_query_name='+', to='base.ApiUser')),
32
            ],
33
            options={
34
                'verbose_name': 'AstresGS',
35
            },
36
        ),
37
        migrations.CreateModel(
38
            name='Link',
39
            fields=[
40
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
41
                ('name_id', models.CharField(max_length=32)),
42
                ('association_id', models.CharField(max_length=32)),
43
                ('created', models.DateTimeField(auto_now_add=True)),
44
                ('resource', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='astregs.AstreGS')),
45
            ],
46
        ),
47
        migrations.CreateModel(
48
            name='LinkRequest',
49
            fields=[
50
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
51
                ('token', models.CharField(max_length=16)),
52
                ('association_id', models.CharField(max_length=32)),
53
                ('resource', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='astregs.AstreGS')),
54
            ],
55
        ),
56
        migrations.AlterUniqueTogether(
57
            name='linkrequest',
58
            unique_together=set([('resource', 'token', 'association_id')]),
59
        ),
60
        migrations.AlterUniqueTogether(
61
            name='link',
62
            unique_together=set([('resource', 'name_id', 'association_id')]),
63
        ),
64
    ]
passerelle/apps/astregs/models.py
1
# -*- coding: utf-8 -*-
2
# Copyright (C) 2019  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
from zeep.helpers import serialize_object
18

  
19
from django.db import models
20
from django.utils.translation import ugettext_lazy as _
21
from django.utils.crypto import get_random_string
22
from django.utils.text import slugify
23
from django.utils.six.moves.urllib import parse as urlparse
24
from django.http import Http404
25

  
26
from passerelle.base.models import BaseResource
27
from passerelle.utils.api import endpoint
28
from passerelle.utils.jsonresponse import APIError
29

  
30
LINK_CREATION_SCHEMA = {
31
    '$schema': 'http://json-schema.org/draft-03/schema#',
32
    'title': 'AstreGS Link',
33
    'description': '',
34
    'type': 'object',
35
    'properties': {
36
        'NameID': {
37
            'description': 'user name_id',
38
            'type': 'string',
39
            'required': True
40
        },
41
        'token': {
42
            'description': 'linking token',
43
            'type': 'string',
44
            'required': True
45
        }
46
    }
47
}
48

  
49

  
50
class AstreGS(BaseResource):
51
    wsdl_base_url = models.URLField(_('Webservices base URL'))
52
    username = models.CharField(_('Username'), max_length=32)
53
    password = models.CharField(_('Password'), max_length=32)
54
    organism = models.CharField('Organisme', max_length=32)
55
    budget = models.CharField('Budget', max_length=32)
56
    exercice = models.CharField('Exercice', max_length=32)
57

  
58
    category = _('Business Process Connectors')
59

  
60
    class Meta:
61
        verbose_name = u'AstresGS'
62

  
63
    @classmethod
64
    def get_verbose_name(cls):
65
        return cls._meta.verbose_name
66

  
67
    def check_status(self):
68
        response = self.requests.get(self.wsdl_base_url)
69
        response.raise_for_status()
70

  
71
    @property
72
    def authentication(self):
73
        return {'USERNOM': self.username, 'USERPWD': self.password}
74

  
75
    @property
76
    def context(self):
77
        return {'Organisme': self.organism,
78
                'Budget': self.budget,
79
                'Exercice': self.exercice}
80

  
81
    def get_client(self, wsdl_name):
82
        url = urlparse.urljoin(self.wsdl_base_url, '%s?wsdl' % wsdl_name)
83
        client = self.soap_client(wsdl_url=url)
84
        parsed_wsdl_address = urlparse.urlparse(client.service._binding_options['address'])
85
        parsed_real_address = urlparse.urlparse(self.wsdl_base_url)
86
        client.service._binding_options['address'] = urlparse.urlunparse(
87
            parsed_real_address[:2] + parsed_wsdl_address[2:])
88
        return client
89

  
90

  
91
    @endpoint(description=_('Find associations by SIREN number'),
92
              perm='can_access',
93
              parameters={
94
                  'siren': {'description': _('SIREN Number'),
95
                            'example_value': '77567227216096'}
96
                  }
97
              )
98
    def associations(self, request, siren):
99
        client = self.get_client('RechercheTiersDetails')
100
        r = client.service.liste(Authentification=self.authentication,
101
                                 Contexte=self.context,
102
                                 Criteres={'siren': '%s*' % siren})
103
        data = []
104
        if r.liste:
105
            for item in r.liste.EnregRechercheTiersDetailsReturn:
106
                association_data = serialize_object(item)
107
                association_data['id'] = association_data['Numero_SIRET']
108
                association_data['text'] = '%(Numero_SIRET)s - %(Nom_enregistrement)s' % association_data
109
                association_data['code'] = association_data['Code_tiers']
110
                association_data['name'] = association_data['Nom_enregistrement']
111
                association_data['address'] = association_data['Nom_enregistrement']
112
                data.append(association_data)
113
        return {'data': data}
114

  
115
    @endpoint(description=_('Check if association exists by its SIRET number'),
116
              name='verify-association-presence-by-siret',
117
              perm='can_access',
118
              parameters={
119
                  'siret': {'description': _('SIRET Number'),
120
                            'example_value': '7756722721609600014'}
121
                  }
122
              )
123
    def verify_association_presence(self, request, siret):
124
        client = self.get_client('RechercheTiers')
125
        r = client.service.liste(Authentification=self.authentication,
126
                                 Contexte=self.context,
127
                                 Criteres={'siren': siret})
128
        if r.liste:
129
            return {'exists': True}
130
        return {'exists': False}
131

  
132
    @endpoint(name='get-association-link-means',
133
              description=_('Get association linking means'),
134
              perm='can_access',
135
              parameters={
136
                  'association_id': {'description': _('Association ID'),
137
                                     'example_value': '42435'}
138
                  }
139
    )
140
    def get_association_link_means(self, request, association_id):
141
        client = self.get_client('Tiers')
142
        r = client.service.Chargement({'Authentification': self.authentication,
143
                                       'Contexte': self.context,
144
                                       'TiersCle': {'CodeTiers': association_id}})
145
        data = []
146
        # assocation contact is defined in EncodeKeyContact attribute
147
        if not r.EncodeKeyContact:
148
            return {'data': data}
149

  
150
        client = self.get_client('Contact')
151
        r = client.service.Chargement({'Authentification': self.authentication,
152
                                       'Contexte': self.context,
153
                                       'ContactCle': {'idContact': r.EncodeKeyContact}})
154
        if r.AdresseMail or r.TelephoneMobile or r.RueVoie:
155
            try:
156
                link_request = LinkRequest.objects.get(resource=self, association_id=association_id)
157
            except LinkRequest.DoesNotExist:
158
                token = get_random_string()
159
                link_request = LinkRequest.objects.create(resource=self, association_id=association_id,
160
                                                          token=token)
161
        if r.AdresseMail:
162
            data.append({'id': '1',
163
                         'text': r.AdresseMail,
164
                         'value': r.AdresseMail,
165
                         'token': link_request.token,
166
                         'type': 'email'})
167
        if r.TelephoneMobile:
168
            data.append({'id': '2',
169
                         'text': r.TelephoneMobile,
170
                         'value': r.TelephoneMobile,
171
                         'token': link_request.token,
172
                         'type': 'mobile'})
173
        if r.RueVoie:
174
            full_address = r.RueVoie
175
            full_address += ', '
176
            if r.CodePostal:
177
                full_address += '%s ' % r.CodePostal
178
            if r.Ville:
179
                full_address += r.Ville
180
            data.append({'id': '3',
181
                         'text': full_address,
182
                         'address': r.RueVoie,
183
                         'zicode': r.CodePostal,
184
                         'city': r.Ville,
185
                         'value': full_address,
186
                         'token': link_request.token,
187
                         'type': 'mail'})
188
        return {'data': data}
189

  
190
    @endpoint(name='link', perm='can_access',
191
              post={
192
                  'description': _('Create link with an association'),
193
                  'request_body': {
194
                      'schema': {
195
                          'application/json': LINK_CREATION_SCHEMA
196
                      }
197
                  }
198
              }
199
    )
200
    def link(self, request, post_data):
201
        try:
202
            link_request = LinkRequest.objects.get(resource=self, token=post_data['token'])
203
            # if link request found, create link
204
            link = Link.objects.create(resource=self, name_id=post_data['NameID'], association_id=link_request.association_id)
205
            # then delete link request
206
            link_request.delete()
207
            return {'link': link.id, 'created': link.created, 'association_id': link_request.association_id}
208
        except LinkRequest.DoesNotExist:
209
            raise Http404('token not found')
210

  
211

  
212
    @endpoint(description=_('Remove link to an association'),
213
              perm='can_access',
214
              parameters={
215
                  'NameID':{
216
                      'description': _('Publik NameID'),
217
                      'example_value': 'xyz24d934',
218
                  },
219
                  'association_id':{
220
                      'description': _('Association ID'),
221
                      'example_value': '12345',
222
                  }
223
              })
224
    def unlink(self, request, NameID, association_id):
225
        try:
226
            link = Link.objects.get(resource=self, name_id=NameID, association_id=association_id)
227
            link.delete()
228
            return {'deleted': True}
229
        except Link.DoesNotExist:
230
            raise Http404('link not found')
231

  
232
    @endpoint(description=_('List user links to associations'),
233
              perm='can_access',
234
              parameters={
235
                  'NameID':{
236
                      'description': _('User NameID'),
237
                      'example_value': 'xyz24d934',
238
                  }
239
              })
240
    def links(self, request, NameID):
241
        data = []
242
        for link in Link.objects.filter(resource=self, name_id=NameID):
243
            data.append({'association_id': link.association_id,
244
                         'created': link.created})
245
        return {'data': data}
246

  
247

  
248
class Link(models.Model):
249
    resource = models.ForeignKey(AstreGS)
250
    name_id = models.CharField(max_length=32)
251
    association_id = models.CharField(max_length=32)
252
    created = models.DateTimeField(auto_now_add=True)
253

  
254
    class Meta:
255
        unique_together = ('resource', 'name_id', 'association_id')
256

  
257

  
258
class LinkRequest(models.Model):
259
    resource = models.ForeignKey(AstreGS)
260
    token = models.CharField(max_length=16)
261
    association_id = models.CharField(max_length=32)
262

  
263
    class Meta:
264
        unique_together = ('resource', 'token', 'association_id')
passerelle/settings.py
123 123
    'passerelle.apps.api_particulier',
124 124
    'passerelle.apps.arcgis',
125 125
    'passerelle.apps.arpege_ecp',
126
    'passerelle.apps.astregs',
126 127
    'passerelle.apps.atal',
127 128
    'passerelle.apps.atos_genesys',
128 129
    'passerelle.apps.base_adresse',
tests/test_astregs.py
1
# -*- coding: utf-8 -*-
2

  
3
import mock
4
import pytest
5

  
6
from passerelle.apps.astregs.models import AstreGS, LinkRequest, Link
7

  
8
from . import utils
9

  
10
BASE_URL = 'https://test-ws-astre-gs.departement06.fr/axis2/services/'
11

  
12

  
13
class FakeSearchDetailsService():
14
    def liste(self, Authentification, Contexte, Criteres):
15
        data = dict(Numero_SIRET='50043390900016',
16
                    Nom_enregistrement='ASSOCIATION OMNISPORTS DES MONTS D AZUR',
17
                    Code_tiers='173957'
18
        )
19
        liste = mock.Mock(EnregRechercheTiersDetailsReturn=[data])
20
        return mock.Mock(liste=liste)
21

  
22

  
23
class FakeSearchService():
24
    def liste(self, Authentification, Contexte, Criteres):
25
        if Criteres['siren'] == 'unknown':
26
            return mock.Mock(liste=False)
27
        return mock.Mock(liste=True)
28

  
29

  
30
class FakeTiersService():
31
    def Chargement(self, data):
32
        if data['TiersCle']['CodeTiers'] == '42':
33
            return mock.Mock(EncodeKeyContact='4242')
34
        return mock.Mock(EncodeKeyContact=None)
35

  
36

  
37
class FakeContactService():
38
    def Chargement(self, data):
39
        return mock.Mock(AdresseMail='foo@example.com',
40
                         TelephoneMobile='0607080900',
41
                         RueVoie='169 rue du Château',
42
                         CodePostal='75014',
43
                         Ville='Paris')
44

  
45

  
46
def search_side_effect(wsdl_url, **kwargs):
47
    if 'RechercheTiersDetail' in wsdl_url:
48
        return mock.Mock(service=FakeSearchDetailsService())
49
    return mock.Mock(service=FakeSearchService())
50

  
51
def contact_search_side_effect(wsdl_url, **kwargs):
52
    if 'Tiers' in wsdl_url:
53
        return mock.Mock(service=FakeTiersService())
54
    return mock.Mock(service=FakeContactService())
55

  
56

  
57
@pytest.fixture
58
def connector(db):
59
    return utils.make_resource(AstreGS,
60
            title='Test', slug='test',
61
            description='test', wsdl_base_url=BASE_URL,
62
            username='CS-FORML', password='secret',
63
            organism='CG06', budget='01',
64
            exercice='2019'
65
    )
66

  
67

  
68
@mock.patch('passerelle.apps.astregs.models.AstreGS.get_client', side_effect=search_side_effect)
69
def test_search_association_by_siren(client, connector, app):
70
    resp = app.get('/astregs/test/associations', params={'siren': '500433909'})
71
    assert isinstance(resp.json['data'], list)
72
    assert len(resp.json['data']) > 0
73
    assert resp.json['data'][0]['id'] == '50043390900016'
74
    assert resp.json['data'][0]['text'] == '50043390900016 - ASSOCIATION OMNISPORTS DES MONTS D AZUR'
75
    assert resp.json['data'][0]['code'] == '173957'
76
    assert resp.json['data'][0]['name'] == 'ASSOCIATION OMNISPORTS DES MONTS D AZUR'
77
    assert client.call_args[0][0] == 'RechercheTiersDetails'
78

  
79

  
80
@mock.patch('passerelle.apps.astregs.models.AstreGS.get_client', side_effect=search_side_effect)
81
def test_check_association_presence(client, connector, app):
82
    resp = app.get('/astregs/test/verify-association-presence-by-siret', params={'siret': '50043390900014'})
83
    assert resp.json['exists'] == True
84
    assert client.call_args[0][0] == 'RechercheTiers'
85
    resp = app.get('/astregs/test/verify-association-presence-by-siret', params={'siret': 'unknown'})
86
    assert resp.json['exists'] == False
87

  
88
@mock.patch('passerelle.apps.astregs.models.AstreGS.get_client', side_effect=contact_search_side_effect)
89
def test_association_linking_means(client, connector, app):
90
    resp = app.get('/astregs/test/get-association-link-means', params={'association_id': '000'})
91
    assert resp.json['data'] == []
92
    assert LinkRequest.objects.count() == 0
93

  
94
    resp = app.get('/astregs/test/get-association-link-means', params={'association_id': '42'})
95
    assert len(resp.json['data']) == 3
96
    assert LinkRequest.objects.filter(association_id='42').count() == 1
97
    for result in resp.json['data']:
98
        assert 'id' in result
99
        assert 'text' in result
100
        assert 'type' in result
101
        assert 'token' in result
102

  
103

  
104
@mock.patch('passerelle.apps.astregs.models.AstreGS.get_client', side_effect=contact_search_side_effect)
105
def test_link_user_to_association(client, connector, app):
106
    assert LinkRequest.objects.count() == 0
107
    resp = app.get('/astregs/test/get-association-link-means', params={'association_id': '42'})
108
    assert len(resp.json['data']) == 3
109
    token = resp.json['data'][0]['token']
110
    assert LinkRequest.objects.filter(association_id='42').count() == 1
111
    resp = app.post_json('/astregs/test/link', params={'token': token, 'NameID': 'user_name_id'})
112
    assert LinkRequest.objects.filter(association_id='42').count() == 0
113
    assert Link.objects.filter(name_id='user_name_id', association_id='42').count() == 1
114
    link = Link.objects.get(name_id='user_name_id', association_id='42')
115
    assert resp.json['association_id'] == link.association_id
116
    assert resp.json['link'] == link.pk
117
    assert 'created' in resp.json
118
    # try to link again
119
    resp = app.post_json('/astregs/test/link', params={'token': token, 'NameID': 'user_name_id'}, status=404)
120

  
121

  
122
@mock.patch('passerelle.apps.astregs.models.AstreGS.get_client', side_effect=contact_search_side_effect)
123
def test_list_user_links(client, connector, app):
124
    resp = app.get('/astregs/test/get-association-link-means', params={'association_id': '42'})
125
    token = resp.json['data'][0]['token']
126
    resp = app.post_json('/astregs/test/link', params={'token': token, 'NameID': 'user_name_id'})
127
    resp = app.get('/astregs/test/links', params={'NameID': 'user_name_id'})
128
    assert len(resp.json['data']) > 0
129
    for link in resp.json['data']:
130
        assert 'association_id' in link
131
        assert 'created' in link
132

  
133
    resp = app.get('/astregs/test/links', params={'NameID': 'foo_name_id'})
134
    assert len(resp.json['data']) == 0
135

  
136

  
137
@mock.patch('passerelle.apps.astregs.models.AstreGS.get_client', side_effect=contact_search_side_effect)
138
def test_unlink_user_from_association(client, connector, app):
139
    resp = app.get('/astregs/test/get-association-link-means', params={'association_id': '42'})
140
    token = resp.json['data'][0]['token']
141
    resp = app.post_json('/astregs/test/link', params={'token': token, 'NameID': 'user_name_id'})
142
    resp = app.get('/astregs/test/unlink', params={'NameID': 'user_name_id', 'association_id': '42'})
143
    assert resp.json['deleted']
144
    resp = app.get('/astregs/test/unlink', params={'NameID': 'user_name_id', 'association_id': '42'}, status=404)
0
-