Repository: owncloud/ocis
Pull request #7479 (2023-10-12 15:54:22)

fix default language fallback
<!--
Thanks for submitting a change to ownCloud!

This is the bug tracker for the oCIS component. Find other components at https://github.com/owncloud/core/blob/master/.github/CONTRIBUTING.md#guidelines

For fixing potential security issues please see https://owncloud.org/security/

To make it possible for us to get your change reviewed and merged please carefully fill out the requested information below.

Please note that any kind of change needs first be submitted to the master branch which holds the next version of oCIS.

Please set the following labels:

- Set label "Status:Needs-Review" for review or "Status:In-Progress" if the PR still has open tasks.
- Assignment: assign to self
- Reviewers: pick at least one
-->

## Description
<!--- Describe your changes in detail -->
Added the global variable OCIS_DEFAULT_LANGUAGE. Now `settings`, `notification`, and `userlog` services support the default language fallback.

The `OCIS_DEFAULT_LANGUAGE` impacts depend on existing translation.

These changes are needed because if the transaction does not exist for notification or userlog services we want to fall down to the OCIS_DEFAULT_LANGUAGE first and after that to the English

## Related Issue
<!--- This project only accepts pull requests related to open issues -->
<!--- If suggesting a new feature or change, please discuss it in an issue first -->
<!--- If fixing a bug, there should be an issue describing it with steps to reproduce -->
<!--- Please link to the issue here: -->
- Fixes [https://github.com/owncloud/ocis/issues/7465](https://github.com/owncloud/ocis/issues/7465)

## Motivation and Context
<!--- Why is this change required? What problem does it solve? -->

## How Has This Been Tested?
<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran to -->
<!--- see how your change affects other areas of the code, etc. -->
- test environment:
- test case 1:
- test case 2:
- ...

## Screenshots (if appropriate):

## Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [ ] Technical debt
- [ ] Tests only (no source changes)

## Checklist:
<!-- Tick the checkboxes when done. -->
<!-- Raise documentation ticket in owncloud.github.io/ -->
- [ ] Code changes
- [ ] Unit tests added
- [ ] Acceptance tests added
- [ ] Documentation ticket raised: <link> 


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=7479)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7479&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7479&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7479&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=CODE_SMELL)

[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/0-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7479&metric=new_coverage&view=list) [0.0% Coverage](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7479&metric=new_coverage&view=list)  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7479&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7479&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: SonarCloud Quality Gate failed.&nbsp; &nbsp; [![Quality Gate failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/failed-16px.png 'Quality Gate failed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=7479)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=BUG) [![C](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/C-16px.png 'C')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=BUG) [1 Bug](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=VULNERABILITY) [![C](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/C-16px.png 'C')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=VULNERABILITY) [3 Vulnerabilities](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7479&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7479&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7479&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=CODE_SMELL)

[![43.5%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/40-16px.png '43.5%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7479&metric=new_coverage&view=list) [43.5% Coverage](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7479&metric=new_coverage&view=list)  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7479&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7479&metric=new_duplicated_lines_density&view=list)



![idea](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/light_bulb-16px.png 'idea') Catch issues before they fail your Quality Gate with our IDE extension ![sonarlint](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/sonarlint-16px.png 'sonarlint') [SonarLint](https://www.sonarsource.com/products/sonarlint/features/connected-mode/?referrer=sonarcloud-welcome)
sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=7479)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=VULNERABILITY) [![C](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/C-16px.png 'C')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=VULNERABILITY) [3 Vulnerabilities](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7479&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7479&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7479&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=CODE_SMELL)

[![43.5%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/40-16px.png '43.5%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7479&metric=new_coverage&view=list) [43.5% Coverage](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7479&metric=new_coverage&view=list)  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7479&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7479&metric=new_duplicated_lines_density&view=list)


mmattel: I am not convinced about:
* `(optional) The default language.` text because it does not tell briefly about for what it is gonna used for. Also `(optional)` is not good.
* referencing to the settings service documentation for more details of the envvar BUT without linking which is just in this case not possible (dev-docs != admin docs). Maybe we should duplicate the referenced text to the readme's accordingly as we have in othe rservices too (see auth-service, userlog, clientlog).

Thinking about how we can improve...
sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=7479)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7479&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7479&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7479&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=CODE_SMELL)

[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/0-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7479&metric=new_coverage&view=list) [0.0% Coverage](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7479&metric=new_coverage&view=list)  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7479&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7479&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=7479)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7479&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7479&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7479&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=CODE_SMELL)

[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/0-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7479&metric=new_coverage&view=list) [0.0% Coverage](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7479&metric=new_coverage&view=list)  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7479&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7479&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=7479)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7479&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7479&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7479&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=CODE_SMELL)

[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/0-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7479&metric=new_coverage&view=list) [0.0% Coverage](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7479&metric=new_coverage&view=list)  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7479&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7479&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=7479)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7479&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7479&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7479&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=CODE_SMELL)

[![43.5%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/40-16px.png '43.5%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7479&metric=new_coverage&view=list) [43.5% Coverage](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7479&metric=new_coverage&view=list)  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7479&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7479&metric=new_duplicated_lines_density&view=list)


mmattel: I will make a local branch, do the doc changes there and commit them. This makes it easier for me testing the rendering.
sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=7479)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7479&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7479&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7479&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=CODE_SMELL)

[![43.5%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/40-16px.png '43.5%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7479&metric=new_coverage&view=list) [43.5% Coverage](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7479&metric=new_coverage&view=list)  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7479&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7479&metric=new_duplicated_lines_density&view=list)


mmattel: @2403905 I have rebased and updated the texts via commits above, pls rebase locally.
sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=7479)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7479&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7479&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7479&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7479&resolved=false&types=CODE_SMELL)

