Projet

Général

Profil

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

Serghei Mihai, 24 juin 2019 16:04

Télécharger (21,9 ko)

Voir les différences:

Subject: [PATCH 2/2] 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             | 258 ++++++++++++++++++
 passerelle/settings.py                        |   1 +
 tests/test_astregs.py                         | 144 ++++++++++
 6 files changed, 467 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, verbose_name='User NameID')),
42
                ('association_id', models.CharField(max_length=32, verbose_name='Association ID')),
43
                ('created', models.DateTimeField(auto_now_add=True, verbose_name='Creation date')),
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, verbose_name='Temporary token')),
52
                ('association_id', models.CharField(max_length=32, verbose_name='Association ID')),
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
import inspect
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(_('Organism'), 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
        return self.soap_client(wsdl_url=url)
84

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

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

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

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

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

  
205

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

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

  
241

  
242
class Link(models.Model):
243
    resource = models.ForeignKey(AstreGS)
244
    name_id = models.CharField(_('User NameID'), max_length=32)
245
    association_id = models.CharField(_('Association ID'), max_length=32)
246
    created = models.DateTimeField(_('Creation date'), auto_now_add=True)
247

  
248
    class Meta:
249
        unique_together = ('resource', 'name_id', 'association_id')
250

  
251

  
252
class LinkRequest(models.Model):
253
    resource = models.ForeignKey(AstreGS)
254
    token = models.CharField(_('Temporary token'), max_length=16)
255
    association_id = models.CharField(_('Association ID'), max_length=32)
256

  
257
    class Meta:
258
        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
        class FakeObject():
16
            Numero_SIRET='50043390900016'
17
            Nom_enregistrement='ASSOCIATION OMNISPORTS DES MONTS D AZUR'
18
            Code_tiers='173957'
19
        liste = mock.Mock(EnregRechercheTiersDetailsReturn=[FakeObject()])
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.base.models.BaseResource.soap_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[1]['wsdl_url'] == '%sRechercheTiersDetails?wsdl' % BASE_URL
78

  
79

  
80
@mock.patch('passerelle.base.models.BaseResource.soap_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[1]['wsdl_url'] == '%sRechercheTiers?wsdl' % BASE_URL
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.base.models.BaseResource.soap_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.base.models.BaseResource.soap_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.base.models.BaseResource.soap_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.base.models.BaseResource.soap_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
-