Repository: owncloud/ocis
Pull request #9079 (2024-05-06 10:49:06)

feat: reva app auth
## Description
- [x] Provide a README.md for that service in the root folder of that service.
  - Use CamelCase for section headers.
- [x] For images and example files used in README.md:
  - Create a folder named `md-sources` on the same level where README.md is located. Put all the images and example files referenced by README.md into this folder.
  - Use absolute references like `https://raw.githubusercontent.com/owncloud/ocis/master/services/<service-name>/md-sources/file` to make the content accessible for both README.md and owncloud.dev
    bad `<img src="https://github.com/owncloud/ocis/blob/master/services/graph/images/mermaid-graph.svg" width="500" />`  
    good `<img src="https://raw.githubusercontent.com/owncloud/ocis/master/services/graph/images/mermaid-graph.svg" width="500" />`
- [x] If new CLI command are introduced, that command must be described in the README.md.
- [x] If new global envvars are introduced, the name must start with `OCIS_`.
- [x] Add the service to the makefile in the ocis repo root.
- [x] Make the service startable for binary and individual startup:
  - For single binary add service to `ocis/pkg/runtime`
  - For individual startup add service to `ocis/pkg/commands`
  - Add the service config to `ocis-pkg/config/defaultconfig.go`
- [x] If the service is using service accounts, add it to `ocis/pkg/init/init.go`
- [ ] Add the service to `.drone.star` to enable CI.
- [x] Inform doc team in an _early stage_ to review the readme AND the environment variables created.
  - The description must reflect the behaviour AND usually has a positive code quality impact.
- [x] Create proper description strings for envvars - see other services for examples, especially when it comes to multiple values. This must include:
  - base description, set of available values, description of each value.
- [ ] When suggestable commits are created for text changes and you agree, collect them to a batch and commit them. Do not forget to rebase locally to avoid overwriting the changes made.
- [x] If new envvars are introduced which serve the same purpose but in multiple services, an additional envvar must be added at the beginning of the list starting with `OCIS_` (global envvar).
- [x] Ensure that a service has a debug port
- [x] If the new service introduces a new port:
  - The port must be added to [port-ranges.md](https://github.com/owncloud/ocis/blob/master/docs/services/general-info/port-ranges.md) and to the README.md file.
- [x] Make sure to have a function `FullDefaultConfig()` in `pkg/config/defaults/defaultconfig.go` of your service. It is needed to create the documentation.
- [x] Add metrics to the code to enable monitoring. See the proxy service for implementation details.
  - Plus add a monitoring documentation in the README.md file

## Related Issue
<!--- This project only accepts pull requests related to open issues -->
<!--- If suggesting a new feature or change, please discuss it in an issue first -->
<!--- If fixing a bug, there should be an issue describing it with steps to reproduce -->
<!--- Please link to the issue here: -->
- Fixes <issue_link>

## Motivation and Context
<!--- Why is this change required? What problem does it solve? -->

## How Has This Been Tested?
<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran to -->
<!--- see how your change affects other areas of the code, etc. -->
- test environment:
- test case 1:
- test case 2:
- ...

## Screenshots (if appropriate):

## Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [ ] Technical debt
- [ ] Tests only (no source changes)

## Checklist:
<!-- Tick the checkboxes when done. -->
<!-- Raise documentation ticket in owncloud.github.io/ -->
- [ ] Code changes
- [ ] Unit tests added
- [ ] Acceptance tests added
- [ ] Documentation ticket raised: <link> 


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]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [2 New issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9079&resolved=false&inNewCodePeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_accepted_issues&view=list)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=9079&resolved=false&inNewCodePeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9079&resolved=false&inNewCodePeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_accepted_issues&view=list)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=9079&resolved=false&inNewCodePeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [2 New issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9079&resolved=false&inNewCodePeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_accepted_issues&view=list)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=9079&resolved=false&inNewCodePeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [5.8% Coverage on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [2 New issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9079&resolved=false&inNewCodePeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_accepted_issues&view=list)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=9079&resolved=false&inNewCodePeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [5.7% Coverage on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079)


DeepDiver1975: Somehow the app auth interface is not what I am expecting.
`GenerateAppPassword` is not taking the user as argument but extracts it from the context (see below)

The function is designed to be called from an authenticated request of the user.
We cannot call it from the migration service to generate tokens for any user.
We cannot call this function from an cli tool to generate tokens for a user for testing purpose or what ever.

https://github.com/owncloud/ocis/blob/afc6ed1e416c8c1abef832df3e6912b7487cccbd/vendor/github.com/cs3org/reva/v2/pkg/appauth/manager/json/json.go#L140
DeepDiver1975: Furthermore I don't think these scopes are of any help for the calendar and contacts integration:
https://github.com/owncloud/ocis/blob/afc6ed1e416c8c1abef832df3e6912b7487cccbd/vendor/github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1/resources.pb.go#L77

We need a scope like `calendar.Read` `files.Read` .... no idea how this should fit in here.
DeepDiver1975: Scopes are hardcoded in reva - kind of crazy - but okay ....

https://github.com/owncloud/ocis/blob/b6d5cd78f0b5185df1c175246fafde0fa88995e1/vendor/github.com/cs3org/reva/v2/pkg/auth/scope/scope.go#L33
sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [2 New issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9079&resolved=false&inNewCodePeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_accepted_issues&view=list)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=9079&resolved=false&inNewCodePeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079)


rhafer: > Furthermore I don't think these scopes are of any help for the calendar and contacts integration:
> 
> https://github.com/owncloud/ocis/blob/afc6ed1e416c8c1abef832df3e6912b7487cccbd/vendor/github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1/resources.pb.go#L77
> 
> We need a scope like `calendar.Read` `files.Read` .... no idea how this should fit in here.
> 
> From my understanding these scopes can only be verified in the oCIS proxy. But prove me wrong ....

Please. Let's keep this PR limited to the migration topic for now. As was decided in https://github.com/owncloud/ocis/pull/8522#issuecomment-2092985475
DeepDiver1975: > Please. Let's keep this PR limited to the migration topic for now. As was decided in [#8522 (comment)](https://github.com/owncloud/ocis/pull/8522#issuecomment-2092985475)


Even then - scopes need to be set properly and I was wondering how this shall work. But I answered this myself already. THX
DeepDiver1975: machine auth does not support scopes as far as I can see .... https://github.com/owncloud/ocis/blob/afc6ed1e416c8c1abef832df3e6912b7487cccbd/vendor/github.com/cs3org/reva/v2/pkg/auth/manager/machine/machine.go#L73

but maybe I am missing something.

to sum it up:
- machine auth for migration service
- app auth for calendar, contacts and related
rhafer: > to sum it up:
> 
>     * machine auth for migration service
> 
>     * app auth for calendar, contacts and related

I guess that was a misunderstanding. I meant you could use machine auth (via the gateway) for getting a token for the target user. Which you can then use with `GenerateAppPassword` to create a app token for the user.
mmattel: Hooking myself in as I need to add a page in the admin docs when merged,
else we get build errors there.
DeepDiver1975: seeing following error when trying to call `GenerateAppPassword` in create.go ....

