Repository: owncloud/ocis
Pull request #7887 (2023-12-05 17:12:05)

feat: Normal user list
<!--
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
Allow regular users to list other users.

People with account management permissions (such as admins) can list users as they have been until now. This PR won't affect them in any way.

Regular users (without account management permission) can list users with the following restrictions:
* Only "id", "displayName" and "mail" attributes will be shown per user.
* "$search" option MUST be present in the query, with at least 3 characters
* "$filter", "$apply", "$expand" and "$compute" options aren't allowed

## Related Issue
https://github.com/owncloud/ocis/issues/7782

## Motivation and Context
<!--- Why is this change required? What problem does it solve? -->

## How Has This Been Tested?
Manually tested.
Unit tests have been adjusted because some of them where failing due to the user not having enough permissions

## 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)
- [x] 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/ -->
- [x] Code changes
- [ ] Unit tests added
- [ ] Acceptance tests added
- [ ] Documentation ticket raised: <link> 


update-docs[bot]: Thanks for opening this pull request! The maintainers of this repository would appreciate it if you would create a [changelog](https://github.com/owncloud/ocis/blob/master/changelog/README.md) item based on your changes.

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=7887)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7887&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=7887&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7887&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=7887&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=7887&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7887&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=7887&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=7887&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7887&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=7887&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=7887&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7887&resolved=false&types=CODE_SMELL)

