Projet

Général

Profil

0001-cmis-add-a-jsonschema-for-uploadfile-endpoint-39193.patch

Lauréline Guérin, 03 février 2020 15:57

Télécharger (14,4 ko)

Voir les différences:

Subject: [PATCH] cmis: add a jsonschema for uploadfile endpoint (#39193)

 passerelle/apps/cmis/models.py                | 90 ++++++++++---------
 .../templates/cmis/cmisconnector_detail.html  | 29 ------
 tests/test_cmis.py                            | 60 +++++++------
 3 files changed, 78 insertions(+), 101 deletions(-)
 delete mode 100644 passerelle/apps/cmis/templates/cmis/cmisconnector_detail.html
passerelle/apps/cmis/models.py
27 27
from cmislib.exceptions import UpdateConflictException
28 28
from django.db import models
29 29
from django.utils.translation import ugettext_lazy as _
30
from django.utils.six import StringIO, text_type
30
from django.utils.six import StringIO
31 31
from django.utils.six.moves.urllib import error as urllib2
32 32

  
33 33
from passerelle.base.models import BaseResource
34
from passerelle.compat import json_loads
35 34
from passerelle.utils.api import endpoint
36 35
from passerelle.utils.jsonresponse import APIError
37 36

  
38 37

  
39 38
SPECIAL_CHARS = '!#$%&+-^_`~;[]{}+=~'
40
FILE_PATH_PATTERN = '^(/|(/[\w%s]+)+)$' % re.escape(SPECIAL_CHARS)
41
FILE_NAME_PATTERN = '[\w%s\.]+' % re.escape(SPECIAL_CHARS)
42
RE_FILE_PATH = re.compile(FILE_PATH_PATTERN)
43
RE_FILE_NAME = re.compile(FILE_NAME_PATTERN)
39
FILE_PATH_PATTERN = r'^(/|(/[\w%s]+)+)$' % re.escape(SPECIAL_CHARS)
40
FILE_NAME_PATTERN = r'[\w%s\.]+$' % re.escape(SPECIAL_CHARS)
41

  
42

  
43
UPLOAD_SCHEMA = {
44
    'type': 'object',
45
    'properties': {
46
        'file': {
47
            'type': 'object',
48
            'properties': {
49
                'filename': {
50
                    'type': 'string',
51
                    'pattern': FILE_NAME_PATTERN,
52
                },
53
                'content': {'type': 'string'},
54
                'content_type': {'type': 'string'},
55
            },
56
            'required': ['content']
57
        },
58
        'filename': {
59
            'type': 'string',
60
            'pattern': FILE_NAME_PATTERN,
61
        },
62
        'path': {
63
            'type': 'string',
64
            'pattern': FILE_PATH_PATTERN,
65
        },
66
    },
67
    'required': ['file', 'path']
68
}
44 69

  
45 70

  
46 71
class CmisConnector(BaseResource):
......
58 83
        fields = super(CmisConnector, self).get_description_fields()
59 84
        return [(x[0], x[1]) for x in fields if x[0].name != 'password']
60 85

  
61
    @endpoint(methods=['post'], perm='can_access')
62
    def uploadfile(self, request):
63
        error, error_msg, data = self._validate_inputs(request.body)
86
    @endpoint(
87
        perm='can_access',
88
        post={
89
            'request_body': {
90
                'schema': {
91
                    'application/json': UPLOAD_SCHEMA,
92
                }
93
            }
94
        })
95
    def uploadfile(self, request, post_data):
96
        error, error_msg, data = self._validate_inputs(post_data)
64 97
        if error:
65 98
            self.logger.debug("received invalid data: %s" % error_msg)
66 99
            raise APIError(error_msg, http_status=400)
......
73 106
            data['file_byte_content'])
74 107
        return {'data': {'properties': doc.properties}}
75 108

  
76
    def _validate_inputs(self, body):
77
        """ process JSON body
109
    def _validate_inputs(self, data):
110
        """ process dict
78 111
        return a tuple (error, error_msg, data)