```
rpc error: code = Unimplemented desc = gateway: error calling GenerateAppPassword: rpc error: code = Unimplemented desc = unknown service cs3.auth.applications.v1beta1.ApplicationsAPI

micbar: > seeing following error when trying to call GenerateAppPassword in create.go ....

Saw a similiar issue before, looks like the connection to the provider doesn't work. Maybe a port issue.
DeepDiver1975: > Saw a similiar issue before, looks like the connection to the provider doesn't work. Maybe a port issue.

THX - sorted with @rhafer ...
sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [3 New issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9079&resolved=false&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_accepted_issues&view=list)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=9079&resolved=false&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [3 New issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9079&resolved=false&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_accepted_issues&view=list)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=9079&resolved=false&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [9.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [3 New issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9079&resolved=false&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_accepted_issues&view=list)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=9079&resolved=false&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [9.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079)


tbsbdr: @DeepDiver1975 what needs to be decided to move this forward?
DeepDiver1975: @kobergj will finish it. I am out of this. Thx
tbsbdr: Good decision 👍
sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [4 New issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9079&resolved=false&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9079&resolutions=WONTFIX)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=9079&resolved=false&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [4 New issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9079&resolved=false&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9079&resolutions=WONTFIX)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=9079&resolved=false&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [4 New issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9079&resolved=false&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9079&resolutions=WONTFIX)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=9079&resolved=false&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [4 New issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9079&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9079&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=9079&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [8.9% Coverage on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [4 New issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9079&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9079&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=9079&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [5 New issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9079&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9079&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=9079&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [8.7% Coverage on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [4 New issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9079&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9079&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=9079&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [8.7% Coverage on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079)


tbsbdr: great that this is moving forward, thx @kobergj !
sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [4 New issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9079&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9079&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=9079&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [8.7% Coverage on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [4 New issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9079&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9079&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=9079&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [8.7% Coverage on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [4 New issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9079&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9079&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=9079&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [8.7% Coverage on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [5 New issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9079&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9079&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=9079&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [8.7% Coverage on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [4 New issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9079&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9079&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=9079&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [8.8% Coverage on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [4 New issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9079&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9079&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=9079&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [8.8% Coverage on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [4 New issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9079&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9079&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=9079&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [9.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [4 New issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9079&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9079&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=9079&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [9.1% Coverage on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [4 New issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9079&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=owncloud_ocis&pullRequest=9079&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=owncloud_ocis&pullRequest=9079&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [8.8% Coverage on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=owncloud_ocis&pullRequest=9079&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=owncloud_ocis&pullRequest=9079)



Patch:
diff --git a/Makefile b/Makefile
index 156cb56a137..23467a7444f 100644
--- a/Makefile
+++ b/Makefile
@@ -25,6 +25,7 @@ OCIS_MODULES = \
 	services/app-provider \
 	services/app-registry \
 	services/audit \
+	services/auth-app \
 	services/auth-basic \
 	services/auth-bearer \
 	services/auth-machine \
diff --git a/changelog/unreleased/auth-app-service.md b/changelog/unreleased/auth-app-service.md
new file mode 100644
index 00000000000..3d27f95eba9
--- /dev/null
+++ b/changelog/unreleased/auth-app-service.md
@@ -0,0 +1,5 @@
+Enhancement: Introduce auth-app service
+
+Introduce a new service, auth-app, that provides authentication and authorization services for applications.
+
+https://github.com/owncloud/ocis/pull/9079
diff --git a/docs/services/general-info/port-ranges.md b/docs/services/general-info/port-ranges.md
index d36dd9a6263..ae027bbee8e 100644
--- a/docs/services/general-info/port-ranges.md
+++ b/docs/services/general-info/port-ranges.md
@@ -60,7 +60,7 @@ We also suggest using the last port in your extensions' range as a debug/metrics
 | 9230-9234  | [nats]({{< ref "../nats/_index.md" >}})                                                |
 | 9235-9239  | [idm]({{< ref "../idm/_index.md" >}})                                                  |
 | 9240-9244  | [app-registry]({{< ref "../app-registry/_index.md" >}})                                |
-| 9245-9249  | FREE                                                                                   |
+| 9245-9249  | [auth-app]({{< ref "../auth-app/_index.md" >}})                                        |
 | 9250-9254  | [ocis server (runtime)](https://github.com/owncloud/ocis/tree/master/ocis/pkg/runtime) |
 | 9255-9259  | [postprocessing]({{< ref "../postprocessing/_index.md" >}})                            |
 | 9260-9264  | [clientlog]({{< ref "../clientlog/_index.md" >}})                                      |
diff --git a/ocis-pkg/config/config.go b/ocis-pkg/config/config.go
index 245e8ceb018..79926807fb6 100644
--- a/ocis-pkg/config/config.go
+++ b/ocis-pkg/config/config.go
@@ -7,6 +7,7 @@ import (
 	appProvider "github.com/owncloud/ocis/v2/services/app-provider/pkg/config"
 	appRegistry "github.com/owncloud/ocis/v2/services/app-registry/pkg/config"
 	audit "github.com/owncloud/ocis/v2/services/audit/pkg/config"
+	authapp "github.com/owncloud/ocis/v2/services/auth-app/pkg/config"
 	authbasic "github.com/owncloud/ocis/v2/services/auth-basic/pkg/config"
 	authbearer "github.com/owncloud/ocis/v2/services/auth-bearer/pkg/config"
 	authmachine "github.com/owncloud/ocis/v2/services/auth-machine/pkg/config"
@@ -86,6 +87,7 @@ type Config struct {
 	AppProvider       *appProvider.Config    `yaml:"app_provider"`
 	AppRegistry       *appRegistry.Config    `yaml:"app_registry"`
 	Audit             *audit.Config          `yaml:"audit"`
+	AuthApp           *authapp.Config        `yaml:"auth_app"`
 	AuthBasic         *authbasic.Config      `yaml:"auth_basic"`
 	AuthBearer        *authbearer.Config     `yaml:"auth_bearer"`
 	AuthMachine       *authmachine.Config    `yaml:"auth_machine"`
diff --git a/ocis-pkg/config/defaultconfig.go b/ocis-pkg/config/defaultconfig.go
index 7e284f006f3..bd15668a765 100644
--- a/ocis-pkg/config/defaultconfig.go
+++ b/ocis-pkg/config/defaultconfig.go
@@ -6,6 +6,7 @@ import (
 	appProvider "github.com/owncloud/ocis/v2/services/app-provider/pkg/config/defaults"
 	appRegistry "github.com/owncloud/ocis/v2/services/app-registry/pkg/config/defaults"
 	audit "github.com/owncloud/ocis/v2/services/audit/pkg/config/defaults"
+	authapp "github.com/owncloud/ocis/v2/services/auth-app/pkg/config/defaults"
 	authbasic "github.com/owncloud/ocis/v2/services/auth-basic/pkg/config/defaults"
 	authbearer "github.com/owncloud/ocis/v2/services/auth-bearer/pkg/config/defaults"
 	authmachine "github.com/owncloud/ocis/v2/services/auth-machine/pkg/config/defaults"
@@ -58,6 +59,7 @@ func DefaultConfig() *Config {
 		AppProvider:       appProvider.DefaultConfig(),
 		AppRegistry:       appRegistry.DefaultConfig(),
 		Audit:             audit.DefaultConfig(),
+		AuthApp:           authapp.DefaultConfig(),
 		AuthBasic:         authbasic.DefaultConfig(),
 		AuthBearer:        authbearer.DefaultConfig(),
 		AuthMachine:       authmachine.DefaultConfig(),
diff --git a/ocis/pkg/command/services.go b/ocis/pkg/command/services.go
index a247e3728a6..0920dd4e3b1 100644
--- a/ocis/pkg/command/services.go
+++ b/ocis/pkg/command/services.go
@@ -13,6 +13,7 @@ import (
 	appprovider "github.com/owncloud/ocis/v2/services/app-provider/pkg/command"
 	appregistry "github.com/owncloud/ocis/v2/services/app-registry/pkg/command"
 	audit "github.com/owncloud/ocis/v2/services/audit/pkg/command"
+	authapp "github.com/owncloud/ocis/v2/services/auth-app/pkg/command"
 	authbasic "github.com/owncloud/ocis/v2/services/auth-basic/pkg/command"
 	authbearer "github.com/owncloud/ocis/v2/services/auth-bearer/pkg/command"
 	authmachine "github.com/owncloud/ocis/v2/services/auth-machine/pkg/command"
@@ -78,6 +79,11 @@ var svccmds = []register.Command{
 			cfg.Audit.Commons = cfg.Commons
 		})
 	},
+	func(cfg *config.Config) *cli.Command {
+		return ServiceCommand(cfg, cfg.AuthApp.Service.Name, authapp.GetCommands(cfg.AuthApp), func(_ *config.Config) {
+			cfg.AuthApp.Commons = cfg.Commons
+		})
+	},
 	func(cfg *config.Config) *cli.Command {
 		return ServiceCommand(cfg, cfg.AuthBasic.Service.Name, authbasic.GetCommands(cfg.AuthBasic), func(c *config.Config) {
 			cfg.AuthBasic.Commons = cfg.Commons
@@ -266,10 +272,10 @@ var svccmds = []register.Command{
 }
 
 // ServiceCommand is the entry point for the all service commands.
-func ServiceCommand(cfg *config.Config, servicename string, subcommands []*cli.Command, f func(*config.Config)) *cli.Command {
+func ServiceCommand(cfg *config.Config, serviceName string, subcommands []*cli.Command, f func(*config.Config)) *cli.Command {
 	return &cli.Command{
-		Name:     servicename,
-		Usage:    helper.SubcommandDescription(servicename),
+		Name:     serviceName,
+		Usage:    helper.SubcommandDescription(serviceName),
 		Category: "services",
 		Before: func(c *cli.Context) error {
 			configlog.Error(parser.ParseConfig(cfg, true))
diff --git a/ocis/pkg/runtime/service/service.go b/ocis/pkg/runtime/service/service.go
index 7c8ec280f5c..d42604b7e64 100644
--- a/ocis/pkg/runtime/service/service.go
+++ b/ocis/pkg/runtime/service/service.go
@@ -13,6 +13,8 @@ import (
 	"syscall"
 	"time"
 
+	authapp "github.com/owncloud/ocis/v2/services/auth-app/pkg/command"
+
 	"github.com/cenkalti/backoff"
 	"github.com/cs3org/reva/v2/pkg/events/stream"
 	"github.com/cs3org/reva/v2/pkg/logger"
@@ -324,6 +326,11 @@ func NewService(options ...Option) (*Service, error) {
 		cfg.Audit.Commons = cfg.Commons
 		return audit.Execute(cfg.Audit)
 	})
+	areg(opts.Config.AuthApp.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error {
+		cfg.AuthApp.Context = ctx
+		cfg.AuthApp.Commons = cfg.Commons
+		return authapp.Execute(cfg.AuthApp)
+	})
 	areg(opts.Config.Policies.Service.Name, func(ctx context.Context, cfg *ociscfg.Config) error {
 		cfg.Policies.Context = ctx
 		cfg.Policies.Commons = cfg.Commons
diff --git a/services/auth-app/Makefile b/services/auth-app/Makefile
new file mode 100644
index 00000000000..3898eb98c8f
--- /dev/null
+++ b/services/auth-app/Makefile
@@ -0,0 +1,37 @@
+SHELL := bash
+NAME := auth-app
+
+include ../../.make/recursion.mk
+
+############ tooling ############
+ifneq (, $(shell command -v go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI
+include ../../.bingo/Variables.mk
+endif
+
+############ go tooling ############
+include ../../.make/go.mk
+
+############ release ############
+include ../../.make/release.mk
+
+############ docs generate ############
+include ../../.make/docs.mk
+
+.PHONY: docs-generate
+docs-generate: config-docs-generate
+
+############ generate ############
+include ../../.make/generate.mk
+
+.PHONY: ci-go-generate
+ci-go-generate: # CI runs ci-node-generate automatically before this target
+
+.PHONY: ci-node-generate
+ci-node-generate:
+
+############ licenses ############
+.PHONY: ci-node-check-licenses
+ci-node-check-licenses:
+
+.PHONY: ci-node-save-licenses
+ci-node-save-licenses:
diff --git a/services/auth-app/README.md b/services/auth-app/README.md
new file mode 100644
index 00000000000..012490527e9
--- /dev/null
+++ b/services/auth-app/README.md
@@ -0,0 +1,30 @@
+# Auth-App
+
+The auth-app service provides authentication for 3rd party apps.
+
+## The `auth` Service Family
+
+ocis uses serveral authentication services for different use cases. All services that start with `auth-` are part of the authentication service family. Each member authenticates requests with different scopes. As of now, these services exist:
+  -   `auth-app` handles authentication of external 3rd party apps
+  -   `auth-basic` handles basic authentication
+  -   `auth-bearer` handles oidc authentication
+  -   `auth-machine` handles interservice authentication when a user is impersonated
+  -   `auth-service` handles interservice authentication when using service accounts
+
+## Service Startup
+
+Because this service is not started automatically, a manual start needs to be initiated which can be done in several ways. To configure the service usage, an environment variable for the proxy service needs to be set to allow app authentication.
+```bash
+OCIS_ADD_RUN_SERVICES=auth-app  # deployment specific. Add the service to the manual startup list, use with binary deployments. Alternatively you can start the service explicitly via the command line.
+PROXY_ENABLE_APP_AUTH=true      # mandatory, allow app authentication. In case of a distributed environment, this envvar needs to be set in the proxy service.
+```
+
+## App Tokens
+
+App Tokens are used to authenticate 3rd party access via https like when using curl (apps) to access an API endpoint. These apps need to authenticate themselves as no logged in user authenticates the request. To be able to use an app token, one must first create a token via the cli. Replace the `user-name` with an existing user. For the `token-expiration`, you can use any time abbreviation from the following list: `h, m, s`. Examples: `72h` or `1h` or `1m` or `1s.` Default is `72h`.
+
+```bash
+ocis auth-app create --user-name={user-name} --expiration={token-expiration}
+```
+
+Once generated, these tokens can be used to authenticate requests to ocis. They are passed as part of the request as `Basic Auth` header.
diff --git a/services/auth-app/cmd/auth-app/main.go b/services/auth-app/cmd/auth-app/main.go
new file mode 100644
index 00000000000..185d6eb9fd9
--- /dev/null
+++ b/services/auth-app/cmd/auth-app/main.go
@@ -0,0 +1,14 @@
+package main
+
+import (
+	"os"
+
+	"github.com/owncloud/ocis/v2/services/auth-app/pkg/command"
+	"github.com/owncloud/ocis/v2/services/auth-app/pkg/config/defaults"
+)
+
+func main() {
+	if err := command.Execute(defaults.DefaultConfig()); err != nil {
+		os.Exit(1)
+	}
+}
diff --git a/services/auth-app/pkg/command/create.go b/services/auth-app/pkg/command/create.go
new file mode 100644
index 00000000000..5e2b897f9e6
--- /dev/null
+++ b/services/auth-app/pkg/command/create.go
@@ -0,0 +1,124 @@
+package command
+
+import (
+	"context"
+	"fmt"
+
+	authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
+	"github.com/cs3org/reva/v2/pkg/auth/scope"
+
+	"time"
+
+	applicationsv1beta1 "github.com/cs3org/go-cs3apis/cs3/auth/applications/v1beta1"
+	gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
+	userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
+	rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
+	typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
+	ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
+	"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
+	"github.com/owncloud/ocis/v2/ocis-pkg/config/configlog"
+	"github.com/owncloud/ocis/v2/ocis-pkg/registry"
+	"github.com/owncloud/ocis/v2/ocis-pkg/tracing"
+	"github.com/owncloud/ocis/v2/services/auth-app/pkg/config"
+	"github.com/owncloud/ocis/v2/services/auth-app/pkg/config/parser"
+	"github.com/urfave/cli/v2"
+	"google.golang.org/grpc/metadata"
+)
+
+// Create is the entrypoint for the app auth create command
+func Create(cfg *config.Config) *cli.Command {
+	return &cli.Command{
+		Name:     "create",
+		Usage:    "create an app auth token for a user",
+		Category: "maintenance",
+		Flags: []cli.Flag{
+			&cli.StringFlag{
+				Name:  "user-name",
+				Value: "",
+				Usage: "user to create the app-token for",
+			},
+			&cli.StringFlag{
+				Name:  "expiration",
+				Value: "72h",
+				Usage: "expiration of the app password, e.g. 72h, 1h, 1m, 1s. Default is 72h.",
+			},
+		},
+		Before: func(_ *cli.Context) error {
+			return configlog.ReturnError(parser.ParseConfig(cfg))
+		},
+		Action: func(c *cli.Context) error {
+			traceProvider, err := tracing.GetServiceTraceProvider(cfg.Tracing, cfg.Service.Name)
+			if err != nil {
+				return err
+			}
+
+			gatewaySelector, err := pool.GatewaySelector(
+				cfg.Reva.Address,
+				append(
+					cfg.Reva.GetRevaOptions(),
+					pool.WithRegistry(registry.GetRegistry()),
+					pool.WithTracerProvider(traceProvider),
+				)...)
+			if err != nil {
+				return err
+			}
+
+			next, err := gatewaySelector.Next()
+			if err != nil {
+				return err
+			}
+
+			userName := c.String("user-name")
+			if userName == "" {
+				fmt.Printf("Username to create app token for: ")
+				if _, err := fmt.Scanln(&userName); err != nil {
+					return err
+				}
+			}
+
+			ctx := context.Background()
+			authRes, err := next.Authenticate(ctx, &gatewayv1beta1.AuthenticateRequest{
+				Type:         "machine",
+				ClientId:     "username:" + userName,
+				ClientSecret: cfg.MachineAuthAPIKey,
+			})
+			if err != nil {
+				return err
+			}
+			if authRes.GetStatus().GetCode() != rpc.Code_CODE_OK {
+				return fmt.Errorf("error authenticating user: %s", authRes.GetStatus().GetMessage())
+			}
+
+			granteeCtx := ctxpkg.ContextSetUser(context.Background(), &userpb.User{Id: authRes.GetUser().GetId()})
+			granteeCtx = metadata.AppendToOutgoingContext(granteeCtx, ctxpkg.TokenHeader, authRes.GetToken())
+
+			scopes, err := scope.AddOwnerScope(map[string]*authpb.Scope{})
+			if err != nil {
+				return err
+			}
+
+			expiry, err := time.ParseDuration(c.String("expiration"))
+			if err != nil {
+				return err
+			}
+
+			appPassword, err := next.GenerateAppPassword(granteeCtx, &applicationsv1beta1.GenerateAppPasswordRequest{
+				TokenScope: scopes,
+				Label:      "Generated via CLI",
+				Expiration: &typesv1beta1.Timestamp{
+					Seconds: uint64(time.Now().Add(expiry).Unix()),
+				},
+			})
+			if err != nil {
+				return err
+			}
+
+			fmt.Printf("App token created for %s", authRes.GetUser().GetUsername())
+			fmt.Println()
+			fmt.Printf(" token: %s", appPassword.GetAppPassword().GetPassword())
+			fmt.Println()
+
+			return nil
+		},
+	}
+}
diff --git a/services/auth-app/pkg/command/health.go b/services/auth-app/pkg/command/health.go
new file mode 100644
index 00000000000..fad375e207e
--- /dev/null
+++ b/services/auth-app/pkg/command/health.go
@@ -0,0 +1,54 @@
+package command
+
+import (
+	"fmt"
+	"net/http"
+
+	"github.com/owncloud/ocis/v2/ocis-pkg/config/configlog"
+	"github.com/owncloud/ocis/v2/services/auth-app/pkg/config"
+	"github.com/owncloud/ocis/v2/services/auth-app/pkg/config/parser"
+	"github.com/owncloud/ocis/v2/services/auth-app/pkg/logging"
+	"github.com/urfave/cli/v2"
+)
+
+// Health is the entrypoint for the health command.
+func Health(cfg *config.Config) *cli.Command {
+	return &cli.Command{
+		Name:     "health",
+		Usage:    "check health status",
+		Category: "info",
+		Before: func(_ *cli.Context) error {
+			return configlog.ReturnError(parser.ParseConfig(cfg))
+		},
+		Action: func(_ *cli.Context) error {
+			logger := logging.Configure(cfg.Service.Name, cfg.Log)
+
+			resp, err := http.Get(
+				fmt.Sprintf(
+					"http://%s/healthz",
+					cfg.Debug.Addr,
+				),
+			)
+
+			if err != nil {
+				logger.Fatal().
+					Err(err).
+					Msg("Failed to request health check")
+			}
+
+			defer resp.Body.Close()
+
+			if resp.StatusCode != http.StatusOK {
+				logger.Fatal().
+					Int("code", resp.StatusCode).
+					Msg("Health seems to be in bad state")
+			}
+
+			logger.Debug().
+				Int("code", resp.StatusCode).
+				Msg("Health got a good state")
+
+			return nil
+		},
+	}
+}
diff --git a/services/auth-app/pkg/command/root.go b/services/auth-app/pkg/command/root.go
new file mode 100644
index 00000000000..f1ba83b36ec
--- /dev/null
+++ b/services/auth-app/pkg/command/root.go
@@ -0,0 +1,35 @@
+package command
+
+import (
+	"os"
+
+	"github.com/owncloud/ocis/v2/ocis-pkg/clihelper"
+	"github.com/owncloud/ocis/v2/services/auth-app/pkg/config"
+	"github.com/urfave/cli/v2"
+)
+
+// GetCommands provides all commands for this service
+func GetCommands(cfg *config.Config) cli.Commands {
+	return []*cli.Command{
+		// start this service
+		Server(cfg),
+
+		// interaction with this service
+		Create(cfg),
+
+		// infos about this service
+		Health(cfg),
+		Version(cfg),
+	}
+}
+
+// Execute is the entry point for the ocis-auth-app command.
+func Execute(cfg *config.Config) error {
+	app := clihelper.DefaultApp(&cli.App{
+		Name:     "auth-app",
+		Usage:    "Provide app authentication for oCIS",
+		Commands: GetCommands(cfg),
+	})
+
+	return app.Run(os.Args)
+}
diff --git a/services/auth-app/pkg/command/server.go b/services/auth-app/pkg/command/server.go
new file mode 100644
index 00000000000..afb3b54dc00
--- /dev/null
+++ b/services/auth-app/pkg/command/server.go
@@ -0,0 +1,104 @@
+package command
+
+import (
+	"context"
+	"fmt"
+	"os"
+	"path"
+
+	"github.com/cs3org/reva/v2/cmd/revad/runtime"
+	"github.com/gofrs/uuid"
+	"github.com/oklog/run"
+	"github.com/owncloud/ocis/v2/ocis-pkg/config/configlog"
+	"github.com/owncloud/ocis/v2/ocis-pkg/registry"
+	"github.com/owncloud/ocis/v2/ocis-pkg/sync"
+	"github.com/owncloud/ocis/v2/ocis-pkg/tracing"
+	"github.com/owncloud/ocis/v2/ocis-pkg/version"
+	"github.com/owncloud/ocis/v2/services/auth-app/pkg/config"
+	"github.com/owncloud/ocis/v2/services/auth-app/pkg/config/parser"
+	"github.com/owncloud/ocis/v2/services/auth-app/pkg/logging"
+	"github.com/owncloud/ocis/v2/services/auth-app/pkg/revaconfig"
+	"github.com/owncloud/ocis/v2/services/auth-app/pkg/server/debug"
+	"github.com/urfave/cli/v2"
+)
+
+// Server is the entry point for the server command.
+func Server(cfg *config.Config) *cli.Command {
+	return &cli.Command{
+		Name:     "server",
+		Usage:    fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name),
+		Category: "server",
+		Before: func(_ *cli.Context) error {
+			return configlog.ReturnFatal(parser.ParseConfig(cfg))
+		},
+		Action: func(_ *cli.Context) error {
+			logger := logging.Configure(cfg.Service.Name, cfg.Log)
+			traceProvider, err := tracing.GetServiceTraceProvider(cfg.Tracing, cfg.Service.Name)
+			if err != nil {
+				return err
+			}
+			gr := run.Group{}
+			ctx, cancel := defineContext(cfg)
+
+			defer cancel()
+
+			gr.Add(func() error {
+				pidFile := path.Join(os.TempDir(), "revad-"+cfg.Service.Name+"-"+uuid.Must(uuid.NewV4()).String()+".pid")
+				rCfg := revaconfig.AuthAppConfigFromStruct(cfg)
+				reg := registry.GetRegistry()
+
+				runtime.RunWithOptions(rCfg, pidFile,
+					runtime.WithLogger(&logger.Logger),
+					runtime.WithRegistry(reg),
+					runtime.WithTraceProvider(traceProvider),
+				)
+
+				return nil
+			}, func(err error) {
+				logger.Error().
+					Str("server", cfg.Service.Name).
+					Err(err).
+					Msg("Shutting down server")
+
+				cancel()
+				os.Exit(1)
+			})
+
+			debugServer, err := debug.Server(
+				debug.Logger(logger),
+				debug.Context(ctx),
+				debug.Config(cfg),
+			)
+			if err != nil {
+				logger.Info().Err(err).Str("server", "debug").Msg("Failed to initialize server")
+				return err
+			}
+
+			gr.Add(debugServer.ListenAndServe, func(_ error) {
+				cancel()
+			})
+
+			if !cfg.Supervised {
+				sync.Trap(&gr, cancel)
+			}
+
+			grpcSvc := registry.BuildGRPCService(cfg.GRPC.Namespace+"."+cfg.Service.Name, uuid.Must(uuid.NewV4()).String(), cfg.GRPC.Addr, version.GetString())
+			if err := registry.RegisterService(ctx, grpcSvc, logger); err != nil {
+				logger.Fatal().Err(err).Msg("failed to register the grpc service")
+			}
+
+			return gr.Run()
+		},
+	}
+}
+
+// defineContext sets the context for the service. If there is a context configured it will create a new child from it,
+// if not, it will create a root context that can be cancelled.
+func defineContext(cfg *config.Config) (context.Context, context.CancelFunc) {
+	return func() (context.Context, context.CancelFunc) {
+		if cfg.Context == nil {
+			return context.WithCancel(context.Background())
+		}
+		return context.WithCancel(cfg.Context)
+	}()
+}
diff --git a/services/auth-app/pkg/command/version.go b/services/auth-app/pkg/command/version.go
new file mode 100644
index 00000000000..b7c644c552d
--- /dev/null
+++ b/services/auth-app/pkg/command/version.go
@@ -0,0 +1,50 @@
+package command
+
+import (
+	"fmt"
+	"os"
+
+	"github.com/owncloud/ocis/v2/ocis-pkg/registry"
+	"github.com/owncloud/ocis/v2/ocis-pkg/version"
+
+	tw "github.com/olekukonko/tablewriter"
+	"github.com/owncloud/ocis/v2/services/auth-app/pkg/config"
+	"github.com/urfave/cli/v2"
+)
+
+// Version prints the service versions of all running instances.
+func Version(cfg *config.Config) *cli.Command {
+	return &cli.Command{
+		Name:     "version",
+		Usage:    "print the version of this binary and the running service instances",
+		Category: "info",
+		Action: func(c *cli.Context) error {
+			fmt.Println("Version: " + version.GetString())
+			fmt.Printf("Compiled: %s\n", version.Compiled())
+			fmt.Println("")
+
+			reg := registry.GetRegistry()
+			services, err := reg.GetService(cfg.GRPC.Namespace + "." + cfg.Service.Name)
+			if err != nil {
+				fmt.Println(fmt.Errorf("could not get %s services from the registry: %v", cfg.Service.Name, err))
+				return err
+			}
+
+			if len(services) == 0 {
+				fmt.Println("No running " + cfg.Service.Name + " service found.")
+				return nil
+			}
+
+			table := tw.NewWriter(os.Stdout)
+			table.SetHeader([]string{"Version", "Address", "Id"})
+			table.SetAutoFormatHeaders(false)
+			for _, s := range services {
+				for _, n := range s.Nodes {
+					table.Append([]string{s.Version, n.Address, n.Id})
+				}
+			}
+			table.Render()
+			return nil
+		},
+	}
+}
diff --git a/services/auth-app/pkg/config/config.go b/services/auth-app/pkg/config/config.go
new file mode 100644
index 00000000000..01d58b81eb5
--- /dev/null
+++ b/services/auth-app/pkg/config/config.go
@@ -0,0 +1,57 @@
+package config
+
+import (
+	"context"
+
+	"github.com/owncloud/ocis/v2/ocis-pkg/shared"
+)
+
+// Config defines the root config structure
+type Config struct {
+	Commons *shared.Commons `yaml:"-"` // don't use this directly as configuration for a service
+	Service Service         `yaml:"-"`
+	Tracing *Tracing        `yaml:"tracing"`
+	Log     *Log            `yaml:"log"`
+	Debug   Debug           `yaml:"debug"`
+
+	GRPC GRPCConfig `yaml:"grpc"`
+
+	TokenManager *TokenManager `yaml:"token_manager"`
+	Reva         *shared.Reva  `yaml:"reva"`
+
+	SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token" env:"AUTH_APP_SKIP_USER_GROUPS_IN_TOKEN" desc:"Disables the encoding of the user's group memberships in the access token. This reduces the token size, especially when users are members of a large number of groups." introductionVersion:"%%NEXT%%"`
+
+	MachineAuthAPIKey string `yaml:"machine_auth_api_key" env:"OCIS_MACHINE_AUTH_API_KEY;AUTH_APP_MACHINE_AUTH_API_KEY" desc:"The machine auth API key used to validate internal requests necessary to access resources from other services." introductionVersion:"%%NEXT%%"`
+
+	Supervised bool            `yaml:"-"`
+	Context    context.Context `yaml:"-"`
+}
+
+// Log defines the loging configuration
+type Log struct {
+	Level  string `yaml:"level" env:"OCIS_LOG_LEVEL;AUTH_APP_LOG_LEVEL" desc:"The log level. Valid values are: 'panic', 'fatal', 'error', 'warn', 'info', 'debug', 'trace'." introductionVersion:"%%NEXT%%"`
+	Pretty bool   `yaml:"pretty" env:"OCIS_LOG_PRETTY;AUTH_APP_LOG_PRETTY" desc:"Activates pretty log output." introductionVersion:"%%NEXT%%"`
+	Color  bool   `yaml:"color" env:"OCIS_LOG_COLOR;AUTH_APP_LOG_COLOR" desc:"Activates colorized log output." introductionVersion:"%%NEXT%%"`
+	File   string `yaml:"file" env:"OCIS_LOG_FILE;AUTH_APP_LOG_FILE" desc:"The path to the log file. Activates logging to this file if set." introductionVersion:"%%NEXT%%"`
+}
+
+// Service defines the service configuration
+type Service struct {
+	Name string `yaml:"-"`
+}
+
+// Debug defines the debug configuration
+type Debug struct {
+	Addr   string `yaml:"addr" env:"AUTH_APP_DEBUG_ADDR" desc:"Bind address of the debug server, where metrics, health, config and debug endpoints will be exposed." introductionVersion:"%%NEXT%%"`
+	Token  string `yaml:"token" env:"AUTH_APP_DEBUG_TOKEN" desc:"Token to secure the metrics endpoint." introductionVersion:"%%NEXT%%"`
+	Pprof  bool   `yaml:"pprof" env:"AUTH_APP_DEBUG_PPROF" desc:"Enables pprof, which can be used for profiling." introductionVersion:"%%NEXT%%"`
+	Zpages bool   `yaml:"zpages" env:"AUTH_APP_DEBUG_ZPAGES" desc:"Enables zpages, which can  be used for collecting and viewing traces in-memory." introductionVersion:"%%NEXT%%"`
+}
+
+// GRPCConfig defines the GRPC configuration
+type GRPCConfig struct {
+	Addr      string                 `yaml:"addr" env:"AUTH_APP_GRPC_ADDR" desc:"The bind address of the GRPC service." introductionVersion:"%%NEXT%%"`
+	TLS       *shared.GRPCServiceTLS `yaml:"tls"`
+	Namespace string                 `yaml:"-"`
+	Protocol  string                 `yaml:"protocol" env:"AUTH_APP_GRPC_PROTOCOL" desc:"The transport protocol of the GRPC service." introductionVersion:"%%NEXT%%"`
+}
diff --git a/services/auth-app/pkg/config/defaults/defaultconfig.go b/services/auth-app/pkg/config/defaults/defaultconfig.go
new file mode 100644
index 00000000000..19953bcef28
--- /dev/null
+++ b/services/auth-app/pkg/config/defaults/defaultconfig.go
@@ -0,0 +1,87 @@
+package defaults
+
+import (
+	"github.com/owncloud/ocis/v2/ocis-pkg/shared"
+	"github.com/owncloud/ocis/v2/ocis-pkg/structs"
+	"github.com/owncloud/ocis/v2/services/auth-app/pkg/config"
+)
+
+// FullDefaultConfig returns a fully initialized default configuration
+func FullDefaultConfig() *config.Config {
+	cfg := DefaultConfig()
+	EnsureDefaults(cfg)
+	Sanitize(cfg)
+	return cfg
+}
+
+// DefaultConfig returns a basic default configuration
+func DefaultConfig() *config.Config {
+	return &config.Config{
+		Debug: config.Debug{
+			Addr:   "127.0.0.1:9245",
+			Token:  "",
+			Pprof:  false,
+			Zpages: false,
+		},
+		GRPC: config.GRPCConfig{
+			Addr:      "127.0.0.1:9246",
+			Namespace: "com.owncloud.api",
+			Protocol:  "tcp",
+		},
+		Service: config.Service{
+			Name: "auth-app",
+		},
+		Reva: shared.DefaultRevaConfig(),
+	}
+}
+
+// EnsureDefaults adds default values to the configuration if they are not set yet
+func EnsureDefaults(cfg *config.Config) {
+	// provide with defaults for shared logging, since we need a valid destination address for "envdecode".
+	if cfg.Log == nil && cfg.Commons != nil && cfg.Commons.Log != nil {
+		cfg.Log = &config.Log{
+			Level:  cfg.Commons.Log.Level,
+			Pretty: cfg.Commons.Log.Pretty,
+			Color:  cfg.Commons.Log.Color,
+			File:   cfg.Commons.Log.File,
+		}
+	} else if cfg.Log == nil {
+		cfg.Log = &config.Log{}
+	}
+	// provide with defaults for shared tracing, since we need a valid destination address for "envdecode".
+	if cfg.Tracing == nil && cfg.Commons != nil && cfg.Commons.Tracing != nil {
+		cfg.Tracing = &config.Tracing{
+			Enabled:   cfg.Commons.Tracing.Enabled,
+			Type:      cfg.Commons.Tracing.Type,
+			Endpoint:  cfg.Commons.Tracing.Endpoint,
+			Collector: cfg.Commons.Tracing.Collector,
+		}
+	} else if cfg.Tracing == nil {
+		cfg.Tracing = &config.Tracing{}
+	}
+
+	if cfg.Reva == nil && cfg.Commons != nil {
+		cfg.Reva = structs.CopyOrZeroValue(cfg.Commons.Reva)
+	}
+
+	if cfg.MachineAuthAPIKey == "" && cfg.Commons != nil && cfg.Commons.MachineAuthAPIKey != "" {
+		cfg.MachineAuthAPIKey = cfg.Commons.MachineAuthAPIKey
+	}
+
+	if cfg.TokenManager == nil && cfg.Commons != nil && cfg.Commons.TokenManager != nil {
+		cfg.TokenManager = &config.TokenManager{
+			JWTSecret: cfg.Commons.TokenManager.JWTSecret,
+		}
+	} else if cfg.TokenManager == nil {
+		cfg.TokenManager = &config.TokenManager{}
+	}
+
+	if cfg.GRPC.TLS == nil && cfg.Commons != nil {
+		cfg.GRPC.TLS = structs.CopyOrZeroValue(cfg.Commons.GRPCServiceTLS)
+	}
+}
+
+// Sanitize sanitized the configuration
+func Sanitize(_ *config.Config) {
+	// nothing to sanitize here atm
+}
diff --git a/services/auth-app/pkg/config/parser/parse.go b/services/auth-app/pkg/config/parser/parse.go
new file mode 100644
index 00000000000..d9c71d92752
--- /dev/null
+++ b/services/auth-app/pkg/config/parser/parse.go
@@ -0,0 +1,42 @@
+package parser
+
+import (
+	"errors"
+
+	ociscfg "github.com/owncloud/ocis/v2/ocis-pkg/config"
+	"github.com/owncloud/ocis/v2/ocis-pkg/shared"
+	"github.com/owncloud/ocis/v2/services/auth-app/pkg/config"
+	"github.com/owncloud/ocis/v2/services/auth-app/pkg/config/defaults"
+
+	"github.com/owncloud/ocis/v2/ocis-pkg/config/envdecode"
+)
+
+// ParseConfig loads configuration from known paths.
+func ParseConfig(cfg *config.Config) error {
+	err := ociscfg.BindSourcesToStructs(cfg.Service.Name, cfg)
+	if err != nil {
+		return err
+	}
+
+	defaults.EnsureDefaults(cfg)
+
+	// load all env variables relevant to the config in the current context.
+	if err := envdecode.Decode(cfg); err != nil {
+		// no environment variable set for this config is an expected "error"
+		if !errors.Is(err, envdecode.ErrNoTargetFieldsAreSet) {
+			return err
+		}
+	}
+
+	defaults.Sanitize(cfg)
+
+	return Validate(cfg)
+}
+
+func Validate(cfg *config.Config) error {
+	if cfg.TokenManager.JWTSecret == "" {
+		return shared.MissingJWTTokenError(cfg.Service.Name)
+	}
+
+	return nil
+}
diff --git a/services/auth-app/pkg/config/reva.go b/services/auth-app/pkg/config/reva.go
new file mode 100644
index 00000000000..45c0e16376e
--- /dev/null
+++ b/services/auth-app/pkg/config/reva.go
@@ -0,0 +1,6 @@
+package config
+
+// TokenManager is the config for using the reva token manager
+type TokenManager struct {
+	JWTSecret string `yaml:"jwt_secret" env:"OCIS_JWT_SECRET;AUTH_APP_JWT_SECRET" desc:"The secret to mint and validate jwt tokens." introductionVersion:"%%NEXT%%"`
+}
diff --git a/services/auth-app/pkg/config/tracing.go b/services/auth-app/pkg/config/tracing.go
new file mode 100644
index 00000000000..3a39cae679a
--- /dev/null
+++ b/services/auth-app/pkg/config/tracing.go
@@ -0,0 +1,21 @@
+package config
+
+import "github.com/owncloud/ocis/v2/ocis-pkg/tracing"
+
+// Tracing defines the tracing configuration.
+type Tracing struct {
+	Enabled   bool   `yaml:"enabled" env:"OCIS_TRACING_ENABLED;AUTH_APP_TRACING_ENABLED" desc:"Activates tracing." introductionVersion:"%%NEXT%%"`
+	Type      string `yaml:"type" env:"OCIS_TRACING_TYPE;AUTH_APP_TRACING_TYPE" desc:"The type of tracing. Defaults to '', which is the same as 'jaeger'. Allowed tracing types are 'jaeger' and '' as of now." introductionVersion:"%%NEXT%%"`
+	Endpoint  string `yaml:"endpoint" env:"OCIS_TRACING_ENDPOINT;AUTH_APP_TRACING_ENDPOINT" desc:"The endpoint of the tracing agent." introductionVersion:"%%NEXT%%"`
+	Collector string `yaml:"collector" env:"OCIS_TRACING_COLLECTOR;AUTH_APP_TRACING_COLLECTOR" desc:"The HTTP endpoint for sending spans directly to a collector, i.e. http://jaeger-collector:14268/api/traces. Only used if the tracing endpoint is unset." introductionVersion:"%%NEXT%%"`
+}
+
+// Convert Tracing to the tracing package's Config struct.
+func (t Tracing) Convert() tracing.Config {
+	return tracing.Config{
+		Enabled:   t.Enabled,
+		Type:      t.Type,
+		Endpoint:  t.Endpoint,
+		Collector: t.Collector,
+	}
+}
diff --git a/services/auth-app/pkg/logging/logging.go b/services/auth-app/pkg/logging/logging.go
new file mode 100644
index 00000000000..b01a179327f
--- /dev/null
+++ b/services/auth-app/pkg/logging/logging.go
@@ -0,0 +1,17 @@
+package logging
+
+import (
+	"github.com/owncloud/ocis/v2/ocis-pkg/log"
+	"github.com/owncloud/ocis/v2/services/auth-app/pkg/config"
+)
+
+// Configure initializes a service-specific logger instance.
+func Configure(name string, cfg *config.Log) log.Logger {
+	return log.NewLogger(
+		log.Name(name),
+		log.Level(cfg.Level),
+		log.Pretty(cfg.Pretty),
+		log.Color(cfg.Color),
+		log.File(cfg.File),
+	)
+}
diff --git a/services/auth-app/pkg/revaconfig/config.go b/services/auth-app/pkg/revaconfig/config.go
new file mode 100644
index 00000000000..da75275314d
--- /dev/null
+++ b/services/auth-app/pkg/revaconfig/config.go
@@ -0,0 +1,56 @@
+package revaconfig
+
+import (
+	"path/filepath"
+
+	"github.com/owncloud/ocis/v2/ocis-pkg/config/defaults"
+	"github.com/owncloud/ocis/v2/services/auth-app/pkg/config"
+)
+
+// AuthAppConfigFromStruct will adapt an oCIS config struct into a reva mapstructure to start a reva service.
+func AuthAppConfigFromStruct(cfg *config.Config) map[string]interface{} {
+	appAuthJSON := filepath.Join(defaults.BaseDataPath(), "appauth.json")
+
+	rcfg := map[string]interface{}{
+		"shared": map[string]interface{}{
+			"jwt_secret":                cfg.TokenManager.JWTSecret,
+			"gatewaysvc":                cfg.Reva.Address,
+			"skip_user_groups_in_token": cfg.SkipUserGroupsInToken,
+			"grpc_client_options":       cfg.Reva.GetGRPCClientConfig(),
+		},
+		"grpc": map[string]interface{}{
+			"network": cfg.GRPC.Protocol,
+			"address": cfg.GRPC.Addr,
+			"tls_settings": map[string]interface{}{
+				"enabled":     cfg.GRPC.TLS.Enabled,
+				"certificate": cfg.GRPC.TLS.Cert,
+				"key":         cfg.GRPC.TLS.Key,
+			},
+			"services": map[string]interface{}{
+				"authprovider": map[string]interface{}{
+					"auth_manager": "appauth",
+					"auth_managers": map[string]interface{}{
+						"appauth": map[string]interface{}{
+							"gateway_addr": cfg.Reva.Address,
+						},
+					},
+				},
+				"applicationauth": map[string]interface{}{
+					"driver": "json",
+					"drivers": map[string]interface{}{
+						"json": map[string]interface{}{
+							"file": appAuthJSON,
+						},
+					},
+				},
+			},
+			"interceptors": map[string]interface{}{
+				"prometheus": map[string]interface{}{
+					"namespace": "ocis",
+					"subsystem": "auth_app",
+				},
+			},
+		},
+	}
+	return rcfg
+}
diff --git a/services/auth-app/pkg/server/debug/option.go b/services/auth-app/pkg/server/debug/option.go
new file mode 100644
index 00000000000..81f51526781
--- /dev/null
+++ b/services/auth-app/pkg/server/debug/option.go
@@ -0,0 +1,50 @@
+package debug
+
+import (
+	"context"
+
+	"github.com/owncloud/ocis/v2/ocis-pkg/log"
+	"github.com/owncloud/ocis/v2/services/auth-app/pkg/config"
+)
+
+// Option defines a single option function.
+type Option func(o *Options)
+
+// Options defines the available options for this package.
+type Options struct {
+	Logger  log.Logger
+	Context context.Context
+	Config  *config.Config
+}
+
+// newOptions initializes the available default options.
+func newOptions(opts ...Option) Options {
+	opt := Options{}
+
+	for _, o := range opts {
+		o(&opt)
+	}
+
+	return opt
+}
+
+// Logger provides a function to set the logger option.
+func Logger(val log.Logger) Option {
+	return func(o *Options) {
+		o.Logger = val
+	}
+}
+
+// Context provides a function to set the context option.
+func Context(val context.Context) Option {
+	return func(o *Options) {
+		o.Context = val
+	}
+}
+
+// Config provides a function to set the config option.
+func Config(val *config.Config) Option {
+	return func(o *Options) {
+		o.Config = val
+	}
+}
diff --git a/services/auth-app/pkg/server/debug/server.go b/services/auth-app/pkg/server/debug/server.go
new file mode 100644
index 00000000000..b87b7e52f11
--- /dev/null
+++ b/services/auth-app/pkg/server/debug/server.go
@@ -0,0 +1,63 @@
+package debug
+
+import (
+	"io"
+	"net/http"
+
+	"github.com/owncloud/ocis/v2/ocis-pkg/service/debug"
+	"github.com/owncloud/ocis/v2/ocis-pkg/version"
+	"github.com/owncloud/ocis/v2/services/auth-app/pkg/config"
+)
+
+// Server initializes the debug service and server.
+func Server(opts ...Option) (*http.Server, error) {
+	options := newOptions(opts...)
+
+	return debug.NewService(
+		debug.Logger(options.Logger),
+		debug.Name(options.Config.Service.Name),
+		debug.Version(version.GetString()),
+		debug.Address(options.Config.Debug.Addr),
+		debug.Token(options.Config.Debug.Token),
+		debug.Pprof(options.Config.Debug.Pprof),
+		debug.Zpages(options.Config.Debug.Zpages),
+		debug.Health(health(options.Config)),
+		debug.Ready(ready(options.Config)),
+		//debug.CorsAllowedOrigins(options.Config.HTTP.CORS.AllowedOrigins),
+		//debug.CorsAllowedMethods(options.Config.HTTP.CORS.AllowedMethods),
+		//debug.CorsAllowedHeaders(options.Config.HTTP.CORS.AllowedHeaders),
+		//debug.CorsAllowCredentials(options.Config.HTTP.CORS.AllowCredentials),
+	), nil
+}
+
+// health implements the health check.
+func health(_ *config.Config) func(http.ResponseWriter, *http.Request) {
+	return func(w http.ResponseWriter, _ *http.Request) {
+		w.Header().Set("Content-Type", "text/plain")
+		w.WriteHeader(http.StatusOK)
+
+		// TODO: check if services are up and running
+
+		_, err := io.WriteString(w, http.StatusText(http.StatusOK))
+		// io.WriteString should not fail but if it does, we want to know.
+		if err != nil {
+			panic(err)
+		}
+	}
+}
+
+// ready implements the ready check.
+func ready(_ *config.Config) func(http.ResponseWriter, *http.Request) {
+	return func(w http.ResponseWriter, _ *http.Request) {
+		w.Header().Set("Content-Type", "text/plain")
+		w.WriteHeader(http.StatusOK)
+
+		// TODO: check if services are up and running
+
+		_, err := io.WriteString(w, http.StatusText(http.StatusOK))
+		// io.WriteString should not fail but if it does, we want to know.
+		if err != nil {
+			panic(err)
+		}
+	}
+}
diff --git a/services/auth-basic/README.md b/services/auth-basic/README.md
index c5afe10633c..5b90fdcb8c2 100644
--- a/services/auth-basic/README.md
+++ b/services/auth-basic/README.md
@@ -9,6 +9,7 @@ To enable `auth-basic`, you first must set `PROXY_ENABLE_BASIC_AUTH` to `true`.
 ## The `auth` Service Family
 
 ocis uses serveral authentication services for different use cases. All services that start with `auth-` are part of the authentication service family. Each member authenticates requests with different scopes. As of now, these services exist:
