Projet

Général

Profil

0001-cmis-add-cmis-connector-to-upload-file-12876.patch

Jean-Baptiste Jaillet, 29 mai 2017 11:03

Télécharger (12,1 ko)

Voir les différences:

Subject: [PATCH] cmis: add cmis connector to upload file (#12876)

 debian/control                                     |   1 +
 passerelle/apps/cmis/__init__.py                   |   0
 passerelle/apps/cmis/migrations/0001_initial.py    |  31 ++++++
 passerelle/apps/cmis/migrations/__init__.py        |   0
 passerelle/apps/cmis/models.py                     | 101 ++++++++++++++++++++
 .../cmis/templates/cmis/cmisconnector_detail.html  |   9 ++
 passerelle/settings.py                             |   1 +
 tests/test_cmis.py                                 | 105 +++++++++++++++++++++
 8 files changed, 248 insertions(+)
 create mode 100644 passerelle/apps/cmis/__init__.py
 create mode 100644 passerelle/apps/cmis/migrations/0001_initial.py
 create mode 100644 passerelle/apps/cmis/migrations/__init__.py
 create mode 100644 passerelle/apps/cmis/models.py
 create mode 100644 passerelle/apps/cmis/templates/cmis/cmisconnector_detail.html
 create mode 100644 tests/test_cmis.py
debian/control
22 22
    python-django-jsonfield,
23 23
    python-magic,
24 24
    python-suds,
25
    python-cmislib,
25 26
Recommends: python-soappy, python-phpserialize
26 27
Suggests: python-sqlalchemy, python-mako
27 28
Description: Uniform access to multiple data sources and services (Python module)
passerelle/apps/cmis/migrations/0001_initial.py
1
# -*- coding: utf-8 -*-
2
from __future__ import unicode_literals
3

  
4
from django.db import models, migrations
5

  
6

  
7
class Migration(migrations.Migration):
8

  
9
    dependencies = [
10
        ('base', '0005_resourcelog'),
11
    ]
12

  
13
    operations = [
14
        migrations.CreateModel(
15
            name='CmisConnector',
16
            fields=[
17
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
18
                ('title', models.CharField(max_length=50)),
19
                ('slug', models.SlugField()),
20
                ('description', models.TextField()),
21
                ('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')])),
22
                ('cmis_endpoint', models.URLField(help_text='URL of the CMIS endpoint', max_length=400, verbose_name='CMIS endpoint')),
23
                ('username', models.CharField(max_length=128, verbose_name='Service username')),
24
                ('password', models.CharField(max_length=128, verbose_name='Password')),
25
                ('users', models.ManyToManyField(to='base.ApiUser', blank=True)),
26
            ],
27
            options={
28
                'verbose_name': 'CMIS connector',
29
            },
30
        ),
31
    ]
passerelle/apps/cmis/models.py
1
# passerelle.apps.cmis
2
# Copyright (C) 2016  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
from cmislib import CmisClient
18
from cmislib.model import (PermissionDeniedException, ObjectNotFoundException,
19
                           UpdateConflictException)
20
import json
21
import base64
22
import logging
23

  
24
from django.db import models
25
from django.utils.translation import ugettext_lazy as _
26

  
27
from passerelle.base.models import BaseResource
28
from passerelle.utils.api import endpoint
29
from passerelle.utils.jsonresponse import APIError
30

  
31

  
32
class CMISError(APIError):
33
    pass
34

  
35

  
36
class CmisConnector(BaseResource):
37
    cmis_endpoint = models.URLField(max_length=400, verbose_name=_('CMIS endpoint'),
38
                                     help_text=_('URL of the CMIS endpoint'))
39
    username = models.CharField(max_length=128, verbose_name=_('Service username'))
40
    password = models.CharField(max_length=128, verbose_name=_('Password'))
41

  
42
    category = _('Business Process Connectors')
43

  
44
    class Meta:
45
        verbose_name = _('CMIS connector')
46

  
47

  
48
    def get_description_fields(self):
49
        fields = super(CmisConnector, self).get_description_fields()
50
        return [(x[0], x[1]) for x in fields if x[0].name != 'password']
51

  
52
    @endpoint(serializer_type='json-api', methods=['post'], perm='can_upload_file')
53
    def upload_file(self, request, **kwargs):
54
        try:
55
            data = json.loads(request.body)
56
        except ValueError:
57
            raise CMISError('invalid JSON string sent.')
58

  
59
        if not set(data.keys()) >= set(['filename', 'path', 'content', 'contentType']):
60
            raise CMISError('missing keys %s' % (set(['filename', 'path', 'content', 'contentType']) -
61
                                                     set(data.keys())))
62
        title = data['filename']
63
        path = data['path'].encode('utf-8')
64
        if not path:
65
            path = '/'
66
        try:
67
            content = base64.b64decode(data['content'])
68
        except (ValueError, TypeError) as e:
69
            raise CMISError('content must be a base64 string')
70

  
71
        content_type = data['contentType']
72

  
73
        repo_name = data.get('repository_name', '')
74
        repo = self.cmis_connection_repository(repo_name)
75
        try:
76
            folder = repo.getObjectByPath(path)
77
        except ObjectNotFoundException:
78
            raise CMISError('path not found on platform.')
79

  
80
        try:
81
            doc = folder.createDocumentFromString(title, contentString=content, contentType=content_type)
82
        except UpdateConflictException:
83
            raise CMISError('the document already exists on platform.')
84

  
85
        return doc.properties
86

  
87
    def cmis_connection_repository(self, repo_name):
