Projet

Général

Profil

0001-add-a-generic-soap-connector-60836.patch

Benjamin Dauvergne, 19 janvier 2022 19:35

Télécharger (10,1 ko)

Voir les différences:

Subject: [PATCH] add a generic soap connector (#60836)

 passerelle/apps/soap/__init__.py              |   0
 .../apps/soap/migrations/0001_initial.py      |  83 ++++++++++++
 passerelle/apps/soap/migrations/__init__.py   |   0
 passerelle/apps/soap/models.py                | 119 ++++++++++++++++++
 .../templates/soap/soapconnector_detail.html  |  23 ++++
 passerelle/settings.py                        |   1 +
 6 files changed, 226 insertions(+)
 create mode 100644 passerelle/apps/soap/__init__.py
 create mode 100644 passerelle/apps/soap/migrations/0001_initial.py
 create mode 100644 passerelle/apps/soap/migrations/__init__.py
 create mode 100644 passerelle/apps/soap/models.py
 create mode 100644 passerelle/apps/soap/templates/soap/soapconnector_detail.html
passerelle/apps/soap/migrations/0001_initial.py
1
# Generated by Django 2.2.24 on 2022-01-19 16:37
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='SOAPConnector',
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
                    'basic_auth_username',
27
                    models.CharField(
28
                        blank=True, max_length=128, verbose_name='Basic authentication username'
29
                    ),
30
                ),
31
                (
32
                    'basic_auth_password',
33
                    models.CharField(
34
                        blank=True, max_length=128, verbose_name='Basic authentication password'
35
                    ),
36
                ),
37
                (
38
                    'client_certificate',
39
                    models.FileField(
40
                        blank=True, null=True, upload_to='', verbose_name='TLS client certificate'
41
                    ),
42
                ),
43
                (
44
                    'trusted_certificate_authorities',
45
                    models.FileField(blank=True, null=True, upload_to='', verbose_name='TLS trusted CAs'),
46
                ),
47
                (
48
                    'verify_cert',
49
                    models.BooleanField(blank=True, default=True, verbose_name='TLS verify certificates'),
50
                ),
51
                (
52
                    'http_proxy',
53
                    models.CharField(blank=True, max_length=128, verbose_name='HTTP and HTTPS proxy'),
54
                ),
55
                (
56
                    'wsdl_url',
57
                    models.URLField(
58
                        help_text='URL of the WSDL file', max_length=400, verbose_name='WSDL URL'
59
                    ),
60
                ),
61
                (
62
                    'zeep_strict',
63
                    models.BooleanField(default=True, verbose_name='Be strict with returned XML'),
64
                ),
65
                (
66
                    'zeep_xsd_ignore_sequence_order',
67
                    models.BooleanField(default=False, verbose_name='Ignore sequence order'),
68
                ),
69
                (
70
                    'users',
71
                    models.ManyToManyField(
72
                        blank=True,
73
                        related_name='_soapconnector_users_+',
74
                        related_query_name='+',
75
                        to='base.ApiUser',
76
                    ),
77
                ),
78
            ],
79
            options={
80
                'verbose_name': 'SOAP connector',
81
            },
82
        ),
83
    ]
passerelle/apps/soap/models.py
1
# passerelle - uniform access to multiple data sources and services
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 collections
18
import io
19
import operator
20

  
21
import zeep
22
import zeep.helpers
23
from django.db import models
24
from django.urls import reverse
25
from django.utils.functional import cached_property
26
from django.utils.translation import ugettext_lazy as _
27

  
28
from passerelle.base.models import BaseResource, HTTPResource
29
from passerelle.utils.api import endpoint
30

  
31

  
32
class SOAPConnector(BaseResource, HTTPResource):
33
    wsdl_url = models.URLField(
34
        max_length=400, verbose_name=_('WSDL URL'), help_text=_('URL of the WSDL file')
35
    )
36
    zeep_strict = models.BooleanField(default=True, verbose_name=_('Be strict with returned XML'))
