diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..038e8cc --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,27 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "" +labels: "" +assignees: "" +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: + +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..2bc5d5f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,19 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: "" +labels: "" +assignees: "" +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/release.yaml b/.github/release.yaml new file mode 100644 index 0000000..f956d0f --- /dev/null +++ b/.github/release.yaml @@ -0,0 +1,15 @@ +# https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes#configuring-automatically-generated-release-notes +changelog: + categories: + - title: 🎉 New Features + labels: + - enhancement + - title: 🐞 Bug Fixes + labels: + - bug + - title: 📔 Documentation + labels: + - documentation + - title: 🔨 Dependency Upgrades + labels: + - dependencies diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..3dcf110 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,51 @@ +name: build and test + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + +jobs: + compliance: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Advance Security Policy as Code + uses: advanced-security/policy-as-code@v2.5.0 + with: + policy: it-at-m/policy-as-code + policy-path: default.yaml + token: ${{ secrets.GITHUB_TOKEN }} + argvs: "--disable-dependabot" + + build-maven: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Inject slug/short variables + uses: rlespinasse/github-slug-action@v4 + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: "17" + distribution: "temurin" + cache: "maven" + - name: Build with Maven + run: mvn -B verify -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN + - name: JUnit Report Action + uses: mikepenz/action-junit-report@v4.0.3 + if: success() || failure() # always run even if the previous step fails + with: + report_paths: "**/target/*-reports/TEST-*.xml" + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Build image + uses: docker/build-push-action@v5 + with: + context: ./microservice + push: false + tags: itatm/ezldap:${{ env.GITHUB_REF_NAME_SLUG }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..0c6e345 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,90 @@ +name: release + +on: + workflow_dispatch: + inputs: + branch: + description: "Branch to release from ?" + required: true + default: "main" + release-version: + description: "Release version ?" + required: true + release-tag: + description: "Release tag ?" + required: true + development-version: + description: "Next Development version ?" + required: true + +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: Check out Git repository + uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.branch }} + - name: Setup git user + uses: fregante/setup-git-user@v2 + + - name: Set up JDK 17 and OSSRH auth / GPG signing + uses: actions/setup-java@v4 + with: + java-version: 17 + distribution: "temurin" + cache: "maven" + server-id: "ossrh" + server-username: OSSRH_USERNAME + server-password: OSSRH_PASSWORD + gpg-private-key: ${{ secrets.gpg_private_key }} + gpg-passphrase: SIGN_KEY_PASS + + - name: Perform maven release + run: > + mvn -B -ntp release:prepare release:perform + -DreleaseVersion=${{ github.event.inputs.release-version }} + -DdevelopmentVersion=${{ github.event.inputs.development-version }} + -Dtag=${{ github.event.inputs.release-tag }} + -Darguments="-DskipTests" + env: + SIGN_KEY_PASS: ${{ secrets.gpg_passphrase }} + OSSRH_USERNAME: ${{ secrets.nexus_username }} + OSSRH_PASSWORD: ${{ secrets.nexus_password }} + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to DockerHub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_KEY }} + + - name: Build and push image + uses: docker/build-push-action@v5 + with: + context: ./microservice + push: true + tags: itatm/ezldap:${{ github.event.inputs.release-tag }},itatm/ezldap:latest + + - name: Create GitHub Release + id: create_release + uses: softprops/action-gh-release@v1 + with: + tag_name: ${{ github.event.inputs.release-tag }} + draft: false + prerelease: false + generate_release_notes: true + + - name: Update Docker Hub Description + uses: peter-evans/dockerhub-description@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_KEY }} + repository: itatm/ezldap + readme-filepath: ./IMAGE_README.md + short-description: ${{ github.event.repository.description }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6560f9d --- /dev/null +++ b/.gitignore @@ -0,0 +1,389 @@ +# Created by https://www.toptal.com/developers/gitignore/api/java,maven,eclipse,intellij,visualstudiocode,node +# Edit at https://www.toptal.com/developers/gitignore?templates=java,maven,eclipse,intellij,visualstudiocode,node + +### Eclipse ### +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.recommenders + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# PyDev specific (Python IDE for Eclipse) +*.pydevproject + +# CDT-specific (C/C++ Development Tooling) +.cproject + +# CDT- autotools +.autotools + +# Java annotation processor (APT) +.factorypath + +# PDT-specific (PHP Development Tools) +.buildpath + +# sbteclipse plugin +.target + +# Tern plugin +.tern-project + +# TeXlipse plugin +.texlipse + +# STS (Spring Tool Suite) +.springBeans + +# Code Recommenders +.recommenders/ + +# Annotation Processing +.apt_generated/ +.apt_generated_test/ + +# Scala IDE specific (Scala & Java development for Eclipse) +.cache-main +.scala_dependencies +.worksheet + +# Uncomment this line if you wish to ignore the project description file. +# Typically, this file would be tracked if it contains build/dependency configurations: +#.project + +### Eclipse Patch ### +# Spring Boot Tooling +.sts4-cache/ + +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Intellij Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +# https://plugins.jetbrains.com/plugin/7973-sonarlint +.idea/**/sonarlint/ + +# SonarQube Plugin +# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator-enh.xml +.idea/**/markdown-navigator/ + +# Cache file creation bug +# See https://youtrack.jetbrains.com/issue/JBR-2257 +.idea/$CACHE_FILE$ + +# CodeStream plugin +# https://plugins.jetbrains.com/plugin/12206-codestream +.idea/codestream.xml + +# Azure Toolkit for IntelliJ plugin +# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij +.idea/**/azureSettings.xml + +### Java ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +replay_pid* + +### Maven ### +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +# https://github.com/takari/maven-wrapper#usage-without-binary-jar +.mvn/wrapper/maven-wrapper.jar + +# Eclipse m2e generated files +# Eclipse Core +.project +# JDT-specific (Eclipse Java Development Tools) +.classpath + +### Node ### +# Logs +logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +### Node Patch ### +# Serverless Webpack directories +.webpack/ + +# Optional stylelint cache + +# SvelteKit build / generate output +.svelte-kit + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +# End of https://www.toptal.com/developers/gitignore/api/java,maven,eclipse,intellij,visualstudiocode,node + +# k6 result summary +load_performance.json \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..8a688ee --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["redhat.java"] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..cd63d1e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "java.format.settings.url": "https://raw.githubusercontent.com/it-at-m/itm-java-codeformat/main/formatter/src/main/resources/itm-java-codeformat/java_codestyle_formatter.xml", + "java.configuration.updateBuildConfiguration": "interactive" +} diff --git a/IMAGE_README.md b/IMAGE_README.md new file mode 100644 index 0000000..dcdb5e3 --- /dev/null +++ b/IMAGE_README.md @@ -0,0 +1,5 @@ +Official Docker image for [it-at-m/ezLDAP](https://github.com/it-at-m/ezLDAP). + +## Usage + +Please see GitHub README.md for documentation: diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..42e6705 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Landeshauptstadt München | it@M + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..28994f4 --- /dev/null +++ b/README.md @@ -0,0 +1,122 @@ +# ezLDAP - easy read access to LDAP + +**ezLDAP** enables easy read access to the internal LDAP directory of the City of Munich (LHM) by providing a custom tailored HTTP-based API. + +## Run + +To function properly, ezLDAP requires a directory schema with LHM customized attributes ([see example schema data](lib-core/src/test/resources/ldap)). To spin up such an environment, you can use the `docker-compose` file provided: + +```bash +docker-compose up +``` + +This spins up a openLDAP server with LHM schema extensions and some testdata on port `8389` and phpLDAPadmin on . You can use the [default admin credentials of osixia/openldap](https://github.com/osixia/docker-openldap) (User-DN `cn=admin,dc=example,dc=org`, Password `admin`) to connect. + +```bash +cd microservice +mvn spring-boot:run -Dspring-boot.run.jvmArguments="-Dezldap.ldap.url=ldap://localhost:8389 -Dezldap.ldap.user-dn=cn=admin,dc=example,dc=org -Dezldap.ldap.password=admin -Dezldap.ldap.user-search-base=o=users,dc=example,dc=org -Dezldap.ldap.ou-search-base=o=oubase,dc=example,dc=org" +``` + +## API Documentation + +The API of ezLDAP is documented via OpenAPI v3 and can be accessed via Swagger-UI: + +### Server cache + +Server caching is disabled by default, but can be activated with property `ezldap.cache.enabled=true`. + +When enabled, the LDAP queries are cached on the JVM heap, off-heap and on disk. + +The folder for disk caching can be configured by adjusting `ezldap.cache.disk.dir`. + +To further customize the caching, you can also store your own, adapted [`ezldap-ehcache.xml`](lib-spring\src\main\resources\ezldap-ehcache.xml) on the class path. + +## Integrate + +**ezLDAP** can also be embedded in existing applications using the `lib-core` or `lib-spring` modules. + +### lib-core + +Provides direct access to the LDAP via `LdapService`. + +Maven Dependency: + +```xml + + de.muenchen.oss.ezldap + ezLDAP-lib-core + latest-version + +``` + +Usage: + +```java +LdapService ldapService = new LdapService("ldaps://ldap.example.org:636", "<>", "<>", + "ou=users,dc=example,dc=org", // User Search Base + "ou=organisation,dc=example,dc=org" // OU Search Base +); + +// start using +Optional = ldapService.getPersonWithUID("erika.musterfrau"); +``` + +#### lib-spring + +Activates the REST API controller endpoint `/v1/ldap` in a Spring Boot application via a Spring `AutoConfiguration`. + +Maven Dependency: + +```xml + + de.muenchen.oss.ezldap + ezLDAP-lib-spring + latest-version + +``` + +Usage: + +```java +@SpringBootApplication +@EnableEzLDAP // enables '/v1/ldap' REST endpoints +public class MySpringBootApplication { + // [...] +} +``` + +required properties (e.g. in `application.properties`): + +```ini +ezldap.ldap.url=ldaps://ldap.example.org:636 +ezldap.ldap.user-dn=<> +ezldap.ldap.password=<> +ezldap.ldap.user-search-base="ou=users,dc=example,dc=org" +ezldap.ldap.ou-search-base="ou=organisation,dc=example,dc=org" +ezldap.cache.enabled=true +ezldap.cache.disk.dir=./target/cache +``` + +**Note**: When integrating into an existing Spring Boot application, the authentication configuration must be done via the Spring Security configuration of the application! For an example, see [SecurityConfiguration.java of the microservice](microservice/src/main/java/de/muenchen/oss/ezldap/config/SecurityConfiguration.java). + +## Contributing + +Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. + +If you have a suggestion that would make this better, please open an issue with the tag "enhancement", fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". +Don't forget to give the project a star! Thanks again! + +1. Open an issue with the tag "enhancement" +2. Fork the Project +3. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) +4. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) +5. Push to the Branch (`git push origin feature/AmazingFeature`) +6. Open a Pull Request + +## License + +Distributed under the MIT License. See [LICENSE](LICENSE) file for more information. + +## Contact + +it@M - diff --git a/client-v1/pom.xml b/client-v1/pom.xml new file mode 100644 index 0000000..9a3a717 --- /dev/null +++ b/client-v1/pom.xml @@ -0,0 +1,64 @@ + + + + 4.0.0 + + + de.muenchen.oss.ezldap + ezLDAP-parent + 1.0.0-SNAPSHOT + + + ezLDAP-client-v1 + ezLDAP :: client-v1 + + + + org.projectlombok + lombok + provided + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + + lombok.launch.AnnotationProcessorHider$AnnotationProcessor + + + + + + + + + diff --git a/client-v1/src/main/java/de/muenchen/oss/ezldap/client/v1/AnschriftDTO.java b/client-v1/src/main/java/de/muenchen/oss/ezldap/client/v1/AnschriftDTO.java new file mode 100644 index 0000000..3901f21 --- /dev/null +++ b/client-v1/src/main/java/de/muenchen/oss/ezldap/client/v1/AnschriftDTO.java @@ -0,0 +1,49 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap.client.v1; + +import java.io.Serializable; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author michael.prankl + * + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class AnschriftDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * Strasse und Hausnummer + */ + private String strasse; + private String plz; + private String ort; + +} diff --git a/client-v1/src/main/java/de/muenchen/oss/ezldap/client/v1/LdapBaseUserDTO.java b/client-v1/src/main/java/de/muenchen/oss/ezldap/client/v1/LdapBaseUserDTO.java new file mode 100644 index 0000000..f7bb0da --- /dev/null +++ b/client-v1/src/main/java/de/muenchen/oss/ezldap/client/v1/LdapBaseUserDTO.java @@ -0,0 +1,80 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap.client.v1; + +import java.io.Serializable; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; + +/** + * Repräsentiert ein LHM-Person im LDAP. + * + * @author michael.prankl + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@ToString(callSuper = false, onlyExplicitlyIncluded = true) +@EqualsAndHashCode(callSuper = false) +public class LdapBaseUserDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ToString.Include(rank = 0) + private String lhmObjectId; + + @ToString.Include(rank = 1) + private String uid; + + /** + * Anrede + */ + private String anrede; + + /** + * Vorname + */ + private String vorname; + + /** + * Nachname + */ + private String nachname; + + /** + * Vor- und Nachname. + */ + @ToString.Include(rank = 2) + private String cn; + + /** + * OU Kurzbezeichnung (z.B. ITM-KM23) + */ + @ToString.Include(rank = 3) + private String ou; + +} diff --git a/client-v1/src/main/java/de/muenchen/oss/ezldap/client/v1/LdapOuDTO.java b/client-v1/src/main/java/de/muenchen/oss/ezldap/client/v1/LdapOuDTO.java new file mode 100644 index 0000000..a8fd2ba --- /dev/null +++ b/client-v1/src/main/java/de/muenchen/oss/ezldap/client/v1/LdapOuDTO.java @@ -0,0 +1,67 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap.client.v1; + +import java.io.Serializable; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; + +/** + * Repräsentiert eine LHM-OU im LDAP. + * + * @author m.zollbrecht + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@ToString(callSuper = false, onlyExplicitlyIncluded = true) +@EqualsAndHashCode(callSuper = false) +public class LdapOuDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ToString.Include(rank = 0) + private String lhmObjectId; + + @ToString.Include(rank = 1) + private String ou; + + private String lhmOUKey; + private String lhmOULongname; + private String lhmOUShortname; + + private String postalCode; + private String street; + + private String mail; + private String telephoneNumber; + private String facsimileTelephoneNumber; + + private LdapUserDTO leitung; + private LdapUserDTO stellvertretung; + +} diff --git a/client-v1/src/main/java/de/muenchen/oss/ezldap/client/v1/LdapUserDTO.java b/client-v1/src/main/java/de/muenchen/oss/ezldap/client/v1/LdapUserDTO.java new file mode 100644 index 0000000..496c498 --- /dev/null +++ b/client-v1/src/main/java/de/muenchen/oss/ezldap/client/v1/LdapUserDTO.java @@ -0,0 +1,101 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap.client.v1; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; + +/** + * Repräsentiert ein LHM-Person im LDAP. + * + * @author michael.prankl + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@ToString(callSuper = true) +@EqualsAndHashCode(callSuper = true) +public class LdapUserDTO extends LdapBaseUserDTO { + + private static final long serialVersionUID = 1L; + + /** + * Erzeugt eine neue Instanz auf Basis des übergebenen {@link LdapBaseUserDTO}. + * + * @param baseUser ein {@link LdapBaseUserDTO} + */ + public LdapUserDTO(LdapBaseUserDTO baseUser) { + super(baseUser.getLhmObjectId(), baseUser.getUid(), baseUser.getAnrede(), baseUser.getVorname(), + baseUser.getNachname(), baseUser.getCn(), baseUser.getOu()); + } + + /** + * OU Langname (z.B. Servicebereich RBS) + */ + private String lhmOULongname; + + private String lhmObjectPath; + private String lhmOberOrga; + + /** + * lhmReferatName + */ + private String lhmReferatName; + + /** + * Funktion + */ + private String lhmFunctionalTitle; + + /** + * Amtsbezeichnung + */ + private String amtsbezeichnung; + /** + * Erreichbarkeit + */ + private String erreichbarkeit; + + private String mail; + private String lhmOrgaMail; + private String telephoneNumber; + private String facsimileTelephoneNumber; + /** + * Mobiltelefon + */ + private String mobile; + + /** + * Zimmernummer + */ + private String zimmer; + + private String personalTitle; + + private AnschriftDTO postanschrift; + private AnschriftDTO bueroanschrift; + +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..be350ff --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,51 @@ +# +# The MIT License +# Copyright © 2023 Landeshauptstadt München | it@M +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +name: ezldap +services: + openldap: + image: osixia/openldap:1.5.0 + # command: [--copy-service, --loglevel, debug] + command: [--copy-service] + volumes: + - ./lib-core/src/test/resources/ldap/schema/lhm.schema:/container/service/slapd/assets/config/bootstrap/schema/lhm.schema + - ./lib-core/src/test/resources/ldap/data:/container/service/slapd/assets/config/bootstrap/ldif/custom + environment: + - LDAP_ORGANISATION=Example Inc. + - LDAP_DOMAIN=example.org + - LDAP_READONLY_USER=true + - LDAP_READONLY_USER_USERNAME=readonly + - LDAP_READONLY_USER_PASSWORD=readonly + ports: + - 8389:389 + - 8636:636 + phpldapadmin: + image: osixia/phpldapadmin:0.9.0 + container_name: phpldapadmin + environment: + PHPLDAPADMIN_LDAP_HOSTS: "openldap" + PHPLDAPADMIN_HTTPS: "false" + ports: + - 8090:80 + depends_on: + - openldap diff --git a/lib-core/pom.xml b/lib-core/pom.xml new file mode 100644 index 0000000..c07c644 --- /dev/null +++ b/lib-core/pom.xml @@ -0,0 +1,113 @@ + + + + 4.0.0 + + de.muenchen.oss.ezldap + ezLDAP-parent + 1.0.0-SNAPSHOT + + ezLDAP-lib-core + ezLDAP :: lib-core + + + + + + org.mapstruct + mapstruct + + + org.slf4j + slf4j-api + + + + + org.springframework.ldap + spring-ldap-core + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + org.springframework.ldap + spring-ldap-test + test + + + org.springframework.boot + spring-boot-testcontainers + test + + + org.testcontainers + junit-jupiter + test + + + + org.projectlombok + lombok + provided + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.mapstruct + mapstruct-processor + ${org.mapstruct.version} + + + org.projectlombok + lombok + ${org.projectlombok.version} + + + org.projectlombok + lombok-mapstruct-binding + ${lombok-mapstruct-binding.version} + + + + + + + \ No newline at end of file diff --git a/lib-core/src/main/java/de/muenchen/oss/ezldap/core/AnschriftDTO.java b/lib-core/src/main/java/de/muenchen/oss/ezldap/core/AnschriftDTO.java new file mode 100644 index 0000000..91632bc --- /dev/null +++ b/lib-core/src/main/java/de/muenchen/oss/ezldap/core/AnschriftDTO.java @@ -0,0 +1,49 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap.core; + +import java.io.Serializable; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author michael.prankl + * + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class AnschriftDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * Strasse und Hausnummer + */ + private String strasse; + private String plz; + private String ort; + +} diff --git a/lib-core/src/main/java/de/muenchen/oss/ezldap/core/DtoMapper.java b/lib-core/src/main/java/de/muenchen/oss/ezldap/core/DtoMapper.java new file mode 100644 index 0000000..a49a63b --- /dev/null +++ b/lib-core/src/main/java/de/muenchen/oss/ezldap/core/DtoMapper.java @@ -0,0 +1,35 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap.core; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@Mapper +public interface DtoMapper { + + @Mapping(target = "leitung", ignore = true) + @Mapping(target = "stellvertretung", ignore = true) + LdapOuDTO toLdapOuDTO(LdapOuSearchResultDTO searchResult); + +} diff --git a/lib-core/src/main/java/de/muenchen/oss/ezldap/core/LdapBaseUserAttributesMapper.java b/lib-core/src/main/java/de/muenchen/oss/ezldap/core/LdapBaseUserAttributesMapper.java new file mode 100644 index 0000000..d4eafc6 --- /dev/null +++ b/lib-core/src/main/java/de/muenchen/oss/ezldap/core/LdapBaseUserAttributesMapper.java @@ -0,0 +1,84 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap.core; + +import java.util.regex.Pattern; + +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; + +import org.springframework.ldap.core.AttributesMapper; + +import lombok.extern.slf4j.Slf4j; + +/** + * @author michael.prankl + * + */ +@Slf4j +public class LdapBaseUserAttributesMapper implements AttributesMapper { + + private static final Pattern DOUBLE_PERCENTAGE_PATTERN = Pattern.compile("\\%\\%"); + + @Override + public LdapBaseUserDTO mapFromAttributes(Attributes attributes) throws NamingException { + LdapBaseUserDTO u = new LdapBaseUserDTO(); + + u.setLhmObjectId(safelyGet("lhmObjectId", attributes)); + u.setUid(safelyGet("uid", attributes)); + u.setAnrede(safelyGet("lhmTitle", attributes)); + u.setVorname(safelyGet("givenName", attributes)); + u.setNachname(safelyGet("sn", attributes)); + u.setCn(safelyGet("cn", attributes)); + u.setOu(safelyGet("ou", attributes)); + log.debug("Mapped user {}.", u); + return u; + } + + /** + * Liest den Value zu Attribut und ersetzt '%%' mit Line-Breaks. + * + * @param key the object key + * @param attributes the {@link Attributes} + * @return the value or null + */ + public static String safelyGet(String key, Attributes attributes) { + String value = null; + Attribute a = attributes.get(key); + if (a != null) { + try { + value = (String) a.get(); + if (value != null && value.contains("%%")) { + log.debug("Replacing '%%' in value {} with line-breaks", value); + value = DOUBLE_PERCENTAGE_PATTERN.matcher(value).replaceAll("\n"); + } + } catch (NamingException e) { + log.debug(String.format("Exception while accessing attribute '%s'"), key); + } + } + log.debug("Resolved attribute['{}']: {}", key, value); + return value; + } + +} diff --git a/lib-core/src/main/java/de/muenchen/oss/ezldap/core/LdapBaseUserDTO.java b/lib-core/src/main/java/de/muenchen/oss/ezldap/core/LdapBaseUserDTO.java new file mode 100644 index 0000000..6748907 --- /dev/null +++ b/lib-core/src/main/java/de/muenchen/oss/ezldap/core/LdapBaseUserDTO.java @@ -0,0 +1,80 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap.core; + +import java.io.Serializable; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; + +/** + * Repräsentiert ein LHM-Person im LDAP. + * + * @author michael.prankl + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@ToString(callSuper = false, onlyExplicitlyIncluded = true) +@EqualsAndHashCode(callSuper = false) +public class LdapBaseUserDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ToString.Include(rank = 0) + private String lhmObjectId; + + @ToString.Include(rank = 1) + private String uid; + + /** + * Anrede + */ + private String anrede; + + /** + * Vorname + */ + private String vorname; + + /** + * Nachname + */ + private String nachname; + + /** + * Vor- und Nachname. + */ + @ToString.Include(rank = 2) + private String cn; + + /** + * OU Kurzbezeichnung (z.B. ITM-KM23) + */ + @ToString.Include(rank = 3) + private String ou; + +} diff --git a/lib-core/src/main/java/de/muenchen/oss/ezldap/core/LdapOuAttributesMapper.java b/lib-core/src/main/java/de/muenchen/oss/ezldap/core/LdapOuAttributesMapper.java new file mode 100644 index 0000000..2703b80 --- /dev/null +++ b/lib-core/src/main/java/de/muenchen/oss/ezldap/core/LdapOuAttributesMapper.java @@ -0,0 +1,65 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap.core; + +import static de.muenchen.oss.ezldap.core.LdapBaseUserAttributesMapper.safelyGet; + +import javax.naming.NamingException; +import javax.naming.directory.Attributes; + +import org.springframework.ldap.core.AttributesMapper; + +import lombok.extern.slf4j.Slf4j; + +/** + * @author m.zollbrecht + */ +@Slf4j +public class LdapOuAttributesMapper implements AttributesMapper { + + @Override + public LdapOuSearchResultDTO mapFromAttributes(Attributes attributes) throws NamingException { + LdapOuSearchResultDTO ou = new LdapOuSearchResultDTO(); + + ou.setLhmObjectId(safelyGet("lhmObjectId", attributes)); + ou.setOu(safelyGet("ou", attributes)); + + ou.setLhmOUKey(safelyGet("lhmOUKey", attributes)); + ou.setLhmOULongname(safelyGet("lhmOULongname", attributes)); + ou.setLhmOUShortname(safelyGet("lhmOUShortname", attributes)); + + ou.setPostalCode(safelyGet("postalCode", attributes)); + ou.setStreet(safelyGet("street", attributes)); + + ou.setMail(safelyGet("mail", attributes)); + ou.setTelephoneNumber(safelyGet("telephoneNumber", attributes)); + ou.setFacsimileTelephoneNumber(safelyGet("facsimileTelephoneNumber", attributes)); + + ou.setLhmOUManager(safelyGet("lhmOUManager", attributes)); + ou.setLhmOU2ndManager(safelyGet("lhmOU2ndManager", attributes)); + + log.debug("Mapped ou {}.", ou); + return ou; + } + +} diff --git a/lib-core/src/main/java/de/muenchen/oss/ezldap/core/LdapOuDTO.java b/lib-core/src/main/java/de/muenchen/oss/ezldap/core/LdapOuDTO.java new file mode 100644 index 0000000..e3d54cb --- /dev/null +++ b/lib-core/src/main/java/de/muenchen/oss/ezldap/core/LdapOuDTO.java @@ -0,0 +1,67 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap.core; + +import java.io.Serializable; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; + +/** + * Repräsentiert eine LHM-OU im LDAP. + * + * @author m.zollbrecht + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@ToString(callSuper = false, onlyExplicitlyIncluded = true) +@EqualsAndHashCode(callSuper = false) +public class LdapOuDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ToString.Include(rank = 0) + private String lhmObjectId; + + @ToString.Include(rank = 1) + private String ou; + + private String lhmOUKey; + private String lhmOULongname; + private String lhmOUShortname; + + private String postalCode; + private String street; + + private String mail; + private String telephoneNumber; + private String facsimileTelephoneNumber; + + private LdapUserDTO leitung; + private LdapUserDTO stellvertretung; + +} diff --git a/lib-core/src/main/java/de/muenchen/oss/ezldap/core/LdapOuSearchResultDTO.java b/lib-core/src/main/java/de/muenchen/oss/ezldap/core/LdapOuSearchResultDTO.java new file mode 100644 index 0000000..e6e95c0 --- /dev/null +++ b/lib-core/src/main/java/de/muenchen/oss/ezldap/core/LdapOuSearchResultDTO.java @@ -0,0 +1,72 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap.core; + +import java.io.Serializable; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; + +/** + * Repräsentiert eine LHM-OU im LDAP. + * + * @author michael.prankl + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@ToString(callSuper = false, onlyExplicitlyIncluded = true) +@EqualsAndHashCode(callSuper = false) +public class LdapOuSearchResultDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ToString.Include(rank = 110) + private String lhmObjectId; + + @ToString.Include(rank = 100) + private String ou; + + private String lhmOUKey; + private String lhmOULongname; + @ToString.Include(rank = 50) + private String lhmOUShortname; + + private String postalCode; + private String street; + + private String mail; + private String telephoneNumber; + private String facsimileTelephoneNumber; + + /** Leitung UID */ + @ToString.Include(rank = 90) + private String lhmOUManager; + /** Stellvertretung UID */ + @ToString.Include(rank = 80) + private String lhmOU2ndManager; + +} diff --git a/lib-core/src/main/java/de/muenchen/oss/ezldap/core/LdapService.java b/lib-core/src/main/java/de/muenchen/oss/ezldap/core/LdapService.java new file mode 100644 index 0000000..90441c9 --- /dev/null +++ b/lib-core/src/main/java/de/muenchen/oss/ezldap/core/LdapService.java @@ -0,0 +1,433 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap.core; + +import static org.springframework.ldap.query.LdapQueryBuilder.query; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import javax.naming.Name; +import javax.naming.ldap.LdapName; + +import org.springframework.ldap.NameNotFoundException; +import org.springframework.ldap.core.AttributesMapper; +import org.springframework.ldap.core.LdapTemplate; +import org.springframework.ldap.core.support.LdapContextSource; +import org.springframework.ldap.query.LdapQuery; +import org.springframework.ldap.query.LdapQueryBuilder; +import org.springframework.ldap.query.SearchScope; + +import lombok.extern.slf4j.Slf4j; + +/** + * @author michael.prankl + * + */ +@Slf4j +public class LdapService { + + public static final int MAX_SEARCH_RESULTS = 100; + + private static final String ATTRIBUTE_UID = "uid"; + private static final String ATTRIBUTE_OU = "ou"; + private static final String ATTRIBUTE_LHM_OBJECT_ID = "lhmObjectId"; + private static final String ATTRIBUTE_OBJECT_CLASS = "objectClass"; + private static final String LHM_ORGANIZATIONAL_UNIT = "lhmOrganizationalUnit"; + private static final String LHM_OU_SHORTNAME = "lhmOUShortname"; + private static final String LHM_PERSON = "lhmPerson"; + private static final String LHM_OBJECT_PATH = "lhmObjectPath"; + private static final String PERSON = "person"; + + private final LdapTemplate ldapTemplate; + private final LdapUserAttributesMapper ldapUserAttributesMapper; + private final LdapBaseUserAttributesMapper ldapBaseUserAttributesMapper; + private final LdapOuAttributesMapper ldapOuAttributesMapper; + private final DtoMapper mapper; + private final String userSearchBase; + private final String ouSearchBase; + + /** + * Erzeugt eine neue Instanz. + * + * @param ldapTemplate ein {@link LdapTemplate} für LDAP + * @param ldapUserAttributesMapper ein {@link LdapUserAttributesMapper} + * @param ldapBaseUserAttributesMapper ein {@link LdapBaseUserAttributesMapper} + * @param ldapOuAttributesMapper ein {@link LdapOuAttributesMapper} + * @param modelMapper ein {@link DtoMapper} + * @param userSearchBase Search-Base für User (DN) + * @param ouSearchBase Search-Base für OUs (DN) + */ + public LdapService(final LdapTemplate ldapTemplate, + final LdapUserAttributesMapper ldapUserAttributesMapper, + final LdapBaseUserAttributesMapper ldapBaseUserAttributesMapper, + final LdapOuAttributesMapper ldapOuAttributesMapper, + final DtoMapper modelMapper, + final String userSearchBase, + final String ouSearchBase) { + this.ldapTemplate = ldapTemplate; + this.ldapUserAttributesMapper = ldapUserAttributesMapper; + this.ldapBaseUserAttributesMapper = ldapBaseUserAttributesMapper; + this.ldapOuAttributesMapper = ldapOuAttributesMapper; + this.mapper = modelMapper; + this.userSearchBase = userSearchBase; + this.ouSearchBase = ouSearchBase; + } + + /** + * Erzeugt eine Instanz. + * + * @param ldapUrl die LDAP-URL (z.B. 'ldaps://ldap.example.org:636') + * @param ldapUserDn LDAP-Zugangsuser (DN) + * @param ldapPassword LDAP-Zugangsuser Passwort + * @param userSearchBase die Search-Base für User (z.B. 'o=example,c=org') + * @param ouSearchBase die Search-Base für OU's (z.B. 'o=example,c=org') + */ + public LdapService(final String ldapUrl, final String ldapUserDn, final String ldapPassword, final String userSearchBase, + final String ouSearchBase) { + final LdapContextSource ldapContextSource = new LdapContextSource(); + ldapContextSource.setUrl(ldapUrl); + ldapContextSource.setUserDn(ldapUserDn); + ldapContextSource.setPassword(ldapPassword); + ldapContextSource.afterPropertiesSet(); + this.ldapTemplate = new LdapTemplate(ldapContextSource); + this.ldapBaseUserAttributesMapper = new LdapBaseUserAttributesMapper(); + this.ldapOuAttributesMapper = new LdapOuAttributesMapper(); + this.ldapUserAttributesMapper = new LdapUserAttributesMapper(this.ldapBaseUserAttributesMapper); + this.mapper = new DtoMapperImpl(); + this.userSearchBase = userSearchBase; + this.ouSearchBase = ouSearchBase; + } + + /** + * Ruft die Person zur angegebenen lhmObjectID ab. + * + * @param lhmObjectId eine LHM Object ID + * @return den Mitarbeiter als {@link LdapUserDTO} + */ + public Optional getPerson(final String lhmObjectId) { + log.info("Searching LDAP for Person[lhmObjectId={}]...", lhmObjectId); + final LdapQuery query = LdapQueryBuilder.query().base(this.userSearchBase).countLimit(1).searchScope(SearchScope.SUBTREE) + .where(ATTRIBUTE_OBJECT_CLASS).is(PERSON) + .and(ATTRIBUTE_OBJECT_CLASS).is(LHM_PERSON) + .and(ATTRIBUTE_LHM_OBJECT_ID).is(lhmObjectId); + final List searchResults = this.ldapTemplate.search(query, this.ldapUserAttributesMapper); + if (searchResults.size() == 1) { + return Optional.of(searchResults.get(0)); + } else { + return Optional.empty(); + } + } + + /** + * Ruft die Person zur angegebenen lhmObjectID ab. + * + * @param uid eine UID ("vorname.nachname") + * @return den Mitarbeiter als {@link LdapUserDTO} + */ + public Optional getPersonWithUID(final String uid) { + log.info("Searching LDAP for Person[uid={}]...", uid); + final LdapQuery query = LdapQueryBuilder.query().base(this.userSearchBase).countLimit(1).searchScope(SearchScope.SUBTREE) + .where(ATTRIBUTE_OBJECT_CLASS).is(PERSON) + .and(ATTRIBUTE_OBJECT_CLASS).is(LHM_PERSON) + .and(ATTRIBUTE_LHM_OBJECT_ID).isPresent() // es gibt Personen ohne lhmObjectId ¯\_(ツ)_/¯ + .and(ATTRIBUTE_UID).is(uid); + final List searchResults = this.ldapTemplate.search(query, this.ldapUserAttributesMapper); + if (searchResults.size() == 1) { + return Optional.of(searchResults.get(0)); + } else { + return Optional.empty(); + } + } + + /** + * Sucht alle Personen, die der angegeben Organisationseinheit zugeordnet sind. + * + * @param ou die Kurzbezeichnung der OU (z.B. "ITM-KM21") + * @return die Ergebnisliste oder {@link Optional#empty()}, wenn keine OU zur Kurzbezeichnung + * existiert + */ + public Optional> findPersonsByOuShortcode(final String ou) { + log.info("Performing LDAP lookup for persons in ou='{}' ...", ou); + // check if OU exists before searching for users + final LdapQuery queryForOu = LdapQueryBuilder.query().base(this.ouSearchBase) + .searchScope(SearchScope.SUBTREE) + .where(ATTRIBUTE_OBJECT_CLASS).is(LHM_ORGANIZATIONAL_UNIT) + .and(LHM_OU_SHORTNAME).is(ou); + final List foundOus = this.ldapTemplate.search(queryForOu, + (AttributesMapper) attributes -> (String) attributes.get(LHM_OU_SHORTNAME).get()); + log.debug("Found OUs for shortcode '{}': {}", ou, foundOus); + if (foundOus.isEmpty()) { + return Optional.empty(); + } else { + final LdapQuery query = LdapQueryBuilder.query().base(this.userSearchBase) + .searchScope(SearchScope.SUBTREE) + .where(ATTRIBUTE_OBJECT_CLASS).is(PERSON) + .and(ATTRIBUTE_OBJECT_CLASS).is(LHM_PERSON) + .and(ATTRIBUTE_LHM_OBJECT_ID).isPresent() // es gibt Personen ohne lhmObjectId ¯\_(ツ)_/¯ + .and(ATTRIBUTE_OU).is(ou); + return Optional.of(this.ldapTemplate.search(query, this.ldapBaseUserAttributesMapper)); + } + + } + + /** + * Sucht nach Personen, deren UID mit der angegeben Phrase beginnt. + * + * @param searchPhrase die Suchphrase (mindestens 3 Zeichen, * Zählen nicht) + * @param resultLimit maximale Treffernzahl + * @return die Ergebnisliste + * @throws IllegalArgumentException wenn die Suchphrase zu kurz ist + */ + public List searchFor(final String searchPhrase, int resultLimit) { + if (searchPhrase == null || searchPhrase.replace("*", "").trim().length() < 3) { + throw new IllegalArgumentException("Keine Suchphrase angegeben bzw. die Suchphrase ist zu kurz"); + } + log.info("Performing LDAP lookup for uid like {}...", searchPhrase); + if (resultLimit > MAX_SEARCH_RESULTS) { + log.warn("Angegebenes Result-Limit ist größer als das erlaubte Maximallimit."); + resultLimit = MAX_SEARCH_RESULTS; + } + final LdapQuery query = LdapQueryBuilder.query().base(this.userSearchBase).countLimit(resultLimit) + .searchScope(SearchScope.SUBTREE) + .where(ATTRIBUTE_OBJECT_CLASS).is(PERSON) + .and(ATTRIBUTE_OBJECT_CLASS).is(LHM_PERSON) + .and(ATTRIBUTE_LHM_OBJECT_ID).isPresent() // es gibt Personen ohne lhmObjectId ¯\_(ツ)_/¯ + .and(ATTRIBUTE_UID).like(searchPhrase); + return this.ldapTemplate.search(query, this.ldapBaseUserAttributesMapper); + } + + /** + * Holt die Daten zu einer Ou. + * + * @param lhmObjectId der gewünschten Ou + * @return Daten der Ou + */ + public Optional getOu(final String lhmObjectId) { + log.info("Searching LDAP for Ou[lhmObjectId={}]...", lhmObjectId); + final LdapQuery query = LdapQueryBuilder.query().base(this.ouSearchBase).countLimit(1).searchScope(SearchScope.SUBTREE) + .where(ATTRIBUTE_OBJECT_CLASS).is(LHM_ORGANIZATIONAL_UNIT) + .and(ATTRIBUTE_LHM_OBJECT_ID).is(lhmObjectId); + final List searchResults = this.ldapTemplate.search(query, this.ldapOuAttributesMapper); + if (searchResults.size() == 1) { + final LdapOuSearchResultDTO searchResultDTO = searchResults.get(0); + log.debug("Found OU with [lhmObjectId={}]: {}", lhmObjectId, searchResultDTO); + return this.resolveManagersForOu(searchResultDTO); + } else { + log.debug("Found no OU with [lhmObjectId={}].", lhmObjectId); + return Optional.empty(); + } + } + + /** + * Holt die Daten zu einer OU. + * + * @param ou OU-Shortcode der Organisationseinheit (z.B. "ITM-KM21") + * @return Daten der Ou + */ + public Optional findOuByShortcode(final String ou) { + log.info("Searching LDAP for ou[lhmOUShortname={}]...", ou); + final LdapQuery query = LdapQueryBuilder.query().base(this.ouSearchBase).countLimit(1).searchScope(SearchScope.SUBTREE) + .where(ATTRIBUTE_OBJECT_CLASS).is(LHM_ORGANIZATIONAL_UNIT) + .and(LHM_OU_SHORTNAME).is(ou); + final List searchResults = this.ldapTemplate.search(query, this.ldapOuAttributesMapper); + if (searchResults.size() == 1) { + final LdapOuSearchResultDTO searchResultDTO = searchResults.get(0); + log.debug("Found OU with [lhmOUShortname={}]: {}", ou, searchResultDTO); + return this.resolveManagersForOu(searchResultDTO); + } else { + log.debug("Found no OU with [lhmOUShortname={}].", ou); + return Optional.empty(); + } + } + + /** + * Liest den OU Tree (Abteilungsbaum) eines user ein + * + * @param lhmObjectId des Users + * @return OU Tree + */ + public Optional> findOuTreeByUserId(final String lhmObjectId) { + log.debug("Get LDAP ou tree for user {}.", lhmObjectId); + final LdapQuery query = query() + .searchScope(SearchScope.SUBTREE) + .base(this.userSearchBase) + .where(ATTRIBUTE_LHM_OBJECT_ID).is(lhmObjectId); + return this.findOuTree(query); + } + + /** + * Liest den OU Tree (Abteilungsbaum) einer OU ein + * + * @param ouShortCode Shortcode der OU (z.B. ITM-KM21) + * @return OU Tree + */ + public Optional> findOuTreeByOuShortCode(final String ouShortCode) { + log.debug("Get LDAP ou tree for ou {}.", ouShortCode); + final LdapQuery ouQuery = query() + .searchScope(SearchScope.SUBTREE) + .base(this.userSearchBase) + .countLimit(1) + .where("cn").is(ouShortCode); + return this.findOuTree(ouQuery); + } + + /** + * Helper method to find the ou tree for a given query. The query can be a user or ou query. + * + * @see #findOuTreeByUserId + * @see #findOuTreeByOuShortCode + * + * @param query + * @return OU Tree + */ + private Optional> findOuTree(final LdapQuery query) { + List ldapNames = this.ldapTemplate.search(query, (AttributesMapper) attrs -> { + if (null != attrs.get(LHM_OBJECT_PATH)) { + return new LdapName((String) attrs.get(LHM_OBJECT_PATH).get()); + } + return null; + }); + // clean ldapNames from null values + ldapNames = ldapNames.stream().filter(Objects::nonNull).toList(); + if (ldapNames.isEmpty()) { + log.debug("Found no ou tree"); + return Optional.empty(); + } + + final LdapName ldapName = ldapNames.get(0); + + final List ouTree = new ArrayList<>(); + + for (int i = 1; i <= ldapName.getRdns().size(); i++) { + final Name partialDN = ldapName.getPrefix(i); + + try { + log.debug("Searching for dn='{} & objectClass='{}' ...", partialDN, LHM_ORGANIZATIONAL_UNIT); + final LdapQuery ouObjectReferenceQuery = query() + .searchScope(SearchScope.OBJECT) + .base(partialDN) + .countLimit(1) + .where(ATTRIBUTE_OBJECT_CLASS).is(LHM_ORGANIZATIONAL_UNIT); + List ouShortnames = this.ldapTemplate.search(ouObjectReferenceQuery, (AttributesMapper) attrs -> { + if (null != attrs.get(LHM_OU_SHORTNAME)) { + return (String) attrs.get(LHM_OU_SHORTNAME).get(); + } + return null; + }); + ouTree.addAll(ouShortnames.stream().filter(Objects::nonNull).toList()); + } catch (final NameNotFoundException ex) { + log.warn("No shortCode found for dn {}. Query failed with {} exception", partialDN, ex.getClass().getName()); + } + } + ouTree.replaceAll(String::toUpperCase); + return Optional.of(ouTree); + } + + private Optional resolveManagersForOu(final LdapOuSearchResultDTO searchResultDTO) { + if (searchResultDTO.getLhmOUManager() != null || searchResultDTO.getLhmOU2ndManager() != null) { + // Manager Attribute sind gesetzt, ermittle Leitung/Stvt. darüber + return Optional.of(this.ermittleLeitungByManagerAttributes(searchResultDTO)); + } else { + // suche Personen in der OU mit lhmRankInOu = 01 / 03 (Leitung / Stellvertretung) + return Optional.of(this.ermittleLeitungByRankInOu(searchResultDTO)); + } + } + + /** + * Ermittelt die Leitung/Stellvertretung zur OU, in dem Personen in der OU gesucht werden, die + * mit dem Attribut lhmRankInOU und Wert 01 (Leitung) bzw. 03 (Stellvertretung) versehen sind. + * + * @param searchResultDTO das {@link LdapOuSearchResultDTO} + * @return das gemappte {@link LdapOuDTO} + */ + private LdapOuDTO ermittleLeitungByRankInOu(final LdapOuSearchResultDTO searchResultDTO) { + final LdapOuDTO ouDTO = this.mapper.toLdapOuDTO(searchResultDTO); + final Optional leitung = this.lookupPersonInOuWithRank(searchResultDTO.getLhmObjectId(), "01"); + if (leitung.isPresent()) { + log.debug("Found Leitung (lhmRankInOU=01): {}", leitung.get().getUid()); + ouDTO.setLeitung(leitung.get()); + } else { + log.debug("No Leitung found (no person in OU with lhmRankInOu=01)."); + } + final Optional stellvertretung = this.lookupPersonInOuWithRank(searchResultDTO.getLhmObjectId(), "03"); + if (stellvertretung.isPresent()) { + log.debug("Found Stellvertretung (lhmRankInOu=03): {}", stellvertretung.get().getUid()); + ouDTO.setStellvertretung(stellvertretung.get()); + } else { + log.debug("No Stellvertretung found (no person in OU with lhmRankInOu=03)."); + } + return ouDTO; + } + + private Optional lookupPersonInOuWithRank(final String lhmObjectIdOfOu, final String rankMarker) { + final LdapQuery query = LdapQueryBuilder.query().base(this.userSearchBase).countLimit(1) + .searchScope(SearchScope.SUBTREE) + .where(ATTRIBUTE_OBJECT_CLASS).is(PERSON) + .and(ATTRIBUTE_OBJECT_CLASS).is(LHM_PERSON) + .and(ATTRIBUTE_LHM_OBJECT_ID).isPresent() // es gibt Personen ohne lhmObjectId ¯\_(ツ)_/¯ + .and("lhmObjectReference").is(lhmObjectIdOfOu) + .and("lhmRankInOu").is(rankMarker); + final List searchResult = this.ldapTemplate.search(query, this.ldapUserAttributesMapper); + if (searchResult.size() == 1) { + return Optional.of(searchResult.get(0)); + } else { + return Optional.empty(); + } + } + + /** + * Ermittelt die Leitung/Stellvertrung zur OU mittels dem im SearchResult vorhandenen Attributen + * lhmOUManager und lhmOU2ndManager. + * + * @param searchResultDTO das {@link LdapOuSearchResultDTO} + * @return das gemappte {@link LdapOuDTO} + */ + private LdapOuDTO ermittleLeitungByManagerAttributes(final LdapOuSearchResultDTO searchResultDTO) { + final LdapOuDTO ouDTO = this.mapper.toLdapOuDTO(searchResultDTO); + if (searchResultDTO.getLhmOUManager() != null) { + log.debug("Looking up lhmOUManager = {} ...", searchResultDTO.getLhmOUManager()); + final Optional manager = this.getPersonWithUID(searchResultDTO.getLhmOUManager()); + if (manager.isPresent()) { + log.debug("Found lhmOUManager person: {}", manager.get()); + ouDTO.setLeitung(manager.get()); + } else { + log.debug("lhmOUManager - No person found for uid: {}", searchResultDTO.getLhmOUManager()); + } + } + if (searchResultDTO.getLhmOU2ndManager() != null) { + log.debug("Looking up lhmOU2ndManager = {} ...", searchResultDTO.getLhmOU2ndManager()); + final Optional manager = this.getPersonWithUID(searchResultDTO.getLhmOU2ndManager()); + if (manager.isPresent()) { + log.debug("Found lhmOU2ndManager person: {}", manager.get()); + ouDTO.setStellvertretung(manager.get()); + } else { + log.debug("lhmOU2ndManager - No person found for uid: {}", searchResultDTO.getLhmOU2ndManager()); + } + } + return ouDTO; + } + +} diff --git a/lib-core/src/main/java/de/muenchen/oss/ezldap/core/LdapUserAttributesMapper.java b/lib-core/src/main/java/de/muenchen/oss/ezldap/core/LdapUserAttributesMapper.java new file mode 100644 index 0000000..75a269a --- /dev/null +++ b/lib-core/src/main/java/de/muenchen/oss/ezldap/core/LdapUserAttributesMapper.java @@ -0,0 +1,84 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap.core; + +import static de.muenchen.oss.ezldap.core.LdapBaseUserAttributesMapper.safelyGet; + +import javax.naming.NamingException; +import javax.naming.directory.Attributes; + +import org.springframework.ldap.core.AttributesMapper; + +import lombok.extern.slf4j.Slf4j; + +/** + * @author michael.prankl + */ +@Slf4j +public class LdapUserAttributesMapper implements AttributesMapper { + + private final LdapBaseUserAttributesMapper baseAttributeMapper; + + /** + * Erzeugt eine Instanze + * + * @param baseAttributeMapper ein {@link LdapBaseUserAttributesMapper} + */ + public LdapUserAttributesMapper(LdapBaseUserAttributesMapper baseAttributeMapper) { + this.baseAttributeMapper = baseAttributeMapper; + } + + @Override + public LdapUserDTO mapFromAttributes(Attributes attributes) throws NamingException { + LdapBaseUserDTO baseUser = this.baseAttributeMapper.mapFromAttributes(attributes); + LdapUserDTO u = new LdapUserDTO(baseUser); + u.setBueroanschrift(new AnschriftDTO()); + u.setPostanschrift(new AnschriftDTO()); + + u.setLhmOULongname(safelyGet("lhmOULongname", attributes)); + u.setLhmObjectPath(safelyGet("lhmObjectPath", attributes)); + u.setLhmOberOrga(safelyGet("lhmOberOrga", attributes)); + u.setLhmReferatName(safelyGet("lhmReferatName", attributes)); + u.setLhmFunctionalTitle(safelyGet("lhmFunctionalTitle", attributes)); + u.setAmtsbezeichnung(safelyGet("title", attributes)); + u.setErreichbarkeit(safelyGet("lhmWorkHours", attributes)); + u.setMail(safelyGet("mail", attributes)); + u.setLhmOrgaMail(safelyGet("lhmOrgaMail", attributes)); + u.setTelephoneNumber(safelyGet("telephoneNumber", attributes)); + u.setFacsimileTelephoneNumber(safelyGet("facsimileTelephoneNumber", attributes)); + u.setMobile(safelyGet("mobile", attributes)); + u.setZimmer(safelyGet("roomNumber", attributes)); + u.setPersonalTitle(safelyGet("personalTitle", attributes)); + + // Anschriften + u.getPostanschrift().setOrt(safelyGet("l", attributes)); + u.getPostanschrift().setPlz(safelyGet("postalCode", attributes)); + u.getPostanschrift().setStrasse(safelyGet("street", attributes)); + u.getBueroanschrift().setOrt(safelyGet("lhmOfficeLocalityName", attributes)); + u.getBueroanschrift().setPlz(safelyGet("lhmOfficePostalCode", attributes)); + u.getBueroanschrift().setStrasse(safelyGet("lhmOfficeStreetAddress", attributes)); + log.debug("Mapped user {}.", u); + return u; + } + +} diff --git a/lib-core/src/main/java/de/muenchen/oss/ezldap/core/LdapUserDTO.java b/lib-core/src/main/java/de/muenchen/oss/ezldap/core/LdapUserDTO.java new file mode 100644 index 0000000..7d587b9 --- /dev/null +++ b/lib-core/src/main/java/de/muenchen/oss/ezldap/core/LdapUserDTO.java @@ -0,0 +1,101 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap.core; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; + +/** + * Repräsentiert ein LHM-Person im LDAP. + * + * @author michael.prankl + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@ToString(callSuper = true) +@EqualsAndHashCode(callSuper = true) +public class LdapUserDTO extends LdapBaseUserDTO { + + private static final long serialVersionUID = 1L; + + /** + * Erzeugt eine neue Instanz auf Basis des übergebenen {@link LdapBaseUserDTO}. + * + * @param baseUser ein {@link LdapBaseUserDTO} + */ + public LdapUserDTO(LdapBaseUserDTO baseUser) { + super(baseUser.getLhmObjectId(), baseUser.getUid(), baseUser.getAnrede(), baseUser.getVorname(), + baseUser.getNachname(), baseUser.getCn(), baseUser.getOu()); + } + + /** + * OU Langname (z.B. Servicebereich RBS) + */ + private String lhmOULongname; + + private String lhmObjectPath; + private String lhmOberOrga; + + /** + * lhmReferatName + */ + private String lhmReferatName; + + /** + * Funktion + */ + private String lhmFunctionalTitle; + + /** + * Amtsbezeichnung + */ + private String amtsbezeichnung; + /** + * Erreichbarkeit + */ + private String erreichbarkeit; + + private String mail; + private String lhmOrgaMail; + private String telephoneNumber; + private String facsimileTelephoneNumber; + /** + * Mobiltelefon + */ + private String mobile; + + /** + * Zimmernummer + */ + private String zimmer; + + private AnschriftDTO postanschrift; + private AnschriftDTO bueroanschrift; + + private String personalTitle; + +} diff --git a/lib-core/src/test/java/de/muenchen/oss/ezldap/core/LdapServiceIntegrationTest.java b/lib-core/src/test/java/de/muenchen/oss/ezldap/core/LdapServiceIntegrationTest.java new file mode 100644 index 0000000..8a2090a --- /dev/null +++ b/lib-core/src/test/java/de/muenchen/oss/ezldap/core/LdapServiceIntegrationTest.java @@ -0,0 +1,202 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap.core; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.LoggerFactory; +import org.springframework.ldap.core.LdapTemplate; +import org.springframework.ldap.core.support.LdapContextSource; +import org.testcontainers.containers.BindMode; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.output.Slf4jLogConsumer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.MountableFile; + +/** + * Integrationtest zu {@link LdapService} mit einem embedded LDAP Server. + * + * @author michael.prankl + * + */ +@Testcontainers +public class LdapServiceIntegrationTest { + + private LdapService sut; + + private static final int OPENLDAP_EXPOSED_PORT = 389; + private static final String USER_BASE = "o=users,dc=example,dc=org"; + private static final String ORG_BASE = "o=oubase,dc=example,dc=org"; + private static final String LDAP_DOMAIN = "example.org"; + + @Container + private static final GenericContainer openldapContainer = new GenericContainer<>("osixia/openldap:1.5.0") + .withNetworkAliases("openldap") + .withCommand("--copy-service --loglevel debug") + .withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("openldap"))) + .withEnv("LDAP_ORGANISATION", "Example Inc.") + .withEnv("LDAP_DOMAIN", LDAP_DOMAIN) + .withExposedPorts(OPENLDAP_EXPOSED_PORT) + .waitingFor(Wait.forLogMessage(".*\\/container\\/run\\/process\\/slapd\\/run started as PID.*\\n", 1)) + .withFileSystemBind(MountableFile.forClasspathResource("/ldap/schema/lhm.schema").getResolvedPath(), + "/container/service/slapd/assets/config/bootstrap/schema/lhm.schema", + BindMode.READ_ONLY) + .withFileSystemBind(MountableFile.forClasspathResource("/ldap/data").getResolvedPath(), + "/container/service/slapd/assets/config/bootstrap/ldif/custom", + BindMode.READ_ONLY); + + private LdapTemplate ldapTemplate(final LdapContextSource contextSource) { + return new LdapTemplate(contextSource); + } + + private LdapContextSource contextSource(final int port) { + final LdapContextSource ldapContextSource = new LdapContextSource(); + ldapContextSource.setUrl("ldap://localhost:" + port); + ldapContextSource.setUserDn("cn=admin,dc=example,dc=org"); + ldapContextSource.setPassword("admin"); + // ldapContextSource.setAnonymousReadOnly(true); + // we need to call this manually if no Spring context present + ldapContextSource.afterPropertiesSet(); + return ldapContextSource; + } + + @BeforeEach + public void beforeEach() { + Integer exposedPort = openldapContainer.getMappedPort(OPENLDAP_EXPOSED_PORT); + System.out.println(exposedPort); + final LdapContextSource contextSource = this.contextSource(exposedPort); + final LdapBaseUserAttributesMapper baseUserAttributesMapper = new LdapBaseUserAttributesMapper(); + this.sut = new LdapService(this.ldapTemplate(contextSource), + new LdapUserAttributesMapper(baseUserAttributesMapper), + baseUserAttributesMapper, new LdapOuAttributesMapper(), new DtoMapperImpl(), USER_BASE, + ORG_BASE); + } + + @Test + void get_ou_with_manager_attributes() { + final Optional ouOpt = this.sut.getOu("30002"); + assertThat(ouOpt).isPresent(); + final LdapOuDTO ou = ouOpt.get(); + assertThat(ou.getFacsimileTelephoneNumber()).isNull(); + assertThat(ou.getLhmObjectId()).isEqualTo("30002"); + assertThat(ou.getLhmOUKey()).isNull(); + assertThat(ou.getLhmOULongname()).isNull(); + assertThat(ou.getLhmOUShortname()).isEqualTo("RBS-A-1"); + assertThat(ou.getMail()).isEqualTo("rbs.a1@" + LDAP_DOMAIN); + assertThat(ou.getOu()).isEqualTo("Abteilung 1"); + // check leitung/stvt lookup + assertThat(ou.getLeitung().getCn()).isEqualTo("Maxi Mustermann"); + assertThat(ou.getStellvertretung().getCn()).isEqualTo("Petra Mustermann"); + } + + @Test + void get_ou_without_manager_attributes() { + final Optional ouOpt = this.sut.getOu("30003"); + assertThat(ouOpt).isPresent(); + final LdapOuDTO ou = ouOpt.get(); + assertThat(ou.getFacsimileTelephoneNumber()).isEqualTo("123123"); + assertThat(ou.getLhmObjectId()).isEqualTo("30003"); + assertThat(ou.getLhmOUKey()).isEqualTo("09707139"); + assertThat(ou.getLhmOULongname()).isEqualTo("Referat, Abteilung 2"); + assertThat(ou.getLhmOUShortname()).isEqualTo("RBS-A-2"); + assertThat(ou.getMail()).isEqualTo("rbs.a2@" + LDAP_DOMAIN); + assertThat(ou.getOu()).isEqualTo("Abteilung 2"); + // check leitung/stvt lookup + assertThat(ou.getLeitung().getCn()).isEqualTo("Peter Lustig"); + assertThat(ou.getStellvertretung().getCn()).isEqualTo("Petra Lustig"); + } + + @Test + void get_person() { + final Optional person = this.sut.getPerson("20011"); + assertThat(person).isPresent(); + assertThat(person.get().getCn()).isEqualTo("Maxi Mustermann"); + } + + @Test + void find_persons_by_ou_shortcode_exists() { + final Optional> result = this.sut.findPersonsByOuShortcode("rbs"); + assertThat(result).isPresent(); + assertThat(result.get()).extracting("uid").containsExactlyInAnyOrder("maxi.mustermann", "petra.mustermann", "peter.lustig", "petra.lustig", "john.doe"); + } + + @Test + void find_persons_by_ou_shortcode_ou_not_existing() { + final Optional> result = this.sut.findPersonsByOuShortcode("hammaned"); + assertThat(result).isEmpty(); + } + + @Test + void search_person_with_wildcard() { + final List result = this.sut.searchFor("*.mustermann", 10); + assertThat(result).extracting("uid").containsExactlyInAnyOrder("maxi.mustermann", "petra.mustermann"); + } + + @Test + void find_ou_with_shortname() { + final Optional result = this.sut.findOuByShortcode("RBS-A-1"); + assertThat(result).isPresent(); + assertThat(result.get().getLhmObjectId()).isEqualTo("30002"); + } + + @Test + void find_ou_with_shortname_not_found() { + final Optional result = this.sut.findOuByShortcode("hammaned"); + assertThat(result).isEmpty(); + } + + @Test + void find_ou_tree_by_ou_shortcode() { + final Optional> result = this.sut.findOuTreeByOuShortCode("RBS-A-1"); + assertThat(result).isPresent(); + Assertions.assertEquals(List.of("LHM", "RBS", "RBS-A-1"), result.get()); + } + + @Test + void find_ou_tree_by_ou_shortcode_not_found() { + final Optional> result = this.sut.findOuTreeByOuShortCode("hammaned"); + assertThat(result).isEmpty(); + } + + @Test + void find_ou_tree_by_user() { + final Optional> result = this.sut.findOuTreeByUserId("99999"); + assertThat(result).isPresent(); + Assertions.assertEquals(List.of("LHM", "RBS", "RBS-A-1"), result.get()); + } + + @Test + void find_ou_tree_by_user_not_found() { + final Optional> result = this.sut.findOuTreeByUserId("00000"); + assertThat(result).isEmpty(); + } + +} diff --git a/lib-core/src/test/resources/ldap/data/data.ldif b/lib-core/src/test/resources/ldap/data/data.ldif new file mode 100644 index 0000000..7ae96f9 --- /dev/null +++ b/lib-core/src/test/resources/ldap/data/data.ldif @@ -0,0 +1,210 @@ +# User Base +dn: o=users,{{ LDAP_BASE_DN }} +objectClass: lhmObject +objectClass: organization +objectClass: top +o: users +lhmObjectID: 33000 + +# OU Base +dn: o=oubase,{{ LDAP_BASE_DN }} +objectClass: lhmObject +objectClass: lhmOrganizationalUnit +objectClass: organization +objectClass: top +o: oubase +l: München +lhmObjectID: 342 +lhmOfficeLocalityName: Marienplatz 8 +lhmOfficePostalCode: 80331 +lhmOUShortname: lhm +postalCode: 80331 +street: Marienplatz 8 +telephoneNumber: 233-00 + +# Dummy-OU +# ou=Referat für Bildung und Sport,o=oubase,c=de +dn: ou=Referat für Bildung und Sport,o=oubase,{{ LDAP_BASE_DN }} +objectClass: lhmObject +objectClass: lhmOrganizationalUnit +objectClass: organizationalUnit +objectClass: top +lhmObjectID: 30001 +ou: Referat für Bildung und Sport +lhmOUShortname: RBS +lhmRankInOu: 40 + +# Dummy-OU2 +dn: ou=Abteilung 1,ou=Referat für Bildung und Sport,o=oubase,{{ LDAP_BASE_DN }} +objectClass: lhmObject +objectClass: lhmOrganizationalUnit +objectClass: organizationalUnit +objectClass: top +lhmObjectID: 30002 +ou: Abteilung 1 +mail: rbs.a1@{{ LDAP_DOMAIN }} +lhmOUShortname: RBS-A-1 +lhmOU2ndManager: petra.mustermann +lhmOUManager: maxi.mustermann +lhmRankInOu: 41 + +# Dummy-OU +dn: ou=Abteilung 2,ou=Referat für Bildung und Sport,o=oubase,{{ LDAP_BASE_DN }} +objectClass: lhmObject +objectClass: lhmOrganizationalUnit +objectClass: organizationalUnit +objectClass: top +lhmObjectID: 30003 +ou: Abteilung 2 +lhmOUKey: 09707139 +mail: rbs.a2@{{ LDAP_DOMAIN }} +facsimileTelephoneNumber: 123123 +lhmOUShortname: RBS-A-2 +lhmOULongname: Referat, Abteilung 2 +lhmRankInOu: 42 + +# Maxi Mustermann +# Leitung Haus für Kinder Josef-Felder-Str. 43a (lhmOUManager bei OU) +dn: cn=Maxi Mustermann,o=users,{{ LDAP_BASE_DN }} +objectClass: inetOrgPerson +objectClass: lhmObject +objectClass: lhmPerson +objectClass: person +objectClass: top +cn: Maxi Mustermann +lhmObjectID: 20011 +uid: maxi.mustermann +l: München +lhmFax: 321321 +lhmTitle: Herr +mail: maxi.mustermann@{{ LDAP_DOMAIN }} +ou: rbs +roomNumber: 1 +telephoneNumber: 123123 +title: Dr. +sn: Person +lhmOfficePostalCode: 80000 +lhmOfficeStreetAddress: Teststr. 11 + +# Petra Mustermann +# Stvt Haus für Kinder Josef-Felder-Str. 43a (lhmOU2ndManager bei OU) +dn: cn=Petra Mustermann,o=users,{{ LDAP_BASE_DN }} +objectClass: inetOrgPerson +objectClass: lhmObject +objectClass: lhmPerson +objectClass: person +objectClass: top +cn: Petra Mustermann +lhmObjectID: 20012 +uid: petra.mustermann +l: München +lhmFax: 321321 +lhmTitle: Herr +mail: petra.mustermann@{{ LDAP_DOMAIN }} +ou: rbs +roomNumber: 1 +telephoneNumber: 123123 +title: Dr. +sn: Person +lhmOfficePostalCode: 80000 +lhmOfficeStreetAddress: Teststr. 11 + +# Peter Lustig +# Leitung Abteilung 2 via lhmRankInOu 01 und lhmObjectReference +dn: cn=Peter Lustig,o=users,{{ LDAP_BASE_DN }} +objectClass: inetOrgPerson +objectClass: lhmObject +objectClass: lhmPerson +objectClass: person +objectClass: top +cn: Peter Lustig +lhmObjectID: 20013 +uid: peter.lustig +l: München +lhmFax: 321321 +lhmTitle: Herr +mail: peter.lustig@{{ LDAP_DOMAIN }} +ou: rbs +roomNumber: 1 +telephoneNumber: 123123 +title: Dr. +sn: Person +lhmOfficePostalCode: 80000 +lhmOfficeStreetAddress: Teststr. 11 +lhmObjectReference: 30003 +lhmRankInOu: 01 + +# Petra Lustig +# Stvt Abteilung 2 via lhmRankInOu 03 und lhmObjectReference +dn: cn=Petra Lustig,o=users,{{ LDAP_BASE_DN }} +objectClass: inetOrgPerson +objectClass: lhmObject +objectClass: lhmPerson +objectClass: person +objectClass: top +cn: Petra Lustig +lhmObjectID: 20014 +uid: petra.lustig +l: München +lhmFax: 321321 +lhmTitle: Herr +mail: petra.lustig@{{ LDAP_DOMAIN }} +ou: rbs +roomNumber: 1 +telephoneNumber: 123123 +title: Dr. +sn: Person +lhmOfficePostalCode: 80000 +lhmOfficeStreetAddress: Teststr. 11 +lhmObjectReference: 30003 +lhmRankInOu: 03 + +# John Doe +dn: cn=John Doe,o=users,{{ LDAP_BASE_DN }} +objectClass: inetOrgPerson +objectClass: lhmObject +objectClass: lhmPerson +objectClass: person +objectClass: top +cn: John Doe +lhmObjectID: 99999 +uid: john.doe +l: München +lhmFax: 321321 +lhmTitle: Herr +mail: john.doe@{{ LDAP_DOMAIN }} +ou: rbs +roomNumber: 1 +telephoneNumber: 123123 +title: Dr. +sn: Person +lhmOfficePostalCode: 80000 +lhmOfficeStreetAddress: Teststr. 11 +lhmObjectPath: ou=Abteilung 1,ou=Referat für Bildung und Sport,o=oubase,{{ LDAP_BASE_DN }} + + +# ou tree search query results +dn: cn=rbs,o=users,{{ LDAP_BASE_DN }} +uniqueMember: uid=john.doe,ou=users,{{ LDAP_BASE_DN }} +cn: rbs +lhmObjectReference: 30001 +lhmObjectID: 111111111 +objectClass: lhmObject +objectClass: groupOfUniqueNames +objectClass: top +description: Automatische Gruppe +lhmObjectPath: ou=Referat für Bildung und Sport,o=oubase,{{ LDAP_BASE_DN }} + +# ou tree search query results +dn: cn=rbs-a-1,o=users,{{ LDAP_BASE_DN }} +uniqueMember: uid=john.doe,ou=users,{{ LDAP_BASE_DN }} +cn: rbs-a-1 +lhmObjectReference: 30002 +lhmObjectID: 111111112 +objectClass: lhmObject +objectClass: groupOfUniqueNames +objectClass: top +description: Automatische Gruppe +lhmObjectPath: ou=Abteilung 1,ou=Referat für Bildung und Sport,o=oubase,{{ LDAP_BASE_DN }} + + diff --git a/lib-core/src/test/resources/ldap/schema/lhm.schema b/lib-core/src/test/resources/ldap/schema/lhm.schema new file mode 100644 index 0000000..4752c24 --- /dev/null +++ b/lib-core/src/test/resources/ldap/schema/lhm.schema @@ -0,0 +1,131 @@ +attributetype ( 1.3.6.1.4.1.24376.1.102 NAME 'lhmOrgaMail' + DESC 'Wollmux LHM LDAP attribute type' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + SINGLE-VALUE + USAGE userApplications ) +attributetype ( 1.3.6.1.4.1.24376.1.106 NAME 'lhmOberOrga' + DESC 'Jumper Server User' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + SINGLE-VALUE + USAGE userApplications ) +attributetype ( 1.3.6.1.4.1.24376.1.11 NAME 'lhmFax' + EQUALITY 2.5.13.2 + SUBSTR 2.5.13.4 + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{32767} + USAGE userApplications ) +attributetype ( 1.3.6.1.4.1.24376.1.16 NAME 'lhmTitle' + EQUALITY 2.5.13.2 + SUBSTR 2.5.13.4 + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{32767} + USAGE userApplications ) +attributetype ( 1.3.6.1.4.1.24376.1.17 NAME 'lhmOUShortname' + EQUALITY 2.5.13.2 + SUBSTR 2.5.13.4 + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{32} + USAGE userApplications ) +attributetype ( 1.3.6.1.4.1.24376.1.2 NAME 'lhmFunctionalTitle' + EQUALITY 2.5.13.2 + SUBSTR 2.5.13.4 + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{64} + USAGE userApplications ) +attributetype ( 1.3.6.1.4.1.24376.1.22 NAME 'lhmRankInOu' + EQUALITY 2.5.13.2 + SUBSTR 2.5.13.4 + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{32767} + SINGLE-VALUE + USAGE userApplications ) +attributetype ( 1.3.6.1.4.1.24376.1.23 NAME 'lhmRankInOuFunction' + EQUALITY 2.5.13.2 + SUBSTR 2.5.13.4 + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{32767} + SINGLE-VALUE + USAGE userApplications ) +attributetype ( 1.3.6.1.4.1.24376.1.27 NAME 'lhmOULongname' + EQUALITY 2.5.13.2 + SUBSTR 2.5.13.4 + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{32767} + SINGLE-VALUE + USAGE userApplications ) +attributetype ( 1.3.6.1.4.1.24376.1.29 NAME 'lhmOUKey' + EQUALITY 2.5.13.2 + SUBSTR 2.5.13.4 + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{9} + SINGLE-VALUE + USAGE userApplications ) +attributetype ( 1.3.6.1.4.1.24376.1.31 NAME 'lhmOU2ndManager' + EQUALITY 2.5.13.2 + SUBSTR 2.5.13.4 + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{32767} + USAGE userApplications ) +attributetype ( 1.3.6.1.4.1.24376.1.32 NAME 'lhmObjectID' + EQUALITY 2.5.13.2 + SUBSTR 2.5.13.4 + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + SINGLE-VALUE + USAGE userApplications ) +attributetype ( 1.3.6.1.4.1.24376.1.33 NAME 'lhmOUManager' + EQUALITY 2.5.13.2 + SUBSTR 2.5.13.4 + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{32767} + USAGE userApplications ) +attributetype ( 1.3.6.1.4.1.24376.1.34 NAME 'lhmOUManager2' + EQUALITY 2.5.13.2 + SUBSTR 2.5.13.4 + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{32767} + USAGE userApplications ) +attributetype ( 1.3.6.1.4.1.24376.1.35 NAME 'lhmOU2ndManager2' + EQUALITY 2.5.13.2 + SUBSTR 2.5.13.4 + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{32767} + USAGE userApplications ) +attributetype ( 1.3.6.1.4.1.24376.1.42 NAME 'lhmWorkHours' + EQUALITY 2.5.13.2 + SUBSTR 2.5.13.4 + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} + SINGLE-VALUE + USAGE userApplications ) +attributetype ( 1.3.6.1.4.1.24376.1.5 NAME 'lhmOfficePostalCode' + EQUALITY 2.5.13.2 + SUBSTR 2.5.13.4 + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{40} + USAGE userApplications ) +attributetype ( 1.3.6.1.4.1.24376.1.6 NAME 'lhmOfficeStreetAddress' + EQUALITY 2.5.13.2 + SUBSTR 2.5.13.4 + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} + USAGE userApplications ) +attributetype ( 1.3.6.1.4.1.24376.1.7 NAME 'lhmOfficeLocalityName' + EQUALITY 2.5.13.2 + SUBSTR 2.5.13.4 + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} + USAGE userApplications ) +attributetype ( 1.3.6.1.4.1.24376.1.86 NAME 'lhmReferatName' + DESC 'Name der Dienststelle' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + USAGE userApplications ) +attributetype ( 1.3.6.1.4.1.24376.1.97 NAME 'lhmObjectPath' + EQUALITY 2.5.13.2 + SUBSTR 2.5.13.4 + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{32767} + SINGLE-VALUE + USAGE userApplications ) +attributetype ( 1.3.6.1.4.1.24376.1.98 NAME 'lhmObjectReference' + EQUALITY 2.5.13.2 + SUBSTR 2.5.13.4 + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{15} + SINGLE-VALUE + USAGE userApplications ) +objectclass ( 1.3.6.1.4.1.24376.2.2 NAME 'lhmPerson' + SUP top + AUXILIARY + MAY ( lhmOberOrga $ lhmOrgaMail $ mail $ personalTitle $ lhmFunctionalTitle $ lhmOfficePostalCode $ lhmOfficeStreetAddress $ lhmOfficeLocalityName $ lhmFax $ lhmTitle $ lhmRankInOu $ lhmRankInOuFunction $ lhmWorkHours $ o $ ou $ employeeNumber $ lhmReferatName ) ) +objectclass ( 1.3.6.1.4.1.24376.2.5 NAME 'lhmOrganizationalUnit' + SUP top + AUXILIARY + MAY ( uid $ mail $ manager $ secretary $ lhmOfficePostalCode $ lhmOfficeStreetAddress $ lhmOfficeLocalityName $ lhmFax $ lhmOUShortname $ lhmRankInOu $ lhmRankInOuFunction $ lhmOULongname $ lhmOUKey $ lhmOU2ndManager $ lhmOUManager $ lhmOUManager2 $ lhmOU2ndManager2 $ lhmWorkHours $ cn $ sn $ o $ lhmReferatName ) ) +objectclass ( 1.3.6.1.4.1.24376.2.7 NAME 'lhmObject' + SUP top + AUXILIARY + MAY ( lhmOUShortname $ lhmOULongname $ lhmObjectID $ lhmObjectPath $ lhmObjectReference ) ) diff --git a/lib-spring/pom.xml b/lib-spring/pom.xml new file mode 100644 index 0000000..351e9b8 --- /dev/null +++ b/lib-spring/pom.xml @@ -0,0 +1,131 @@ + + + + 4.0.0 + + de.muenchen.oss.ezldap + ezLDAP-parent + 1.0.0-SNAPSHOT + + ezLDAP-lib-spring + ezLDAP :: lib-spring + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-cache + + + + + org.ehcache + ehcache + jakarta + + + javax.cache + cache-api + + + + + io.swagger.core.v3 + swagger-annotations + 2.2.19 + + + + + org.springframework.boot + spring-boot-starter-validation + + + + org.projectlombok + lombok + provided + + + + + org.mapstruct + mapstruct + + + + + + de.muenchen.oss.ezldap + ezLDAP-lib-core + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.mapstruct + mapstruct-processor + ${org.mapstruct.version} + + + org.projectlombok + lombok + ${org.projectlombok.version} + + + org.projectlombok + lombok-mapstruct-binding + ${lombok-mapstruct-binding.version} + + + org.springframework.boot + spring-boot-configuration-processor + ${spring-boot.version} + + + + + + + + \ No newline at end of file diff --git a/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/CachingConfiguration.java b/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/CachingConfiguration.java new file mode 100644 index 0000000..e2a72fe --- /dev/null +++ b/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/CachingConfiguration.java @@ -0,0 +1,78 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap.spring; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Arrays; + +import javax.cache.CacheManager; +import javax.cache.Caching; +import javax.cache.spi.CachingProvider; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cache.annotation.CachingConfigurer; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cache.jcache.JCacheCacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.ResourceUtils; + +import lombok.extern.slf4j.Slf4j; + +/** + * @author michael.prankl + */ +@Configuration +@ConditionalOnProperty(name = "ezldap.cache.enabled", havingValue = "true") +@EnableCaching +@Slf4j +public class CachingConfiguration implements CachingConfigurer { + + @Bean + EhCachePropertiesReplacer replacer() { + EhCachePropertiesReplacer replacer = new EhCachePropertiesReplacer(); + // pass through spring environment property as System property (that is resolved in ehcache.xml) + replacer.setEhcahePropertyNames(Arrays.asList("ezldap.cache.disk.dir")); + return replacer; + } + + @Override + @Bean + public org.springframework.cache.CacheManager cacheManager() { + CachingProvider cachingProvider = Caching.getCachingProvider(); + CacheManager cacheManager; + try { + URL url = ResourceUtils.getURL("classpath:ezldap-ehcache.xml"); + log.info("Configuring caching for ezLDAP from {} ...", url.toString()); + cacheManager = cachingProvider.getCacheManager( + url.toURI(), + getClass().getClassLoader()); + return new JCacheCacheManager(cacheManager); + } catch (IOException | URISyntaxException e) { + throw new RuntimeException("ezLDAP EHCache configuration failed.", e); + } + } + +} diff --git a/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/EhCachePropertiesReplacer.java b/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/EhCachePropertiesReplacer.java new file mode 100644 index 0000000..9205d09 --- /dev/null +++ b/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/EhCachePropertiesReplacer.java @@ -0,0 +1,60 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap.spring; + +import java.util.List; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.EnvironmentAware; +import org.springframework.core.env.Environment; + +/** + * @author michael.prankl + */ +public class EhCachePropertiesReplacer implements BeanFactoryPostProcessor, EnvironmentAware { + + private Environment environment; + + private List ehcachePropertyNames; + + public void setEhcahePropertyNames(List ehcachePropertyNames) { + this.ehcachePropertyNames = ehcachePropertyNames; + } + + public void setEnvironment(Environment environment) { + this.environment = environment; + } + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + for (String ehcahePropertyName : ehcachePropertyNames) { + String ehcachePropertyValue = environment.getProperty(ehcahePropertyName); + if (ehcachePropertyValue != null) { + System.setProperty(ehcahePropertyName, ehcachePropertyValue); + } + } + + } +} diff --git a/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/EnableEzLDAP.java b/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/EnableEzLDAP.java new file mode 100644 index 0000000..1a20015 --- /dev/null +++ b/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/EnableEzLDAP.java @@ -0,0 +1,37 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap.spring; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Import; + +@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME) +@Target(value = { java.lang.annotation.ElementType.TYPE }) +@Documented +@Import(EnableEzLDAPConfiguration.class) +public @interface EnableEzLDAP { + +} diff --git a/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/EnableEzLDAPConfiguration.java b/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/EnableEzLDAPConfiguration.java new file mode 100644 index 0000000..0754dda --- /dev/null +++ b/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/EnableEzLDAPConfiguration.java @@ -0,0 +1,38 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap.spring; + +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +/** + * @author michael.prankl + * + */ +@Configuration +@ComponentScan(basePackageClasses = EnableEzLDAPConfiguration.class) +@ConfigurationPropertiesScan(basePackageClasses = EnableEzLDAPConfiguration.class) +public class EnableEzLDAPConfiguration { + +} diff --git a/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/LdapConfiguration.java b/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/LdapConfiguration.java new file mode 100644 index 0000000..a780b9e --- /dev/null +++ b/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/LdapConfiguration.java @@ -0,0 +1,76 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap.spring; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.ldap.core.LdapTemplate; +import org.springframework.ldap.core.support.LdapContextSource; + +import de.muenchen.oss.ezldap.core.DtoMapperImpl; +import de.muenchen.oss.ezldap.core.LdapBaseUserAttributesMapper; +import de.muenchen.oss.ezldap.core.LdapOuAttributesMapper; +import de.muenchen.oss.ezldap.core.LdapService; +import de.muenchen.oss.ezldap.core.LdapUserAttributesMapper; +import de.muenchen.oss.ezldap.spring.props.EzLdapConfigurationProperties; +import de.muenchen.oss.ezldap.spring.rest.v1.LdapServiceAdapter; +import lombok.extern.slf4j.Slf4j; + +/** + * @author michael.prankl + */ +@Configuration +@Slf4j +public class LdapConfiguration { + + @Bean + LdapContextSource ldapContextSource(final EzLdapConfigurationProperties props) { + final LdapContextSource contextSource = new LdapContextSource(); + contextSource.setUrl(props.getLdap().getUrl()); + contextSource.setUserDn(props.getLdap().getUserDn()); + contextSource.setPassword(props.getLdap().getPassword()); + log.info("Initiating LDAP connection with url='{}' and user-dn='{}'.", props.getLdap().getUrl(), + props.getLdap().getUserDn()); + return contextSource; + } + + @Bean + LdapTemplate ldapTemplate(final LdapContextSource ldapContextSource) { + return new LdapTemplate(ldapContextSource); + } + + @Bean + LdapService ldapService(final LdapTemplate template, final EzLdapConfigurationProperties props) { + final LdapBaseUserAttributesMapper ldapBaseUserAttributesMapper = new LdapBaseUserAttributesMapper(); + final LdapOuAttributesMapper ldapOuAttributesMapper = new LdapOuAttributesMapper(); + final LdapUserAttributesMapper ldapUserAttributesMapper = new LdapUserAttributesMapper(ldapBaseUserAttributesMapper); + return new LdapService(template, ldapUserAttributesMapper, ldapBaseUserAttributesMapper, ldapOuAttributesMapper, + new DtoMapperImpl(), props.getLdap().getUserSearchBase(), props.getLdap().getOuSearchBase()); + } + + @Bean + LdapServiceAdapter ldapServiceAdapter(final LdapService ldapService) { + return new LdapServiceAdapter(ldapService); + } + +} diff --git a/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/props/EzLdapCacheConfigurationProperties.java b/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/props/EzLdapCacheConfigurationProperties.java new file mode 100644 index 0000000..c8930af --- /dev/null +++ b/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/props/EzLdapCacheConfigurationProperties.java @@ -0,0 +1,41 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap.spring.props; + +import lombok.Data; + +/** + * @author michael.prankl + * + */ +@Data +public class EzLdapCacheConfigurationProperties { + + /** + * Aktiviert das Server Caching. + */ + private boolean enabled = true; + + private EzLdapCacheDiskConfigurationProperties disk; + +} diff --git a/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/props/EzLdapCacheDiskConfigurationProperties.java b/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/props/EzLdapCacheDiskConfigurationProperties.java new file mode 100644 index 0000000..7140d80 --- /dev/null +++ b/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/props/EzLdapCacheDiskConfigurationProperties.java @@ -0,0 +1,36 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap.spring.props; + +import lombok.Data; + +/** + * @author michael.prankl + * + */ +@Data +public class EzLdapCacheDiskConfigurationProperties { + + private String dir = "./target/cache"; + +} diff --git a/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/props/EzLdapConfigurationProperties.java b/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/props/EzLdapConfigurationProperties.java new file mode 100644 index 0000000..32f845d --- /dev/null +++ b/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/props/EzLdapConfigurationProperties.java @@ -0,0 +1,57 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap.spring.props; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; +import org.springframework.context.annotation.Configuration; +import org.springframework.validation.annotation.Validated; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * @author michael.prankl + * + */ +@Data +@Validated +@Configuration +@ConfigurationProperties(prefix = "ezldap") +public class EzLdapConfigurationProperties { + + @NestedConfigurationProperty + @Valid + private EzLdapLdapConnectionProperties ldap; + + @NestedConfigurationProperty + private EzLdapCacheConfigurationProperties cache; + + /** + * ezLDAP API path prefix, default "". + */ + @NotNull + private String apiPath = ""; + +} diff --git a/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/props/EzLdapLdapConnectionProperties.java b/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/props/EzLdapLdapConnectionProperties.java new file mode 100644 index 0000000..93924d9 --- /dev/null +++ b/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/props/EzLdapLdapConnectionProperties.java @@ -0,0 +1,59 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap.spring.props; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +/** + * @author michael.prankl + * + */ +@Data +public class EzLdapLdapConnectionProperties { + + /** + * LDAP Verbindungs-URL. + */ + @NotBlank + private String url; + /** + * User-DN für Authentifizierung am LDAP. + */ + private String userDn; + /** + * Passwort für Authentifizierung am LDAP. + */ + private String password; + /** + * Search-Base für Personen. + */ + @NotBlank + private String userSearchBase; + /** + * Search-Base für Organistationseinheiten. + */ + @NotBlank + private String ouSearchBase; + +} diff --git a/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/rest/v1/LdapOuController.java b/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/rest/v1/LdapOuController.java new file mode 100644 index 0000000..0b3abb7 --- /dev/null +++ b/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/rest/v1/LdapOuController.java @@ -0,0 +1,151 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap.spring.rest.v1; + +import java.time.Duration; +import java.util.List; +import java.util.Optional; + +import org.springframework.http.CacheControl; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import de.muenchen.oss.ezldap.core.LdapOuDTO; +import de.muenchen.oss.ezldap.core.LdapUserDTO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; + +/** + * @author michael.prankl + * @author m.zollbrecht + */ +@Controller +@Slf4j +@RequestMapping(value = "${ezldap.api-path:}/v1/ldap", produces = MediaType.APPLICATION_JSON_VALUE) +@Tag(name = "OU", description = "Lese/Suchzugriffe für Organisationseinheiten (OU)") +public class LdapOuController { + + private final LdapServiceAdapter ldapService; + + /** + * Erzeugt eine Instanz. + * + * @param ldapServiceAdapter eine {@link LdapServiceAdapter} + */ + public LdapOuController(final LdapServiceAdapter ldapServiceAdapter) { + this.ldapService = ldapServiceAdapter; + } + + /** + * GET /ldap/ou/{lhmObjectId} + * + * @param lhmObjectId eine lhmObjectId + * @return {@link LdapUserDTO} + */ + @GetMapping("/ou/{lhmObjectId}") + @Operation( + summary = "Lookup einer OU via lhmObjectId", operationId = "getOuByLhmObjectId", method = "GET", parameters = { + @Parameter(name = "lhmObjectId", required = true, description = "lhmObjectId der OU", example = "112043571") + }, + responses = { + @ApiResponse(responseCode = "200", description = "OK") + } + ) + public ResponseEntity getOuByLhmObjectId( + @PathVariable(name = "lhmObjectId") final String lhmObjectId) { + log.info("Incoming LDAP OU request for lhmObjectId: {}", lhmObjectId); + final Optional ou = this.ldapService.getOu(lhmObjectId); + if (ou.isPresent()) { + return ResponseEntity.ok() + .cacheControl(CacheControl.maxAge(Duration.ofMinutes(10))) + .body(ou.get()); + } else { + return ResponseEntity.notFound().build(); + } + } + + /** + * GET /ldap/outree/{ouShortCode} + * + * @param ouShortCode eine OU Kurzbezeichnung (z.B. "ITM-KM21") + * @return Liste der OU-Shortcodes des OU-Baums + */ + @GetMapping("/outree/{ouShortCode}") + @Operation( + summary = "Lookup des OU Baums via OU Kurzbezeichnung", operationId = "ouTree", method = "GET", parameters = { + @Parameter(name = "ouShortCode", required = true, description = "OU Kurzbezeichnung (z.B. ITM-KM21)", example = "ITM-KM21") + }, + responses = { + @ApiResponse(responseCode = "200", description = "OK") + } + ) + public ResponseEntity> getOUTreeByOUShortcode( + @PathVariable(name = "ouShortCode") final String ouShortCode) { + log.info("Incoming LDAP OU request for ou: {}", ouShortCode); + final Optional> result = this.ldapService.findOuTree(ouShortCode); + if (result.isPresent()) { + return ResponseEntity.ok() + .cacheControl(CacheControl.maxAge(Duration.ofMinutes(10))) + .body(result.get()); + } else { + return ResponseEntity.notFound().build(); + } + } + + /** + * GET /ldap/ou/search/findByOu?ou={ou} + * + * @param ou ein OU-Shortcode (z.B. "ITM-KM21") + * @return {@link LdapUserDTO} + */ + @GetMapping("/ou/search/findByOu") + @Operation( + summary = "Lookup einer OU via OU Kurzbezeichnung", operationId = "findOuByOuShortcode", method = "GET", parameters = { + @Parameter(name = "ou", required = true, description = "OU Kurzbezeichnung (z.B. ITM-KM21)", example = "ITM-KM21") + }, + responses = { + @ApiResponse(responseCode = "200", description = "OK") + } + ) + public ResponseEntity findOuByOuShortcode( + @RequestParam(name = "ou") final String ou) { + log.info("Incoming LDAP OU request for ou shortcode: {}", ou); + final Optional result = this.ldapService.findOuByOuShortcode(ou); + if (result.isPresent()) { + return ResponseEntity.ok() + .cacheControl(CacheControl.maxAge(Duration.ofMinutes(10))) + .body(result.get()); + } else { + return ResponseEntity.notFound().build(); + } + } + +} diff --git a/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/rest/v1/LdapServiceAdapter.java b/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/rest/v1/LdapServiceAdapter.java new file mode 100644 index 0000000..15fd1fc --- /dev/null +++ b/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/rest/v1/LdapServiceAdapter.java @@ -0,0 +1,97 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap.spring.rest.v1; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.Cacheable; + +import de.muenchen.oss.ezldap.core.LdapBaseUserDTO; +import de.muenchen.oss.ezldap.core.LdapOuDTO; +import de.muenchen.oss.ezldap.core.LdapService; +import de.muenchen.oss.ezldap.core.LdapUserDTO; + +import java.util.List; +import java.util.Optional; + +/** + * @author michael.prankl + * + */ +@Slf4j +public class LdapServiceAdapter { + + private final LdapService ldapService; + + public LdapServiceAdapter(final LdapService ldapService) { + this.ldapService = ldapService; + } + + @Cacheable("ousByLhmObjectId") + public Optional getOu(final String lhmObjectId) { + log.debug("Looking up ou with lhmObject '{}' via ldapService...", lhmObjectId); + return this.ldapService.getOu(lhmObjectId); + } + + @Cacheable("ousByOuShortcode") + public Optional findOuByOuShortcode(final String ou) { + log.debug("Looking up ou with lhmOUShortname '{}' via ldapService...", ou); + return this.ldapService.findOuByShortcode(ou); + } + + @Cacheable("usersByLhmObjectId") + public Optional getPerson(final String lhmObjectId) { + log.debug("Looking up person with lhmObject '{}' via ldapService...", lhmObjectId); + return this.ldapService.getPerson(lhmObjectId); + } + + @Cacheable("usersByUid") + public Optional getPersonWithUID(final String uid) { + log.debug("Looking up person with uid '{}' via ldapService...", uid); + return this.ldapService.getPersonWithUID(uid); + } + + @Cacheable("usersByUidSearch") + public List searchFor(final String uid, final Integer size) { + log.debug("Searching for person '{}' (size: {}) via ldapService...", uid, size); + return this.ldapService.searchFor(uid, size); + } + + @Cacheable("usersByOuShortcode") + public Optional> findPersonsByOuShortcode(final String ou) { + log.debug("Looking up persons in ou with shortcode '{}' ...", ou); + return this.ldapService.findPersonsByOuShortcode(ou); + } + + @Cacheable("ouTreeByOuShortcode") + public Optional> findOuTree(final String ouShortCode) { + log.debug("Looking up ou tree for ou '{}' ...", ouShortCode); + return this.ldapService.findOuTreeByOuShortCode(ouShortCode); + } + + @Cacheable("ouTreeByUserId") + public Optional> findOuTreeForUser(final String userId) { + log.debug("Looking up ou tree for user '{}' ...", userId); + return this.ldapService.findOuTreeByUserId(userId); + } + +} diff --git a/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/rest/v1/LdapUserController.java b/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/rest/v1/LdapUserController.java new file mode 100644 index 0000000..37d2ea4 --- /dev/null +++ b/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/rest/v1/LdapUserController.java @@ -0,0 +1,221 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap.spring.rest.v1; + +import java.time.Duration; +import java.util.List; +import java.util.Optional; + +import org.springframework.http.CacheControl; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import de.muenchen.oss.ezldap.core.LdapService; +import de.muenchen.oss.ezldap.spring.rest.v1.dto.LdapBaseUserDTO; +import de.muenchen.oss.ezldap.spring.rest.v1.dto.LdapUserDTO; +import de.muenchen.oss.ezldap.spring.rest.v1.dto.WebMapper; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Size; +import lombok.extern.slf4j.Slf4j; + +/** + * @author michael.prankl + * + */ +@Controller +@Slf4j +@RequestMapping(value = "${ezldap.api-path:}/v1/ldap", produces = MediaType.APPLICATION_JSON_VALUE) +@Tag(name = "User", description = "Lese/Suchzugriffe für Personen") +public class LdapUserController { + + private final LdapServiceAdapter ldapService; + private final WebMapper webMapper; + + /** + * Erzeugt eine Instanz. + * + * @param ldapServiceAdapter eine {@link LdapServiceAdapter} + * @param webMapper ein {@link WebMapper} + */ + public LdapUserController(final LdapServiceAdapter ldapServiceAdapter, final WebMapper webMapper) { + this.ldapService = ldapServiceAdapter; + this.webMapper = webMapper; + } + + /** + * GET /ldap/user/{lhmObjectId} + * + * @param lhmObjectId eine lhmObjectId + * @return {@link LdapUserDTO} + */ + @GetMapping("/user/{lhmObjectId}") + @Operation( + summary = "Lookup eines Users via lhmObjectId", operationId = "getUserByLhmObjectId", method = "GET", parameters = { + @Parameter(name = "lhmObjectId", required = true, description = "lhmObjectId des Users", example = "111140670") + }, + responses = { + @ApiResponse(responseCode = "200", description = "OK") + } + ) + public ResponseEntity getUserByLhmObjectId( + @PathVariable(name = "lhmObjectId") final String lhmObjectId) { + log.info("Incoming LDAP User request for lhmObjectId: {}", lhmObjectId); + Optional person = this.ldapService.getPerson(lhmObjectId); + if (person.isPresent()) { + return ResponseEntity.ok() + .cacheControl(CacheControl.maxAge(Duration.ofMinutes(10))) + .body(webMapper.toWebDto(person.get())); + } else { + return ResponseEntity.notFound().build(); + } + } + + /** + * GET /ldap/search/findByUid?uid={uid} + * + * @param uid eine UID + * @return {@link LdapUserDTO} + */ + @GetMapping("/search/findByUid") + @Operation( + summary = "Lookup eines Users via uid", operationId = "getUserByUid", method = "GET", parameters = { + @Parameter(name = "uid", required = true, description = "uid des Users", example = "erika.musterfrau") + }, + responses = { + @ApiResponse(responseCode = "200", description = "OK") + } + ) + public ResponseEntity getUserByUid( + @RequestParam(name = "uid", required = true) final String uid) { + log.info("Incoming LDAP User request for uid: {}", uid); + Optional personWithUID = this.ldapService.getPersonWithUID(uid); + if (personWithUID.isPresent()) { + return ResponseEntity.ok() + .cacheControl(CacheControl.maxAge(Duration.ofMinutes(10))) + .body(webMapper.toWebDto(personWithUID.get())); + } else { + return ResponseEntity.notFound().build(); + } + } + + /** + * GET /ldap/search/findByUidWildcard?uid=[suchphrase](&size=20) + * + * @param uid eine UID, unterstützt * als Platzhalter. Muss mindestens 3 Zeichen lang sein (* + * zählen nicht als Zeichen). + * @return {@link LdapUserDTO} + */ + @GetMapping("/search/findByUidWildcard") + @Operation( + summary = "Wildcard-unterstüztende Suche nach Usern via uid", operationId = "searchUser", method = "GET", parameters = { + @Parameter(name = "uid", required = true, description = "uid des Users (* als Wildcards erlaubt)", example = "erika.*"), + @Parameter(name = "size", description = "Trefferanzahl, default: 10, Maximum: 100"), + }, + responses = { + @ApiResponse(responseCode = "200", description = "OK") + } + ) + public ResponseEntity> searchUser( + // @formatter:off + @RequestParam(name = "uid", required = true) + @Size(min = 3, message = "UID muss mindestens 3 Zeichen haben") final + String uid, + + @RequestParam(name = "size", defaultValue = "10") + @Max(value = LdapService.MAX_SEARCH_RESULTS, message = "Parameter 'size' darf nicht größer wie 100 sein") final + Integer size + ) { + // @formatter:on + log.info("Incoming LDAP User search request for uid like '{}'", uid); + if (uid.replace("*", "").length() < 3) { + return ResponseEntity.badRequest().build(); + } + List results = this.ldapService.searchFor(uid, size); + return ResponseEntity.ok(webMapper.toWebDtoList(results)); + } + + /** + * GET /ldap/search/findByOu?ou={ou} + * + * @param ou Kurzbezeichnung der OU (z.B. "ITM-KM21") + * @return {@link List} + */ + @GetMapping("/search/findByOu") + @Operation( + summary = "Lookup von Usern via OU Kurzbezeichnung", operationId = "findByOu", method = "GET", parameters = { + @Parameter(name = "ou", required = true, description = "OU Kurzbezeichnung (z.B. ITM-KM21)", example = "ITM-KM21") + }, + responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "OU existiert nicht"), + } + ) + public ResponseEntity> findByOu(@RequestParam(name = "ou") final String ou) { + Optional> result = this.ldapService.findPersonsByOuShortcode(ou); + if (result.isEmpty()) { + return ResponseEntity.notFound().build(); + } else { + return ResponseEntity.ok() + .cacheControl(CacheControl.maxAge(Duration.ofMinutes(10))) + .body(webMapper.toWebDtoList(result.get())); + } + } + + /** + * GET /ldap/outree/{lhmObjectId} + * + * @param lhmObjectId des Users + * @return Liste der OU-Shortcodes des OU-Baums des Users + */ + @GetMapping("/user/outree/{lhmObjectId}") + @Operation( + summary = "Lookup des OU Baums des Users", operationId = "ouTree", method = "GET", parameters = { + @Parameter(name = "lhmObjectId", required = true, description = "lhmObjectId des Users", example = "111140670") + }, + responses = { + @ApiResponse(responseCode = "200", description = "OK") + } + ) + public ResponseEntity> getOUTreeByOUShortcode( + @PathVariable(name = "lhmObjectId") final String lhmObjectId) { + log.info("Incoming LDAP OU request for user: {}", lhmObjectId); + final Optional> result = this.ldapService.findOuTreeForUser(lhmObjectId); + if (result.isPresent()) { + return ResponseEntity.ok() + .cacheControl(CacheControl.maxAge(Duration.ofMinutes(10))) + .body(result.get()); + } else { + return ResponseEntity.notFound().build(); + } + } + +} diff --git a/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/rest/v1/dto/AnschriftDTO.java b/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/rest/v1/dto/AnschriftDTO.java new file mode 100644 index 0000000..48af009 --- /dev/null +++ b/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/rest/v1/dto/AnschriftDTO.java @@ -0,0 +1,39 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap.spring.rest.v1.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(name = "AnschriftDTO", description = "Representation einer Adresse / Anschrift") +public class AnschriftDTO { + + @Schema(description = "Straße und Hausnummer ink. Hausnummernzusatz", example = "Marienplatz 8") + private String strasse; + @Schema(description = "Postleitzahl", example = "80331") + private String plz; + @Schema(description = "Ort", example = "München") + private String ort; + +} diff --git a/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/rest/v1/dto/LdapBaseUserDTO.java b/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/rest/v1/dto/LdapBaseUserDTO.java new file mode 100644 index 0000000..df1ecab --- /dev/null +++ b/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/rest/v1/dto/LdapBaseUserDTO.java @@ -0,0 +1,61 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap.spring.rest.v1.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.ToString; + +@Data +@Schema(name = "LdapBaseUserDTO", description = "Basic Representation einer Person") +public class LdapBaseUserDTO { + + @ToString.Include(rank = 0) + @Schema(description = "Eindeutige ID einer Person in der LHM", example = "123456789") + private String lhmObjectId; + + @Schema(description = "UID der Person, kann sich ändern (z.B. nach Heirat)", example = "erika.musterfrau") + @ToString.Include(rank = 1) + private String uid; + + @Schema(description = "Anrede der Person", example = "Frau") + private String anrede; + + @Schema(description = "Vorname(n) der Person", example = "Erika") + private String vorname; + + @Schema(description = "Familienname der Person", example = "Musterfrau") + private String nachname; + + @ToString.Include(rank = 2) + @Schema(description = "Vor- und Familienname der Person", example = "Erika Musterfrau") + private String cn; + + /** + * OU Kurzbezeichnung (z.B. ITM-KM23) + */ + @ToString.Include(rank = 3) + @Schema(description = "Kürzel der organisatorischen Einheit der Person", example = "REF-A1") + private String ou; + +} diff --git a/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/rest/v1/dto/LdapOuDTO.java b/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/rest/v1/dto/LdapOuDTO.java new file mode 100644 index 0000000..4923221 --- /dev/null +++ b/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/rest/v1/dto/LdapOuDTO.java @@ -0,0 +1,66 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap.spring.rest.v1.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.ToString; + +@Data +@Schema(name = "LdapOuDTO", description = "Representation einer Organisationseinheit") +public class LdapOuDTO { + + @ToString.Include(rank = 0) + @Schema(description = "Eindeutige ID der Organisationseinheit in der LHM", example = "123456789") + private String lhmObjectId; + + @ToString.Include(rank = 1) + @Schema(description = "Kürzel der Organisationseinheit", example = "REF-A1") + private String ou; + + private String lhmOUKey; + @Schema(description = "Langname der Organisationseinheit", example = "Servicebereich Grundsatz A1") + private String lhmOULongname; + @Schema(description = "Kurzbezeichnung Organisationseinheit", example = "REF-A1") + private String lhmOUShortname; + + @Schema(description = "Postleitzahl", example = "80331") + private String postalCode; + @Schema(description = "Straße und Hausnummer ink. Hausnummernzusatz", example = "Marienplatz 8") + private String street; + + @Schema(description = "Mailadresse der Organisationseinheit", example = "ref.a1@muenchen.de") + private String mail; + + @Schema(description = "Festnetztelefonnummer", example = "99998 000") + private String telephoneNumber; + + @Schema(description = "Faxnummer", example = "99998 001") + private String facsimileTelephoneNumber; + + @Schema(description = "Leitung der Organisationseinheit") + private LdapUserDTO leitung; + @Schema(description = "Stellvertretende Leitung der Organisationseinheit") + private LdapUserDTO stellvertretung; + +} diff --git a/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/rest/v1/dto/LdapUserDTO.java b/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/rest/v1/dto/LdapUserDTO.java new file mode 100644 index 0000000..5e0b3b9 --- /dev/null +++ b/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/rest/v1/dto/LdapUserDTO.java @@ -0,0 +1,106 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap.spring.rest.v1.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.ToString; + +@Data +@Schema(name = "LdapUserDTO", description = "Detaillierte Representation einer Person") +public class LdapUserDTO { + + @ToString.Include(rank = 0) + @Schema(description = "Eindeutige ID einer Person in der LHM", example = "123456789") + private String lhmObjectId; + + @Schema(description = "UID der Person, kann sich ändern (z.B. nach Heirat)", example = "erika.musterfrau") + @ToString.Include(rank = 1) + private String uid; + + @Schema(description = "Anrede der Person", example = "Frau") + private String anrede; + + @Schema(description = "Vorname(n) der Person", example = "Erika") + private String vorname; + + @Schema(description = "Familienname der Person", example = "Musterfrau") + private String nachname; + + @ToString.Include(rank = 2) + @Schema(description = "Vor- und Familienname der Person", example = "Erika Musterfrau") + private String cn; + + @ToString.Include(rank = 3) + @Schema(description = "Kurzbezeichnung der organisatorischen Einheit der Person", example = "REF-A1") + private String ou; + + @Schema(description = "Langname der organisatorischen Einheit der Person", example = "Servicebereich Grundsatz A1") + private String lhmOULongname; + + @Schema(description = "DN der organisatorischen Einheit der Person", example = "ou=Servicebereich Grundsatz A1,ou=Referat,o=Landeshauptstadt München,c=de") + private String lhmObjectPath; + + @Schema(description = "Langname der organisatorischen übergeordneten Einheit der Person", example = "Geschäftsbereich A") + private String lhmOberOrga; + + @Schema(description = "Kurzbezeichnung des Referats der Person", example = "REF") + private String lhmReferatName; + + @Schema(description = "Funktionsbezeichnung der Person", example = "Sachbearbeitung A-Z") + private String lhmFunctionalTitle; + + @Schema(description = "Amtskurzbezeichnung der Person", example = "Techn. Rat") + private String amtsbezeichnung; + + @Schema(description = "Erreibarkeitszeiten der Person (z.B. bei Teilzeit)", example = "Mo-Mi") + private String erreichbarkeit; + + @Schema(description = "Mailadresse der Person", example = "erika.musterfrau@muenchen.de") + private String mail; + + @Schema(description = "Mailadresse der Organisationseinheit der Person", example = "ref.a1@muenchen.de") + private String lhmOrgaMail; + + @Schema(description = "Festnetztelefonnummer", example = "99998 000") + private String telephoneNumber; + + @Schema(description = "Faxnummer", example = "99998 001") + private String facsimileTelephoneNumber; + + @Schema(description = "Mobilnummer", example = "0152-28-817386") + private String mobile; + + @Schema(description = "Zimmernummer", example = "EG.001") + private String zimmer; + + @Schema(description = "Postanschrift") + private AnschriftDTO postanschrift; + + @Schema(description = "Büroanschrift") + private AnschriftDTO bueroanschrift; + + @Schema(description = "Akademische Titel", example = "Dr.") + private String personalTitle; + +} diff --git a/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/rest/v1/dto/WebMapper.java b/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/rest/v1/dto/WebMapper.java new file mode 100644 index 0000000..42d71bf --- /dev/null +++ b/lib-spring/src/main/java/de/muenchen/oss/ezldap/spring/rest/v1/dto/WebMapper.java @@ -0,0 +1,42 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap.spring.rest.v1.dto; + +import java.util.List; + +import org.mapstruct.Mapper; + +@Mapper(componentModel = "spring") +public interface WebMapper { + + LdapUserDTO toWebDto(de.muenchen.oss.ezldap.core.LdapUserDTO coreDto); + + LdapBaseUserDTO toWebDto(de.muenchen.oss.ezldap.core.LdapBaseUserDTO coreDto); + + AnschriftDTO toWebDto(de.muenchen.oss.ezldap.core.AnschriftDTO coreDto); + + LdapOuDTO toWebDto(de.muenchen.oss.ezldap.core.LdapOuDTO coreDto); + + List toWebDtoList(List coreDtoList); + +} diff --git a/lib-spring/src/main/resources/ezldap-ehcache.xml b/lib-spring/src/main/resources/ezldap-ehcache.xml new file mode 100644 index 0000000..9be6d07 --- /dev/null +++ b/lib-spring/src/main/resources/ezldap-ehcache.xml @@ -0,0 +1,35 @@ + + + + + + + + 60 + + + 400 + 512 + + + + + + + + + 10 + + + + + + + + + diff --git a/microservice/Dockerfile b/microservice/Dockerfile new file mode 100644 index 0000000..e2b463c --- /dev/null +++ b/microservice/Dockerfile @@ -0,0 +1,8 @@ +# Dockerfiles may only contain a FROM and the application data. +# For Java applications use /ubi8/openjdk-11 or /ubi8/openjdk-17 as Base Image when using OpenShift S2I builds +# For Java applications use /ubi9/openjdk-11-runtime or /ubi9/openjdk-17-runtime as Base Image when using traditional Dockerfile +# for documentation please see https://access.redhat.com/documentation/en-us/red_hat_jboss_middleware_for_openshift/3/html/red_hat_java_s2i_for_openshift/ +# All other variations must be approved by KM8 +FROM registry.access.redhat.com/ubi9/openjdk-17-runtime:latest + +COPY target/*.jar /deployments/application.jar \ No newline at end of file diff --git a/microservice/pom.xml b/microservice/pom.xml new file mode 100644 index 0000000..5bf850b --- /dev/null +++ b/microservice/pom.xml @@ -0,0 +1,188 @@ + + + + 4.0.0 + + + de.muenchen.oss.ezldap + ezLDAP-parent + 1.0.0-SNAPSHOT + + + ezLDAP-microservice + ezLDAP :: microservice + + + ${skipTests} + + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-actuator + + + io.micrometer + micrometer-registry-prometheus + + + + + + org.springframework.boot + spring-boot-starter-security + + + + + org.springframework.boot + spring-boot-test + test + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + net.logstash.logback + logstash-logback-encoder + ${logstash.encoder} + + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.3.0 + + + + + + jakarta.xml.bind + jakarta.xml.bind-api + provided + + + + + + org.springframework.boot + spring-boot-devtools + true + + + org.projectlombok + lombok + provided + + + + + + de.muenchen.oss.ezldap + ezLDAP-lib-spring + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + + ${project.artifactId} + + + src/main/resources + true + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + build-info + repackage + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + lombok.launch.AnnotationProcessorHider$AnnotationProcessor + + + + + + maven-surefire-plugin + + + ${skip.surefire.tests} + + ${surefireArgLine} + + + + maven-failsafe-plugin + + + + integration-test + verify + + + + ${failsafeArgLine} + + + + + + + + \ No newline at end of file diff --git a/microservice/src/main/java/de/muenchen/oss/ezldap/EzLdapMicroserviceApplication.java b/microservice/src/main/java/de/muenchen/oss/ezldap/EzLdapMicroserviceApplication.java new file mode 100644 index 0000000..8bcfa48 --- /dev/null +++ b/microservice/src/main/java/de/muenchen/oss/ezldap/EzLdapMicroserviceApplication.java @@ -0,0 +1,41 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +import de.muenchen.oss.ezldap.spring.EnableEzLDAP; + +/** + * Application class for starting the micro-service. + */ +@SpringBootApplication +@EnableEzLDAP +public class EzLdapMicroserviceApplication { + + public static void main(String[] args) { + SpringApplication.run(EzLdapMicroserviceApplication.class, args); + } + +} diff --git a/microservice/src/main/java/de/muenchen/oss/ezldap/config/AppConfigurationProperties.java b/microservice/src/main/java/de/muenchen/oss/ezldap/config/AppConfigurationProperties.java new file mode 100644 index 0000000..ea5c6e3 --- /dev/null +++ b/microservice/src/main/java/de/muenchen/oss/ezldap/config/AppConfigurationProperties.java @@ -0,0 +1,60 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; +import org.springframework.context.annotation.Configuration; +import org.springframework.validation.annotation.Validated; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * @author michael.prankl + * + */ +@Data +@Configuration +@ConfigurationProperties(prefix = "app") +@Validated +public class AppConfigurationProperties { + + @NotNull + private AuthMode authMode = AuthMode.NONE; + + @NestedConfigurationProperty + @Valid + private BasicAuthConfigurationProperties basicAuth; + + public enum AuthMode { + NONE, BASIC; + } + + private String swaggerDescription; + private String swaggerContactName; + private String swaggerContactMail; + private String swaggerContactUrl; + +} diff --git a/microservice/src/main/java/de/muenchen/oss/ezldap/config/BasicAuthConfigurationProperties.java b/microservice/src/main/java/de/muenchen/oss/ezldap/config/BasicAuthConfigurationProperties.java new file mode 100644 index 0000000..bd942b8 --- /dev/null +++ b/microservice/src/main/java/de/muenchen/oss/ezldap/config/BasicAuthConfigurationProperties.java @@ -0,0 +1,48 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap.config; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +/** + * @author michael.prankl + * + */ +@Data +public class BasicAuthConfigurationProperties { + + /** + * Username für Basic-Auth. + */ + @NotBlank + private String user; + + /** + * Passwort für Basic-Auth, kodiert entsprechend + * https://docs.spring.io/spring-security/reference/features/authentication/password-storage.html#authentication-password-storage-dpe-format + */ + @NotBlank + private String password; + +} diff --git a/microservice/src/main/java/de/muenchen/oss/ezldap/config/CorsConfiguration.java b/microservice/src/main/java/de/muenchen/oss/ezldap/config/CorsConfiguration.java new file mode 100644 index 0000000..a65c8b4 --- /dev/null +++ b/microservice/src/main/java/de/muenchen/oss/ezldap/config/CorsConfiguration.java @@ -0,0 +1,57 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap.config; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import de.muenchen.oss.ezldap.spring.props.EzLdapConfigurationProperties; +import lombok.extern.slf4j.Slf4j; + +/** + * @author michael.prankl + * + */ +@Configuration +@ConditionalOnProperty(name = "ezldap.cors.enabled", havingValue = "true", matchIfMissing = true) +@Slf4j +public class CorsConfiguration { + + @Bean + WebMvcConfigurer corsConfigurer(EzLdapConfigurationProperties configProps, CorsConfigurationProperties corsProps) { + return new WebMvcConfigurer() { + @Override + public void addCorsMappings(CorsRegistry registry) { + if (!corsProps.getAllowedOriginPatterns().isEmpty()) { + String mapping = configProps.getApiPath() + "/v1/ldap/**"; + registry.addMapping(mapping).allowedOriginPatterns(corsProps.getAllowedOriginPatterns().toArray(new String[0])); + log.info("Configured CORS for '{}' with allowedOriginsPatterns: {}", mapping, corsProps.getAllowedOriginPatterns()); + } + } + }; + } + +} diff --git a/microservice/src/main/java/de/muenchen/oss/ezldap/config/CorsConfigurationProperties.java b/microservice/src/main/java/de/muenchen/oss/ezldap/config/CorsConfigurationProperties.java new file mode 100644 index 0000000..f228007 --- /dev/null +++ b/microservice/src/main/java/de/muenchen/oss/ezldap/config/CorsConfigurationProperties.java @@ -0,0 +1,41 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap.config; + +import java.util.Collections; +import java.util.List; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import lombok.Data; + +@Data +@Configuration +@ConfigurationProperties(prefix = "ezldap.cors") +public class CorsConfigurationProperties { + + private boolean enabled; + private List allowedOriginPatterns = Collections.emptyList(); + +} diff --git a/microservice/src/main/java/de/muenchen/oss/ezldap/config/ForwardedHeaderConfiguration.java b/microservice/src/main/java/de/muenchen/oss/ezldap/config/ForwardedHeaderConfiguration.java new file mode 100644 index 0000000..874d046 --- /dev/null +++ b/microservice/src/main/java/de/muenchen/oss/ezldap/config/ForwardedHeaderConfiguration.java @@ -0,0 +1,45 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap.config; + +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.filter.ForwardedHeaderFilter; + +/** + * This class provides the {@link ForwardedHeaderFilter} to handle + * the headers of type "Forwarded" and "X-Forwarded-*". + */ +@Configuration +public class ForwardedHeaderConfiguration { + + @Bean + FilterRegistrationBean forwardedHeaderFilter() { + final FilterRegistrationBean registration = new FilterRegistrationBean<>(); + registration.setFilter(new ForwardedHeaderFilter()); + registration.addUrlPatterns("/*"); + return registration; + } + +} diff --git a/microservice/src/main/java/de/muenchen/oss/ezldap/config/SecurityConfiguration.java b/microservice/src/main/java/de/muenchen/oss/ezldap/config/SecurityConfiguration.java new file mode 100644 index 0000000..42982c2 --- /dev/null +++ b/microservice/src/main/java/de/muenchen/oss/ezldap/config/SecurityConfiguration.java @@ -0,0 +1,80 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap.config; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import org.springframework.security.web.SecurityFilterChain; + +import de.muenchen.oss.ezldap.config.AppConfigurationProperties.AuthMode; +import de.muenchen.oss.ezldap.spring.props.EzLdapConfigurationProperties; +import lombok.extern.slf4j.Slf4j; + +/** + * Spring Security Filter Chain Konfiguration + * + * @author michael.prankl + */ +@Configuration +@EnableWebSecurity +@Slf4j +public class SecurityConfiguration { + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http, EzLdapConfigurationProperties configProps, AppConfigurationProperties appProps) + throws Exception { + if (AuthMode.NONE.equals(appProps.getAuthMode())) { + log.info("Bootstrapping Spring Security filter chain for auth-mode 'none' ..."); + http + .authorizeHttpRequests(authz -> authz.anyRequest().permitAll()); + http.sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); + } else { + log.info("Bootstrapping Spring Security filter chain for auth-mode 'basic' ..."); + http + .authorizeHttpRequests(authz -> { + authz.requestMatchers("/openapi/v3/**", "/swagger-ui/**").permitAll(); + authz.requestMatchers("/actuator/prometheus", "/actuator/info", "/actuator/health/**").permitAll(); + authz.anyRequest().authenticated(); + }); + http.sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); + http.httpBasic(Customizer.withDefaults()); + } + return http.build(); + } + + @Bean + @ConditionalOnProperty(name = "app.auth-mode", havingValue = "basic") + InMemoryUserDetailsManager userDetailsService(AppConfigurationProperties appProps) { + UserDetails userDetails = User.withUsername(appProps.getBasicAuth().getUser()).password(appProps.getBasicAuth().getPassword()).roles("USER").build(); + return new InMemoryUserDetailsManager(userDetails); + } + +} diff --git a/microservice/src/main/java/de/muenchen/oss/ezldap/config/SpringdocsSwaggerConfig.java b/microservice/src/main/java/de/muenchen/oss/ezldap/config/SpringdocsSwaggerConfig.java new file mode 100644 index 0000000..917f032 --- /dev/null +++ b/microservice/src/main/java/de/muenchen/oss/ezldap/config/SpringdocsSwaggerConfig.java @@ -0,0 +1,100 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap.config; + +import org.springdoc.core.customizers.OpenApiCustomizer; +import org.springdoc.core.models.GroupedOpenApi; +import org.springframework.boot.info.BuildProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import de.muenchen.oss.ezldap.config.AppConfigurationProperties.AuthMode; +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Contact; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.responses.ApiResponse; +import io.swagger.v3.oas.models.responses.ApiResponses; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; + +/** + * SpringDocs Swagger API Docs configuration. + * + * @author michael.prankl + * + */ +@Configuration +public class SpringdocsSwaggerConfig { + + @Bean + GroupedOpenApi publicApi(OpenApiCustomizer globalResponseCodeCustomiser) { + return GroupedOpenApi.builder() + .group("ezLDAP") + .packagesToScan("de.muenchen.oss.ezldap.spring.rest.v1") + .addOpenApiCustomizer(globalResponseCodeCustomiser) + .build(); + } + + @Bean + OpenApiCustomizer globalResponseCodeCustomiser(AppConfigurationProperties appProps) { + return openApi -> { + openApi.getPaths().values().forEach(pathItem -> pathItem.readOperations().forEach(operation -> { + ApiResponses apiResponses = operation.getResponses(); + ApiResponse response401 = new ApiResponse().description("Unauthenticated"); + ApiResponse response403 = new ApiResponse().description("Forbidden"); + ApiResponse response404 = new ApiResponse().description("Not Found"); + ApiResponse response500 = new ApiResponse().description("Server Error"); + apiResponses.addApiResponse("401", response401); + apiResponses.addApiResponse("403", response403); + apiResponses.addApiResponse("404", response404); + apiResponses.addApiResponse("500", response500); + })); + + if (AuthMode.BASIC.equals(appProps.getAuthMode())) { + openApi.addSecurityItem(new SecurityRequirement().addList("basicAuth")) + .components(new Components() + .addSecuritySchemes("basicAuth", new SecurityScheme() + .type(SecurityScheme.Type.HTTP) + .scheme("basic"))); + } + }; + } + + @Bean + OpenAPI openApi(BuildProperties buildProperties, AppConfigurationProperties appProps) { + return new OpenAPI() + // @formatter:off + .info(new Info() + .title("ezLDAP API") + .version(buildProperties.getVersion()) + .description(appProps.getSwaggerDescription()) + .contact(new Contact() + .name(appProps.getSwaggerContactName()) + .email(appProps.getSwaggerContactMail()) + .url(appProps.getSwaggerContactUrl())) + ); + // @formatter:on + } + +} diff --git a/microservice/src/main/resources/application.yml b/microservice/src/main/resources/application.yml new file mode 100644 index 0000000..c4e37d4 --- /dev/null +++ b/microservice/src/main/resources/application.yml @@ -0,0 +1,47 @@ +spring: + application: + name: ezLDAP-microservice + profiles: + active: local + + +springdoc: + api-docs: + path: "/openapi/v3/api-docs" + +server: + error: + include-exception: false + include-binding-errors: never + include-stacktrace: never + include-message: never + +--- +spring: + config: + activate: + on-profile: local + +logging: + level: + "[de.muenchen.itm]": DEBUG + "[org.springframework.security]": DEBUG + +server: + port: 8080 +--- +# OpenShift specific runtime settings +spring: + config: + activate: + on-profile: openshift + main: + # disable Spring Boot banner log + banner-mode: "OFF" + +server: + port: 8080 + shutdown: graceful + tomcat: + mbeanregistry: + enabled: true diff --git a/microservice/src/main/resources/banner.txt b/microservice/src/main/resources/banner.txt new file mode 100644 index 0000000..23668c7 --- /dev/null +++ b/microservice/src/main/resources/banner.txt @@ -0,0 +1,9 @@ + _ _____ _____ + | | | __ \ /\ | __ \ + ___ ____ | | | | | | / \ | |__) | + / _ \ |_ / | | | | | | / /\ \ | ___/ + | __/ / / | |____ | |__| | / ____ \ | | + \___| /___| |______| |_____/ /_/ \_\ |_| + +| App: @project.version@ +| Spring Boot: @spring.boot.version@ \ No newline at end of file diff --git a/microservice/src/main/resources/logback-spring.xml b/microservice/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..a8424b5 --- /dev/null +++ b/microservice/src/main/resources/logback-spring.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + diff --git a/microservice/src/test/java/de/muenchen/oss/ezldap/AppTest.java b/microservice/src/test/java/de/muenchen/oss/ezldap/AppTest.java new file mode 100644 index 0000000..083e05f --- /dev/null +++ b/microservice/src/test/java/de/muenchen/oss/ezldap/AppTest.java @@ -0,0 +1,83 @@ +/* + * The MIT License + * Copyright © 2023 Landeshauptstadt München | it@M + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.muenchen.oss.ezldap; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.Optional; + +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.ldap.core.LdapTemplate; +import org.springframework.ldap.core.support.LdapContextSource; +import org.springframework.test.web.servlet.MockMvc; + +import de.muenchen.oss.ezldap.core.LdapBaseUserDTO; +import de.muenchen.oss.ezldap.core.LdapService; +import de.muenchen.oss.ezldap.core.LdapUserDTO; + +/** + * @author michael.prankl + * + */ +@SpringBootTest +@AutoConfigureMockMvc +public class AppTest { + + @MockBean + LdapService ldapService; + + @MockBean + LdapTemplate ldapTemplate; + + @MockBean + LdapContextSource ldapContextSource; + + @Autowired + MockMvc mockMvc; + + @Test + public void context_loads_and_cors_correct() throws Exception { + Mockito.when(ldapService.getPerson(Mockito.anyString())) + .thenReturn(Optional.of(new LdapUserDTO(new LdapBaseUserDTO()))); + mockMvc.perform(get("/v1/ldap/user/{lhmObjectId}", "111140670").header("Origin", "http://localhost:8081")) + .andDo(print()) + .andExpect(status().isOk()); + mockMvc.perform(get("/v1/ldap/user/{lhmObjectId}", "111140670").header("Origin", "https://app.example.org")) + .andDo(print()) + .andExpect(status().isOk()); + mockMvc.perform(get("/v1/ldap/user/{lhmObjectId}", "111140670").header("Origin", "https://app.example.org:8443")) + .andDo(print()) + .andExpect(status().isOk()); + mockMvc.perform(get("/v1/ldap/user/{lhmObjectId}", "111140670").header("Origin", "https://example.com")) + .andDo(print()) + .andExpect(status().isForbidden()); + } + +} diff --git a/microservice/src/test/resources/application.yml b/microservice/src/test/resources/application.yml new file mode 100644 index 0000000..6198ca7 --- /dev/null +++ b/microservice/src/test/resources/application.yml @@ -0,0 +1,17 @@ +ezldap: + cors: + enabled: true + allowed-origin-patterns: + - https://*.example.org:[*] + - http://localhost:[*] + cache: + enabled: false + +app: + auth-mode: none + +logging: + level: + "[de.muenchen.itm]": DEBUG + "[org.springframework.security]": DEBUG + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..1f3e81b --- /dev/null +++ b/pom.xml @@ -0,0 +1,387 @@ + + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.2.0 + + + de.muenchen.oss.ezldap + ezLDAP-parent + 1.0.0-SNAPSHOT + ezLDAP :: parent + pom + + + microservice + client-v1 + lib-core + lib-spring + + + + + v1 + + UTF-8 + 17 + UTF-8 + UTF-8 + + 3.2.0 + 1.5.5.Final + 1.18.30 + 0.2.0 + + 7.4 + + + + https://github.com/it-at-m/ezLDAP + scm:git:https://github.com/it-at-m/ezLDAP.git + + scm:git:https://github.com/it-at-m/ezLDAP.git + HEAD + + + + + + ossrh + https://s01.oss.sonatype.org/content/repositories/snapshots + + + + + + + org.projectlombok + lombok + ${org.projectlombok.version} + + + org.mapstruct + mapstruct + ${org.mapstruct.version} + + + + + de.muenchen.oss.ezldap + ezLDAP-client-v1 + ${project.version} + + + de.muenchen.oss.ezldap + ezLDAP-lib-core + ${project.version} + + + de.muenchen.oss.ezldap + ezLDAP-microservice + ${project.version} + + + de.muenchen.oss.ezldap + ezLDAP-lib-spring + ${project.version} + + + + + + + + + maven-scm-plugin + 2.0.1 + + ${project.version} + + + + + org.codehaus.mojo + versions-maven-plugin + + + + com.mycila + license-maven-plugin + 4.3 + + + Landeshauptstadt München | it@M + 2023 + + + +
+ com/mycila/maven/plugin/license/templates/MIT.txt
+ + **/*.java + **/*.xml + **/*.yml + **/*.properties + + + **/LICENSE + **/README + **/src/test/resources/** + **/src/main/resources/** + +
+
+ + SLASHSTAR_STYLE + +
+ + + check-license-headers + + check + + test + + +
+ + com.diffplug.spotless + spotless-maven-plugin + 2.33.0 + + + de.muenchen.oss + itm-java-codeformat + 1.0.9 + + + + + + src/main/java/**/*.java + src/test/java/**/*.java + + + + itm-java-codeformat/java_codestyle_formatter.xml + + + + + + + + + check + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + none + + + + org.apache.maven.plugins + maven-release-plugin + + true + false + release + deploy + @{project.version} + + +
+ + + + org.apache.maven.plugins + maven-release-plugin + 3.0.1 + + + org.apache.maven.plugins + maven-gpg-plugin + 3.1.0 + + + org.apache.maven.plugins + maven-source-plugin + 3.3.0 + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.6.3 + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.13 + + + org.eclipse.m2e + lifecycle-mapping + 1.0.0 + + + + + + com.diffplug.spotless + spotless-maven-plugin + [2,) + + check + + + + + + + + + + + + +
+ + + + + + release + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar-no-fork + + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + attach-javadocs + + jar + + + + + none + + **/*.java + + + + + + + org.apache.maven.plugins + maven-gpg-plugin + + ${skipGpg} + + + + sign-artifacts + verify + + sign + + + + + --pinentry-mode + loopback + + + + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + true + + ossrh + https://s01.oss.sonatype.org/ + true + + + + + + + + + + MIT License + + + + + + Michael Prankl + Landeshauptstadt München + https://github.com/eidottermihi + + developer + + + + +
\ No newline at end of file diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..d6c85b7 --- /dev/null +++ b/renovate.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["config:base"], + "labels": ["dependencies"] +}