commit 67bd9619b6a1fa21cf332e60bbc0de51d131c31d Author: Etienne Loupias Date: Thu Mar 8 11:50:35 2018 +0100 Version d'initialisation diff --git a/CartADS.egg-info/PKG-INFO b/CartADS.egg-info/PKG-INFO new file mode 100644 index 0000000..30f8845 --- /dev/null +++ b/CartADS.egg-info/PKG-INFO @@ -0,0 +1,10 @@ +Metadata-Version: 1.0 +Name: CartADS +Version: 0.0.0 +Summary: UNKNOWN +Home-page: http://example.net/ +Author: Grand Lyon +Author-email: toto@example.net +License: UNKNOWN +Description: UNKNOWN +Platform: UNKNOWN diff --git a/CartADS.egg-info/SOURCES.txt b/CartADS.egg-info/SOURCES.txt new file mode 100644 index 0000000..da32e77 --- /dev/null +++ b/CartADS.egg-info/SOURCES.txt @@ -0,0 +1,8 @@ +README +setup.py +CartADS.egg-info/PKG-INFO +CartADS.egg-info/SOURCES.txt +CartADS.egg-info/dependency_links.txt +CartADS.egg-info/top_level.txt +cartads/__init__.py +cartads/models.py \ No newline at end of file diff --git a/CartADS.egg-info/dependency_links.txt b/CartADS.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/CartADS.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/CartADS.egg-info/top_level.txt b/CartADS.egg-info/top_level.txt new file mode 100644 index 0000000..de78169 --- /dev/null +++ b/CartADS.egg-info/top_level.txt @@ -0,0 +1 @@ +cartads diff --git a/README b/README new file mode 100644 index 0000000..e69de29 diff --git a/cartads/__init__.py b/cartads/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cartads/__init__.pyc b/cartads/__init__.pyc new file mode 100644 index 0000000..db16a04 Binary files /dev/null and b/cartads/__init__.pyc differ diff --git a/cartads/formdata.py b/cartads/formdata.py new file mode 100644 index 0000000..5b22d4f --- /dev/null +++ b/cartads/formdata.py @@ -0,0 +1,81 @@ +# Copyright (C) 2016 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from datetime import datetime + + +def is_required(value): + if not value: + raise ValueError('is required') + return value + +def to_datetime(value): + if not value: + return + return datetime.strptime(value[:19], '%Y-%m-%dT%H:%M:%S') + +def default_to_now(value): + if not value: + return datetime.now() + return value + + +CREATION_SCHEMA = ( +) + +def list_schema_fields(schema): + for fieldname in schema: + yield fieldname[0] if isinstance(fieldname, tuple) else fieldname + +class FormData(object): + + def __init__(self, formdata, schema): + if not isinstance(formdata, dict): + raise ValueError('formdata must be a dict') + + if 'fields' in formdata and isinstance(formdata['fields'], dict): + values = formdata['fields'] + if 'extra' in formdata: + values.update(formdata['extra']) + else: + values = formdata + + # extract/create/validate fields according to schema + self.fields = {} + for fieldname in schema: + if isinstance(fieldname, tuple): + value = values.get(fieldname[0]) + for modifier in fieldname[1:]: + try: + value = modifier(value) + except ValueError as e: + raise ValueError('%s: %s' % (fieldname[0], e.message)) + fieldname = fieldname[0] + else: + value = values.get(fieldname) + if value is not None: + self.fields[fieldname] = value + + # extract attachments + self.attachments = [] + attachments = { + key: value + for key, value in values.items() + if isinstance(value, dict) and ('filename' in value and + 'content_type' in value and + 'content' in value) + } + for key in sorted(attachments.keys()): + self.attachments.append(attachments[key]) diff --git a/cartads/formdata.pyc b/cartads/formdata.pyc new file mode 100644 index 0000000..20cb50d Binary files /dev/null and b/cartads/formdata.pyc differ diff --git a/cartads/migrations/0001_initial.py b/cartads/migrations/0001_initial.py new file mode 100644 index 0000000..404619e --- /dev/null +++ b/cartads/migrations/0001_initial.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0007_auto_20180129_1355'), + ] + + operations = [ + migrations.CreateModel( + name='CartADS', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('title', models.CharField(max_length=50, verbose_name='Title')), + ('description', models.TextField(verbose_name='Description')), + ('slug', models.SlugField(unique=True)), + ('log_level', models.CharField(default=b'INFO', max_length=10, verbose_name='Log Level', choices=[(b'NOTSET', b'NOTSET'), (b'DEBUG', b'DEBUG'), (b'INFO', b'INFO'), (b'WARNING', b'WARNING'), (b'ERROR', b'ERROR'), (b'CRITICAL', b'CRITICAL'), (b'FATAL', b'FATAL')])), + ('users', models.ManyToManyField(to='base.ApiUser', blank=True)), + ], + options={ + 'verbose_name': 'Webservice CartADS', + }, + ), + ] diff --git a/cartads/migrations/0001_initial.pyc b/cartads/migrations/0001_initial.pyc new file mode 100644 index 0000000..10d03ff Binary files /dev/null and b/cartads/migrations/0001_initial.pyc differ diff --git a/cartads/migrations/0002_auto_20180129_1403.py b/cartads/migrations/0002_auto_20180129_1403.py new file mode 100644 index 0000000..eadf8e3 --- /dev/null +++ b/cartads/migrations/0002_auto_20180129_1403.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('cartads', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='cartads', + name='application', + field=models.CharField(default='CartADS', max_length=200, verbose_name='Application identifier'), + preserve_default=False, + ), + migrations.AddField( + model_name='cartads', + name='token_authorization', + field=models.CharField(default='', max_length=128, verbose_name='Token Authorization'), + preserve_default=False, + ), + migrations.AddField( + model_name='cartads', + name='token_url', + field=models.URLField(default='', max_length=256, verbose_name='Token URL'), + preserve_default=False, + ), + migrations.AddField( + model_name='cartads', + name='verify_cert', + field=models.BooleanField(default=True, verbose_name='Check HTTPS Certificate validity'), + ), + migrations.AddField( + model_name='cartads', + name='wsdl_url', + field=models.CharField(default='', max_length=256, verbose_name='WSDL URL'), + preserve_default=False, + ), + ] diff --git a/cartads/migrations/0002_auto_20180129_1403.pyc b/cartads/migrations/0002_auto_20180129_1403.pyc new file mode 100644 index 0000000..2528961 Binary files /dev/null and b/cartads/migrations/0002_auto_20180129_1403.pyc differ diff --git a/cartads/migrations/0003_auto_20180129_1405.py b/cartads/migrations/0003_auto_20180129_1405.py new file mode 100644 index 0000000..fcac7a6 --- /dev/null +++ b/cartads/migrations/0003_auto_20180129_1405.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('cartads', '0002_auto_20180129_1403'), + ] + + operations = [ + migrations.AlterModelOptions( + name='cartads', + options={'verbose_name': 'CartADS Webservices'}, + ), + ] diff --git a/cartads/migrations/0003_auto_20180129_1405.pyc b/cartads/migrations/0003_auto_20180129_1405.pyc new file mode 100644 index 0000000..25c11f3 Binary files /dev/null and b/cartads/migrations/0003_auto_20180129_1405.pyc differ diff --git a/cartads/migrations/0004_remove_cartads_application.py b/cartads/migrations/0004_remove_cartads_application.py new file mode 100644 index 0000000..59a2b16 --- /dev/null +++ b/cartads/migrations/0004_remove_cartads_application.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('cartads', '0003_auto_20180129_1405'), + ] + + operations = [ + migrations.RemoveField( + model_name='cartads', + name='application', + ), + ] diff --git a/cartads/migrations/0004_remove_cartads_application.pyc b/cartads/migrations/0004_remove_cartads_application.pyc new file mode 100644 index 0000000..1420122 Binary files /dev/null and b/cartads/migrations/0004_remove_cartads_application.pyc differ diff --git a/cartads/migrations/__init__.py b/cartads/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cartads/migrations/__init__.pyc b/cartads/migrations/__init__.pyc new file mode 100644 index 0000000..21f8f8e Binary files /dev/null and b/cartads/migrations/__init__.pyc differ diff --git a/cartads/models.py b/cartads/models.py new file mode 100644 index 0000000..0976749 --- /dev/null +++ b/cartads/models.py @@ -0,0 +1,299 @@ +# Copyright (C) 2016 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import base64 +import json +import datetime +import uuid + +from email import encoders +from email.mime.audio import MIMEAudio +from email.mime.base import MIMEBase +from email.mime.image import MIMEImage +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText + +from suds.client import Client +from suds.transport import Reply +from suds.transport.http import HttpAuthenticated +import suds.sudsobject + +from django.db import models +from django.utils.translation import ugettext_lazy as _ +from django.core.cache import cache + +from passerelle.base.models import BaseResource +from passerelle.utils.api import endpoint + +from .formdata import FormData, CREATION_SCHEMA, list_schema_fields + + +class ParameterTypeError(Exception): + http_status = 400 + log_error = False + + +def fill_sudsobject_with_dict(sudsobject, fields, prefix=None): + for key, value in sudsobject: + if prefix: + attr = '%s_%s' % (prefix, key) + else: + attr = key + if isinstance(value, suds.sudsobject.Object): + fill_sudsobject_with_dict(value, fields, attr) + else: + if attr in fields: + # sudsobject.foo.bar <- fields['foo_bar'] + setattr(sudsobject, key, fields[attr]) + +def sudsobject_to_dict(sudsobject): + out = {} + for key, value in suds.sudsobject.asdict(sudsobject).iteritems(): + if hasattr(value, '__keylist__'): + out[key] = sudsobject_to_dict(value) + elif isinstance(value, list): + out[key] = [] + for item in value: + if hasattr(item, '__keylist__'): + out[key].append(sudsobject_to_dict(item)) + else: + out[key].append(item) + else: + out[key] = value + return out + + +class CartADS(BaseResource): + token_url = models.URLField(_('Token URL'), max_length=256) + token_authorization = models.CharField(_('Token Authorization'), max_length=128) + wsdl_url = models.CharField(_('WSDL URL'), max_length=256) # not URLField, it can be file:// + verify_cert = models.BooleanField(default=True, + verbose_name=_('Check HTTPS Certificate validity')) + # TODO : a mettre en param du connecteur + dirname = 'D:\LSM\Gfi\DepotDossierFTP\client1\\' + + category = _('Business Process Connectors') + + class Meta: + verbose_name = _('CartADS Webservices') + + def get_token(self, renew=False): + cache_key = 'cartads-%s-token' % self.id + if not renew: + token = cache.get(cache_key) + if token: + return token + headers = {'Authorization': 'Basic %s' % self.token_authorization} + resp = self.requests.post(self.token_url, headers=headers, + data={'grant_type': 'client_credentials'}, + verify=self.verify_cert).json() + token = '%s %s' % (resp.get('token_type'), resp.get('access_token')) + timeout = int(resp.get('expires_in')) + cache.set(cache_key, token, timeout) + self.logger.debug('new token: %s (timeout %ss)', token, timeout) + return token + + def get_client(self, attachments=[]): + class Transport(HttpAuthenticated): + def __init__(self, instance, attachments): + self.instance = instance + self.attachments = attachments + HttpAuthenticated.__init__(self) + + def send(self, request): + request.message = request.message.replace("contentType", "xm:contentType") + + # a priori attachement inutile pour cartads, a voir si on supprime + if self.attachments: + # SOAP Attachement format + message = MIMEMultipart('related', type="text/xml", + start="") + xml = MIMEText(None, _subtype='xml', _charset='utf-8') + xml.add_header('Content-ID', '') + # do not base64-encode the soap message + xml.replace_header('Content-Transfer-Encoding', '8bit') + xml_payload = request.message + + # hack payload to include attachment filenames in + # SOAP-ENV:Header. + soap_headers = [] + for num, attachment in enumerate(self.attachments): + filename = attachment.get('filename') or 'file%s.bin' % num + if isinstance(filename, unicode): + filename = filename.encode('utf-8', 'ignore') + soap_headers.append('%s' % (num, filename, num)) + xml_payload = xml_payload.replace('', + '%s' % ''.join(soap_headers)) + xml.set_payload(xml_payload) + message.attach(xml) + + for num, attachment in enumerate(self.attachments): + filename = attachment.get('filename') or 'file%s.bin' % num + content = base64.b64decode(attachment.get('content') or '') + content_type = attachment.get('content_type') or 'application/octet-stream' + maintype, subtype = content_type.split('/', 1) + if maintype == 'text': + part = MIMEText(content, _subtype=subtype) + else: + part = MIMEBase(maintype, subtype, name=filename) + part.set_payload(content) + part.add_header('Content-Transfer-Encoding', 'binary') + encoders.encode_noop(part) + part.add_header('Content-Disposition', 'attachment', name=filename, filename=filename) + part.add_header('Content-ID', '<%s>' % filename) + message.attach(part) + + message._write_headers = lambda x: None + msg_x = message.as_string(unixfrom=False) + # RFC 2045 defines MIME multipart boundaries: + # * boundary := 0*69 bcharsnospace + # * dash-boundary := "--" boundary + # * delimiter := CRLF dash-boundary + # but Python doesn't use CRLF, will only use LF (on Unix systems + # at least). This is http://bugs.python.org/issue1349106 and has + # been fixed in Python 3.2. + # + # Manually hack message to put \r\n so that the message is + # correctly read by Apache Axis strict parser. + boundary = message.get_boundary() + request.message = message.as_string(unixfrom=False + ).replace(boundary + '\n', boundary + '\r\n' + ).replace('\n--' + boundary, '\r\n--' + boundary) + request.headers.update(dict(message._headers)) + request.headers['Authorization'] = self.instance.get_token() + resp = self.instance.requests.post(request.url, data=request.message, + headers=request.headers, + verify=self.instance.verify_cert) + if resp.status_code == 401: + # ask for a new token, and retry + request.headers['Authorization'] = self.instance.get_token(renew=True) + resp = self.instance.requests.post(request.url, data=request.message, + headers=request.headers, + verify=self.instance.verify_cert) + + return Reply(resp.status_code, resp.headers, resp.content) + + return Client(url=self.wsdl_url, transport=Transport(self, attachments)) + + + @endpoint(perm='can_access', methods=['get','post']) + def create(self, request): + + # get creation fields from payload + try: + formdata = FormData(json.loads(request.body), CREATION_SCHEMA) + except ValueError as e: + raise ParameterTypeError(e.message) + + if formdata.attachments: + print >> open('/home/grandlyon/log/cartads.debug', 'a+'), datetime.datetime.now(), "nb attach: ", len(formdata.attachments) + for num, attachment in enumerate(formdata.attachments): + filename = attachment.get('filename') or 'file%s.bin' % num + print >> open('/home/grandlyon/log/cartads.debug', 'a+'), datetime.datetime.now(), "filename: ", filename + #content = base64.b64decode(attachment.get('content') or '') + b64_fileContent = attachment.get('content') + #print >> open('/home/grandlyon/log/cartads.debug', 'a+'), datetime.datetime.now(), "b64_fileContent: ", b64_fileContent + + + # Pour l'instant pour test, on envoie le dernier fichier du formulaires + # TODO : construire le zip de tous les fichiers du form + + + # Nom du fichier zip a envoyer + fileName = str(uuid.uuid4()) + ".zip"; + + # test base64 + #b64_fileContent = "UEsDBBQAAAAAAMqEQUzI2HQpGgAAABoAAAAKAAAAdGVzdDAxLnR4dHRlc3QgYmFzZTY0IG1pbmkgdG90byB0YXRhUEsBAhQAFAAAAAAAyoRBTMjYdCkaAAAAGgAAAAoAAAAAAAAAAQAgAAAAAAAAAHRlc3QwMS50eHRQSwUGAAAAAAEAAQA4AAAAQgAAAAAA" + + # size_max doit etre un multiple de 4 pour avoir un nb de caracteres valide en base 64 (car 3 octets y sont encodes sur 4 caracteres) + # size_max choisi pour eviter erreur OpenSSL.SSL.WantWriteError due au openssl socket send buffer size (https://pyopenssl.org/en/stable/api/ssl.html) + # TODO: demander a EO si on peut l'augmenter, car en curl on peut envoyer plus de 7 Mo d'un coup d'apres nos tests + size_max = 65536 # 64 ko, d'apres test semble etre la limite par defaut du openssl socket send buffer size + + # TODO : mettre en parametre l'url du ws sendfile + for x in range(0, len(b64_fileContent)/size_max + 1): + resp = self.requests.post('https://api-rec.grandlyon.com/ads-sendfile-dev/sendfile.aspx', + data={'fileName': self.dirname+fileName, + 'b64_fileContent': b64_fileContent[x*size_max:(x+1)*size_max], + 'part': str(x), + 'total': int(len(b64_fileContent)/size_max)} + ) #.json() + + return {'data': resp.content, 'length': len(b64_fileContent)} + + + @classmethod + def creation_fields(cls): + '''used in cartads_detail.html template''' + return list_schema_fields(CREATION_SCHEMA) + + def get_token_cartads(self): + #TODO : a encoder d'apres les exemples php et c# fournispar GFI + return 'ieTluf0q6vwjitxope55YZ2ud0CEtuO9BBHr2hQaxySeDrz66mntHl83Wqj7oadMSyZqwSkzVdZJrQ92Zg2p3bwkAuv5yUzwmpBfdtAYYLE=' + + def get_type_compte_utilisateur(self): + #TODO : a encoder d'apres les exemples php et c# fournispar GFI + return 'ContactService' + + @endpoint(perm='can_access') + def get_communes(self, request): + resp = self.get_client().service.GetCommunes(self.get_token_cartads(), + self.get_type_compte_utilisateur() + ) + ''' + #TODO: Ca serait mieux mais ca marche pas... + resp = self.get_client().service.GetCommunes({ + 'token': self.get_token_cartads(), + 'typeCompteUtilisateur': self.get_type_compte_utilisateur(), + })''' + return {'data': sudsobject_to_dict(resp)} + + @endpoint(perm='can_access') + def get_objets_demande(self, request, type_dossier): + resp = self.get_client().service.GetObjetsDemande(self.get_token_cartads(), type_dossier) + + # TODO : parcourir la liste en json devrait etre plus simple qu'en suds, mais j'ai pas reussi pour l'instant + # On parcourt la liste pour la mettre sous la forme id, text, pour l'utiliser comme source de donnees dans le formulaire + out = {} + for key, value in suds.sudsobject.asdict(resp).iteritems(): + out[key] = [] + for item in value: + if hasattr(item, '__keylist__'): + out_item = {} + for key_item, value_item in suds.sudsobject.asdict(item).iteritems(): + out_item['id'] = item.Key + out_item['text'] = item.Value + out[key].append(out_item) + else: + item.id = item.Key + item.text = item.Value + out[key].append(item) + return {'data': out['KeyValueOfintstring']} + + @endpoint(perm='can_access') + def get_liste_pdf(self, request, type_dossier): + resp = self.get_client().service.GetListePdf(self.get_token_cartads(), + type_dossier, + self.get_type_compte_utilisateur() + ) + return {'data': sudsobject_to_dict(resp)} + + @endpoint(perm='can_access') + def get_pieces(self, request, type_dossier, objet_demande): + resp = self.get_client().service.GetPieces(self.get_token_cartads(), + type_dossier, + objet_demande + ) + return {'data': sudsobject_to_dict(resp)} diff --git a/cartads/models.pyc b/cartads/models.pyc new file mode 100644 index 0000000..7d6d88c Binary files /dev/null and b/cartads/models.pyc differ diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..6d92d7e --- /dev/null +++ b/setup.py @@ -0,0 +1,11 @@ +#! /usr/bin/env python + +from setuptools import setup, find_packages + +setup( + name='CartADS', + author='Grand Lyon', + author_email='toto@example.net', + url='http://example.net/', + packages=find_packages(), +)