79 112
        """
80
        try:
81
            data = json_loads(body)
82
        except ValueError as e:
83
            return True, "could not decode body to json: %s" % e, None
84
        if 'file' not in data:
85
            return True, '"file" is required', None
86
        if 'path' not in data:
87
            return True, '"path" is required', None
88
        if not isinstance(data['file'], dict):
89
            return True, '"file" must be a dict', None
90
        if not isinstance(data['path'], text_type):
91
            return True, '"path" must be string', None
92
        if not RE_FILE_PATH.match(data['path']):
93
            return True, '"path" must be valid path', None
94

  
95 113
        file_ = data['file']
96
        if 'filename' in data:
97
            if not isinstance(data['filename'], text_type):
98
                return True, '"filename" must be string', None
99
            if not RE_FILE_NAME.match(data['filename']):
100
                return True, '"filename" must be valid file name', None
101
        else:
102
            if 'filename' not in file_:
103
                return True, '"file[\'filename\']" is required', None
104
            if not isinstance(file_['filename'], text_type):
105
                return True, '"file[\'filename\']" must be string', None
106
            if not RE_FILE_NAME.match(file_['filename']):
107
                return True, '"file[\'filename\']" must be valid file name', None
108

  
109
        if 'content' not in file_:
110
            return True, '"file[\'content\']" is required', None
111
        if not isinstance(file_['content'], text_type):
112
            return True, '"file[\'content\']" must be string', None
114
        if 'filename' not in file_ and 'filename' not in data:
115
            return True, '"filename" or "file[\'filename\']" is required', None
116

  
113 117
        try:
114 118
            data['file_byte_content'] = base64.b64decode(file_['content'])
115 119
        except (TypeError, binascii.Error):
passerelle/apps/cmis/templates/cmis/cmisconnector_detail.html
1
{% extends "passerelle/manage/service_view.html" %}
2
{% load i18n passerelle %}
3

  
4
{% block endpoints %}
5
<ul>
6
  <li>
7
    <h4>{% trans 'Upload file' %}</h4>
8
    {% url "generic-endpoint" connector="cmis" slug=object.slug endpoint="uploadfile" as uploadfile %}
9
    <p> <strong>POST</strong> <a href="{{uploadfile}}">{{uploadfile}}</a></p>
10
    <pre>
11
      data_send = {
12
      'path': '/a/path',
13
      'file': {
14
        'filename': 'test.txt',
15
        'content': 'ZmlsZSBjb250ZW50',
16
        'content_type': 'image/jpeg'
17
       }
18
    }
19
    </pre>
20
  </li>
21
</ul>
22
{% endblock %}
23

  
24
{% block security %}
25
<p>
26
{% trans 'Upload is limited to the following API users:' %}
27
</p>
28
{% access_rights_table resource=object permission='can_access' %}
29
{% endblock %}
tests/test_cmis.py
1 1
import base64
2 2
import httplib2
3
import re
3 4

  
4 5
from cmislib import CmisClient
5 6
from cmislib.exceptions import CmisException
......
60 61
    assert result_file.exists()
61 62
    with result_file.open('rb'):
62 63
        assert result_file.read() == file_content
63
    json_result = response.json_body
64
    json_result = response.json
64 65
    assert json_result['err'] == 0
65 66
    assert json_result['data']['properties'] == {"toto": "tata"}
66 67

  
......
76 77
    assert result_file.exists()
77 78
    with result_file.open('rb'):
78 79
        assert result_file.read() == file_content
79
    json_result = response.json_body
80
    json_result = response.json
80 81
    assert json_result['err'] == 0
81 82
    assert json_result['data']['properties'] == {"toto": "tata"}
82 83

  
......
89 90
                         "content_type": "image/jpeg"}},
90 91
        expect_errors=True)
91 92
    assert response.status_code == 400
92
    assert response.json_body['err'] == 1
93
    assert response.json_body['err_desc'].startswith('"file[\'filename\']" is required')
93
    assert response.json['err'] == 1
94
    assert response.json['err_desc'].startswith('"filename" or "file[\'filename\']" is required')
94 95

  
95 96

  
96 97
def test_uploadfile_error_if_non_string_file_name(app, setup):
......
101 102
                         "content_type": "image/jpeg"}},
102 103
        expect_errors=True)
103 104
    assert response.status_code == 400
104
    assert response.json_body['err'] == 1
105
    assert response.json_body['err_desc'].startswith('"file[\'filename\']" must be string')
105
    assert response.json['err'] == 1
106
    assert response.json['err_desc'] == "1 is not of type 'string'"
106 107

  
107 108
    response = app.post_json(
108 109
        '/cmis/slug-cmis/uploadfile',
......
112 113
                "filename": 1},
113 114
        expect_errors=True)
114 115
    assert response.status_code == 400
115
    assert response.json_body['err'] == 1
116
    assert response.json_body['err_desc'].startswith('"filename" must be string')
116
    assert response.json['err'] == 1
117
    assert response.json['err_desc'] == "1 is not of type 'string'"
117 118

  
118 119

  
119 120
def test_uploadfile_error_if_non_valid_file_name(app, setup):
......
124 125
                         "content_type": "image/jpeg"}},
125 126
        expect_errors=True)
126 127
    assert response.status_code == 400
127
    assert response.json_body['err'] == 1
128
    assert response.json_body['err_desc'].startswith('"file[\'filename\']" must be valid file name')
128
    assert response.json['err'] == 1
129
    assert "',.,' does not match " in response.json['err_desc']
129 130

  
130 131
    response = app.post_json(
131 132
        '/cmis/slug-cmis/uploadfile',
......
135 136
                "filename": ",.,"},
136 137
        expect_errors=True)
137 138
    assert response.status_code == 400
138
    assert response.json_body['err'] == 1
139
    assert response.json_body['err_desc'].startswith('"filename" must be valid file name')
139
    assert response.json['err'] == 1
140
    assert "',.,' does not match " in response.json['err_desc']
140 141

  
141 142

  
142 143
def test_uploadfile_error_if_no_path(app, setup):
......
146 147
                         "content_type": "image/jpeg"}},
147 148
        expect_errors=True)
148 149
    assert response.status_code == 400
149
    assert response.json_body['err'] == 1
150
    assert response.json_body['err_desc'].startswith('"path" is required')
150
    assert response.json['err'] == 1
151
    assert response.json['err_desc'] == "'path' is a required property"
151 152

  
152 153

  
153 154
def test_uploadfile_error_if_non_string_path(app, setup):
......
158 159
                         "content_type": "image/jpeg"}},
159 160
        expect_errors=True)
160 161
    assert response.status_code == 400
161
    assert response.json_body['err'] == 1
162
    assert response.json_body['err_desc'].startswith('"path" must be string')
162
    assert response.json['err'] == 1
163
    assert response.json['err_desc'] == "1 is not of type 'string'"
163 164

  
164 165

  
165 166
def test_uploadfile_error_if_no_regular_path(app, setup):
......
170 171
                         "content_type": "image/jpeg"}},
171 172
        expect_errors=True)
172 173
    assert response.status_code == 400
173
    assert response.json_body['err'] == 1
174
    assert response.json_body['err_desc'].startswith('"path" must be valid path')
174
    assert response.json['err'] == 1
175
    assert "'no/leading/slash' does not match " in response.json['err_desc']
175 176

  
176 177

  
177 178
def test_uploadfile_error_if_no_file_content(app, setup):
......
181 182
                "file": {"filename": 'somefile.txt', "content_type": "image/jpeg"}},
182 183
        expect_errors=True)
183 184
    assert response.status_code == 400
184
    assert response.json_body['err'] == 1
185
    assert response.json_body['err_desc'].startswith('"file[\'content\']" is required')
185
    assert response.json['err'] == 1
186
    assert response.json['err_desc'] == "'content' is a required property"
186 187

  
187 188

  
188 189
def test_uploadfile_error_if_non_string_file_content(app, setup):
......
192 193
                "file": {"filename": 'somefile.txt', "content": 1, "content_type": "image/jpeg"}},
193 194
        expect_errors=True)
194 195
    assert response.status_code == 400
195
    assert response.json_body['err'] == 1
196
    assert response.json_body['err_desc'].startswith('"file[\'content\']" must be string')
196
    assert response.json['err'] == 1
197
    assert response.json['err_desc'] == "1 is not of type 'string'"
197 198

  
198 199

  
199 200
def test_uploadfile_error_if_no_proper_base64_encoding(app, setup):
......
203 204
                "file": {"filename": 'somefile.txt', "content": "1", "content_type": "image/jpeg"}},
204 205
        expect_errors=True)
205 206
    assert response.status_code == 400
206
    assert response.json_body['err'] == 1
207
    assert response.json_body['err_desc'].startswith(
208
        '"file[\'content\']" must be a valid base64 string')
207
    assert response.json['err'] == 1
208
    assert response.json['err_desc'].startswith('"file[\'content\']" must be a valid base64 string')
209 209

  
210 210

  
211 211
def test_uploadfile_cmis_gateway_error(app, setup, monkeypatch):
......
220 220
        params={"path": "/some/folder/structure",
221 221
                "file": {"filename": "file_name", "content": b64encode('aaaa'),
222 222
                         "content_type": "image/jpeg"}})
223
    assert response.json_body['err'] == 1
224
    assert response.json_body['err_desc'].startswith("some error")
223
    assert response.json['err'] == 1
224
    assert response.json['err_desc'].startswith("some error")
225 225

  
226 226

  
227 227
def test_get_or_create_folder_already_existing(monkeypatch):
......
331 331

  
332 332

  
333 333
def test_re_file_path():
334
    from passerelle.apps.cmis.models import RE_FILE_PATH
334
    from passerelle.apps.cmis.models import FILE_PATH_PATTERN
335
    RE_FILE_PATH = re.compile(FILE_PATH_PATTERN)
335 336
    assert RE_FILE_PATH.match('/')
336 337
    assert RE_FILE_PATH.match('/some')
337 338
    assert RE_FILE_PATH.match('/some/path')
......
346 347

  
347 348

  
348 349
def test_re_file_name():
349
    from passerelle.apps.cmis.models import RE_FILE_NAME
350
    from passerelle.apps.cmis.models import FILE_NAME_PATTERN
351
    RE_FILE_NAME = re.compile(FILE_NAME_PATTERN)
350 352
    assert RE_FILE_NAME.match('toto.tata')
351 353
    assert RE_FILE_NAME.match('TOTO.TATA')
352
-