passerelle/passerelle/apps/api_entreprise/models.py

313 lines
12 KiB
Python

# passerelle - uniform access to multiple data sources and services
# Copyright (C) 2019 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 <http://www.gnu.org/licenses/>.
'''Gateway to API-Entreprise web-service from SGMAP:
https://entreprise.api.gouv.fr
'''
from six.moves.urllib_parse import urljoin
import requests
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils.timezone import datetime, make_aware, timedelta
from django.http import HttpResponse, Http404, HttpResponseBadRequest
from django.core import signing
from django.core.urlresolvers import reverse
from passerelle.base.models import BaseResource
from passerelle.utils.api import endpoint
from passerelle.utils.jsonresponse import APIError
from passerelle.views import WrongParameter
DOCUMENT_SIGNATURE_MAX_AGE = timedelta(days=7)
def normalize_dates(data):
timestamp_to_datetime = {}
for key in data:
if isinstance(data[key], dict):
normalize_dates(data[key])
if isinstance(data[key], list):
for item in data[key]:
normalize_dates(item)
if key.startswith('date') and not key.endswith('timestamp'):
if isinstance(data[key], int):
try:
data[key] = datetime.fromtimestamp(int(data[key])).date()
except (ValueError, TypeError):
pass
if key.endswith('timestamp'):
# timestamps can be integers or strings
# convert only if positive values
if int(data[key]) > 0:
try:
timestamp_to_datetime[key[:-len('timestamp')] + 'datetime'] = make_aware(datetime.fromtimestamp(int(data[key])))
except (ValueError, TypeError):
pass
# add converted timestamps to initial data
data.update(timestamp_to_datetime)
class APIEntreprise(BaseResource):
url = models.URLField(_('API URL'), max_length=256, default='https://entreprise.api.gouv.fr/v2/')
token = models.CharField(max_length=1024, verbose_name=_('API token'))
category = _('Business Process Connectors')
class Meta:
verbose_name = _('API Entreprise')
def get(self, path, **kwargs):
params = {'token': self.token}
for param in ('context', 'object', 'recipient'):
if not kwargs.get(param):
raise WrongParameter([param], [])
params[param] = kwargs[param]
url = urljoin(self.url, path)
try:
response = self.requests.get(url, data=params)
except requests.RequestException as e:
raise APIError(u'API-entreprise connection error: %s' %
response.status_code,
data={'error': unicode(e)})
try:
data = response.json()
except ValueError as e:
content = repr(response.content[:1000])
raise APIError(
u'API-entreprise returned non-JSON content with status %s: %s' %
(response.status_code, content),
data={'status_code': response.status_code,
'exception': unicode(e),
'content': content,
})
if response.status_code != 200:
if data.get('error') == 'not_found':
return {
'err': 1,
'err_desc': data.get('message', 'not-found'),
}
raise APIError(
u'API-entreprise returned a non 200 status %s: %s' %
(response.status_code, data),
data={
'status_code': response.status_code,
'content': data,
})
normalize_dates(data)
return {
'err': 0,
'data': data,
}
@endpoint(perm='can_access',
pattern='(?P<association_id>\w+)/$',
example_pattern='{association_id}/',
description=_('Get association\'s documents'),
parameters={
'association_id': {
'description': _('association\'s SIREN or WALDEC number'),
'example_value': '44317013900036',
},
'object': {
'Description': _('request object (form number, file identifier, ...)'),
'example_value': '42'
},
'context': {
'description': _('request context (MPS, APS, ... )'),
'example_value': 'APS'
},
'recipient': {
'description': _('request recipient: usually customer number'),
'example_value': '44317013900036'
}
}
)
def documents_associations(self, request, association_id, **kwargs):
data = []
resp = self.get('documents_associations/%s/' % association_id, **kwargs)
for item in resp['data'].get('documents', []):
# ignore documents with no type
if not item.get('type'):
continue
signature_elements = {'url': item['url'],
'context': kwargs['context'],
'object': kwargs['object'],
'recipient': kwargs['recipient']}
signature = signing.dumps(signature_elements)
document_url = request.build_absolute_uri(reverse('generic-endpoint', kwargs={'connector': self.get_connector_slug(),
'slug': self.slug,
'endpoint': 'document',
'rest': '%s/%s/' % (association_id, signature)}))
item['id'] = item['timestamp']
item['text'] = item['type']
item['url'] = document_url
data.append(item)
# sort data by date
data.sort(key=lambda i: i['id'])
return {'err': 0, 'data': data}
@endpoint(pattern='(?P<association_id>\w+)/(?P<document_id>[\:\w-]+)/$',
example_pattern='{association_id}/{document_id}/',
description=_('Get association\'s document'),
parameters={
'association_id': {
'description': _('association\'s SIREN or WALDEC number'),
'example_value': '44317013900036',
},
'document_id': {
'description': _('document id'),
'example_value': 'A1500660325',
},
'object': {
'Description': _('request object (form number, file identifier, ...)'),
'example_value': '42'
},
'context': {
'description': _('request context (MPS, APS, ... )'),
'example_value': 'APS'
},
'recipient': {
'description': _('request recipient: usually customer number'),
'example_value': '44317013900036'
}
}
)
def document(self, request, association_id, document_id, **kwargs):
try:
params = signing.loads(document_id, max_age=DOCUMENT_SIGNATURE_MAX_AGE)
except signing.BadSignature:
raise Http404('document not found')
response = self.requests.get(params['url'])
if response.ok:
return HttpResponse(response, content_type='application/pdf')
raise Http404('document not found')
@endpoint(perm='can_access',
pattern='(?P<siren>\w+)/$',
example_pattern='{siren}/',
description=_('Get firm\'s data from Infogreffe'),
parameters={
'siren': {
'description': _('firm\'s SIREN number'),
'example_value': '44317013900036',
},
'object': {
'Description': _('request object (form number, file identifier, ...)'),
'example_value': '42'
},
'context': {
'description': _('request context (MPS, APS, ... )'),
'example_value': 'APS'
},
'recipient': {
'description': _('request recipient: usually customer number'),
'example_value': '44317013900036'
}
}
)
def extraits_rcs(self, request, siren, **kwargs):
return self.get('extraits_rcs_infogreffe/%s/' % siren, **kwargs)
@endpoint(perm='can_access',
pattern='(?P<association_id>\w+)/$',
example_pattern='{association_id}/',
description=_('Get association\'s related informations'),
parameters={
'association_id': {
'description': _('association\'s SIRET or WALDEC number'),
'example_value': '44317013900036',
},
'object': {
'Description': _('request object (form number, file identifier, ...)'),
'example_value': '42'
},
'context': {
'description': _('request context (MPS, APS, ... )'),
'example_value': 'APS'
},
'recipient': {
'description': _('request recipient: usually customer number'),
'example_value': '44317013900036'
}
}
)
def associations(self, request, association_id, **kwargs):
return self.get('associations/%s/' % association_id, **kwargs)
@endpoint(perm='can_access',
pattern='(?P<siren>\w+)/$',
example_pattern='{siren}/',
description=_('Get firm\'s related informations'),
parameters={
'siren': {
'description': _('firm\'s SIREN number'),
'example_value': '44317013900036',
},
'object': {
'description': _('request object (form number, file identifier, ...)'),
'example_value': '42'
},
'context': {
'description': _('request context (MPS, APS, ... )'),
'example_value': 'APS'
},
'recipient': {
'description': _('request recipient: usually customer number'),
'example_value': '44317013900036'
}
}
)
def entreprises(self, request, siren, **kwargs):
return self.get('entreprises/%s/' % siren, **kwargs)
@endpoint(perm='can_access',
methods=['get'],
pattern='(?P<siret>\w+)/$',
example_pattern='{siret}/',
description_get=_('Get firms\'s related informations'),
parameters={
'siret': {
'description': _('firms\'s SIRET number'),
'example_value': '44317013900036',
},
'object': {
'description': _('request object (form number, file identifier, ...)'),
'example_value': '42'
},
'context': {
'description': _('request context (MPS, APS, ... )'),
'example_value': 'APS'
},
'recipient': {
'description': _('request recipient: usually customer number'),
'example_value': '44317013900036'
}
}
)
def etablissements(self, request, siret, **kwargs):
return self.get('etablissements/%s/' % siret, **kwargs)