Skip to content

Commit

Permalink
Merge pull request #7917 from 4teamwork/lg/CA-6591/transfer-content
Browse files Browse the repository at this point in the history
Add full content serialization mode for GET /@dossier-transfers
  • Loading branch information
lukasgraf committed Apr 29, 2024
2 parents a7397b3 + 8ba1de6 commit f93c050
Show file tree
Hide file tree
Showing 14 changed files with 1,428 additions and 28 deletions.
1 change: 1 addition & 0 deletions changes/CA-6591.feature
@@ -0,0 +1 @@
Add full_content mode for GET /@dossier-transfers. [lgraf]
3 changes: 3 additions & 0 deletions docs/public/dev-manual/api/api_changelog.rst
Expand Up @@ -8,8 +8,11 @@ API Changelog

Other Changes
^^^^^^^^^^^^^
- ``GET @dossier-transfers/<id>/blob/<document-UID>``: New endpoint to download document blobs of a dossier transfer.
- ``GET @dossier-transfers/<id>?full_content=1``: New mode to fetch full content representation for dossier transfers.
- Add ris_base_url to config endpoint.


2024.7.0 (2024-04-23)
---------------------

Expand Down
79 changes: 79 additions & 0 deletions docs/public/dev-manual/api/dossier_transfers.rst
Expand Up @@ -192,3 +192,82 @@ Mittels eines DELETE Requests können Dossier-Transfers gelöscht werden:

HTTP/1.1 204 No Content
Content-Type: application/json


Dossier-Transfer-Inhalt abrufen
-------------------------------

Mit einem GET Request auf ``/@dossier-transfers/<id>?full_content=1`` kann
zusätzlich zu den Metadaten eines Dossier-Transfers eine Serialisierung des
Inhalts des Transfers abgerufen werden.

Dieser serialisierte Inhalt wird in einem zusätzlichen key ``content``
zurückgegeben:

**Beispiel-Request**:

.. sourcecode:: http

GET /@dossier-transfers/42?full_content=1 HTTP/1.1
Accept: application/json


**Beispiel-Response**:

.. sourcecode:: http

HTTP/1.1 200 OK
Content-Type: application/json

{
"@id": "http://localhost:8080/fd/@dossier-transfers/4",
"...": "...",
"content": {
"contacts": {
"person:39c2789d-a123-44ba-a3b1-4323d6e941c6": {
"id": "39c2789d-a123-44ba-a3b1-4323d6e941c6",
"firstName": "John",
"fullName": "John Doe",
"...": "..."
}
},
"documents": [
{
"@id": "http://localhost:8080/fd/dossier-20/dossier-21/document-44",
"@type": "opengever.document.document",
"UID": "a663689540a34538b6f408d4b41baee8",
"...": "..."
}
],
"dossiers": [
{
"@id": "http://localhost:8080/fd/dossier-20",
"@type": "opengever.dossier.businesscasedossier",
"UID": "1b6d8dbf1f954bbb9510a1b65d51ede5",
"...": "...",
"participations": [
[
"person:39c2789d-a123-44ba-a3b1-4323d6e941c6",
["final-drawing", "regard"]
]
]
},
{
"@id": "http://localhost:8080/fd/dossier-20/dossier-21",
"@type": "opengever.dossier.businesscasedossier",
"UID": "f510a6bb410f40258b53090bf2f0c545",
"...": "..."
}
]
},
"...": "..."
}


Blobs von Dossier-Transfers herunterladen
-----------------------------------------

Mit einem GET Request auf ``/@dossier-transfers/<transfer-id>/blob/<document-uid>``
kann das Blob eines Dokuments heruntergeladen werden. Der Request muss dazu einem
gültigen Token für diesen Transfer authentisiert werden, und das Dokument muss
in diesem Transfer enthalten sein.
21 changes: 13 additions & 8 deletions opengever/api/document.py
Expand Up @@ -17,6 +17,7 @@
from opengever.workspace.utils import is_within_workspace
from opengever.workspaceclient import is_workspace_client_feature_enabled
from opengever.workspaceclient.interfaces import ILinkedDocuments
from plone import api
from plone.restapi.deserializer import json_body
from plone.restapi.interfaces import IExpandableElement
from plone.restapi.interfaces import IJsonCompatible
Expand Down Expand Up @@ -46,14 +47,18 @@ def __call__(self, *args, **kwargs):

