[![Java Build with Maven](https://github.com/cap-java/cds-feature-attachments/actions/workflows/main.yml/badge.svg)](https://github.com/cap-java/cds-feature-attachments/actions/workflows/main.yml) [![Deploy new Version with Maven](https://github.com/cap-java/cds-feature-attachments/actions/workflows/release.yml/badge.svg)](https://github.com/cap-java/cds-feature-attachments/actions/workflows/release.yml) [![REUSE status](https://api.reuse.software/badge/github.com/cap-java/cds-feature-attachments)](https://api.reuse.software/info/github.com/cap-java/cds-feature-attachments) # Attachments Plugin for SAP Cloud Application Programming Model (CAP) The `com.sap.cds:cds-feature-attachments` dependency is a [CAP Java plugin](https://cap.cloud.sap/docs/java/building-plugins) that provides out-of-the box attachments storage and handling by using an aspect Attachments. It supports the [AWS, Azure, and Google object stores](storage-targets/cds-feature-attachments-oss) and can connect to a [malware scanner](#malware-scanner). ## Table of Contents * [Quick Start](#quick-start) * [Usage](#usage) * [MVN Setup](#mvn-setup) * [Changes in the CDS Models and for the UI](#changes-in-the-cds-models-and-for-the-UI) * [Single (Inline) Attachments](#single-inline-attachments) * [Try the Bookshop Sample](#try-the-bookshop-sample) * [Storage Targets](#storage-targets) * [Malware Scanner](#malware-scanner) * [Specify the maximum file size](#specify-the-maximum-file-size) * [Restrict allowed MIME types](#restrict-allowed-mime-types) * [Content Disposition](#content-disposition) * [Outbox](#outbox) * [Restore Endpoint](#restore-endpoint) * [Motivation](#motivation) * [HTTP Endpoint](#http-endpoint) * [Security](#security) * [Releases: Maven Central and Artifactory](#releases-maven-central-and-artifactory) * [Minimum UI5 and CAP Java Version](#minimum-ui5-and-cap-java-version) * [Architecture Overview](#architecture-overview) * [Design](#design) * [Multitenancy](#multitenancy) * [Object Stores](#object-stores) * [Model Texts](#model-texts) * [Monitoring \& Logging](#monitoring--logging) * [Support, Feedback, Contributing](#support-feedback-contributing) * [References \& Links](#references--links) ## Quick Start For a quick setup with in-memory storage: - Add the `cds-feature-attachments` Maven dependency to the `srv/pom.xml` and configure the `cds-maven-plugin` with the `resolve` goal as described in [MVN Setup](#mvn-setup). - Extend the CDS model with the `Attachments` aspect and annotate the service for UI integration as explained in [Changes in the CDS Models and for the UI](#changes-in-the-cds-models-and-for-the-UI). For a complete working example, see the [bookshop sample](samples/bookshop/). The [incidents app](https://github.com/cap-java/incidents-app/) provides a demonstration of how to use this plugin. For object store integration, see [Amazon, Azure, and Google Object Stores](storage-targets/cds-feature-attachments-oss). ## Usage ### MVN Setup As described in the [CAP Java Documentation](https://cap.cloud.sap/docs/java/building-plugins#reference-the-new-cds-model-in-an-existing-cap-java-project), the attachments plugin needs to be referenced in the `srv/pom.xml` of the consuming CAP Java application: ```xml com.sap.cds cds-feature-attachments ${latest-version} ``` Additionally, the `cds-maven-plugin` must be configured with the `resolve` goal to ensure CDS models from dependencies are available. For this, add the following to the `srv/pom.xml` before the entry `build` as well: ```xml com.sap.cds cds-maven-plugin ${cds.services.version} cds.resolve resolve ``` After that, the aspect `Attachments` can be used in the application's CDS model. ### Changes in the CDS Models and for the UI To use the aspect `Attachments` on an existing entity, the corresponding entity needs to be extended in a CDS file in the `srv` module. The following example shows how to extend the entity `Incidents` in the `srv` module with an additional `attachments.cds` file, it also directly adds the respective UI Facet. To use this file with the [incidents app](https://github.com/cap-java/incidents-app/), check out the source code, copy the [file from the xmpls folder](https://github.com/cap-java/incidents-app/blob/main/xmpls/attachments.cds) to the srv folder and run the app as explained in the [incidents app README](https://github.com/cap-java/incidents-app/blob/main/README.md). ```cds using { sap.capire.incidents as my } from '../db/schema'; using { Attachments } from 'com.sap.cds/cds-feature-attachments'; extend my.Incidents with { attachments: Composition of many Attachments; } using { ProcessorService as service } from '../app/services'; annotate service.Incidents with @( UI.Facets: [ ..., { $Type : 'UI.ReferenceFacet', ID : 'AttachmentsFacet', Label : '{i18n>attachments}', Target : 'attachments/@UI.LineItem' } ] ); ``` The UI Facet can also be added directly after other UI Facets in a `cds` file in the `app` folder. ### Single (Inline) Attachments > [!Important] > Inline attachments are available from **cds-feature-attachments 1.7.0**. In addition to the composition-based `Attachments` aspect (which supports multiple files), `cds-feature-attachments` provides the `Attachment` type for **single-file** attachment fields directly on an entity. This is useful when an entity needs exactly one file, for example a profile icon or a cover image. ```cds using { Attachment } from 'com.sap.cds/cds-feature-attachments'; entity Books { key ID : UUID; title : String; profileIcon : Attachment; coverImage : Attachment; } ``` CDS flattens inline attachment fields onto the parent entity. For example, `profileIcon : Attachment` generates the following columns on the `Books` table: - `profileIcon_content` (LargeBinary) - `profileIcon_mimeType` (String) - `profileIcon_fileName` (String) - `profileIcon_contentId` (String) - `profileIcon_status` (StatusCode) - `profileIcon_scannedAt` (Timestamp) - `profileIcon_note` (String) All plugin features: malware scanning, storage targets, maximum file size, and MIME type validation work the same way for inline attachments as for composition-based attachments. #### UI Annotations for Inline Attachments To display inline attachments in a Fiori Elements UI, use a `FieldGroup` referencing the flattened field names: ```cds annotate AdminService.Books with @(UI: { Facets: [ // ... other facets ... { $Type : 'UI.ReferenceFacet', Label : 'Profile Icon', Target: '@UI.FieldGroup#ProfileIcon' } ], FieldGroup #ProfileIcon: {Data: [ {Value: profileIcon_content}, {Value: profileIcon_status} ]} }); ``` ### Storage Targets By default, the plugin operates without a dedicated storage target, storing attachments directly in the [underlying database](cds-feature-attachments/src/main/resources/cds/com.sap.cds/cds-feature-attachments/attachments.cds#L17). Other available storage targets: - [Amazon, Azure, and Google Object Stores](storage-targets/cds-feature-attachments-oss) - [local file system as a storage backend](storage-targets/cds-feature-attachments-fs) > **Warning:** The [file system storage target](storage-targets/cds-feature-attachments-fs) (`cds-feature-attachments-fs`) is intended **only for local development and testing purposes**. It is **not suitable for production or deployed environments**. For production use, configure one of the supported object stores (AWS, Azure, or Google). When using a dedicated storage target, the attachment is not stored in the underlying database; instead, it is saved on the specified storage target and only a reference to the file is kept in the database, as defined in the [CDS model](cds-feature-attachments/src/main/resources/cds/com.sap.cds/cds-feature-attachments/attachments.cds#L20). ### Malware Scanner This plugin checks for a binding to the [SAP Malware Scanning Service](https://help.sap.com/docs/malware-scanning-servce), which needs to have the label `malware-scanner`. The entry in the [mta-file](https://cap.cloud.sap/docs/guides/deployment/to-cf#add-mta-yaml) may look like: ``` _schema-version: '0.1' ID: consuming-app version: 1.0.0 description: "App consuming the attachments plugin with a malware scanner" parameters: ... modules: - name: consuming-app-srv # ------------------------------------------------------------ type: java path: srv parameters: ... properties: ... build-parameters: ... requires: - name: consuming-app-hdi-container - name: consuming-app-uaa - name: cf-logging - name: malware-scanner ... resources: ... - name: malware-scanner type: org.cloudfoundry.managed-service parameters: service: malware-scanner service-plan: clamav ``` The malware scanner is used in the `AttachmentService` to scan attachments. > [!Note] > The malware scanner supports both mTLS and basic authentication; mTLS is recommended as basic authentication has been deprecated. If there is no malware scanner available, the attachments are automatically marked as `Clean`. Scan status codes: - `Clean`: Only attachments with the status `Clean` are accessible. - `Scanning`: Immediately after upload, the attachment is marked as `Scanning`. Depending on processing speed, it may already appear as `Clean` when the page is reloaded. - `Unscanned`: Attachment is still unscanned. - `Failed`: Scanning failed. - `Infected`: The attachment is infected. ### Specify the maximum file size You can specify the maximum file size by annotating the attachments content property with @Validation.Maximum ```cds entity Books { ... attachments: Composition of many Attachments; } annotate Books.attachments with { content @Validation.Maximum : '20MB'; } ``` The @Validation.Maximum value is a size string consisting of a number followed by a unit. The following units are supported: - B (bytes) - KB, MB, GB, TB (decimal units) - KiB, MiB, GiB, TiB (binary units) The default is 400MB ### Restrict allowed MIME types You can restrict which MIME types are allowed for attachments by annotating the content property with @Core.AcceptableMediaTypes. This validation is performed during file upload. ```cds entity Books { ... attachments: Composition of many Attachments; } annotate Books.attachments with { content @Core.AcceptableMediaTypes : ['image/jpeg', 'image/png', 'application/pdf']; } ``` Wildcard patterns are supported: ```cds annotate Books.attachments with { content @Core.AcceptableMediaTypes : ['image/*', 'application/pdf']; } ``` To allow all MIME types (default behavior), either omit the annotation or use: ```cds annotate Books.attachments with { content @Core.AcceptableMediaTypes : ['*/*']; } ``` ### Content Disposition Attachment content is served with `Content-Disposition: attachment`. Browsers therefore download the file instead of rendering it inline in the application origin. This is the safe default that mitigates stored XSS attacks via user-uploaded HTML or SVG payloads, because the content type of a stored attachment is derived from the uploaded file. If your application intentionally wants to preview certain attachments inline (e.g. images restricted via `@Core.AcceptableMediaTypes`), you can opt in explicitly in your own CDS model: ```cds using { sap.attachments.Attachments } from '@cap-js/cds-feature-attachments'; annotate Books.attachments with { content @Core.ContentDisposition.Type: 'inline'; } ``` > **Security warning:** Only enable inline disposition for MIME types that cannot execute script — for example, plain images such as `image/jpeg` or `image/png` — restricted via `@Core.AcceptableMediaTypes`. Never enable inline disposition for `image/svg+xml`, `text/html`, `application/xhtml+xml`, or `application/xml` when the uploader is not fully trusted. Note that `application/pdf` may execute JavaScript in some PDF viewers (notably Adobe Reader); verify the viewer used by your target audience before allowing PDFs inline. ### Outbox In this plugin the [persistent outbox](https://cap.cloud.sap/docs/java/outbox#persistent) is used to mark attachments as deleted. When using this plugin, the persistent outbox is enabled by default. In the capire documentation of the [persistent outbox](https://cap.cloud.sap/docs/java/outbox#persistent) is it described how to overwrite the default outbox configuration. If the default shall be used, nothing needs to be done. ### Restore Endpoint The attachment service has an event `RESTORE_ATTACHMENTS`. This event can be called with a timestamp to restore externally stored attachments. #### Motivation Documents which are marked as deleted can be restored. The use cases behind this feature are: - Restoring attachments after a database backup is restored: When restoring a database backup, any attachments stored in external storage (object stores, etc.) also need to be restored to maintain data consistency. - Restoring attachments that were marked as deleted: The restore endpoint provides a way to recover attachments that were previously marked as deleted, making it possible to undo deletions if needed. In the default implementation of the technical service `AttachmentService` this is not needed as the attachments are stored directly in the database and are restored with the database. If the default implementation is replaced by overwriting the [respective handler](cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/service/handler/DefaultAttachmentsServiceHandler.java#L87), for example by the [SAP Document Management Service](https://help.sap.com/docs/document-management-service), then the overwriting plugin needs to handle the restore of attachments. In such cases the restore endpoint can be used to restore attachments. How long attachments are marked as deleted before they are actually deleted depends on the configuration of the used storage. #### HTTP Endpoint There is no predefined endpoint for the restore action. To call the action of the service from outside the application a service could be defined as in the following example: ```cds service RestoreAttachments { action restoreAttachments (restoreTimestamp: Timestamp); } ``` See [Security](#security) for how to secure this endpoint. The action `restoreAttachments` could get in a timestamp from which the attachments need to be restored. The action could be called with a POST request to the endpoint: - OData v4: `/odata/v4/RestoreAttachments/restoreAttachments` - OData v2: `/odata/v2/RestoreAttachments/restoreAttachments` With the body: ```json { "restoreTimestamp": "2024-04-17T10:36:38.813491100Z" } ``` The action needs to be implemented and can call the attachment service as in the following example: ```java @ServiceName(RestoreAttachments_.CDS_NAME) public class RestoreAttachmentsHandler implements EventHandler { private final AttachmentService attachmentService; public RestoreAttachmentsHandler(AttachmentService attachmentService) { this.attachmentService = attachmentService; } @On(event = RestoreAttachmentsContext.CDS_NAME) public void restoreAttachments(RestoreAttachmentsContext context) { attachmentService.restoreAttachment(context.getRestoreTimestamp()); context.setCompleted(); } } ``` In the Spring Boot context the `AttachmentService` can be autowired in the handler. #### Security To secure the endpoint, security annotations can be used. For example: ```cds using {Attachments} from `com.sap.cds/cds-feature-attachments`; entity Items : cuid { ... attachments : Composition of many Attachments; ... } annotate RestoreAttachments with @(requires: 'internal-user'); ``` Here the `RestoreAttachments` service is annotated with the `requires` annotation to secure the service. Various other annotations can be used to secure the service. More information about the CAP Java security concept can be found in the [CAP Java Documentation](https://cap.cloud.sap/docs/java/security). ## Releases: Maven Central and Artifactory - The plugin is released to Maven Central at: https://central.sonatype.com/artifact/com.sap.cds/cds-feature-attachments (public access). - See the [changelog](./doc/CHANGELOG.md) for the latest changes. - To test snapshot versions of this plugin, the artifactory in `${HOME}/.m2/settings.xml` needs to be configured. See [the maven settings](https://maven.apache.org/settings.html#Repositories) for further details. ## Minimum UI5 and CAP Java Version | Component | Minimum Version | | --------- | --------------- | | CAP Java | 3.10.3 | | UI5 | 1.136.0 | ## Architecture Overview ### Design - [Design Details](./doc/Design.md) - [Process of Creating, Reading and Deleting an Attachment](./doc/Processes.md) ### Multitenancy - When using SAP HANA as the storage target, multitenancy support depends on the consuming application. In most cases, multitenancy is achieved by using a dedicated schema for each tenant, providing strong data isolation at the database level. - When using an [object store](storage-targets/cds-feature-attachments-oss) as the storage target, shared-bucket multitenancy is supported (since version 1.5.0). The plugin prefixes object keys with the tenant ID to ensure tenant isolation, and automatically cleans up tenant data on unsubscribe. See [Object Stores Multitenancy](storage-targets/cds-feature-attachments-oss#multitenancy) for configuration details. ### Object Stores See [Object Stores](storage-targets/cds-feature-attachments-oss). ### Model Texts In the model, several fields are annotated with the `@title` annotation. Default texts are provided in [35 languages](https://github.com/cap-java/cds-feature-attachments/tree/main/cds-feature-attachments/src/main/resources/cds/com.sap.cds/cds-feature-attachments/_i18n). If these defaults are not sufficient for an application, they can be overwritten by applications with custom texts or translations. The following table gives an overview of the fields and the i18n codes: | Field Name | i18n Code | | ---------- | --------------------- | | `content` | `attachment_content` | | `mimeType` | `attachment_mimeType` | | `fileName` | `attachment_fileName` | | `status` | `attachment_status` | | `note` | `attachment_note` | In addition to the field names, header information (`@UI.HeaderInfo`) are also annotated: | Header Info | i18n Code | | ---------------- | ------------- | | `TypeName` | `attachment` | | `TypeNamePlural` | `attachments` | ## Monitoring & Logging To configure logging for the attachments plugin, add the following line to the `/srv/src/main/resources/application.yaml` of the consuming application: ``` logging: level: ... '[com.sap.cds.feature.attachments]': DEBUG ... ``` ## Support, Feedback, Contributing This project is open to feature requests/suggestions, bug reports etc. via [GitHub issues](https://github.com/cap-java/cds-feature-attachments/issues). Contribution and feedback are encouraged and always welcome. For more information about how to contribute, the project structure, as well as additional contribution information, see our [Contribution Guidelines](./doc/CONTRIBUTING.md). ## References & Links - [License](./LICENSE)