+  -   `auth-app` handles authentication of external 3rd party apps
   -   `auth-basic` handles basic authentication
   -   `auth-bearer` handles oidc authentication
   -   `auth-machine` handles interservice authentication when a user is impersonated
diff --git a/services/auth-bearer/README.md b/services/auth-bearer/README.md
index 024f6caa138..152359c8859 100644
--- a/services/auth-bearer/README.md
+++ b/services/auth-bearer/README.md
@@ -5,6 +5,7 @@ The oCIS Auth Bearer service communicates with the configured OpenID Connect ide
 ## The `auth` Service Family
 
 ocis uses serveral authentication services for different use cases. All services that start with `auth-` are part of the authentication service family. Each member authenticates requests with different scopes. As of now, these services exist:
+  -   `auth-app` handles authentication of external 3rd party apps
   -   `auth-basic` handles basic authentication
   -   `auth-bearer` handles oidc authentication
   -   `auth-machine` handles interservice authentication when a user is impersonated
diff --git a/services/auth-machine/README.md b/services/auth-machine/README.md
index b06664054d5..31222489e25 100644
--- a/services/auth-machine/README.md
+++ b/services/auth-machine/README.md
@@ -3,6 +3,7 @@
 The oCIS Auth Machine is used for interservice communication when using user impersonation.
 
 ocis uses serveral authentication services for different use cases. All services that start with `auth-` are part of the authentication service family. Each member authenticates requests with different scopes. As of now, these services exist:
+  -   `auth-app` handles authentication of external 3rd party apps
   -   `auth-basic` handles basic authentication
   -   `auth-bearer` handles oidc authentication
   -   `auth-machine` handles interservice authentication when a user is impersonated
diff --git a/services/auth-service/README.md b/services/auth-service/README.md
index b34057d14c9..b4b94633e1d 100644
--- a/services/auth-service/README.md
+++ b/services/auth-service/README.md
@@ -5,6 +5,7 @@ The ocis Auth Service is used to authenticate service accounts. Compared to norm
 ## The `auth` Service Family
 
 ocis uses serveral authentication services for different use cases. All services that start with `auth-` are part of the authentication service family. Each member authenticates requests with different scopes. As of now, these services exist:
+  -   `auth-app` handles authentication of external 3rd party apps
   -   `auth-basic` handles basic authentication
   -   `auth-bearer` handles oidc authentication
   -   `auth-machine` handles interservice authentication when a user is impersonated
diff --git a/services/gateway/pkg/config/config.go b/services/gateway/pkg/config/config.go
index 73b932de740..1d683de4b1e 100644
--- a/services/gateway/pkg/config/config.go
+++ b/services/gateway/pkg/config/config.go
@@ -35,6 +35,7 @@ type Config struct {
 	GroupsEndpoint            string `yaml:"-"`
 	PermissionsEndpoint       string `yaml:"-"`
 	SharingEndpoint           string `yaml:"-"`
+	AuthAppEndpoint           string `yaml:"-"`
 	AuthBasicEndpoint         string `yaml:"-"`
 	AuthBearerEndpoint        string `yaml:"-"`
 	AuthMachineEndpoint       string `yaml:"-"`
diff --git a/services/gateway/pkg/config/defaults/defaultconfig.go b/services/gateway/pkg/config/defaults/defaultconfig.go
index b8bea1a4bcc..5d003bb3790 100644
--- a/services/gateway/pkg/config/defaults/defaultconfig.go
+++ b/services/gateway/pkg/config/defaults/defaultconfig.go
@@ -52,6 +52,7 @@ func DefaultConfig() *config.Config {
 		FrontendPublicURL: "https://localhost:9200",
 
 		AppRegistryEndpoint:       "com.owncloud.api.app-registry",
+		AuthAppEndpoint:           "com.owncloud.api.auth-app",
 		AuthBasicEndpoint:         "com.owncloud.api.auth-basic",
 		AuthMachineEndpoint:       "com.owncloud.api.auth-machine",
 		AuthServiceEndpoint:       "com.owncloud.api.auth-service",
diff --git a/services/gateway/pkg/revaconfig/config.go b/services/gateway/pkg/revaconfig/config.go
index f4b5e841efc..035ae05bf6b 100644
--- a/services/gateway/pkg/revaconfig/config.go
+++ b/services/gateway/pkg/revaconfig/config.go
@@ -37,6 +37,7 @@ func GatewayConfigFromStruct(cfg *config.Config, logger log.Logger) map[string]i
 			// TODO build services dynamically
 			"services": map[string]interface{}{
 				"gateway": map[string]interface{}{
+					"applicationauthsvc": cfg.AuthAppEndpoint,
 					// registries are located on the gateway
 					"authregistrysvc":    cfg.Reva.Address,
 					"storageregistrysvc": cfg.Reva.Address,
@@ -89,6 +90,7 @@ func GatewayConfigFromStruct(cfg *config.Config, logger log.Logger) map[string]i
 					"drivers": map[string]interface{}{
 						"static": map[string]interface{}{
 							"rules": map[string]interface{}{
+								"appauth":         cfg.AuthAppEndpoint,
 								"basic":           cfg.AuthBasicEndpoint,
 								"machine":         cfg.AuthMachineEndpoint,
 								"publicshares":    cfg.StoragePublicLinkEndpoint,
diff --git a/services/proxy/pkg/command/server.go b/services/proxy/pkg/command/server.go
index dd26ce29ecc..beb83ae506f 100644
--- a/services/proxy/pkg/command/server.go
+++ b/services/proxy/pkg/command/server.go
@@ -194,6 +194,7 @@ func Server(cfg *config.Config) *cli.Command {
 
 			{
 				middlewares := loadMiddlewares(logger, cfg, userInfoCache, signingKeyStore, traceProvider, *m, userProvider, gatewaySelector)
+
 				server, err := proxyHTTP.Server(
 					proxyHTTP.Handler(lh.Handler()),
 					proxyHTTP.Logger(logger),
@@ -293,6 +294,12 @@ func loadMiddlewares(logger log.Logger, cfg *config.Config,
 		})
 	}
 
+	if cfg.AuthMiddleware.AllowAppAuth {
+		authenticators = append(authenticators, middleware.AppAuthAuthenticator{
+			Logger:              logger,
+			RevaGatewaySelector: gatewaySelector,
+		})
+	}
 	authenticators = append(authenticators, middleware.NewOIDCAuthenticator(
 		middleware.Logger(logger),
 		middleware.UserInfoCache(userInfoCache),
diff --git a/services/proxy/pkg/config/config.go b/services/proxy/pkg/config/config.go
index 9c067c45d7a..4f9c67a6e7f 100644
--- a/services/proxy/pkg/config/config.go
+++ b/services/proxy/pkg/config/config.go
@@ -91,6 +91,7 @@ var (
 // AuthMiddleware configures the proxy http auth middleware.
 type AuthMiddleware struct {
 	CredentialsByUserAgent map[string]string `yaml:"credentials_by_user_agent"`
+	AllowAppAuth           bool              `yaml:"allow_app_auth" env:"PROXY_ENABLE_APP_AUTH" desc:"Allow app authentication. This can be used to authenticate 3rd party applications. Note that auth-app service must be running for this feature to work." introductionVersion:"%%NEXT%%"`
 }
 
 // PoliciesMiddleware configures the proxy's policies middleware.
diff --git a/services/proxy/pkg/middleware/app_auth.go b/services/proxy/pkg/middleware/app_auth.go
new file mode 100644
index 00000000000..b828af4b48e
--- /dev/null
+++ b/services/proxy/pkg/middleware/app_auth.go
@@ -0,0 +1,53 @@
+package middleware
+
+import (
+	"net/http"
+
+	gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
+	cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
+	revactx "github.com/cs3org/reva/v2/pkg/ctx"
+	"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
+	"github.com/owncloud/ocis/v2/ocis-pkg/log"
+)
+
+// AppAuthAuthenticator defines the app auth authenticator
+type AppAuthAuthenticator struct {
+	Logger              log.Logger
+	RevaGatewaySelector pool.Selectable[gateway.GatewayAPIClient]
+}
+
+// Authenticate implements the authenticator interface to authenticate requests via app auth.
+func (m AppAuthAuthenticator) Authenticate(r *http.Request) (*http.Request, bool) {
+	if isPublicPath(r.URL.Path) {
+		// The authentication of public path requests is handled by another authenticator.
+		// Since we can't guarantee the order of execution of the authenticators, we better
+		// implement an early return here for paths we can't authenticate in this authenticator.
+		return nil, false
+	}
+
+	username, password, ok := r.BasicAuth()
+	if !ok {
+		return nil, false
+	}
+	next, err := m.RevaGatewaySelector.Next()
+	if err != nil {
+		return nil, false
+	}
+
+	authenticateResponse, err := next.Authenticate(r.Context(), &gateway.AuthenticateRequest{
+		Type:         "appauth",
+		ClientId:     username,
+		ClientSecret: password,
+	})
+	if err != nil {
+		return nil, false
+	}
+	if authenticateResponse.GetStatus().GetCode() != cs3rpc.Code_CODE_OK {
+		// TODO: log???
+		return nil, false
+	}
+
+	r.Header.Set(revactx.TokenHeader, authenticateResponse.GetToken())
+
+	return r, true
+}


Test patch:
diff --git a/services/proxy/pkg/middleware/app_auth_test.go b/services/proxy/pkg/middleware/app_auth_test.go
new file mode 100644
index 00000000000..79d857019f8
--- /dev/null
+++ b/services/proxy/pkg/middleware/app_auth_test.go
@@ -0,0 +1,69 @@
+package middleware
+
+import (
+	"net/http"
+	"net/http/httptest"
+
+	gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
+	rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
+	"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
+	"google.golang.org/grpc"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+	"github.com/owncloud/ocis/v2/ocis-pkg/log"
+)
+
+var _ = Describe("Authenticating requests", Label("AppAuthAuthenticator"), func() {
+	var authenticator Authenticator
+	BeforeEach(func() {
+		pool.RemoveSelector("GatewaySelector" + "com.owncloud.api.gateway")
+		authenticator = AppAuthAuthenticator{
+			Logger: log.NewLogger(),
+			RevaGatewaySelector: pool.GetSelector[gateway.GatewayAPIClient](
+				"GatewaySelector",
+				"com.owncloud.api.gateway",
+				func(cc grpc.ClientConnInterface) gateway.GatewayAPIClient {
+					return mockGatewayClient{
+						AuthenticateFunc: func(authType, clientID, clientSecret string) (string, rpcv1beta1.Code) {
+							if authType != "appauth" {
+								return "", rpcv1beta1.Code_CODE_NOT_FOUND
+							}
+
+							if clientID == "test-user" && clientSecret == "AppPassword" {
+								return "reva-token", rpcv1beta1.Code_CODE_OK
+							}
+
+							return "", rpcv1beta1.Code_CODE_NOT_FOUND
+						},
+					}
+				},
+			),
+		}
+	})
+
+	When("the request contains correct data", func() {
+		It("should successfully authenticate", func() {
+			req := httptest.NewRequest(http.MethodGet, "http://example.com/example/path", http.NoBody)
+			req.SetBasicAuth("test-user", "AppPassword")
+
+			req2, valid := authenticator.Authenticate(req)
+
+			Expect(valid).To(Equal(true))
+			Expect(req2).ToNot(BeNil())
+			Expect(req2.Header.Get("x-access-token")).To(Equal("reva-token"))
+		})
+	})
+
+	When("the request contains incorrect data", func() {
+		It("should not successfully authenticate", func() {
+			req := httptest.NewRequest(http.MethodGet, "http://example.com/example/path", http.NoBody)
+			req.SetBasicAuth("test-user", "WrongAppPassword")
+
+			req2, valid := authenticator.Authenticate(req)
+
+			Expect(valid).To(Equal(false))
+			Expect(req2).To(BeNil())
+		})
+	})
+})
diff --git a/services/proxy/pkg/middleware/public_share_auth_test.go b/services/proxy/pkg/middleware/public_share_auth_test.go
index 4c07ffe0615..9f748c4ed19 100644
--- a/services/proxy/pkg/middleware/public_share_auth_test.go
+++ b/services/proxy/pkg/middleware/public_share_auth_test.go
@@ -18,6 +18,7 @@ import (
 var _ = Describe("Authenticating requests", Label("PublicShareAuthenticator"), func() {
 	var authenticator Authenticator
 	BeforeEach(func() {
+		pool.RemoveSelector("GatewaySelector" + "com.owncloud.api.gateway")
 		authenticator = PublicShareAuthenticator{
 			Logger: log.NewLogger(),
 			RevaGatewaySelector: pool.GetSelector[gateway.GatewayAPIClient](
diff --git a/tests/TestHelpers/GraphHelper.php b/tests/TestHelpers/GraphHelper.php
index 7e5c77b8d1f..39ccf8de77b 100644
--- a/tests/TestHelpers/GraphHelper.php
+++ b/tests/TestHelpers/GraphHelper.php
@@ -2240,57 +2240,4 @@ class GraphHelper {
 			$body
 		);
 	}
-
-	/**
-	 * @param string $baseUrl
-	 * @param string $xRequestId
-	 * @param string $user
-	 * @param string $password
-	 *
-	 * @return ResponseInterface
-	 * @throws GuzzleException
-	 */
-	public static function getPermissionsRoleDefinitions(
-		string $baseUrl,
-		string $xRequestId,
-		string $user,
-		string $password,
-	): ResponseInterface {
-		$url = self::getBetaFullUrl($baseUrl, "roleManagement/permissions/roleDefinitions");
-		return HttpRequestHelper::get(
-			$url,
-			$xRequestId,
-			$user,
-			$password,
-			self::getRequestHeaders()
-		);
-	}
-
-	/**
-	 * @param string $permissionRole
-	 * @param string $baseUrl
-	 * @param string $xRequestId
-	 * @param string $user
-	 * @param string $password
-	 *
-	 * @return ResponseInterface
-	 * @throws \Exception|GuzzleException
-	 */
-	public static function getPermissionRoleDefinition(
-		string $permissionRole,
-		string $baseUrl,
-		string $xRequestId,
-		string $user,
-		string $password,
-	): ResponseInterface {
-		$roleId = self::getPermissionsRoleIdByName($permissionRole);
-		$url = self::getBetaFullUrl($baseUrl, "roleManagement/permissions/roleDefinitions/{$roleId}");
-		return HttpRequestHelper::get(
-			$url,
-			$xRequestId,
-			$user,
-			$password,
-			self::getRequestHeaders()
-		);
-	}
 }
diff --git a/tests/acceptance/features/apiGraph/roleManagementEndpoint.feature b/tests/acceptance/features/apiGraph/roleManagementEndpoint.feature
deleted file mode 100644
index 2071ee413b6..00000000000
--- a/tests/acceptance/features/apiGraph/roleManagementEndpoint.feature
+++ /dev/null
@@ -1,603 +0,0 @@
-Feature: permissions role definitions
-  As a user
-  I want to get the role management endpoints
-  So that I can find out if those endpoints are working correctly or not
-
-  Background:
-    Given user "Alice" has been created with default attributes and without skeleton files
-
-
-  Scenario: get a list of permissions role definitions
-    When user "Alice" gets a list of permissions role definitions using the Graph API
-    Then the HTTP status code should be "200"
-    And the JSON data of the response should match
-      """
-      {
-        "type": "array",
-        "maxItems": 8,
-        "minItems": 8,
-        "uniqueItems": true,
-        "items": {
-          "oneOf": [
-            {
-              "type": "object",
-              "required": [
-                "@libre.graph.weight",
-                "description",
-                "displayName",
-                "id",
-                "rolePermissions"
-              ],
-              "properties": {
-                "@libre.graph.weight": {
-                  "const": 0
-                },
-                "description": {
-                  "const": "View and download."
-                },
-                "displayName": {
-                  "const": "Can view"
-                },
-                "id": {
-                  "const": "b1e2218d-eef8-4d4c-b82d-0f1a1b48f3b5"
-                },
-                "rolePermissions": {
-                  "type": "array",
-                  "maxItems": 2,
-                  "minItems": 2,
-                  "uniqueItems": true,
-                  "items": {
-                    "oneOf": [
-                      {
-                        "type": "object",
-                        "required": [
-                          "allowedResourceActions",
-                          "condition"
-                        ],
-                        "properties": {
-                          "allowedResourceActions": {
-                            "const": [
-                              "libre.graph/driveItem/path/read",
-                              "libre.graph/driveItem/quota/read",
-                              "libre.graph/driveItem/content/read",
-                              "libre.graph/driveItem/children/read",
-                              "libre.graph/driveItem/deleted/read",
-                              "libre.graph/driveItem/basic/read"
-                            ]
-                          },
-                          "condition": {
-                            "const": "exists @Resource.File"
-                          }
-                        }
-                      },
-                      {
-                        "type": "object",
-                        "required": [
-                          "allowedResourceActions",
-                          "condition"
-                        ],
-                        "properties": {
-                          "allowedResourceActions": {
-                            "const": [
-                              "libre.graph/driveItem/path/read",
-                              "libre.graph/driveItem/quota/read",
-                              "libre.graph/driveItem/content/read",
-                              "libre.graph/driveItem/children/read",
-                              "libre.graph/driveItem/deleted/read",
-                              "libre.graph/driveItem/basic/read"
-                            ]
-                          },
-                          "condition": {
-                            "const": "exists @Resource.Folder"
-                          }
-                        }
-                      }
-                    ]
-                  }
-                }
-              }
-            },
-            {
-              "type": "object",
-              "required": [
-                "@libre.graph.weight",
-                "description",
-                "displayName",
-                "id",
-                "rolePermissions"
-              ],
-              "properties": {
-                "@libre.graph.weight": {
-                  "const": 0
-                },
-                "description": {
-                  "const": "View and download."
-                },
-                "displayName": {
-                  "const": "Can view"
-                },
-                "id": {
-                  "const": "a8d5fe5e-96e3-418d-825b-534dbdf22b99"
-                },
-                "rolePermissions": {
-                  "type": "array",
-                  "maxItems": 1,
-                  "minItems": 1,
-                  "uniqueItems": true,
-                  "items": {
-                    "type": "object",
-                    "required": [
-                      "allowedResourceActions",
-                      "condition"
-                    ],
-                    "properties": {
-                      "allowedResourceActions": {
-                        "const": [
-                          "libre.graph/driveItem/path/read",
-                          "libre.graph/driveItem/quota/read",
-                          "libre.graph/driveItem/content/read",
-                          "libre.graph/driveItem/permissions/read",
-                          "libre.graph/driveItem/children/read",
-                          "libre.graph/driveItem/deleted/read",
-                          "libre.graph/driveItem/basic/read"
-                        ]
-                      },
-                      "condition": {
-                        "const": "exists @Resource.Root"
-                      }
-                    }
-                  }
-                }
-              }
-            },
-            {
-              "type": "object",
-              "required": [
-                "@libre.graph.weight",
-                "description",
-                "displayName",
-                "id",
-                "rolePermissions"
-              ],
-              "properties": {
-                "@libre.graph.weight": {
-                  "const": 0
-                },
-                "description": {
-                  "const": "View, download, upload, edit, add and delete."
-                },
-                "displayName": {
-                  "const": "Can edit"
-                },
-                "id": {
-                  "const": "fb6c3e19-e378-47e5-b277-9732f9de6e21"
-                },
-                "rolePermissions": {
-                  "type": "array",
-                  "maxItems": 1,
-                  "minItems": 1,
-                  "uniqueItems": true,
-                  "items": {
-                    "type": "object",
-                    "required": [
-                      "allowedResourceActions",
-                      "condition"
-                    ],
-                    "properties": {
-                      "allowedResourceActions": {
-                        "const": [
-                          "libre.graph/driveItem/children/create",
-                          "libre.graph/driveItem/standard/delete",
-                          "libre.graph/driveItem/path/read",
-                          "libre.graph/driveItem/quota/read",
-                          "libre.graph/driveItem/content/read",
-                          "libre.graph/driveItem/upload/create",
-                          "libre.graph/driveItem/children/read",
-                          "libre.graph/driveItem/deleted/read",
-                          "libre.graph/driveItem/path/update",
-                          "libre.graph/driveItem/deleted/update",
-                          "libre.graph/driveItem/basic/read"
-                        ]
-                      },
-                      "condition": {
-                        "const": "exists @Resource.Folder"
-                      }
-                    }
-                  }
-                }
-              }
-            },
-            {
-              "type": "object",
-              "required": [
-                "@libre.graph.weight",
-                "description",
-                "displayName",
-                "id",
-                "rolePermissions"
-              ],
-              "properties": {
-                "@libre.graph.weight": {
-                  "const": 0
-                },
-                "description": {
-                  "const": "View, download, upload, edit, add and delete."
-                },
-                "displayName": {
-                  "const": "Can edit"
-                },
-                "id": {
-                  "const": "58c63c02-1d89-4572-916a-870abc5a1b7d"
-                },
-                "rolePermissions": {
-                  "type": "array",
-                  "maxItems": 1,
-                  "minItems": 1,
-                  "uniqueItems": true,
-                  "items": {
-                    "type": "object",
-                    "required": [
-                      "allowedResourceActions",
-                      "condition"
-                    ],
-                    "properties": {
-                      "allowedResourceActions": {
-                        "const": [
-                          "libre.graph/driveItem/children/create",
-                          "libre.graph/driveItem/standard/delete",
-                          "libre.graph/driveItem/path/read",
-                          "libre.graph/driveItem/quota/read",
-                          "libre.graph/driveItem/content/read",
-                          "libre.graph/driveItem/upload/create",
-                          "libre.graph/driveItem/permissions/read",
-                          "libre.graph/driveItem/children/read",
-                          "libre.graph/driveItem/versions/read",
-                          "libre.graph/driveItem/deleted/read",
-                          "libre.graph/driveItem/path/update",
-                          "libre.graph/driveItem/versions/update",
-                          "libre.graph/driveItem/deleted/update",
-                          "libre.graph/driveItem/basic/read"
-                        ]
-                      },
-                      "condition": {
-                        "const": "exists @Resource.Root"
-                      }
-                    }
-                  }
-                }
-              }
-            },
-            {
-              "type": "object",
-              "required": [
-                "@libre.graph.weight",
-                "description",
-                "displayName",
-                "id",
-                "rolePermissions"
-              ],
-              "properties": {
-                "@libre.graph.weight": {
-                  "const": 0
-                },
-                "description": {
-                  "const": "View, download and edit."
-                },
-                "displayName": {
-                  "const": "Can edit"
-                },
-                "id": {
-                  "const": "2d00ce52-1fc2-4dbc-8b95-a73b73395f5a"
-                },
-                "rolePermissions": {
-                  "type": "array",
-                  "maxItems": 1,
-                  "minItems": 1,
-                  "uniqueItems": true,
-                  "items": {
-                    "type": "object",
-                    "required": [
-                      "allowedResourceActions",
-                      "condition"
-                    ],
-                    "properties": {
-                      "allowedResourceActions": {
-                        "const": [
-                          "libre.graph/driveItem/path/read",
-                          "libre.graph/driveItem/quota/read",
-                          "libre.graph/driveItem/content/read",
-                          "libre.graph/driveItem/upload/create",
-                          "libre.graph/driveItem/children/read",
-                          "libre.graph/driveItem/deleted/read",
-                          "libre.graph/driveItem/deleted/update",
-                          "libre.graph/driveItem/basic/read"
-                        ]
-                      },
-                      "condition": {
-                        "const":"exists @Resource.File"
-                      }
-                    }
-                  }
-                }
-              }
-            },
-            {
-              "type": "object",
-              "required": [
-                "@libre.graph.weight",
-                "description",
-                "displayName",
-                "id",
-                "rolePermissions"
-              ],
-              "properties": {
-                "@libre.graph.weight": {
-                  "const": 0
-                },
-                "description": {
-                  "const": "View, download and upload."
-                },
-                "displayName": {
-                  "const": "Can upload"
-                },
-                "id": {
-                  "const": "1c996275-f1c9-4e71-abdf-a42f6495e960"
-                },
-                "rolePermissions": {
-                  "type": "array",
-                  "maxItems": 1,
-                  "minItems": 1,
-                  "uniqueItems": true,
-                  "items": {
-                    "type": "object",
-                    "required": [
-                      "allowedResourceActions",
-                      "condition"
-                    ],
-                    "properties": {
-                      "allowedResourceActions": {
-                        "const": [
-                          "libre.graph/driveItem/children/create",
-                          "libre.graph/driveItem/path/read",
-                          "libre.graph/driveItem/content/read",
-                          "libre.graph/driveItem/upload/create",
-                          "libre.graph/driveItem/children/read",
-                          "libre.graph/driveItem/path/update",
-                          "libre.graph/driveItem/basic/read"
-                        ]
-                      },
-                      "condition": {
-                        "const": "exists @Resource.Folder"
-                      }
-                    }
-                  }
-                }
-              }
-            },
-            {
-              "type": "object",
-              "required": [
-                "@libre.graph.weight",
-                "description",
-                "displayName",
-                "id",
-                "rolePermissions"
-              ],
-              "properties": {
-                "@libre.graph.weight": {
-                  "const": 0
-                },
-                "description": {
-                  "const": "View, download, upload, edit, add, delete and manage members."
-                },
-                "displayName": {
-                  "const": "Can manage"
-                },
-                "id": {
-                  "const": "312c0871-5ef7-4b3a-85b6-0e4074c64049"
-                },
-                "rolePermissions": {
-                  "type": "array",
-                  "maxItems": 1,
-                  "minItems": 1,
-                  "uniqueItems": true,
-                  "items": {
-                    "type": "object",
-                    "required": [
-                      "allowedResourceActions",
-                      "condition"
-                    ],
-                    "properties": {
-                      "allowedResourceActions": {
-                        "const": [
-                          "libre.graph/driveItem/permissions/create",
-                          "libre.graph/driveItem/children/create",
-                          "libre.graph/driveItem/standard/delete",
-                          "libre.graph/driveItem/path/read",
-                          "libre.graph/driveItem/quota/read",
-                          "libre.graph/driveItem/content/read",
-                          "libre.graph/driveItem/upload/create",
-                          "libre.graph/driveItem/permissions/read",
-                          "libre.graph/driveItem/children/read",
-                          "libre.graph/driveItem/versions/read",
-                          "libre.graph/driveItem/deleted/read",
-                          "libre.graph/driveItem/path/update",
-                          "libre.graph/driveItem/permissions/delete",
-                          "libre.graph/driveItem/deleted/delete",
-                          "libre.graph/driveItem/versions/update",
-                          "libre.graph/driveItem/deleted/update",
-                          "libre.graph/driveItem/basic/read",
-                          "libre.graph/driveItem/permissions/update",
-                          "libre.graph/driveItem/permissions/deny"
-                        ]
-                      },
-                      "condition": {
-                        "const": "exists @Resource.Root"
-                      }
-                    }
-                  }
-                }
-              }
-            },
-            {
-              "type": "object",
-              "required": [
-                "@libre.graph.weight",
-                "description",
-                "displayName",
-                "id",
-                "rolePermissions"
-              ],
-              "properties": {
-                "@libre.graph.weight": {
-                  "const": 0
-                },
-                "description": {
-                  "const": "View only documents, images and PDFs. Watermarks will be applied."
-                },
-                "displayName": {
-                  "const": "Can view (secure)"
-                },
-                "id": {
-                  "const": "aa97fe03-7980-45ac-9e50-b325749fd7e6"
-                },
-                "rolePermissions": {
-                  "type": "array",
-                  "maxItems": 2,
-                  "minItems": 2,
-                  "uniqueItems": true,
-                  "items": {
-                    "oneOf": [
-                      {
-                        "type": "object",
-                        "required": [
-                          "allowedResourceActions",
-                          "condition"
-                        ],
-                        "properties": {
-                          "allowedResourceActions": {
-                            "const": [
-                              "libre.graph/driveItem/path/read",
-                              "libre.graph/driveItem/children/read",
-                              "libre.graph/driveItem/basic/read"
-                            ]
-                          },
-                          "condition": {
-                            "const": "exists @Resource.File"
-                          }
-                        }
-                      },
-                      {
-                        "type": "object",
-                        "required": [
-                          "allowedResourceActions",
-                          "condition"
-                        ],
-                        "properties": {
-                          "allowedResourceActions": {
-                            "const": [
-                              "libre.graph/driveItem/path/read",
-                              "libre.graph/driveItem/children/read",
-                              "libre.graph/driveItem/basic/read"
-                            ]
-                          },
-                          "condition": {
-                            "const": "exists @Resource.Folder"
-                          }
-                        }
-                      }
-                    ]
-                  }
-                }
-              }
-            }
-          ]
-        }
-      }
-      """
-
-
-  Scenario: get details of a specific permission role definition
-    When user "Alice" gets the "Viewer" role definition using the Graph API
-    Then the HTTP status code should be "200"
-    And the JSON data of the response should match
-      """
-      {
-        "type": "object",
-          "required": [
-            "@libre.graph.weight",
-            "description",
-            "displayName",
-            "id",
-            "rolePermissions"
-          ],
-          "properties": {
-            "@libre.graph.weight":{
-              "const": 0
-            },
-            "description": {
-              "const": "View and download."
-            },
-            "displayName": {
-              "const": "Can view"
-            },
-            "id": {
-              "const": "b1e2218d-eef8-4d4c-b82d-0f1a1b48f3b5"
-            },
-            "rolePermissions": {
-              "type": "array",
-              "maxItems": 2,
-              "minItems": 2,
-              "uniqueItems": true,
-              "items": {
-                "oneOf": [
-                  {
-                    "type": "object",
-                    "required": [
-                      "allowedResourceActions",
-                      "condition"
-                    ],
-                    "properties": {
-                      "allowedResourceActions": {
-                        "const": [
-                          "libre.graph/driveItem/path/read",
-                          "libre.graph/driveItem/quota/read",
-                          "libre.graph/driveItem/content/read",
-                          "libre.graph/driveItem/children/read",
-                          "libre.graph/driveItem/deleted/read",
-                          "libre.graph/driveItem/basic/read"
-                        ]
-                      },
-                      "condition": {
-                        "const": "exists @Resource.File"
-                      }
-                    }
-                  },
-                  {
-                    "type": "object",
-                    "required": [
-                      "allowedResourceActions",
-                      "condition"
-                    ],
-                    "properties": {
-                      "allowedResourceActions": {
-                        "const": [
-                          "libre.graph/driveItem/path/read",
-                          "libre.graph/driveItem/quota/read",
-                          "libre.graph/driveItem/content/read",
-                          "libre.graph/driveItem/children/read",
-                          "libre.graph/driveItem/deleted/read",
-                          "libre.graph/driveItem/basic/read"
-                        ]
-                      },
-                      "condition": {
-                        "const": "exists @Resource.Folder"
-                      }
-                    }
-                  }
-                ]
-              }
-            }
-          }
-      }
-      """
diff --git a/tests/acceptance/features/bootstrap/GraphContext.php b/tests/acceptance/features/bootstrap/GraphContext.php
index ee33aa7fe9c..bf18bb7dcd0 100644
--- a/tests/acceptance/features/bootstrap/GraphContext.php
+++ b/tests/acceptance/features/bootstrap/GraphContext.php
@@ -2732,47 +2732,4 @@ class GraphContext implements Context {
 		$this->featureContext->theHTTPStatusCodeShouldBe(200, '', $response);
 		$this->featureContext->updateUsernameInCreatedUserList($byUser, $userName);
 	}
-
-	/**
-	 * @When user :user gets a list of permissions role definitions using the Graph API
-	 *
-	 * @param string $user
-	 *
-	 * @return void
-	 * @throws GuzzleException
-	 */
-	public function getListOfPermissionRoleDefinitionsUsingTheGraphAPI(string $user): void {
-		$credentials = $this->getAdminOrUserCredentials($user);
-		$this->featureContext->setResponse(
-			GraphHelper::getPermissionsRoleDefinitions(
-				$this->featureContext->getBaseUrl(),
-				$this->featureContext->getStepLineRef(),
-				$credentials['username'],
-				$credentials['password'],
-			)
-		);
-	}
-
-	/**
-	 * @When user :user gets the :permissionRole role definition using the Graph API
-	 *
-	 * @param string $user
-	 * @param string $permissionRole
-	 *
-	 * @return void
-	 * @throws GuzzleException
-	 * @throws Exception
-	 */
-	public function getPermissionRoleDefinitionUsingGraphAPI(string $user, string $permissionRole):void {
-		$credentials = $this->getAdminOrUserCredentials($user);
-		$this->featureContext->setResponse(
-			GraphHelper::getPermissionRoleDefinition(
-				$permissionRole,
-				$this->featureContext->getBaseUrl(),
-				$this->featureContext->getStepLineRef(),
-				$credentials['username'],
-				$credentials['password'],
-			)
-		);
-	}
 }