37
    zeep_xsd_ignore_sequence_order = models.BooleanField(
38
        default=False, verbose_name=_('Ignore sequence order')
39
    )
40
    category = _('Business Process Connectors')
41

  
42
    class Meta:
43
        verbose_name = _('SOAP connector')
44

  
45
    @cached_property
46
    def client(self):
47
        return self.soap_client(
48
            wsdl_url=self.wsdl_url,
49
            settings=zeep.Settings(
50
                strict=self.zeep_strict, xsd_ignore_sequence_order=self.zeep_xsd_ignore_sequence_order
51
            ),
52
        )
53

  
54
    @endpoint(
55
        methods=['get', 'post'],
56
        perm='can_access',
57
        name='method',
58
        pattern=r'^(?P<method_name>\w+)/$',
59
        example_pattern='method_name/',
60
        description=_('Call a SOAP method'),
61
    )
62
    def method(self, request, method_name, **kwargs):
63
        def jsonify(data):
64
            if isinstance(data, (dict, collections.OrderedDict)):
65
                # ignore _raw_elements, zeep put there nodes not maching the
66
                # XSD when strict parsing is disabled.
67
                return {
68
                    jsonify(k): jsonify(v)
69
                    for k, v in data.items()
70
                    if (self.zeep_strict or k != '_raw_elements')
71
                }
72
            elif isinstance(data, (list, tuple, collections.deque)):
73
                return [jsonify(item) for item in data]
74
            else:
75
                return data
76

  
77
        soap_response = getattr(self.client.service, method_name)(**kwargs)
78
        serialized = zeep.helpers.serialize_object(soap_response)
79
        json_response = jsonify(serialized)
80
        return json_response
81

  
82
    @property
83
    def methods(self):
84
        def generator():
85
            for name in dir(self.client.service):
86
                if name.startswith('_'):
87
                    continue
88
                url = (
89
                    reverse(
90
                        'generic-endpoint',
91
                        kwargs={'connector': 'soap', 'endpoint': 'method', 'slug': self.slug},
92
                    )
93
                    + '/'
94
                    + name
95
                    + '/'
96
                )
97
                yield name, url
98

  
99
        return list(generator())
100

  
101
    @property
102
    def wsdl_dump(self):
103
        # get each operation signature
104
        dump = io.StringIO()
105

  
106
        def p(*args, **kwargs):
107
            print(*args, **kwargs, file=dump)
108

  
109
        for service in self.client.wsdl.services.values():
110
            p("service:", service.name)
111
            for port in service.ports.values():
112
                operations = sorted(port.binding._operations.values(), key=operator.attrgetter('name'))
113

  
114
                for operation in operations:
115
                    p("  method :", operation.name)
116
                    p("    input :", operation.input.signature())
117
                    p()
118
            p()
119
        return dump.getvalue()
passerelle/apps/soap/templates/soap/soapconnector_detail.html
1
{% extends "passerelle/manage/service_view.html" %}
2
{% load i18n passerelle %}
3

  
4
{% block endpoints %}
5
{{ block.super }}
6
<h4>{% trans "Available methods" %}</h4>
7
<ul class="endpoints">
8
    {% for name, url in object.methods %}
9
    <li class="get-method">
10
        <a href="{{ url }}">{{ site_base_uri }}{{ url }}</a>
11
    </li>
12
    {% endfor %}
13
</ul>
14
{% endblock %}
15

  
16
{% block extra-sections %}
17
<div id="soap-details" class="section">
18
  <h3>{% trans "WSDL dump" %}</h3>
19
  <pre>
20
  {{ object.wsdl_dump }}
21
  </pre>
22
</div>
23
{% endblock %}
passerelle/settings.py
161 161
    'passerelle.apps.photon',
162 162
    'passerelle.apps.plone_restapi',
163 163
    'passerelle.apps.sector',
164
    'passerelle.apps.soap',
164 165
    'passerelle.apps.solis',
165 166
    'passerelle.apps.twilio',
166 167
    'passerelle.apps.vivaticket',
167
-