diff --git a/passerelle/apps/esup_signature/models.py b/passerelle/apps/esup_signature/models.py index 2062f633..93009623 100644 --- a/passerelle/apps/esup_signature/models.py +++ b/passerelle/apps/esup_signature/models.py @@ -35,7 +35,7 @@ SIGN_REQUEST_SCHEMA = { 'title': '', 'description': '', 'type': 'object', - 'required': ['file', 'recipients_emails', 'eppn'], + 'required': ['file', 'recipients_emails', 'create_by_eppn'], 'unflatten': True, 'properties': collections.OrderedDict( { @@ -62,13 +62,120 @@ SIGN_REQUEST_SCHEMA = { 'description': 'Recipients emails', 'items': {'type': 'string'}, }, - 'eppn': {'type': 'string', 'description': 'EPPN of the sign request owner'}, + 'recipients_cc_emails': { + 'type': 'array', + 'description': 'Recipients CC emails', + 'items': {'type': 'string'}, + }, + 'all_sign_to_complete': { + 'type': 'string', + 'description': 'Every recipient has to sign', + 'enum': ['true', 'false'], + 'default': 'false', + }, + 'user_sign_first': { + 'type': 'string', + 'description': 'the author must sign first', + 'enum': ['true', 'false'], + 'default': 'false', + }, + 'pending': { + 'type': 'string', + 'description': 'Pending', + 'enum': ['true', 'false'], + 'default': 'true', + }, + 'force_all_sign': { + 'type': 'string', + 'description': 'Force signing on every document', + 'enum': ['true', 'false'], + 'default': 'false', + }, + 'comment': {'type': 'string', 'description': 'Comment'}, + 'sign_type': { + 'type': 'string', + 'description': 'Signature type', + 'enum': ['visa', 'pdfImageStamp', 'certSign', 'nexuSign'], + 'default': 'pdfImageStamp', + }, + 'create_by_eppn': {'type': 'string', 'description': 'EPPN of the sign request owner'}, 'title': {'type': 'string', 'description': 'Title'}, + 'target_url': { + 'type': 'string', + 'description': 'End location', + }, } ), } +SIGN_REQUEST_WITH_WORKFLOW_SCHEMA = { + '$schema': 'http://json-schema.org/draft-04/schema#', + 'title': '', + 'description': '', + 'type': 'object', + 'required': ['file', 'eppn', 'workflow_id'], + 'unflatten': True, + 'properties': collections.OrderedDict( + { + 'file': { + 'type': 'object', + 'description': 'File object', + 'required': ['filename', 'content_type', 'content'], + 'properties': { + 'filename': { + 'type': 'string', + }, + 'content_type': { + 'type': 'string', + 'description': 'MIME content-type', + }, + 'content': { + 'type': 'string', + 'description': 'Content, base64 encoded', + }, + }, + }, + 'recipients_emails': { + 'type': 'array', + 'description': 'Recipients emails at each step', + 'items': {'type': 'string'}, + }, + 'target_emails': { + 'type': 'array', + 'description': 'Target emails', + 'items': {'type': 'string'}, + }, + 'all_sign_to_completes': { + 'type': 'array', + 'description': 'Steps numbers were every recipient has to sign', + 'items': {'type': 'string'}, + }, + 'eppn': {'type': 'string', 'description': 'EPPN of the sign request owner'}, + 'workflow_id': {'type': 'string', 'description': 'Identifier of the workflow'}, + 'title': {'type': 'string', 'description': 'Title'}, + 'target_urls': { + 'type': 'array', + 'description': 'End locations', + 'items': {'type': 'string'}, + }, + 'signrequest_params_jsonstring': { + 'type': 'string', + 'description': 'Signature parameters', + }, + } + ), +} + + +def clean_list(some_list): + return [elem for elem in some_list if elem] + + +def to_bool(some_str): + return some_str == 'true' + + class EsupSignature(BaseResource, HTTPResource): base_url = models.URLField(_('API URL')) @@ -77,12 +184,12 @@ class EsupSignature(BaseResource, HTTPResource): class Meta: verbose_name = _('Esup Signature') - def _call(self, path, method='get', data=None, files=None, expect_json=True): + def _call(self, path, method='get', params=None, files=None, expect_json=True): url = urllib.parse.urljoin(self.base_url, path) kwargs = {} if method == 'post': - kwargs['data'] = data + kwargs['params'] = params kwargs['files'] = files try: @@ -125,8 +232,18 @@ class EsupSignature(BaseResource, HTTPResource): 'recipients_emails/0': 'xx@foo.com', 'recipients_emails/1': 'yy@foo.com', 'recipients_emails/2': 'zz@foo.com', - 'eppn': 'aa@foo.com', + 'recipients_cc_emails/0': 'xx@foo.com', + 'recipients_cc_emails/1': 'yy@foo.com', + 'recipients_cc_emails/2': 'zz@foo.com', + 'all_sign_to_complete': 'true', + 'user_sign_first': 'false', + 'pending': 'true', + 'force_all_sign': 'false', + 'comment': 'a comment', + 'sign_type': 'pdfImageStamp', + 'create_by_eppn': 'aa@foo.com', 'title': 'a title', + 'target_url': 'smb://foo.bar/location-1/', }, }, ) @@ -143,16 +260,92 @@ class EsupSignature(BaseResource, HTTPResource): ) } - recipients_emails = [email for email in post_data['recipients_emails'] if email] - data = { - 'signType': 'pdfImageStamp', - 'recipientsEmails': recipients_emails, - 'eppn': post_data['eppn'], + params = { + 'recipientsEmails': clean_list(post_data['recipients_emails']), + 'recipientsCCEmails': clean_list(post_data.get('recipients_cc_emails', [])), + 'comment': post_data.get('comment', ''), + 'signType': post_data.get('sign_type', 'pdfImageStamp'), + 'createByEppn': post_data['create_by_eppn'], 'title': post_data.get('title', ''), - 'pending': True, + 'targetUrl': post_data.get('target_url', ''), } - return {'data': self._call('ws/signrequests/new', method='post', data=data, files=files)} + bool_params = { + 'all_sign_to_complete': ('allSignToComplete', False), + 'user_sign_first': ('userSignFirst', False), + 'pending': ('pending', True), + 'force_all_sign': ('forceAllSign', False), + } + for key, value in bool_params.items(): + ext_param, default = value + params[ext_param] = default + if key in post_data: + params[ext_param] = to_bool(post_data[key]) + + return {'data': self._call('ws/signrequests/new', method='post', params=params, files=files)} + + @endpoint( + name='new-with-workflow', + description=_('Create a sign request'), + perm='can_access', + post={ + 'request_body': { + 'schema': { + 'application/json': SIGN_REQUEST_WITH_WORKFLOW_SCHEMA, + } + }, + 'input_example': { + 'file': { + 'filename': 'example-1.pdf', + 'content_type': 'application/pdf', + 'content': 'JVBERi0xL...(base64 PDF)...', + }, + 'workflow_id': '99', + 'eppn': 'aa@foo.com', + 'title': 'a title', + 'recipients_emails/0': '0*xx@foo.com', + 'recipients_emails/1': '0*yy@foo.com', + 'recipients_emails/2': '1*zz@foo.com', + 'all_sign_to_completes/0': '12', + 'all_sign_to_completes/1': '13', + 'target_emails/0': 'xx@foo.com', + 'target_emails/1': 'yy@foo.com', + 'target_emails/2': 'zz@foo.com', + 'signrequest_params_jsonstring': 'List [ OrderedMap { "xPos": 100, "yPos": 100, "signPageNumber": 1 }, ' + 'OrderedMap { "xPos": 200, "yPos": 200, "signPageNumber": 1 } ]', + 'target_urls/0': 'smb://foo.bar/location-1/', + 'target_urls/1': 'smb://foo.bar/location-2/', + }, + }, + ) + def new_with_workflow(self, request, post_data): + try: + file_bytes = io.BytesIO(base64.b64decode(post_data['file']['content'])) + except (TypeError, binascii.Error): + raise APIError("Can't decode file") + files = { + 'multipartFiles': ( + post_data['file']['filename'], + file_bytes, + post_data['file']['content_type'], + ) + } + + params = { + 'createByEppn': post_data['eppn'], + 'title': post_data.get('title', ''), + 'recipientsEmails': clean_list(post_data.get('recipients_emails', [])), + 'allSignToCompletes': clean_list(post_data.get('all_sign_to_completes', [])), + 'targetEmails': clean_list(post_data.get('target_emails', [])), + 'signRequestParamsJsonString': post_data.get('signrequest_params_jsonstring', ''), + 'targetUrls': clean_list(post_data.get('target_urls', [])), + } + + return { + 'data': self._call( + '/ws/workflows/%s/new' % post_data['workflow_id'], method='post', params=params, files=files + ) + } @endpoint( methods=['get'], diff --git a/tests/test_esup_signature.py b/tests/test_esup_signature.py index 5ba1ad54..9d3dba81 100644 --- a/tests/test_esup_signature.py +++ b/tests/test_esup_signature.py @@ -31,11 +31,26 @@ def test_new(app, connector): }, 'recipients_emails/0': 'foo@invalid', 'recipients_emails/1': 'bar@invalid', - 'eppn': 'baz@invalid', + 'create_by_eppn': 'baz@invalid', 'title': 'a title', } with responses.RequestsMock() as rsps: - rsps.post('https://esup-signature.invalid/ws/signrequests/new', status=200, json=9) + query_params = { + 'recipientsEmails': ['foo@invalid', 'bar@invalid'], + 'createByEppn': 'baz@invalid', + 'title': 'a title', + 'signType': 'pdfImageStamp', + 'pending': True, + 'allSignToComplete': False, + 'userSignFirst': False, + 'forceAllSign': False, + } + rsps.post( + 'https://esup-signature.invalid/ws/signrequests/new', + match=[responses.matchers.query_param_matcher(query_params)], + status=200, + json=9, + ) resp = app.post_json('/esup-signature/esup-signature/new', params=params) assert len(rsps.calls) == 1 assert rsps.calls[0].request.headers['Content-Type'].startswith('multipart/form-data') @@ -44,6 +59,54 @@ def test_new(app, connector): assert json_resp['data'] == 9 +def test_new_with_workflow(app, connector): + params = { + 'file': { + 'filename': 'bla', + 'content': base64.b64encode(b'who what').decode(), + 'content_type': 'text/plain', + }, + 'workflow_id': '99', + 'eppn': 'aa@foo.com', + 'title': 'a title', + 'recipients_emails/0': '0*xx@foo.com', + 'recipients_emails/1': '0*yy@foo.com', + 'recipients_emails/2': '1*zz@foo.com', + 'all_sign_to_completes/0': '12', + 'all_sign_to_completes/1': '13', + 'target_emails/0': 'xx@foo.com', + 'target_emails/1': 'yy@foo.com', + 'target_emails/2': 'zz@foo.com', + 'signrequest_params_jsonstring': 'List [ OrderedMap { "xPos": 100, "yPos": 100, "signPageNumber": 1 }, ' + 'OrderedMap { "xPos": 200, "yPos": 200, "signPageNumber": 1 } ]', + 'target_urls/0': 'smb://foo.bar/location-1/', + 'target_urls/1': 'smb://foo.bar/location-2/', + } + with responses.RequestsMock() as rsps: + query_params = { + 'createByEppn': 'aa@foo.com', + 'title': 'a title', + 'recipientsEmails': ['0*xx@foo.com', '0*yy@foo.com', '1*zz@foo.com'], + 'allSignToCompletes': ['12', '13'], + 'targetEmails': ['xx@foo.com', 'yy@foo.com', 'zz@foo.com'], + 'signRequestParamsJsonString': 'List [ OrderedMap { "xPos": 100, "yPos": 100, "signPageNumber": 1 }, ' + 'OrderedMap { "xPos": 200, "yPos": 200, "signPageNumber": 1 } ]', + 'targetUrls': ['smb://foo.bar/location-1/', 'smb://foo.bar/location-2/'], + } + rsps.post( + 'https://esup-signature.invalid/ws/workflows/99/new', + match=[responses.matchers.query_param_matcher(query_params)], + status=200, + json=9, + ) + resp = app.post_json('/esup-signature/esup-signature/new-with-workflow', params=params) + assert len(rsps.calls) == 1 + assert rsps.calls[0].request.headers['Content-Type'].startswith('multipart/form-data') + json_resp = resp.json + assert json_resp['err'] == 0 + assert json_resp['data'] == 9 + + def test_status(app, connector): with responses.RequestsMock() as rsps: rsps.get('https://esup-signature.invalid/ws/signrequests/1', status=200, json={'status': 'completed'})