88
        try:
89
            cmis_client = CmisClient(self.cmis_endpoint, self.username, self.password)
90
        except PermissionDeniedException:
91
            raise CMISError('wrong username or password to connect to platform.')
92
        except ObjectNotFoundException:
93
            raise CMISError('platform endpoint not found.')
94

  
95
        if repo_name:
96
            for repo in cmis_client.getRepositories():
97
                if repo_name == repo['repositoryName']:
98
                    return cmis_client.getRepository(repo['repositoryId'])
99
            raise CMISError('repository %s not found' % repo_name)
100
        else:
101
            return cmis_client.defaultRepository
passerelle/apps/cmis/templates/cmis/cmisconnector_detail.html
1
{% extends "passerelle/manage/service_view.html" %}
2
{% load i18n passerelle %}
3

  
4
{% block security %}
5
<p>
6
{% trans 'Upload is limited to the following API users:' %}
7
</p>
8
{% access_rights_table resource=object permission='can_upload_file' %}
9
{% endblock %}
passerelle/settings.py
113 113
    'passerelle.apps.opengis',
114 114
    'passerelle.apps.airquality',
115 115
    'passerelle.apps.okina',
116
    'passerelle.apps.cmis',
116 117
    # backoffice templates and static
117 118
    'gadjo',
118 119
)
tests/test_cmis.py
1
import pytest
2
import mock
3
import base64
4
import json
5

  
6
from django.contrib.contenttypes.models import ContentType
7
from django.core.urlresolvers import reverse
8

  
9
from passerelle.base.models import ApiUser, AccessRight
10
from passerelle.apps.cmis.models import CmisConnector
11

  
12
@pytest.fixture()
13
def setup(db):
14
    api = ApiUser.objects.create(username='all', keytype='', key='')
15

  
16
    conn = CmisConnector.objects.create(cmis_endpoint='http://example.com/cmisatom', username='admin',
17
                                        password='admin', slug='slug-cmis')
18
    obj_type = ContentType.objects.get_for_model(conn)
19

  
20
    AccessRight.objects.create(codename='can_upload_file', apiuser=api,
21
                               resource_type=obj_type, resource_pk=conn.pk)
22

  
23
    return conn
24

  
25
fake_file = {
26
    'filename': 'test.txt',
27
    'path': '/a/path',
28
    'content': base64.b64encode('fake file content.'),
29
    'contentType': 'text/plain'
30
}
31

  
32
fake_file_error_one = {
33
    'path': '/a/path',
34
    'content': base64.b64encode('fake file content.'),
35
    'contentType': 'text/plain'
36
}
37

  
38
fake_file_error_two = {
39
    'filename': 'test.txt',
40
    'path': '/a/path',
41
    'content': 'fake file content.',
42
    'contentType': 'text/plain'
43
}
44

  
45
class MockedCmisDocument(mock.Mock):
46

  
47
    @property
48
    def properties(self):
49
        return {
50
            'cmis:baseTypeId': 'cmis:document',
51
            'cmis:contentStreamFileName': self.title,
52
            'cmis:contentStreamMimeType': self.contentType,
53
        }
54

  
55

  
56
class MockedCmisFolder(mock.Mock):
57

  
58
    def createDocumentFromString(self, title, contentString, contentType):
59
        return MockedCmisDocument(title=title, contentString=contentString, contentType=contentType)
60

  
61

  
62
class MockedCmisRepository(mock.Mock):
63

  
64
    def getObjectByPath(self, path):
65
        return MockedCmisFolder()
66

  
67

  
68
class MockedCmisClient(mock.Mock):
69

  
70
    @property
71
    def defaultRepository(self):
72
        return MockedCmisRepository()
73

  
74
@mock.patch('passerelle.apps.cmis.models.CmisClient')
75
def test_cmis_upload(mock_connection, app, setup):
76
    mock_connection.return_value = MockedCmisClient()
77
    response = app.post_json('/cmis/slug-cmis/upload_file', fake_file)
78
    body = json.loads(response.body)
79
    assert 'data' in body
80
    assert body['err'] == 0
81
    data = body['data']
82
    assert 'cmis:baseTypeId' in data
83
    assert 'cmis:contentStreamFileName' in data
84
    assert 'cmis:contentStreamMimeType' in data
85
    assert data['cmis:baseTypeId'] == 'cmis:document'
86
    assert data['cmis:contentStreamFileName'] == fake_file['filename']
87
    assert data['cmis:contentStreamMimeType'] == fake_file['contentType']
88

  
89
@mock.patch('passerelle.apps.cmis.models.CmisClient')
90
def test_cmis_upload_error_key(mock_connection, app, setup):
91
    mock_connection.return_value = MockedCmisClient()
92
    resp = app.post_json('/cmis/slug-cmis/upload_file', fake_file_error_one)
93
    assert 'err' in resp.json
94
    assert 'err_desc' in resp.json
95
    assert resp.json['err'] == 1
96
    assert 'CmisError : missing keys' in resp.json['err_desc']
97

  
98
@mock.patch('passerelle.apps.cmis.models.CmisClient')
99
def test_cmis_upload_error_base64(mock_connection, app, setup):
100
    mock_connection.return_value = MockedCmisClient()
101
    resp = app.post_json('/cmis/slug-cmis/upload_file', fake_file_error_two)
102
    assert 'err' in resp.json
103
    assert 'err_desc' in resp.json
104
    assert resp.json['err'] == 1
105
    assert 'CmisError : content must be a base64 string' in resp.json['err_desc']
0
-