[![43.5%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/40-16px.png '43.5%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7479&metric=new_coverage&view=list) [43.5% Coverage](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7479&metric=new_coverage&view=list)  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7479&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7479&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 880073ed647..42a21736e47 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -36,6 +36,7 @@ The following sections list the changes for unreleased.
 * Enhancement - Proxy uses service accounts for provisioning: [#7240](https://github.com/owncloud/ocis/pull/7240)
 * Enhancement - Introduce service accounts: [#6427](https://github.com/owncloud/ocis/pull/6427)
 * Enhancement - Make sse service scalable: [#7382](https://github.com/owncloud/ocis/pull/7382)
+* Enhancement - Thumbnail generation with image processors: [#7409](https://github.com/owncloud/ocis/pull/7409)
 
 ## Details
 
@@ -309,6 +310,34 @@ The following sections list the changes for unreleased.
    fixed.
 
    https://github.com/owncloud/ocis/pull/7382
+
+* Enhancement - Thumbnail generation with image processors: [#7409](https://github.com/owncloud/ocis/pull/7409)
+
+   Thumbnails can now be changed during creation, previously the images were always scaled to fit
+   the given frame, but it could happen that the images were cut off because they could not be placed
+   better due to the aspect ratio.
+
+   This pr introduces the possibility of specifying how the behavior should be, following
+   processors are available
+
+  * resize
+  * fit
+  * fill
+  * thumbnail
+
+   The processor can be applied by adding the processor query param to the request, e.g.
+   `processor=fit`, `processor=fill`, ...
+
+   To find out more how the individual processors work please read
+   https://github.com/disintegration/imaging
+
+   If no processor is provided it behaves the same as before (resize for gif's and thumbnail for all
+   other)
+
+   https://github.com/owncloud/enterprise/issues/6057
+   https://github.com/owncloud/ocis/issues/5179
+   https://github.com/owncloud/web/issues/7728
+   https://github.com/owncloud/ocis/pull/7409
 # Changelog for [4.0.0] (2023-08-21)
 
 The following sections list the changes for 4.0.0.
diff --git a/changelog/unreleased/fix-default-mail-language-fallback.md b/changelog/unreleased/fix-default-mail-language-fallback.md
new file mode 100644
index 00000000000..0d023f993ec
--- /dev/null
+++ b/changelog/unreleased/fix-default-mail-language-fallback.md
@@ -0,0 +1,6 @@
+Bugfix: Fix default language fallback
+
+Add the default language for the webui,
+the settings, userlog and notification service.
+
+https://github.com/owncloud/ocis/issues/7465
diff --git a/changelog/unreleased/thumbnail-processors.md b/changelog/unreleased/thumbnail-processors.md
new file mode 100644
index 00000000000..fcd49ab21a1
--- /dev/null
+++ b/changelog/unreleased/thumbnail-processors.md
@@ -0,0 +1,22 @@
+Enhancement: Thumbnail generation with image processors
+
+Thumbnails can now be changed during creation, previously the images were always scaled to fit the given frame,
+but it could happen that the images were cut off because they could not be placed better due to the aspect ratio.
+
+This pr introduces the possibility of specifying how the behavior should be, following processors are available
+
+* resize
+* fit
+* fill
+* thumbnail
+
+the processor can be applied by adding the processor query param to the request, e.g. `processor=fit`, `processor=fill`, ...
+
+to find out more how the individual processors work please read https://github.com/disintegration/imaging
+
+if no processor is provided it behaves the same as before (resize for gif's and thumbnail for all other)
+
+https://github.com/owncloud/ocis/pull/7409
+https://github.com/owncloud/enterprise/issues/6057
+https://github.com/owncloud/ocis/issues/5179
+https://github.com/owncloud/web/issues/7728
diff --git a/docs/services/app-registry/apps.md b/docs/services/app-registry/apps.md
index c8ac8451ace..719ac0a46a7 100644
--- a/docs/services/app-registry/apps.md
+++ b/docs/services/app-registry/apps.md
@@ -493,30 +493,4 @@ App drivers represent apps, if the app is not able to register itself. Currently
 
 The CS3org WOPI server app driver is included in oCIS by default. It needs at least one WOPI compliant app (e.g. Collabora, OnlyOffice or Microsoft Online Server) or a CS3org WOPI bridge supported app (CodiMD or Etherpad) and the CS3org WOPI server.
 
-Here is a closer look at the configuration of the actual app provider in a docker-compose example (see also [full example](https://github.com/owncloud/ocis/blob/master/deployments/examples/ocis_wopi/docker-compose.yml)):
-
-```yaml
-services:
-  ocis:
-    image: owncloud/ocis:latest
-    ...
-    environment:
-      ...
-      GATEWAY_GRPC_ADDR: 0.0.0.0:9142 # make the REVA gateway accessible to the app drivers
-
-  ocis-appdriver-collabora:
-    image: owncloud/ocis:latest
-    command: storage-app-provider server # start only the app driver
-    environment:
-      STORAGE_GATEWAY_ENDPOINT: ocis:9142 # oCIS gateway endpoint
-      APP_PROVIDER_GRPC_ADDR: 0.0.0.0:9164
-      APP_PROVIDER_EXTERNAL_ADDR: ocis-appdriver-collabora:9164 # how oCIS can reach this app driver
-      OCIS_JWT_SECRET: ocis-jwt-secret
-      APP_PROVIDER_DRIVER: wopi
-      APP_PROVIDER_WOPI_DRIVER_APP_NAME: Collabora # will be used as name for this app
-      APP_PROVIDER_WOPI_DRIVER_APP_ICON_URI: https://www.collaboraoffice.com/wp-content/uploads/2019/01/CP-icon.png # will be used as icon for this app
-      APP_PROVIDER_WOPI_DRIVER_APP_URL: https://collabora.owncloud.test # endpoint of collabora
-      APP_PROVIDER_WOPI_DRIVER_INSECURE: false
-      APP_PROVIDER_WOPI_DRIVER_IOP_SECRET: wopi-iop-secret
-      APP_PROVIDER_WOPI_DRIVER_WOPI_URL: https://wopiserver.owncloud.test # endpoint of the CS3org WOPI server
-```
+The configuration of the actual app provider in a docker-compose example can be found in the [full example](https://github.com/owncloud/ocis/blob/master/deployments/examples/ocis_wopi/docker-compose.yml) especially [ocis-appprovider-collabora](https://github.com/owncloud/ocis/blob/master/deployments/examples/ocis_wopi/docker-compose.yml#L102-L128) and [ocis-appprovider-onlyoffice](https://github.com/owncloud/ocis/blob/master/deployments/examples/ocis_wopi/docker-compose.yml#L130-L161)
diff --git a/go.mod b/go.mod
index 506d25bb518..988972eb556 100644
--- a/go.mod
+++ b/go.mod
@@ -99,7 +99,7 @@ require (
 	golang.org/x/term v0.13.0
 	golang.org/x/text v0.13.0
 	google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d
-	google.golang.org/grpc v1.58.2
+	google.golang.org/grpc v1.58.3
 	google.golang.org/protobuf v1.31.0
 	gopkg.in/square/go-jose.v2 v2.6.0
 	gopkg.in/yaml.v2 v2.4.0
diff --git a/go.sum b/go.sum
index 77e8ecaa0a5..c32fe8b1c48 100644
--- a/go.sum
+++ b/go.sum
@@ -2904,8 +2904,8 @@ google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGO
 google.golang.org/grpc v1.56.1/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
 google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
 google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo=
-google.golang.org/grpc v1.58.2 h1:SXUpjxeVF3FKrTYQI4f4KvbGD5u2xccdYdurwowix5I=
-google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
+google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ=
+google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
 google.golang.org/grpc/examples v0.0.0-20211102180624-670c133e568e h1:m7aQHHqd0q89mRwhwS9Bx2rjyl/hsFAeta+uGrHsQaU=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
diff --git a/protogen/gen/ocis/services/thumbnails/v0/thumbnails.pb.go b/protogen/gen/ocis/services/thumbnails/v0/thumbnails.pb.go
index 25633f26e52..1b0aa92c6d3 100644
--- a/protogen/gen/ocis/services/thumbnails/v0/thumbnails.pb.go
+++ b/protogen/gen/ocis/services/thumbnails/v0/thumbnails.pb.go
@@ -36,6 +36,8 @@ type GetThumbnailRequest struct {
 	Width int32 `protobuf:"varint,3,opt,name=width,proto3" json:"width,omitempty"`
 	// The height of the thumbnail
 	Height int32 `protobuf:"varint,4,opt,name=height,proto3" json:"height,omitempty"`
+	// Indicates which processor should be used
+	Processor string `protobuf:"bytes,5,opt,name=processor,proto3" json:"processor,omitempty"`
 	// Types that are assignable to Source:
 	//
 	//	*GetThumbnailRequest_WebdavSource
@@ -103,6 +105,13 @@ func (x *GetThumbnailRequest) GetHeight() int32 {
 	return 0
 }
 
+func (x *GetThumbnailRequest) GetProcessor() string {
+	if x != nil {
+		return x.Processor
+	}
+	return ""
+}
+
 func (m *GetThumbnailRequest) GetSource() isGetThumbnailRequest_Source {
 	if m != nil {
 		return m.Source
@@ -129,11 +138,11 @@ type isGetThumbnailRequest_Source interface {
 }
 
 type GetThumbnailRequest_WebdavSource struct {
-	WebdavSource *v0.WebdavSource `protobuf:"bytes,5,opt,name=webdav_source,json=webdavSource,proto3,oneof"`
+	WebdavSource *v0.WebdavSource `protobuf:"bytes,6,opt,name=webdav_source,json=webdavSource,proto3,oneof"`
 }
 
 type GetThumbnailRequest_Cs3Source struct {
-	Cs3Source *v0.CS3Source `protobuf:"bytes,6,opt,name=cs3_source,json=cs3Source,proto3,oneof"`
+	Cs3Source *v0.CS3Source `protobuf:"bytes,7,opt,name=cs3_source,json=cs3Source,proto3,oneof"`
 }
 
 func (*GetThumbnailRequest_WebdavSource) isGetThumbnailRequest_Source() {}
@@ -220,7 +229,7 @@ var file_ocis_services_thumbnails_v0_thumbnails_proto_rawDesc = []byte{
 	0x69, 0x6c, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
 	0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x76, 0x32, 0x2f,
 	0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69,
-	0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xd7, 0x02, 0x0a, 0x13, 0x47, 0x65,
+	0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xf5, 0x02, 0x0a, 0x13, 0x47, 0x65,
 	0x74, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
 	0x74, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20,
 	0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x70, 0x61, 0x74, 0x68, 0x12, 0x51, 0x0a,
@@ -231,58 +240,59 @@ var file_ocis_services_thumbnails_v0_thumbnails_proto_rawDesc = []byte{
 	0x65, 0x52, 0x0d, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x54, 0x79, 0x70, 0x65,
 	0x12, 0x14, 0x0a, 0x05, 0x77, 0x69, 0x64, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52,
 	0x05, 0x77, 0x69, 0x64, 0x74, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74,
-	0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x50,
-	0x0a, 0x0d, 0x77, 0x65, 0x62, 0x64, 0x61, 0x76, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18,
-	0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x6d, 0x65, 0x73,
-	0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73,
-	0x2e, 0x76, 0x30, 0x2e, 0x57, 0x65, 0x62, 0x64, 0x61, 0x76, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65,
-	0x48, 0x00, 0x52, 0x0c, 0x77, 0x65, 0x62, 0x64, 0x61, 0x76, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65,
-	0x12, 0x47, 0x0a, 0x0a, 0x63, 0x73, 0x33, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x06,
-	0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73,
-	0x61, 0x67, 0x65, 0x73, 0x2e, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2e,
-	0x76, 0x30, 0x2e, 0x43, 0x53, 0x33, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x48, 0x00, 0x52, 0x09,
-	0x63, 0x73, 0x33, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x42, 0x08, 0x0a, 0x06, 0x73, 0x6f, 0x75,
-	0x72, 0x63, 0x65, 0x22, 0x7e, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e,
-	0x61, 0x69, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x64,
-	0x61, 0x74, 0x61, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01,
-	0x28, 0x09, 0x52, 0x0c, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74,
-	0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x5f, 0x74, 0x6f, 0x6b,
-	0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66,
-	0x65, 0x72, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x69, 0x6d, 0x65, 0x74,
-	0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x69, 0x6d, 0x65, 0x74,
-	0x79, 0x70, 0x65, 0x32, 0x87, 0x01, 0x0a, 0x10, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69,
-	0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x73, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x54,
-	0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x12, 0x30, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e,
+	0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1c,
+	0x0a, 0x09, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x6f, 0x72, 0x12, 0x50, 0x0a, 0x0d,
+	0x77, 0x65, 0x62, 0x64, 0x61, 0x76, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x06, 0x20,
+	0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61,
+	0x67, 0x65, 0x73, 0x2e, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x76,
+	0x30, 0x2e, 0x57, 0x65, 0x62, 0x64, 0x61, 0x76, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x48, 0x00,
+	0x52, 0x0c, 0x77, 0x65, 0x62, 0x64, 0x61, 0x76, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x47,
+	0x0a, 0x0a, 0x63, 0x73, 0x33, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01,
+	0x28, 0x0b, 0x32, 0x26, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67,
+	0x65, 0x73, 0x2e, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x76, 0x30,
+	0x2e, 0x43, 0x53, 0x33, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x48, 0x00, 0x52, 0x09, 0x63, 0x73,
+	0x33, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x42, 0x08, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63,
+	0x65, 0x22, 0x7e, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69,
+	0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x61, 0x74,
+	0x61, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x0c, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x25,
+	0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e,
+	0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72,
+	0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x69, 0x6d, 0x65, 0x74, 0x79, 0x70,
+	0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x69, 0x6d, 0x65, 0x74, 0x79, 0x70,
+	0x65, 0x32, 0x87, 0x01, 0x0a, 0x10, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x53,
+	0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x73, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x54, 0x68, 0x75,
+	0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x12, 0x30, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x73, 0x65,
+	0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c,
+	0x73, 0x2e, 0x76, 0x30, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69,
+	0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e,
 	0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61,
 	0x69, 0x6c, 0x73, 0x2e, 0x76, 0x30, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e,
-	0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x6f, 0x63, 0x69,
-	0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x68, 0x75, 0x6d, 0x62,
-	0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x76, 0x30, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, 0x75, 0x6d,
-	0x62, 0x6e, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0xe9, 0x02,
-	0x5a, 0x41, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x77, 0x6e,
-	0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6f, 0x63, 0x69, 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-	0x67, 0x65, 0x6e, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x6f, 0x63, 0x69, 0x73, 0x2f, 0x73, 0x65, 0x72,
-	0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73,
-	0x2f, 0x76, 0x30, 0x92, 0x41, 0xa2, 0x02, 0x12, 0xb8, 0x01, 0x0a, 0x22, 0x6f, 0x77, 0x6e, 0x43,
-	0x6c, 0x6f, 0x75, 0x64, 0x20, 0x49, 0x6e, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x65, 0x20, 0x53, 0x63,
-	0x61, 0x6c, 0x65, 0x20, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x22, 0x47,
-	0x0a, 0x0d, 0x6f, 0x77, 0x6e, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x20, 0x47, 0x6d, 0x62, 0x48, 0x12,
-	0x20, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
-	0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6f, 0x63, 0x69,
-	0x73, 0x1a, 0x14, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x40, 0x6f, 0x77, 0x6e, 0x63, 0x6c,
-	0x6f, 0x75, 0x64, 0x2e, 0x63, 0x6f, 0x6d, 0x2a, 0x42, 0x0a, 0x0a, 0x41, 0x70, 0x61, 0x63, 0x68,
-	0x65, 0x2d, 0x32, 0x2e, 0x30, 0x12, 0x34, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67,
-	0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f,
-	0x75, 0x64, 0x2f, 0x6f, 0x63, 0x69, 0x73, 0x2f, 0x62, 0x6c, 0x6f, 0x62, 0x2f, 0x6d, 0x61, 0x73,
-	0x74, 0x65, 0x72, 0x2f, 0x4c, 0x49, 0x43, 0x45, 0x4e, 0x53, 0x45, 0x32, 0x05, 0x31, 0x2e, 0x30,
-	0x2e, 0x30, 0x2a, 0x02, 0x01, 0x02, 0x32, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74,
-	0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x3a, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63,
-	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x3d, 0x0a, 0x10, 0x44, 0x65,
-	0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x12, 0x29,
-	0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64,
-	0x2e, 0x64, 0x65, 0x76, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x74, 0x68,
-	0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-	0x33,
+	0x61, 0x69, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0xe9, 0x02, 0x5a, 0x41,
+	0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x77, 0x6e, 0x63, 0x6c,
+	0x6f, 0x75, 0x64, 0x2f, 0x6f, 0x63, 0x69, 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x67, 0x65,
+	0x6e, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x6f, 0x63, 0x69, 0x73, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69,
+	0x63, 0x65, 0x73, 0x2f, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2f, 0x76,
+	0x30, 0x92, 0x41, 0xa2, 0x02, 0x12, 0xb8, 0x01, 0x0a, 0x22, 0x6f, 0x77, 0x6e, 0x43, 0x6c, 0x6f,
+	0x75, 0x64, 0x20, 0x49, 0x6e, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x65, 0x20, 0x53, 0x63, 0x61, 0x6c,
+	0x65, 0x20, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x22, 0x47, 0x0a, 0x0d,
+	0x6f, 0x77, 0x6e, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x20, 0x47, 0x6d, 0x62, 0x48, 0x12, 0x20, 0x68,
+	0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
+	0x6d, 0x2f, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6f, 0x63, 0x69, 0x73, 0x1a,
+	0x14, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x40, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75,
+	0x64, 0x2e, 0x63, 0x6f, 0x6d, 0x2a, 0x42, 0x0a, 0x0a, 0x41, 0x70, 0x61, 0x63, 0x68, 0x65, 0x2d,
+	0x32, 0x2e, 0x30, 0x12, 0x34, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74,
+	0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64,
+	0x2f, 0x6f, 0x63, 0x69, 0x73, 0x2f, 0x62, 0x6c, 0x6f, 0x62, 0x2f, 0x6d, 0x61, 0x73, 0x74, 0x65,
+	0x72, 0x2f, 0x4c, 0x49, 0x43, 0x45, 0x4e, 0x53, 0x45, 0x32, 0x05, 0x31, 0x2e, 0x30, 0x2e, 0x30,
+	0x2a, 0x02, 0x01, 0x02, 0x32, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+	0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x3a, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74,
+	0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x3d, 0x0a, 0x10, 0x44, 0x65, 0x76, 0x65,
+	0x6c, 0x6f, 0x70, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x12, 0x29, 0x68, 0x74,
+	0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x64,
+	0x65, 0x76, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x74, 0x68, 0x75, 0x6d,
+	0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
diff --git a/protogen/proto/ocis/services/thumbnails/v0/thumbnails.proto b/protogen/proto/ocis/services/thumbnails/v0/thumbnails.proto
index 03c1dba60c0..f3499e9e91f 100644
--- a/protogen/proto/ocis/services/thumbnails/v0/thumbnails.proto
+++ b/protogen/proto/ocis/services/thumbnails/v0/thumbnails.proto
@@ -47,9 +47,11 @@ message GetThumbnailRequest {
     int32 width = 3;
     // The height of the thumbnail
     int32 height = 4;
+    // Indicates which image processor to use
+    string processor = 5;
     oneof source {
-      ocis.messages.thumbnails.v0.WebdavSource webdav_source = 5;
-      ocis.messages.thumbnails.v0.CS3Source cs3_source = 6;
+      ocis.messages.thumbnails.v0.WebdavSource webdav_source = 6;
+      ocis.messages.thumbnails.v0.CS3Source cs3_source = 7;
     }
 }
 
diff --git a/services/idp/package.json b/services/idp/package.json
index 84bfc1cbe6d..55774e6c1fa 100644
--- a/services/idp/package.json
+++ b/services/idp/package.json
@@ -134,7 +134,7 @@
     "i18next-conv": "^14.0.0",
     "i18next-parser": "^8.7.0",
     "jest": "29.7.0",
-    "license-checker-rseidelsohn": "^4.2.6",
+    "license-checker-rseidelsohn": "^4.2.10",
     "mini-css-extract-plugin": "1.6.2",
     "optimize-css-assets-webpack-plugin": "6.0.1",
     "pnp-webpack-plugin": "1.7.0",
diff --git a/services/idp/pnpm-lock.yaml b/services/idp/pnpm-lock.yaml
index 5128fb384bf..df2e937473e 100644
--- a/services/idp/pnpm-lock.yaml
+++ b/services/idp/pnpm-lock.yaml
@@ -191,8 +191,8 @@ devDependencies:
     specifier: 29.7.0
     version: 29.7.0(@types/node@20.5.9)
   license-checker-rseidelsohn:
-    specifier: ^4.2.6
-    version: 4.2.6
+    specifier: ^4.2.10
+    version: 4.2.10
   mini-css-extract-plugin:
     specifier: 1.6.2
     version: 1.6.2(webpack@4.47.0)
@@ -3364,8 +3364,9 @@ packages:
     resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==}
     dev: true
 
-  /abbrev@1.1.1:
-    resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
+  /abbrev@2.0.0:
+    resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==}
+    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
     dev: true
 
   /abort-controller@3.0.0:
@@ -8152,8 +8153,8 @@ packages:
       type-check: 0.4.0
     dev: true
 
-  /license-checker-rseidelsohn@4.2.6:
-    resolution: {integrity: sha512-cV69Exm7y7stSVoYpFaQ1rEdmTtA7y1bMgTeeB1fqE0TZRkW1zpn8uIsIKeHK5rMvJYQzEuExNmLOTDXJMplgg==}
+  /license-checker-rseidelsohn@4.2.10:
+    resolution: {integrity: sha512-phvcph9WTQ/Kb209kqwLxVA6CV8BAeUnFMIo+I+wBlp74El4ngbMwq49qSqqPYC5bfj49EWBXqwjvTFswGE2Fg==}
     engines: {node: '>=18', npm: '>=8'}
     hasBin: true
     dependencies:
@@ -8161,7 +8162,7 @@ packages:
       debug: 4.3.4
       lodash.clonedeep: 4.5.0
       mkdirp: 1.0.4
-      nopt: 5.0.0
+      nopt: 7.2.0
       read-installed-packages: 2.0.1
       semver: 7.5.4
       spdx-correct: 3.2.0
@@ -8682,12 +8683,12 @@ packages:
   /node-releases@2.0.13:
     resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==}
 
-  /nopt@5.0.0:
-    resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==}
-    engines: {node: '>=6'}
+  /nopt@7.2.0:
+    resolution: {integrity: sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA==}
+    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
     hasBin: true
     dependencies:
-      abbrev: 1.1.1
+      abbrev: 2.0.0
     dev: true
 
   /normalize-package-data@5.0.0:
diff --git a/services/notifications/README.md b/services/notifications/README.md
index c75a6853946..7b233e36cda 100644
--- a/services/notifications/README.md
+++ b/services/notifications/README.md
@@ -66,3 +66,7 @@ Important: For the time being, the embedded ownCloud Web frontend only supports
 *   If a requested language code is not available, the service tries to fall back to the base language if available. For example, if the requested language-code `de_DE` is not available, the service tries to fall back to translations in the `de` folder.
 *   If the base language `de` is also not available, the service falls back to the system's default English (`en`),
 which is the source of the texts provided by the code.
+
+## Default Language
+
+The default language can be defined via the `OCIS_DEFAULT_LANGUAGE` environment variable. See the `settings` service for a detailed description.
diff --git a/services/notifications/pkg/command/server.go b/services/notifications/pkg/command/server.go
index 8887bf1ff61..a6355d11295 100644
--- a/services/notifications/pkg/command/server.go
+++ b/services/notifications/pkg/command/server.go
@@ -116,7 +116,7 @@ func Server(cfg *config.Config) *cli.Command {
 				logger.Fatal().Err(err).Str("addr", cfg.Notifications.RevaGateway).Msg("could not get reva gateway selector")
 			}
 			valueService := settingssvc.NewValueService("com.owncloud.api.settings", grpcClient)
-			svc := service.NewEventsNotifier(evts, channel, logger, gatewaySelector, valueService, cfg.ServiceAccount.ServiceAccountID, cfg.ServiceAccount.ServiceAccountSecret, cfg.Notifications.EmailTemplatePath, cfg.WebUIURL)
+			svc := service.NewEventsNotifier(evts, channel, logger, gatewaySelector, valueService, cfg.ServiceAccount.ServiceAccountID, cfg.ServiceAccount.ServiceAccountSecret, cfg.Notifications.EmailTemplatePath, cfg.Notifications.DefaultLanguage, cfg.WebUIURL)
 
 			gr.Add(svc.Run, func(error) {
 				cancel()
diff --git a/services/notifications/pkg/config/config.go b/services/notifications/pkg/config/config.go
index 2f805854079..74908dda50c 100644
--- a/services/notifications/pkg/config/config.go
+++ b/services/notifications/pkg/config/config.go
@@ -1,3 +1,4 @@
+// Package config provides the service configuration.
 package config
 
 import (
@@ -31,6 +32,7 @@ type Notifications struct {
 	Events            Events                `yaml:"events"`
 	EmailTemplatePath string                `yaml:"email_template_path" env:"OCIS_EMAIL_TEMPLATE_PATH;NOTIFICATIONS_EMAIL_TEMPLATE_PATH" desc:"Path to Email notification templates overriding embedded ones."`
 	TranslationPath   string                `yaml:"translation_path" env:"OCIS_TRANSLATION_PATH,NOTIFICATIONS_TRANSLATION_PATH" desc:"(optional) Set this to a path with custom translations to overwrite the builtin translations. Note that file and folder naming rules apply, see the documentation for more details."`
+	DefaultLanguage   string                `yaml:"default_language" env:"OCIS_DEFAULT_LANGUAGE" desc:"The default language used by services and the WebUI. If not defined, English will be used as default. See the documentation for more details."`
 	RevaGateway       string                `yaml:"reva_gateway" env:"OCIS_REVA_GATEWAY" desc:"CS3 gateway used to look up user metadata"`
 	GRPCClientTLS     *shared.GRPCClientTLS `yaml:"grpc_client_tls"`
 }
diff --git a/services/notifications/pkg/email/composer.go b/services/notifications/pkg/email/composer.go
index 8a9c61bc522..81f4cab1acb 100644
--- a/services/notifications/pkg/email/composer.go
+++ b/services/notifications/pkg/email/composer.go
@@ -9,9 +9,9 @@ import (
 )
 
 // NewTextTemplate replace the body message template placeholders with the translated template
-func NewTextTemplate(mt MessageTemplate, locale string, translationPath string, vars map[string]string) (MessageTemplate, error) {
+func NewTextTemplate(mt MessageTemplate, locale, defaultLocale string, translationPath string, vars map[string]string) (MessageTemplate, error) {
 	var err error
-	t := l10n.NewTranslator(locale, translationPath)
+	t := l10n.NewTranslator(locale, defaultLocale, translationPath)
 	mt.Subject, err = composeMessage(t.Translate(mt.Subject), vars)
 	if err != nil {
 		return mt, err
@@ -32,9 +32,9 @@ func NewTextTemplate(mt MessageTemplate, locale string, translationPath string,
 }
 
 // NewHTMLTemplate replace the body message template placeholders with the translated template
-func NewHTMLTemplate(mt MessageTemplate, locale string, translationPath string, vars map[string]string) (MessageTemplate, error) {
+func NewHTMLTemplate(mt MessageTemplate, locale, defaultLocale string, translationPath string, vars map[string]string) (MessageTemplate, error) {
 	var err error
-	t := l10n.NewTranslator(locale, translationPath)
+	t := l10n.NewTranslator(locale, defaultLocale, translationPath)
 	mt.Subject, err = composeMessage(t.Translate(mt.Subject), vars)
 	if err != nil {
 		return mt, err
diff --git a/services/notifications/pkg/email/email.go b/services/notifications/pkg/email/email.go
index 03a37b4992c..b1b78931407 100644
--- a/services/notifications/pkg/email/email.go
+++ b/services/notifications/pkg/email/email.go
@@ -26,8 +26,8 @@ var (
 )
 
 // RenderEmailTemplate renders the email template for a new share
-func RenderEmailTemplate(mt MessageTemplate, locale string, emailTemplatePath string, translationPath string, vars map[string]string) (*channels.Message, error) {
-	textMt, err := NewTextTemplate(mt, locale, translationPath, vars)
+func RenderEmailTemplate(mt MessageTemplate, locale, defaultLocale string, emailTemplatePath string, translationPath string, vars map[string]string) (*channels.Message, error) {
+	textMt, err := NewTextTemplate(mt, locale, defaultLocale, translationPath, vars)
 	if err != nil {
 		return nil, err
 	}
@@ -39,7 +39,7 @@ func RenderEmailTemplate(mt MessageTemplate, locale string, emailTemplatePath st
 	if err != nil {
 		return nil, err
 	}
-	htmlMt, err := NewHTMLTemplate(mt, locale, translationPath, escapeStringMap(vars))
+	htmlMt, err := NewHTMLTemplate(mt, locale, defaultLocale, translationPath, escapeStringMap(vars))
 	if err != nil {
 		return nil, err
 	}
diff --git a/services/notifications/pkg/email/l10n/locate.go b/services/notifications/pkg/email/l10n/locate.go
index 37d99f34f08..65ced721033 100644
--- a/services/notifications/pkg/email/l10n/locate.go
+++ b/services/notifications/pkg/email/l10n/locate.go
@@ -22,11 +22,19 @@ type Translator interface {
 }
 
 type translator struct {
-	l *gotext.Locale
+	locale *gotext.Locale
 }
 
 // NewTranslator Create Translator with library path and language code and load default domain
-func NewTranslator(local string, path string) Translator {
+func NewTranslator(locale, defaultLocale string, path string) Translator {
+	l := newLocate(locale, path)
+	if locale != "en" && len(l.GetTranslations()) == 0 {
+		l = newLocate(defaultLocale, path)
+	}
+	return &translator{locale: l}
+}
+
+func newLocate(local string, path string) *gotext.Locale {
 	var l *gotext.Locale
 	if path == "" {
 		filesystem, _ := fs.Sub(_translationFS, "locale")
@@ -35,9 +43,9 @@ func NewTranslator(local string, path string) Translator {
 		l = gotext.NewLocale(path, local)
 	}
 	l.AddDomain(_domain) // make domain configurable only if needed
-	return &translator{l: l}
+	return l
 }
 
 func (t *translator) Translate(str string) string {
-	return t.l.Get(str)
+	return t.locale.Get(str)
 }
diff --git a/services/notifications/pkg/service/service.go b/services/notifications/pkg/service/service.go
index a8a3bf0e7ae..8b4de702fb9 100644
--- a/services/notifications/pkg/service/service.go
+++ b/services/notifications/pkg/service/service.go
@@ -42,7 +42,7 @@ func NewEventsNotifier(
 	logger log.Logger,
 	gatewaySelector pool.Selectable[gateway.GatewayAPIClient],
 	valueService settingssvc.ValueService,
-	serviceAccountID, serviceAccountSecret, emailTemplatePath, ocisURL string) Service {
+	serviceAccountID, serviceAccountSecret, emailTemplatePath, defaultLanguage, ocisURL string) Service {
 
 	return eventsNotifier{
 		logger:               logger,
@@ -54,6 +54,7 @@ func NewEventsNotifier(
 		serviceAccountID:     serviceAccountID,
 		serviceAccountSecret: serviceAccountSecret,
 		emailTemplatePath:    emailTemplatePath,
+		defaultLanguage:      defaultLanguage,
 		ocisURL:              ocisURL,
 	}
 }
@@ -67,6 +68,7 @@ type eventsNotifier struct {
 	valueService         settingssvc.ValueService
 	emailTemplatePath    string
 	translationPath      string
+	defaultLanguage      string
 	ocisURL              string
 	serviceAccountID     string
 	serviceAccountSecret string
@@ -109,7 +111,7 @@ func (s eventsNotifier) render(ctx context.Context, template email.MessageTempla
 		locale := s.getUserLang(ctx, usr.GetId())
 		fields[granteeFieldName] = usr.GetDisplayName()
 
-		rendered, err := email.RenderEmailTemplate(template, locale, s.emailTemplatePath, s.translationPath, fields)
+		rendered, err := email.RenderEmailTemplate(template, locale, s.defaultLanguage, s.emailTemplatePath, s.translationPath, fields)
 		if err != nil {
 			return nil, err
 		}
diff --git a/services/proxy/pkg/middleware/authentication.go b/services/proxy/pkg/middleware/authentication.go
index f81e4dec183..a726eae4c31 100644
--- a/services/proxy/pkg/middleware/authentication.go
+++ b/services/proxy/pkg/middleware/authentication.go
@@ -89,13 +89,7 @@ func Authentication(auths []Authenticator, opts ...Option) func(next http.Handle
 				}
 
 				// if the request is not bound to any user agent, write all available challenges
-				if !touch &&
-					// This is a temporary hack... Before the authentication middleware rewrite all
-					// unauthenticated requests were still handled. The reva http services then did add
-					// the supported authentication headers to the response. Since we are not allowing the
-					// requests to continue so far we have to do it here. But we shouldn't do it for the graph service.
-					// That's the reason for this hard check here.
-					!strings.HasPrefix(r.URL.Path, "/graph") {
+				if !touch {
 					writeSupportedAuthenticateHeader(w, r)
 				}
 			}
@@ -204,7 +198,6 @@ func evalRequestURI(l userAgentLocker, r regexp.Regexp) {
 			return
 		}
 	}
-	l.w.Header().Add(WwwAuthenticate, fmt.Sprintf("%v realm=\"%s\", charset=\"UTF-8\"", caser.String(l.fallback), l.r.Host))
 }
 
 func getTraceProvider(o Options) trace.TracerProvider {
diff --git a/services/proxy/pkg/proxy/proxy.go b/services/proxy/pkg/proxy/proxy.go
index 0694f661975..4d3894c84d0 100644
--- a/services/proxy/pkg/proxy/proxy.go
+++ b/services/proxy/pkg/proxy/proxy.go
@@ -36,9 +36,9 @@ func NewMultiHostReverseProxy(opts ...Option) (*MultiHostReverseProxy, error) {
 		config:    options.Config,
 	}
 
-	rp.Director = func(r *http.Request) {
-		ri := router.ContextRoutingInfo(r.Context())
-		ri.Director()(r)
+	rp.Rewrite = func(r *httputil.ProxyRequest) {
+		ri := router.ContextRoutingInfo(r.In.Context())
+		ri.Rewrite()(r)
 	}
 
 	tlsConf := &tls.Config{
diff --git a/services/proxy/pkg/router/router.go b/services/proxy/pkg/router/router.go
index d61e99a720b..85734ae5d46 100644
--- a/services/proxy/pkg/router/router.go
+++ b/services/proxy/pkg/router/router.go
@@ -3,6 +3,7 @@ package router
 import (
 	"context"
 	"net/http"
+	"net/http/httputil"
 	"net/url"
 	"regexp"
 	"strings"
@@ -57,7 +58,7 @@ func New(policySelector *config.PolicySelector, policies []config.Policy, logger
 
 	r := Router{
 		logger:         logger,
-		directors:      make(map[string]map[config.RouteType]map[string][]RoutingInfo),
+		rewriters:      make(map[string]map[config.RouteType]map[string][]RoutingInfo),
 		policySelector: selector,
 	}
 	for _, pol := range policies {
@@ -83,16 +84,16 @@ func New(policySelector *config.PolicySelector, policies []config.Policy, logger
 	return r
 }
 
-// RoutingInfo contains the proxy director and some information about the route.
+// RoutingInfo contains the proxy rewrite hook and some information about the route.
 type RoutingInfo struct {
-	director    func(*http.Request)
+	rewrite     func(*httputil.ProxyRequest)
 	endpoint    string
 	unprotected bool
 }
 
-// Director returns the proxy director.
-func (r RoutingInfo) Director() func(*http.Request) {
-	return r.director
+// Rewrite returns the proxy rewrite hook.
+func (r RoutingInfo) Rewrite() func(*httputil.ProxyRequest) {
+	return r.rewrite
 }
 
 // IsRouteUnprotected returns true if the route doesn't need to be authenticated.
@@ -103,33 +104,33 @@ func (r RoutingInfo) IsRouteUnprotected() bool {
 // Router handles the routing of HTTP requests according to the given policies.
 type Router struct {
 	logger         log.Logger
-	directors      map[string]map[config.RouteType]map[string][]RoutingInfo
+	rewriters      map[string]map[config.RouteType]map[string][]RoutingInfo
 	policySelector policy.Selector
 }
 
 func (rt Router) addHost(policy string, target *url.URL, route config.Route) {
 	targetQuery := target.RawQuery
-	if rt.directors[policy] == nil {
-		rt.directors[policy] = make(map[config.RouteType]map[string][]RoutingInfo)
+	if rt.rewriters[policy] == nil {
+		rt.rewriters[policy] = make(map[config.RouteType]map[string][]RoutingInfo)
 	}
 	routeType := config.DefaultRouteType
 	if route.Type != "" {
 		routeType = route.Type
 	}
-	if rt.directors[policy][routeType] == nil {
-		rt.directors[policy][routeType] = make(map[string][]RoutingInfo)
+	if rt.rewriters[policy][routeType] == nil {
+		rt.rewriters[policy][routeType] = make(map[string][]RoutingInfo)
 	}
-	if rt.directors[policy][routeType][route.Method] == nil {
-		rt.directors[policy][routeType][route.Method] = make([]RoutingInfo, 0)
+	if rt.rewriters[policy][routeType][route.Method] == nil {
+		rt.rewriters[policy][routeType][route.Method] = make([]RoutingInfo, 0)
 	}
 
 	reg := registry.GetRegistry()
 	sel := selector.NewSelector(selector.Registry(reg))
 
-	rt.directors[policy][routeType][route.Method] = append(rt.directors[policy][routeType][route.Method], RoutingInfo{
+	rt.rewriters[policy][routeType][route.Method] = append(rt.rewriters[policy][routeType][route.Method], RoutingInfo{
 		endpoint:    route.Endpoint,
 		unprotected: route.Unprotected,
-		director: func(req *http.Request) {
+		rewrite: func(req *httputil.ProxyRequest) {
 			if route.Service != "" {
 				// select next node
 				next, err := sel.Select(route.Service)
@@ -146,32 +147,33 @@ func (rt Router) addHost(policy string, target *url.URL, route config.Route) {
 						Msg("could not select next node")
 					return // TODO error? fallback to target.Host & Scheme?
 				}
-				req.URL.Host = node.Address
-				req.URL.Scheme = node.Metadata["protocol"] // TODO check property exists?
+				req.Out.URL.Host = node.Address
+				req.Out.URL.Scheme = node.Metadata["protocol"] // TODO check property exists?
 				if node.Metadata["use_tls"] == "true" {
-					req.URL.Scheme = "https"
+					req.Out.URL.Scheme = "https"
 				}
 			} else {
-				req.URL.Host = target.Host
-				req.URL.Scheme = target.Scheme
+				req.Out.URL.Host = target.Host
+				req.Out.URL.Scheme = target.Scheme
 			}
 
-			// Apache deployments host addresses need to match on req.Host and req.URL.Host
+			// Apache deployments host addresses need to match on req.Out.Host and req.Out.URL.Host
 			// see https://stackoverflow.com/questions/34745654/golang-reverseproxy-with-apache2-sni-hostname-error
 			if route.ApacheVHost {
-				req.Host = target.Host
+				req.Out.Host = target.Host
 			}
 
-			req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path)
-			if targetQuery == "" || req.URL.RawQuery == "" {
-				req.URL.RawQuery = targetQuery + req.URL.RawQuery
+			req.Out.URL.Path = singleJoiningSlash(target.Path, req.Out.URL.Path)
+			if targetQuery == "" || req.Out.URL.RawQuery == "" {
+				req.Out.URL.RawQuery = targetQuery + req.Out.URL.RawQuery
 			} else {
-				req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
+				req.Out.URL.RawQuery = targetQuery + "&" + req.Out.URL.RawQuery
 			}
-			if _, ok := req.Header["User-Agent"]; !ok {
+			if _, ok := req.Out.Header["User-Agent"]; !ok {
 				// explicitly disable User-Agent so it's not set to default value
-				req.Header.Set("User-Agent", "")
+				req.Out.Header.Set("User-Agent", "")
 			}
+			req.SetXForwarded()
 		},
 	})
 }
@@ -184,7 +186,7 @@ func (rt Router) Route(r *http.Request) (RoutingInfo, bool) {
 		return noInfo, false
 	}
 
-	if _, ok := rt.directors[pol]; !ok {
+	if _, ok := rt.rewriters[pol]; !ok {
 		rt.logger.
 			Error().
 			Str("policy", pol).
@@ -193,7 +195,7 @@ func (rt Router) Route(r *http.Request) (RoutingInfo, bool) {
 	}
 
 	method := ""
-	// find matching director
+	// find matching rewrite hook
 	for _, rtype := range config.RouteTypes {
 		var handler func(string, url.URL) bool
 		switch rtype {
@@ -206,12 +208,12 @@ func (rt Router) Route(r *http.Request) (RoutingInfo, bool) {
 		default:
 			handler = prefixRouteMatcher
 		}
-		if rt.directors[pol][rtype][r.Method] != nil {
+		if rt.rewriters[pol][rtype][r.Method] != nil {
 			// use specific method
 			method = r.Method
 		}
 
-		for _, ri := range rt.directors[pol][rtype][method] {
+		for _, ri := range rt.rewriters[pol][rtype][method] {
 			if handler(ri.endpoint, *r.URL) {
 				rt.logger.Debug().
 					Str("policy", pol).
@@ -219,17 +221,17 @@ func (rt Router) Route(r *http.Request) (RoutingInfo, bool) {
 					Str("prefix", ri.endpoint).
 					Str("path", r.URL.Path).
 					Str("routeType", string(rtype)).
-					Msg("director found")
+					Msg("rewrite hook found")
 
 				return ri, true
 			}
 		}
 	}
 
-	// override default director with root. If any
-	if ri := rt.directors[pol][config.PrefixRoute][method][0]; ri.endpoint == "/" { // try specific method
+	// override default rewrite hook with root. If any
+	if ri := rt.rewriters[pol][config.PrefixRoute][method][0]; ri.endpoint == "/" { // try specific method
 		return ri, true
-	} else if ri := rt.directors[pol][config.PrefixRoute][""][0]; ri.endpoint == "/" { // fallback to unspecific method
+	} else if ri := rt.rewriters[pol][config.PrefixRoute][""][0]; ri.endpoint == "/" { // fallback to unspecific method
 		return ri, true
 	}
 
@@ -237,7 +239,7 @@ func (rt Router) Route(r *http.Request) (RoutingInfo, bool) {
 		Warn().
 		Str("policy", pol).
 		Str("path", r.URL.Path).
-		Msg("no director found")
+		Msg("no rewrite hook found")
 	return noInfo, false
 }
 
diff --git a/services/settings/README.md b/services/settings/README.md
index a22272cee6b..afcc394773e 100644
--- a/services/settings/README.md
+++ b/services/settings/README.md
@@ -76,6 +76,16 @@ The settings service needs to know the ID's of service accounts but it doesn't n
 
 ## Default Language
 
-The default language can be defined via `SETTINGS_DEFAULT_LANGUAGE` environment variable. If this variable is not defined, English will be used as default. The value has the ISO 639-1 format ("de", "en", etc.) and is limited by the list supported languages. This setting can be used to set the default language for invitation emails.
+The default language can be defined via the `OCIS_DEFAULT_LANGUAGE` environment variable. If this variable is not defined, English will be used as default. The value has the ISO 639-1 format ("de", "en", etc.) and is limited by the list supported languages. This setting can be used to set the default language for notification and invitation emails.
 
-Important developer note: the list of supported languages is at the moment not easy defineable, as it is the minimum intersection of languages shown in the WebUI and languages defined in the ocis code for the use of notifications. Even more, not all languages where there are translations available on transifex, are available in the WebUI respectively for ocis notifications, and the translation rate for existing languages is partially not that high. You will see therefore quite often English default strings though a supported language may exist and was selected.
+Important developer note: the list of supported languages is at the moment not easy defineable, as it is the minimum intersection of languages shown in the WebUI and languages defined in the ocis code for the use of notifications and userlog. Even more, not all languages where there are translations available on transifex, are available in the WebUI respectively for ocis notifications, and the translation rate for existing languages is partially not that high. You will see therefore quite often English default strings though a supported language may exist and was selected.
+
+The `OCIS_DEFAULT_LANGUAGE` setting impacts the `notification` and `userlog` services and the WebUI. Note that translations must exist for all named components to be presented correctly.
+
+*   If  `OCIS_DEFAULT_LANGUAGE` **is not set**, the expected behavior is:
+    *   The `notification` and `userlog` services and the WebUI use English by default until a user sets another language in the WebUI via _Account -> Language_.
+    *    If a user sets another language in the WebUI in _Account -> Language_, then the `notification` and `userlog` services and WebUI use the language defined by the user. If no translation is found, it falls back to English.
+
+*   If  `OCIS_DEFAULT_LANGUAGE` **is set**, the expected behavior is:
+    *   The `notification` and `userlog` services and the WebUI use `OCIS_DEFAULT_LANGUAGE`  by default until a user sets another language in the WebUI via _Account -> Language_.
+    *   If a user sets another language in the WebUI in _Account -> Language_, the `notification` and `userlog` services and WebUI use the language defined by the user. If no translation is found, it falls back to `OCIS_DEFAULT_LANGUAGE` and then to English.
diff --git a/services/settings/pkg/config/config.go b/services/settings/pkg/config/config.go
index 7b75412021e..1791a4ac6ec 100644
--- a/services/settings/pkg/config/config.go
+++ b/services/settings/pkg/config/config.go
@@ -39,7 +39,7 @@ type Config struct {
 
 	ServiceAccountIDAdmin string `yaml:"service_account_id_admin" env:"OCIS_SERVICE_ACCOUNT_ID;SETTINGS_SERVICE_ACCOUNT_ID_ADMIN" desc:"The ID of the service account having the admin role. See the 'auth-service' service description for more details."`
 
-	DefaultLanguage string `yaml:"default_language" env:"SETTINGS_DEFAULT_LANGUAGE" desc:"The default language. If not defined, English will be used as default. See the documentation for more details."`
+	DefaultLanguage string `yaml:"default_language" env:"OCIS_DEFAULT_LANGUAGE" desc:"The default language used by services and the WebUI. If not defined, English will be used as default. See the documentation for more details."`
 
 	Context context.Context `yaml:"-"`
 }
diff --git a/services/sharing/pkg/config/config.go b/services/sharing/pkg/config/config.go
index c2b24e61201..a00441fe899 100644
--- a/services/sharing/pkg/config/config.go
+++ b/services/sharing/pkg/config/config.go
@@ -149,6 +149,6 @@ type Events struct {
 	Addr              string `yaml:"endpoint" env:"OCIS_EVENTS_ENDPOINT;SHARING_EVENTS_ENDPOINT" desc:"The address of the event system. The event system is the message queuing service. It is used as message broker for the microservice architecture."`
 	ClusterID         string `yaml:"cluster" env:"OCIS_EVENTS_CLUSTER;SHARING_EVENTS_CLUSTER" desc:"The clusterID of the event system. The event system is the message queuing service. It is used as message broker for the microservice architecture. Mandatory when using NATS as event system."`
 	TLSInsecure       bool   `yaml:"tls_insecure" env:"OCIS_INSECURE;SHARING_EVENTS_TLS_INSECURE" desc:"Whether to verify the server TLS certificates."`
-	TLSRootCaCertPath string `yaml:"tls_root_ca_cert_path" env:"OCIS_EVENTS_TLS_ROOT_CA_CERTIFICATE;SHARING_EVENTS_TLS_ROOT_CA_CERT" desc:"The root CA certificate used to validate the server's TLS certificate. If provided SHARING_EVENTS_TLS_INSECURE will be seen as false."`
+	TLSRootCaCertPath string `yaml:"tls_root_ca_cert_path" env:"OCIS_EVENTS_TLS_ROOT_CA_CERTIFICATE;SHARING_EVENTS_TLS_ROOT_CA_CERTIFICATE;SHARING_EVENTS_TLS_ROOT_CA_CERT" desc:"The root CA certificate used to validate the server's TLS certificate. If provided SHARING_EVENTS_TLS_INSECURE will be seen as false." deprecationVersion:"4.0.3" removalVersion:"5.0.0" deprecationInfo:"SHARING_EVENTS_TLS_ROOT_CA_CERT changing name for consistency" deprecationReplacement:"SHARING_EVENTS_TLS_ROOT_CA_CERTIFICATE"`
 	EnableTLS         bool   `yaml:"enable_tls" env:"OCIS_EVENTS_ENABLE_TLS;SHARING_EVENTS_ENABLE_TLS" desc:"Enable TLS for the connection to the events broker. The events broker is the ocis service which receives and delivers events between the services.."`
 }
diff --git a/services/thumbnails/README.md b/services/thumbnails/README.md
index 43dbc463d08..2d39563ee14 100644
--- a/services/thumbnails/README.md
+++ b/services/thumbnails/README.md
@@ -10,9 +10,9 @@ The relevant environment variables defining file locations are:
 -   (2) `STORAGE_USERS_OCIS_ROOT`
 -   (3) `THUMBNAILS_FILESYSTEMSTORAGE_ROOT`
 
-(1) ... Having a default set by the Infinite Scale code, but if defined, used as base path for other services.  
-(2) ... Source files, defaults to (1) plus path component, but can be freely defined if required.  
-(3) ... Target files, defaults to (1) plus path component, but can be freely defined if required.  
+(1) ... Having a default set by the Infinite Scale code, but if defined, used as base path for other services.
+(2) ... Source files, defaults to (1) plus path component, but can be freely defined if required.
+(3) ... Target files, defaults to (1) plus path component, but can be freely defined if required.
 
 For details and defaults for these environment variables see the ocis admin documentation.
 
@@ -45,9 +45,20 @@ Various resolutions can be defined via `THUMBNAILS_RESOLUTIONS`. A requestor can
 
 Example:
 
-Requested: 18x12  
-Available: 30x20, 15x10, 9x6  
-Returned: 15x10  
+Requested: 18x12
+Available: 30x20, 15x10, 9x6
+Returned: 15x10
+
+## Thumbnail Processors
+
+Image generation can be configured by defining different processors, following processors are available:
+
+*   `resize`
+*   `fit`
+*   `fill`
+*   `thumbnail`
+
+To apply one of those, a query parameter has to be added to the request, e.g. `?processor=fit`
 
 ## Deleting Thumbnails
 
diff --git a/services/thumbnails/pkg/service/grpc/v0/service.go b/services/thumbnails/pkg/service/grpc/v0/service.go
index 070613b0c03..fd97bfd897e 100644
--- a/services/thumbnails/pkg/service/grpc/v0/service.go
+++ b/services/thumbnails/pkg/service/grpc/v0/service.go
@@ -16,6 +16,10 @@ import (
 	"github.com/cs3org/reva/v2/pkg/storagespace"
 	"github.com/cs3org/reva/v2/pkg/utils"
 	"github.com/golang-jwt/jwt/v4"
+	"github.com/pkg/errors"
+	merrors "go-micro.dev/v4/errors"
+	"google.golang.org/grpc/metadata"
+
 	"github.com/owncloud/ocis/v2/ocis-pkg/log"
 	thumbnailssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/thumbnails/v0"
 	"github.com/owncloud/ocis/v2/services/thumbnails/pkg/preprocessor"
@@ -23,9 +27,6 @@ import (
 	tjwt "github.com/owncloud/ocis/v2/services/thumbnails/pkg/service/jwt"
 	"github.com/owncloud/ocis/v2/services/thumbnails/pkg/thumbnail"
 	"github.com/owncloud/ocis/v2/services/thumbnails/pkg/thumbnail/imgsource"
-	"github.com/pkg/errors"
-	merrors "go-micro.dev/v4/errors"
-	"google.golang.org/grpc/metadata"
 )
 
 // NewService returns a service implementation for Service.
@@ -124,7 +125,7 @@ func (g Thumbnail) handleCS3Source(ctx context.Context, req *thumbnailssvc.GetTh
 	if tType == "" {
 		tType = req.GetThumbnailType().String()
 	}
-	tr, err := thumbnail.PrepareRequest(int(req.Width), int(req.Height), tType, sRes.GetInfo().GetChecksum().GetSum())
+	tr, err := thumbnail.PrepareRequest(int(req.Width), int(req.Height), tType, sRes.GetInfo().GetChecksum().GetSum(), req.Processor)
 	if err != nil {
 		return "", merrors.BadRequest(g.serviceID, err.Error())
 	}
@@ -207,7 +208,7 @@ func (g Thumbnail) handleWebdavSource(ctx context.Context, req *thumbnailssvc.Ge
 	if tType == "" {
 		tType = req.GetThumbnailType().String()
 	}
-	tr, err := thumbnail.PrepareRequest(int(req.Width), int(req.Height), tType, sRes.GetInfo().GetChecksum().GetSum())
+	tr, err := thumbnail.PrepareRequest(int(req.Width), int(req.Height), tType, sRes.GetInfo().GetChecksum().GetSum(), req.Processor)
 	if err != nil {
 		return "", merrors.BadRequest(g.serviceID, err.Error())
 	}
diff --git a/services/thumbnails/pkg/thumbnail/encoding.go b/services/thumbnails/pkg/thumbnail/encoding.go
index 5126fd6af9b..96f71074439 100644
--- a/services/thumbnails/pkg/thumbnail/encoding.go
+++ b/services/thumbnails/pkg/thumbnail/encoding.go
@@ -114,8 +114,8 @@ func EncoderForType(fileType string) (Encoder, error) {
 }
 
 // GetExtForMime return the supported extension by mime
-func GetExtForMime(mime string) string {
-	ext := strings.TrimPrefix(strings.TrimSpace(strings.ToLower(mime)), "image/")
+func GetExtForMime(fileType string) string {
+	ext := strings.TrimPrefix(strings.TrimSpace(strings.ToLower(fileType)), "image/")
 	switch ext {
 	case typeJpg, typeJpeg, typePng, typeGif:
 		return ext
diff --git a/services/thumbnails/pkg/thumbnail/generator.go b/services/thumbnails/pkg/thumbnail/generator.go
index b22c15da458..e8d967c4eb9 100644
--- a/services/thumbnails/pkg/thumbnail/generator.go
+++ b/services/thumbnails/pkg/thumbnail/generator.go
@@ -1,7 +1,6 @@
 package thumbnail
 
 import (
-	"errors"
 	"image"
 	"image/color"
 	"image/draw"
@@ -11,36 +10,34 @@ import (
 	"github.com/disintegration/imaging"
 )
 
-var (
-	// ErrInvalidType represents the error when a type can't be encoded.
-	ErrInvalidType2 = errors.New("can't encode this type")
-	// ErrNoGeneratorForType represents the error when no generator could be found for a type.
-	ErrNoGeneratorForType = errors.New("no generator for this type found")
-)
-
+// Generator generates a web friendly file version.
 type Generator interface {
-	GenerateThumbnail(image.Rectangle, interface{}) (interface{}, error)
+	Generate(image.Rectangle, interface{}, Processor) (interface{}, error)
 }
 
+// SimpleGenerator is the default image generator and is used for all image types expect gif.
 type SimpleGenerator struct{}
 
-func (g SimpleGenerator) GenerateThumbnail(size image.Rectangle, img interface{}) (interface{}, error) {
+// Generate generates a alternative image version.
+func (g SimpleGenerator) Generate(size image.Rectangle, img interface{}, processor Processor) (interface{}, error) {
 	m, ok := img.(image.Image)
 	if !ok {
-		return nil, ErrInvalidType2
+		return nil, ErrInvalidType
 	}
 
-	return imaging.Thumbnail(m, size.Dx(), size.Dy(), imaging.Lanczos), nil
+	return processor.Process(m, size.Dx(), size.Dy(), imaging.Lanczos), nil
 }
 
+// GifGenerator is used to create a web friendly version of the provided gif image.
 type GifGenerator struct{}
 
-func (g GifGenerator) GenerateThumbnail(size image.Rectangle, img interface{}) (interface{}, error) {
+// Generate generates a alternative gif version.
+func (g GifGenerator) Generate(size image.Rectangle, img interface{}, processor Processor) (interface{}, error) {
 	// Code inspired by https://github.com/willnorris/gifresize/blob/db93a7e1dcb1c279f7eeb99cc6d90b9e2e23e871/gifresize.go
 
 	m, ok := img.(*gif.GIF)
 	if !ok {
-		return nil, ErrInvalidType2
+		return nil, ErrInvalidType
 	}
 	// Create a new RGBA image to hold the incremental frames.
 	srcX, srcY := m.Config.Width, m.Config.Height
@@ -51,8 +48,8 @@ func (g GifGenerator) GenerateThumbnail(size image.Rectangle, img interface{}) (
 		bounds := frame.Bounds()
 		prev := tmp
 		draw.Draw(tmp, bounds, frame, bounds.Min, draw.Over)
-		scaled := imaging.Resize(tmp, size.Dx(), size.Dy(), imaging.Lanczos)
-		m.Image[i] = g.imageToPaletted(scaled, frame.Palette)
+		processed := processor.Process(tmp, size.Dx(), size.Dy(), imaging.Lanczos)
+		m.Image[i] = g.imageToPaletted(processed, frame.Palette)
 
 		switch m.Disposal[i] {
 		case gif.DisposalBackground:
diff --git a/services/thumbnails/pkg/thumbnail/processor.go b/services/thumbnails/pkg/thumbnail/processor.go
new file mode 100644
index 00000000000..3782fac4fce
--- /dev/null
+++ b/services/thumbnails/pkg/thumbnail/processor.go
@@ -0,0 +1,51 @@
+package thumbnail
+
+import (
+	"image"
+	"strings"
+
+	"github.com/disintegration/imaging"
+)
+
+// Processor processes the thumbnail by applying different transformations to it.
+type Processor interface {
+	ID() string
+	Process(img image.Image, width, height int, filter imaging.ResampleFilter) *image.NRGBA
+}
+
+// DefinableProcessor is the most simple processor, it holds a replaceable image converter function.
+type DefinableProcessor struct {
+	Slug      string
+	Converter func(img image.Image, width, height int, filter imaging.ResampleFilter) *image.NRGBA
+}
+
+// ID returns the processor identification.
+func (p DefinableProcessor) ID() string { return p.Slug }
+
+// Process transforms the given image.
+func (p DefinableProcessor) Process(img image.Image, width, height int, filter imaging.ResampleFilter) *image.NRGBA {
+	return p.Converter(img, width, height, filter)
+}
+
+// ProcessorFor returns a matching Processor
+func ProcessorFor(id, fileType string) (DefinableProcessor, error) {
+	switch strings.ToLower(id) {
+	case "fit":
+		return DefinableProcessor{Slug: strings.ToLower(id), Converter: imaging.Fit}, nil
+	case "resize":
+		return DefinableProcessor{Slug: strings.ToLower(id), Converter: imaging.Resize}, nil
+	case "fill":
+		return DefinableProcessor{Slug: strings.ToLower(id), Converter: func(img image.Image, width, height int, filter imaging.ResampleFilter) *image.NRGBA {
+			return imaging.Fill(img, width, height, imaging.Center, filter)
+		}}, nil
+	case "thumbnail":
+		return DefinableProcessor{Slug: strings.ToLower(id), Converter: imaging.Thumbnail}, nil
+	default:
+		switch strings.ToLower(fileType) {
+		case typeGif:
+			return DefinableProcessor{Converter: imaging.Resize}, nil
+		default:
+			return DefinableProcessor{Converter: imaging.Thumbnail}, nil
+		}
+	}
+}
diff --git a/services/thumbnails/pkg/thumbnail/storage/filesystem.go b/services/thumbnails/pkg/thumbnail/storage/filesystem.go
index 692669d3024..dd18e88f577 100644
--- a/services/thumbnails/pkg/thumbnail/storage/filesystem.go
+++ b/services/thumbnails/pkg/thumbnail/storage/filesystem.go
@@ -5,10 +5,12 @@ import (
 	"os"
 	"path/filepath"
 	"strconv"
+	"strings"
+
+	"github.com/pkg/errors"
 
 	"github.com/owncloud/ocis/v2/ocis-pkg/log"
 	"github.com/owncloud/ocis/v2/services/thumbnails/pkg/config"
-	"github.com/pkg/errors"
 )
 
 const (
@@ -82,7 +84,14 @@ func (s FileSystem) Put(key string, img []byte) error {
 func (s FileSystem) BuildKey(r Request) string {
 	checksum := r.Checksum
 	filetype := r.Types[0]
-	filename := strconv.Itoa(r.Resolution.Dx()) + "x" + strconv.Itoa(r.Resolution.Dy()) + "." + filetype
 
-	return filepath.Join(checksum[:2], checksum[2:4], checksum[4:], filename)
+	parts := []string{strconv.Itoa(r.Resolution.Dx()), "x", strconv.Itoa(r.Resolution.Dy())}
+
+	if r.Characteristic != "" {
+		parts = append(parts, "-", r.Characteristic)
+	}
+
+	parts = append(parts, ".", filetype)
+
+	return filepath.Join(checksum[:2], checksum[2:4], checksum[4:], strings.Join(parts, ""))
 }
diff --git a/services/thumbnails/pkg/thumbnail/storage/inmemory.go b/services/thumbnails/pkg/thumbnail/storage/inmemory.go
index 1c7befd888e..17683185181 100644
--- a/services/thumbnails/pkg/thumbnail/storage/inmemory.go
+++ b/services/thumbnails/pkg/thumbnail/storage/inmemory.go
@@ -38,7 +38,13 @@ func (s InMemory) BuildKey(r Request) string {
 	parts := []string{
 		r.Checksum,
 		r.Resolution.String(),
-		strings.Join(r.Types, ","),
 	}
+
+	if r.Characteristic != "" {
+		parts = append(parts, r.Characteristic)
+	}
+
+	parts = append(parts, strings.Join(r.Types, ","))
+
 	return strings.Join(parts, "+")
 }
diff --git a/services/thumbnails/pkg/thumbnail/storage/storage.go b/services/thumbnails/pkg/thumbnail/storage/storage.go
index 691fdca5af3..591b05bdc12 100644
--- a/services/thumbnails/pkg/thumbnail/storage/storage.go
+++ b/services/thumbnails/pkg/thumbnail/storage/storage.go
@@ -15,6 +15,12 @@ type Request struct {
 	Types []string
 	// The resolution of the thumbnail
 	Resolution image.Rectangle
+	// Characteristic defines the different image characteristics,
+	// for example, if its scaled up to fit in the bounding box or not,
+	// is it a chroma version of the image, and so on...
+	// the main propose for this is to be able to differentiate between images which have
+	// the same resolution but different characteristics.
+	Characteristic string
 }
 
 // Storage defines the interface for a thumbnail store.
diff --git a/services/thumbnails/pkg/thumbnail/thumbnail.go b/services/thumbnails/pkg/thumbnail/thumbnail.go
index 70fcdc5b20e..b3de987709d 100644
--- a/services/thumbnails/pkg/thumbnail/thumbnail.go
+++ b/services/thumbnails/pkg/thumbnail/thumbnail.go
@@ -30,6 +30,7 @@ type Request struct {
 	Encoder    Encoder
 	Generator  Generator
 	Checksum   string
+	Processor  Processor
 }
 
 // Manager is responsible for generating thumbnails
@@ -69,7 +70,7 @@ func (s SimpleManager) Generate(r Request, img interface{}) (string, error) {
 		match = s.resolutions.ClosestMatch(r.Resolution, m.Bounds())
 	}
 
-	thumbnail, err := r.Generator.GenerateThumbnail(match, img)
+	thumbnail, err := r.Generator.Generate(match, img, r.Processor)
 	if err != nil {
 		return "", err
 	}
@@ -98,9 +99,10 @@ func (s SimpleManager) GetThumbnail(key string) ([]byte, error) {
 
 func mapToStorageRequest(r Request) storage.Request {
 	return storage.Request{
-		Checksum:   r.Checksum,
-		Resolution: r.Resolution,
-		Types:      r.Encoder.Types(),
+		Checksum:       r.Checksum,
+		Resolution:     r.Resolution,
+		Types:          r.Encoder.Types(),
+		Characteristic: r.Processor.ID(),
 	}
 }
 
@@ -115,7 +117,7 @@ func IsMimeTypeSupported(m string) bool {
 }
 
 // PrepareRequest prepare the request based on image parameters
-func PrepareRequest(width, height int, tType, checksum string) (Request, error) {
+func PrepareRequest(width, height int, tType, checksum, pID string) (Request, error) {
 	generator, err := GeneratorForType(tType)
 	if err != nil {
 		return Request{}, err
@@ -124,11 +126,16 @@ func PrepareRequest(width, height int, tType, checksum string) (Request, error)
 	if err != nil {
 		return Request{}, err
 	}
+	processor, err := ProcessorFor(pID, tType)
+	if err != nil {
+		return Request{}, err
+	}
 
 	return Request{
 		Resolution: image.Rect(0, 0, width, height),
 		Generator:  generator,
 		Encoder:    encoder,
 		Checksum:   checksum,
+		Processor:  processor,
 	}, nil
 }
diff --git a/services/userlog/README.md b/services/userlog/README.md
index 0261b2ceea5..be9964ceb3d 100644
--- a/services/userlog/README.md
+++ b/services/userlog/README.md
@@ -76,3 +76,7 @@ Important: For the time being, the embedded ownCloud Web frontend only supports
 *   If a requested language code is not available, the service tries to fall back to the base language if available. For example, if the requested language-code `de_DE` is not available, the service tries to fall back to translations in the `de` folder.
 *   If the base language `de` is also not available, the service falls back to the system's default English (`en`),
 which is the source of the texts provided by the code.
+
+## Default Language
+
+The default language can be defined via the `OCIS_DEFAULT_LANGUAGE` environment variable. See the `settings` service for a detailed description.
diff --git a/services/userlog/pkg/config/config.go b/services/userlog/pkg/config/config.go
index a2b71d9823c..a29e0eeac8c 100644
--- a/services/userlog/pkg/config/config.go
+++ b/services/userlog/pkg/config/config.go
@@ -24,6 +24,7 @@ type Config struct {
 
 	RevaGateway     string      `yaml:"reva_gateway" env:"OCIS_REVA_GATEWAY" desc:"CS3 gateway used to look up user metadata"`
 	TranslationPath string      `yaml:"translation_path" env:"OCIS_TRANSLATION_PATH;USERLOG_TRANSLATION_PATH" desc:"(optional) Set this to a path with custom translations to overwrite the builtin translations. Note that file and folder naming rules apply, see the documentation for more details."`
+	DefaultLanguage string      `yaml:"default_language" env:"OCIS_DEFAULT_LANGUAGE" desc:"The default language used by services and the WebUI. If not defined, English will be used as default. See the documentation for more details."`
 	Events          Events      `yaml:"events"`
 	Persistence     Persistence `yaml:"persistence"`
 
diff --git a/services/userlog/pkg/service/conversion.go b/services/userlog/pkg/service/conversion.go
index c3cfc704375..d84dd7bca56 100644
--- a/services/userlog/pkg/service/conversion.go
+++ b/services/userlog/pkg/service/conversion.go
@@ -55,6 +55,7 @@ type Converter struct {
 	gwc                   gateway.GatewayAPIClient
 	serviceName           string
 	translationPath       string
+	defaultLanguage       string
 	serviceAccountContext context.Context
 
 	// cached within one request not to query other service too much
@@ -64,12 +65,13 @@ type Converter struct {
 }
 
 // NewConverter returns a new Converter
-func NewConverter(ctx context.Context, loc string, gwc gateway.GatewayAPIClient, name string, translationPath string) *Converter {
+func NewConverter(ctx context.Context, loc string, gwc gateway.GatewayAPIClient, name, translationPath, defaultLanguage string) *Converter {
 	return &Converter{
 		locale:                loc,
 		gwc:                   gwc,
 		serviceName:           name,
 		translationPath:       translationPath,
+		defaultLanguage:       defaultLanguage,
 		serviceAccountContext: ctx,
 		spaces:                make(map[string]*storageprovider.StorageSpace),
 		users:                 make(map[string]*user.User),
@@ -137,7 +139,7 @@ func (c *Converter) spaceDeletedMessage(eventid string, executant *user.UserId,
 		return OC10Notification{}, err
 	}
 
-	subj, subjraw, msg, msgraw, err := composeMessage(SpaceDeleted, c.locale, c.translationPath, map[string]interface{}{
+	subj, subjraw, msg, msgraw, err := composeMessage(SpaceDeleted, c.locale, c.defaultLanguage, c.translationPath, map[string]interface{}{
 		"username":  usr.GetDisplayName(),
 		"spacename": spacename,
 	})
@@ -173,7 +175,7 @@ func (c *Converter) spaceMessage(eventid string, nt NotificationTemplate, execut
 		return OC10Notification{}, err
 	}
 
-	subj, subjraw, msg, msgraw, err := composeMessage(nt, c.locale, c.translationPath, map[string]interface{}{
+	subj, subjraw, msg, msgraw, err := composeMessage(nt, c.locale, c.defaultLanguage, c.translationPath, map[string]interface{}{
 		"username":  usr.GetDisplayName(),
 		"spacename": space.GetName(),
 	})
@@ -207,7 +209,7 @@ func (c *Converter) shareMessage(eventid string, nt NotificationTemplate, execut
 		return OC10Notification{}, err
 	}
 
-	subj, subjraw, msg, msgraw, err := composeMessage(nt, c.locale, c.translationPath, map[string]interface{}{
+	subj, subjraw, msg, msgraw, err := composeMessage(nt, c.locale, c.defaultLanguage, c.translationPath, map[string]interface{}{
 		"username":     usr.GetDisplayName(),
 		"resourcename": info.GetName(),
 	})
@@ -231,7 +233,7 @@ func (c *Converter) shareMessage(eventid string, nt NotificationTemplate, execut
 }
 
 func (c *Converter) virusMessage(eventid string, nt NotificationTemplate, executant *user.User, rid *storageprovider.ResourceId, filename string, virus string, ts time.Time) (OC10Notification, error) {
-	subj, subjraw, msg, msgraw, err := composeMessage(nt, c.locale, c.translationPath, map[string]interface{}{
+	subj, subjraw, msg, msgraw, err := composeMessage(nt, c.locale, c.defaultLanguage, c.translationPath, map[string]interface{}{
 		"resourcename":     filename,
 		"virusdescription": virus,
 	})
@@ -265,7 +267,7 @@ func (c *Converter) virusMessage(eventid string, nt NotificationTemplate, execut
 }
 
 func (c *Converter) policiesMessage(eventid string, nt NotificationTemplate, executant *user.User, filename string, ts time.Time) (OC10Notification, error) {
-	subj, subjraw, msg, msgraw, err := composeMessage(nt, c.locale, c.translationPath, map[string]interface{}{
+	subj, subjraw, msg, msgraw, err := composeMessage(nt, c.locale, c.defaultLanguage, c.translationPath, map[string]interface{}{
 		"resourcename": filename,
 	})
 	if err != nil {
@@ -293,7 +295,7 @@ func (c *Converter) policiesMessage(eventid string, nt NotificationTemplate, exe
 }
 
 func (c *Converter) deprovisionMessage(nt NotificationTemplate, deproDate string) (OC10Notification, error) {
-	subj, subjraw, msg, msgraw, err := composeMessage(nt, c.locale, c.translationPath, map[string]interface{}{
+	subj, subjraw, msg, msgraw, err := composeMessage(nt, c.locale, c.defaultLanguage, c.translationPath, map[string]interface{}{
 		"date": deproDate,
 	})
 	if err != nil {
@@ -347,8 +349,8 @@ func (c *Converter) getUser(ctx context.Context, userID *user.UserId) (*user.Use
 	return usr, err
 }
 
-func composeMessage(nt NotificationTemplate, locale string, path string, vars map[string]interface{}) (string, string, string, string, error) {
-	subjectraw, messageraw := loadTemplates(nt, locale, path)
+func composeMessage(nt NotificationTemplate, locale, defaultLocale, path string, vars map[string]interface{}) (string, string, string, string, error) {
+	subjectraw, messageraw := loadTemplates(nt, locale, defaultLocale, path)
 
 	subject, err := executeTemplate(subjectraw, vars)
 	if err != nil {
@@ -359,7 +361,7 @@ func composeMessage(nt NotificationTemplate, locale string, path string, vars ma
 	return subject, subjectraw, message, messageraw, err
 }
 
-func loadTemplates(nt NotificationTemplate, locale string, path string) (string, string) {
+func newLocate(locale string, path string) *gotext.Locale {
 	// Create Locale with library path and language code and load default domain
 	var l *gotext.Locale
 	if path == "" {
@@ -369,6 +371,14 @@ func loadTemplates(nt NotificationTemplate, locale string, path string) (string,
 		l = gotext.NewLocale(path, locale)
 	}
 	l.AddDomain(_domain) // make domain configurable only if needed
+	return l
+}
+
+func loadTemplates(nt NotificationTemplate, locale, defaultLocale, path string) (string, string) {
+	l := newLocate(locale, path)
+	if locale != "en" && len(l.GetTranslations()) == 0 {
+		l = newLocate(defaultLocale, path)
+	}
 	return l.Get(nt.Subject), l.Get(nt.Message)
 }
 
diff --git a/services/userlog/pkg/service/http.go b/services/userlog/pkg/service/http.go
index b869d3e1545..05dca5050fc 100644
--- a/services/userlog/pkg/service/http.go
+++ b/services/userlog/pkg/service/http.go
@@ -59,7 +59,7 @@ func (ul *UserlogService) HandleGetEvents(w http.ResponseWriter, r *http.Request
 		return
 	}
 
-	conv := NewConverter(ctx, r.Header.Get(HeaderAcceptLanguage), gwc, ul.cfg.Service.Name, ul.cfg.TranslationPath)
+	conv := NewConverter(ctx, r.Header.Get(HeaderAcceptLanguage), gwc, ul.cfg.Service.Name, ul.cfg.TranslationPath, ul.cfg.DefaultLanguage)
 
 	var outdatedEvents []string
 	resp := GetEventResponseOC10{}
diff --git a/services/userlog/pkg/service/l10n/locale/de/LC_MESSAGES/userlog.po b/services/userlog/pkg/service/l10n/locale/de/LC_MESSAGES/userlog.po
index cde31b5f008..bfdda0582f4 100644
--- a/services/userlog/pkg/service/l10n/locale/de/LC_MESSAGES/userlog.po
+++ b/services/userlog/pkg/service/l10n/locale/de/LC_MESSAGES/userlog.po
@@ -14,7 +14,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: EMAIL\n"
-"POT-Creation-Date: 2023-09-25 04:24+0000\n"
+"POT-Creation-Date: 2023-10-16 01:14+0000\n"
 "PO-Revision-Date: 2023-03-15 08:28+0000\n"
 "Last-Translator: Michael Barz <mbarz@owncloud.com>, 2023\n"
 "Language-Team: German (https://app.transifex.com/owncloud-org/teams/6149/de/)\n"
diff --git a/services/userlog/pkg/service/l10n/locale/de_DE/LC_MESSAGES/userlog.po b/services/userlog/pkg/service/l10n/locale/de_DE/LC_MESSAGES/userlog.po
index 3a1521866f9..3602f22869a 100644
--- a/services/userlog/pkg/service/l10n/locale/de_DE/LC_MESSAGES/userlog.po
+++ b/services/userlog/pkg/service/l10n/locale/de_DE/LC_MESSAGES/userlog.po
@@ -13,7 +13,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: EMAIL\n"
-"POT-Creation-Date: 2023-09-26 00:05+0000\n"
+"POT-Creation-Date: 2023-10-16 01:14+0000\n"
 "PO-Revision-Date: 2023-03-15 08:28+0000\n"
 "Last-Translator: Jürgen Weigert <jnweiger@gmail.com>, 2023\n"
 "Language-Team: German (Germany) (https://app.transifex.com/owncloud-org/teams/6149/de_DE/)\n"
diff --git a/services/userlog/pkg/service/service.go b/services/userlog/pkg/service/service.go
index 6e5897e9da2..684b9c8f3d4 100644
--- a/services/userlog/pkg/service/service.go
+++ b/services/userlog/pkg/service/service.go
@@ -337,7 +337,7 @@ func (ul *UserlogService) addEventToUser(ctx context.Context, userid string, eve
 }
 
 func (ul *UserlogService) sendSSE(ctx context.Context, userid string, event events.Event, gwc gateway.GatewayAPIClient) error {
-	ev, err := NewConverter(ctx, ul.getUserLocale(userid), gwc, ul.cfg.Service.Name, ul.cfg.TranslationPath).ConvertEvent(event.ID, event.Event)
+	ev, err := NewConverter(ctx, ul.getUserLocale(userid), gwc, ul.cfg.Service.Name, ul.cfg.TranslationPath, ul.cfg.DefaultLanguage).ConvertEvent(event.ID, event.Event)
 	if err != nil {
 		return err
 	}
diff --git a/services/webdav/pkg/dav/requests/thumbnail.go b/services/webdav/pkg/dav/requests/thumbnail.go
index 0c703d37253..2a5aa7da57f 100644
--- a/services/webdav/pkg/dav/requests/thumbnail.go
+++ b/services/webdav/pkg/dav/requests/thumbnail.go
@@ -36,6 +36,8 @@ type ThumbnailRequest struct {
 	Height int32
 	// In case of a public share the public link token.
 	PublicLinkToken string
+	// Indicates which image processor to use
+	Processor string
 	// The Identifier from the requested URL
 	Identifier string
 }
@@ -73,6 +75,7 @@ func ParseThumbnailRequest(r *http.Request) (*ThumbnailRequest, error) {
 		Extension:       filepath.Ext(fp),
 		Width:           int32(width),
 		Height:          int32(height),
+		Processor:       q.Get("processor"),
 		PublicLinkToken: chi.URLParam(r, "token"),
 		Identifier:      id,
 	}, nil
diff --git a/services/webdav/pkg/service/v0/service.go b/services/webdav/pkg/service/v0/service.go
index 4c1535f3fec..b1655c2b8c0 100644
--- a/services/webdav/pkg/service/v0/service.go
+++ b/services/webdav/pkg/service/v0/service.go
@@ -243,6 +243,7 @@ func (g Webdav) SpacesThumbnail(w http.ResponseWriter, r *http.Request) {
 		ThumbnailType: extensionToThumbnailType(strings.TrimLeft(tr.Extension, ".")),
 		Width:         tr.Width,
 		Height:        tr.Height,
+		Processor:     tr.Processor,
 		Source: &thumbnailssvc.GetThumbnailRequest_Cs3Source{
 			Cs3Source: &thumbnailsmsg.CS3Source{
 				Path:          fullPath,
@@ -335,6 +336,7 @@ func (g Webdav) Thumbnail(w http.ResponseWriter, r *http.Request) {
 		ThumbnailType: extensionToThumbnailType(strings.TrimLeft(tr.Extension, ".")),
 		Width:         tr.Width,
 		Height:        tr.Height,
+		Processor:     tr.Processor,
 		Source: &thumbnailssvc.GetThumbnailRequest_Cs3Source{
 			Cs3Source: &thumbnailsmsg.CS3Source{
 				Path:          fullPath,
@@ -375,6 +377,7 @@ func (g Webdav) PublicThumbnail(w http.ResponseWriter, r *http.Request) {
 		ThumbnailType: extensionToThumbnailType(strings.TrimLeft(tr.Extension, ".")),
 		Width:         tr.Width,
 		Height:        tr.Height,
+		Processor:     tr.Processor,
 		Source: &thumbnailssvc.GetThumbnailRequest_WebdavSource{
 			WebdavSource: &thumbnailsmsg.WebdavSource{
 				Url:             g.config.OcisPublicURL + r.URL.RequestURI(),
@@ -416,6 +419,7 @@ func (g Webdav) PublicThumbnailHead(w http.ResponseWriter, r *http.Request) {
 		ThumbnailType: extensionToThumbnailType(strings.TrimLeft(tr.Extension, ".")),
 		Width:         tr.Width,
 		Height:        tr.Height,
+		Processor:     tr.Processor,
 		Source: &thumbnailssvc.GetThumbnailRequest_WebdavSource{
 			WebdavSource: &thumbnailsmsg.WebdavSource{
 				Url:             g.config.OcisPublicURL + r.URL.RequestURI(),
diff --git a/vendor/google.golang.org/grpc/internal/transport/http2_server.go b/vendor/google.golang.org/grpc/internal/transport/http2_server.go
index 8d3a353c1d5..c06db679d89 100644
--- a/vendor/google.golang.org/grpc/internal/transport/http2_server.go
+++ b/vendor/google.golang.org/grpc/internal/transport/http2_server.go
@@ -171,15 +171,10 @@ func NewServerTransport(conn net.Conn, config *ServerConfig) (_ ServerTransport,
 		ID:  http2.SettingMaxFrameSize,
 		Val: http2MaxFrameLen,
 	}}
-	// TODO(zhaoq): Have a better way to signal "no limit" because 0 is
-	// permitted in the HTTP2 spec.
-	maxStreams := config.MaxStreams
-	if maxStreams == 0 {
-		maxStreams = math.MaxUint32
-	} else {
+	if config.MaxStreams != math.MaxUint32 {
 		isettings = append(isettings, http2.Setting{
 			ID:  http2.SettingMaxConcurrentStreams,
-			Val: maxStreams,
+			Val: config.MaxStreams,
 		})
 	}
 	dynamicWindow := true
@@ -258,7 +253,7 @@ func NewServerTransport(conn net.Conn, config *ServerConfig) (_ ServerTransport,
 		framer:            framer,
 		readerDone:        make(chan struct{}),
 		writerDone:        make(chan struct{}),
-		maxStreams:        maxStreams,
+		maxStreams:        config.MaxStreams,
 		inTapHandle:       config.InTapHandle,
 		fc:                &trInFlow{limit: uint32(icwz)},
 		state:             reachable,
diff --git a/vendor/google.golang.org/grpc/server.go b/vendor/google.golang.org/grpc/server.go
index 244123c6c5a..eeae92fbe02 100644
--- a/vendor/google.golang.org/grpc/server.go
+++ b/vendor/google.golang.org/grpc/server.go
@@ -115,12 +115,6 @@ type serviceInfo struct {
 	mdata       any
 }
 
-type serverWorkerData struct {
-	st     transport.ServerTransport
-	wg     *sync.WaitGroup
-	stream *transport.Stream
-}
-
 // Server is a gRPC server to serve RPC requests.
 type Server struct {
 	opts serverOptions
@@ -145,7 +139,7 @@ type Server struct {
 	channelzID *channelz.Identifier
 	czData     *channelzData
 
-	serverWorkerChannel chan *serverWorkerData
+	serverWorkerChannel chan func()
 }
 
 type serverOptions struct {
@@ -179,6 +173,7 @@ type serverOptions struct {
 }
 
 var defaultServerOptions = serverOptions{
+	maxConcurrentStreams:  math.MaxUint32,
 	maxReceiveMessageSize: defaultServerMaxReceiveMessageSize,
 	maxSendMessageSize:    defaultServerMaxSendMessageSize,
 	connectionTimeout:     120 * time.Second,
@@ -404,6 +399,9 @@ func MaxSendMsgSize(m int) ServerOption {
 // MaxConcurrentStreams returns a ServerOption that will apply a limit on the number
 // of concurrent streams to each ServerTransport.
 func MaxConcurrentStreams(n uint32) ServerOption {
+	if n == 0 {
+		n = math.MaxUint32
+	}
 	return newFuncServerOption(func(o *serverOptions) {
 		o.maxConcurrentStreams = n
 	})
@@ -605,24 +603,19 @@ const serverWorkerResetThreshold = 1 << 16
 // [1] https://github.com/golang/go/issues/18138
 func (s *Server) serverWorker() {
 	for completed := 0; completed < serverWorkerResetThreshold; completed++ {
-		data, ok := <-s.serverWorkerChannel
+		f, ok := <-s.serverWorkerChannel
 		if !ok {
 			return
 		}
-		s.handleSingleStream(data)
+		f()
 	}
 	go s.serverWorker()
 }
 
-func (s *Server) handleSingleStream(data *serverWorkerData) {
-	defer data.wg.Done()
-	s.handleStream(data.st, data.stream, s.traceInfo(data.st, data.stream))
-}
-
 // initServerWorkers creates worker goroutines and a channel to process incoming
 // connections to reduce the time spent overall on runtime.morestack.
 func (s *Server) initServerWorkers() {
-	s.serverWorkerChannel = make(chan *serverWorkerData)
+	s.serverWorkerChannel = make(chan func())
 	for i := uint32(0); i < s.opts.numServerWorkers; i++ {
 		go s.serverWorker()
 	}
@@ -982,21 +975,26 @@ func (s *Server) serveStreams(st transport.ServerTransport) {
 	defer st.Close(errors.New("finished serving streams for the server transport"))
 	var wg sync.WaitGroup
 
+	streamQuota := newHandlerQuota(s.opts.maxConcurrentStreams)
 	st.HandleStreams(func(stream *transport.Stream) {
 		wg.Add(1)
+
+		streamQuota.acquire()
+		f := func() {
+			defer streamQuota.release()
+			defer wg.Done()
+			s.handleStream(st, stream, s.traceInfo(st, stream))
+		}
+
 		if s.opts.numServerWorkers > 0 {
-			data := &serverWorkerData{st: st, wg: &wg, stream: stream}
 			select {
-			case s.serverWorkerChannel <- data:
+			case s.serverWorkerChannel <- f:
 				return
 			default:
 				// If all stream workers are busy, fallback to the default code path.
 			}
 		}
-		go func() {
-			defer wg.Done()
-			s.handleStream(st, stream, s.traceInfo(st, stream))
-		}()
+		go f()
 	}, func(ctx context.Context, method string) context.Context {
 		if !EnableTracing {
 			return ctx
@@ -2091,3 +2089,34 @@ func validateSendCompressor(name, clientCompressors string) error {
 	}
 	return fmt.Errorf("client does not support compressor %q", name)
 }
+
+// atomicSemaphore implements a blocking, counting semaphore. acquire should be
+// called synchronously; release may be called asynchronously.
+type atomicSemaphore struct {
+	n    atomic.Int64
+	wait chan struct{}
+}
+
+func (q *atomicSemaphore) acquire() {
+	if q.n.Add(-1) < 0 {
+		// We ran out of quota.  Block until a release happens.
+		<-q.wait
+	}
+}
+
+func (q *atomicSemaphore) release() {
+	// N.B. the "<= 0" check below should allow for this to work with multiple
+	// concurrent calls to acquire, but also note that with synchronous calls to
+	// acquire, as our system does, n will never be less than -1.  There are
+	// fairness issues (queuing) to consider if this was to be generalized.
+	if q.n.Add(1) <= 0 {
+		// An acquire was waiting on us.  Unblock it.
+		q.wait <- struct{}{}
+	}
+}
+
+func newHandlerQuota(n uint32) *atomicSemaphore {
+	a := &atomicSemaphore{wait: make(chan struct{}, 1)}
+	a.n.Store(int64(n))
+	return a
+}
diff --git a/vendor/google.golang.org/grpc/version.go b/vendor/google.golang.org/grpc/version.go
index d3f5bcbfcef..724ad210213 100644
--- a/vendor/google.golang.org/grpc/version.go
+++ b/vendor/google.golang.org/grpc/version.go
@@ -19,4 +19,4 @@
 package grpc
 
 // Version is the current grpc version.
-const Version = "1.58.2"
+const Version = "1.58.3"
diff --git a/vendor/modules.txt b/vendor/modules.txt
index bda735cde73..147fad9c3aa 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -2118,7 +2118,7 @@ google.golang.org/genproto/googleapis/api/httpbody
 ## explicit; go 1.19
 google.golang.org/genproto/googleapis/rpc/errdetails
 google.golang.org/genproto/googleapis/rpc/status
-# google.golang.org/grpc v1.58.2
+# google.golang.org/grpc v1.58.3
 ## explicit; go 1.19
 google.golang.org/grpc
 google.golang.org/grpc/attributes


Test patch:
diff --git a/services/notifications/pkg/service/service_test.go b/services/notifications/pkg/service/service_test.go
index 056866391f8..afd7e51c4c0 100644
--- a/services/notifications/pkg/service/service_test.go
+++ b/services/notifications/pkg/service/service_test.go
@@ -77,7 +77,7 @@ var _ = Describe("Notifications", func() {
 			cfg := defaults.FullDefaultConfig()
 			cfg.GRPCClientTLS = &shared.GRPCClientTLS{}
 			ch := make(chan events.Event)
-			evts := service.NewEventsNotifier(ch, tc, log.NewLogger(), gatewaySelector, vs, "", "", "", "")
+			evts := service.NewEventsNotifier(ch, tc, log.NewLogger(), gatewaySelector, vs, "", "", "", "", "")
 			go evts.Run()
 
 			ch <- ev
@@ -275,7 +275,7 @@ var _ = Describe("Notifications X-Site Scripting", func() {
 			cfg := defaults.FullDefaultConfig()
 			cfg.GRPCClientTLS = &shared.GRPCClientTLS{}
 			ch := make(chan events.Event)
-			evts := service.NewEventsNotifier(ch, tc, log.NewLogger(), gatewaySelector, vs, "", "", "", "")
+			evts := service.NewEventsNotifier(ch, tc, log.NewLogger(), gatewaySelector, vs, "", "", "", "", "")
 			go evts.Run()
 
 			ch <- ev
diff --git a/services/proxy/pkg/router/router_test.go b/services/proxy/pkg/router/router_test.go
index 3bbebc868c9..2652db56aac 100644
--- a/services/proxy/pkg/router/router_test.go
+++ b/services/proxy/pkg/router/router_test.go
@@ -1,9 +1,11 @@
 package router
 
 import (
+	"context"
 	"fmt"
 	"net/http"
 	"net/http/httptest"
+	"net/http/httputil"
 	"net/url"
 	"testing"
 
@@ -146,10 +148,14 @@ func TestRouter(t *testing.T) {
 			t.Errorf("TestRouter route flag unprotected expected to be %t got %t", test.unprotected, routingInfo.IsRouteUnprotected())
 		}
 
-		routingInfo.Director()(r)
+		pr := &httputil.ProxyRequest{
+			In:  r,
+			Out: r.Clone(context.Background()),
+		}
+		routingInfo.Rewrite()(pr)
 
-		if r.URL.Host != test.target {
-			t.Errorf("TestRouter got host %s expected %s", r.URL.Host, test.target)
+		if pr.Out.URL.Host != test.target {
+			t.Errorf("TestRouter got host %s expected %s", pr.Out.URL.Host, test.target)
 		}
 	}
 }
diff --git a/services/thumbnails/pkg/thumbnail/processor_test.go b/services/thumbnails/pkg/thumbnail/processor_test.go
new file mode 100644
index 00000000000..febda33b817
--- /dev/null
+++ b/services/thumbnails/pkg/thumbnail/processor_test.go
@@ -0,0 +1,98 @@
+package thumbnail_test
+
+import (
+	"testing"
+
+	"github.com/disintegration/imaging"
+	tAssert "github.com/stretchr/testify/assert"
+
+	"github.com/owncloud/ocis/v2/services/thumbnails/pkg/thumbnail"
+)
+
+func TestProcessorFor(t *testing.T) {
+	tests := []struct {
+		id       string
+		fileType string
+		wantP    thumbnail.Processor
+		wantE    error
+	}{
+		{
+			id:       "fit",
+			fileType: "",
+			wantP:    thumbnail.DefinableProcessor{Slug: "fit", Converter: imaging.Fit},
+			wantE:    nil,
+		},
+		{
+			id:       "fit",
+			fileType: "jpg",
+			wantP:    thumbnail.DefinableProcessor{Slug: "fit"},
+			wantE:    nil,
+		},
+		{
+			id:       "FIT",
+			fileType: "jpg",
+			wantP:    thumbnail.DefinableProcessor{Slug: "fit"},
+			wantE:    nil,
+		},
+		{
+			id:       "resize",
+			fileType: "jpg",
+			wantP:    thumbnail.DefinableProcessor{Slug: "resize"},
+			wantE:    nil,
+		},
+		{
+			id:       "RESIZE",
+			fileType: "jpg",
+			wantP:    thumbnail.DefinableProcessor{Slug: "resize"},
+			wantE:    nil,
+		},
+		{
+			id:       "fill",
+			fileType: "jpg",
+			wantP:    thumbnail.DefinableProcessor{Slug: "fill"},
+			wantE:    nil,
+		},
+		{
+			id:       "FILL",
+			fileType: "jpg",
+			wantP:    thumbnail.DefinableProcessor{Slug: "fill"},
+			wantE:    nil,
+		},
+		{
+			id:       "thumbnail",
+			fileType: "jpg",
+			wantP:    thumbnail.DefinableProcessor{Slug: "thumbnail"},
+			wantE:    nil,
+		},
+		{
+			id:       "THUMBNAIL",
+			fileType: "jpg",
+			wantP:    thumbnail.DefinableProcessor{Slug: "thumbnail"},
+			wantE:    nil,
+		},
+		{
+			id:       "",
+			fileType: "jpg",
+			wantP:    thumbnail.DefinableProcessor{},
+			wantE:    nil,
+		},
+		{
+			id:       "",
+			fileType: "gif",
+			wantP:    thumbnail.DefinableProcessor{},
+			wantE:    nil,
+		},
+	}
+
+	assert := tAssert.New(t)
+
+	for _, tt := range tests {
+		tt := tt
+		t.Run("", func(t *testing.T) {
+			p, e := thumbnail.ProcessorFor(tt.id, tt.fileType)
+			assert.Equal(p.ID(), tt.wantP.ID())
+			assert.Equal(e, tt.wantE)
+		})
+	}
+
+}
diff --git a/services/thumbnails/pkg/thumbnail/storage/filesystem_test.go b/services/thumbnails/pkg/thumbnail/storage/filesystem_test.go
new file mode 100644
index 00000000000..a139a9199b1
--- /dev/null
+++ b/services/thumbnails/pkg/thumbnail/storage/filesystem_test.go
@@ -0,0 +1,65 @@
+package storage_test
+
+import (
+	"image"
+	"testing"
+
+	tAssert "github.com/stretchr/testify/assert"
+
+	"github.com/owncloud/ocis/v2/services/thumbnails/pkg/thumbnail/storage"
+)
+
+func TestFileSystem_BuildKey(t *testing.T) {
+	tests := []struct {
+		r    storage.Request
+		want string
+	}{
+		{
+			r: storage.Request{
+				Checksum: "120EA8A25E5D487BF68B5F7096440019",
+				Types:    []string{"png", "jpg"},
+				Resolution: image.Rectangle{
+					Min: image.Point{
+						X: 1,
+						Y: 2,
+					},
+					Max: image.Point{
+						X: 3,
+						Y: 4,
+					},
+				},
+				Characteristic: "",
+			},
+			want: "12/0E/A8A25E5D487BF68B5F7096440019/2x2.png",
+		},
+		{
+			r: storage.Request{
+				Checksum: "120EA8A25E5D487BF68B5F7096440019",
+				Types:    []string{"png", "jpg"},
+				Resolution: image.Rectangle{
+					Min: image.Point{
+						X: 1,
+						Y: 2,
+					},
+					Max: image.Point{
+						X: 3,
+						Y: 4,
+					},
+				},
+				Characteristic: "fill",
+			},
+			want: "12/0E/A8A25E5D487BF68B5F7096440019/2x2-fill.png",
+		},
+	}
+
+	s := storage.FileSystem{}
+	assert := tAssert.New(t)
+
+	for _, tt := range tests {
+		tt := tt
+		t.Run("", func(t *testing.T) {
+			assert.Equal(s.BuildKey(tt.r), tt.want)
+		})
+	}
+
+}
diff --git a/services/thumbnails/pkg/thumbnail/storage/inmemory_test.go b/services/thumbnails/pkg/thumbnail/storage/inmemory_test.go
new file mode 100644
index 00000000000..9f33a6ae494
--- /dev/null
+++ b/services/thumbnails/pkg/thumbnail/storage/inmemory_test.go
@@ -0,0 +1,65 @@
+package storage_test
+
+import (
+	"image"
+	"testing"
+
+	tAssert "github.com/stretchr/testify/assert"
+
+	"github.com/owncloud/ocis/v2/services/thumbnails/pkg/thumbnail/storage"
+)
+
+func TestInMemory_BuildKey(t *testing.T) {
+	tests := []struct {
+		r    storage.Request
+		want string
+	}{
+		{
+			r: storage.Request{
+				Checksum: "cs",
+				Types:    []string{"png", "jpg"},
+				Resolution: image.Rectangle{
+					Min: image.Point{
+						X: 1,
+						Y: 2,
+					},
+					Max: image.Point{
+						X: 3,
+						Y: 4,
+					},
+				},
+				Characteristic: "",
+			},
+			want: "cs+(1,2)-(3,4)+png,jpg",
+		},
+		{
+			r: storage.Request{
+				Checksum: "cs",
+				Types:    []string{"png", "jpg"},
+				Resolution: image.Rectangle{
+					Min: image.Point{
+						X: 1,
+						Y: 2,
+					},
+					Max: image.Point{
+						X: 3,
+						Y: 4,
+					},
+				},
+				Characteristic: "fill",
+			},
+			want: "cs+(1,2)-(3,4)+fill+png,jpg",
+		},
+	}
+
+	s := storage.InMemory{}
+	assert := tAssert.New(t)
+
+	for _, tt := range tests {
+		tt := tt
+		t.Run("", func(t *testing.T) {
+			assert.Equal(s.BuildKey(tt.r), tt.want)
+		})
+	}
+
+}
diff --git a/services/thumbnails/pkg/thumbnail/thumbnail_test.go b/services/thumbnails/pkg/thumbnail/thumbnail_test.go
index 45303dcc871..fc4c9796429 100644
--- a/services/thumbnails/pkg/thumbnail/thumbnail_test.go
+++ b/services/thumbnails/pkg/thumbnail/thumbnail_test.go
@@ -4,9 +4,11 @@ import (
 	"image"
 	"os"
 	"path/filepath"
-	"reflect"
 	"testing"
 
+	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/cmpopts"
+
 	"github.com/owncloud/ocis/v2/ocis-pkg/log"
 	"github.com/owncloud/ocis/v2/services/thumbnails/pkg/thumbnail/storage"
 )
@@ -118,13 +120,15 @@ func TestPrepareRequest(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got, err := PrepareRequest(tt.args.width, tt.args.height, tt.args.tType, tt.args.checksum)
+			got, err := PrepareRequest(tt.args.width, tt.args.height, tt.args.tType, tt.args.checksum, "")
 			if (err != nil) != tt.wantErr {
 				t.Errorf("PrepareRequest() error = %v, wantErr %v", err, tt.wantErr)
 				return
 			}
-			if !reflect.DeepEqual(got, tt.want) {
-				t.Errorf("PrepareRequest() got = %v, want %v", got, tt.want)
+
+			// func's are not reflactable, ignore
+			if diff := cmp.Diff(tt.want, got, cmpopts.IgnoreFields(Request{}, "Processor")); diff != "" {
+				t.Errorf("PrepareRequest(): %v", diff)
 			}
 		})
 	}
diff --git a/tests/TestHelpers/HttpLogger.php b/tests/TestHelpers/HttpLogger.php
index b0080aa6b6c..6d755030f43 100644
--- a/tests/TestHelpers/HttpLogger.php
+++ b/tests/TestHelpers/HttpLogger.php
@@ -70,6 +70,7 @@ class HttpLogger {
 	public static function logRequest(RequestInterface $request): void {
 		$method = $request->getMethod();
 		$path = $request->getUri()->getPath();
+		$query = $request->getUri()->getQuery();
 		$body = $request->getBody();
 
 		$headers = "";
@@ -80,6 +81,7 @@ class HttpLogger {
 		$logMessage = "\t\t_______________________________________________________________________\n\n";
 		$logMessage .= "\t\t==> REQUEST\n";
 		$logMessage .= "\t\t$method $path\n";
+		$logMessage .= $query ? "\t\tQUERY: $query\n" : "";
 		$logMessage .= "\t\t$headers";
 
 		if ($body->getSize() > 0) {
diff --git a/tests/acceptance/expected-failures-API-on-OCIS-storage.md b/tests/acceptance/expected-failures-API-on-OCIS-storage.md
index a879e5f3563..b192aaba33b 100644
--- a/tests/acceptance/expected-failures-API-on-OCIS-storage.md
+++ b/tests/acceptance/expected-failures-API-on-OCIS-storage.md
@@ -284,10 +284,10 @@ cannot share a folder with create permission
 
 #### [Sharing folder and sub-folder with same user but different permission,the permission of sub-folder is not obeyed ](https://github.com/owncloud/ocis/issues/2440)
 
-- [coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature:221](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature#L221)
-- [coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature:351](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature#L351)
-- [coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature:252](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature#L252)
-- [coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature:382](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature#L382)
+- [coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature:220](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature#L220)
+- [coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature:350](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature#L350)
+- [coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature:251](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature#L251)
+- [coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature:381](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature#L381)
 
 #### [Empty OCS response for a share create request using a disabled user](https://github.com/owncloud/ocis/issues/2212)
 
diff --git a/tests/acceptance/features/apiGraph/addUserToGroup.feature b/tests/acceptance/features/apiGraph/addUserToGroup.feature
index ec69869ce77..f43df4b74f7 100644
--- a/tests/acceptance/features/apiGraph/addUserToGroup.feature
+++ b/tests/acceptance/features/apiGraph/addUserToGroup.feature
@@ -205,8 +205,8 @@ Feature: add users to group
 
   Scenario: admin tries to add a nonexistent user to a group
     Given group "groupA" has been created
-    When the administrator tries to add user "nonexistentuser" to group "groupA" using the provisioning API
-    Then the HTTP status code should be "405"
+    When the administrator tries to add nonexistent user to group "groupA" using the Graph API
+    Then the HTTP status code should be "404"
 
 
   Scenario: admin tries to add user to a group without sending the group
diff --git a/tests/acceptance/features/apiNotification/emailNotification.feature b/tests/acceptance/features/apiNotification/emailNotification.feature
index 09b610a86a5..f921c4e9f9a 100644
--- a/tests/acceptance/features/apiNotification/emailNotification.feature
+++ b/tests/acceptance/features/apiNotification/emailNotification.feature
@@ -140,7 +140,7 @@ Feature: Email notification
     And user "Brian" has switched the system language to "es"
     And user "Carol" has switched the system language to "de"
     And user "Alice" has created a space "new-space" with the default quota using the GraphApi
-    When user "Alice" has shared a space "new-space" with settings:
+    When user "Alice" shares a space "new-space" with settings:
       | shareWith | group1 |
       | role      | viewer |
     Then the HTTP status code should be "200"
@@ -183,7 +183,7 @@ Feature: Email notification
 
   @env-config
   Scenario: group members get an email notification in default language when someone shares a file with the group
-    Given the config "SETTINGS_DEFAULT_LANGUAGE" has been set to "de"
+    Given the config "OCIS_DEFAULT_LANGUAGE" has been set to "de"
     And user "Carol" has been created with default attributes and without skeleton files
     And group "group1" has been created
     And user "Brian" has been added to group "group1"
diff --git a/tests/acceptance/features/apiNotification/notification.feature b/tests/acceptance/features/apiNotification/notification.feature
index 841b7782c68..0289b548237 100644
--- a/tests/acceptance/features/apiNotification/notification.feature
+++ b/tests/acceptance/features/apiNotification/notification.feature
@@ -270,7 +270,7 @@ Feature: Notification
 
   @env-config
   Scenario: get a notification about a file share in default languages
-    Given the config "SETTINGS_DEFAULT_LANGUAGE" has been set to "de"
+    Given the config "OCIS_DEFAULT_LANGUAGE" has been set to "de"
     And user "Alice" has shared entry "textfile1.txt" with user "Brian" with permissions "17"
     When user "Brian" lists all notifications
     Then the HTTP status code should be "200"
diff --git a/tests/acceptance/features/apiSpaces/changeSpaces.feature b/tests/acceptance/features/apiSpaces/changeSpaces.feature
index ca087a08ede..5992d457eff 100644
--- a/tests/acceptance/features/apiSpaces/changeSpaces.feature
+++ b/tests/acceptance/features/apiSpaces/changeSpaces.feature
@@ -339,6 +339,7 @@ Feature: Change data of space
     When user "<user>" uploads a file inside space "Project Jupiter" owned by the user "Alice" with content "new content" to ".space/readme.md" using the WebDAV API
     Then the HTTP status code should be "<code>"
     And for user "<user>" the content of the file ".space/readme.md" of the space "Project Jupiter" should be "<expectedContent>"
+    When user "<user>" lists all available spaces via the GraphApi
     And the JSON response should contain space called "Project Jupiter" owned by "Alice" with description file ".space/readme.md" and match
     """
     {
@@ -422,7 +423,7 @@ Feature: Change data of space
     Given user "Alice" has created a folder ".space" in space "Project Jupiter"
     And user "Alice" has uploaded a file inside space "Project Jupiter" with content "" to ".space/spaceImage.jpeg"
     And user "Alice" has set the file ".space/spaceImage.jpeg" as a space image in a special section of the "Project Jupiter" space
-    When user "<user>" has uploaded a file inside space "Project Jupiter" with content "" to ".space/newSpaceImage.png"
+    When user "<user>" uploads a file inside space "Project Jupiter" with content "" to ".space/newSpaceImage.png" using the WebDAV API
     And user "<user>" sets the file ".space/newSpaceImage.png" as a space image in a special section of the "Project Jupiter" space
     Then the HTTP status code should be "200"
     And the JSON response should contain space called "Project Jupiter" owned by "Alice" with description file ".space/newSpaceImage.png" and match
diff --git a/tests/acceptance/features/bootstrap/GraphContext.php b/tests/acceptance/features/bootstrap/GraphContext.php
index 9cda27ea320..83eb0d73f95 100644
--- a/tests/acceptance/features/bootstrap/GraphContext.php
+++ b/tests/acceptance/features/bootstrap/GraphContext.php
@@ -762,6 +762,7 @@ class GraphContext implements Context {
 
 	/**
 	 * adds a user to a group
+	 * NOTE: If you want to make a request with non-existing user or group,provide "nonexistent" as their name
 	 *
 	 * @param string $group
 	 * @param string $user
@@ -772,8 +773,16 @@ class GraphContext implements Context {
 	 */
 	public function addUserToGroup(string $group, string $user, ?string $byUser = null): ResponseInterface {
 		$credentials = $this->getAdminOrUserCredentials($byUser);
-		$groupId = $this->featureContext->getAttributeOfCreatedGroup($group, "id");
-		$userId = $this->featureContext->getAttributeOfCreatedUser($user, "id");
+		if ($group === "nonexistent") {
+			$groupId = WebDavHelper::generateUUIDv4();
+		} else {
+			$groupId = $this->featureContext->getAttributeOfCreatedGroup($group, "id");
+		}
+		if ($user === "nonexistent") {
+			$userId = WebDavHelper::generateUUIDv4();
+		} else {
+			$userId = $this->featureContext->getAttributeOfCreatedUser($user, "id");
+		}
 		return GraphHelper::addUserToGroup(
 			$this->featureContext->getBaseUrl(),
 			$this->featureContext->getStepLineRef(),
@@ -800,10 +809,8 @@ class GraphContext implements Context {
 		string $group,
 		bool $checkResult = true
 	): void {
-		$result = $this->addUserToGroup($group, $user);
-		if ($checkResult && ($result->getStatusCode() !== 204)) {
-			$this->throwHttpException($result, "Could not add user '$user' to group '$group'.");
-		}
+		$response = $this->addUserToGroup($group, $user);
+		$this->featureContext->theHTTPStatusCodeShouldBe(204, '', $response);
 	}
 
 	/**
@@ -824,15 +831,14 @@ class GraphContext implements Context {
 	}
 
 	/**
-	 * @When the administrator tries to add user :user to group :group using the Graph API
+	 * @When the administrator tries to add nonexistent user to group :group using the Graph API
 	 *
-	 * @param string $user
 	 * @param string $group
 	 *
 	 * @return void
 	 */
-	public function theAdministratorTriesToAddUserToGroupUsingTheGraphAPI(string $user, string $group): void {
-		$this->featureContext->setResponse($this->addUserToGroup($group, $user));
+	public function theAdministratorTriesToAddNonExistentUserToGroupUsingTheGraphAPI(string $group): void {
+		$this->featureContext->setResponse($this->addUserToGroup($group, "nonexistent"));
 	}
 
 	/**
@@ -847,19 +853,7 @@ class GraphContext implements Context {
 	 * @throws GuzzleException | Exception
 	 */
 	public function theAdministratorTriesToAddUserToNonExistentGroupUsingTheGraphAPI(string $user, ?string $byUser = null): void {
-		$credentials = $this->getAdminOrUserCredentials($byUser);
-		$groupId = WebDavHelper::generateUUIDv4();
-		$userId = $this->featureContext->getAttributeOfCreatedUser($user, "id");
-		$this->featureContext->setResponse(
-			GraphHelper::addUserToGroup(
-				$this->featureContext->getBaseUrl(),
-				$this->featureContext->getStepLineRef(),
-				$credentials['username'],
-				$credentials['password'],
-				$userId,
-				$groupId
-			)
-		);
+		$this->featureContext->setResponse($this->addUserToGroup("nonexistent", $user, $byUser));
 	}
 
 	/**
@@ -1423,25 +1417,21 @@ class GraphContext implements Context {
 	 * @param string $user
 	 * @param array $userIds
 	 * @param string $groupId
-	 * @param TableNode $table
 	 *
 	 * @return void
 	 * @throws GuzzleException
 	 * @throws Exception
 	 */
-	public function addMultipleUsersToGroup(string $user, array $userIds, string $groupId, TableNode $table): void {
+	public function addMultipleUsersToGroup(string $user, array $userIds, string $groupId): ResponseInterface {
 		$credentials = $this->getAdminOrUserCredentials($user);
-		$this->featureContext->verifyTableNodeColumns($table, ['username']);
 
-		$this->featureContext->setResponse(
-			GraphHelper::addUsersToGroup(
-				$this->featureContext->getBaseUrl(),
-				$this->featureContext->getStepLineRef(),
-				$credentials["username"],
-				$credentials["password"],
-				$groupId,
-				$userIds
-			)
+		return GraphHelper::addUsersToGroup(
+			$this->featureContext->getBaseUrl(),
+			$this->featureContext->getStepLineRef(),
+			$credentials["username"],
+			$credentials["password"],
+			$groupId,
+			$userIds
 		);
 	}
 
@@ -1462,7 +1452,9 @@ class GraphContext implements Context {
 		foreach ($table->getHash() as $row) {
 			$userIds[] = $this->featureContext->getAttributeOfCreatedUser($row['username'], "id");
 		}
-		$this->addMultipleUsersToGroup($user, $userIds, $groupId, $table);
+		$this->featureContext->verifyTableNodeColumns($table, ['username']);
+		$response = $this->addMultipleUsersToGroup($user, $userIds, $groupId);
+		$this->featureContext->setResponse($response);
 	}
 
 	/**
@@ -1552,7 +1544,9 @@ class GraphContext implements Context {
 		foreach ($table->getHash() as $row) {
 			$userIds[] = $this->featureContext->getAttributeOfCreatedUser($row['username'], "id");
 		}
-		$this->addMultipleUsersToGroup($user, $userIds, $groupId, $table);
+		$this->featureContext->verifyTableNodeColumns($table, ['username']);
+		$response = $this->addMultipleUsersToGroup($user, $userIds, $groupId);
+		$this->featureContext->setResponse($response);
 	}
 
 	/**
@@ -1572,7 +1566,9 @@ class GraphContext implements Context {
 		foreach ($table->getHash() as $row) {
 			$userIds[] = WebDavHelper::generateUUIDv4();
 		}
-		$this->addMultipleUsersToGroup($user, $userIds, $groupId, $table);
+		$this->featureContext->verifyTableNodeColumns($table, ['username']);
+		$response = $this->addMultipleUsersToGroup($user, $userIds, $groupId);
+		$this->featureContext->setResponse($response);
 	}
 
 	/**
@@ -1594,7 +1590,9 @@ class GraphContext implements Context {
 			$userId = $this->featureContext->getAttributeOfCreatedUser($row['username'], "id");
 			$userIds[] = $userId ?: WebDavHelper::generateUUIDv4();
 		}
-		$this->addMultipleUsersToGroup($user, $userIds, $groupId, $table);
+		$this->featureContext->verifyTableNodeColumns($table, ['username']);
+		$response = $this->addMultipleUsersToGroup($user, $userIds, $groupId);
+		$this->featureContext->setResponse($response);
 	}
 
 	/**
@@ -1987,12 +1985,9 @@ class GraphContext implements Context {
 		foreach ($table->getHash() as $row) {
 			$userIds[] = $this->featureContext->getAttributeOfCreatedUser($row['username'], "id");
 		}
-		$this->addMultipleUsersToGroup($user, $userIds, $groupId, $table);
-		$response = $this->featureContext->getResponse();
-		if ($response->getStatusCode() !== 204) {
-			$this->throwHttpException($response, "Cannot add users to group '$group'");
-		}
-		$this->featureContext->emptyLastHTTPStatusCodesArray();
+		$this->featureContext->verifyTableNodeColumns($table, ['username']);
+		$response = $this->addMultipleUsersToGroup($user, $userIds, $groupId);
+		$this->featureContext->theHTTPStatusCodeShouldBe(204, '', $response);
 	}
 
 	/**
diff --git a/tests/acceptance/features/bootstrap/Provisioning.php b/tests/acceptance/features/bootstrap/Provisioning.php
index 5508abff0e6..27f47ebcb43 100644
--- a/tests/acceptance/features/bootstrap/Provisioning.php
+++ b/tests/acceptance/features/bootstrap/Provisioning.php
@@ -2966,82 +2966,8 @@ trait Provisioning {
 	 * @throws Exception
 	 */
 	public function adminAddsUserToGroupUsingTheProvisioningApi(string $user, string $group):void {
-		$this->addUserToGroup($user, $group, "api");
-	}
-
-	/**
-	 * @When the administrator adds the following users to the following groups using the provisioning API
-	 *
-	 * @param TableNode $table
-	 *
-	 * @return void
-	 * @throws Exception
-	 */
-	public function theAdministratorAddsUserToTheFollowingGroupsUsingTheProvisioningApi(TableNode $table):void {
-		$this->verifyTableNodeColumns($table, ["username", "groupname"], ["comment"]);
-		$rows = $table->getHash();
-		foreach ($rows as $row) {
-			$this->adminAddsUserToGroupUsingTheProvisioningApi($row["username"], $row["groupname"]);
-		}
-	}
-
-	/**
-	 * @When user :user tries to add user :otherUser to group :group using the provisioning API
-	 *
-	 * @param string $user
-	 * @param string $otherUser
-	 * @param string $group
-	 *
-	 * @return void
-	 * @throws Exception
-	 */
-	public function userTriesToAddUserToGroupUsingTheProvisioningApi(string $user, string $otherUser, string $group):void {
-		$actualUser = $this->getActualUsername($user);
-		$actualPassword = $this->getUserPassword($actualUser);
-		$actualOtherUser = $this->getActualUsername($otherUser);
-		$result = UserHelper::addUserToGroup(
-			$this->getBaseUrl(),
-			$actualOtherUser,
-			$group,
-			$actualUser,
-			$actualPassword,
-			$this->getStepLineRef(),
-			$this->ocsApiVersion
-		);
-		$this->response = $result;
-	}
-
-	/**
-	 * @When user :user tries to add himself to group :group using the provisioning API
-	 *
-	 * @param string $user
-	 * @param string $group
-	 *
-	 * @return void
-	 * @throws Exception
-	 */
-	public function userTriesToAddHimselfToGroupUsingTheProvisioningApi(string $user, string $group):void {
-		$this->userTriesToAddUserToGroupUsingTheProvisioningApi($user, $user, $group);
-	}
-
-	/**
-	 * @When the administrator tries to add user :user to group :group using the provisioning API
-	 *
-	 * @param string $user
-	 * @param string $group
-	 *
-	 * @return void
-	 * @throws Exception
-	 */
-	public function theAdministratorTriesToAddUserToGroupUsingTheProvisioningApi(
-		string $user,
-		string $group
-	):void {
-		$this->userTriesToAddUserToGroupUsingTheProvisioningApi(
-			$this->getAdminUsername(),
-			$user,
-			$group
-		);
+		$response = $this->graphContext->addUserToGroup($group, $user);
+		$this->setResponse($response);
 	}
 
 	/**
@@ -3055,7 +2981,21 @@ trait Provisioning {
 	 */
 	public function userHasBeenAddedToGroup(string $user, string $group):void {
 		$user = $this->getActualUsername($user);
-		$this->addUserToGroup($user, $group, null, true);
+		if ($this->isTestingWithLdap()) {
+			try {
+				$this->addUserToLdapGroup(
+					$user,
+					$group
+				);
+			} catch (LdapException $exception) {
+				throw new Exception(
+					"User $user cannot be added to $group Error: $exception"
+				);
+			}
+		} else {
+			$response = $this->graphContext->addUserToGroup($group, $user);
+			$this->theHTTPStatusCodeShouldBe(204, '', $response);
+		}
 	}
 
 	/**
@@ -3073,105 +3013,6 @@ trait Provisioning {
 		}
 	}
 
-	/**
-	 * @Given /^user "([^"]*)" has been added to database backend group "([^"]*)"$/
-	 *
-	 * @param string $user
-	 * @param string $group
-	 *
-	 * @return void
-	 * @throws Exception
-	 */
-	public function userHasBeenAddedToDatabaseBackendGroup(string $user, string $group):void {
-		$this->addUserToGroup($user, $group, 'api', true);
-	}
-
-	/**
-	 * @param string $user
-	 * @param string $group
-	 * @param string|null $method how to add the user to the group api|occ
-	 * @param bool $checkResult if true, then check the status of the operation. default false.
-	 * 			                for given step checkResult is expected to be set as true
-	 * 			                for when step checkResult is expected to be set as false
-	 *
-	 * @return void
-	 * @throws Exception
-	 */
-	public function addUserToGroup(string $user, string $group, ?string $method = null, bool $checkResult = false):void {
-		$user = $this->getActualUsername($user);
-		if ($method === null
-			&& $this->isTestingWithLdap()
-			&& !$this->isLocalAdminGroup($group)
-		) {
-			//guess yourself
-			$method = "ldap";
-		} elseif ($method === null && OcisHelper::isTestingWithGraphApi()) {
-			$method = "graph";
-		} elseif ($method === null) {
-			$method = "api";
-		}
-		$method = \trim(\strtolower($method));
-		switch ($method) {
-			case "api":
-				$result = UserHelper::addUserToGroup(
-					$this->getBaseUrl(),
-					$user,
-					$group,
-					$this->getAdminUsername(),
-					$this->getAdminPassword(),
-					$this->getStepLineRef(),
-					$this->ocsApiVersion
-				);
-				if ($checkResult && ($result->getStatusCode() !== 200)) {
-					throw new Exception(
-						"could not add user to group. "
-						. $result->getStatusCode() . " " . $result->getBody()
-					);
-				}
-				$this->response = $result;
-				if (!$checkResult) {
-					// for when step only
-					$this->pushToLastStatusCodesArrays();
-				}
-				break;
-			case "ldap":
-				try {
-					$this->addUserToLdapGroup(
-						$user,
-						$group
-					);
-				} catch (LdapException $exception) {
-					throw new Exception(
-						"User $user cannot be added to $group Error: $exception"
-					);
-				};
-				break;
-			case "graph":
-				$this->graphContext->adminHasAddedUserToGroupUsingTheGraphApi(
-					$user,
-					$group
-				);
-				break;
-			default:
-				throw new InvalidArgumentException(
-					"Invalid method to add a user to a group"
-				);
-		}
-	}
-
-	/**
-	 * @Given the administrator has been added to group :group
-	 *
-	 * @param string $group
-	 *
-	 * @return void
-	 * @throws Exception
-	 */
-	public function theAdministratorHasBeenAddedToGroup(string $group):void {
-		$admin = $this->getAdminUsername();
-		$this->addUserToGroup($admin, $group, null, true);
-	}
-
 	/**
 	 * @param string $group
 	 * @param bool $shouldExist - true if the group should exist
diff --git a/tests/acceptance/features/bootstrap/SpacesContext.php b/tests/acceptance/features/bootstrap/SpacesContext.php
index 1164ec3b795..4f1f13b6e76 100644
--- a/tests/acceptance/features/bootstrap/SpacesContext.php
+++ b/tests/acceptance/features/bootstrap/SpacesContext.php
@@ -159,16 +159,18 @@ class SpacesContext implements Context {
 	 * Get Space Array by name
 	 *
 	 * @param string $name
+	 * @param ResponseInterface|null $response
 	 *
 	 * @return array
 	 *
 	 * @throws Exception
 	 */
-	public function getSpaceByNameFromResponse(string $name): array {
-		$response = json_decode((string)$this->featureContext->getResponse()->getBody(), true, 512, JSON_THROW_ON_ERROR);
-		$spaceAsArray = $response;
-		if (isset($response['name']) && $response['name'] === $name) {
-			return $response;
+	public function getSpaceByNameFromResponse(string $name, ?ResponseInterface $response = null): array {
+		$response = $response ?? $this->featureContext->getResponse();
+		$decodedResponse = $this->featureContext->getJsonDecodedResponse($response);
+		$spaceAsArray = $decodedResponse;
+		if (isset($decodedResponse['name']) && $decodedResponse['name'] === $name) {
+			return $decodedResponse;
 		}
 		if (isset($spaceAsArray["value"])) {
 			foreach ($spaceAsArray["value"] as $spaceCandidate) {
@@ -194,9 +196,9 @@ class SpacesContext implements Context {
 			$spaceName = $this->featureContext->getUserDisplayName($user);
 		}
 		if (strtolower($user) === 'admin') {
-			$this->theUserListsAllAvailableSpacesUsingTheGraphApi($user);
+			$this->listAllAvailableSpaces($user);
 		} else {
-			$this->theUserListsAllHisAvailableSpacesUsingTheGraphApi($user);
+			$this->listAllAvailableSpacesOfUser($user);
 		}
 		$spaces = $this->getAvailableSpaces();
 		Assert::assertArrayHasKey($spaceName, $spaces, "Space with name $spaceName for user $user not found");
@@ -245,7 +247,7 @@ class SpacesContext implements Context {
 	 * @throws GuzzleException
 	 */
 	public function getSpaceByNameManager(string $user, string $spaceName): array {
-		$this->theUserListsAllAvailableSpacesUsingTheGraphApi($user);
+		$this->listAllAvailableSpaces($user);
 
 		$spaces = $this->getAvailableSpaces();
 		Assert::assertArrayHasKey($spaceName, $spaces, "Space with name '$spaceName' for user '$user' not found");
@@ -425,7 +427,7 @@ class SpacesContext implements Context {
 		$query = "\$filter=driveType eq project";
 		$userAdmin = $this->featureContext->getAdminUsername();
 
-		$this->theUserListsAllAvailableSpacesUsingTheGraphApi(
+		$this->listAllAvailableSpaces(
 			$userAdmin,
 			$query
 		);
@@ -501,6 +503,27 @@ class SpacesContext implements Context {
 		return HttpRequestHelper::sendRequest($fullUrl, $xRequestId, $method, $user, $password, $headers);
 	}
 
+	/**
+	 * @param string $user
+	 * @param string $query
+	 *
+	 * @return ResponseInterface
+	 *
+	 * @throws GuzzleException
+	 * @throws Exception
+	 */
+	public function listAllAvailableSpacesOfUser(string $user, string $query = ''): ResponseInterface {
+		$response = GraphHelper::getMySpaces(
+			$this->featureContext->getBaseUrl(),
+			$user,
+			$this->featureContext->getPasswordForUser($user),
+			"?" . $query,
+			$this->featureContext->getStepLineRef()
+		);
+		$this->rememberTheAvailableSpaces($response);
+		return $response;
+	}
+
 	/**
 	 * @When /^user "([^"]*)" lists all available spaces via the GraphApi$/
 	 * @When /^user "([^"]*)" lists all available spaces via the GraphApi with query "([^"]*)"$/
@@ -514,21 +537,33 @@ class SpacesContext implements Context {
 	 * @throws Exception
 	 */
 	public function theUserListsAllHisAvailableSpacesUsingTheGraphApi(string $user, string $query = ''): void {
-		$this->featureContext->setResponse(
-			GraphHelper::getMySpaces(
-				$this->featureContext->getBaseUrl(),
-				$user,
-				$this->featureContext->getPasswordForUser($user),
-				"?" . $query,
-				$this->featureContext->getStepLineRef()
-			)
-		);
-		$this->rememberTheAvailableSpaces();
+		$this->featureContext->setResponse($this->listAllAvailableSpacesOfUser($user, $query));
 	}
 
 	/**
 	 * The method is used on the administration setting tab, which only the Admin user and the Space admin user have access to
 	 *
+	 * @param string $user
+	 * @param string $query
+	 *
+	 * @return ResponseInterface
+	 *
+	 * @throws GuzzleException
+	 * @throws Exception
+	 */
+	public function listAllAvailableSpaces(string $user, string $query = ''): ResponseInterface {
+		$response = GraphHelper::getAllSpaces(
+			$this->featureContext->getBaseUrl(),
+			$user,
+			$this->featureContext->getPasswordForUser($user),
+			"?" . $query,
+			$this->featureContext->getStepLineRef()
+		);
+		$this->rememberTheAvailableSpaces($response);
+		return $response;
+	}
+
+	/**
 	 * @When /^user "([^"]*)" lists all spaces via the GraphApi$/
 	 * @When /^user "([^"]*)" lists all spaces via the GraphApi with query "([^"]*)"$/
 	 * @When /^user "([^"]*)" tries to list all spaces via the GraphApi$/
@@ -543,15 +578,8 @@ class SpacesContext implements Context {
 	 */
 	public function theUserListsAllAvailableSpacesUsingTheGraphApi(string $user, string $query = ''): void {
 		$this->featureContext->setResponse(
-			GraphHelper::getAllSpaces(
-				$this->featureContext->getBaseUrl(),
-				$user,
-				$this->featureContext->getPasswordForUser($user),
-				"?" . $query,
-				$this->featureContext->getStepLineRef()
-			)
+			$this->listAllAvailableSpaces($user, $query)
 		);
-		$this->rememberTheAvailableSpaces();
 	}
 
 	/**
@@ -872,7 +900,6 @@ class SpacesContext implements Context {
 			null,
 			$userName,
 		);
-
 		$this->featureContext->assertJsonDocumentMatchesSchema(
 			$responseBody,
 			$this->featureContext->getJSONSchema($schemaString)
@@ -896,12 +923,13 @@ class SpacesContext implements Context {
 		string $grantedUser,
 		string $role
 	): void {
-		$this->theUserListsAllHisAvailableSpacesUsingTheGraphApi($user);
+		$response = $this->listAllAvailableSpacesOfUser($user);
 		$this->featureContext->theHTTPStatusCodeShouldBe(
 			200,
-			"Expected response status code should be 200"
+			"Expected response status code should be 200",
+			$response
 		);
-		Assert::assertIsArray($spaceAsArray = $this->getSpaceByNameFromResponse($spaceName), "No space with name $spaceName found");
+		Assert::assertIsArray($spaceAsArray = $this->getSpaceByNameFromResponse($spaceName, $response), "No space with name $spaceName found");
 		$permissions = $spaceAsArray["root"]["permissions"];
 		$userId = $this->featureContext->getUserIdByUserName($grantedUser);
 
@@ -945,15 +973,16 @@ class SpacesContext implements Context {
 		string $shouldOrNot,
 		string $spaceName
 	): void {
-		$this->theUserListsAllHisAvailableSpacesUsingTheGraphApi($user);
+		$response = $this->listAllAvailableSpacesOfUser($user);
 		$this->featureContext->theHTTPStatusCodeShouldBe(
 			200,
-			"Expected response status code should be 200"
+			"Expected response status code should be 200",
+			$response
 		);
 		if (\trim($shouldOrNot) === "not") {
-			$this->jsonRespondedShouldNotContain($spaceName);
+			Assert::assertEmpty($this->getSpaceByNameFromResponse($spaceName, $response), "space $spaceName should not be available for a user");
 		} else {
-			Assert::assertNotEmpty($this->getSpaceByNameFromResponse($spaceName), "space '$spaceName' should be available for a user '$user' but not found");
+			Assert::assertNotEmpty($this->getSpaceByNameFromResponse($spaceName, $response), "space '$spaceName' should be available for a user '$user' but not found");
 		}
 	}
 
@@ -970,14 +999,15 @@ class SpacesContext implements Context {
 		string $user,
 		string $spaceName
 	): void {
-		$this->theUserListsAllHisAvailableSpacesUsingTheGraphApi($user);
+		$response = $this->listAllAvailableSpacesOfUser($user);
 		$this->featureContext->theHTTPStatusCodeShouldBe(
 			200,
-			"Expected response status code should be 200"
+			"Expected response status code should be 200",
+			$response
 		);
-		$response = json_decode((string)$this->featureContext->getResponse()->getBody(), true, 512, JSON_THROW_ON_ERROR);
-		if (isset($response["value"])) {
-			foreach ($response["value"] as $spaceCandidate) {
+		$decodedResponse = $this->featureContext->getJsonDecodedResponse($response);
+		if (isset($decodedResponse["value"])) {
+			foreach ($decodedResponse["value"] as $spaceCandidate) {
 				if ($spaceCandidate['name'] === $spaceName) {
 					if ($spaceCandidate['root']['deleted']['state'] !== 'trashed') {
 						throw new \Exception(
@@ -1355,10 +1385,12 @@ class SpacesContext implements Context {
 		string $spaceName,
 		string $newDescription
 	): void {
-		$this->updateSpaceDescription($user, $spaceName, $newDescription);
+		$bodyData = ["description" => $newDescription];
+		$response = $this->updateSpace($user, $spaceName, $bodyData, '');
 		$this->featureContext->theHTTPStatusCodeShouldBe(
 			200,
-			"Expected response status code should be 200"
+			"Expected response status code should be 200",
+			$response
 		);
 	}
 
@@ -1402,22 +1434,22 @@ class SpacesContext implements Context {
 		string $spaceName,
 		int $newQuota
 	): void {
-		$this->updateSpaceQuota($user, $spaceName, $newQuota);
+		$bodyData = ["quota" => ["total" => $newQuota]];
+		$response = $this->updateSpace($user, $spaceName, $bodyData, '');
 		$this->featureContext->theHTTPStatusCodeShouldBe(
 			200,
-			"Expected response status code should be 200"
+			"Expected response status code should be 200",
+			$response
 		);
 	}
 
 	/**
-	 * @When /^user "([^"]*)" sets the file "([^"]*)" as a (description|space image)\s? in a special section of the "([^"]*)" space$/
-	 *
 	 * @param string $user
 	 * @param string $file
 	 * @param string $type
 	 * @param string $spaceName
 	 *
-	 * @return void
+	 * @return ResponseInterface
 	 * @throws GuzzleException
 	 * @throws Exception
 	 */
@@ -1426,7 +1458,7 @@ class SpacesContext implements Context {
 		string $file,
 		string $type,
 		string $spaceName
-	): void {
+	): ResponseInterface {
 		$space = $this->getSpaceByName($user, $spaceName);
 		$spaceId = $space["id"];
 		$fileId = $this->getFileId($user, $spaceName, $file);
@@ -1439,15 +1471,40 @@ class SpacesContext implements Context {
 
 		$bodyData = ["special" => [["specialFolder" => ["name" => "$type"], "id" => "$fileId"]]];
 		$body = json_encode($bodyData, JSON_THROW_ON_ERROR);
+		return GraphHelper::updateSpace(
+			$this->featureContext->getBaseUrl(),
+			$user,
+			$this->featureContext->getPasswordForUser($user),
+			$body,
+			$spaceId,
+			$this->featureContext->getStepLineRef()
+		);
+	}
 
+	/**
+	 * @When /^user "([^"]*)" sets the file "([^"]*)" as a (description|space image)\s? in a special section of the "([^"]*)" space$/
+	 *
+	 * @param string $user
+	 * @param string $file
+	 * @param string $type
+	 * @param string $spaceName
+	 *
+	 * @return void
+	 * @throws GuzzleException
+	 * @throws Exception
+	 */
+	public function userSetsFileAsDescriptionOrImageInSpecialSectionOfSpace(
+		string $user,
+		string $file,
+		string $type,
+		string $spaceName
+	): void {
 		$this->featureContext->setResponse(
-			GraphHelper::updateSpace(
-				$this->featureContext->getBaseUrl(),
+			$this->updateSpaceSpecialSection(
 				$user,
-				$this->featureContext->getPasswordForUser($user),
-				$body,
-				$spaceId,
-				$this->featureContext->getStepLineRef()
+				$file,
+				$type,
+				$spaceName
 			)
 		);
 	}
@@ -1470,10 +1527,11 @@ class SpacesContext implements Context {
 		string $type,
 		string $spaceName
 	): void {
-		$this->updateSpaceSpecialSection($user, $file, $type, $spaceName);
+		$response = $this->updateSpaceSpecialSection($user, $file, $type, $spaceName);
 		$this->featureContext->theHTTPStatusCodeShouldBe(
 			200,
-			"Expected response status code should be 200"
+			"Expected response status code should be 200",
+			$response
 		);
 	}
 
@@ -1495,11 +1553,12 @@ class SpacesContext implements Context {
 		int $quota
 	): void {
 		$space = ["Name" => $spaceName, "driveType" => $spaceType, "quota" => ["total" => $quota]];
-		$this->featureContext->setResponse($this->createSpace($user, $space));
+		$response = $this->createSpace($user, $space);
 		$this->setSpaceCreator($spaceName, $user);
 		$this->featureContext->theHTTPStatusCodeShouldBe(
 			201,
-			"Expected response status code should be 201 (Created)"
+			"Expected response status code should be 201 (Created)",
+			$response
 		);
 	}
 
@@ -1519,11 +1578,12 @@ class SpacesContext implements Context {
 		string $spaceName
 	): void {
 		$space = ["Name" => $spaceName];
-		$this->featureContext->setResponse($this->createSpace($user, $space));
+		$response = $this->createSpace($user, $space);
 		$this->setSpaceCreator($spaceName, $user);
 		$this->featureContext->theHTTPStatusCodeShouldBe(
 			201,
-			"Expected response status code should be 201 (Created)"
+			"Expected response status code should be 201 (Created)",
+			$response
 		);
 	}
 
@@ -1578,22 +1638,20 @@ class SpacesContext implements Context {
 	}
 
 	/**
-	 * @When /^user "([^"]*)" moves (?:file|folder) "([^"]*)" to "([^"]*)" in space "([^"]*)" using the WebDAV API$/
-	 *
 	 * @param string $user
 	 * @param string $fileSource
 	 * @param string $fileDestination
 	 * @param string $spaceName
 	 *
-	 * @return void
+	 * @return ResponseInterface
 	 * @throws GuzzleException
 	 */
-	public function userMovesFileWithinSpaceUsingTheWebDAVAPI(
+	public function moveFileWithinSpace(
 		string $user,
 		string $fileSource,
 		string $fileDestination,
 		string $spaceName
-	):void {
+	):ResponseInterface {
 		$space = $this->getSpaceByName($user, $spaceName);
 		$headers['Destination'] = $this->destinationHeaderValueWithSpaceName(
 			$user,
@@ -1604,7 +1662,27 @@ class SpacesContext implements Context {
 
 		$fileSource = $this->escapePath(\trim($fileSource, "/"));
 		$fullUrl = $space["root"]["webDavUrl"] . '/' . $fileSource;
-		$this->featureContext->setResponse($this->moveFilesAndFoldersRequest($user, $fullUrl, $headers));
+		return $this->moveFilesAndFoldersRequest($user, $fullUrl, $headers);
+	}
+
+	/**
+	 * @When /^user "([^"]*)" moves (?:file|folder) "([^"]*)" to "([^"]*)" in space "([^"]*)" using the WebDAV API$/
+	 *
+	 * @param string $user
+	 * @param string $fileSource
+	 * @param string $fileDestination
+	 * @param string $spaceName
+	 *
+	 * @return void
+	 * @throws GuzzleException
+	 */
+	public function userMovesFileWithinSpaceUsingTheWebDAVAPI(
+		string $user,
+		string $fileSource,
+		string $fileDestination,
+		string $spaceName
+	):void {
+		$this->featureContext->setResponse($this->moveFileWithinSpace($user, $fileSource, $fileDestination, $spaceName));
 		$this->featureContext->pushToLastHttpStatusCodesArray();
 	}
 
@@ -1625,7 +1703,7 @@ class SpacesContext implements Context {
 		string $fileDestination,
 		string $spaceName
 	):void {
-		$this->userMovesFileWithinSpaceUsingTheWebDAVAPI(
+		$response = $this->moveFileWithinSpace(
 			$user,
 			$fileSource,
 			$fileDestination,
@@ -1634,8 +1712,9 @@ class SpacesContext implements Context {
 		$this->featureContext->theHTTPStatusCodeShouldBe(
 			201,
 			__METHOD__ . "Expected response status code should be 201 (Created)\n" .
-			"Actual response status code was: " . $this->featureContext->getResponse()->getStatusCode() . "\n" .
-			"Actual response body was: " . $this->featureContext->getResponse()->getBody()
+			"Actual response status code was: " . $response->getStatusCode() . "\n" .
+			"Actual response body was: " . $response->getBody(),
+			$response
 		);
 	}
 
@@ -1868,7 +1947,8 @@ class SpacesContext implements Context {
 		string $fileContent,
 		string $destination
 	): array {
-		$this->theUserListsAllHisAvailableSpacesUsingTheGraphApi($user);
+		$response = $this->listAllAvailableSpacesOfUser($user);
+		$this->featureContext->theHTTPStatusCodeShouldBe(200, "", $response);
 		$this->setSpaceIDByName($user, $spaceName);
 		$response = $this->featureContext->uploadFileWithContent($user, $fileContent, $destination, true);
 		$this->featureContext->theHTTPStatusCodeShouldBe(['201', '204'], "", $response);
@@ -1953,20 +2033,18 @@ class SpacesContext implements Context {
 	}
 
 	/**
-	 * @When /^user "([^"]*)" creates a share inside of space "([^"]*)" with settings:$/
-	 *
 	 * @param  string $user
 	 * @param  string $spaceName
 	 * @param TableNode $table
 	 *
-	 * @return void
+	 * @return ResponseInterface
 	 * @throws GuzzleException
 	 */
 	public function createShareResource(
 		string $user,
 		string $spaceName,
 		TableNode $table
-	): void {
+	): ResponseInterface {
 		$rows = $table->getRowsHash();
 		$rows["path"] = \array_key_exists("path", $rows) ? $rows["path"] : null;
 		$rows["shareType"] = \array_key_exists("shareType", $rows) ? $rows["shareType"] : 0;
@@ -1982,17 +2060,36 @@ class SpacesContext implements Context {
 		];
 
 		$fullUrl = $this->baseUrl . $this->ocsApiUrl;
+		$response = $this->sendPostRequestToUrl(
+			$fullUrl,
+			$user,
+			$this->featureContext->getPasswordForUser($user),
+			$body,
+			$this->featureContext->getStepLineRef()
+		);
+		$responseXml = $this->featureContext->getResponseXml($response, __METHOD__);
+		$this->featureContext->addToCreatedPublicShares($responseXml->data);
+		return $response;
+	}
+
+	/**
+	 * @When /^user "([^"]*)" creates a share inside of space "([^"]*)" with settings:$/
+	 *
+	 * @param  string $user
+	 * @param  string $spaceName
+	 * @param TableNode $table
+	 *
+	 * @return void
+	 * @throws GuzzleException
+	 */
+	public function userCreatesShareInsideOfSpaceWithSettings(
+		string $user,
+		string $spaceName,
+		TableNode $table
+	): void {
 		$this->featureContext->setResponse(
-			$this->sendPostRequestToUrl(
-				$fullUrl,
-				$user,
-				$this->featureContext->getPasswordForUser($user),
-				$body,
-				$this->featureContext->getStepLineRef()
-			)
+			$this->createShareResource($user, $spaceName, $table)
 		);
-		$response = $this->featureContext->getResponseXml(null, __METHOD__);
-		$this->featureContext->addToCreatedPublicShares($response->data);
 	}
 
 	/**
@@ -2005,17 +2102,13 @@ class SpacesContext implements Context {
 	 * @return void
 	 * @throws GuzzleException
 	 */
-	public function hasSharedTheFollowingEntityInsideOfSpace(
+	public function userHasSharedTheFollowingEntityInsideOfSpace(
 		string $user,
 		string $spaceName,
 		TableNode $table
 	): void {
-		$this->createShareResource($user, $spaceName, $table);
-		Assert::assertEquals(
-			$this->featureContext->getResponse()->getStatusCode(),
-			200,
-			"Expected response status code should be 200"
-		);
+		$response = $this->createShareResource($user, $spaceName, $table);
+		$this->featureContext->theHTTPStatusCodeShouldBe(200, "", $response);
 	}
 
 	/**
@@ -2070,21 +2163,20 @@ class SpacesContext implements Context {
 		$rows['expireDate'] = $dateTime->format('Y-m-d\\TH:i:sP');
 		$this->featureContext->setResponse($this->updateSharedResource($user, $rows));
 	}
+
 	/**
-	 * @When /^user "([^"]*)" creates a public link share inside of space "([^"]*)" with settings:$/
-	 *
 	 * @param  string $user
 	 * @param  string $spaceName
 	 * @param TableNode $table
 	 *
-	 * @return void
+	 * @return ResponseInterface
 	 * @throws GuzzleException
 	 */
 	public function createPublicLinkToEntityInsideOfSpaceRequest(
 		string $user,
 		string $spaceName,
 		TableNode $table
-	): void {
+	): ResponseInterface {
 		$space = $this->getSpaceByName($user, $spaceName);
 		$rows = $table->getRowsHash();
 
@@ -2106,18 +2198,37 @@ class SpacesContext implements Context {
 
 		$fullUrl = $this->baseUrl . $this->ocsApiUrl;
 
-		$this->featureContext->setResponse(
-			$this->sendPostRequestToUrl(
-				$fullUrl,
-				$user,
-				$this->featureContext->getPasswordForUser($user),
-				$body,
-				$this->featureContext->getStepLineRef()
-			)
+		$response =  $this->sendPostRequestToUrl(
+			$fullUrl,
+			$user,
+			$this->featureContext->getPasswordForUser($user),
+			$body,
+			$this->featureContext->getStepLineRef()
 		);
 
-		$response = $this->featureContext->getResponseXml(null, __METHOD__);
-		$this->featureContext->addToCreatedPublicShares($response->data);
+		$responseXml = $this->featureContext->getResponseXml($response, __METHOD__);
+		$this->featureContext->addToCreatedPublicShares($responseXml->data);
+		return $response;
+	}
+
+	/**
+	 * @When /^user "([^"]*)" creates a public link share inside of space "([^"]*)" with settings:$/
+	 *
+	 * @param  string $user
+	 * @param  string $spaceName
+	 * @param TableNode $table
+	 *
+	 * @return void
+	 * @throws GuzzleException
+	 */
+	public function userCreatesPublicLinkShareInsideOfSpaceWithSettings(
+		string $user,
+		string $spaceName,
+		TableNode $table
+	): void {
+		$this->featureContext->setResponse(
+			$this->createPublicLinkToEntityInsideOfSpaceRequest($user, $spaceName, $table)
+		);
 	}
 
 	/**
@@ -2135,12 +2246,12 @@ class SpacesContext implements Context {
 		string $spaceName,
 		TableNode $table
 	): void {
-		$this->createPublicLinkToEntityInsideOfSpaceRequest($user, $spaceName, $table);
+		$response = $this->createPublicLinkToEntityInsideOfSpaceRequest($user, $spaceName, $table);
 
-		$expectedHTTPStatus = "200";
 		$this->featureContext->theHTTPStatusCodeShouldBe(
-			$expectedHTTPStatus,
-			"Expected response status code should be $expectedHTTPStatus"
+			200,
+			"Expected response status code should be 200",
+			$response
 		);
 	}
 
@@ -2159,14 +2270,16 @@ class SpacesContext implements Context {
 		string $spaceName,
 		TableNode $tableNode
 	): void {
-		$this->sendShareSpaceRequest($user, $spaceName, $tableNode);
+		$rows = $tableNode->getRowsHash();
+		$response = $this->shareSpace($user, $spaceName, $rows);
 		$expectedHTTPStatus = "200";
 		$this->featureContext->theHTTPStatusCodeShouldBe(
 			$expectedHTTPStatus,
-			"Expected response status code should be $expectedHTTPStatus"
+			"Expected response status code should be $expectedHTTPStatus",
+			$response
 		);
 		$expectedOCSStatus = "200";
-		$this->ocsContext->theOCSStatusCodeShouldBe($expectedOCSStatus, "Expected OCS response status code $expectedOCSStatus");
+		$this->ocsContext->theOCSStatusCodeShouldBe($expectedOCSStatus, "Expected OCS response status code $expectedOCSStatus", $response);
 	}
 
 	/**
@@ -2184,38 +2297,78 @@ class SpacesContext implements Context {
 		string $spaceName,
 		string $recipient
 	): void {
-		$this->sendUnshareSpaceRequest($user, $spaceName, $recipient);
+		$response = $this->sendUnshareSpaceRequest($user, $spaceName, $recipient);
 		$this->featureContext->theHTTPStatusCodeShouldBe(
 			200,
-			"Expected response status code should be 200"
+			"Expected response status code should be 200",
+			$response
 		);
 	}
 
 	/**
-	 * @When /^user "([^"]*)" unshares a space "([^"]*)" to (?:user|group) "([^"]*)"$/
-	 *
 	 * @param  string $user
 	 * @param  string $spaceName
 	 * @param  string $recipient
 	 *
-	 * @return void
+	 * @return ResponseInterface
 	 * @throws GuzzleException
 	 */
 	public function sendUnshareSpaceRequest(
 		string $user,
 		string $spaceName,
 		string $recipient
-	): void {
+	): ResponseInterface {
 		$space = $this->getSpaceByName($user, $spaceName);
 		$fullUrl = $this->baseUrl . $this->ocsApiUrl . "/" . $space['id'] . "?shareWith=" . $recipient;
 
+		return HttpRequestHelper::delete(
+			$fullUrl,
+			$this->featureContext->getStepLineRef(),
+			$user,
+			$this->featureContext->getPasswordForUser($user)
+		);
+	}
+
+	/**
+	 * @When /^user "([^"]*)" unshares a space "([^"]*)" to (?:user|group) "([^"]*)"$/
+	 *
+	 * @param  string $user
+	 * @param  string $spaceName
+	 * @param  string $recipient
+	 *
+	 * @return void
+	 * @throws GuzzleException
+	 */
+	public function userUnsharesSpace(
+		string $user,
+		string $spaceName,
+		string $recipient
+	): void {
 		$this->featureContext->setResponse(
-			HttpRequestHelper::delete(
-				$fullUrl,
-				$this->featureContext->getStepLineRef(),
-				$user,
-				$this->featureContext->getPasswordForUser($user)
-			)
+			$this->sendUnshareSpaceRequest($user, $spaceName, $recipient)
+		);
+	}
+
+	/**
+	 * @param  string $user
+	 * @param  string $object
+	 * @param  string $spaceName
+	 *
+	 * @return ResponseInterface
+	 * @throws GuzzleException
+	 */
+	public function removeObjectFromSpace(
+		string $user,
+		string $object,
+		string $spaceName
+	): ResponseInterface {
+		$space = $this->getSpaceByName($user, $spaceName);
+		$spaceWebDavUrl = $space["root"]["webDavUrl"] . '/' . ltrim($object, "/");
+		return HttpRequestHelper::delete(
+			$spaceWebDavUrl,
+			$this->featureContext->getStepLineRef(),
+			$user,
+			$this->featureContext->getPasswordForUser($user)
 		);
 	}
 
@@ -2229,20 +2382,13 @@ class SpacesContext implements Context {
 	 * @return void
 	 * @throws GuzzleException
 	 */
-	public function sendRemoveObjectFromSpaceRequest(
+	public function userRemovesFileOrFolderFromSpace(
 		string $user,
 		string $object,
 		string $spaceName
 	): void {
-		$space = $this->getSpaceByName($user, $spaceName);
-		$spaceWebDavUrl = $space["root"]["webDavUrl"] . '/' . ltrim($object, "/");
 		$this->featureContext->setResponse(
-			HttpRequestHelper::delete(
-				$spaceWebDavUrl,
-				$this->featureContext->getStepLineRef(),
-				$user,
-				$this->featureContext->getPasswordForUser($user)
-			)
+			$this->removeObjectFromSpace($user, $object, $spaceName)
 		);
 	}
 
@@ -2259,10 +2405,34 @@ class SpacesContext implements Context {
 		string $user,
 		string $spaceName
 	):void {
-		$this->sendDeleteSpaceRequest($user, $spaceName);
+		$response = $this->deleteSpace($user, $spaceName);
 		$this->featureContext->theHTTPStatusCodeShouldBe(
 			204,
-			"Expected response status code should be 200"
+			"Expected response status code should be 200",
+			$response
+		);
+	}
+
+	/**
+	 * @param  string $user
+	 * @param  string $spaceName
+	 * @param  string $owner
+	 *
+	 * @return ResponseInterface
+	 * @throws GuzzleException
+	 */
+	public function disableSpace(
+		string $user,
+		string $spaceName,
+		string $owner = ''
+	): ResponseInterface {
+		$space = $this->getSpaceByName(($owner !== "") ? $owner : $user, $spaceName);
+		return GraphHelper::disableSpace(
+			$this->featureContext->getBaseUrl(),
+			$user,
+			$this->featureContext->getPasswordForUser($user),
+			$space["id"],
+			$this->featureContext->getStepLineRef()
 		);
 	}
 
@@ -2277,20 +2447,13 @@ class SpacesContext implements Context {
 	 * @return void
 	 * @throws GuzzleException
 	 */
-	public function sendDisableSpaceRequest(
+	public function userDisablesSpace(
 		string $user,
 		string $spaceName,
 		string $owner = ''
 	): void {
-		$space = $this->getSpaceByName(($owner !== "") ? $owner : $user, $spaceName);
 		$this->featureContext->setResponse(
-			GraphHelper::disableSpace(
-				$this->featureContext->getBaseUrl(),
-				$user,
-				$this->featureContext->getPasswordForUser($user),
-				$space["id"],
-				$this->featureContext->getStepLineRef()
-			)
+			$this->disableSpace($user, $spaceName, $owner)
 		);
 	}
 
@@ -2304,16 +2467,17 @@ class SpacesContext implements Context {
 	 * @return void
 	 * @throws GuzzleException
 	 */
-	public function sendUserHasRemovedObjectFromSpaceRequest(
+	public function userHasRemovedFileOrFolderFromSpace(
 		string $user,
 		string $object,
 		string $spaceName
 	): void {
-		$this->sendRemoveObjectFromSpaceRequest($user, $object, $spaceName);
+		$response = $this->removeObjectFromSpace($user, $object, $spaceName);
 		$expectedHTTPStatus = "204";
 		$this->featureContext->theHTTPStatusCodeShouldBe(
 			$expectedHTTPStatus,
-			"Expected response status code should be $expectedHTTPStatus"
+			"Expected response status code should be $expectedHTTPStatus",
+			$response
 		);
 	}
 
@@ -2330,11 +2494,36 @@ class SpacesContext implements Context {
 		string $user,
 		string $spaceName
 	): void {
-		$this->sendDisableSpaceRequest($user, $spaceName);
+		$response = $this->disableSpace($user, $spaceName);
 		$expectedHTTPStatus = "204";
 		$this->featureContext->theHTTPStatusCodeShouldBe(
 			$expectedHTTPStatus,
-			"Expected response status code should be $expectedHTTPStatus"
+			"Expected response status code should be $expectedHTTPStatus",
+			$response
+		);
+	}
+
+	/**
+	 * @param  string $user
+	 * @param  string $spaceName
+	 * @param string $owner
+	 *
+	 * @return ResponseInterface
+	 * @throws GuzzleException
+	 */
+	public function deleteSpace(
+		string $user,
+		string $spaceName,
+		string $owner = ''
+	): ResponseInterface {
+		$space = $this->getSpaceByName(($owner !== "") ? $owner : $user, $spaceName);
+
+		return GraphHelper::deleteSpace(
+			$this->featureContext->getBaseUrl(),
+			$user,
+			$this->featureContext->getPasswordForUser($user),
+			$space["id"],
+			$this->featureContext->getStepLineRef()
 		);
 	}
 
@@ -2349,21 +2538,36 @@ class SpacesContext implements Context {
 	 * @return void
 	 * @throws GuzzleException
 	 */
-	public function sendDeleteSpaceRequest(
+	public function userDeletesSpace(
 		string $user,
 		string $spaceName,
 		string $owner = ''
 	): void {
-		$space = $this->getSpaceByName(($owner !== "") ? $owner : $user, $spaceName);
-
 		$this->featureContext->setResponse(
-			GraphHelper::deleteSpace(
-				$this->featureContext->getBaseUrl(),
-				$user,
-				$this->featureContext->getPasswordForUser($user),
-				$space["id"],
-				$this->featureContext->getStepLineRef()
-			)
+			$this->deleteSpace($user, $spaceName, $owner)
+		);
+	}
+
+	/**
+	 * @param  string $user
+	 * @param  string $spaceName
+	 * @param  string $owner
+	 *
+	 * @return ResponseInterface
+	 * @throws GuzzleException
+	 */
+	public function restoreSpace(
+		string $user,
+		string $spaceName,
+		string $owner = ''
+	): ResponseInterface {
+		$space = $this->getSpaceByName(($owner !== "") ? $owner : $user, $spaceName);
+		return GraphHelper::restoreSpace(
+			$this->featureContext->getBaseUrl(),
+			$user,
+			$this->featureContext->getPasswordForUser($user),
+			$space["id"],
+			$this->featureContext->getStepLineRef()
 		);
 	}
 
@@ -2378,20 +2582,13 @@ class SpacesContext implements Context {
 	 * @return void
 	 * @throws GuzzleException
 	 */
-	public function sendRestoreSpaceRequest(
+	public function userRestoresADisabledSpace(
 		string $user,
 		string $spaceName,
 		string $owner = ''
 	): void {
-		$space = $this->getSpaceByName(($owner !== "") ? $owner : $user, $spaceName);
 		$this->featureContext->setResponse(
-			GraphHelper::restoreSpace(
-				$this->featureContext->getBaseUrl(),
-				$user,
-				$this->featureContext->getPasswordForUser($user),
-				$space["id"],
-				$this->featureContext->getStepLineRef()
-			)
+			$this->restoreSpace($user, $spaceName, $owner)
 		);
 	}
 
@@ -2408,11 +2605,34 @@ class SpacesContext implements Context {
 		string $user,
 		string $spaceName
 	): void {
-		$this->sendRestoreSpaceRequest($user, $spaceName);
+		$response = $this->restoreSpace($user, $spaceName);
 		$expectedHTTPStatus = "200";
 		$this->featureContext->theHTTPStatusCodeShouldBe(
 			$expectedHTTPStatus,
-			"Expected response status code should be $expectedHTTPStatus"
+			"Expected response status code should be $expectedHTTPStatus",
+			$response
+		);
+	}
+
+	/**
+	 * @param  string $user
+	 * @param  string $spaceName
+	 *
+	 * @return ResponseInterface
+	 * @throws GuzzleException
+	 */
+	public function listAllDeletedFilesFromTrash(
+		string $user,
+		string $spaceName
+	): ResponseInterface {
+		$space = $this->getSpaceByName($user, $spaceName);
+		$fullUrl = $this->baseUrl . $this->davSpacesUrl . "trash-bin/" . $space["id"];
+		return HttpRequestHelper::sendRequest(
+			$fullUrl,
+			$this->featureContext->getStepLineRef(),
+			'PROPFIND',
+			$user,
+			$this->featureContext->getPasswordForUser($user)
 		);
 	}
 
@@ -2429,10 +2649,8 @@ class SpacesContext implements Context {
 		string $user,
 		string $spaceName
 	): void {
-		$space = $this->getSpaceByName($user, $spaceName);
-		$fullUrl = $this->baseUrl . $this->davSpacesUrl . "trash-bin/" . $space["id"];
 		$this->featureContext->setResponse(
-			HttpRequestHelper::sendRequest($fullUrl, $this->featureContext->getStepLineRef(), 'PROPFIND', $user, $this->featureContext->getPasswordForUser($user))
+			$this->listAllDeletedFilesFromTrash($user, $spaceName)
 		);
 	}
 
@@ -2479,13 +2697,14 @@ class SpacesContext implements Context {
 		string $user,
 		string $spaceName
 	): array {
-		$this->userListAllDeletedFilesInTrash($user, $spaceName);
+		$response = $this->listAllDeletedFilesFromTrash($user, $spaceName);
 		$this->featureContext->theHTTPStatusCodeShouldBe(
 			207,
-			"Expected response status code should be 207"
+			"Expected response status code should be 207",
+			$response
 		);
 		return $this->trashbinContext->getTrashbinContentFromResponseXml(
-			$this->featureContext->getResponseXml($this->featureContext->getResponse())
+			$this->featureContext->getResponseXml($response)
 		);
 	}
 
@@ -2910,20 +3129,18 @@ class SpacesContext implements Context {
 	}
 
 	/**
-	 * @When /^user "([^"]*)" creates a public link share of the space "([^"]*)" with settings:$/
-	 *
 	 * @param  string $user
 	 * @param  string $spaceName
 	 * @param TableNode|null $table
 	 *
-	 * @return void
+	 * @return ResponseInterface
 	 * @throws GuzzleException
 	 */
 	public function sendShareSpaceViaLinkRequest(
 		string $user,
 		string $spaceName,
 		?TableNode $table
-	): void {
+	): ResponseInterface {
 		$space = $this->getSpaceByName($user, $spaceName);
 		$rows = $table->getRowsHash();
 
@@ -2944,18 +3161,41 @@ class SpacesContext implements Context {
 
 		$fullUrl = $this->baseUrl . $this->ocsApiUrl;
 
+		$response = $this->sendPostRequestToUrl(
+			$fullUrl,
+			$user,
+			$this->featureContext->getPasswordForUser($user),
+			$body,
+			$this->featureContext->getStepLineRef()
+		);
+
+		$responseXml = $this->featureContext->getResponseXml($response, __METHOD__);
+		$this->featureContext->addToCreatedPublicShares($responseXml->data);
+		return $response;
+	}
+
+	/**
+	 * @When /^user "([^"]*)" creates a public link share of the space "([^"]*)" with settings:$/
+	 *
+	 * @param  string $user
+	 * @param  string $spaceName
+	 * @param TableNode|null $table
+	 *
+	 * @return void
+	 * @throws GuzzleException
+	 */
+	public function userCreatesAPublicLinkShareOfSpaceWithSettings(
+		string $user,
+		string $spaceName,
+		?TableNode $table
+	): void {
 		$this->featureContext->setResponse(
-			$this->sendPostRequestToUrl(
-				$fullUrl,
+			$this->sendShareSpaceViaLinkRequest(
 				$user,
-				$this->featureContext->getPasswordForUser($user),
-				$body,
-				$this->featureContext->getStepLineRef()
+				$spaceName,
+				$table
 			)
 		);
-
-		$response = $this->featureContext->getResponseXml(null, __METHOD__);
-		$this->featureContext->addToCreatedPublicShares($response->data);
 	}
 
 	/**
@@ -2973,12 +3213,13 @@ class SpacesContext implements Context {
 		string $spaceName,
 		?TableNode $table
 	): void {
-		$this->sendShareSpaceViaLinkRequest($user, $spaceName, $table);
+		$response = $this->sendShareSpaceViaLinkRequest($user, $spaceName, $table);
 
 		$expectedHTTPStatus = "200";
 		$this->featureContext->theHTTPStatusCodeShouldBe(
 			$expectedHTTPStatus,
-			"Expected response status code should be $expectedHTTPStatus"
+			"Expected response status code should be $expectedHTTPStatus",
+			$response
 		);
 	}
 
@@ -3374,12 +3615,13 @@ class SpacesContext implements Context {
 		string $role,
 		string $expirationDate = null
 	): void {
-		$this->theUserListsAllHisAvailableSpacesUsingTheGraphApi($user);
+		$response = $this->listAllAvailableSpacesOfUser($user);
 		$this->featureContext->theHTTPStatusCodeShouldBe(
 			200,
-			"Expected response status code should be 200"
+			"Expected response status code should be 200",
+			$response
 		);
-		Assert::assertIsArray($spaceAsArray = $this->getSpaceByNameFromResponse($spaceName), "No space with name $spaceName found");
+		Assert::assertIsArray($spaceAsArray = $this->getSpaceByNameFromResponse($spaceName, $response), "No space with name $spaceName found");
 		$recipientType === 'user' ? $recipientId = $this->featureContext->getUserIdByUserName($recipient) : $recipientId = $this->featureContext->getGroupIdByGroupName($recipient);
 		$foundRoleInResponse = false;
 		foreach ($spaceAsArray['root']['permissions'] as $permission) {
diff --git a/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature b/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature
index de3b4e97abd..ef7f4171438 100644
--- a/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature
+++ b/tests/acceptance/features/coreApiShareCreateSpecialToShares2/createShareReceivedInMultipleWays.feature
@@ -160,7 +160,7 @@ Feature: share resources where the sharee receives the share in multiple ways
       | 1               | 100             |
       | 2               | 200             |
 
-  @skipOnGraph
+  @skipOnReva
   Scenario Outline: share with a group and then add a user to that group that already has a file with the shared name
     Given using OCS API version "<ocs_api_version>"
     And user "Carol" has been created with default attributes and without skeleton files
@@ -176,16 +176,15 @@ Feature: share resources where the sharee receives the share in multiple ways
       | shareWith | grp1       |
     And user "Brian" has accepted share "/lorem.txt" offered by user "Alice"
     When the administrator adds user "Carol" to group "grp1" using the provisioning API
-    Then the OCS status code should be "<ocs_status_code>"
-    And the HTTP status code should be "200"
+    Then the HTTP status code should be "204"
     And user "Carol" should be able to accept pending share "/lorem.txt" offered by user "Alice"
     And the content of file "Shares/lorem.txt" for user "Brian" should be "Shared content"
     And the content of file "lorem.txt" for user "Carol" should be "My content"
     And the content of file "Shares/lorem.txt" for user "Carol" should be "Shared content"
     Examples:
-      | ocs_api_version | ocs_status_code |
-      | 1               | 100             |
-      | 2               | 200             |
+      | ocs_api_version |
+      | 1               |
+      | 2               |
 
   @issue-2440
   Scenario: sharing parent folder to user with all permissions and its child folder to group with read permission then check create operation
diff --git a/tests/acceptance/features/coreApiShareManagementBasicToShares/createShareToSharesFolder.feature b/tests/acceptance/features/coreApiShareManagementBasicToShares/createShareToSharesFolder.feature
index c2408a0ffaa..c7457e9ae04 100644
--- a/tests/acceptance/features/coreApiShareManagementBasicToShares/createShareToSharesFolder.feature
+++ b/tests/acceptance/features/coreApiShareManagementBasicToShares/createShareToSharesFolder.feature
@@ -466,7 +466,7 @@ Feature: sharing
       | 1               | 100             |
       | 2               | 200             |
 
-  @skipOnGraph
+  @skipOnReva
   Scenario Outline: share with a group and then add a user to that group
     Given using OCS API version "<ocs_api_version>"
     And these users have been created with default attributes and without skeleton files: