Repository: owncloud/ocis
Pull request #3705 (2022-05-05 16:17:11)

Add graph /me/changePassword endpoint
To allow users to change their own password when using the Graph LDAP backend.

- Fixes:	#3063

## Checklist:
- [x] emit Event when password is changed
- [x] Code changes
- [x] Unit tests added
- [ ] Acceptance tests added
- [x] add Documentation
- https://github.com/owncloud/libre-graph-api/pull/39


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

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

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

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


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

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

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


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

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

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


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

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

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


ownclouders: :boom: Acceptance test [<strong>Core-API-Tests-ocis-storage-8</strong>](https://drone.owncloud.com/owncloud/ocis/11758/46/1) failed. Further test are cancelled...
<!-- id: pr-3705 -->

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

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

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


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

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

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


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

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

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



Patch:
diff --git a/.drone.env b/.drone.env
index 27d9dcdbcb0..33d0b580567 100644
--- a/.drone.env
+++ b/.drone.env
@@ -1,5 +1,5 @@
 # The test runner source for API tests
-CORE_COMMITID=ee75bfbb934ec300e8aa8f45e326c5308d50bdc7
+CORE_COMMITID=6321f96cf6739d9130c10376e8e4fc96af75edef
 CORE_BRANCH=master
 
 # The test runner source for UI tests
diff --git a/changelog/unreleased/graph-me-changepw.md b/changelog/unreleased/graph-me-changepw.md
new file mode 100644
index 00000000000..607ae4848a2
--- /dev/null
+++ b/changelog/unreleased/graph-me-changepw.md
@@ -0,0 +1,7 @@
+Enhancement: Add /me/changePassword endpoint to GraphAPI
+
+When using the builtin user management, allow users to update their own password via
+the graph/v1.0/me/changePassword endpoint.
+
+https://github.com/owncloud/ocis/issues/3063
+https://github.com/owncloud/ocis/pull/3705
diff --git a/docs/extensions/graph/users.md b/docs/extensions/graph/users.md
index 2b5e18dc040..d8715d7b792 100644
--- a/docs/extensions/graph/users.md
+++ b/docs/extensions/graph/users.md
@@ -163,3 +163,27 @@ to be updated. E.g. to update the `displayName` Attribute:
 ```
 
 Similar to creating a user via `POST`,  the `PATCH` request will return the user object containing the new attribute values.
+
+### Change password
+
+#### `POST /me/changePassword`
+
+Users can change their own password by sending a POST request to `/me/changePassword`
+
+##### Request Body
+
+```
+{
+  "currentPassword": "current",
+  "newPassword": "new"
+}
+
+```
+
+When successful the API returns no response body and the HTTP status code 204 (No Content)
+
+```
+ curl -i -k --header "Content-Type: application/json" \
+      --request POST  --data '{"currentPassword": "current", "newPassword": "new" }' \
+      'https://localhost:9200/graph/v1.0/me/changePassword' -u user:current
+```
diff --git a/extensions/graph/Makefile b/extensions/graph/Makefile
index 67ad0d888d2..d17a086a335 100644
--- a/extensions/graph/Makefile
+++ b/extensions/graph/Makefile
@@ -28,6 +28,7 @@ ci-go-generate: $(MOCKERY) # CI runs ci-node-generate automatically before this
 	$(MOCKERY) --dir pkg/service/v0 --case underscore --name GatewayClient
 	$(MOCKERY) --dir pkg/service/v0 --case underscore --name HTTPClient
 	$(MOCKERY) --dir pkg/service/v0 --case underscore --name Publisher
+	$(MOCKERY) --srcpkg github.com/go-ldap/ldap/v3 --case underscore --filename ldapclient.go --name Client
 
 
 .PHONY: ci-node-generate
diff --git a/extensions/graph/mocks/gateway_client.go b/extensions/graph/mocks/gateway_client.go
index c2ba9bc1f2f..aba88f1eb7a 100644
--- a/extensions/graph/mocks/gateway_client.go
+++ b/extensions/graph/mocks/gateway_client.go
@@ -18,6 +18,36 @@ type GatewayClient struct {
 	mock.Mock
 }
 
+// Authenticate provides a mock function with given fields: ctx, in, opts
+func (_m *GatewayClient) Authenticate(ctx context.Context, in *gatewayv1beta1.AuthenticateRequest, opts ...grpc.CallOption) (*gatewayv1beta1.AuthenticateResponse, error) {
+	_va := make([]interface{}, len(opts))
+	for _i := range opts {
+		_va[_i] = opts[_i]
+	}
+	var _ca []interface{}
+	_ca = append(_ca, ctx, in)
+	_ca = append(_ca, _va...)
+	ret := _m.Called(_ca...)
+
+	var r0 *gatewayv1beta1.AuthenticateResponse
+	if rf, ok := ret.Get(0).(func(context.Context, *gatewayv1beta1.AuthenticateRequest, ...grpc.CallOption) *gatewayv1beta1.AuthenticateResponse); ok {
+		r0 = rf(ctx, in, opts...)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*gatewayv1beta1.AuthenticateResponse)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *gatewayv1beta1.AuthenticateRequest, ...grpc.CallOption) error); ok {
+		r1 = rf(ctx, in, opts...)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
 // CreateStorageSpace provides a mock function with given fields: ctx, in, opts
 func (_m *GatewayClient) CreateStorageSpace(ctx context.Context, in *providerv1beta1.CreateStorageSpaceRequest, opts ...grpc.CallOption) (*providerv1beta1.CreateStorageSpaceResponse, error) {
 	_va := make([]interface{}, len(opts))
diff --git a/extensions/graph/mocks/ldapclient.go b/extensions/graph/mocks/ldapclient.go
new file mode 100644
index 00000000000..f2c10eb7539
--- /dev/null
+++ b/extensions/graph/mocks/ldapclient.go
@@ -0,0 +1,294 @@
+// Code generated by mockery v2.10.4. DO NOT EDIT.
+
+package mocks
+
+import (
+	ldap "github.com/go-ldap/ldap/v3"
+	mock "github.com/stretchr/testify/mock"
+
+	time "time"
+
+	tls "crypto/tls"
+)
+
+// Client is an autogenerated mock type for the Client type
+type Client struct {
+	mock.Mock
+}
+
+// Add provides a mock function with given fields: _a0
+func (_m *Client) Add(_a0 *ldap.AddRequest) error {
+	ret := _m.Called(_a0)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(*ldap.AddRequest) error); ok {
+		r0 = rf(_a0)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// Bind provides a mock function with given fields: username, password
+func (_m *Client) Bind(username string, password string) error {
+	ret := _m.Called(username, password)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(string, string) error); ok {
+		r0 = rf(username, password)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// Close provides a mock function with given fields:
+func (_m *Client) Close() {
+	_m.Called()
+}
+
+// Compare provides a mock function with given fields: dn, attribute, value
+func (_m *Client) Compare(dn string, attribute string, value string) (bool, error) {
+	ret := _m.Called(dn, attribute, value)
+
+	var r0 bool
+	if rf, ok := ret.Get(0).(func(string, string, string) bool); ok {
+		r0 = rf(dn, attribute, value)
+	} else {
+		r0 = ret.Get(0).(bool)
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(string, string, string) error); ok {
+		r1 = rf(dn, attribute, value)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// Del provides a mock function with given fields: _a0
+func (_m *Client) Del(_a0 *ldap.DelRequest) error {
+	ret := _m.Called(_a0)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(*ldap.DelRequest) error); ok {
+		r0 = rf(_a0)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// ExternalBind provides a mock function with given fields:
+func (_m *Client) ExternalBind() error {
+	ret := _m.Called()
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func() error); ok {
+		r0 = rf()
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// IsClosing provides a mock function with given fields:
+func (_m *Client) IsClosing() bool {
+	ret := _m.Called()
+
+	var r0 bool
+	if rf, ok := ret.Get(0).(func() bool); ok {
+		r0 = rf()
+	} else {
+		r0 = ret.Get(0).(bool)
+	}
+
+	return r0
+}
+
+// Modify provides a mock function with given fields: _a0
+func (_m *Client) Modify(_a0 *ldap.ModifyRequest) error {
+	ret := _m.Called(_a0)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(*ldap.ModifyRequest) error); ok {
+		r0 = rf(_a0)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// ModifyDN provides a mock function with given fields: _a0
+func (_m *Client) ModifyDN(_a0 *ldap.ModifyDNRequest) error {
+	ret := _m.Called(_a0)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(*ldap.ModifyDNRequest) error); ok {
+		r0 = rf(_a0)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// ModifyWithResult provides a mock function with given fields: _a0
+func (_m *Client) ModifyWithResult(_a0 *ldap.ModifyRequest) (*ldap.ModifyResult, error) {
+	ret := _m.Called(_a0)
+
+	var r0 *ldap.ModifyResult
+	if rf, ok := ret.Get(0).(func(*ldap.ModifyRequest) *ldap.ModifyResult); ok {
+		r0 = rf(_a0)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*ldap.ModifyResult)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(*ldap.ModifyRequest) error); ok {
+		r1 = rf(_a0)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// PasswordModify provides a mock function with given fields: _a0
+func (_m *Client) PasswordModify(_a0 *ldap.PasswordModifyRequest) (*ldap.PasswordModifyResult, error) {
+	ret := _m.Called(_a0)
+
+	var r0 *ldap.PasswordModifyResult
+	if rf, ok := ret.Get(0).(func(*ldap.PasswordModifyRequest) *ldap.PasswordModifyResult); ok {
+		r0 = rf(_a0)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*ldap.PasswordModifyResult)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(*ldap.PasswordModifyRequest) error); ok {
+		r1 = rf(_a0)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// Search provides a mock function with given fields: _a0
+func (_m *Client) Search(_a0 *ldap.SearchRequest) (*ldap.SearchResult, error) {
+	ret := _m.Called(_a0)
+
+	var r0 *ldap.SearchResult
+	if rf, ok := ret.Get(0).(func(*ldap.SearchRequest) *ldap.SearchResult); ok {
+		r0 = rf(_a0)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*ldap.SearchResult)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(*ldap.SearchRequest) error); ok {
+		r1 = rf(_a0)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// SearchWithPaging provides a mock function with given fields: searchRequest, pagingSize
+func (_m *Client) SearchWithPaging(searchRequest *ldap.SearchRequest, pagingSize uint32) (*ldap.SearchResult, error) {
+	ret := _m.Called(searchRequest, pagingSize)
+
+	var r0 *ldap.SearchResult
+	if rf, ok := ret.Get(0).(func(*ldap.SearchRequest, uint32) *ldap.SearchResult); ok {
+		r0 = rf(searchRequest, pagingSize)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*ldap.SearchResult)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(*ldap.SearchRequest, uint32) error); ok {
+		r1 = rf(searchRequest, pagingSize)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// SetTimeout provides a mock function with given fields: _a0
+func (_m *Client) SetTimeout(_a0 time.Duration) {
+	_m.Called(_a0)
+}
+
+// SimpleBind provides a mock function with given fields: _a0
+func (_m *Client) SimpleBind(_a0 *ldap.SimpleBindRequest) (*ldap.SimpleBindResult, error) {
+	ret := _m.Called(_a0)
+
+	var r0 *ldap.SimpleBindResult
+	if rf, ok := ret.Get(0).(func(*ldap.SimpleBindRequest) *ldap.SimpleBindResult); ok {
+		r0 = rf(_a0)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*ldap.SimpleBindResult)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(*ldap.SimpleBindRequest) error); ok {
+		r1 = rf(_a0)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// Start provides a mock function with given fields:
+func (_m *Client) Start() {
+	_m.Called()
+}
+
+// StartTLS provides a mock function with given fields: _a0
+func (_m *Client) StartTLS(_a0 *tls.Config) error {
+	ret := _m.Called(_a0)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(*tls.Config) error); ok {
+		r0 = rf(_a0)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// UnauthenticatedBind provides a mock function with given fields: username
+func (_m *Client) UnauthenticatedBind(username string) error {
+	ret := _m.Called(username)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(string) error); ok {
+		r0 = rf(username)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
diff --git a/extensions/graph/pkg/service/v0/graph.go b/extensions/graph/pkg/service/v0/graph.go
index 676c8d7b955..1b0ab42462c 100644
--- a/extensions/graph/pkg/service/v0/graph.go
+++ b/extensions/graph/pkg/service/v0/graph.go
@@ -17,12 +17,14 @@ import (
 	"google.golang.org/grpc"
 )
 
-//go:generate make generate
+//go:generate make -C ../../.. generate
 
 // GatewayClient is the subset of the gateway.GatewayAPIClient that is being used to interact with the gateway
 type GatewayClient interface {
 	//gateway.GatewayAPIClient
 
+	// Authenticates a user.
+	Authenticate(ctx context.Context, in *gateway.AuthenticateRequest, opts ...grpc.CallOption) (*gateway.AuthenticateResponse, error)
 	// Returns the home path for the given authenticated user.
 	// When a user has access to multiple storage providers, one of them is the home.
 	GetHome(ctx context.Context, in *provider.GetHomeRequest, opts ...grpc.CallOption) (*provider.GetHomeResponse, error)
diff --git a/extensions/graph/pkg/service/v0/instrument.go b/extensions/graph/pkg/service/v0/instrument.go
index 0b31c66172d..7cad7f7c9c0 100644
--- a/extensions/graph/pkg/service/v0/instrument.go
+++ b/extensions/graph/pkg/service/v0/instrument.go
@@ -54,6 +54,11 @@ func (i instrument) PatchUser(w http.ResponseWriter, r *http.Request) {
 	i.next.PatchUser(w, r)
 }
 
+// ChangeOwnPassword implements the Service interface.
+func (i instrument) ChangeOwnPassword(w http.ResponseWriter, r *http.Request) {
+	i.next.ChangeOwnPassword(w, r)
+}
+
 // GetGroups implements the Service interface.
 func (i instrument) GetGroups(w http.ResponseWriter, r *http.Request) {
 	i.next.GetGroups(w, r)
diff --git a/extensions/graph/pkg/service/v0/logging.go b/extensions/graph/pkg/service/v0/logging.go
index 6b28bf523bb..51213475d7e 100644
--- a/extensions/graph/pkg/service/v0/logging.go
+++ b/extensions/graph/pkg/service/v0/logging.go
@@ -54,6 +54,11 @@ func (l logging) PatchUser(w http.ResponseWriter, r *http.Request) {
 	l.next.PatchUser(w, r)
 }
 
+// ChangeOwnPassword implements the Service interface.
+func (l logging) ChangeOwnPassword(w http.ResponseWriter, r *http.Request) {
+	l.next.ChangeOwnPassword(w, r)
+}
+
 // GetGroups implements the Service interface.
 func (l logging) GetGroups(w http.ResponseWriter, r *http.Request) {
 	l.next.GetGroups(w, r)
diff --git a/extensions/graph/pkg/service/v0/option.go b/extensions/graph/pkg/service/v0/option.go
index a5feb4e69d4..b8ebe47479c 100644
--- a/extensions/graph/pkg/service/v0/option.go
+++ b/extensions/graph/pkg/service/v0/option.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/cs3org/reva/v2/pkg/events"
 	"github.com/owncloud/ocis/v2/extensions/graph/pkg/config"
+	"github.com/owncloud/ocis/v2/extensions/graph/pkg/identity"
 	"github.com/owncloud/ocis/v2/ocis-pkg/log"
 	"github.com/owncloud/ocis/v2/ocis-pkg/roles"
 	settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0"
@@ -19,6 +20,7 @@ type Options struct {
 	Config          *config.Config
 	Middleware      []func(http.Handler) http.Handler
 	GatewayClient   GatewayClient
+	IdentityBackend identity.Backend
 	HTTPClient      HTTPClient
 	RoleService     settingssvc.RoleService
 	RoleManager     *roles.Manager
@@ -64,6 +66,13 @@ func WithGatewayClient(val GatewayClient) Option {
 	}
 }
 
+// WithIdentityBackend provides a function to set the IdentityBackend option.
+func WithIdentityBackend(val identity.Backend) Option {
+	return func(o *Options) {
+		o.IdentityBackend = val
+	}
+}
+
 // WithHTTPClient provides a function to set the http client option.
 func WithHTTPClient(val HTTPClient) Option {
 	return func(o *Options) {
diff --git a/extensions/graph/pkg/service/v0/password.go b/extensions/graph/pkg/service/v0/password.go
new file mode 100644
index 00000000000..51da7b41ba5
--- /dev/null
+++ b/extensions/graph/pkg/service/v0/password.go
@@ -0,0 +1,98 @@
+package svc
+
+import (
+	"encoding/json"
+	"net/http"
+	"strings"
+
+	"github.com/CiscoM31/godata"
+	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/events"
+	"github.com/go-chi/render"
+	libregraph "github.com/owncloud/libre-graph-api-go"
+	"github.com/owncloud/ocis/v2/extensions/graph/pkg/service/v0/errorcode"
+)
+
+// ChangeOwnPassword implements the Service interface. It allows the user to change
+// its own password
+func (g Graph) ChangeOwnPassword(w http.ResponseWriter, r *http.Request) {
+	ctx := r.Context()
+	u, ok := revactx.ContextGetUser(ctx)
+	if !ok {
+		g.logger.Error().Msg("user not in context")
+		errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, "user not in context")
+		return
+	}
+
+	sanitizedPath := strings.TrimPrefix(r.URL.Path, "/graph/v1.0/")
+	_, err := godata.ParseRequest(r.Context(), sanitizedPath, r.URL.Query())
+	if err != nil {
+		g.logger.Err(err).Interface("query", r.URL.Query()).Msg("query error")
+		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
+		return
+	}
+	cpw := libregraph.NewPasswordChange()
+	err = json.NewDecoder(r.Body).Decode(cpw)
+	if err != nil {
+		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
+		return
+	}
+
+	currentPw := cpw.GetCurrentPassword()
+	if currentPw == "" {
+		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "current password cannot be empty")
+		return
+	}
+
+	newPw := cpw.GetNewPassword()
+	if newPw == "" {
+		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "new password cannot be empty")
+		return
+	}
+
+	if newPw == currentPw {
+		errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "new password must be differnt from current password")
+		return
+	}
+
+	authReq := &gateway.AuthenticateRequest{
+		Type:         "basic",
+		ClientId:     u.Username,
+		ClientSecret: currentPw,
+	}
+	authRes, err := g.gatewayClient.Authenticate(r.Context(), authReq)
+	if err != nil {
+		errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, err.Error())
+		return
+	}
+
+	if authRes.Status.Code != cs3rpc.Code_CODE_OK {
+		errorcode.InvalidRequest.Render(w, r, http.StatusInternalServerError, "password change failed")
+		return
+	}
+
+	newPwProfile := libregraph.NewPasswordProfile()
+	newPwProfile.SetPassword(newPw)
+	changes := libregraph.NewUser()
+	changes.SetPasswordProfile(*newPwProfile)
+	_, err = g.identityBackend.UpdateUser(ctx, u.Id.OpaqueId, *changes)
+	if err != nil {
+		errorcode.InvalidRequest.Render(w, r, http.StatusInternalServerError, "password change failed")
+		g.logger.Debug().Err(err).Str("userid", u.Id.OpaqueId).Msg("failed to update user password")
+		return
+	}
+
+	g.publishEvent(
+		events.UserFeatureChanged{
+			UserID: u.Id.OpaqueId,
+			Features: []events.UserFeature{
+				events.UserFeature{Name: "password", Value: "***"},
+			},
+		},
+	)
+
+	render.Status(r, http.StatusNoContent)
+	render.NoContent(w, r)
+}
diff --git a/extensions/graph/pkg/service/v0/service.go b/extensions/graph/pkg/service/v0/service.go
index 4184a1fea57..cf328ce0503 100644
--- a/extensions/graph/pkg/service/v0/service.go
+++ b/extensions/graph/pkg/service/v0/service.go
@@ -35,6 +35,7 @@ type Service interface {
 	PostUser(http.ResponseWriter, *http.Request)
 	DeleteUser(http.ResponseWriter, *http.Request)
 	PatchUser(http.ResponseWriter, *http.Request)
+	ChangeOwnPassword(http.ResponseWriter, *http.Request)
 
 	GetGroups(http.ResponseWriter, *http.Request)
 	GetGroup(http.ResponseWriter, *http.Request)
@@ -55,46 +56,10 @@ func NewService(opts ...Option) Service {
 	m := chi.NewMux()
 	m.Use(options.Middleware...)
 
-	var backend identity.Backend
-	switch options.Config.Identity.Backend {
-	case "cs3":
-		backend = &identity.CS3{
-			Config: options.Config.Reva,
-			Logger: &options.Logger,
-		}
-	case "ldap":
-		var err error
-
-		var tlsConf *tls.Config
-		if options.Config.Identity.LDAP.Insecure {
-			tlsConf = &tls.Config{
-				//nolint:gosec // We need the ability to run with "insecure" (dev/testing)
-				InsecureSkipVerify: options.Config.Identity.LDAP.Insecure,
-			}
-		}
-
-		conn := ldap.NewLDAPWithReconnect(&options.Logger,
-			ldap.Config{
-				URI:          options.Config.Identity.LDAP.URI,
-				BindDN:       options.Config.Identity.LDAP.BindDN,
-				BindPassword: options.Config.Identity.LDAP.BindPassword,
-				TLSConfig:    tlsConf,
-			},
-		)
-		if backend, err = identity.NewLDAPBackend(conn, options.Config.Identity.LDAP, &options.Logger); err != nil {
-			options.Logger.Error().Msgf("Error initializing LDAP Backend: '%s'", err)
-			return nil
-		}
-	default:
-		options.Logger.Error().Msgf("Unknown Identity Backend: '%s'", options.Config.Identity.Backend)
-		return nil
-	}
-
 	svc := Graph{
 		config:               options.Config,
 		mux:                  m,
 		logger:               &options.Logger,
-		identityBackend:      backend,
 		spacePropertiesCache: ttlcache.NewCache(),
 		eventsPublisher:      options.EventsPublisher,
 	}
@@ -108,6 +73,44 @@ func NewService(opts ...Option) Service {
 	} else {
 		svc.gatewayClient = options.GatewayClient
 	}
+	if options.IdentityBackend == nil {
+		switch options.Config.Identity.Backend {
+		case "cs3":
+			svc.identityBackend = &identity.CS3{
+				Config: options.Config.Reva,
+				Logger: &options.Logger,
+			}
+		case "ldap":
+			var err error
+
+			var tlsConf *tls.Config
+			if options.Config.Identity.LDAP.Insecure {
+				tlsConf = &tls.Config{
+					//nolint:gosec // We need the ability to run with "insecure" (dev/testing)
+					InsecureSkipVerify: options.Config.Identity.LDAP.Insecure,
+				}
+			}
+
+			conn := ldap.NewLDAPWithReconnect(&options.Logger,
+				ldap.Config{
+					URI:          options.Config.Identity.LDAP.URI,
+					BindDN:       options.Config.Identity.LDAP.BindDN,
+					BindPassword: options.Config.Identity.LDAP.BindPassword,
+					TLSConfig:    tlsConf,
+				},
+			)
+			if svc.identityBackend, err = identity.NewLDAPBackend(conn, options.Config.Identity.LDAP, &options.Logger); err != nil {
+				options.Logger.Error().Msgf("Error initializing LDAP Backend: '%s'", err)
+				return nil
+			}
+		default:
+			options.Logger.Error().Msgf("Unknown Identity Backend: '%s'", options.Config.Identity.Backend)
+			return nil
+		}
+	} else {
+		svc.identityBackend = options.IdentityBackend
+	}
+
 	if options.HTTPClient == nil {
 		http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{
 			InsecureSkipVerify: options.Config.Spaces.Insecure, //nolint:gosec
@@ -143,6 +146,7 @@ func NewService(opts ...Option) Service {
 				r.Get("/", svc.GetMe)
 				r.Get("/drives", svc.GetDrives)
 				r.Get("/drive/root/children", svc.GetRootDriveChildren)
+				r.Post("/changePassword", svc.ChangeOwnPassword)
 			})
 			r.Route("/users", func(r chi.Router) {
 				r.With(requireAdmin).Get("/", svc.GetUsers)
diff --git a/extensions/graph/pkg/service/v0/tracing.go b/extensions/graph/pkg/service/v0/tracing.go
index 0d2581ba98d..d3a0167b463 100644
--- a/extensions/graph/pkg/service/v0/tracing.go
+++ b/extensions/graph/pkg/service/v0/tracing.go
@@ -50,6 +50,11 @@ func (t tracing) PatchUser(w http.ResponseWriter, r *http.Request) {
 	t.next.PatchUser(w, r)
 }
 
+// ChangeOwnPassword implements the Service interface.
+func (t tracing) ChangeOwnPassword(w http.ResponseWriter, r *http.Request) {
+	t.next.ChangeOwnPassword(w, r)
+}
+
 // GetGroups implements the Service interface.
 func (t tracing) GetGroups(w http.ResponseWriter, r *http.Request) {
 	t.next.GetGroups(w, r)
diff --git a/go.mod b/go.mod
index 52f6bed48a6..cece48d345d 100644
--- a/go.mod
+++ b/go.mod
@@ -48,7 +48,7 @@ require (
 	github.com/onsi/ginkgo v1.16.5
 	github.com/onsi/ginkgo/v2 v2.1.4
 	github.com/onsi/gomega v1.19.0
-	github.com/owncloud/libre-graph-api-go v0.13.3
+	github.com/owncloud/libre-graph-api-go v0.14.2
 	github.com/pkg/errors v0.9.1
 	github.com/prometheus/client_golang v1.12.1
 	github.com/rs/zerolog v1.26.1
@@ -67,8 +67,8 @@ require (
 	go.opentelemetry.io/otel/trace v1.7.0
 	golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29
 	golang.org/x/image v0.0.0-20220321031419-a8550c1d254a
-	golang.org/x/net v0.0.0-20220225172249-27dd8689420f
-	golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a
+	golang.org/x/net v0.0.0-20220516155154-20f960328961
+	golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5
 	google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb
 	google.golang.org/grpc v1.46.0
 	google.golang.org/protobuf v1.28.0
diff --git a/go.sum b/go.sum
index 577328e1e96..05d0bf74a8f 100644
--- a/go.sum
+++ b/go.sum
@@ -1018,8 +1018,8 @@ github.com/oracle/oci-go-sdk v24.3.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35uk
 github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
 github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
 github.com/ovh/go-ovh v1.1.0/go.mod h1:AxitLZ5HBRPyUd+Zl60Ajaag+rNTdVXWIkzfrVuTXWA=
-github.com/owncloud/libre-graph-api-go v0.13.3 h1:jNtQ8QcT7AZTfhdVHDaqAOs2xaJTfqfkucM9GARBIrQ=
-github.com/owncloud/libre-graph-api-go v0.13.3/go.mod h1:579sFrPP7aP24LZXGPopLfvE+hAka/2DYHk0+Ij+w+U=
+github.com/owncloud/libre-graph-api-go v0.14.2 h1:JiI32eDp7JZmiVv4aUpC4yaPpv6gK4xxM9MOe/0cXpE=
+github.com/owncloud/libre-graph-api-go v0.14.2/go.mod h1:579sFrPP7aP24LZXGPopLfvE+hAka/2DYHk0+Ij+w+U=
 github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
 github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
@@ -1485,8 +1485,9 @@ golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qx
 golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
 golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220516155154-20f960328961 h1:+W/iTMPG0EL7aW+/atntZwZrvSRIj3m3yX414dSULUU=
+golang.org/x/net v0.0.0-20220516155154-20f960328961/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1504,8 +1505,9 @@ golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ
 golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a h1:qfl7ob3DIEs3Ml9oLuPwY2N04gymzAW04WsUQHIClgM=
 golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
+golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 h1:OSnWWcOd/CtWQC2cYSBgbTSJv3ciqd8r54ySIW2y3RE=
+golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=


Test patch:
diff --git a/extensions/graph/pkg/service/v0/password_test.go b/extensions/graph/pkg/service/v0/password_test.go
new file mode 100644
index 00000000000..65a0cb2edf2
--- /dev/null
+++ b/extensions/graph/pkg/service/v0/password_test.go
@@ -0,0 +1,160 @@
+package svc_test
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"errors"
+	"net/http"
+	"net/http/httptest"
+
+	gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
+	userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
+	revactx "github.com/cs3org/reva/v2/pkg/ctx"
+	"github.com/cs3org/reva/v2/pkg/rgrpc/status"
+	"github.com/go-ldap/ldap/v3"
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+	libregraph "github.com/owncloud/libre-graph-api-go"
+	"github.com/owncloud/ocis/v2/extensions/graph/mocks"
+	"github.com/owncloud/ocis/v2/extensions/graph/pkg/config"
+	"github.com/owncloud/ocis/v2/extensions/graph/pkg/config/defaults"
+	"github.com/owncloud/ocis/v2/extensions/graph/pkg/identity"
+	service "github.com/owncloud/ocis/v2/extensions/graph/pkg/service/v0"
+	"github.com/owncloud/ocis/v2/ocis-pkg/log"
+	"github.com/stretchr/testify/mock"
+)
+
+type changePwTest struct {
+	desc      string
+	currentpw string
+	newpw     string
+	expected  int
+}
+
+var _ = Describe("Users changing their own password", func() {
+	var (
+		svc             service.Service
+		gatewayClient   *mocks.GatewayClient
+		httpClient      *mocks.HTTPClient
+		ldapClient      *mocks.Client
+		ldapConfig      config.LDAP
+		identityBackend identity.Backend
+		eventsPublisher mocks.Publisher
+		ctx             context.Context
+		cfg             *config.Config
+		user            *userv1beta1.User
+		err             error
+	)
+
+	JustBeforeEach(func() {
+		ctx = context.Background()
+		cfg = defaults.FullDefaultConfig()
+		cfg.TokenManager.JWTSecret = "loremipsum"
+
+		gatewayClient = &mocks.GatewayClient{}
+		ldapClient = mockedLDAPClient()
+
+		ldapConfig = config.LDAP{
+			WriteEnabled:             true,
+			UserDisplayNameAttribute: "displayName",
+			UserNameAttribute:        "uid",
+			UserEmailAttribute:       "mail",
+			UserIDAttribute:          "ownclouduuid",
+			UserSearchScope:          "sub",
+			GroupNameAttribute:       "cn",
+			GroupIDAttribute:         "ownclouduui",
+			GroupSearchScope:         "sub",
+		}
+		loggger := log.NewLogger()
+		identityBackend, err = identity.NewLDAPBackend(ldapClient, ldapConfig, &loggger)
+		Expect(err).To(BeNil())
+
+		httpClient = &mocks.HTTPClient{}
+		eventsPublisher = mocks.Publisher{}
+		svc = service.NewService(
+			service.Config(cfg),
+			service.WithGatewayClient(gatewayClient),
+			service.WithIdentityBackend(identityBackend),
+			service.WithHTTPClient(httpClient),
+			service.EventsPublisher(&eventsPublisher),
+		)
+		user = &userv1beta1.User{
+			Id: &userv1beta1.UserId{
+				OpaqueId: "user",
+			},
+		}
+		ctx = revactx.ContextSetUser(ctx, user)
+		eventsPublisher.On("Publish", mock.Anything, mock.Anything, mock.Anything).Return(nil)
+	})
+
+	It("fails if no user in context", func() {
+		r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/changePassword", nil)
+		rr := httptest.NewRecorder()
+		svc.ChangeOwnPassword(rr, r)
+		Expect(rr.Code).To(Equal(http.StatusInternalServerError))
+	})
+
+	DescribeTable("changing the password",
+		func(current string, newpw string, authresult string, expected int) {
+			switch authresult {
+			case "error":
+				gatewayClient.On("Authenticate", mock.Anything, mock.Anything).Return(nil, errors.New("fail"))
+			case "deny":
+				gatewayClient.On("Authenticate", mock.Anything, mock.Anything).Return(&gateway.AuthenticateResponse{
+					Status: status.NewPermissionDenied(ctx, errors.New("wrong password"), "wrong password"),
+					Token:  "authtoken",
+				}, nil)
+			default:
+				gatewayClient.On("Authenticate", mock.Anything, mock.Anything).Return(&gateway.AuthenticateResponse{
+					Status: status.NewOK(ctx),
+					Token:  "authtoken",
+				}, nil)
+			}
+			cpw := libregraph.NewPasswordChange()
+			cpw.SetCurrentPassword(current)
+			cpw.SetNewPassword(newpw)
+			body, _ := json.Marshal(cpw)
+			b := bytes.NewBuffer(body)
+			r := httptest.NewRequest(http.MethodPost, "/graph/v1.0/me/changePassword", b).WithContext(ctx)
+			rr := httptest.NewRecorder()
+			svc.ChangeOwnPassword(rr, r)
+			Expect(rr.Code).To(Equal(expected))
+		},
+		Entry("fails when current password is empty", "", "newpassword", "", http.StatusBadRequest),
+		Entry("fails when new password is empty", "currentpassword", "", "", http.StatusBadRequest),
+		Entry("fails when current and new password are equal", "password", "password", "", http.StatusBadRequest),
+		Entry("fails authentication with current password errors", "currentpassword", "newpassword", "error", http.StatusInternalServerError),
+		Entry("fails when current password is wrong", "currentpassword", "newpassword", "deny", http.StatusInternalServerError),
+		Entry("succeeds when current password is correct", "currentpassword", "newpassword", "", http.StatusNoContent),
+	)
+})
+
+func mockedLDAPClient() *mocks.Client {
+	lm := &mocks.Client{}
+
+	userEntry := ldap.NewEntry("uid=test", map[string][]string{
+		"uid":         {"test"},
+		"displayName": {"test"},
+		"mail":        {"test@example.org"},
+	})
+
+	lm.On("Search", mock.Anything, mock.Anything, mock.Anything, mock.Anything,
+		mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
+		Return(
+			&ldap.SearchResult{Entries: []*ldap.Entry{userEntry}},
+			nil)
+
+	mr := ldap.NewModifyRequest("uid=test", nil)
+	mr.Changes = []ldap.Change{
+		{
+			Operation: ldap.ReplaceAttribute,
+			Modification: ldap.PartialAttribute{
+				Type: "userPassword",
+				Vals: []string{"newpassword"},
+			},
+		},
+	}
+	lm.On("Modify", mr).Return(nil)
+	return lm
+}