[![46.2%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/40-16px.png '46.2%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7887&metric=new_coverage&view=list) [46.2% Coverage](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7887&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=7887&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7887&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=7887)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7887&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=7887&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7887&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=7887&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=7887&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7887&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=7887&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=7887&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7887&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=7887&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=7887&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7887&resolved=false&types=CODE_SMELL)

[![46.2%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/40-16px.png '46.2%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7887&metric=new_coverage&view=list) [46.2% Coverage](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7887&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=7887&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7887&metric=new_duplicated_lines_density&view=list)


rhafer: > Could you please add a test to validate that only the minimal set of id,displayName and mail is present if the regular user is requesting the users endpoint ?

@JanAckermann done. Please re-review
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=7887)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7887&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=7887&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7887&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=7887&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=7887&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7887&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=7887&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=7887&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7887&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=7887&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=7887&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7887&resolved=false&types=CODE_SMELL)

[![80.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/60-16px.png '80.0%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7887&metric=new_coverage&view=list) [80.0% Coverage](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7887&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=7887&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7887&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=7887)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7887&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=7887&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7887&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=7887&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=7887&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7887&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=7887&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=7887&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7887&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=7887&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=7887&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7887&resolved=false&types=CODE_SMELL)

[![80.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/60-16px.png '80.0%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7887&metric=new_coverage&view=list) [80.0% Coverage](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7887&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=7887&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7887&metric=new_duplicated_lines_density&view=list)


micbar: Some getUser tests are failing.
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=7887)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7887&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=7887&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7887&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=7887&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=7887&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7887&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=7887&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=7887&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7887&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=7887&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=7887&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7887&resolved=false&types=CODE_SMELL)

[![80.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/60-16px.png '80.0%')](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7887&metric=new_coverage&view=list) [80.0% Coverage](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7887&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=7887&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=7887&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=7887)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7887&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=7887&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7887&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=7887&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=7887&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7887&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=7887&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=7887&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=7887&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=7887&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=7887&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=7887&resolved=false&types=CODE_SMELL)

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



Patch:
diff --git a/.drone.star b/.drone.star
index eaa0834c524..7a33c245e5f 100644
--- a/.drone.star
+++ b/.drone.star
@@ -18,7 +18,7 @@ OC_CI_PHP = "owncloudci/php:%s"
 OC_CI_WAIT_FOR = "owncloudci/wait-for:latest"
 OC_CS3_API_VALIDATOR = "owncloud/cs3api-validator:0.2.0"
 OC_LITMUS = "owncloudci/litmus:latest"
-OC_OC_TEST_MIDDLEWARE = "owncloud/owncloud-test-middleware:1.8.7"
+OC_OC_TEST_MIDDLEWARE = "owncloud/owncloud-test-middleware:1.8.8"
 OC_UBUNTU = "owncloud/ubuntu:20.04"
 PLUGINS_CODACY = "plugins/codacy:1"
 PLUGINS_DOCKER = "plugins/docker:latest"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 777063f1e8d..346acad5e04 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -37,6 +37,7 @@ The following sections list the changes for unreleased.
 ## Summary
 
 * Enhancement - Add validation to public share provider: [#7877](https://github.com/owncloud/ocis/pull/7877)
+* Enhancement - Store and index metadata: [#7886](https://github.com/owncloud/ocis/pull/7886)
 
 ## Details
 
@@ -49,6 +50,13 @@ The following sections list the changes for unreleased.
    https://github.com/owncloud/ocis/issues/6993
    https://github.com/owncloud/ocis/pull/7877
 
+* Enhancement - Store and index metadata: [#7886](https://github.com/owncloud/ocis/pull/7886)
+
+   Location metadata is now extracted and stored by the search service. It is
+   available for driveItems in a folder listing using the Graph API.
+
+   https://github.com/owncloud/ocis/pull/7886
+
 # Changelog for [5.0.0-alpha.5] (2023-12-05)
 
 The following sections list the changes for 5.0.0-alpha.5.
diff --git a/changelog/unreleased/enhancement-allow-listing-regular-users.md b/changelog/unreleased/enhancement-allow-listing-regular-users.md
new file mode 100644
index 00000000000..b8a2d3b3572
--- /dev/null
+++ b/changelog/unreleased/enhancement-allow-listing-regular-users.md
@@ -0,0 +1,14 @@
+Enhancement: Allow regular users to list other users
+
+Regular users can search for other users and groups. The following limitations
+apply:
+
+* Only search queries are allowed (using the `$search=term` query parameter)
+* The search term needs to have at least 3 characters
+* for user searches the result set only contains the attributes `displayName`,
+  `userType`, `mail` and `id`
+* for group searches the result set only contains the attributes `displayName`,
+  `groupTypes` and `id`
+
+https://github.com/owncloud/ocis/pull/7887
+https://github.com/owncloud/ocis/issues/7782
diff --git a/changelog/unreleased/enhancement-store-and-index-location-metadata.md b/changelog/unreleased/enhancement-store-and-index-location-metadata.md
new file mode 100644
index 00000000000..ffc804b55d4
--- /dev/null
+++ b/changelog/unreleased/enhancement-store-and-index-location-metadata.md
@@ -0,0 +1,6 @@
+Enhancement: Store and index metadata
+
+location metadata is now extracted and stored by the search service.
+It is available for driveItems in a folder listing using the Graph API.
+
+https://github.com/owncloud/ocis/pull/7886
diff --git a/go.mod b/go.mod
index 2eefbc5e797..d090772df8e 100644
--- a/go.mod
+++ b/go.mod
@@ -13,7 +13,7 @@ require (
 	github.com/coreos/go-oidc v2.2.1+incompatible
 	github.com/coreos/go-oidc/v3 v3.8.0
 	github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781
-	github.com/cs3org/reva/v2 v2.16.1-0.20231206110211-7198abf507f6
+	github.com/cs3org/reva/v2 v2.16.1-0.20231206142634-7b47abdafd55
 	github.com/dhowden/tag v0.0.0-20230630033851-978a0926ee25
 	github.com/disintegration/imaging v1.6.2
 	github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e
diff --git a/go.sum b/go.sum
index e1b5d013c16..d1585e3e7b2 100644
--- a/go.sum
+++ b/go.sum
@@ -1019,6 +1019,8 @@ github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781 h1:BUdwkIlf8IS2F
 github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY=
 github.com/cs3org/reva/v2 v2.16.1-0.20231206110211-7198abf507f6 h1:wR1XlTT8ilWd5Yd943yayvRPyz1GBaAt7vZ9SOxHzqI=
 github.com/cs3org/reva/v2 v2.16.1-0.20231206110211-7198abf507f6/go.mod h1:zcrrYVsBv/DwhpyO2/W5hoSZ/k6az6Z2EYQok65uqZY=
+github.com/cs3org/reva/v2 v2.16.1-0.20231206142634-7b47abdafd55 h1:89YKeYd7nFa1AassJRvA8KOCpFN/4mfaiSxytUnG/AI=
+github.com/cs3org/reva/v2 v2.16.1-0.20231206142634-7b47abdafd55/go.mod h1:zcrrYVsBv/DwhpyO2/W5hoSZ/k6az6Z2EYQok65uqZY=
 github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
diff --git a/protogen/gen/ocis/messages/search/v0/search.pb.go b/protogen/gen/ocis/messages/search/v0/search.pb.go
index 267fcf0e65f..0349d3e1772 100644
--- a/protogen/gen/ocis/messages/search/v0/search.pb.go
+++ b/protogen/gen/ocis/messages/search/v0/search.pb.go
@@ -306,6 +306,69 @@ func (x *Audio) GetYear() int32 {
 	return 0
 }
 
+type GeoCoordinates struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Altitude  *float64 `protobuf:"fixed64,1,opt,name=altitude,proto3,oneof" json:"altitude,omitempty"`
+	Latitude  *float64 `protobuf:"fixed64,2,opt,name=latitude,proto3,oneof" json:"latitude,omitempty"`
+	Longitude *float64 `protobuf:"fixed64,3,opt,name=longitude,proto3,oneof" json:"longitude,omitempty"`
+}
+
+func (x *GeoCoordinates) Reset() {
+	*x = GeoCoordinates{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_ocis_messages_search_v0_search_proto_msgTypes[3]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *GeoCoordinates) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GeoCoordinates) ProtoMessage() {}
+
+func (x *GeoCoordinates) ProtoReflect() protoreflect.Message {
+	mi := &file_ocis_messages_search_v0_search_proto_msgTypes[3]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use GeoCoordinates.ProtoReflect.Descriptor instead.
+func (*GeoCoordinates) Descriptor() ([]byte, []int) {
+	return file_ocis_messages_search_v0_search_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *GeoCoordinates) GetAltitude() float64 {
+	if x != nil && x.Altitude != nil {
+		return *x.Altitude
+	}
+	return 0
+}
+
+func (x *GeoCoordinates) GetLatitude() float64 {
+	if x != nil && x.Latitude != nil {
+		return *x.Latitude
+	}
+	return 0
+}
+
+func (x *GeoCoordinates) GetLongitude() float64 {
+	if x != nil && x.Longitude != nil {
+		return *x.Longitude
+	}
+	return 0
+}
+
 type Entity struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -326,12 +389,13 @@ type Entity struct {
 	Tags             []string               `protobuf:"bytes,13,rep,name=tags,proto3" json:"tags,omitempty"`
 	Highlights       string                 `protobuf:"bytes,14,opt,name=highlights,proto3" json:"highlights,omitempty"`
 	Audio            *Audio                 `protobuf:"bytes,15,opt,name=audio,proto3" json:"audio,omitempty"`
+	Location         *GeoCoordinates        `protobuf:"bytes,16,opt,name=location,proto3" json:"location,omitempty"`
 }
 
 func (x *Entity) Reset() {
 	*x = Entity{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_ocis_messages_search_v0_search_proto_msgTypes[3]
+		mi := &file_ocis_messages_search_v0_search_proto_msgTypes[4]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -344,7 +408,7 @@ func (x *Entity) String() string {
 func (*Entity) ProtoMessage() {}
 
 func (x *Entity) ProtoReflect() protoreflect.Message {
-	mi := &file_ocis_messages_search_v0_search_proto_msgTypes[3]
+	mi := &file_ocis_messages_search_v0_search_proto_msgTypes[4]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -357,7 +421,7 @@ func (x *Entity) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use Entity.ProtoReflect.Descriptor instead.
 func (*Entity) Descriptor() ([]byte, []int) {
-	return file_ocis_messages_search_v0_search_proto_rawDescGZIP(), []int{3}
+	return file_ocis_messages_search_v0_search_proto_rawDescGZIP(), []int{4}
 }
 
 func (x *Entity) GetRef() *Reference {
@@ -465,6 +529,13 @@ func (x *Entity) GetAudio() *Audio {
 	return nil
 }
 
+func (x *Entity) GetLocation() *GeoCoordinates {
+	if x != nil {
+		return x.Location
+	}
+	return nil
+}
+
 type Match struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -479,7 +550,7 @@ type Match struct {
 func (x *Match) Reset() {
 	*x = Match{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_ocis_messages_search_v0_search_proto_msgTypes[4]
+		mi := &file_ocis_messages_search_v0_search_proto_msgTypes[5]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -492,7 +563,7 @@ func (x *Match) String() string {
 func (*Match) ProtoMessage() {}
 
 func (x *Match) ProtoReflect() protoreflect.Message {
-	mi := &file_ocis_messages_search_v0_search_proto_msgTypes[4]
+	mi := &file_ocis_messages_search_v0_search_proto_msgTypes[5]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -505,7 +576,7 @@ func (x *Match) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use Match.ProtoReflect.Descriptor instead.
 func (*Match) Descriptor() ([]byte, []int) {
-	return file_ocis_messages_search_v0_search_proto_rawDescGZIP(), []int{4}
+	return file_ocis_messages_search_v0_search_proto_rawDescGZIP(), []int{5}
 }
 
 func (x *Match) GetEntity() *Entity {
@@ -588,7 +659,17 @@ var file_ocis_messages_search_v0_search_proto_rawDesc = []byte{
 	0x5f, 0x69, 0x73, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x42, 0x69, 0x74, 0x72, 0x61,
 	0x74, 0x65, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x42, 0x08, 0x0a, 0x06,
 	0x5f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x6b,
-	0x43, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x79, 0x65, 0x61, 0x72, 0x22, 0xb8,
+	0x43, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x79, 0x65, 0x61, 0x72, 0x22, 0x9d,
+	0x01, 0x0a, 0x0e, 0x47, 0x65, 0x6f, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65,
+	0x73, 0x12, 0x1f, 0x0a, 0x08, 0x61, 0x6c, 0x74, 0x69, 0x74, 0x75, 0x64, 0x65, 0x18, 0x01, 0x20,
+	0x01, 0x28, 0x01, 0x48, 0x00, 0x52, 0x08, 0x61, 0x6c, 0x74, 0x69, 0x74, 0x75, 0x64, 0x65, 0x88,
+	0x01, 0x01, 0x12, 0x1f, 0x0a, 0x08, 0x6c, 0x61, 0x74, 0x69, 0x74, 0x75, 0x64, 0x65, 0x18, 0x02,
+	0x20, 0x01, 0x28, 0x01, 0x48, 0x01, 0x52, 0x08, 0x6c, 0x61, 0x74, 0x69, 0x74, 0x75, 0x64, 0x65,
+	0x88, 0x01, 0x01, 0x12, 0x21, 0x0a, 0x09, 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74, 0x75, 0x64, 0x65,
+	0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x48, 0x02, 0x52, 0x09, 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74,
+	0x75, 0x64, 0x65, 0x88, 0x01, 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x61, 0x6c, 0x74, 0x69, 0x74,
+	0x75, 0x64, 0x65, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x6c, 0x61, 0x74, 0x69, 0x74, 0x75, 0x64, 0x65,
+	0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74, 0x75, 0x64, 0x65, 0x22, 0xfd,
 	0x04, 0x0a, 0x06, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x34, 0x0a, 0x03, 0x72, 0x65, 0x66,
 	0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x6d, 0x65,
 	0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30,
@@ -624,17 +705,22 @@ var file_ocis_messages_search_v0_search_proto_rawDesc = []byte{
 	0x68, 0x74, 0x73, 0x12, 0x34, 0x0a, 0x05, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x18, 0x0f, 0x20, 0x01,
 	0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67,
 	0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x41, 0x75, 0x64,
-	0x69, 0x6f, 0x52, 0x05, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x22, 0x56, 0x0a, 0x05, 0x4d, 0x61, 0x74,
-	0x63, 0x68, 0x12, 0x37, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01,
-	0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67,
-	0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x45, 0x6e, 0x74,
-	0x69, 0x74, 0x79, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x73,
-	0x63, 0x6f, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x02, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x72,
-	0x65, 0x42, 0x42, 0x5a, 0x40, 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, 0x76, 0x32,
-	0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x6f, 0x63,
-	0x69, 0x73, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2f, 0x73, 0x65, 0x61, 0x72,
-	0x63, 0x68, 0x2f, 0x76, 0x30, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x69, 0x6f, 0x52, 0x05, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x12, 0x43, 0x0a, 0x08, 0x6c, 0x6f, 0x63,
+	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6f, 0x63,
+	0x69, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72,
+	0x63, 0x68, 0x2e, 0x76, 0x30, 0x2e, 0x47, 0x65, 0x6f, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e,
+	0x61, 0x74, 0x65, 0x73, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x56,
+	0x0a, 0x05, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x37, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74,
+	0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x6d,
+	0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x76,
+	0x30, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79,
+	0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x02, 0x52,
+	0x05, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x42, 0x42, 0x5a, 0x40, 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, 0x76, 0x32, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x67, 0x65, 0x6e, 0x2f, 0x67,
+	0x65, 0x6e, 0x2f, 0x6f, 0x63, 0x69, 0x73, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73,
+	0x2f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2f, 0x76, 0x30, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
+	0x6f, 0x33,
 }
 
 var (
@@ -649,28 +735,30 @@ func file_ocis_messages_search_v0_search_proto_rawDescGZIP() []byte {
 	return file_ocis_messages_search_v0_search_proto_rawDescData
 }
 
-var file_ocis_messages_search_v0_search_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
+var file_ocis_messages_search_v0_search_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
 var file_ocis_messages_search_v0_search_proto_goTypes = []interface{}{
 	(*ResourceID)(nil),            // 0: ocis.messages.search.v0.ResourceID
 	(*Reference)(nil),             // 1: ocis.messages.search.v0.Reference
 	(*Audio)(nil),                 // 2: ocis.messages.search.v0.Audio
-	(*Entity)(nil),                // 3: ocis.messages.search.v0.Entity
-	(*Match)(nil),                 // 4: ocis.messages.search.v0.Match
-	(*timestamppb.Timestamp)(nil), // 5: google.protobuf.Timestamp
+	(*GeoCoordinates)(nil),        // 3: ocis.messages.search.v0.GeoCoordinates
+	(*Entity)(nil),                // 4: ocis.messages.search.v0.Entity
+	(*Match)(nil),                 // 5: ocis.messages.search.v0.Match
+	(*timestamppb.Timestamp)(nil), // 6: google.protobuf.Timestamp
 }
 var file_ocis_messages_search_v0_search_proto_depIdxs = []int32{
 	0, // 0: ocis.messages.search.v0.Reference.resource_id:type_name -> ocis.messages.search.v0.ResourceID
 	1, // 1: ocis.messages.search.v0.Entity.ref:type_name -> ocis.messages.search.v0.Reference
 	0, // 2: ocis.messages.search.v0.Entity.id:type_name -> ocis.messages.search.v0.ResourceID
-	5, // 3: ocis.messages.search.v0.Entity.last_modified_time:type_name -> google.protobuf.Timestamp
+	6, // 3: ocis.messages.search.v0.Entity.last_modified_time:type_name -> google.protobuf.Timestamp
 	0, // 4: ocis.messages.search.v0.Entity.parent_id:type_name -> ocis.messages.search.v0.ResourceID
 	2, // 5: ocis.messages.search.v0.Entity.audio:type_name -> ocis.messages.search.v0.Audio
-	3, // 6: ocis.messages.search.v0.Match.entity:type_name -> ocis.messages.search.v0.Entity
-	7, // [7:7] is the sub-list for method output_type
-	7, // [7:7] is the sub-list for method input_type
-	7, // [7:7] is the sub-list for extension type_name
-	7, // [7:7] is the sub-list for extension extendee
-	0, // [0:7] is the sub-list for field type_name
+	3, // 6: ocis.messages.search.v0.Entity.location:type_name -> ocis.messages.search.v0.GeoCoordinates
+	4, // 7: ocis.messages.search.v0.Match.entity:type_name -> ocis.messages.search.v0.Entity
+	8, // [8:8] is the sub-list for method output_type
+	8, // [8:8] is the sub-list for method input_type
+	8, // [8:8] is the sub-list for extension type_name
+	8, // [8:8] is the sub-list for extension extendee
+	0, // [0:8] is the sub-list for field type_name
 }
 
 func init() { file_ocis_messages_search_v0_search_proto_init() }
@@ -716,7 +804,7 @@ func file_ocis_messages_search_v0_search_proto_init() {
 			}
 		}
 		file_ocis_messages_search_v0_search_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*Entity); i {
+			switch v := v.(*GeoCoordinates); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -728,6 +816,18 @@ func file_ocis_messages_search_v0_search_proto_init() {
 			}
 		}
 		file_ocis_messages_search_v0_search_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Entity); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_ocis_messages_search_v0_search_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*Match); i {
 			case 0:
 				return &v.state
@@ -741,13 +841,14 @@ func file_ocis_messages_search_v0_search_proto_init() {
 		}
 	}
 	file_ocis_messages_search_v0_search_proto_msgTypes[2].OneofWrappers = []interface{}{}
+	file_ocis_messages_search_v0_search_proto_msgTypes[3].OneofWrappers = []interface{}{}
 	type x struct{}
 	out := protoimpl.TypeBuilder{
 		File: protoimpl.DescBuilder{
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_ocis_messages_search_v0_search_proto_rawDesc,
 			NumEnums:      0,
-			NumMessages:   5,
+			NumMessages:   6,
 			NumExtensions: 0,
 			NumServices:   0,
 		},
diff --git a/protogen/gen/ocis/messages/search/v0/search.pb.web.go b/protogen/gen/ocis/messages/search/v0/search.pb.web.go
index 1ed7a0bf377..cbcd502c50f 100644
--- a/protogen/gen/ocis/messages/search/v0/search.pb.web.go
+++ b/protogen/gen/ocis/messages/search/v0/search.pb.web.go
@@ -118,6 +118,42 @@ func (m *Audio) UnmarshalJSON(b []byte) error {
 
 var _ json.Unmarshaler = (*Audio)(nil)
 
+// GeoCoordinatesJSONMarshaler describes the default jsonpb.Marshaler used by all
+// instances of GeoCoordinates. This struct is safe to replace or modify but
+// should not be done so concurrently.
+var GeoCoordinatesJSONMarshaler = new(jsonpb.Marshaler)
+
+// MarshalJSON satisfies the encoding/json Marshaler interface. This method
+// uses the more correct jsonpb package to correctly marshal the message.
+func (m *GeoCoordinates) MarshalJSON() ([]byte, error) {
+	if m == nil {
+		return json.Marshal(nil)
+	}
+
+	buf := &bytes.Buffer{}
+
+	if err := GeoCoordinatesJSONMarshaler.Marshal(buf, m); err != nil {
+		return nil, err
+	}
+
+	return buf.Bytes(), nil
+}
+
+var _ json.Marshaler = (*GeoCoordinates)(nil)
+
+// GeoCoordinatesJSONUnmarshaler describes the default jsonpb.Unmarshaler used by all
+// instances of GeoCoordinates. This struct is safe to replace or modify but
+// should not be done so concurrently.
+var GeoCoordinatesJSONUnmarshaler = new(jsonpb.Unmarshaler)
+
+// UnmarshalJSON satisfies the encoding/json Unmarshaler interface. This method
+// uses the more correct jsonpb package to correctly unmarshal the message.
+func (m *GeoCoordinates) UnmarshalJSON(b []byte) error {
+	return GeoCoordinatesJSONUnmarshaler.Unmarshal(bytes.NewReader(b), m)
+}
+
+var _ json.Unmarshaler = (*GeoCoordinates)(nil)
+
 // EntityJSONMarshaler describes the default jsonpb.Marshaler used by all
 // instances of Entity. This struct is safe to replace or modify but
 // should not be done so concurrently.
diff --git a/protogen/gen/ocis/messages/settings/v0/settings.pb.go b/protogen/gen/ocis/messages/settings/v0/settings.pb.go
index 9f0f3d92c64..bae3a9c8710 100644
--- a/protogen/gen/ocis/messages/settings/v0/settings.pb.go
+++ b/protogen/gen/ocis/messages/settings/v0/settings.pb.go
@@ -489,13 +489,13 @@ type Bundle struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Id          string      `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`                                                 // @gotags: yaml:"id"
-	Name        string      `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`                                             // @gotags: yaml:"name"
-	Type        Bundle_Type `protobuf:"varint,3,opt,name=type,proto3,enum=ocis.messages.settings.v0.Bundle_Type" json:"type,omitempty"` // @gotags: yaml:"type"
-	Extension   string      `protobuf:"bytes,4,opt,name=extension,proto3" json:"extension,omitempty"`                                   // @gotags: yaml:"extension"
-	DisplayName string      `protobuf:"bytes,5,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"`            // @gotags: yaml:"display_name"
-	Settings    []*Setting  `protobuf:"bytes,6,rep,name=settings,proto3" json:"settings,omitempty"`                                     // @gotags: yaml:"settings"
-	Resource    *Resource   `protobuf:"bytes,7,opt,name=resource,proto3" json:"resource,omitempty"`                                     // @gotags: yaml:"resource"
+	Id          string      `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" yaml:"id"`                                                 // @gotags: yaml:"id"
+	Name        string      `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty" yaml:"name"`                                             // @gotags: yaml:"name"
+	Type        Bundle_Type `protobuf:"varint,3,opt,name=type,proto3,enum=ocis.messages.settings.v0.Bundle_Type" json:"type,omitempty" yaml:"type"` // @gotags: yaml:"type"
+	Extension   string      `protobuf:"bytes,4,opt,name=extension,proto3" json:"extension,omitempty" yaml:"extension"`                                   // @gotags: yaml:"extension"
+	DisplayName string      `protobuf:"bytes,5,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty" yaml:"display_name"`            // @gotags: yaml:"display_name"
+	Settings    []*Setting  `protobuf:"bytes,6,rep,name=settings,proto3" json:"settings,omitempty" yaml:"settings"`                                     // @gotags: yaml:"settings"
+	Resource    *Resource   `protobuf:"bytes,7,opt,name=resource,proto3" json:"resource,omitempty" yaml:"resource"`                                     // @gotags: yaml:"resource"
 }
 
 func (x *Bundle) Reset() {
@@ -584,10 +584,10 @@ type Setting struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Id          string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`                                      // @gotags: yaml:"id"
-	Name        string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`                                  // @gotags: yaml:"name"
-	DisplayName string `protobuf:"bytes,3,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"` // @gotags: yaml:"display_name"
-	Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty"`                    // @gotags: yaml:"description"
+	Id          string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" yaml:"id"`                                      // @gotags: yaml:"id"
+	Name        string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty" yaml:"name"`                                  // @gotags: yaml:"name"
+	DisplayName string `protobuf:"bytes,3,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty" yaml:"display_name"` // @gotags: yaml:"display_name"
+	Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty" yaml:"description"`                    // @gotags: yaml:"description"
 	// Types that are assignable to Value:
 	//
 	//	*Setting_IntValue
@@ -597,7 +597,7 @@ type Setting struct {
 	//	*Setting_MultiChoiceValue
 	//	*Setting_PermissionValue
 	Value    isSetting_Value `protobuf_oneof:"value"`
-	Resource *Resource       `protobuf:"bytes,11,opt,name=resource,proto3" json:"resource,omitempty"` // @gotags: yaml:"resource"
+	Resource *Resource       `protobuf:"bytes,11,opt,name=resource,proto3" json:"resource,omitempty" yaml:"resource"` // @gotags: yaml:"resource"
 }
 
 func (x *Setting) Reset() {
@@ -721,27 +721,27 @@ type isSetting_Value interface {
 }
 
 type Setting_IntValue struct {
-	IntValue *Int `protobuf:"bytes,5,opt,name=int_value,json=intValue,proto3,oneof"` // @gotags: yaml:"int_value"
+	IntValue *Int `protobuf:"bytes,5,opt,name=int_value,json=intValue,proto3,oneof" yaml:"int_value"` // @gotags: yaml:"int_value"
 }
 
 type Setting_StringValue struct {
-	StringValue *String `protobuf:"bytes,6,opt,name=string_value,json=stringValue,proto3,oneof"` // @gotags: yaml:"string_value"
+	StringValue *String `protobuf:"bytes,6,opt,name=string_value,json=stringValue,proto3,oneof" yaml:"string_value"` // @gotags: yaml:"string_value"
 }
 
 type Setting_BoolValue struct {
-	BoolValue *Bool `protobuf:"bytes,7,opt,name=bool_value,json=boolValue,proto3,oneof"` // @gotags: yaml:"bool_value"
+	BoolValue *Bool `protobuf:"bytes,7,opt,name=bool_value,json=boolValue,proto3,oneof" yaml:"bool_value"` // @gotags: yaml:"bool_value"
 }
 
 type Setting_SingleChoiceValue struct {
-	SingleChoiceValue *SingleChoiceList `protobuf:"bytes,8,opt,name=single_choice_value,json=singleChoiceValue,proto3,oneof"` // @gotags: yaml:"single_choice_value"
+	SingleChoiceValue *SingleChoiceList `protobuf:"bytes,8,opt,name=single_choice_value,json=singleChoiceValue,proto3,oneof" yaml:"single_choice_value"` // @gotags: yaml:"single_choice_value"
 }
 
 type Setting_MultiChoiceValue struct {
-	MultiChoiceValue *MultiChoiceList `protobuf:"bytes,9,opt,name=multi_choice_value,json=multiChoiceValue,proto3,oneof"` // @gotags: yaml:"multi_choice_value"
+	MultiChoiceValue *MultiChoiceList `protobuf:"bytes,9,opt,name=multi_choice_value,json=multiChoiceValue,proto3,oneof" yaml:"multi_choice_value"` // @gotags: yaml:"multi_choice_value"
 }
 
 type Setting_PermissionValue struct {
-	PermissionValue *Permission `protobuf:"bytes,10,opt,name=permission_value,json=permissionValue,proto3,oneof"` // @gotags: yaml:"permission_value"
+	PermissionValue *Permission `protobuf:"bytes,10,opt,name=permission_value,json=permissionValue,proto3,oneof" yaml:"permission_value"` // @gotags: yaml:"permission_value"
 }
 
 func (*Setting_IntValue) isSetting_Value() {}
@@ -761,11 +761,11 @@ type Int struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Default     int64  `protobuf:"varint,1,opt,name=default,proto3" json:"default,omitempty"`        // @gotags: yaml:"default"
-	Min         int64  `protobuf:"varint,2,opt,name=min,proto3" json:"min,omitempty"`                // @gotags: yaml:"min"
-	Max         int64  `protobuf:"varint,3,opt,name=max,proto3" json:"max,omitempty"`                // @gotags: yaml:"max"
-	Step        int64  `protobuf:"varint,4,opt,name=step,proto3" json:"step,omitempty"`              // @gotags: yaml:"step"
-	Placeholder string `protobuf:"bytes,5,opt,name=placeholder,proto3" json:"placeholder,omitempty"` // @gotags: yaml:"placeholder"
+	Default     int64  `protobuf:"varint,1,opt,name=default,proto3" json:"default,omitempty" yaml:"default"`        // @gotags: yaml:"default"
+	Min         int64  `protobuf:"varint,2,opt,name=min,proto3" json:"min,omitempty" yaml:"min"`                // @gotags: yaml:"min"
+	Max         int64  `protobuf:"varint,3,opt,name=max,proto3" json:"max,omitempty" yaml:"max"`                // @gotags: yaml:"max"
+	Step        int64  `protobuf:"varint,4,opt,name=step,proto3" json:"step,omitempty" yaml:"step"`              // @gotags: yaml:"step"
+	Placeholder string `protobuf:"bytes,5,opt,name=placeholder,proto3" json:"placeholder,omitempty" yaml:"placeholder"` // @gotags: yaml:"placeholder"
 }
 
 func (x *Int) Reset() {
@@ -840,11 +840,11 @@ type String struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Default     string `protobuf:"bytes,1,opt,name=default,proto3" json:"default,omitempty"`                       // @gotags: yaml:"default"
-	Required    bool   `protobuf:"varint,2,opt,name=required,proto3" json:"required,omitempty"`                    // @gotags: yaml:"required"
-	MinLength   int32  `protobuf:"varint,3,opt,name=min_length,json=minLength,proto3" json:"min_length,omitempty"` // @gotags: yaml:"min_length"
-	MaxLength   int32  `protobuf:"varint,4,opt,name=max_length,json=maxLength,proto3" json:"max_length,omitempty"` // @gotags: yaml:"max_length"
-	Placeholder string `protobuf:"bytes,5,opt,name=placeholder,proto3" json:"placeholder,omitempty"`               // @gotags: yaml:"placeholder"
+	Default     string `protobuf:"bytes,1,opt,name=default,proto3" json:"default,omitempty" yaml:"default"`                       // @gotags: yaml:"default"
+	Required    bool   `protobuf:"varint,2,opt,name=required,proto3" json:"required,omitempty" yaml:"required"`                    // @gotags: yaml:"required"
+	MinLength   int32  `protobuf:"varint,3,opt,name=min_length,json=minLength,proto3" json:"min_length,omitempty" yaml:"min_length"` // @gotags: yaml:"min_length"
+	MaxLength   int32  `protobuf:"varint,4,opt,name=max_length,json=maxLength,proto3" json:"max_length,omitempty" yaml:"max_length"` // @gotags: yaml:"max_length"
+	Placeholder string `protobuf:"bytes,5,opt,name=placeholder,proto3" json:"placeholder,omitempty" yaml:"placeholder"`               // @gotags: yaml:"placeholder"
 }
 
 func (x *String) Reset() {
@@ -919,8 +919,8 @@ type Bool struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Default bool   `protobuf:"varint,1,opt,name=default,proto3" json:"default,omitempty"` // @gotags: yaml:"default"
-	Label   string `protobuf:"bytes,2,opt,name=label,proto3" json:"label,omitempty"`      // @gotags: yaml:"label"
+	Default bool   `protobuf:"varint,1,opt,name=default,proto3" json:"default,omitempty" yaml:"default"` // @gotags: yaml:"default"
+	Label   string `protobuf:"bytes,2,opt,name=label,proto3" json:"label,omitempty" yaml:"label"`      // @gotags: yaml:"label"
 }
 
 func (x *Bool) Reset() {
@@ -974,7 +974,7 @@ type SingleChoiceList struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Options []*ListOption `protobuf:"bytes,1,rep,name=options,proto3" json:"options,omitempty"` // @gotags: yaml:"options"
+	Options []*ListOption `protobuf:"bytes,1,rep,name=options,proto3" json:"options,omitempty" yaml:"options"` // @gotags: yaml:"options"
 }
 
 func (x *SingleChoiceList) Reset() {
@@ -1021,7 +1021,7 @@ type MultiChoiceList struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Options []*ListOption `protobuf:"bytes,1,rep,name=options,proto3" json:"options,omitempty"` // @gotags: yaml:"options"
+	Options []*ListOption `protobuf:"bytes,1,rep,name=options,proto3" json:"options,omitempty" yaml:"options"` // @gotags: yaml:"options"
 }
 
 func (x *MultiChoiceList) Reset() {
@@ -1068,9 +1068,9 @@ type ListOption struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Value        *ListOptionValue `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"`                                   // @gotags: yaml:"value"
-	Default      bool             `protobuf:"varint,2,opt,name=default,proto3" json:"default,omitempty"`                              // @gotags: yaml:"default"
-	DisplayValue string           `protobuf:"bytes,3,opt,name=display_value,json=displayValue,proto3" json:"display_value,omitempty"` // @gotags: yaml:"display_value"
+	Value        *ListOptionValue `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty" yaml:"value"`                                   // @gotags: yaml:"value"
+	Default      bool             `protobuf:"varint,2,opt,name=default,proto3" json:"default,omitempty" yaml:"default"`                              // @gotags: yaml:"default"
+	DisplayValue string           `protobuf:"bytes,3,opt,name=display_value,json=displayValue,proto3" json:"display_value,omitempty" yaml:"display_value"` // @gotags: yaml:"display_value"
 }
 
 func (x *ListOption) Reset() {
@@ -1131,8 +1131,8 @@ type Permission struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Operation  Permission_Operation  `protobuf:"varint,1,opt,name=operation,proto3,enum=ocis.messages.settings.v0.Permission_Operation" json:"operation,omitempty"`    // @gotags: yaml:"operation"
-	Constraint Permission_Constraint `protobuf:"varint,2,opt,name=constraint,proto3,enum=ocis.messages.settings.v0.Permission_Constraint" json:"constraint,omitempty"` // @gotags: yaml:"constraint"
+	Operation  Permission_Operation  `protobuf:"varint,1,opt,name=operation,proto3,enum=ocis.messages.settings.v0.Permission_Operation" json:"operation,omitempty" yaml:"operation"`    // @gotags: yaml:"operation"
+	Constraint Permission_Constraint `protobuf:"varint,2,opt,name=constraint,proto3,enum=ocis.messages.settings.v0.Permission_Constraint" json:"constraint,omitempty" yaml:"constraint"` // @gotags: yaml:"constraint"
 }
 
 func (x *Permission) Reset() {
@@ -1187,12 +1187,12 @@ type Value struct {
 	unknownFields protoimpl.UnknownFields
 
 	// id is the id of the Value. It is generated on saving it.
-	Id       string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`                             // @gotags: yaml:"id"
-	BundleId string `protobuf:"bytes,2,opt,name=bundle_id,json=bundleId,proto3" json:"bundle_id,omitempty"` // @gotags: yaml:"bundle_id"
+	Id       string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" yaml:"id"`                             // @gotags: yaml:"id"
+	BundleId string `protobuf:"bytes,2,opt,name=bundle_id,json=bundleId,proto3" json:"bundle_id,omitempty" yaml:"bundle_id"` // @gotags: yaml:"bundle_id"
 	// setting_id is the id of the setting from within its bundle.
-	SettingId   string    `protobuf:"bytes,3,opt,name=setting_id,json=settingId,proto3" json:"setting_id,omitempty"`       // @gotags: yaml:"setting_id"
-	AccountUuid string    `protobuf:"bytes,4,opt,name=account_uuid,json=accountUuid,proto3" json:"account_uuid,omitempty"` // @gotags: yaml:"account_uuid"
-	Resource    *Resource `protobuf:"bytes,5,opt,name=resource,proto3" json:"resource,omitempty"`                          // @gotags: yaml:"resource"
+	SettingId   string    `protobuf:"bytes,3,opt,name=setting_id,json=settingId,proto3" json:"setting_id,omitempty" yaml:"setting_id"`       // @gotags: yaml:"setting_id"
+	AccountUuid string    `protobuf:"bytes,4,opt,name=account_uuid,json=accountUuid,proto3" json:"account_uuid,omitempty" yaml:"account_uuid"` // @gotags: yaml:"account_uuid"
+	Resource    *Resource `protobuf:"bytes,5,opt,name=resource,proto3" json:"resource,omitempty" yaml:"resource"`                          // @gotags: yaml:"resource"
 	// Types that are assignable to Value:
 	//
 	//	*Value_BoolValue
@@ -1309,19 +1309,19 @@ type isValue_Value interface {
 }
 
 type Value_BoolValue struct {
-	BoolValue bool `protobuf:"varint,6,opt,name=bool_value,json=boolValue,proto3,oneof"` // @gotags: yaml:"bool_value"
+	BoolValue bool `protobuf:"varint,6,opt,name=bool_value,json=boolValue,proto3,oneof" yaml:"bool_value"` // @gotags: yaml:"bool_value"
 }
 
 type Value_IntValue struct {
-	IntValue int64 `protobuf:"varint,7,opt,name=int_value,json=intValue,proto3,oneof"` // @gotags: yaml:"int_value"
+	IntValue int64 `protobuf:"varint,7,opt,name=int_value,json=intValue,proto3,oneof" yaml:"int_value"` // @gotags: yaml:"int_value"
 }
 
 type Value_StringValue struct {
-	StringValue string `protobuf:"bytes,8,opt,name=string_value,json=stringValue,proto3,oneof"` // @gotags: yaml:"string_value"
+	StringValue string `protobuf:"bytes,8,opt,name=string_value,json=stringValue,proto3,oneof" yaml:"string_value"` // @gotags: yaml:"string_value"
 }
 
 type Value_ListValue struct {
-	ListValue *ListValue `protobuf:"bytes,9,opt,name=list_value,json=listValue,proto3,oneof"` // @gotags: yaml:"list_value"
+	ListValue *ListValue `protobuf:"bytes,9,opt,name=list_value,json=listValue,proto3,oneof" yaml:"list_value"` // @gotags: yaml:"list_value"
 }
 
 func (*Value_BoolValue) isValue_Value() {}
@@ -1337,7 +1337,7 @@ type ListValue struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Values []*ListOptionValue `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"` // @gotags: yaml:"values"
+	Values []*ListOptionValue `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty" yaml:"values"` // @gotags: yaml:"values"
 }
 
 func (x *ListValue) Reset() {
@@ -1449,11 +1449,11 @@ type isListOptionValue_Option interface {
 }
 
 type ListOptionValue_StringValue struct {
-	StringValue string `protobuf:"bytes,1,opt,name=string_value,json=stringValue,proto3,oneof"` // @gotags: yaml:"string_value"
+	StringValue string `protobuf:"bytes,1,opt,name=string_value,json=stringValue,proto3,oneof" yaml:"string_value"` // @gotags: yaml:"string_value"
 }
 
 type ListOptionValue_IntValue struct {
-	IntValue int64 `protobuf:"varint,2,opt,name=int_value,json=intValue,proto3,oneof"` // @gotags: yaml:"int_value"
+	IntValue int64 `protobuf:"varint,2,opt,name=int_value,json=intValue,proto3,oneof" yaml:"int_value"` // @gotags: yaml:"int_value"
 }
 
 func (*ListOptionValue_StringValue) isListOptionValue_Option() {}
diff --git a/protogen/gen/ocis/services/search/v0/search.swagger.json b/protogen/gen/ocis/services/search/v0/search.swagger.json
index 6fce96faa1f..d84ac28f330 100644
--- a/protogen/gen/ocis/services/search/v0/search.swagger.json
+++ b/protogen/gen/ocis/services/search/v0/search.swagger.json
@@ -270,6 +270,26 @@
         },
         "audio": {
           "$ref": "#/definitions/v0Audio"
+        },
+        "location": {
+          "$ref": "#/definitions/v0GeoCoordinates"
+        }
+      }
+    },
+    "v0GeoCoordinates": {
+      "type": "object",
+      "properties": {
+        "altitude": {
+          "type": "number",
+          "format": "double"
+        },
+        "latitude": {
+          "type": "number",
+          "format": "double"
+        },
+        "longitude": {
+          "type": "number",
+          "format": "double"
         }
       }
     },
diff --git a/protogen/gen/ocis/services/thumbnails/v0/thumbnails.pb.go b/protogen/gen/ocis/services/thumbnails/v0/thumbnails.pb.go
index 1b0aa92c6d3..5d03a85fad6 100644
--- a/protogen/gen/ocis/services/thumbnails/v0/thumbnails.pb.go
+++ b/protogen/gen/ocis/services/thumbnails/v0/thumbnails.pb.go
@@ -36,7 +36,7 @@ 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
+	// Indicates which image processor to use
 	Processor string `protobuf:"bytes,5,opt,name=processor,proto3" json:"processor,omitempty"`
 	// Types that are assignable to Source:
 	//
diff --git a/protogen/proto/ocis/messages/search/v0/search.proto b/protogen/proto/ocis/messages/search/v0/search.proto
index 034d7f85e4b..84b2b9c2504 100644
--- a/protogen/proto/ocis/messages/search/v0/search.proto
+++ b/protogen/proto/ocis/messages/search/v0/search.proto
@@ -36,6 +36,12 @@ message Audio {
 	optional int32 year = 16;
 }
 
+message GeoCoordinates {
+	optional double altitude  = 1;
+	optional double latitude = 2;
+	optional double longitude = 3;
+}
+
 message Entity {
 	Reference ref = 1;
 	ResourceID id = 2;
@@ -52,6 +58,7 @@ message Entity {
 	repeated string tags = 13;
 	string highlights = 14;
 	Audio audio = 15;
+	GeoCoordinates location = 16;
 }
 
 message Match {
diff --git a/services/graph/pkg/config/config.go b/services/graph/pkg/config/config.go
index ee18c1a2dc0..27a151bcf83 100644
--- a/services/graph/pkg/config/config.go
+++ b/services/graph/pkg/config/config.go
@@ -108,9 +108,10 @@ type Identity struct {
 
 // API represents API configuration parameters.
 type API struct {
-	GroupMembersPatchLimit int    `yaml:"group_members_patch_limit" env:"GRAPH_GROUP_MEMBERS_PATCH_LIMIT" desc:"The amount of group members allowed to be added with a single patch request."`
-	UsernameMatch          string `yaml:"graph_username_match" env:"GRAPH_USERNAME_MATCH" desc:"Apply restrictions to usernames. Supported values are 'default' and 'none'. When set to 'default', user names must not start with a number and are restricted to ASCII characters. When set to 'none', no restrictions are applied. The default value is 'default'."`
-	AssignDefaultUserRole  bool   `yaml:"graph_assign_default_user_role" env:"GRAPH_ASSIGN_DEFAULT_USER_ROLE" desc:"Whether to assign newly created users the default role 'User'. Set this to 'false' if you want to assign roles manually, or if the role assignment should happen at first login. Set this to 'true' (the default) to assign the role 'User' when creating a new user."`
+	GroupMembersPatchLimit  int    `yaml:"group_members_patch_limit" env:"GRAPH_GROUP_MEMBERS_PATCH_LIMIT" desc:"The amount of group members allowed to be added with a single patch request."`
+	UsernameMatch           string `yaml:"graph_username_match" env:"GRAPH_USERNAME_MATCH" desc:"Apply restrictions to usernames. Supported values are 'default' and 'none'. When set to 'default', user names must not start with a number and are restricted to ASCII characters. When set to 'none', no restrictions are applied. The default value is 'default'."`
+	AssignDefaultUserRole   bool   `yaml:"graph_assign_default_user_role" env:"GRAPH_ASSIGN_DEFAULT_USER_ROLE" desc:"Whether to assign newly created users the default role 'User'. Set this to 'false' if you want to assign roles manually, or if the role assignment should happen at first login. Set this to 'true' (the default) to assign the role 'User' when creating a new user."`
+	IdentitySearchMinLength int    `yaml:"graph_identity_search_min_length" env:"GRAPH_IDENTITY_SEARCH_MIN_LENGTH" desc:"The minimum length the search term needs to have for unprivileged users when searching for users or groups."`
 }
 
 // Events combines the configuration options for the event bus.
diff --git a/services/graph/pkg/config/defaults/defaultconfig.go b/services/graph/pkg/config/defaults/defaultconfig.go
index 730ce442d5e..3062e5ed9c1 100644
--- a/services/graph/pkg/config/defaults/defaultconfig.go
+++ b/services/graph/pkg/config/defaults/defaultconfig.go
@@ -44,9 +44,10 @@ func DefaultConfig() *config.Config {
 			DisplayName: "ownCloud Infinite Scale",
 		},
 		API: config.API{
-			GroupMembersPatchLimit: 20,
-			UsernameMatch:          "default",
-			AssignDefaultUserRole:  true,
+			GroupMembersPatchLimit:  20,
+			UsernameMatch:           "default",
+			AssignDefaultUserRole:   true,
+			IdentitySearchMinLength: 3,
 		},
 		Reva: shared.DefaultRevaConfig(),
 		Spaces: config.Spaces{
diff --git a/services/graph/pkg/identity/ldap.go b/services/graph/pkg/identity/ldap.go
index d2f11432a0b..4362560c526 100644
--- a/services/graph/pkg/identity/ldap.go
+++ b/services/graph/pkg/identity/ldap.go
@@ -582,7 +582,7 @@ func (i *LDAP) GetUsers(ctx context.Context, oreq *godata.GoDataRequest) ([]*lib
 	if search != "" {
 		search = ldap.EscapeFilter(search)
 		userFilter = fmt.Sprintf(
-			"(|(%s=%s*)(%s=%s*)(%s=%s*))",
+			"(|(%s=*%s*)(%s=*%s*)(%s=*%s*))",
 			i.userAttributeMap.userName, search,
 			i.userAttributeMap.mail, search,
 			i.userAttributeMap.displayName, search,
diff --git a/services/graph/pkg/identity/ldap_group.go b/services/graph/pkg/identity/ldap_group.go
index ab02f5327ef..c7c66f94c4a 100644
--- a/services/graph/pkg/identity/ldap_group.go
+++ b/services/graph/pkg/identity/ldap_group.go
@@ -75,7 +75,7 @@ func (i *LDAP) GetGroups(ctx context.Context, queryParam url.Values) ([]*libregr
 	if search != "" {
 		search = ldap.EscapeFilter(search)
 		groupFilter = fmt.Sprintf(
-			"(|(%s=%s*)(%s=%s*))",
+			"(|(%s=*%s*)(%s=*%s*))",
 			i.groupAttributeMap.name, search,
 			i.groupAttributeMap.id, search,
 		)
diff --git a/services/graph/pkg/service/v0/driveitems.go b/services/graph/pkg/service/v0/driveitems.go
index 78243d9802a..38e960204ea 100644
--- a/services/graph/pkg/service/v0/driveitems.go
+++ b/services/graph/pkg/service/v0/driveitems.go
@@ -870,7 +870,10 @@ func cs3ResourceToDriveItem(logger *log.Logger, res *storageprovider.ResourceInf
 		driveItem.Folder = &libregraph.Folder{}
 	}
 
-	driveItem.Audio = cs3ResourceToDriveItemAudioFacet(logger, res)
+	if res.ArbitraryMetadata != nil {
+		driveItem.Audio = cs3ResourceToDriveItemAudioFacet(logger, res)
+		driveItem.Location = cs3ResourceToDriveItemLocationFacet(logger, res)
+	}
 
 	return driveItem, nil
 }
@@ -893,6 +896,20 @@ func cs3ResourceToDriveItemAudioFacet(logger *log.Logger, res *storageprovider.R
 	return nil
 }
 
+func cs3ResourceToDriveItemLocationFacet(logger *log.Logger, res *storageprovider.ResourceInfo) *libregraph.GeoCoordinates {
+	k := res.ArbitraryMetadata.Metadata
+	if k == nil {
+		return nil
+	}
+
+	var location = &libregraph.GeoCoordinates{}
+	if ok := unmarshalStringMap(logger, location, k, "libre.graph.location."); ok {
+		return location
+	}
+
+	return nil
+}
+
 func getFieldName(structField reflect.StructField) string {
 	tag := structField.Tag.Get("json")
 	if tag == "" {
@@ -922,6 +939,10 @@ func unmarshalStringMap(logger *log.Logger, out any, flatMap map[string]string,
 					tmp, err = strconv.ParseInt(value, 10, 32)
 				case reflect.Int64:
 					tmp, err = strconv.ParseInt(value, 10, 64)
+				case reflect.Float32:
+					tmp, err = strconv.ParseFloat(value, 32)
+				case reflect.Float64:
+					tmp, err = strconv.ParseFloat(value, 64)
 				case reflect.Bool:
 					tmp, err = strconv.ParseBool(value)
 				default:
diff --git a/services/graph/pkg/service/v0/groups.go b/services/graph/pkg/service/v0/groups.go
index 434ebef4b1b..f80924025ac 100644
--- a/services/graph/pkg/service/v0/groups.go
+++ b/services/graph/pkg/service/v0/groups.go
@@ -31,6 +31,20 @@ func (g Graph) GetGroups(w http.ResponseWriter, r *http.Request) {
 		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
 		return
 	}
+	ctxHasFullPerms := g.contextUserHasFullAccountPerms(r.Context())
+	if !ctxHasFullPerms && (odataReq.Query == nil || odataReq.Query.Search == nil || len(odataReq.Query.Search.RawValue) < g.config.API.IdentitySearchMinLength) {
+		// for regular user the search term must have a minimum length
+		logger.Debug().Interface("query", r.URL.Query()).Msgf("search with less than %d chars for a regular user", g.config.API.IdentitySearchMinLength)
+		errorcode.AccessDenied.Render(w, r, http.StatusForbidden, "search term too short")
+		return
+	}
+
+	if !ctxHasFullPerms && (odataReq.Query.Filter != nil || odataReq.Query.Apply != nil || odataReq.Query.Expand != nil || odataReq.Query.Compute != nil) {
+		// regular users can't use filter, apply, expand and compute
+		logger.Debug().Interface("query", r.URL.Query()).Msg("forbidden query elements for a regular user")
+		errorcode.AccessDenied.Render(w, r, http.StatusForbidden, "query has forbidden elements for regular users")
+		return
+	}
 
 	groups, err := g.identityBackend.GetGroups(r.Context(), r.URL.Query())
 	if err != nil {
@@ -38,6 +52,18 @@ func (g Graph) GetGroups(w http.ResponseWriter, r *http.Request) {
 		errorcode.RenderError(w, r, err)
 		return
 	}
+	// If the user isn't admin, we'll show just the minimum group attibutes
+	if !ctxHasFullPerms {
+		finalGroups := make([]*libregraph.Group, len(groups))
+		for i, grp := range groups {
+			finalGroups[i] = &libregraph.Group{
+				Id:          grp.Id,
+				DisplayName: grp.DisplayName,
+				GroupTypes:  grp.GroupTypes,
+			}
+		}
+		groups = finalGroups
+	}
 
 	groups, err = sortGroups(odataReq, groups)
 	if err != nil {
diff --git a/services/graph/pkg/service/v0/service.go b/services/graph/pkg/service/v0/service.go
index f2566e30ddd..b552a2b0b76 100644
--- a/services/graph/pkg/service/v0/service.go
+++ b/services/graph/pkg/service/v0/service.go
@@ -247,7 +247,7 @@ func NewService(opts ...Option) (Graph, error) {
 				r.Post("/changePassword", svc.ChangeOwnPassword)
 			})
 			r.Route("/users", func(r chi.Router) {
-				r.With(requireAdmin).Get("/", svc.GetUsers)
+				r.Get("/", svc.GetUsers)
 				r.With(requireAdmin).Post("/", svc.PostUser)
 				r.Route("/{userID}", func(r chi.Router) {
 					r.Get("/", svc.GetUser)
@@ -265,7 +265,7 @@ func NewService(opts ...Option) (Graph, error) {
 				})
 			})
 			r.Route("/groups", func(r chi.Router) {
-				r.With(requireAdmin).Get("/", svc.GetGroups)
+				r.Get("/", svc.GetGroups)
 				r.With(requireAdmin).Post("/", svc.PostGroup)
 				r.Route("/{groupID}", func(r chi.Router) {
 					r.Get("/", svc.GetGroup)
diff --git a/services/graph/pkg/service/v0/users.go b/services/graph/pkg/service/v0/users.go
index 04e4a2c23e5..d048b7d9fb2 100644
--- a/services/graph/pkg/service/v0/users.go
+++ b/services/graph/pkg/service/v0/users.go
@@ -195,6 +195,21 @@ func (g Graph) GetUserDrive(w http.ResponseWriter, r *http.Request) {
 	}
 }
 
+func (g Graph) contextUserHasFullAccountPerms(reqctx context.Context) bool {
+	// mostly copied from the canCreateSpace method
+	pr, err := g.permissionsService.GetPermissionByID(reqctx, &settingssvc.GetPermissionByIDRequest{
+		PermissionId: defaults.AccountManagementPermission(0).Id,
+	})
+	if err != nil || pr.Permission == nil {
+		return false
+	}
+
+	if pr.Permission.Constraint != defaults.All {
+		return false
+	}
+	return true
+}
+
 // GetUsers implements the Service interface.
 func (g Graph) GetUsers(w http.ResponseWriter, r *http.Request) {
 	logger := g.logger.SubloggerWithRequestID(r.Context())
@@ -207,6 +222,21 @@ func (g Graph) GetUsers(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
+	ctxHasFullPerms := g.contextUserHasFullAccountPerms(r.Context())
+	if !ctxHasFullPerms && (odataReq.Query == nil || odataReq.Query.Search == nil || len(odataReq.Query.Search.RawValue) < g.config.API.IdentitySearchMinLength) {
+		// for regular user the search term must have a minimum length
+		logger.Debug().Interface("query", r.URL.Query()).Msgf("search with less than %d chars for a regular user", g.config.API.IdentitySearchMinLength)
+		errorcode.AccessDenied.Render(w, r, http.StatusForbidden, "search term too short")
+		return
+	}
+
+	if !ctxHasFullPerms && (odataReq.Query.Filter != nil || odataReq.Query.Apply != nil || odataReq.Query.Expand != nil || odataReq.Query.Compute != nil) {
+		// regular users can't use filter, apply, expand and compute
+		logger.Debug().Interface("query", r.URL.Query()).Msg("forbidden query elements for a regular user")
+		errorcode.AccessDenied.Render(w, r, http.StatusForbidden, "query has forbidden elements for regular users")
+		return
+	}
+
 	logger.Debug().Interface("query", r.URL.Query()).Msg("calling get users on backend")
 
 	var users []*libregraph.User
@@ -232,6 +262,20 @@ func (g Graph) GetUsers(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
+	// If the user isn't admin, we'll show just the minimum user attibutes
+	if !ctxHasFullPerms {
+		finalUsers := make([]*libregraph.User, len(users))
+		for i, u := range users {
+			finalUsers[i] = &libregraph.User{
+				Id:          u.Id,
+				DisplayName: u.DisplayName,
+				Mail:        u.Mail,
+				UserType:    u.UserType,
+			}
+		}
+		users = finalUsers
+	}
+
 	exp, err := identity.GetExpandValues(odataReq.Query)
 	if err != nil {
 		logger.Debug().Err(err).Interface("query", r.URL.Query()).Msg("could not get users: $expand error")
diff --git a/services/search/pkg/content/content.go b/services/search/pkg/content/content.go
index fccd8062896..ba61befaf40 100644
--- a/services/search/pkg/content/content.go
+++ b/services/search/pkg/content/content.go
@@ -21,7 +21,8 @@ type Document struct {
 	Mtime    string
 	MimeType string
 	Tags     []string
-	Audio    *libregraph.Audio `json:"audio,omitempty"`
+	Audio    *libregraph.Audio          `json:"audio,omitempty"`
+	Location *libregraph.GeoCoordinates `json:"location,omitempty"`
 }
 
 func CleanString(content, langCode string) string {
diff --git a/services/search/pkg/content/tika.go b/services/search/pkg/content/tika.go
index b169de8fd88..d4d0b53f447 100644
--- a/services/search/pkg/content/tika.go
+++ b/services/search/pkg/content/tika.go
@@ -89,72 +89,121 @@ func (t Tika) Extract(ctx context.Context, ri *provider.ResourceInfo) (Document,
 			doc.Content = strings.TrimSpace(fmt.Sprintf("%s %s", doc.Content, content))
 		}
 
+		doc.Location = t.getLocation(meta)
+
 		if contentType, err := getFirstValue(meta, "Content-Type"); err == nil && strings.HasPrefix(contentType, "audio/") {
-			audio := libregraph.Audio{}
+			doc.Audio = t.getAudio(meta)
+		}
+	}
+
+	if langCode, _ := t.tika.LanguageString(ctx, doc.Content); langCode != "" && t.CleanStopWords {
+		doc.Content = CleanString(doc.Content, langCode)
+	}
+
+	return doc, nil
+}
+
+func (t Tika) getLocation(meta map[string][]string) *libregraph.GeoCoordinates {
+	var location *libregraph.GeoCoordinates
+	initLocation := func() {
+		if location == nil {
+			location = libregraph.NewGeoCoordinates()
+		}
+	}
+
+	// TODO: location.Altitute: transform the following data to … feet above sea level.
+	// "GPS:GPS Altitude":                          []string{"227.4 metres"},
+	// "GPS:GPS Altitude Ref":                      []string{"Sea level"},
 
-			if v, err := getFirstValue(meta, "xmpDM:album"); err == nil {
-				audio.SetAlbum(v)
-			}
+	if v, err := getFirstValue(meta, "geo:lat"); err == nil {
+		if i, err := strconv.ParseFloat(v, 64); err == nil {
+			initLocation()
+			location.SetLatitude(i)
+		}
+	}
+
+	if v, err := getFirstValue(meta, "geo:long"); err == nil {
+		if i, err := strconv.ParseFloat(v, 64); err == nil {
+			initLocation()
+			location.SetLongitude(i)
+		}
+	}
 
-			if v, err := getFirstValue(meta, "xmpDM:albumArtist"); err == nil {
-				audio.SetAlbumArtist(v)
-			}
+	return location
+}
 
-			if v, err := getFirstValue(meta, "xmpDM:artist"); err == nil {
-				audio.SetArtist(v)
-			}
+func (t Tika) getAudio(meta map[string][]string) *libregraph.Audio {
+	var audio *libregraph.Audio
+	initAudio := func() {
+		if audio == nil {
+			audio = libregraph.NewAudio()
+		}
+	}
 
-			// TODO: audio.Bitrate: not provided by tika
-			// TODO: audio.Composers: not provided by tika
-			// TODO: audio.Copyright: not provided by tika for audio files?
+	if v, err := getFirstValue(meta, "xmpDM:album"); err == nil {
+		initAudio()
+		audio.SetAlbum(v)
+	}
 
-			if v, err := getFirstValue(meta, "xmpDM:discNumber"); err == nil {
-				if i, err := strconv.ParseInt(v, 10, 32); err == nil {
-					audio.SetDisc(int32(i))
-				}
+	if v, err := getFirstValue(meta, "xmpDM:albumArtist"); err == nil {
+		initAudio()
+		audio.SetAlbumArtist(v)
+	}
 
-			}
+	if v, err := getFirstValue(meta, "xmpDM:artist"); err == nil {
+		initAudio()
+		audio.SetArtist(v)
+	}
 
-			//  TODO: audio.DiscCount: not provided by tika
+	// TODO: audio.Bitrate: not provided by tika
+	// TODO: audio.Composers: not provided by tika
+	// TODO: audio.Copyright: not provided by tika for audio files?
 
-			if v, err := getFirstValue(meta, "xmpDM:duration"); err == nil {
-				if i, err := strconv.ParseInt(v, 10, 64); err == nil {
-					audio.SetDuration(i * 1000)
-				}
-			}
+	if v, err := getFirstValue(meta, "xmpDM:discNumber"); err == nil {
+		if i, err := strconv.ParseInt(v, 10, 32); err == nil {
+			initAudio()
+			audio.SetDisc(int32(i))
+		}
 
-			if v, err := getFirstValue(meta, "xmpDM:genre"); err == nil {
-				audio.SetGenre(v)
-			}
+	}
 
-			// TODO: audio.HasDrm: not provided by tika
-			// TODO: audio.IsVariableBitrate: not provided by tika
+	//  TODO: audio.DiscCount: not provided by tika
 
-			if v, err := getFirstValue(meta, "dc:title"); err == nil {
-				audio.SetTitle(v)
-			}
+	if v, err := getFirstValue(meta, "xmpDM:duration"); err == nil {
+		if i, err := strconv.ParseInt(v, 10, 64); err == nil {
+			initAudio()
+			audio.SetDuration(i * 1000)
+		}
+	}
 
-			if v, err := getFirstValue(meta, "xmpDM:trackNumber"); err == nil {
-				if i, err := strconv.ParseInt(v, 10, 32); err == nil {
-					audio.SetTrack(int32(i))
-				}
-			}
+	if v, err := getFirstValue(meta, "xmpDM:genre"); err == nil {
+		initAudio()
+		audio.SetGenre(v)
+	}
 
-			// TODO: audio.TrackCount: not provided by tika
+	// TODO: audio.HasDrm: not provided by tika
+	// TODO: audio.IsVariableBitrate: not provided by tika
 
-			if v, err := getFirstValue(meta, "xmpDM:releaseDate"); err == nil {
-				if i, err := strconv.ParseInt(v, 10, 32); err == nil {
-					audio.SetYear(int32(i))
-				}
-			}
+	if v, err := getFirstValue(meta, "dc:title"); err == nil {
+		initAudio()
+		audio.SetTitle(v)
+	}
 
-			doc.Audio = &audio
+	if v, err := getFirstValue(meta, "xmpDM:trackNumber"); err == nil {
+		if i, err := strconv.ParseInt(v, 10, 32); err == nil {
+			initAudio()
+			audio.SetTrack(int32(i))
 		}
 	}
 
-	if langCode, _ := t.tika.LanguageString(ctx, doc.Content); langCode != "" && t.CleanStopWords {
-		doc.Content = CleanString(doc.Content, langCode)
+	// TODO: audio.TrackCount: not provided by tika
+
+	if v, err := getFirstValue(meta, "xmpDM:releaseDate"); err == nil {
+		if i, err := strconv.ParseInt(v, 10, 32); err == nil {
+			initAudio()
+			audio.SetYear(int32(i))
+		}
 	}
 
-	return doc, nil
+	return audio
 }
diff --git a/services/search/pkg/engine/bleve.go b/services/search/pkg/engine/bleve.go
index 8e1b6efedbb..a0b3f26f30c 100644
--- a/services/search/pkg/engine/bleve.go
+++ b/services/search/pkg/engine/bleve.go
@@ -211,6 +211,7 @@ func (b *Bleve) Search(ctx context.Context, sir *searchService.SearchIndexReques
 				Tags:       getFieldSliceValue[string](hit.Fields, "Tags"),
 				Highlights: getFragmentValue(hit.Fragments, "Content", 0),
 				Audio:      getAudioValue[searchMessage.Audio](hit.Fields),
+				Location:   getLocationValue[searchMessage.GeoCoordinates](hit.Fields),
 			},
 		}
 
@@ -329,6 +330,7 @@ func (b *Bleve) getResource(id string) (*Resource, error) {
 			Content:  getFieldValue[string](fields, "Content"),
 			Tags:     getFieldSliceValue[string](fields, "Tags"),
 			Audio:    getAudioValue[libregraph.Audio](fields),
+			Location: getLocationValue[libregraph.GeoCoordinates](fields),
 		},
 	}, nil
 }
@@ -382,6 +384,15 @@ func getAudioValue[T any](fields map[string]interface{}) *T {
 	return nil
 }
 
+func getLocationValue[T any](fields map[string]interface{}) *T {
+	var location = newPointerOfType[T]()
+	if ok := unmarshalInterfaceMap(location, fields, "location."); ok {
+		return location
+	}
+
+	return nil
+}
+
 func (b *Bleve) updateEntity(id string, mutateFunc func(r *Resource)) (*Resource, error) {
 	it, err := b.getResource(id)
 	if err != nil {
diff --git a/services/search/pkg/search/service.go b/services/search/pkg/search/service.go
index 199b3df31e3..7561e880fe0 100644
--- a/services/search/pkg/search/service.go
+++ b/services/search/pkg/search/service.go
@@ -494,6 +494,7 @@ func (s *Service) UpsertItem(ref *provider.Reference, uID *user.UserId) {
 	// determine if metadata needs to be stored in storage as well
 	metadata := map[string]string{}
 	addAudioMetadata(metadata, doc.Audio)
+	addLocationMetadata(metadata, doc.Location)
 	if len(metadata) == 0 {
 		return
 	}
@@ -525,6 +526,13 @@ func addAudioMetadata(metadata map[string]string, audio *libregraph.Audio) {
 	marshalToStringMap(audio, metadata, "libre.graph.audio.")
 }
 
+func addLocationMetadata(metadata map[string]string, location *libregraph.GeoCoordinates) {
+	if location == nil {
+		return
+	}
+	marshalToStringMap(location, metadata, "libre.graph.location.")
+}
+
 func marshalToStringMap[T libregraph.MappedNullable](source T, target map[string]string, prefix string) {
 	// ToMap never returns a non-nil error ...
 	m, _ := source.ToMap()
@@ -549,6 +557,10 @@ func valueToString(value interface{}) string {
 		return strconv.FormatInt(int64(*v), 10)
 	case *int64:
 		return strconv.FormatInt(*v, 10)
+	case *float32:
+		return strconv.FormatFloat(float64(*v), 'f', -1, 32)
+	case *float64:
+		return strconv.FormatFloat(*v, 'f', -1, 64)
 	case *bool:
 		return strconv.FormatBool(*v)
 	default:
diff --git a/services/userlog/pkg/service/l10n/locale/ru/LC_MESSAGES/userlog.po b/services/userlog/pkg/service/l10n/locale/ru/LC_MESSAGES/userlog.po
index ddd4c405502..6a00d6e8f05 100644
--- a/services/userlog/pkg/service/l10n/locale/ru/LC_MESSAGES/userlog.po
+++ b/services/userlog/pkg/service/l10n/locale/ru/LC_MESSAGES/userlog.po
@@ -13,7 +13,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: EMAIL\n"
-"POT-Creation-Date: 2023-11-16 00:37+0000\n"
+"POT-Creation-Date: 2023-12-07 00:55+0000\n"
 "PO-Revision-Date: 2023-03-15 08:28+0000\n"
 "Last-Translator: Roman Perekhod, 2023\n"
 "Language-Team: Russian (https://app.transifex.com/owncloud-org/teams/6149/ru/)\n"
diff --git a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/propfind/propfind.go b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/propfind/propfind.go
index 648ded62100..d410bfcba78 100644
--- a/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/propfind/propfind.go
+++ b/vendor/github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/propfind/propfind.go
@@ -64,6 +64,33 @@ const (
 	tracerName = "ocdav"
 )
 
+// these keys are used to lookup in ArbitraryMetadata, generated prop names are lowercased
+var (
+	audioKeys = []string{
+		"album",
+		"albumArtist",
+		"artist",
+		"bitrate",
+		"composers",
+		"copyright",
+		"disc",
+		"discCount",
+		"duration",
+		"genre",
+		"hasDrm",
+		"isVariableBitrate",
+		"title",
+		"track",
+		"trackCount",
+		"year",
+	}
+	locationKeys = []string{
+		"altitude",
+		"latitude",
+		"longitude",
+	}
+)
+
 type countingReader struct {
 	n int
 	r io.Reader
@@ -786,6 +813,14 @@ func (p *Handler) getSpaceResourceInfos(ctx context.Context, w http.ResponseWrit
 	return resourceInfos, true
 }
 
+func metadataKeysWithPrefix(prefix string, keys []string) []string {
+	fullKeys := []string{}
+	for _, key := range keys {
+		fullKeys = append(fullKeys, fmt.Sprintf("%s.%s", prefix, key))
+	}
+	return fullKeys
+}
+
 // metadataKeys splits the propfind properties into arbitrary metadata and ResourceInfo field mask paths
 func metadataKeys(pf XML) ([]string, []string) {
 
@@ -809,6 +844,10 @@ func metadataKeys(pf XML) ([]string, []string) {
 				switch key {
 				case "share-types":
 					fieldMaskKeys = append(fieldMaskKeys, key)
+				case "http://owncloud.org/ns/audio":
+					metadataKeys = append(metadataKeys, metadataKeysWithPrefix("libre.graph.audio", audioKeys)...)
+				case "http://owncloud.org/ns/location":
+					metadataKeys = append(metadataKeys, metadataKeysWithPrefix("libre.graph.location", locationKeys)...)
 				default:
 					metadataKeys = append(metadataKeys, key)
 				}
@@ -866,7 +905,7 @@ func requiresExplicitFetching(n *xml.Name) bool {
 		}
 	case net.NsOwncloud:
 		switch n.Local {
-		case "favorite", "share-types", "checksums", "size", "tags":
+		case "favorite", "share-types", "checksums", "size", "tags", "audio", "location":
 			return true
 		default:
 			return false
@@ -1082,6 +1121,33 @@ func mdToPropResponse(ctx context.Context, pf *XML, md *provider.ResourceInfo, p
 		appendToNotFound = func(p ...prop.PropertyXML) {}
 	}
 
+	appendMetadataProp := func(metadata map[string]string, tagNamespace string, name string, metadataPrefix string, keys []string) {
+		content := strings.Builder{}
+		for _, key := range keys {
+			lowerCaseKey := strings.ToLower(key)
+			if v, ok := metadata[fmt.Sprintf("%s.%s", metadataPrefix, key)]; ok {
+				content.WriteString("<")
+				content.WriteString(tagNamespace)
+				content.WriteString(":")
+				content.WriteString(lowerCaseKey)
+				content.WriteString(">")
+				content.Write(prop.Escaped("", v).InnerXML)
+				content.WriteString("</")
+				content.WriteString(tagNamespace)
+				content.WriteString(":")
+				content.WriteString(lowerCaseKey)
+				content.WriteString(">")
+			}
+		}
+
+		propName := fmt.Sprintf("%s:%s", tagNamespace, name)
+		if content.Len() > 0 {
+			appendToOK(prop.Raw(propName, content.String()))
+		} else {
+			appendToNotFound(prop.NotFound(propName))
+		}
+	}
+
 	// when allprops has been requested
 	if pf.Allprop != nil {
 		// return all known properties
@@ -1180,6 +1246,8 @@ func mdToPropResponse(ctx context.Context, pf *XML, md *provider.ResourceInfo, p
 
 		if k := md.GetArbitraryMetadata().GetMetadata(); k != nil {
 			propstatOK.Prop = append(propstatOK.Prop, prop.Raw("oc:tags", k["tags"]))
+			appendMetadataProp(k, "oc", "audio", "libre.graph.audio", audioKeys)
+			appendMetadataProp(k, "oc", "location", "libre.graph.location", locationKeys)
 		}
 
 		// ls do not report any properties as missing by default
@@ -1453,6 +1521,14 @@ func mdToPropResponse(ctx context.Context, pf *XML, md *provider.ResourceInfo, p
 					if k := md.GetArbitraryMetadata().GetMetadata(); k != nil {
 						propstatOK.Prop = append(propstatOK.Prop, prop.Raw("oc:tags", k["tags"]))
 					}
+				case "audio":
+					if k := md.GetArbitraryMetadata().GetMetadata(); k != nil {
+						appendMetadataProp(k, "oc", "audio", "libre.graph.audio", audioKeys)
+					}
+				case "location":
+					if k := md.GetArbitraryMetadata().GetMetadata(); k != nil {
+						appendMetadataProp(k, "oc", "location", "libre.graph.location", locationKeys)
+					}
 				case "name":
 					appendToOK(prop.Escaped("oc:name", md.Name))
 				case "shareid":
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 4185eac1794..d41253d9b2a 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -359,7 +359,7 @@ github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1
 github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1
 github.com/cs3org/go-cs3apis/cs3/tx/v1beta1
 github.com/cs3org/go-cs3apis/cs3/types/v1beta1
-# github.com/cs3org/reva/v2 v2.16.1-0.20231206110211-7198abf507f6
+# github.com/cs3org/reva/v2 v2.16.1-0.20231206142634-7b47abdafd55
 ## explicit; go 1.20
 github.com/cs3org/reva/v2/cmd/revad/internal/grace
 github.com/cs3org/reva/v2/cmd/revad/runtime


Test patch:
diff --git a/services/graph/pkg/service/v0/driveitems_test.go b/services/graph/pkg/service/v0/driveitems_test.go
index 0eab263318f..e3eb2cb2562 100644
--- a/services/graph/pkg/service/v0/driveitems_test.go
+++ b/services/graph/pkg/service/v0/driveitems_test.go
@@ -759,6 +759,7 @@ var _ = Describe("Driveitems", func() {
 
 				res := assertItemsList(1)
 				Expect(res.Value[0].Audio).To(BeNil())
+				Expect(res.Value[0].Location).To(BeNil())
 			})
 
 			It("returns the audio facet if metadata is available", func() {
@@ -816,6 +817,36 @@ var _ = Describe("Driveitems", func() {
 				Expect(audio.TrackCount).To(Equal(libregraph.PtrInt32(9)))
 				Expect(audio.Year).To(Equal(libregraph.PtrInt32(1994)))
 			})
+
+			It("returns the location facet if metadata is available", func() {
+				gatewayClient.On("ListContainer", mock.Anything, mock.Anything).Return(&provider.ListContainerResponse{
+					Status: status.NewOK(ctx),
+					Infos: []*provider.ResourceInfo{
+						{
+							Type:     provider.ResourceType_RESOURCE_TYPE_FILE,
+							Id:       &provider.ResourceId{StorageId: "storageid", SpaceId: "spaceid", OpaqueId: "opaqueid"},
+							Etag:     "etag",
+							Mtime:    utils.TimeToTS(mtime),
+							MimeType: "image/jpeg",
+							ArbitraryMetadata: &provider.ArbitraryMetadata{
+								Metadata: map[string]string{
+									"libre.graph.location.altitude":  "1047.7",
+									"libre.graph.location.latitude":  "49.48675890884328",
+									"libre.graph.location.longitude": "11.103870357204285",
+								},
+							},
+						},
+					},
+				}, nil)
+
+				res := assertItemsList(1)
+				location := res.Value[0].Location
+
+				Expect(location).ToNot(BeNil())
+				Expect(location.Altitude).To(Equal(libregraph.PtrFloat64(1047.7)))
+				Expect(location.Latitude).To(Equal(libregraph.PtrFloat64(49.48675890884328)))
+				Expect(location.Longitude).To(Equal(libregraph.PtrFloat64(11.103870357204285)))
+			})
 		})
 	})
 })
diff --git a/services/graph/pkg/service/v0/groups_test.go b/services/graph/pkg/service/v0/groups_test.go
index d492798e23e..d8aab638466 100644
--- a/services/graph/pkg/service/v0/groups_test.go
+++ b/services/graph/pkg/service/v0/groups_test.go
@@ -19,6 +19,8 @@ import (
 	. "github.com/onsi/gomega"
 	libregraph "github.com/owncloud/libre-graph-api-go"
 	"github.com/owncloud/ocis/v2/ocis-pkg/shared"
+	settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0"
+	settings "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0"
 	"github.com/owncloud/ocis/v2/services/graph/mocks"
 	"github.com/owncloud/ocis/v2/services/graph/pkg/config"
 	"github.com/owncloud/ocis/v2/services/graph/pkg/config/defaults"
@@ -35,13 +37,14 @@ type groupList struct {
 
 var _ = Describe("Groups", func() {
 	var (
-		svc             service.Service
-		ctx             context.Context
-		cfg             *config.Config
-		gatewayClient   *cs3mocks.GatewayAPIClient
-		gatewaySelector pool.Selectable[gateway.GatewayAPIClient]
-		eventsPublisher mocks.Publisher
-		identityBackend *identitymocks.Backend
+		svc               service.Service
+		ctx               context.Context
+		cfg               *config.Config
+		gatewayClient     *cs3mocks.GatewayAPIClient
+		gatewaySelector   pool.Selectable[gateway.GatewayAPIClient]
+		eventsPublisher   mocks.Publisher
+		identityBackend   *identitymocks.Backend
+		permissionService *mocks.Permissions
 
 		rr *httptest.ResponseRecorder
 
@@ -65,6 +68,7 @@ var _ = Describe("Groups", func() {
 				return gatewayClient
 			},
 		)
+		permissionService = &mocks.Permissions{}
 
 		identityBackend = &identitymocks.Backend{}
 		newGroup = libregraph.NewGroup()
@@ -85,6 +89,7 @@ var _ = Describe("Groups", func() {
 			service.WithGatewaySelector(gatewaySelector),
 			service.EventsPublisher(&eventsPublisher),
 			service.WithIdentityBackend(identityBackend),
+			service.PermissionService(permissionService),
 		)
 	})
 
@@ -97,6 +102,12 @@ var _ = Describe("Groups", func() {
 		})
 
 		It("handles invalid sorting queries", func() {
+			permissionService.On("GetPermissionByID", mock.Anything, mock.Anything).Return(&settings.GetPermissionByIDResponse{
+				Permission: &settingsmsg.Permission{
+					Operation:  settingsmsg.Permission_OPERATION_UNKNOWN,
+					Constraint: settingsmsg.Permission_CONSTRAINT_ALL,
+				},
+			}, nil)
 			identityBackend.On("GetGroups", ctx, mock.Anything).Return([]*libregraph.Group{newGroup}, nil)
 
 			r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/groups?$orderby=invalid", nil)
@@ -113,6 +124,12 @@ var _ = Describe("Groups", func() {
 		})
 
 		It("handles unknown backend errors", func() {
+			permissionService.On("GetPermissionByID", mock.Anything, mock.Anything).Return(&settings.GetPermissionByIDResponse{
+				Permission: &settingsmsg.Permission{
+					Operation:  settingsmsg.Permission_OPERATION_UNKNOWN,
+					Constraint: settingsmsg.Permission_CONSTRAINT_ALL,
+				},
+			}, nil)
 			identityBackend.On("GetGroups", ctx, mock.Anything).Return(nil, errors.New("failed"))
 
 			r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/groups", nil)
@@ -128,6 +145,12 @@ var _ = Describe("Groups", func() {
 		})
 
 		It("handles backend errors", func() {
+			permissionService.On("GetPermissionByID", mock.Anything, mock.Anything).Return(&settings.GetPermissionByIDResponse{
+				Permission: &settingsmsg.Permission{
+					Operation:  settingsmsg.Permission_OPERATION_UNKNOWN,
+					Constraint: settingsmsg.Permission_CONSTRAINT_ALL,
+				},
+			}, nil)
 			identityBackend.On("GetGroups", ctx, mock.Anything).Return(nil, errorcode.New(errorcode.AccessDenied, "access denied"))
 
 			r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/groups", nil)
@@ -144,6 +167,12 @@ var _ = Describe("Groups", func() {
 		})
 
 		It("renders an empty list of groups", func() {
+			permissionService.On("GetPermissionByID", mock.Anything, mock.Anything).Return(&settings.GetPermissionByIDResponse{
+				Permission: &settingsmsg.Permission{
+					Operation:  settingsmsg.Permission_OPERATION_UNKNOWN,
+					Constraint: settingsmsg.Permission_CONSTRAINT_ALL,
+				},
+			}, nil)
 			identityBackend.On("GetGroups", ctx, mock.Anything).Return([]*libregraph.Group{}, nil)
 
 			r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/groups", nil)
@@ -160,6 +189,12 @@ var _ = Describe("Groups", func() {
 		})
 
 		It("renders a list of groups", func() {
+			permissionService.On("GetPermissionByID", mock.Anything, mock.Anything).Return(&settings.GetPermissionByIDResponse{
+				Permission: &settingsmsg.Permission{
+					Operation:  settingsmsg.Permission_OPERATION_UNKNOWN,
+					Constraint: settingsmsg.Permission_CONSTRAINT_ALL,
+				},
+			}, nil)
 			identityBackend.On("GetGroups", ctx, mock.Anything).Return([]*libregraph.Group{newGroup}, nil)
 
 			r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/groups", nil)
@@ -176,6 +211,51 @@ var _ = Describe("Groups", func() {
 			Expect(len(res.Value)).To(Equal(1))
 			Expect(res.Value[0].GetId()).To(Equal("group1"))
 		})
+		It("denies listing for unprivileged users", func() {
+			permissionService.On("GetPermissionByID", mock.Anything, mock.Anything).Return(&settings.GetPermissionByIDResponse{}, nil)
+			r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/users", nil)
+			svc.GetGroups(rr, r)
+
+			Expect(rr.Code).To(Equal(http.StatusForbidden))
+		})
+		It("denies using to short search terms for unprivileged users", func() {
+			permissionService.On("GetPermissionByID", mock.Anything, mock.Anything).Return(&settings.GetPermissionByIDResponse{}, nil)
+			r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/users?$search=a", nil)
+			svc.GetGroups(rr, r)
+
+			Expect(rr.Code).To(Equal(http.StatusForbidden))
+		})
+		It("only returns a restricted set of attributes for unprivileged users", func() {
+			permissionService.On("GetPermissionByID", mock.Anything, mock.Anything).Return(&settings.GetPermissionByIDResponse{}, nil)
+			group := &libregraph.Group{}
+			group.SetId("group1")
+			group.SetDisplayName("Group Name")
+			group.SetMembers(
+				[]libregraph.User{
+					libregraph.User{
+						Id: libregraph.PtrString("userid"),
+					},
+				},
+			)
+			groups := []*libregraph.Group{group}
+
+			identityBackend.On("GetGroups", mock.Anything, mock.Anything, mock.Anything).Return(groups, nil)
+			r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/groups?$search=abc", nil)
+			svc.GetGroups(rr, r)
+
+			Expect(rr.Code).To(Equal(http.StatusOK))
+			data, err := io.ReadAll(rr.Body)
+			Expect(err).ToNot(HaveOccurred())
+
+			res := groupList{}
+			err = json.Unmarshal(data, &res)
+			Expect(err).ToNot(HaveOccurred())
+			groupMap, err := res.Value[0].ToMap()
+			Expect(err).ToNot(HaveOccurred())
+			for k, _ := range groupMap {
+				Expect(k).Should(BeElementOf([]string{"displayName", "id", "groupTypes"}))
+			}
+		})
 	})
 
 	Describe("GetGroup", func() {
diff --git a/services/graph/pkg/service/v0/users_test.go b/services/graph/pkg/service/v0/users_test.go
index c5dfa0fe5ef..4cc9561e268 100644
--- a/services/graph/pkg/service/v0/users_test.go
+++ b/services/graph/pkg/service/v0/users_test.go
@@ -41,15 +41,16 @@ type userList struct {
 
 var _ = Describe("Users", func() {
 	var (
-		svc             service.Service
-		ctx             context.Context
-		cfg             *config.Config
-		gatewayClient   *cs3mocks.GatewayAPIClient
-		gatewaySelector pool.Selectable[gateway.GatewayAPIClient]
-		eventsPublisher mocks.Publisher
-		roleService     *mocks.RoleService
-		valueService    *mocks.ValueService
-		identityBackend *identitymocks.Backend
+		svc               service.Service
+		ctx               context.Context
+		cfg               *config.Config
+		gatewayClient     *cs3mocks.GatewayAPIClient
+		gatewaySelector   pool.Selectable[gateway.GatewayAPIClient]
+		eventsPublisher   mocks.Publisher
+		roleService       *mocks.RoleService
+		valueService      *mocks.ValueService
+		permissionService *mocks.Permissions
+		identityBackend   *identitymocks.Backend
 
 		rr *httptest.ResponseRecorder
 
@@ -76,6 +77,7 @@ var _ = Describe("Users", func() {
 		identityBackend = &identitymocks.Backend{}
 		roleService = &mocks.RoleService{}
 		valueService = &mocks.ValueService{}
+		permissionService = &mocks.Permissions{}
 
 		rr = httptest.NewRecorder()
 		ctx = context.Background()
@@ -94,6 +96,7 @@ var _ = Describe("Users", func() {
 			service.WithIdentityBackend(identityBackend),
 			service.WithRoleService(roleService),
 			service.WithValueService(valueService),
+			service.PermissionService(permissionService),
 		)
 	})
 
@@ -234,6 +237,13 @@ var _ = Describe("Users", func() {
 		})
 
 		It("lists the users", func() {
+			permissionService.On("GetPermissionByID", mock.Anything, mock.Anything).Return(&settings.GetPermissionByIDResponse{
+				Permission: &settingsmsg.Permission{
+					Operation:  settingsmsg.Permission_OPERATION_UNKNOWN,
+					Constraint: settingsmsg.Permission_CONSTRAINT_ALL,
+				},
+			}, nil)
+
 			user := &libregraph.User{}
 			user.SetId("user1")
 			users := []*libregraph.User{user}
@@ -254,6 +264,50 @@ var _ = Describe("Users", func() {
 			Expect(len(res.Value)).To(Equal(1))
 			Expect(res.Value[0].GetId()).To(Equal("user1"))
 		})
+		It("denies listing for unprivileged users", func() {
+			permissionService.On("GetPermissionByID", mock.Anything, mock.Anything).Return(&settings.GetPermissionByIDResponse{}, nil)
+			r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/users", nil)
+			svc.GetUsers(rr, r)
+
+			Expect(rr.Code).To(Equal(http.StatusForbidden))
+		})
+		It("denies using to short search terms for unprivileged users", func() {
+			permissionService.On("GetPermissionByID", mock.Anything, mock.Anything).Return(&settings.GetPermissionByIDResponse{}, nil)
+			r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/users?$search=a", nil)
+			svc.GetUsers(rr, r)
+
+			Expect(rr.Code).To(Equal(http.StatusForbidden))
+		})
+		It("only returns a restricted set of attributes for unprivileged users", func() {
+			permissionService.On("GetPermissionByID", mock.Anything, mock.Anything).Return(&settings.GetPermissionByIDResponse{}, nil)
+			user := &libregraph.User{}
+			user.SetId("user1")
+			user.SetPasswordProfile(
+				libregraph.PasswordProfile{
+					Password: libregraph.PtrString("password"),
+				},
+			)
+			user.SetUserType("usertype")
+			user.SetDisplayName("abc user")
+			users := []*libregraph.User{user}
+
+			identityBackend.On("GetUsers", mock.Anything, mock.Anything, mock.Anything).Return(users, nil)
+			r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/users?$search=abc", nil)
+			svc.GetUsers(rr, r)
+
+			Expect(rr.Code).To(Equal(http.StatusOK))
+			data, err := io.ReadAll(rr.Body)
+			Expect(err).ToNot(HaveOccurred())
+
+			res := userList{}
+			err = json.Unmarshal(data, &res)
+			Expect(err).ToNot(HaveOccurred())
+			userMap, err := res.Value[0].ToMap()
+			Expect(err).ToNot(HaveOccurred())
+			for k, _ := range userMap {
+				Expect(k).Should(BeElementOf([]string{"mail", "displayName", "id", "userType"}))
+			}
+		})
 
 		It("sorts", func() {
 			user := &libregraph.User{}
@@ -270,6 +324,13 @@ var _ = Describe("Users", func() {
 
 			identityBackend.On("GetUsers", mock.Anything, mock.Anything, mock.Anything).Return(users, nil)
 
+			permissionService.On("GetPermissionByID", mock.Anything, mock.Anything).Return(&settings.GetPermissionByIDResponse{
+				Permission: &settingsmsg.Permission{
+					Operation:  settingsmsg.Permission_OPERATION_UNKNOWN,
+					Constraint: settingsmsg.Permission_CONSTRAINT_ALL,
+				},
+			}, nil)
+
 			getUsers := func(path string) []*libregraph.User {
 				r := httptest.NewRequest(http.MethodGet, path, nil)
 				rec := httptest.NewRecorder()
@@ -351,6 +412,12 @@ var _ = Describe("Users", func() {
 			users := []*libregraph.User{user, user2}
 			identityBackend.On("GetUsers", mock.Anything, mock.Anything, mock.Anything).Return(users, nil)
 
+			permissionService.On("GetPermissionByID", mock.Anything, mock.Anything).Return(&settings.GetPermissionByIDResponse{
+				Permission: &settingsmsg.Permission{
+					Operation:  settingsmsg.Permission_OPERATION_UNKNOWN,
+					Constraint: settingsmsg.Permission_CONSTRAINT_ALL,
+				},
+			}, nil)
 			roleService.On("ListRoleAssignments", mock.Anything, mock.Anything, mock.Anything).Return(func(ctx context.Context, in *settings.ListRoleAssignmentsRequest, opts ...client.CallOption) *settings.ListRoleAssignmentsResponse {
 				return &settings.ListRoleAssignmentsResponse{Assignments: []*settingsmsg.UserRoleAssignment{
 					{
@@ -395,6 +462,13 @@ var _ = Describe("Users", func() {
 
 	DescribeTable("GetUsers handles unsupported or invalid filters",
 		func(filter string, status int) {
+			permissionService.On("GetPermissionByID", mock.Anything, mock.Anything).Return(&settings.GetPermissionByIDResponse{
+				Permission: &settingsmsg.Permission{
+					Operation:  settingsmsg.Permission_OPERATION_UNKNOWN,
+					Constraint: settingsmsg.Permission_CONSTRAINT_ALL,
+				},
+			}, nil)
+
 			r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/users?$filter="+url.QueryEscape(filter), nil)
 			svc.GetUsers(rr, r)
 
@@ -422,6 +496,13 @@ var _ = Describe("Users", func() {
 
 	DescribeTable("With a valid filter",
 		func(filter string, status int) {
+			permissionService.On("GetPermissionByID", mock.Anything, mock.Anything).Return(&settings.GetPermissionByIDResponse{
+				Permission: &settingsmsg.Permission{
+					Operation:  settingsmsg.Permission_OPERATION_UNKNOWN,
+					Constraint: settingsmsg.Permission_CONSTRAINT_ALL,
+				},
+			}, nil)
+
 			user := &libregraph.User{}
 			user.SetId("25cb7bc0-3168-4a0c-adbe-396f478ad494")
 			users := []*libregraph.User{user}
diff --git a/services/search/pkg/content/tika_test.go b/services/search/pkg/content/tika_test.go
index 497247cac8d..8a1c7699453 100644
--- a/services/search/pkg/content/tika_test.go
+++ b/services/search/pkg/content/tika_test.go
@@ -140,6 +140,28 @@ var _ = Describe("Tika", func() {
 
 		})
 
+		It("adds location content", func() {
+			fullResponse = `[
+				{
+					"geo:lat": "49.48675890884328",
+					"geo:long": "11.103870357204285"
+				}
+			]`
+			doc, err := tika.Extract(context.TODO(), &provider.ResourceInfo{
+				Type: provider.ResourceType_RESOURCE_TYPE_FILE,
+				Size: 1,
+			})
+			Expect(err).ToNot(HaveOccurred())
+
+			location := doc.Location
+			Expect(location).ToNot(BeNil())
+
+			// TODO: Altitude is not supported right now
+			Expect(location.Altitude).To(BeNil())
+			Expect(location.Latitude).To(Equal(libregraph.PtrFloat64(49.48675890884328)))
+			Expect(location.Longitude).To(Equal(libregraph.PtrFloat64(11.103870357204285)))
+		})
+
 		It("removes stop words", func() {
 			body = "body to test stop words!!! against almost everyone"
 			language = "en"
diff --git a/services/search/pkg/engine/bleve_test.go b/services/search/pkg/engine/bleve_test.go
index 8b9668faf47..1023ee8da0c 100644
--- a/services/search/pkg/engine/bleve_test.go
+++ b/services/search/pkg/engine/bleve_test.go
@@ -548,5 +548,38 @@ var _ = Describe("Bleve", func() {
 			})
 		})
 
+		Context("with location metadata", func() {
+			BeforeEach(func() {
+				resource := engine.Resource{
+					ID:       "1$2!7",
+					ParentID: rootResource.ID,
+					RootID:   rootResource.ID,
+					Path:     "./team.jpg",
+					Type:     uint64(sprovider.ResourceType_RESOURCE_TYPE_FILE),
+					Document: content.Document{
+						Name:     "team.jpg",
+						MimeType: "image/jpeg",
+						Location: &libregraph.GeoCoordinates{
+							Altitude:  libregraph.PtrFloat64(1047.7),
+							Latitude:  libregraph.PtrFloat64(49.48675890884328),
+							Longitude: libregraph.PtrFloat64(11.103870357204285),
+						},
+					},
+				}
+				err := eng.Upsert(resource.ID, resource)
+				Expect(err).ToNot(HaveOccurred())
+			})
+
+			It("returns audio metadata for search", func() {
+				matches := assertDocCount(rootResource.ID, `*team*`, 1)
+				location := matches[0].Entity.Location
+
+				Expect(location).ToNot(BeNil())
+
+				Expect(location.Altitude).To(Equal(libregraph.PtrFloat64(1047.7)))
+				Expect(location.Latitude).To(Equal(libregraph.PtrFloat64(49.48675890884328)))
+				Expect(location.Longitude).To(Equal(libregraph.PtrFloat64(11.103870357204285)))
+			})
+		})
 	})
 })
diff --git a/tests/acceptance/expected-failures-API-on-OCIS-storage.md b/tests/acceptance/expected-failures-API-on-OCIS-storage.md
index e829619bccb..28d00027620 100644
--- a/tests/acceptance/expected-failures-API-on-OCIS-storage.md
+++ b/tests/acceptance/expected-failures-API-on-OCIS-storage.md
@@ -517,7 +517,8 @@ Not everything needs to be implemented for ocis. While the oc10 testsuite covers
 
 #### [Normal users can update the public link to delete its password if permission is not sent in data](https://github.com/owncloud/ocis/issues/7821)
 
-- [coreApiSharePublicLink1/changingPublicLinkShare.feature:154](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharePublicLink1/changingPublicLinkShare.feature#L154)
+- [coreApiSharePublicLink1/changingPublicLinkShare.feature:171](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharePublicLink1/changingPublicLinkShare.feature#L171)
+- [coreApiSharePublicLink1/changingPublicLinkShare.feature:172](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharePublicLink1/changingPublicLinkShare.feature#L172)
 
 Note: always have an empty line at the end of this file.
 The bash script that processes this file requires that the last line has a newline on the end.
diff --git a/tests/acceptance/expected-failures-localAPI-on-OCIS-storage.md b/tests/acceptance/expected-failures-localAPI-on-OCIS-storage.md
index 0a1ebacb0f5..331c3953b0c 100644
--- a/tests/acceptance/expected-failures-localAPI-on-OCIS-storage.md
+++ b/tests/acceptance/expected-failures-localAPI-on-OCIS-storage.md
@@ -115,15 +115,9 @@ The expected failures in this file are from features in the owncloud/ocis repo.
 - [apiGraph/editGroup.feature:35](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiGraph/editGroup.feature#L35)
 - [apiGraph/editGroup.feature:34](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiGraph/editGroup.feature#L34)
 - [apiGraph/editGroup.feature:36](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiGraph/editGroup.feature#L36)
-- [apiGraph/getGroup.feature:54](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiGraph/getGroup.feature#L54)
-- [apiGraph/getGroup.feature:55](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiGraph/getGroup.feature#L55)
-- [apiGraph/getGroup.feature:56](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiGraph/getGroup.feature#L56)
 - [apiGraph/getGroup.feature:103](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiGraph/getGroup.feature#L103)
 - [apiGraph/getGroup.feature:104](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiGraph/getGroup.feature#L104)
 - [apiGraph/getGroup.feature:105](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiGraph/getGroup.feature#L105)
-- [apiGraph/getGroup.feature:267](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiGraph/getGroup.feature#L267)
-- [apiGraph/getGroup.feature:268](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiGraph/getGroup.feature#L268)
-- [apiGraph/getGroup.feature:269](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiGraph/getGroup.feature#L269)
 - [apiGraph/removeUserFromGroup.feature:191](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiGraph/removeUserFromGroup.feature#L191)
 - [apiGraph/removeUserFromGroup.feature:192](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiGraph/removeUserFromGroup.feature#L192)
 - [apiGraph/removeUserFromGroup.feature:193](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/apiGraph/removeUserFromGroup.feature#L193)
diff --git a/tests/acceptance/features/apiGraph/getUser.feature b/tests/acceptance/features/apiGraph/getUser.feature
index e9f2cd0d0a8..94a1e6e52cb 100644
--- a/tests/acceptance/features/apiGraph/getUser.feature
+++ b/tests/acceptance/features/apiGraph/getUser.feature
@@ -229,7 +229,7 @@ Feature: get users
   Scenario Outline: non-admin user tries to get all users
     Given the administrator has assigned the role "<userRole>" to user "Alice" using the Graph API
     When user "Brian" tries to get all users using the Graph API
-    Then the HTTP status code should be "401"
+    Then the HTTP status code should be "403"
     And the JSON data of the response should match
     """
     {
@@ -831,7 +831,7 @@ Feature: get users
     And group "tea-lover" has been created
     And user "Alice" has been added to group "tea-lover"
     When the user "Brian" gets all users of the group "tea-lover" using the Graph API
-    Then the HTTP status code should be "401"
+    Then the HTTP status code should be "403"
     And the JSON data of the response should match
     """
     {
@@ -968,7 +968,7 @@ Feature: get users
   Scenario Outline: non-admin user tries to get users with a certain role
     Given the administrator has assigned the role "<userRole>" to user "Alice" using the Graph API
     When the user "Alice" gets all users with role "<role>" using the Graph API
-    Then the HTTP status code should be "401"
+    Then the HTTP status code should be "403"
     And the JSON data of the response should match
     """
     {
diff --git a/tests/acceptance/features/apiSpacesShares/shareOperations.feature b/tests/acceptance/features/apiSpacesShares/shareOperations.feature
index fea00f4591d..4f59d41923c 100644
--- a/tests/acceptance/features/apiSpacesShares/shareOperations.feature
+++ b/tests/acceptance/features/apiSpacesShares/shareOperations.feature
@@ -355,6 +355,7 @@ Feature: sharing
 
   Scenario Outline: space admin tries to remove password of a public link share (change/create permission)
     Given using spaces DAV path
+    And using OCS API version "<ocs_api_version>"
     And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API
     And user "Alice" has created folder "FOLDER"
     And user "Alice" has created a public link share with settings
@@ -365,17 +366,20 @@ Feature: sharing
       | path        | /FOLDER       |
       | permissions | <permissions> |
       | password    |               |
-    Then the HTTP status code should be "200"
+    Then the HTTP status code should be "<http_status_code>"
     And the OCS status code should be "400"
     And the OCS status message should be "missing required password"
     Examples:
-      | permissions |
-      | change      |
-      | create      |
+      | ocs_api_version | permissions | http_status_code |
+      | 1               | change      | 200              |
+      | 2               | change      | 400              |
+      | 1               | create      | 200              |
+      | 2               | create      | 400              |
 
 
-  Scenario: space admin removes password of a public link share (read permission)
+  Scenario Outline: space admin removes password of a public link share (read permission)
     Given using spaces DAV path
+    And using OCS API version "<ocs_api_version>"
     And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API
     And user "Alice" has created folder "FOLDER"
     And user "Alice" has created a public link share with settings
@@ -387,4 +391,8 @@ Feature: sharing
       | permissions | read    |
       | password    |         |
     Then the HTTP status code should be "200"
-    And the OCS status code should be "100"
+    And the OCS status code should be "<ocs_status_code>"
+    Examples:
+      | ocs_api_version | ocs_status_code |
+      | 1               | 100             |
+      | 2               | 200             |
diff --git a/tests/acceptance/features/apiSpacesShares/shareSpacesViaLink.feature b/tests/acceptance/features/apiSpacesShares/shareSpacesViaLink.feature
index ac986773b9e..a9b3c1ac3e0 100644
--- a/tests/acceptance/features/apiSpacesShares/shareSpacesViaLink.feature
+++ b/tests/acceptance/features/apiSpacesShares/shareSpacesViaLink.feature
@@ -150,20 +150,24 @@ Feature: Share spaces via link
 
 
   Scenario Outline: space admin tries to remove password of a public link share of a space (various permission)
-    Given user "Alice" has created a public link share of the space "share space" with settings:
+    Given using OCS API version "<ocs_api_version>"
+    And user "Alice" has created a public link share of the space "share space" with settings:
       | permissions | <permissions> |
       | password    | %public%      |
     When user "Alice" updates the last public link share using the sharing API with
       | permissions | <permissions> |
       | password    |               |
-    Then the HTTP status code should be "200"
+    Then the HTTP status code should be "<http_status_code>"
     And the OCS status code should be "400"
     And the OCS status message should be "missing required password"
     Examples:
-      | permissions |
-      | 5           |
-      | 15          |
-      | 4           |
+      | ocs_api_version | permissions | http_status_code |
+      | 1               | 5           | 200              |
+      | 2               | 5           | 400              |
+      | 1               | 15          | 200              |
+      | 2               | 15          | 400              |
+      | 1               | 4           | 200              |
+      | 2               | 4           | 400              |
 
 
   Scenario Outline: space admin removes password of a public link share of a space (invite permission)
@@ -183,8 +187,9 @@ Feature: Share spaces via link
       | 2               | 200             |
 
 
-  Scenario: space manager tries to remove password of a public link share of a space (read permission)
-    Given user "Alice" has shared a space "share space" with settings:
+  Scenario Outline: space manager tries to remove password of a public link share of a space (read permission)
+    Given using OCS API version "<ocs_api_version>"
+    And user "Alice" has shared a space "share space" with settings:
       | shareWith | Brian   |
       | role      | manager |
     And user "Brian" has created a public link share of the space "share space" with settings:
@@ -193,13 +198,18 @@ Feature: Share spaces via link
     When user "Brian" updates the last public link share using the sharing API with
       | permissions | 1 |
       | password    |   |
-    Then the HTTP status code should be "200"
+    Then the HTTP status code should be "<http_status_code>"
     And the OCS status code should be "104"
     And the OCS status message should be "user is not allowed to delete the password from the public link"
+    Examples:
+      | ocs_api_version | http_status_code |
+      | 1               | 200              |
+      | 2               | 403              |
 
 
   Scenario Outline: space manager tries to remove password of a public link share of a space (various permission)
-    Given user "Alice" has shared a space "share space" with settings:
+    Given using OCS API version "<ocs_api_version>"
+    And user "Alice" has shared a space "share space" with settings:
       | shareWith | Brian   |
       | role      | manager |
     And user "Brian" has created a public link share of the space "share space" with settings:
@@ -208,14 +218,17 @@ Feature: Share spaces via link
     When user "Brian" updates the last public link share using the sharing API with
       | permissions | <permissions> |
       | password    |               |
-    Then the HTTP status code should be "200"
+    Then the HTTP status code should be "<http_status_code>"
     And the OCS status code should be "400"
     And the OCS status message should be "missing required password"
     Examples:
-      | permissions |
-      | 5           |
-      | 15          |
-      | 4           |
+      | ocs_api_version | permissions | http_status_code |
+      | 1               | 5           | 200              |
+      | 2               | 5           | 400              |
+      | 1               | 15          | 200              |
+      | 2               | 15          | 400              |
+      | 1               | 4           | 200              |
+      | 2               | 4           | 400              |
 
 
   Scenario Outline: space manager removes password of a public link share of a space (invite permission)
diff --git a/tests/acceptance/features/coreApiSharePublicLink1/changingPublicLinkShare.feature b/tests/acceptance/features/coreApiSharePublicLink1/changingPublicLinkShare.feature
index 3de10b72e66..bb37a2f0cdf 100644
--- a/tests/acceptance/features/coreApiSharePublicLink1/changingPublicLinkShare.feature
+++ b/tests/acceptance/features/coreApiSharePublicLink1/changingPublicLinkShare.feature
@@ -134,7 +134,8 @@ Feature: changing a public link share
 
 
   Scenario Outline: normal user tries to remove password of a public link share (change/create permission)
-    Given user "Alice" has created a public link share with settings
+    Given using OCS API version "<ocs_api_version>"
+    And user "Alice" has created a public link share with settings
       | path        | /PARENT       |
       | permissions | <permissions> |
       | password    | %public%      |
@@ -142,26 +143,33 @@ Feature: changing a public link share
       | path        | /PARENT       |
       | permissions | <permissions> |
       | password    |               |
-    Then the HTTP status code should be "200"
+    Then the HTTP status code should be "<http_status_code>"
     And the OCS status code should be "400"
     And the OCS status message should be "missing required password"
     Examples:
-      | permissions |
-      | change      |
-      | create      |
+      | ocs_api_version | permissions | http_status_code |
+      | 1               | change      | 200              |
+      | 2               | change      | 400              |
+      | 1               | create      | 200              |
+      | 2               | create      | 400              |
 
   @issue-7821
-  Scenario: normal user tries to remove password of a public link (update without sending permissions)
-    Given user "Alice" has created a public link share with settings
+  Scenario Outline: normal user tries to remove password of a public link (update without sending permissions)
+    Given using OCS API version "<ocs_api_version>"
+    And user "Alice" has created a public link share with settings
       | path        | /PARENT  |
       | permissions | change   |
       | password    | %public% |
     When user "Alice" updates the last public link share using the sharing API with
       | path     | /PARENT |
       | password |         |
-    Then the HTTP status code should be "200"
-    And the OCS status code should be "400"
-    And the OCS status message should be "missing required password"
+    Then the HTTP status code should be "<http_status_code>"
+    And the OCS status code should be "104"
+    And the OCS status message should be "user is not allowed to delete the password from the public link"
+    Examples:
+      | ocs_api_version | http_status_code |
+      | 1               | 200              |
+      | 2               | 403              |
 
 
   Scenario Outline: normal user removes password of a public link (invite only public link)
@@ -184,8 +192,9 @@ Feature: changing a public link share
       | 2               | 200             |
 
 
-  Scenario: administrator removes password of a read-only public link
-    Given admin has created folder "/PARENT"
+  Scenario Outline: administrator removes password of a read-only public link
+    Given using OCS API version "<ocs_api_version>"
+    And admin has created folder "/PARENT"
     And user "admin" has uploaded file "filesForUpload/textfile.txt" to "PARENT/parent.txt"
     And user "admin" has created a public link share with settings
       | path        | /PARENT  |
@@ -196,12 +205,17 @@ Feature: changing a public link share
       | permissions | read    |
       | password    |         |
     Then the HTTP status code should be "200"
-    And the OCS status code should be "100"
+    And the OCS status code should be "<ocs_status_code>"
     And the public should be able to download file "/parent.txt" from inside the last public link shared folder using the new public WebDAV API with password ""
+    Examples:
+      | ocs_api_version | ocs_status_code |
+      | 1               | 100             |
+      | 2               | 200             |
 
 
   Scenario Outline: administrator tries to remove password of a public link share (change/create permission)
-    Given admin has created folder "/PARENT"
+    Given using OCS API version "<ocs_api_version>"
+    And admin has created folder "/PARENT"
     And user "admin" has created a public link share with settings
       | path        | /PARENT       |
       | permissions | <permissions> |
@@ -210,10 +224,12 @@ Feature: changing a public link share
       | path        | /PARENT       |
       | permissions | <permissions> |
       | password    |               |
-    Then the HTTP status code should be "200"
+    Then the HTTP status code should be "<http_status_code>"
     And the OCS status code should be "400"
     And the OCS status message should be "missing required password"
     Examples:
-      | permissions |
-      | change      |
-      | create      |
+      | ocs_api_version | permissions | http_status_code |
+      | 1               | change      | 200              |
+      | 2               | change      | 400              |
+      | 1               | create      | 200              |
+      | 2               | create      | 400              |