version = "current" if kwargs.get('version') is None else kwargs.get('version')
obj = self.getVersion(version)
bumblebee_service = bumblebee.get_service_v3()
result['bumblebee_checksum'] = IBumblebeeDocument(obj).get_checksum()
result[u'thumbnail_url'] = bumblebee_service.get_representation_url(
obj, 'thumbnail')
result[u'preview_url'] = bumblebee_service.get_representation_url(
obj, 'preview')
result[u'pdf_url'] = bumblebee_service.get_representation_url(
obj, 'pdf')

user_id = api.user.get_current().getId()
if user_id:
# Include Bumblebee URLs for non-anonymous users
bumblebee_service = bumblebee.get_service_v3()
result['bumblebee_checksum'] = IBumblebeeDocument(obj).get_checksum()
result[u'thumbnail_url'] = bumblebee_service.get_representation_url(
obj, 'thumbnail')
result[u'preview_url'] = bumblebee_service.get_representation_url(
obj, 'preview')
result[u'pdf_url'] = bumblebee_service.get_representation_url(
obj, 'pdf')
result[u'file_extension'] = obj.get_file_extension()

extend_with_backreferences(
Expand Down
1 change: 1 addition & 0 deletions opengever/base/subscribers.py
Expand Up @@ -13,6 +13,7 @@

ALLOWED_ENDPOINTS = set([
'GET_application_json_@config',
'GET_application_json_@dossier-transfers',
'GET_application_json_@white-labeling-settings',
'POST_application_json_@caslogin',
'POST_application_json_@login',
Expand Down
47 changes: 40 additions & 7 deletions opengever/dossiertransfer/api/base.py
@@ -1,4 +1,6 @@
from opengever.base.security import elevated_privileges
from opengever.dossiertransfer import is_dossier_transfer_feature_enabled
from opengever.dossiertransfer.api.serializers import FullTransferContentSerializer
from opengever.dossiertransfer.model import DossierTransfer
from opengever.dossiertransfer.model import TRANSFER_STATE_COMPLETED
from opengever.dossiertransfer.model import TRANSFER_STATE_PENDING
Expand Down Expand Up @@ -27,8 +29,18 @@ def render(self):
raise BadRequest("Feature 'dossier_transfers' is not enabled.")
return super(DossierTransfersBase, self).render()

def serialize(self, transfer):
return getMultiAdapter((transfer, getRequest()), ISerializeToJson)()
def serialize(self, transfer, full_content=False):
serialized = getMultiAdapter(
(transfer, getRequest()), ISerializeToJson)()

if full_content:
if not self.has_valid_token():
raise Unauthorized

with elevated_privileges():
serialized['content'] = FullTransferContentSerializer(transfer)()

return serialized


@implementer(IPublishTraverse)
Expand Down Expand Up @@ -66,16 +78,28 @@ def publishTraverse(self, request, name):
self.params.append(name)
return self

def has_valid_token(self):
transfer_id = self._extract_transfer_id()
transfer = DossierTransfer.get(transfer_id)
if transfer:
token = self.request.getHeader('X-GEVER-Dossier-Transfer-Token')
return transfer.is_valid_token(token)
return False

def _extract_transfer_id(self):
# We'll accept zero (listing) or one (get by id) params, but not more
if len(self.params) > 1:
# We'll accept
# - zero (listing)
# - one (get by id) or
# - three (blob download)
# params, but not more
if len(self.params) not in (0, 1, 3):
raise BadRequest(
'Must supply either exactly one {transfer_id} path parameter '
'to fetch a specific transfer, or no parameter for a '
'listing of all transfers.')
'to fetch a specific transfer, no parameter for a '
'listing of all transfers, or three to fetch a blob.')

# We have a valid number of parameters for the given endpoint
if len(self.params) == 1:
if len(self.params) in (1, 3):
try:
transfer_id = int(self.params[0])
except ValueError:
Expand All @@ -90,6 +114,9 @@ def _is_inbox_user(self, user_id):
return True
return False

def is_blob_request(self):
return len(self.params) == 3 and self.params[1] == 'blob'

def locate_transfer(self):
transfer_id = self.transfer_id
if not DossierTransfer.get(transfer_id):
Expand Down Expand Up @@ -128,6 +155,12 @@ def extend_with_security_filters(self, query):
DossierTransfer.target_id == local_unit_id,
)]

