passerelle/passerelle/apps/filr_rest/models.py

157 lines
5.5 KiB
Python

# passerelle - uniform access to multiple data sources and services
# Copyright (C) 2023 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/>.
import base64
import binascii
import json
import urllib
import requests
from django.db import models
from django.utils.translation import ugettext_lazy as _
from passerelle.base.models import BaseResource, HTTPResource
from passerelle.utils.api import endpoint
from passerelle.utils.jsonresponse import APIError
from . import schemas
class Filr(BaseResource, HTTPResource):
base_url = models.URLField(
verbose_name=_('Webservice Base URL'),
)
category = _('File Storage')
class Meta:
verbose_name = _('Filr REST API')
def _call(self, path, method='get', json_data=None, data=None, headers=None, params=None):
kwargs = {}
if data:
kwargs['data'] = data
if json_data:
kwargs['json'] = json_data
if headers:
kwargs['headers'] = headers
if params:
kwargs['params'] = params
try:
resp = self.requests.request(
method=method, url=urllib.parse.urljoin(self.base_url, path), **kwargs
)
except (requests.Timeout, requests.RequestException) as e:
raise APIError(str(e))
try:
resp.raise_for_status()
except requests.RequestException as main_exc:
try:
err_data = resp.json()
except (json.JSONDecodeError, requests.exceptions.RequestException):
err_data = {'response_text': resp.text}
raise APIError(str(main_exc), data=err_data)
content_type = resp.headers.get('Content-Type')
if content_type and content_type.startswith('application/json') and resp.status_code != 204:
try:
return resp.json()
except (json.JSONDecodeError, requests.exceptions.JSONDecodeError) as e:
raise APIError(str(e))
return resp.text
@endpoint(
perm='can_access',
description=_('Upload a file'),
post={
'request_body': {'schema': {'application/json': schemas.UPLOAD}},
},
)
def upload(self, request, post_data):
root_folder_id, folder_name = post_data['root_folder_id'], post_data['folder_name']
try:
file_content = base64.b64decode(post_data['file']['content'])
except (TypeError, binascii.Error):
raise APIError('invalid base64 string', http_status=400)
filename = post_data.get('filename') or post_data['file']['filename']
# get or create folder
folder_id = None
root_folder_info = self._call('rest/folders/%s/library_folders' % root_folder_id)
for folder in root_folder_info.get('items', []):
if folder.get('title') == folder_name:
folder_id = folder.get('id')
break
else:
folder_info = self._call(
'rest/folders/%s/library_folders' % root_folder_id,
method='post',
json_data={'title': folder_name},
)
folder_id = folder_info['id']
# upload file
file_info = self._call(
'rest/folders/%s/library_files' % folder_id,
method='post',
params={'file_name': filename},
headers={'Content-Type': 'application/octet-stream'},
data=file_content,
)
return {'data': {'folder_id': folder_id, 'file_info': file_info}}
@endpoint(
name='share-folder',
perm='can_access',
description=_('Share a folder to external users'),
post={
'request_body': {'schema': {'application/json': schemas.SHARE_FOLDER}},
'input_example': {
'folder_id': '1234',
'emails/0': 'foo@invalid',
'emails/1': 'bar@invalid',
'days_to_expire': '30',
},
},
)
def share_folder(self, request, post_data):
data = []
for email in post_data['emails']:
share_info = self._call(
'rest/folders/%s/shares' % post_data['folder_id'],
method='post',
params={'notify': 'true'},
json_data={
'days_to_expire': post_data['days_to_expire'],
'recipient': {'type': 'external_user', 'email': email},
'access': {'role': 'VIEWER'},
},
)
data.append(share_info)
return {'data': data}
@endpoint(
name="delete-folder",
perm='can_access',
methods=['post'],
description=_('Delete a folder'),
post={'request_body': {'schema': {'application/json': schemas.DELETE_FOLDER}}},
)
def delete_folder(self, request, post_data):
delete_infos = self._call('rest/folders/%s' % post_data['folder_id'], method='delete')
return {'data': delete_infos}