if self.has_valid_token():
# Server-to-server requests to fetch the full transfer contents are
# performed anonymously, but with a valid token that matches a
# particular transfer. We must not restrict these.
return query.filter(*filters)

if not self._is_inbox_user(user_id):
# Only inbox users may see transfers other than their own
filters.append(DossierTransfer.source_user_id == user_id)
Expand Down
84 changes: 81 additions & 3 deletions opengever/dossiertransfer/api/get.py
@@ -1,22 +1,68 @@
from opengever.base.behaviors.utils import set_attachment_content_disposition
from opengever.base.security import elevated_privileges
from opengever.document.behaviors import IBaseDocument
from opengever.dossiertransfer.api.base import DossierTransferLocator
from plone import api
from plone.namedfile.utils import stream_data
from plone.restapi.services import _no_content_marker
from zExceptions import NotFound
from zExceptions.unauthorized import Unauthorized
import json


class DossierTransfersGet(DossierTransferLocator):
"""API Endpoint that returns DossierTransfers.
GET /@dossier-transfers/42 HTTP/1.1
GET /@dossier-transfers HTTP/1.1
GET /@dossier-transfers/blob/<document-UID> HTTP/1.1
"""

def render(self):
self.check_permission()
content = self.reply()

# Exception for blob download: Do not attempt to JSON serialize
# blob download responses. Instead directly return the
# IStreamIterator and let ZPublisher handle it.
if self.is_blob_request():
return content

if content is not _no_content_marker:
self.request.response.setHeader("Content-Type", self.content_type)
return json.dumps(
content, indent=2, sort_keys=True, separators=(", ", ": ")
)

def check_permission(self):
if self.has_valid_token():
# Server-to-server requests to fetch the full transfer contents are
# performed anonymously, but with a valid token that matches a
# particular transfer. We must not restrict these.
return

return super(DossierTransfersGet, self).check_permission()

def reply(self):
if self.transfer_id:
if self.transfer_id and len(self.params) == 1:
# Get transfer by id
transfer = self.locate_transfer()
if self.full_content_requested():
# Get full content required to perform a transfer
if not self.has_valid_token():
raise Unauthorized(
"full_content requires a valid transfer token")
return self.serialize(transfer, full_content=True)
return self.serialize(transfer)

# List all transfers
return self.list()
elif self.is_blob_request():
return self.stream_document_blob()

if len(self.params) == 0:
# List all transfers
return self.list()
else:
raise NotFound

def list(self):
transfers = self.list_transfers()
Expand All @@ -25,3 +71,35 @@ def list(self):
'items': [self.serialize(t) for t in transfers],
}
return result

def full_content_requested(self):
return bool(self.request.form.get('full_content', False))

def stream_document_blob(self):
transfer = self.locate_transfer()
document_uid = self.params[2]

with elevated_privileges():
root_dossier = api.content.uuidToObject(transfer.root)
catalog = api.portal.get_tool('portal_catalog')

query = {
'path': '/'.join(root_dossier.getPhysicalPath()),
'object_provides': IBaseDocument.__identifier__,
'UID': document_uid,
}
brains = catalog.unrestrictedSearchResults(**query)

if len(brains) == 0:
raise NotFound

if not transfer.all_documents:
if document_uid not in transfer.documents:
raise Unauthorized

document = brains[0].getObject()
namedfile = document.file

filename = getattr(namedfile, 'filename', u'document.bin').encode('utf-8')
set_attachment_content_disposition(self.request, filename, namedfile)
return stream_data(namedfile)
4 changes: 2 additions & 2 deletions opengever/dossiertransfer/api/post.py
Expand Up @@ -69,8 +69,8 @@ def reply(self):
session.flush()

token = transfer.issue_token()
transfer.validate_token(token)
transfer.token = token
if not (transfer.token == token and transfer.is_valid_token(token)):
raise Unauthorized

serialized_transfer = self.serialize(transfer)

Expand Down

0 comments on commit f93c050

Please sign in to comment.