Merge remote-tracking branch 'origin/bugfix/hosting-assets-import-into-external-db' into feature/add-scenario-test-for-deceased-partner-with-community-of-heirs
This commit is contained in:
commit
a8964017ca
55
.aliases
55
.aliases
@ -1,4 +1,4 @@
|
||||
# For using the alias gw-importOfficeData or gw-importHostingAssets,
|
||||
# For using the alias gw-importHostingAssets,
|
||||
# copy the file .tc-environment to .environment (ignored by git)
|
||||
# and amend them according to your external DB.
|
||||
|
||||
@ -71,7 +71,6 @@ function importLegacyData() {
|
||||
./gradlew $target --rerun
|
||||
fi
|
||||
}
|
||||
alias gw-importOfficeData='importLegacyData importOfficeData'
|
||||
alias gw-importHostingAssets='importLegacyData importHostingAssets'
|
||||
|
||||
alias podman-start='systemctl --user enable --now podman.socket && systemctl --user status podman.socket && ls -la /run/user/$UID/podman/podman.sock'
|
||||
@ -90,11 +89,54 @@ alias pg-sql-restore='gunzip --stdout | docker exec -i hsadmin-ng-postgres psql
|
||||
alias fp='grep -r '@Accepts' src | sed -e 's/^.*@/@/g' | sort -u | wc -l'
|
||||
|
||||
alias gw-spotless='./gradlew spotlessApply -x pitest -x test -x :processResources'
|
||||
alias gw-test='. .aliases; ./gradlew test'
|
||||
alias gw-check='. .aliases; gw test check -x pitest'
|
||||
alias gw-check='. .aliases; . .tc-environment; gw test check -x pitest'
|
||||
|
||||
# HOWTO: run all 'normal' tests (by default without scenario+import-tests): `gw-test`
|
||||
# You can also mention specific targets: `gw-test importHostingAssets`, in that case only these tests are executed.
|
||||
# This will always use the environment from `.tc-environment`.
|
||||
#
|
||||
# HOWTO: re-run tests even if no changed can be detected: `gw-test --rerun`
|
||||
# You can also mention specific targets: `gw-test scenarioTest --rerun`.
|
||||
# This will always use the environment from `.tc-environment`.
|
||||
#
|
||||
# HOWTO: run all tests (unit, integration+acceptance, import and scenario): `gw-test --all`
|
||||
# You can also re-run all these tests, which will take ~20min: `gw-test --all --rerun`
|
||||
# This will always use the environment from `.tc-environment`.
|
||||
#
|
||||
function _gwTest1() {
|
||||
echo
|
||||
printf -- '=%0.s' {1..80}; echo
|
||||
echo "RUNNING gw $@"
|
||||
printf -- '-%0.s' {1..80}; echo
|
||||
./gradlew "$@"
|
||||
printf -- '-%0.s' {1..80}; echo
|
||||
echo "DONE gw $@"
|
||||
}
|
||||
function _gwTest() {
|
||||
. .aliases
|
||||
. .tc-environment
|
||||
rm -f /tmp/gwTest.tmp
|
||||
if [ "$1" == "--all" ]; then
|
||||
shift # to remove the --all from $@
|
||||
# delierately in separate gradlew-calls to avoid Testcontains-PostgreSQL problem spillover
|
||||
time (_gwTest1 unitTest "$@" &&
|
||||
_gwTest1 officeIntegrationTest bookingIntegrationTest hostingIntegrationTest "$@" &&
|
||||
_gwTest1 scenarioTest "$@" &&
|
||||
_gwTest1 importHostingAssets "$@");
|
||||
elif [ $# -eq 0 ] || [[ $1 == -* ]]; then
|
||||
time _gwTest1 test "$@";
|
||||
else
|
||||
time _gwTest1 "$@";
|
||||
fi
|
||||
printf -- '=%0.s' {1..80}; echo
|
||||
}
|
||||
alias gw-test=_gwTest
|
||||
|
||||
alias howto=bin/howto
|
||||
alias cas-curl=bin/cas-curl
|
||||
|
||||
# etc/docker-compose.yml limits CPUs+MEM and includes a PostgreSQL config for analysing slow queries
|
||||
alias gw-importOfficeData-in-docker-compose='
|
||||
alias gw-importHostingAssets-in-docker-compose='
|
||||
docker-compose -f etc/docker-compose.yml down &&
|
||||
docker-compose -f etc/docker-compose.yml up -d && sleep 10 &&
|
||||
time gw-importHostingAssets'
|
||||
@ -104,5 +146,6 @@ if [ ! -f .environment ]; then
|
||||
fi
|
||||
source .environment
|
||||
|
||||
alias scenario-reports-upload='./gradlew scenarioTests convertMarkdownToHtml && ssh hsh03-hsngdev@h50.hostsharing.net "rm -f doms/hsngdev.hs-example.de/htdocs-ssl/scenarios/office/*.html" && scp build/doc/scenarios/*.html hsh03-hsngdev@h50.hostsharing.net:doms/hsngdev.hs-example.de/htdocs-ssl/scenarios/office'
|
||||
alias scenario-reports-upload='./gradlew scenarioTest convertMarkdownToHtml && ssh hsh03-hsngdev@h50.hostsharing.net "rm -f doms/hsngdev.hs-example.de/htdocs-ssl/scenarios/office/*.html" && scp build/doc/scenarios/*.html hsh03-hsngdev@h50.hostsharing.net:doms/hsngdev.hs-example.de/htdocs-ssl/scenarios/office'
|
||||
alias scenario-reports-open='open https://hsngdev.hs-example.de/scenarios/office'
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
<entry key="HSADMINNG_POSTGRES_ADMIN_USERNAME" value="postgres" />
|
||||
<entry key="HSADMINNG_POSTGRES_JDBC_URL" value="jdbc:postgresql://localhost:5432/postgres" />
|
||||
<entry key="HSADMINNG_POSTGRES_RESTRICTED_USERNAME" value="restricted" />
|
||||
<entry key="HSADMINNG_MIGRATION_DATA_PATH" value="migration" />
|
||||
</map>
|
||||
</option>
|
||||
<option name="executionName" />
|
||||
|
@ -3,9 +3,9 @@
|
||||
<ExternalSystemSettings>
|
||||
<option name="env">
|
||||
<map>
|
||||
<entry key="HSADMINNG_MIGRATION_DATA_PATH" value="migration" />
|
||||
<entry key="HSADMINNG_POSTGRES_ADMIN_USERNAME" value="admin" />
|
||||
<entry key="HSADMINNG_POSTGRES_RESTRICTED_USERNAME" value="restricted" />
|
||||
<entry key="HSADMINNG_SUPERUSER" value="import-superuser@hostsharing.net" />
|
||||
</map>
|
||||
</option>
|
||||
<option name="executionName" />
|
||||
|
@ -1,103 +0,0 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="ImportOfficeData" type="GradleRunConfiguration" factoryName="Gradle">
|
||||
<ExternalSystemSettings>
|
||||
<option name="env">
|
||||
<map>
|
||||
<entry key="HSADMINNG_MIGRATION_DATA_PATH" value="migration" />
|
||||
<entry key="HSADMINNG_POSTGRES_ADMIN_USERNAME" value="admin" />
|
||||
<entry key="HSADMINNG_POSTGRES_RESTRICTED_USERNAME" value="restricted" />
|
||||
</map>
|
||||
</option>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" value="" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value=":importOfficeData" />
|
||||
<option value="--tests" />
|
||||
<option value=""net.hostsharing.hsadminng.hs.migration.ImportOfficeData"" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" />
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<EXTENSION ID="com.intellij.execution.ExternalSystemRunConfigurationJavaExtension">
|
||||
<extension name="coverage" sample_coverage="false" />
|
||||
</EXTENSION>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<RunAsTest>true</RunAsTest>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration default="false" name="ImportOfficeData" type="GradleRunConfiguration" factoryName="Gradle">
|
||||
<ExternalSystemSettings>
|
||||
<option name="env">
|
||||
<map>
|
||||
<entry key="HSADMINNG_MIGRATION_DATA_PATH" value="migration" />
|
||||
<entry key="HSADMINNG_POSTGRES_ADMIN_USERNAME" value="admin" />
|
||||
<entry key="HSADMINNG_POSTGRES_RESTRICTED_USERNAME" value="restricted" />
|
||||
</map>
|
||||
</option>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" value="" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value=":importOfficeData" />
|
||||
<option value="--tests" />
|
||||
<option value=""net.hostsharing.hsadminng.hs.office.migration.ImportOfficeData"" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" />
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<EXTENSION ID="com.intellij.execution.ExternalSystemRunConfigurationJavaExtension">
|
||||
<extension name="coverage" sample_coverage="false" />
|
||||
</EXTENSION>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<RunAsTest>true</RunAsTest>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration default="false" name="ImportOfficeData" type="GradleRunConfiguration" factoryName="Gradle">
|
||||
<ExternalSystemSettings>
|
||||
<option name="env">
|
||||
<map>
|
||||
<entry key="HSADMINNG_POSTGRES_ADMIN_USERNAME" value="admin" />
|
||||
<entry key="HSADMINNG_POSTGRES_RESTRICTED_USERNAME" value="restricted" />
|
||||
</map>
|
||||
</option>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" value="" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value=":importOfficeData" />
|
||||
<option value="--tests" />
|
||||
<option value=""net.hostsharing.hsadminng.hs.migration.ImportOfficeData"" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" />
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<EXTENSION ID="com.intellij.execution.ExternalSystemRunConfigurationJavaExtension">
|
||||
<extension name="coverage" sample_coverage="false" />
|
||||
</EXTENSION>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<RunAsTest>true</RunAsTest>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
@ -1,8 +1,7 @@
|
||||
unset HSADMINNG_POSTGRES_JDBC_URL # dynamically set, different for normal tests and imports
|
||||
export HSADMINNG_POSTGRES_ADMIN_USERNAME=admin
|
||||
export HSADMINNG_POSTGRES_ADMIN_PASSWORD=
|
||||
source .unset-environment
|
||||
|
||||
export HSADMINNG_POSTGRES_RESTRICTED_USERNAME=restricted
|
||||
export HSADMINNG_SUPERUSER=superuser-alex@hostsharing.net
|
||||
export HSADMINNG_MIGRATION_DATA_PATH=migration
|
||||
export LIQUIBASE_CONTEXT=
|
||||
export HSADMINNG_POSTGRES_ADMIN_USERNAME=admin
|
||||
export HSADMINNG_SUPERUSER=import-superuser@hostsharing.net
|
||||
|
||||
export LANG=en_US.UTF-8
|
||||
|
@ -4,5 +4,5 @@ unset HSADMINNG_POSTGRES_ADMIN_PASSWORD
|
||||
unset HSADMINNG_POSTGRES_RESTRICTED_USERNAME
|
||||
unset HSADMINNG_SUPERUSER
|
||||
unset HSADMINNG_MIGRATION_DATA_PATH
|
||||
unset LIQUIBASE_CONTEXT
|
||||
unset HSADMINNG_OFFICE_DATA_SQL_FILE
|
||||
|
||||
|
25
Jenkinsfile
vendored
25
Jenkinsfile
vendored
@ -35,19 +35,34 @@ pipeline {
|
||||
|
||||
stage ('Tests') {
|
||||
parallel {
|
||||
stage('Unit-/Integration/Acceptance-Tests') {
|
||||
stage('Unit-Tests') {
|
||||
steps {
|
||||
sh './gradlew check --no-daemon -x pitest -x dependencyCheckAnalyze -x importOfficeData -x importHostingAssets'
|
||||
sh './gradlew unitTest --no-daemon'
|
||||
}
|
||||
}
|
||||
stage('Import-Tests') {
|
||||
stage('General-Tests') {
|
||||
steps {
|
||||
sh './gradlew importOfficeData importHostingAssets --no-daemon'
|
||||
sh './gradlew generalTest --no-daemon'
|
||||
}
|
||||
}
|
||||
stage('Office-Tests') {
|
||||
steps {
|
||||
sh './gradlew officeIntegrationTest --no-daemon'
|
||||
}
|
||||
}
|
||||
stage('Booking+Hosting-Tests') {
|
||||
steps {
|
||||
sh './gradlew bookingIntegrationTest hostingIntegrationTest --no-daemon'
|
||||
}
|
||||
}
|
||||
stage('Test-Imports') {
|
||||
steps {
|
||||
sh './gradlew importHostingAssets --no-daemon'
|
||||
}
|
||||
}
|
||||
stage ('Scenario-Tests') {
|
||||
steps {
|
||||
sh './gradlew scenarioTests --no-daemon'
|
||||
sh './gradlew scenarioTest --no-daemon'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
148
README.md
148
README.md
@ -27,7 +27,13 @@ For architecture consider the files in the `doc` and `adr` folder.
|
||||
- [OWASP Security Vulnerability Check](#owasp-security-vulnerability-check)
|
||||
- [Dependency-License-Compatibility](#dependency-license-compatibility)
|
||||
- [Dependency Version Upgrade](#dependency-version-upgrade)
|
||||
- [Biggest Flaws in our Architecture](#biggest-flaws-in-our-architecture)
|
||||
- [The RBAC System is too Complicated](#the-rbac-system-is-too-complicated)
|
||||
- [The Mapper is Error-Prone](#the-mapper-is-error-prone)
|
||||
- [Too Many Business-Rules Implemented in Controllers](#too-many-business-rules-implemented-in-controllers)
|
||||
- [How To ...](#how-to-...)
|
||||
- [How to Run the Application With Other Profiles, e.g. production](#)
|
||||
- [How to Do a Clean Run of the Application](#how-to-do-a-clean-run-of-the-application)
|
||||
- [How to Configure .pgpass for the Default PostgreSQL Database?](#how-to-configure-.pgpass-for-the-default-postgresql-database?)
|
||||
- [How to Run the Tests Against a Local User-Space Podman Daemon?](#how-to-run-the-tests-against-a-local-user-space-podman-daemon?)
|
||||
- [Install and Run Podman](#install-and-run-podman)
|
||||
@ -40,6 +46,7 @@ For architecture consider the files in the `doc` and `adr` folder.
|
||||
- [How to Amend Liquibase SQL Changesets?](#how-to-amend-liquibase-sql-changesets?)
|
||||
- [How to Re-Generate Spring-Controller-Interfaces from OpenAPI specs?](#how-to-re-generate-spring-controller-interfaces-from-openapi-specs?)
|
||||
- [How to Generate Database Table Diagrams?](#how-to-generate-database-table-diagrams?)
|
||||
- [How to Add (Real) Admin Users](#how-to-add-(real)-admin-users)
|
||||
- [Further Documentation](#further-documentation)
|
||||
<!-- generated TOC end. -->
|
||||
|
||||
@ -51,10 +58,11 @@ Everything is tested on _Ubuntu Linux 22.04_ and _MacOS Monterey (12.4)_.
|
||||
To be able to build and run the Java Spring Boot application, you need the following tools:
|
||||
|
||||
- Docker 20.x (on MacOS you also need *Docker Desktop* or similar) or Podman
|
||||
- optionally: PostgreSQL Server 15.5-bookworm
|
||||
- optionally: PostgreSQL Server 15.5-bookworm, if you want to use the database directly, not just via Docker
|
||||
(see instructions below to install and run in Docker)
|
||||
- The matching Java JDK at will be automatically installed by Gradle toolchain support to `~/.gradle/jdks/`.
|
||||
- You also might need an IDE (e.g. *IntelliJ IDEA* or *Eclipse* or *VS Code* with *[STS](https://spring.io/tools)* and a GUI Frontend for *PostgreSQL* like *Postbird*.
|
||||
- Python 3 is expected in /usr/bin/python3 if you want to run the `howto` tool (see `bin/howto`)
|
||||
|
||||
If you have at least Docker and the Java JDK installed in appropriate versions and in your `PATH`, then you can start like this:
|
||||
|
||||
@ -64,32 +72,55 @@ If you have at least Docker and the Java JDK installed in appropriate versions a
|
||||
gw # initially downloads the configured Gradle version into the project
|
||||
|
||||
gw test # compiles and runs unit- and integration-tests - takes >10min even on a fast machine
|
||||
gw scenarioTests # compiles and scenario-tests - takes ~1min on a decent machine
|
||||
# `gw test` does NOT run import- and scenario-tests.
|
||||
# Use `gw-test` instead to make sure .tc-environment is sourced.
|
||||
gw scenarioTest # compiles and scenario-tests - takes ~1min on a decent machine
|
||||
# Use `gw-test scenarioTest` instead to make sure .tc-environment is sourced.
|
||||
|
||||
howto test # shows more test information about how to run tests
|
||||
|
||||
# if the container has not been built yet, run this:
|
||||
pg-sql-run # downloads + runs PostgreSQL in a Docker container on localhost:5432
|
||||
|
||||
# if the container has been built already and you want to keep the data, run this:
|
||||
pg-sql-start
|
||||
|
||||
gw bootRun # compiles and runs the application on localhost:8080
|
||||
Next, compile and run the application on `localhost:8080` and the management server on `localhost:8081`:
|
||||
|
||||
# this disables CAS-authentication, for using the REST-API with CAS-authentication, see `bin/cas-curl`.
|
||||
export HSADMINNG_CAS_SERVER=
|
||||
|
||||
# this runs the application with test-data and all modules:
|
||||
gw bootRun --args='--spring.profiles.active=dev,complete,test-data'
|
||||
|
||||
The meaning of these profiles is:
|
||||
|
||||
- **dev**: the PostgreSQL users are created via Liquibase
|
||||
- **complete**: all modules are started
|
||||
- **test-data**: some test data inserted
|
||||
|
||||
Running just `gw bootRun` would just run the *office* module, not insert any test-data and
|
||||
require the PostgreSQL users created in the database (see env-vars in `.aliases`).
|
||||
|
||||
Now we can access the REST API, e.g. using curl:
|
||||
|
||||
# the following command should reply with "pong":
|
||||
curl -f http://localhost:8080/api/ping
|
||||
curl -f -s http://localhost:8080/api/ping
|
||||
|
||||
# the following command should return a JSON array with just all customers:
|
||||
curl -f\
|
||||
curl -f -s\
|
||||
-H 'current-subject: superuser-alex@hostsharing.net' \
|
||||
http://localhost:8080/api/test/customers \
|
||||
| jq # just if `jq` is installed, to prettyprint the output
|
||||
|
||||
# the following command should return a JSON array with just all packages visible for the admin of the customer yyy:
|
||||
curl -f\
|
||||
curl -f -s\
|
||||
-H 'current-subject: superuser-alex@hostsharing.net' -H 'assumed-roles: rbactest.customer#yyy:ADMIN' \
|
||||
http://localhost:8080/api/test/packages \
|
||||
| jq
|
||||
|
||||
# add a new customer
|
||||
curl -f\
|
||||
curl -f -s\
|
||||
-H 'current-subject: superuser-alex@hostsharing.net' -H "Content-Type: application/json" \
|
||||
-d '{ "prefix":"ttt", "reference":80001, "adminUserName":"admin@ttt.example.com" }' \
|
||||
-X POST http://localhost:8080/api/test/customers \
|
||||
@ -101,7 +132,7 @@ Also try for example 'admin@xxx.example.com' or 'unknown@example.org'.
|
||||
|
||||
If you want a formatted JSON output, you can pipe the result to `jq` or similar.
|
||||
|
||||
And to see the full, currently implemented, API, open http://localhost:8080/swagger-ui/index.html.
|
||||
And to see the full, currently implemented, API, open http://localhost:8081/actuator/swagger-ui/index.html (uses management-port and thus bypasses authentication).
|
||||
|
||||
If you still need to install some of these tools, find some hints in the next chapters.
|
||||
|
||||
@ -181,7 +212,7 @@ To generate the TOC (Table of Contents), a little bash script from a
|
||||
Given this is in PATH as `md-toc`, use:
|
||||
|
||||
```shell
|
||||
md-toc <README.md 2 4 | cut -c5-'
|
||||
md-toc <README.md 2 4 | cut -c5-
|
||||
```
|
||||
|
||||
To render the Markdown files, especially to watch embedded PlantUML diagrams, you can use one of the following methods:
|
||||
@ -419,36 +450,42 @@ Some of these rules are checked with *ArchUnit* unit tests.
|
||||
|
||||
### Run Tests from Command Line
|
||||
|
||||
Run all tests which have not yet been passed with the current source code:
|
||||
Run all unit-, integration- and acceptance-tests which have not yet been passed with the current source code:
|
||||
|
||||
```shell
|
||||
gw test
|
||||
gw test # uses the current environment, especially HSADMINNG_POSTGRES_JDBC_URL
|
||||
```
|
||||
|
||||
If the referenced database is not empty, the tests might fail.
|
||||
|
||||
To explicitly use the Testcontainers-environment, run:
|
||||
|
||||
```shell
|
||||
gw-test # uses the environment from .tc-environment
|
||||
```
|
||||
|
||||
Force running all tests:
|
||||
|
||||
```shell
|
||||
gw cleanTest test
|
||||
gw-test --rerun
|
||||
```
|
||||
|
||||
To find more options about running tests, try `howto test`.
|
||||
|
||||
|
||||
### Spotless Code Formatting
|
||||
|
||||
Code formatting for Java is checked via *spotless*.
|
||||
The formatting style can be checked with this command:
|
||||
|
||||
```shell
|
||||
gw spotlessCheck
|
||||
```
|
||||
|
||||
This task is also included in `gw build` and `gw check`.
|
||||
|
||||
To apply formatting rules, use:
|
||||
|
||||
```shell
|
||||
gw spotlessApply
|
||||
gw-spotless
|
||||
```
|
||||
|
||||
The gradle task spotlessCheck is also included in `gw build` and `gw check`,
|
||||
thus if the formatting is not compliant to the rules, the build is going to fail.
|
||||
|
||||
|
||||
### JaCoCo Test Code Coverage Check
|
||||
|
||||
This project uses the JaCoCo test code coverage report with limit checks.
|
||||
@ -486,13 +523,12 @@ Classes to be scanned, tests to be executed and thresholds are configured in [bu
|
||||
A report is generated under [build/reports/pitest/index.html](./build/reports/pitest/index.html).
|
||||
A link to the report is also printed after the `pitest` run.
|
||||
|
||||
This task is also executed as part of `gw check`.
|
||||
<!-- TODO.test: This task is also executed as part of `gw check`. -->
|
||||
|
||||
#### Remark
|
||||
|
||||
In this project, there is little business logic in *Java* code;
|
||||
most business code is in *plsql*
|
||||
and *Java* ist mostly used for mapping REST calls to database queries.
|
||||
In this project, there is a large amount of code is in *plsql*, especially for RBAC.
|
||||
*Java* ist mostly used for mapping and validating REST calls to database queries.
|
||||
This mapping ist mostly done through *Spring* annotations and other implicit code.
|
||||
|
||||
Therefore, there are only few unit tests and thus mutation testing has limited value.
|
||||
@ -526,7 +562,7 @@ In case of suppression, a note must be added to explain why it does not apply to
|
||||
|
||||
See also: https://jeremylong.github.io/DependencyCheck/dependency-check-gradle/index.html.
|
||||
|
||||
### Dependency-License-Compatibility
|
||||
### How to Check Dependency-License-Compatibility
|
||||
|
||||
The `gw check` phase depends on a dependency-license-compatibility check.
|
||||
If any dependency violates the configured [list of allowed licenses](etc/allowed-licenses.json), the build will fail.
|
||||
@ -556,7 +592,7 @@ The generated license can be found here: [index.html](build/reports/dependency-l
|
||||
|
||||
More information can be found on the [project's website](https://github.com/jk1/Gradle-License-Report).
|
||||
|
||||
### Dependency Version Upgrade
|
||||
### How to Upgrade Versions of Dependencies
|
||||
|
||||
Dependency versions can be automatically upgraded to the latest available version:
|
||||
|
||||
@ -584,8 +620,9 @@ This way we would get rid of all explicit grants within the same DB-row
|
||||
and would not need the `rbac.role` table anymore.
|
||||
We would also reduce the depth of the expensive recursive CTE-query.
|
||||
|
||||
This has to be explored further.
|
||||
For now, we just keep it in mind and
|
||||
This has to be explored further. For now, we just keep it in mind and avoid roles+grants
|
||||
which would not fit into a simplified system with a fixed role-type-system.
|
||||
|
||||
|
||||
### The Mapper is Error-Prone
|
||||
|
||||
@ -609,6 +646,36 @@ Besides the following *How Tos* you can also find several *How Tos* in the sourc
|
||||
grep -r HOWTO src
|
||||
```
|
||||
|
||||
also try this (assumed you've sourced .aliases):
|
||||
```sh
|
||||
howto
|
||||
```
|
||||
|
||||
### How to Run the Application With Other Profiles, e.g. production:
|
||||
|
||||
Add `--args='--spring.profiles.active=...` with the wanted profile selector:
|
||||
|
||||
```sh
|
||||
gw bootRun --args='--spring.profiles.active=external-db,only -office,without-test-data'
|
||||
```
|
||||
|
||||
These profiles mean:
|
||||
|
||||
- **external-db**: an external PostgreSQL database is used with the PostgreSQL users already created as specified in the environment
|
||||
- **only-office**: only the Office module is started, but neither the Booking nor the Hosting modules
|
||||
- **without-test-data**: no test-data is inserted
|
||||
|
||||
|
||||
### How to Do a Clean Run of the Application
|
||||
|
||||
If you frequently need to run with a fresh database and a clean build, you can use this:
|
||||
|
||||
```sh
|
||||
export HSADMINNG_CAS_SERVER=
|
||||
gw clean && pg-sql-reset && sleep 5 && gw bootRun' 2>&1 | tee log
|
||||
```
|
||||
|
||||
|
||||
### How to Configure .pgpass for the Default PostgreSQL Database?
|
||||
|
||||
To access the default database schema as used during development, add this line to your `.pgpass` file in your users home directory:
|
||||
@ -807,6 +874,29 @@ postgres-autodoc
|
||||
The output will list the generated files.
|
||||
|
||||
|
||||
### How to Add (Real) Admin Users
|
||||
|
||||
```sql
|
||||
DO $$
|
||||
DECLARE
|
||||
-- replace with your admin account names
|
||||
admin_users TEXT[] := ARRAY['admin-1', 'admin-2', 'admin-3'];
|
||||
admin TEXT;
|
||||
BEGIN
|
||||
-- run as superuser
|
||||
call base.defineContext('adding real admin users', null, null, null);
|
||||
|
||||
-- for all new admin accounts
|
||||
FOREACH admin IN ARRAY admin_users LOOP
|
||||
call rbac.grantRoleToSubjectUnchecked(
|
||||
rbac.findRoleId(rbac.global_ADMIN()), -- granted by role
|
||||
rbac.findRoleId(rbac.global_ADMIN()), -- role to grant
|
||||
rbac.create_subject(admin)); -- creates the new admin account
|
||||
END LOOP;
|
||||
END $$;
|
||||
```
|
||||
|
||||
|
||||
## Further Documentation
|
||||
|
||||
- the `doc` directory contains architecture concepts and a glossary
|
||||
|
249
bin/cas-curl
Executable file
249
bin/cas-curl
Executable file
@ -0,0 +1,249 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ "$2" == "--show-password" ]; then
|
||||
HSADMINNG_CAS_SHOW_PASSWORD=yes
|
||||
shift
|
||||
else
|
||||
HSADMINNG_CAS_SHOW_PASSWORD=
|
||||
fi
|
||||
|
||||
if [ "$1" == "--trace" ]; then
|
||||
function trace() {
|
||||
echo "$*" >&2
|
||||
}
|
||||
function doCurl() {
|
||||
set -x
|
||||
if [ -z "$HSADMINNG_CAS_ASSUME" ]; then
|
||||
curl --fail-with-body \
|
||||
--header "Authorization: $HSADMINNG_CAS_TICKET" \
|
||||
"$@"
|
||||
else
|
||||
curl --fail-with-body \
|
||||
--header "Authorization: $HSADMINNG_CAS_TICKET" \
|
||||
--header "assumed-roles: $HSADMINNG_CAS_ASSUME" \
|
||||
"$@"
|
||||
fi
|
||||
set +x
|
||||
}
|
||||
shift
|
||||
else
|
||||
function trace() {
|
||||
: # noop
|
||||
}
|
||||
function doCurl() {
|
||||
curl --fail-with-body --header "Authorization: $HSADMINNG_CAS_TICKET" "$@"
|
||||
}
|
||||
fi
|
||||
|
||||
export HSADMINNG_CAS_ASSUME_HEADER
|
||||
if [ -f ~/.cas-curl-assume ]; then
|
||||
HSADMINNG_CAS_ASSUME="$(cat ~/.cas-curl-assume)"
|
||||
else
|
||||
HSADMINNG_CAS_ASSUME=
|
||||
fi
|
||||
|
||||
if [ -z "$HSADMINNG_CAS_LOGIN" ] || [ -z "$HSADMINNG_CAS_VALIDATE" ] || \
|
||||
[ -z "$HSADMINNG_CAS_SERVICE_ID" ]; then
|
||||
cat >&2 <<EOF
|
||||
ERROR: environment incomplete
|
||||
|
||||
please set the following environment variables:
|
||||
export HSADMINNG_CAS_LOGIN=https://login.hostsharing.net/cas/v1/tickets
|
||||
export HSADMINNG_CAS_VALIDATE=https://login.hostsharing.net/cas/proxyValidate
|
||||
export HSADMINNG_CAS_USERNAME=<<optionally, your username, or leave empty after '='>>
|
||||
export HSADMINNG_CAS_PASSWORD=<<optionally, your password, or leave empty after '='>>
|
||||
export HSADMINNG_CAS_SERVICE_ID=https://hsadminng.hostsharing.net:443/
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
|
||||
function casCurlDocumentation() {
|
||||
cat <<EOF
|
||||
curl-wrapper utilizing CAS-authentication for hsadmin-ng
|
||||
usage: $0 [--trace] [--show-password] <<command>> [parameters]
|
||||
|
||||
commands:
|
||||
EOF
|
||||
# filters out help texts (containing double-# and following lines with leading single-#) from the commands itself
|
||||
# (the '' makes sure that this line is not found, just the lines with actual help texts)
|
||||
sed -n '/#''#/ {x; p; x; s/#''#//; p; :a; n; /^[[:space:]]*#/!b; s/^[[:space:]]*#//; p; ba}' <$0
|
||||
}
|
||||
|
||||
function casLogin() {
|
||||
# ticket granting ticket exists and not expired?
|
||||
if find ~/.cas-login-tgt -type f -size +0c -mmin -60 2>/dev/null | grep -q .; then
|
||||
return
|
||||
fi
|
||||
|
||||
if [ -z "$HSADMINNG_CAS_USERNAME" ]; then
|
||||
read -e -p "Username: " HSADMINNG_CAS_USERNAME
|
||||
fi
|
||||
|
||||
if [ -z "$HSADMINNG_CAS_PASSWORD" ]; then
|
||||
read -s -e -p "Password: " HSADMINNG_CAS_PASSWORD
|
||||
fi
|
||||
|
||||
if [ "$HSADMINNG_CAS_SHOW_PASSWORD" == "--show-password" ]; then
|
||||
HSADMINNG_CAS_PASSWORD_DISPLAY=$HSADMINNG_CAS_PASSWORD
|
||||
else
|
||||
HSADMINNG_CAS_PASSWORD_DISPLAY="<<password hidden - use --show-password to show>>"
|
||||
fi
|
||||
|
||||
# Do NOT use doCurl here! We do neither want to print the password nor pass a CAS service ticket.
|
||||
trace "+ curl --fail-with-body -s -i -X POST \
|
||||
-H 'Content-Type: application/x-www-form-urlencoded' \
|
||||
-d \"username=$HSADMINNG_CAS_USERNAME&password=$HSADMINNG_CAS_PASSWORD_DISPLAY\" \
|
||||
$HSADMINNG_CAS_LOGIN -o ~/.cas-login-tgt.response -D -"
|
||||
HSADMINNG_CAS_TGT=`curl --fail-with-body -s -i -X POST \
|
||||
-H 'Content-Type: application/x-www-form-urlencoded' \
|
||||
-d "username=$HSADMINNG_CAS_USERNAME&password=$HSADMINNG_CAS_PASSWORD" \
|
||||
$HSADMINNG_CAS_LOGIN -o ~/.cas-login-tgt.response -D - \
|
||||
| grep -i "^Location: " | sed -e 's/^Location: //' -e 's/\\r//'`
|
||||
if [ -z "$HSADMINNG_CAS_TGT" ]; then
|
||||
echo "ERROR: could not get ticket granting ticket" >&2
|
||||
cat ~/.cas-login-tgt.response >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "$HSADMINNG_CAS_TGT" >~/.cas-login-tgt
|
||||
trace "$HSADMINNG_CAS_TGT"
|
||||
}
|
||||
|
||||
function casLogout() {
|
||||
rm -f ~/.cas-login-tgt
|
||||
}
|
||||
|
||||
function casTicket() {
|
||||
HSADMINNG_CAS_TGT=$(<~/.cas-login-tgt)
|
||||
if [[ -z "$HSADMINNG_CAS_TGT" ]]; then
|
||||
echo "ERROR: cannot get CAS ticket granting ticket for $HSADMINNG_CAS_USERNAME" >&2
|
||||
exit 1
|
||||
fi
|
||||
trace "CAS-TGT: $HSADMINNG_CAS_TGT"
|
||||
|
||||
trace "fetching CAS service ticket"
|
||||
trace "curl -s -d \"service=$HSADMINNG_CAS_SERVICE_ID\" $HSADMINNG_CAS_TGT"
|
||||
HSADMINNG_CAS_TICKET=$(curl -s -d "service=$HSADMINNG_CAS_SERVICE_ID" $HSADMINNG_CAS_TGT)
|
||||
if [[ -z "$HSADMINNG_CAS_TICKET" ]]; then
|
||||
echo "ERROR: cannot get CAS service ticket" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo $HSADMINNG_CAS_TICKET
|
||||
}
|
||||
|
||||
function casValidate() {
|
||||
HSADMINNG_CAS_TICKET=`casTicket`
|
||||
|
||||
trace "validating CAS-TICKET: $HSADMINNG_CAS_TICKET"
|
||||
# Do NOT use doCurl here! We do not pass a CAS service ticket.
|
||||
trace curl -i -s $HSADMINNG_CAS_VALIDATE?ticket=${HSADMINNG_CAS_TICKET}\&service=${HSADMINNG_CAS_SERVICE_ID}
|
||||
HSADMINNG_CAS_USER=`curl -i -s $HSADMINNG_CAS_VALIDATE?ticket=${HSADMINNG_CAS_TICKET}\&service=${HSADMINNG_CAS_SERVICE_ID} | grep -oPm1 "(?<=<cas:user>)[^<]+"`
|
||||
if [ -z "$HSADMINNG_CAS_USER" ]; then
|
||||
echo "validation failed" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "CAS-User: $HSADMINNG_CAS_USER"
|
||||
}
|
||||
|
||||
case "${1,,}" in
|
||||
|
||||
# -- generic commands --------------------------------------------------------------------------
|
||||
|
||||
""|"-h"|"--help"|"help") ## prints documentation about commands and options
|
||||
casCurlDocumentation
|
||||
exit
|
||||
;;
|
||||
|
||||
"env") ## prints all related HSADMINNG_CAS_... environment variables; use '--show-password' to show the password as well
|
||||
# example: cas-curl env --show-password
|
||||
echo "HSADMINNG_CAS_LOGIN: $HSADMINNG_CAS_LOGIN"
|
||||
echo "HSADMINNG_CAS_VALIDATE: $HSADMINNG_CAS_VALIDATE"
|
||||
echo "HSADMINNG_CAS_USERNAME: $HSADMINNG_CAS_USERNAME"
|
||||
if [ "$2" == "--show-password" ]; then
|
||||
echo "HSADMINNG_CAS_PASSWORD: $HSADMINNG_CAS_PASSWORD"
|
||||
elif [ -z "$HSADMINNG_CAS_PASSWORD" ]; then
|
||||
echo "HSADMINNG_CAS_PASSWORD: <<not given>>"
|
||||
else
|
||||
echo "HSADMINNG_CAS_PASSWORD: <<given, but hidden - add --show-password to show>>"
|
||||
fi
|
||||
echo "HSADMINNG_CAS_SERVICE_ID: $HSADMINNG_CAS_SERVICE_ID"
|
||||
;;
|
||||
|
||||
# --- authentication-related commands ------------------------------------------------------------
|
||||
|
||||
"login") ## reads username+password and fetches ticket granting ticket (bypasses HSADMINNG_CAS_USERNAME+HSADMINNG_CAS_PASSWORD)
|
||||
# example: cas-curl login
|
||||
casLogout
|
||||
export HSADMINNG_CAS_USERNAME=
|
||||
export HSADMINNG_CAS_PASSWORD=
|
||||
casLogin
|
||||
;;
|
||||
"assume") ## assumes the given comma-separated roles
|
||||
# example using object-id-name: cas-curl assume 'hs_office.relation#ExampleMandant-with-PARTNER-ExamplePartner:AGENT'
|
||||
# example using object-uuid: cas-curl assume 'hs_office.relation#1d3bc468-c5c8-11ef-9d0d-4751ecfda2b7:AGENT'
|
||||
shift
|
||||
if [ -z "$1" ]; then
|
||||
echo "ERROR: requires comma-separated list of roles to assume" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "$1" >~/.cas-curl-assume
|
||||
;;
|
||||
"unassume") ## do not assume any particular role anymore, use the plain user as RBAC subject
|
||||
rm ~/.cas-curl-assume
|
||||
;;
|
||||
"validate") ## validates current ticket granting ticket and prints currently logged in user
|
||||
casValidate
|
||||
;;
|
||||
"logout") ## logout, deletes ticket granting ticket
|
||||
casLogout
|
||||
;;
|
||||
|
||||
# --- HTTP-commands ----------------------------------------------------------------------
|
||||
|
||||
"get") ## HTTP GET, add URL as parameter
|
||||
# example: cas-curl GET http://localhost:8080/api/hs/office/partners/P-10003 | jq
|
||||
# hint: '| jq' is just for human-readable formatted JSON output
|
||||
shift
|
||||
casLogin
|
||||
HSADMINNG_CAS_TICKET=`casTicket`
|
||||
doCurl "$*"
|
||||
;;
|
||||
"post") ## HTTP POST, add curl options to specify the request body and the URL as last parameter
|
||||
# example: cas-curl POST \
|
||||
# -d '{ "prefix":"ttt", "reference":80001, "adminUserName":"admin@ttt.example.com" }' \
|
||||
# http://localhost:8080/api/test/customers | jq
|
||||
# hint: '| jq' is just for human-readable formatted JSON output
|
||||
shift
|
||||
casLogin
|
||||
HSADMINNG_CAS_TICKET=`casTicket`
|
||||
doCurl --header "Content-Type: application/json" -X POST "$@"
|
||||
;;
|
||||
"patch") ## HTTP PATCH, add curl options to specify the request body and the URL as last parameterparameter
|
||||
# example: cas-curl PATCH \
|
||||
# -d '{ "reference":80002 }' \
|
||||
# http://localhost:8080/api/test/customers/ae90ac2a-4728-4ca9-802e-a0d0108b2324 | jq
|
||||
# hint: '| jq' is just for human-readable formatted JSON output
|
||||
shift
|
||||
casLogin
|
||||
HSADMINNG_CAS_TICKET=`casTicket`
|
||||
doCurl --header "Content-Type: application/json" -X POST "$*"
|
||||
;;
|
||||
"delete") ## HTTP DELETE, add curl options to specify the request body and the URL as last parameter
|
||||
# example: cas-curl DELETE http://localhost:8080/api/hs/office/persons/ae90ac2a-4728-4ca9-802e-a0d0108b2324
|
||||
shift
|
||||
casLogin
|
||||
HSADMINNG_CAS_TICKET=`casTicket`
|
||||
curl -X POST "$@"
|
||||
;;
|
||||
*)
|
||||
cat >&2 <<EOF
|
||||
unknown command: '$1'
|
||||
valid commands: help, login, logout, validate, get, post, patch, delete
|
||||
EOF
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
|
||||
|
||||
|
86
bin/howto
Executable file
86
bin/howto
Executable file
@ -0,0 +1,86 @@
|
||||
#!/usr/bin/python3
|
||||
import os
|
||||
import sys
|
||||
from urllib.parse import urljoin, quote
|
||||
|
||||
def path_to_file_uri(path):
|
||||
"""
|
||||
Converts a file path to a file URI.
|
||||
Handles absolute and relative paths.
|
||||
"""
|
||||
abs_path = os.path.abspath(path)
|
||||
return urljoin("file://", quote(abs_path))
|
||||
|
||||
def is_binary_file(filepath, chunk_size=1024):
|
||||
"""
|
||||
Prüft, ob eine Datei binär ist, indem sie den Inhalt der Datei auf nicht-druckbare Zeichen untersucht.
|
||||
"""
|
||||
try:
|
||||
with open(filepath, "rb") as file:
|
||||
chunk = file.read(chunk_size)
|
||||
if b"\0" in chunk: # Nullbyte ist ein typisches Zeichen für Binärdateien
|
||||
return True
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"Fehler beim Prüfen, ob Datei binär ist: {filepath}: {e}")
|
||||
return True
|
||||
|
||||
def search_keywords_in_files(keywords):
|
||||
if not keywords:
|
||||
print("Bitte geben Sie mindestens ein Stichwort an.")
|
||||
sys.exit(1)
|
||||
|
||||
# Allowed comment symbols
|
||||
comment_symbols = {"//", "#", ";"}
|
||||
|
||||
for root, dirs, files in os.walk("."):
|
||||
# Ausschließen bestimmter Verzeichnisse
|
||||
dirs[:] = [d for d in dirs if d not in {".git", "build"}]
|
||||
|
||||
for file in files:
|
||||
filepath = os.path.join(root, file)
|
||||
|
||||
# Überspringen von Binärdateien
|
||||
if is_binary_file(filepath):
|
||||
continue
|
||||
|
||||
try:
|
||||
with open(filepath, "r", encoding="utf-8") as f:
|
||||
lines = f.readlines()
|
||||
|
||||
for line_number, line in enumerate(lines, start=1):
|
||||
stripped_line = line.lstrip() # Entfernt führende Leerzeichen
|
||||
for symbol in comment_symbols:
|
||||
if stripped_line.startswith(symbol):
|
||||
# Entfernt das Kommentarzeichen und nachfolgende Leerzeichen
|
||||
howtoMatch = stripped_line[len(symbol):].lstrip()
|
||||
if howtoMatch.startswith(("HOWTO ", "HOWTO: ", "How to ")):
|
||||
if all(keyword in howtoMatch.lower() for keyword in keywords):
|
||||
|
||||
# Titelzeile ohne Kommentarzeichen
|
||||
print(howtoMatch.rstrip())
|
||||
|
||||
# Ausgabe nachfolgender Zeilen mit dem gleichen Kommentar-Präfix
|
||||
for subsequent_line in lines[line_number:]:
|
||||
subsequent_line = subsequent_line.lstrip()
|
||||
if subsequent_line.startswith(symbol):
|
||||
# Entfernt Kommentarzeichen aus Folgezeilen
|
||||
print("\t" + subsequent_line[len(symbol):].strip())
|
||||
else:
|
||||
break
|
||||
|
||||
# Link mit Zeilennummer
|
||||
print(f"--> {path_to_file_uri(filepath)}:{line_number}")
|
||||
|
||||
# Abstand zwischen Matches
|
||||
print()
|
||||
break
|
||||
except Exception as e:
|
||||
print(f"Fehler beim Lesen der Datei {filepath}: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
print("Verwendung: bin/howto <Stichwort1> <Stichwort2> ...")
|
||||
sys.exit(1)
|
||||
|
||||
search_keywords_in_files([arg.lower() for arg in sys.argv[1:]])
|
135
build.gradle
135
build.gradle
@ -1,17 +1,20 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.boot' version '3.3.4'
|
||||
id 'io.spring.dependency-management' version '1.1.6'
|
||||
id 'io.openapiprocessor.openapi-processor' version '2023.2'
|
||||
id 'com.github.jk1.dependency-license-report' version '2.9'
|
||||
id "org.owasp.dependencycheck" version "10.0.4"
|
||||
id "com.diffplug.spotless" version "6.25.0"
|
||||
id 'jacoco'
|
||||
id 'info.solidsoft.pitest' version '1.15.0'
|
||||
id 'se.patrikerdes.use-latest-versions' version '0.2.18'
|
||||
id 'com.github.ben-manes.versions' version '0.51.0'
|
||||
id 'org.springframework.boot' version '3.4.1'
|
||||
id 'io.spring.dependency-management' version '1.1.7' // manages implicit dependencies
|
||||
id 'io.openapiprocessor.openapi-processor' version '2023.2' // generates Controller-interface and resources from API-spec
|
||||
id 'com.github.jk1.dependency-license-report' version '2.9' // checks dependency-license compatibility
|
||||
id "org.owasp.dependencycheck" version "12.0.1" // checks dependencies for known vulnerabilities
|
||||
id "com.diffplug.spotless" version "7.0.2" // formats + checks formatting for source-code
|
||||
id 'jacoco' // determines code-coverage of tests
|
||||
id 'info.solidsoft.pitest' version '1.15.0' // performs mutation testing
|
||||
id 'se.patrikerdes.use-latest-versions' version '0.2.18' // updates module and plugin versions
|
||||
id 'com.github.ben-manes.versions' version '0.52.0' // determines which dependencies have updates
|
||||
}
|
||||
|
||||
// HOWTO: find out which dependency versions are managed by Spring Boot:
|
||||
// https://docs.spring.io/spring-boot/appendix/dependency-versions/coordinates.html
|
||||
|
||||
group = 'net.hostsharing'
|
||||
version = '0.0.1-SNAPSHOT'
|
||||
|
||||
@ -20,6 +23,9 @@ wrapper {
|
||||
gradleVersion = '8.5'
|
||||
}
|
||||
|
||||
// TODO.impl: self-attaching is deprecated, see:
|
||||
// https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#0.3
|
||||
|
||||
configurations {
|
||||
compileOnly {
|
||||
extendsFrom annotationProcessor
|
||||
@ -60,25 +66,25 @@ dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-actuator'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||
implementation 'com.github.gavlyukovskiy:datasource-proxy-spring-boot-starter:1.9.2'
|
||||
implementation 'org.springdoc:springdoc-openapi:2.6.0'
|
||||
implementation 'org.postgresql:postgresql:42.7.4'
|
||||
implementation 'org.liquibase:liquibase-core:4.29.2'
|
||||
implementation 'io.hypersistence:hypersistence-utils-hibernate-63:3.8.3'
|
||||
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.0'
|
||||
implementation 'com.github.gavlyukovskiy:datasource-proxy-spring-boot-starter:1.10.0'
|
||||
implementation 'org.springdoc:springdoc-openapi:2.8.3'
|
||||
implementation 'org.postgresql:postgresql'
|
||||
implementation 'org.liquibase:liquibase-core'
|
||||
implementation 'io.hypersistence:hypersistence-utils-hibernate-63:3.9.0'
|
||||
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
|
||||
implementation 'org.openapitools:jackson-databind-nullable:0.2.6'
|
||||
implementation 'org.apache.commons:commons-text:1.12.0'
|
||||
implementation 'net.java.dev.jna:jna:5.15.0'
|
||||
implementation 'org.modelmapper:modelmapper:3.2.1'
|
||||
implementation 'org.apache.commons:commons-text:1.13.0'
|
||||
implementation 'net.java.dev.jna:jna:5.16.0'
|
||||
implementation 'org.modelmapper:modelmapper:3.2.2'
|
||||
implementation 'org.iban4j:iban4j:3.2.10-RELEASE'
|
||||
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0'
|
||||
implementation 'org.webjars:swagger-ui:5.17.14'
|
||||
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.3'
|
||||
implementation 'org.reflections:reflections:0.10.2'
|
||||
|
||||
compileOnly 'org.projectlombok:lombok'
|
||||
testCompileOnly 'org.projectlombok:lombok'
|
||||
|
||||
developmentOnly 'org.springframework.boot:spring-boot-devtools'
|
||||
// TODO.impl: version conflict with SpringDoc, check later and re-enable if fixed
|
||||
// developmentOnly 'org.springframework.boot:spring-boot-devtools'
|
||||
|
||||
annotationProcessor 'org.projectlombok:lombok'
|
||||
testAnnotationProcessor 'org.projectlombok:lombok'
|
||||
@ -90,9 +96,10 @@ dependencies {
|
||||
testImplementation 'org.testcontainers:postgresql'
|
||||
testImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0'
|
||||
testImplementation 'io.rest-assured:spring-mock-mvc'
|
||||
testImplementation 'org.hamcrest:hamcrest-core:3.0'
|
||||
testImplementation 'org.hamcrest:hamcrest-core'
|
||||
testImplementation 'org.pitest:pitest-junit5-plugin:1.2.1'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api'
|
||||
testImplementation 'org.wiremock:wiremock-standalone:3.10.0'
|
||||
}
|
||||
|
||||
dependencyManagement {
|
||||
@ -173,7 +180,9 @@ openapiProcessor {
|
||||
}
|
||||
}
|
||||
sourceSets.main.java.srcDir 'build/generated/sources/openapi'
|
||||
|
||||
abstract class ProcessSpring extends DefaultTask {}
|
||||
|
||||
tasks.register('processSpring', ProcessSpring)
|
||||
['processSpringRoot',
|
||||
'processSpringRbac',
|
||||
@ -204,7 +213,7 @@ openApiGenerate.dependsOn processSpring
|
||||
spotless {
|
||||
java {
|
||||
removeUnusedImports()
|
||||
indentWithSpaces(4)
|
||||
leadingTabsToSpaces(4)
|
||||
endWithNewline()
|
||||
toggleOffOn()
|
||||
|
||||
@ -218,7 +227,7 @@ project.tasks.check.dependsOn(spotlessCheck)
|
||||
// HACK: no idea why spotless uses the output of these tasks, but we get warnings without those
|
||||
project.tasks.spotlessJava.dependsOn(
|
||||
tasks.generateLicenseReport,
|
||||
tasks.pitest,
|
||||
// tasks.pitest, TODO.test: PiTest currently does not work, needs to be fixed
|
||||
tasks.jacocoTestReport,
|
||||
tasks.processResources,
|
||||
tasks.processTestResources)
|
||||
@ -247,19 +256,21 @@ licenseReport {
|
||||
}
|
||||
project.tasks.check.dependsOn(checkLicense)
|
||||
|
||||
// JaCoCo Test Code Coverage
|
||||
jacoco {
|
||||
toolVersion = "0.8.10"
|
||||
}
|
||||
// HOWTO: run all tests except import- and scenario-tests: gw test
|
||||
test {
|
||||
finalizedBy jacocoTestReport // generate report after tests
|
||||
excludes = [
|
||||
'net.hostsharing.hsadminng.**.generated.**',
|
||||
]
|
||||
useJUnitPlatform {
|
||||
excludeTags 'importOfficeData', 'importHostingData', 'scenarioTest'
|
||||
excludeTags 'importHostingAssets', 'scenarioTest'
|
||||
}
|
||||
}
|
||||
|
||||
// JaCoCo Test Code Coverage for unit-tests
|
||||
jacoco {
|
||||
toolVersion = "0.8.10"
|
||||
}
|
||||
jacocoTestReport {
|
||||
dependsOn test
|
||||
afterEvaluate {
|
||||
@ -324,13 +335,63 @@ jacocoTestCoverageVerification {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register('importOfficeData', Test) {
|
||||
// HOWTO: run all unit-tests which don't need a database: gw-test unitTest
|
||||
tasks.register('unitTest', Test) {
|
||||
useJUnitPlatform {
|
||||
includeTags 'importOfficeData'
|
||||
excludeTags 'importHostingAssets', 'scenarioTest', 'generalIntegrationTest',
|
||||
'officeIntegrationTest', 'bookingIntegrationTest', 'hostingIntegrationTest'
|
||||
}
|
||||
|
||||
group 'verification'
|
||||
description 'run the import jobs as tests'
|
||||
description 'runs all unit-tests which do not need a database'
|
||||
|
||||
mustRunAfter spotlessJava
|
||||
}
|
||||
|
||||
// HOWTO: run all integration tests which are not specific to a module, like base, rbac, config etc.
|
||||
tasks.register('generalIntegrationTest', Test) {
|
||||
useJUnitPlatform {
|
||||
includeTags 'generalIntegrationTest'
|
||||
}
|
||||
|
||||
group 'verification'
|
||||
description 'runs integration tests which are not specific to a module, like base, rbac, config etc.'
|
||||
|
||||
mustRunAfter spotlessJava
|
||||
}
|
||||
|
||||
// HOWTO: run all integration tests of the office module: gw-test officeIntegrationTest
|
||||
tasks.register('officeIntegrationTest', Test) {
|
||||
useJUnitPlatform {
|
||||
includeTags 'officeIntegrationTest'
|
||||
}
|
||||
|
||||
group 'verification'
|
||||
description 'runs integration tests of the office module'
|
||||
|
||||
mustRunAfter spotlessJava
|
||||
}
|
||||
|
||||
// HOWTO: run all integration tests of the booking module: gw-test bookingIntegrationTest
|
||||
tasks.register('bookingIntegrationTest', Test) {
|
||||
useJUnitPlatform {
|
||||
includeTags 'bookingIntegrationTest'
|
||||
}
|
||||
|
||||
group 'verification'
|
||||
description 'runs integration tests of the booking module'
|
||||
|
||||
mustRunAfter spotlessJava
|
||||
}
|
||||
|
||||
// HOWTO: run all integration tests of the hosting module: gw-test hostingIntegrationTest
|
||||
tasks.register('hostingIntegrationTest', Test) {
|
||||
useJUnitPlatform {
|
||||
includeTags 'hostingIntegrationTest'
|
||||
}
|
||||
|
||||
group 'verification'
|
||||
description 'runs integration tests of the hosting module'
|
||||
|
||||
mustRunAfter spotlessJava
|
||||
}
|
||||
@ -346,7 +407,7 @@ tasks.register('importHostingAssets', Test) {
|
||||
mustRunAfter spotlessJava
|
||||
}
|
||||
|
||||
tasks.register('scenarioTests', Test) {
|
||||
tasks.register('scenarioTest', Test) {
|
||||
useJUnitPlatform {
|
||||
includeTags 'scenarioTest'
|
||||
}
|
||||
@ -367,7 +428,7 @@ pitest {
|
||||
]
|
||||
|
||||
targetTests = ['net.hostsharing.hsadminng.**.*UnitTest', 'net.hostsharing.hsadminng.**.*RestTest']
|
||||
excludedTestClasses = ['**AcceptanceTest*', '**IntegrationTest*']
|
||||
excludedTestClasses = ['**AcceptanceTest*', '**IntegrationTest*', '**ImportHostingAssets']
|
||||
|
||||
pitestVersion = '1.17.0'
|
||||
junit5PluginVersion = '1.1.0'
|
||||
@ -382,7 +443,7 @@ pitest {
|
||||
outputFormats = ['XML', 'HTML']
|
||||
timestampedReports = false
|
||||
}
|
||||
project.tasks.check.dependsOn(project.tasks.pitest)
|
||||
// project.tasks.check.dependsOn(project.tasks.pitest) TODO.test: PiTest currently does not work, needs to be fixed
|
||||
project.tasks.pitest.doFirst { // Why not doLast? See README.md!
|
||||
println "PiTest Mutation Report: file:///${project.rootDir}/build/reports/pitest/index.html"
|
||||
}
|
||||
@ -446,7 +507,7 @@ tasks.register('convertMarkdownToHtml') {
|
||||
}
|
||||
}
|
||||
}
|
||||
convertMarkdownToHtml.dependsOn scenarioTests
|
||||
convertMarkdownToHtml.dependsOn scenarioTest
|
||||
|
||||
// shortcut for compiling all files
|
||||
tasks.register('compile') {
|
||||
|
@ -50,6 +50,7 @@ classDiagram
|
||||
UNKNOWN: nur für Import
|
||||
NATURAL_PERSON: natürliche Person
|
||||
LEGAL_PERSON: z.B. GmbH, e.K., eG, e.V.
|
||||
ORGANIZATIONAL_UNIT: z.B. "Admin-Team", "Buchhaltung"
|
||||
INCORORATED_FIRM: z.B. OHG, Partnerschaftsgesellschaft
|
||||
UNINCORPORATED_FIRM: z.B. GbR, ARGE, Erbengemeinschaft
|
||||
PUBLIC_INSTITUTION: KdöR, AöR [ohne Registergericht/Registernummer]
|
||||
|
@ -5,9 +5,23 @@
|
||||
{ "moduleLicense": "Apache-2.0" },
|
||||
{ "moduleLicense": "Apache License 2.0" },
|
||||
{ "moduleLicense": "Apache License v2.0" },
|
||||
{ "moduleLicense": "Apache License Version 2.0" },
|
||||
{ "moduleLicense": "Apache License, Version 2.0" },
|
||||
{ "moduleLicense": "The Apache License, Version 2.0" },
|
||||
{ "moduleLicense": "The Apache Software License, Version 2.0" },
|
||||
|
||||
{
|
||||
"moduleLicense": null,
|
||||
"#moduleLicense": "Apache License 2.0, see https://github.com/springdoc/springdoc-openapi/blob/main/LICENSE",
|
||||
"moduleVersion": "2.4.0",
|
||||
"moduleName": "org.springdoc:springdoc-openapi"
|
||||
},
|
||||
{
|
||||
"moduleLicense": null,
|
||||
"moduleVersion": "1.0.0",
|
||||
"moduleName": "org.jspecify:jspecify"
|
||||
},
|
||||
|
||||
{ "moduleLicense": "BSD License" },
|
||||
{ "moduleLicense": "BSD-2-Clause" },
|
||||
{ "moduleLicense": "BSD-3-Clause" },
|
||||
@ -46,14 +60,8 @@
|
||||
{
|
||||
"moduleLicense": "Public Domain, per Creative Commons CC0",
|
||||
"moduleVersion": "2.0.3"
|
||||
},
|
||||
|
||||
{
|
||||
"moduleLicense": null,
|
||||
"#moduleLicense": "Apache License 2.0, see https://github.com/springdoc/springdoc-openapi/blob/main/LICENSE",
|
||||
"moduleVersion": "2.4.0",
|
||||
"moduleName": "org.springdoc:springdoc-openapi"
|
||||
}
|
||||
|
||||
|
||||
]
|
||||
}
|
||||
|
@ -9,8 +9,12 @@
|
||||
</suppress>
|
||||
<suppress>
|
||||
<notes><![CDATA[
|
||||
Malicious HTTP redirect in JAXB on a REST-endpoint is not that dangerous.
|
||||
file name: logback-core-1.5.12.jar
|
||||
A successful attack requires the user to have write access to a configuration file or environment vars.
|
||||
]]></notes>
|
||||
<cve>CVE-2024-9329</cve>
|
||||
<packageUrl regex="true">^pkg:maven/ch\.qos\.logback/logback-core@.*$</packageUrl>
|
||||
<cpe>cpe:/a:qos:logback</cpe>
|
||||
<cve>CVE-2024-12798</cve>
|
||||
</suppress>
|
||||
|
||||
</suppressions>
|
||||
|
@ -0,0 +1,105 @@
|
||||
package net.hostsharing.hsadminng.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.actuate.endpoint.SanitizableData;
|
||||
import org.springframework.boot.actuate.endpoint.SanitizingFunction;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
// HOWTO: exclude sensitive values, like passwords and other secrets, from being show by actuator endpoints:
|
||||
// either use: add your custom keys to management.endpoint.additionalKeysToSanitize,
|
||||
// or, if you need more heuristics, amend this code down here.
|
||||
@Component
|
||||
public class ActuatorSanitizer implements SanitizingFunction {
|
||||
|
||||
private static final String[] REGEX_PARTS = {"*", "$", "^", "+"};
|
||||
|
||||
private static final Set<String> DEFAULT_KEYS_TO_SANITIZE = Set.of(
|
||||
"password", "secret", "token", ".*credentials.*", "vcap_services", "^vcap\\.services.*$", "sun.java.command", "^spring[._]application[._]json$"
|
||||
);
|
||||
|
||||
private static final Set<String> URI_USERINFO_KEYS = Set.of(
|
||||
"uri", "uris", "url", "urls", "address", "addresses"
|
||||
);
|
||||
|
||||
private static final Pattern URI_USERINFO_PATTERN = Pattern.compile("^\\[?[A-Za-z][A-Za-z0-9\\+\\.\\-]+://.+:(.*)@.+$");
|
||||
|
||||
private final List<Pattern> keysToSanitize = new ArrayList<>();
|
||||
|
||||
public ActuatorSanitizer(@Value("${management.endpoint.additionalKeysToSanitize:}") final List<String> additionalKeysToSanitize) {
|
||||
addKeysToSanitize(DEFAULT_KEYS_TO_SANITIZE);
|
||||
addKeysToSanitize(URI_USERINFO_KEYS);
|
||||
addKeysToSanitize(additionalKeysToSanitize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SanitizableData apply(final SanitizableData data) {
|
||||
if (data.getValue() == null) {
|
||||
return data;
|
||||
}
|
||||
|
||||
for (final Pattern pattern : keysToSanitize) {
|
||||
if (pattern.matcher(data.getKey()).matches()) {
|
||||
if (keyIsUriWithUserInfo(pattern)) {
|
||||
return data.withValue(sanitizeUris(data.getValue().toString()));
|
||||
}
|
||||
|
||||
return data.withValue(SanitizableData.SANITIZED_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private void addKeysToSanitize(final Collection<String> keysToSanitize) {
|
||||
for (final String key : keysToSanitize) {
|
||||
this.keysToSanitize.add(getPattern(key));
|
||||
}
|
||||
}
|
||||
|
||||
private Pattern getPattern(final String value) {
|
||||
if (isRegex(value)) {
|
||||
return Pattern.compile(value, Pattern.CASE_INSENSITIVE);
|
||||
}
|
||||
return Pattern.compile(".*" + value + "$", Pattern.CASE_INSENSITIVE);
|
||||
}
|
||||
|
||||
private boolean isRegex(final String value) {
|
||||
for (final String part : REGEX_PARTS) {
|
||||
if (value.contains(part)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean keyIsUriWithUserInfo(final Pattern pattern) {
|
||||
for (String uriKey : URI_USERINFO_KEYS) {
|
||||
if (pattern.matcher(uriKey).matches()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private Object sanitizeUris(final String value) {
|
||||
return Arrays.stream(value.split(",")).map(this::sanitizeUri).collect(Collectors.joining(","));
|
||||
}
|
||||
|
||||
private String sanitizeUri(final String value) {
|
||||
final var matcher = URI_USERINFO_PATTERN.matcher(value);
|
||||
final var password = matcher.matches() ? matcher.group(1) : null;
|
||||
if (password != null) {
|
||||
return StringUtils.replace(value, ":" + password + "@", ":" + SanitizableData.SANITIZED_VALUE + "@");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package net.hostsharing.hsadminng.config;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequestWrapper;
|
||||
import java.util.*;
|
||||
|
||||
public class AuthenticatedHttpServletRequestWrapper extends HttpServletRequestWrapper {
|
||||
|
||||
private final Map<String, String> customHeaders = new HashMap<>();
|
||||
|
||||
public AuthenticatedHttpServletRequestWrapper(HttpServletRequest request) {
|
||||
super(request);
|
||||
}
|
||||
|
||||
public void addHeader(final String name, final String value) {
|
||||
customHeaders.put(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeader(final String name) {
|
||||
// Check custom headers first
|
||||
final var customHeaderValue = customHeaders.get(name);
|
||||
if (customHeaderValue != null) {
|
||||
return customHeaderValue;
|
||||
}
|
||||
// Fall back to the original headers
|
||||
return super.getHeader(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<String> getHeaderNames() {
|
||||
// Combine original headers and custom headers
|
||||
final var headerNames = new HashSet<>(customHeaders.keySet());
|
||||
final var originalHeaderNames = super.getHeaderNames();
|
||||
while (originalHeaderNames.hasMoreElements()) {
|
||||
headerNames.add(originalHeaderNames.nextElement());
|
||||
}
|
||||
return Collections.enumeration(headerNames);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<String> getHeaders(final String name) {
|
||||
// Combine original headers and custom header
|
||||
final var values = new HashSet<String>();
|
||||
if (customHeaders.containsKey(name)) {
|
||||
values.add(customHeaders.get(name));
|
||||
}
|
||||
final var originalValues = super.getHeaders(name);
|
||||
while (originalValues.hasMoreElements()) {
|
||||
values.add(originalValues.nextElement());
|
||||
}
|
||||
return Collections.enumeration(values);
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package net.hostsharing.hsadminng.config;
|
||||
|
||||
import jakarta.servlet.Filter;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class AuthenticationFilter implements Filter {
|
||||
|
||||
@Autowired
|
||||
private Authenticator authenticator;
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) {
|
||||
final var httpRequest = (HttpServletRequest) request;
|
||||
final var httpResponse = (HttpServletResponse) response;
|
||||
|
||||
try {
|
||||
final var currentSubject = authenticator.authenticate(httpRequest);
|
||||
|
||||
final var authenticatedRequest = new AuthenticatedHttpServletRequestWrapper(httpRequest);
|
||||
authenticatedRequest.addHeader("current-subject", currentSubject);
|
||||
|
||||
chain.doFilter(authenticatedRequest, response);
|
||||
} catch (final BadCredentialsException exc) {
|
||||
// TODO.impl: should not be necessary if ResponseStatusException worked
|
||||
httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package net.hostsharing.hsadminng.config;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
public interface Authenticator {
|
||||
|
||||
String authenticate(final HttpServletRequest httpRequest);
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
package net.hostsharing.hsadminng.config;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import lombok.SneakyThrows;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import java.io.IOException;
|
||||
|
||||
public class CasAuthenticator implements Authenticator {
|
||||
|
||||
@Value("${hsadminng.cas.server}")
|
||||
private String casServerUrl;
|
||||
|
||||
@Value("${hsadminng.cas.service}")
|
||||
private String serviceUrl;
|
||||
|
||||
private final RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
@SneakyThrows
|
||||
@Timed("app.cas.authenticate")
|
||||
public String authenticate(final HttpServletRequest httpRequest) {
|
||||
final var userName = StringUtils.isBlank(casServerUrl)
|
||||
? bypassCurrentSubject(httpRequest)
|
||||
: casValidation(httpRequest);
|
||||
final var authentication = new UsernamePasswordAuthenticationToken(userName, null, null);
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
return authentication.getName();
|
||||
}
|
||||
|
||||
private static String bypassCurrentSubject(final HttpServletRequest httpRequest) {
|
||||
final var userName = httpRequest.getHeader("current-subject");
|
||||
System.err.println("CasAuthenticator.bypassCurrentSubject: " + userName);
|
||||
return userName;
|
||||
}
|
||||
|
||||
private String casValidation(final HttpServletRequest httpRequest)
|
||||
throws SAXException, IOException, ParserConfigurationException {
|
||||
|
||||
final var ticket = httpRequest.getHeader("Authorization");
|
||||
final var url = casServerUrl + "/p3/serviceValidate" +
|
||||
"?service=" + serviceUrl +
|
||||
"&ticket=" + ticket;
|
||||
|
||||
System.err.println("CasAuthenticator.casValidation using URL: " + url);
|
||||
|
||||
final var response = restTemplate.getForObject(url, String.class);
|
||||
|
||||
final var doc = DocumentBuilderFactory.newInstance().newDocumentBuilder()
|
||||
.parse(new java.io.ByteArrayInputStream(response.getBytes()));
|
||||
if (doc.getElementsByTagName("cas:authenticationSuccess").getLength() == 0) {
|
||||
// TODO.impl: for unknown reasons, this results in a 403 FORBIDDEN
|
||||
// throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "CAS service ticket could not be validated");
|
||||
System.err.println("CAS service ticket could not be validated");
|
||||
System.err.println("CAS-validation-URL: " + url);
|
||||
System.err.println(response);
|
||||
throw new BadCredentialsException("CAS service ticket could not be validated");
|
||||
}
|
||||
final var userName = doc.getElementsByTagName("cas:user").item(0).getTextContent();
|
||||
System.err.println("CAS-user: " + userName);
|
||||
return userName;
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
|
||||
@Configuration
|
||||
@ -22,6 +23,14 @@ public class WebSecurityConfig {
|
||||
.requestMatchers("/actuator/**").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.csrf(AbstractHttpConfigurer::disable)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Profile("!test")
|
||||
public Authenticator casServiceTicketValidator() {
|
||||
return new CasAuthenticator();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ public class Context {
|
||||
cast(:currentTask as varchar(127)),
|
||||
cast(:currentRequest as text),
|
||||
cast(:currentSubject as varchar(63)),
|
||||
cast(:assumedRoles as varchar(1023)));
|
||||
cast(:assumedRoles as text));
|
||||
""");
|
||||
query.setParameter("currentTask", shortenToMaxLength(currentTask, 127));
|
||||
query.setParameter("currentRequest", currentRequest);
|
||||
|
@ -97,6 +97,7 @@ public class RestResponseEntityExceptionHandler
|
||||
return errorResponse(request, HttpStatus.valueOf(statusCode.value()),
|
||||
Optional.ofNullable(response.getBody()).map(Object::toString).orElse(firstMessageLine(exc)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked,rawtypes")
|
||||
protected ResponseEntity handleHttpMessageNotReadable(
|
||||
@ -131,7 +132,7 @@ public class RestResponseEntityExceptionHandler
|
||||
final HttpStatusCode status,
|
||||
final WebRequest request) {
|
||||
final var errorList = exc
|
||||
.getAllValidationResults()
|
||||
.getParameterValidationResults()
|
||||
.stream()
|
||||
.map(ParameterValidationResult::getResolvableErrors)
|
||||
.flatMap(Collection::stream)
|
||||
|
@ -5,6 +5,7 @@ import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||
import net.hostsharing.hsadminng.rbac.role.WithRoleId;
|
||||
import net.hostsharing.hsadminng.repr.Stringify;
|
||||
import net.hostsharing.hsadminng.repr.Stringifyable;
|
||||
|
||||
@ -24,7 +25,7 @@ import static net.hostsharing.hsadminng.repr.Stringify.stringify;
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@DisplayAs("BookingDebitor")
|
||||
public class HsBookingDebitorEntity implements Stringifyable {
|
||||
public class HsBookingDebitorEntity implements Stringifyable, WithRoleId {
|
||||
|
||||
public static final String DEBITOR_NUMBER_TAG = "D-";
|
||||
|
||||
|
@ -2,11 +2,13 @@ package net.hostsharing.hsadminng.hs.booking.debitor;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Profile("!only-office")
|
||||
public interface HsBookingDebitorRepository extends Repository<HsBookingDebitorEntity, UUID> {
|
||||
|
||||
@Timed("app.booking.debitor.repo.findByUuid")
|
||||
|
@ -1,10 +1,12 @@
|
||||
package net.hostsharing.hsadminng.hs.booking.item;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Profile("!only-office")
|
||||
public interface BookingItemCreatedEventRepository extends Repository<BookingItemCreatedEventEntity, UUID> {
|
||||
|
||||
@Timed("app.booking.items.repo.save")
|
||||
|
@ -16,6 +16,7 @@ import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
@ -30,6 +31,7 @@ import static java.util.Optional.ofNullable;
|
||||
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange;
|
||||
|
||||
@RestController
|
||||
@Profile("!only-office")
|
||||
public class HsBookingItemController implements HsBookingItemsApi {
|
||||
|
||||
@Autowired
|
||||
|
@ -5,8 +5,8 @@ import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRbacEntity;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL;
|
||||
|
||||
import jakarta.persistence.AttributeOverride;
|
||||
import jakarta.persistence.AttributeOverrides;
|
||||
@ -15,20 +15,20 @@ import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Table;
|
||||
import java.io.IOException;
|
||||
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Column.dependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.ColumnValue.usingDefaultCase;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.GLOBAL;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Nullable.NULLABLE;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.DELETE;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.INSERT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.SELECT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.UPDATE;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.ADMIN;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.AGENT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.OWNER;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.TENANT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.directlyFetchedByDependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Column.dependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.ColumnValue.usingDefaultCase;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.GLOBAL;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Nullable.NULLABLE;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.DELETE;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.INSERT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.SELECT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.UPDATE;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.ADMIN;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.AGENT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.OWNER;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.TENANT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL.directlyFetchedByDependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.rbacViewFor;
|
||||
|
||||
@Entity
|
||||
@Table(schema = "hs_booking", name = "item_rv")
|
||||
@ -41,7 +41,7 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
||||
})
|
||||
public class HsBookingItemRbacEntity extends HsBookingItem {
|
||||
|
||||
public static RbacView rbac() {
|
||||
public static RbacSpec rbac() {
|
||||
return rbacViewFor("bookingItem", HsBookingItemRbacEntity.class)
|
||||
.withIdentityView(SQL.projection("caption"))
|
||||
.withRestrictedViewOrderBy(SQL.expression("validity"))
|
||||
|
@ -1,12 +1,14 @@
|
||||
package net.hostsharing.hsadminng.hs.booking.item;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Profile("!only-office")
|
||||
public interface HsBookingItemRbacRepository extends HsBookingItemRepository<HsBookingItemRbacEntity>,
|
||||
Repository<HsBookingItemRbacEntity, UUID> {
|
||||
|
||||
|
@ -1,12 +1,14 @@
|
||||
package net.hostsharing.hsadminng.hs.booking.item;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Profile("!only-office")
|
||||
public interface HsBookingItemRealRepository extends HsBookingItemRepository<HsBookingItemRealEntity>,
|
||||
Repository<HsBookingItemRealEntity, UUID> {
|
||||
|
||||
|
@ -1,10 +1,13 @@
|
||||
package net.hostsharing.hsadminng.hs.booking.item;
|
||||
|
||||
|
||||
import org.springframework.context.annotation.Profile;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Profile("!only-office")
|
||||
public interface HsBookingItemRepository<E extends HsBookingItem> {
|
||||
|
||||
Optional<E> findByUuid(final UUID bookingItemUuid);
|
||||
|
@ -7,8 +7,9 @@ import net.hostsharing.hsadminng.hs.booking.generated.api.v1.api.HsBookingProjec
|
||||
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingProjectInsertResource;
|
||||
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingProjectPatchResource;
|
||||
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingProjectResource;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
@ -20,13 +21,14 @@ import java.util.UUID;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
@RestController
|
||||
@Profile("!only-office")
|
||||
public class HsBookingProjectController implements HsBookingProjectsApi {
|
||||
|
||||
@Autowired
|
||||
private Context context;
|
||||
|
||||
@Autowired
|
||||
private StandardMapper mapper;
|
||||
private StrictMapper mapper;
|
||||
|
||||
@Autowired
|
||||
private HsBookingProjectRbacRepository bookingProjectRepo;
|
||||
|
@ -6,30 +6,30 @@ import lombok.Setter;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRbacEntity;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Table;
|
||||
import java.io.IOException;
|
||||
|
||||
import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.DEBITOR;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Column.dependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.ColumnValue.usingCase;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.ColumnValue.usingDefaultCase;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.GLOBAL;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Nullable.NOT_NULL;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.DELETE;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.INSERT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.SELECT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.UPDATE;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.ADMIN;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.AGENT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.OWNER;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.TENANT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.directlyFetchedByDependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.fetchedBySql;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Column.dependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.ColumnValue.usingCase;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.ColumnValue.usingDefaultCase;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.GLOBAL;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Nullable.NOT_NULL;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.DELETE;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.INSERT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.SELECT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.UPDATE;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.ADMIN;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.AGENT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.OWNER;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.TENANT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL.directlyFetchedByDependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL.fetchedBySql;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.rbacViewFor;
|
||||
|
||||
@Entity
|
||||
@Table(schema = "hs_booking", name = "project_rv")
|
||||
@ -39,7 +39,7 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
||||
@NoArgsConstructor
|
||||
public class HsBookingProjectRbacEntity extends HsBookingProject {
|
||||
|
||||
public static RbacView rbac() {
|
||||
public static RbacSpec rbac() {
|
||||
return rbacViewFor("project", HsBookingProjectRbacEntity.class)
|
||||
.withIdentityView(SQL.query("""
|
||||
SELECT bookingProject.uuid as uuid, debitorIV.idName || '-' || base.cleanIdentifier(bookingProject.caption) as idName
|
||||
|
@ -1,12 +1,14 @@
|
||||
package net.hostsharing.hsadminng.hs.booking.project;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Profile("!only-office")
|
||||
public interface HsBookingProjectRbacRepository extends HsBookingProjectRepository<HsBookingProjectRbacEntity>,
|
||||
Repository<HsBookingProjectRbacEntity, UUID> {
|
||||
|
||||
|
@ -1,12 +1,14 @@
|
||||
package net.hostsharing.hsadminng.hs.booking.project;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Profile("!only-office")
|
||||
public interface HsBookingProjectRealRepository extends HsBookingProjectRepository<HsBookingProjectRealEntity>,
|
||||
Repository<HsBookingProjectRealEntity, UUID> {
|
||||
|
||||
|
@ -1,11 +1,13 @@
|
||||
package net.hostsharing.hsadminng.hs.booking.project;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Profile("!only-office")
|
||||
public interface HsBookingProjectRepository<E extends HsBookingProject> {
|
||||
|
||||
@Timed("app.booking.projects.repo.findByUuid")
|
||||
|
@ -89,7 +89,7 @@ public abstract class HsHostingAsset implements Stringifyable, BaseEntity<HsHost
|
||||
@JoinColumn(name = "alarmcontactuuid")
|
||||
private HsOfficeContactRealEntity alarmContact;
|
||||
|
||||
@OneToMany(cascade = CascadeType.REFRESH, orphanRemoval = true, fetch = FetchType.LAZY)
|
||||
@OneToMany(cascade = { CascadeType.PERSIST, CascadeType.REFRESH }, orphanRemoval = true, fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "parentassetuuid", referencedColumnName = "uuid")
|
||||
private List<HsHostingAssetRealEntity> subHostingAssets;
|
||||
|
||||
|
@ -12,9 +12,10 @@ import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAsse
|
||||
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetResource;
|
||||
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetTypeResource;
|
||||
import net.hostsharing.hsadminng.mapper.KeyValueMap;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
@ -27,6 +28,7 @@ import java.util.UUID;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
@RestController
|
||||
@Profile("!only-office")
|
||||
public class HsHostingAssetController implements HsHostingAssetsApi {
|
||||
|
||||
@Autowired
|
||||
@ -36,7 +38,7 @@ public class HsHostingAssetController implements HsHostingAssetsApi {
|
||||
private Context context;
|
||||
|
||||
@Autowired
|
||||
private StandardMapper mapper;
|
||||
private StrictMapper mapper;
|
||||
|
||||
@Autowired
|
||||
private HsHostingAssetRbacRepository rbacAssetRepo;
|
||||
|
@ -4,6 +4,7 @@ import io.micrometer.core.annotation.Timed;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.validators.HostingAssetEntityValidatorRegistry;
|
||||
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.api.HsHostingAssetPropsApi;
|
||||
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetTypeResource;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@ -12,6 +13,7 @@ import java.util.Map;
|
||||
|
||||
|
||||
@RestController
|
||||
@Profile("!only-office")
|
||||
public class HsHostingAssetPropsController implements HsHostingAssetPropsApi {
|
||||
|
||||
@Override
|
||||
|
@ -6,31 +6,31 @@ import lombok.Setter;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRbacEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRbacEntity;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Table;
|
||||
import java.io.IOException;
|
||||
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.CaseDef.inCaseOf;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Column.dependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.ColumnValue.usingDefaultCase;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.GLOBAL;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Nullable.NULLABLE;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.DELETE;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.INSERT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.SELECT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.UPDATE;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.RbacSubjectReference.UserRole.CREATOR;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.ADMIN;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.AGENT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.GUEST;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.OWNER;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.REFERRER;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.TENANT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.directlyFetchedByDependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.CaseDef.inCaseOf;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Column.dependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.ColumnValue.usingDefaultCase;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.GLOBAL;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Nullable.NULLABLE;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.DELETE;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.INSERT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.SELECT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.UPDATE;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.RbacSubjectReference.UserRole.CREATOR;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.ADMIN;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.AGENT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.GUEST;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.OWNER;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.REFERRER;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.TENANT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL.directlyFetchedByDependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.rbacViewFor;
|
||||
|
||||
@Entity
|
||||
@Table(schema = "hs_hosting", name = "asset_rv")
|
||||
@ -40,7 +40,7 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
||||
@NoArgsConstructor
|
||||
public class HsHostingAssetRbacEntity extends HsHostingAsset {
|
||||
|
||||
public static RbacView rbac() {
|
||||
public static RbacSpec rbac() {
|
||||
return rbacViewFor("asset", HsHostingAssetRbacEntity.class)
|
||||
.withIdentityView(SQL.projection("identifier"))
|
||||
.withRestrictedViewOrderBy(SQL.expression("identifier"))
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
@ -8,7 +9,7 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
|
||||
@Profile("!only-office")
|
||||
public interface HsHostingAssetRbacRepository extends HsHostingAssetRepository<HsHostingAssetRbacEntity>, Repository<HsHostingAssetRbacEntity, UUID> {
|
||||
|
||||
@Timed("app.hostingAsset.repo.findByUuid.rbac")
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
@ -9,6 +10,7 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Profile("!only-office")
|
||||
public interface HsHostingAssetRealRepository extends HsHostingAssetRepository<HsHostingAssetRealEntity>, Repository<HsHostingAssetRealEntity, UUID> {
|
||||
|
||||
@Timed("app.hostingAsset.repo.findByUuid.real")
|
||||
|
@ -1,11 +1,13 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Profile("!only-office")
|
||||
public interface HsHostingAssetRepository<E extends HsHostingAsset> {
|
||||
|
||||
@Timed("app.hosting.assets.repo.findByUuid")
|
||||
|
@ -9,7 +9,7 @@ import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity;
|
||||
import net.hostsharing.hsadminng.lambda.Reducer;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import net.hostsharing.hsadminng.mapper.ToStringConverter;
|
||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||
|
||||
@ -31,8 +31,8 @@ public class DomainSetupHostingAssetFactory extends HostingAssetFactory {
|
||||
final EntityManagerWrapper emw,
|
||||
final HsBookingItemRealEntity newBookingItemRealEntity,
|
||||
final HsHostingAssetAutoInsertResource asset,
|
||||
final StandardMapper standardMapper) {
|
||||
super(emw, newBookingItemRealEntity, asset, standardMapper);
|
||||
final StrictMapper StrictMapper) {
|
||||
super(emw, newBookingItemRealEntity, asset, StrictMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -6,7 +6,7 @@ import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsHostingAsse
|
||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.validators.HostingAssetEntitySaveProcessor;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||
|
||||
|
||||
@ -16,7 +16,7 @@ abstract class HostingAssetFactory {
|
||||
final EntityManagerWrapper emw;
|
||||
final HsBookingItemRealEntity fromBookingItem;
|
||||
final HsHostingAssetAutoInsertResource asset;
|
||||
final StandardMapper standardMapper;
|
||||
final StrictMapper StrictMapper;
|
||||
|
||||
protected abstract HsHostingAsset create();
|
||||
|
||||
|
@ -9,13 +9,15 @@ import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsHostingAsse
|
||||
import net.hostsharing.hsadminng.hs.booking.item.BookingItemCreatedAppEvent;
|
||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@Profile("!only-office")
|
||||
public class HsBookingItemCreatedListener implements ApplicationListener<BookingItemCreatedAppEvent> {
|
||||
|
||||
@Autowired
|
||||
@ -25,7 +27,7 @@ public class HsBookingItemCreatedListener implements ApplicationListener<Booking
|
||||
private ObjectMapper jsonMapper;
|
||||
|
||||
@Autowired
|
||||
private StandardMapper standardMapper;
|
||||
private StrictMapper StrictMapper;
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@ -44,9 +46,9 @@ public class HsBookingItemCreatedListener implements ApplicationListener<Booking
|
||||
final var asset = jsonMapper.readValue(event.getEntity().getAssetJson(), HsHostingAssetAutoInsertResource.class);
|
||||
final var factory = switch (newBookingItemRealEntity.getType()) {
|
||||
case PRIVATE_CLOUD, CLOUD_SERVER, MANAGED_SERVER ->
|
||||
forNowNoAutomaticHostingAssetCreationPossible(emw, newBookingItemRealEntity, asset, standardMapper);
|
||||
case MANAGED_WEBSPACE -> new ManagedWebspaceHostingAssetFactory(emw, newBookingItemRealEntity, asset, standardMapper);
|
||||
case DOMAIN_SETUP -> new DomainSetupHostingAssetFactory(emw, newBookingItemRealEntity, asset, standardMapper);
|
||||
forNowNoAutomaticHostingAssetCreationPossible(emw, newBookingItemRealEntity, asset, StrictMapper);
|
||||
case MANAGED_WEBSPACE -> new ManagedWebspaceHostingAssetFactory(emw, newBookingItemRealEntity, asset, StrictMapper);
|
||||
case DOMAIN_SETUP -> new DomainSetupHostingAssetFactory(emw, newBookingItemRealEntity, asset, StrictMapper);
|
||||
};
|
||||
if (factory != null) {
|
||||
final var statusMessage = factory.createAndPersist();
|
||||
@ -62,9 +64,9 @@ public class HsBookingItemCreatedListener implements ApplicationListener<Booking
|
||||
final EntityManagerWrapper emw,
|
||||
final HsBookingItemRealEntity fromBookingItem,
|
||||
final HsHostingAssetAutoInsertResource asset,
|
||||
final StandardMapper standardMapper
|
||||
final StrictMapper StrictMapper
|
||||
) {
|
||||
return new HostingAssetFactory(emw, fromBookingItem, asset, standardMapper) {
|
||||
return new HostingAssetFactory(emw, fromBookingItem, asset, StrictMapper) {
|
||||
|
||||
@Override
|
||||
protected HsHostingAsset create() {
|
||||
|
@ -5,7 +5,7 @@ import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsHostingAsse
|
||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetRealEntity;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||
|
||||
import jakarta.validation.ValidationException;
|
||||
@ -19,8 +19,8 @@ public class ManagedWebspaceHostingAssetFactory extends HostingAssetFactory {
|
||||
final EntityManagerWrapper emw,
|
||||
final HsBookingItemRealEntity newBookingItemRealEntity,
|
||||
final HsHostingAssetAutoInsertResource asset,
|
||||
final StandardMapper standardMapper) {
|
||||
super(emw, newBookingItemRealEntity, asset, standardMapper);
|
||||
final StrictMapper StrictMapper) {
|
||||
super(emw, newBookingItemRealEntity, asset, StrictMapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -32,7 +32,7 @@ public class ManagedWebspaceHostingAssetFactory extends HostingAssetFactory {
|
||||
.map(Enum::name)
|
||||
.orElse(null));
|
||||
}
|
||||
final var managedWebspaceHostingAsset = standardMapper.map(asset, HsHostingAssetRealEntity.class);
|
||||
final var managedWebspaceHostingAsset = StrictMapper.map(asset, HsHostingAssetRealEntity.class);
|
||||
managedWebspaceHostingAsset.setBookingItem(fromBookingItem);
|
||||
emw.createQuery(
|
||||
"SELECT asset FROM HsHostingAssetRealEntity asset WHERE asset.bookingItem.uuid=:bookingItemUuid",
|
||||
|
@ -5,7 +5,7 @@ import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeBankAccountsApi;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeBankAccountInsertResource;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeBankAccountResource;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import org.iban4j.BicUtil;
|
||||
import org.iban4j.IbanUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@ -25,7 +25,7 @@ public class HsOfficeBankAccountController implements HsOfficeBankAccountsApi {
|
||||
private Context context;
|
||||
|
||||
@Autowired
|
||||
private StandardMapper mapper;
|
||||
private StrictMapper mapper;
|
||||
|
||||
@Autowired
|
||||
private HsOfficeBankAccountRepository bankAccountRepo;
|
||||
|
@ -4,7 +4,7 @@ import lombok.*;
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec;
|
||||
import net.hostsharing.hsadminng.repr.Stringify;
|
||||
import net.hostsharing.hsadminng.repr.Stringifyable;
|
||||
|
||||
@ -12,10 +12,10 @@ import jakarta.persistence.*;
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.RbacSubjectReference.UserRole.CREATOR;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.RbacSubjectReference.UserRole.CREATOR;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.*;
|
||||
import static net.hostsharing.hsadminng.repr.Stringify.stringify;
|
||||
|
||||
@Entity
|
||||
@ -57,7 +57,7 @@ public class HsOfficeBankAccountEntity implements BaseEntity<HsOfficeBankAccount
|
||||
return holder;
|
||||
}
|
||||
|
||||
public static RbacView rbac() {
|
||||
public static RbacSpec rbac() {
|
||||
return rbacViewFor("bankAccount", HsOfficeBankAccountEntity.class)
|
||||
.withIdentityView(SQL.projection("iban"))
|
||||
.withUpdatableColumns("holder", "iban", "bic")
|
||||
|
@ -12,6 +12,7 @@ import lombok.experimental.SuperBuilder;
|
||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||
import net.hostsharing.hsadminng.mapper.PatchableMapWrapper;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import net.hostsharing.hsadminng.rbac.role.WithRoleId;
|
||||
import net.hostsharing.hsadminng.repr.Stringify;
|
||||
import net.hostsharing.hsadminng.repr.Stringifyable;
|
||||
import org.hibernate.annotations.GenericGenerator;
|
||||
@ -37,7 +38,7 @@ import static net.hostsharing.hsadminng.repr.Stringify.stringify;
|
||||
@SuperBuilder(toBuilder = true)
|
||||
@FieldNameConstants
|
||||
@DisplayAs("Contact")
|
||||
public class HsOfficeContact implements Stringifyable, BaseEntity<HsOfficeContact> {
|
||||
public class HsOfficeContact implements Stringifyable, BaseEntity<HsOfficeContact>, WithRoleId {
|
||||
|
||||
private static Stringify<HsOfficeContact> toString = stringify(HsOfficeContact.class, "contact")
|
||||
.withProp(Fields.caption, HsOfficeContact::getCaption)
|
||||
|
@ -1,7 +1,7 @@
|
||||
package net.hostsharing.hsadminng.hs.office.contact;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeContactsApi;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeContactInsertResource;
|
||||
@ -28,7 +28,7 @@ public class HsOfficeContactController implements HsOfficeContactsApi {
|
||||
private Context context;
|
||||
|
||||
@Autowired
|
||||
private StandardMapper mapper;
|
||||
private StrictMapper mapper;
|
||||
|
||||
@Autowired
|
||||
private HsOfficeContactRbacRepository contactRepo;
|
||||
@ -131,6 +131,7 @@ public class HsOfficeContactController implements HsOfficeContactsApi {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
final BiConsumer<HsOfficeContactInsertResource, HsOfficeContactRbacEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
|
||||
entity.putPostalAddress(from(resource.getPostalAddress()));
|
||||
entity.putEmailAddresses(from(resource.getEmailAddresses()));
|
||||
entity.putPhoneNumbers(from(resource.getPhoneNumbers()));
|
||||
};
|
||||
|
@ -3,17 +3,17 @@ package net.hostsharing.hsadminng.hs.office.contact;
|
||||
import lombok.*;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import java.io.IOException;
|
||||
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.GLOBAL;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.RbacSubjectReference.UserRole.CREATOR;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.GLOBAL;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.RbacSubjectReference.UserRole.CREATOR;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.rbacViewFor;
|
||||
|
||||
@Entity
|
||||
@Table(schema = "hs_office", name = "contact_rv")
|
||||
@ -24,7 +24,7 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
||||
@DisplayAs("RbacContact")
|
||||
public class HsOfficeContactRbacEntity extends HsOfficeContact {
|
||||
|
||||
public static RbacView rbac() {
|
||||
public static RbacSpec rbac() {
|
||||
return rbacViewFor("contact", HsOfficeContactRbacEntity.class)
|
||||
.withIdentityView(SQL.projection("caption"))
|
||||
.withUpdatableColumns("caption", "postalAddress", "emailAddresses", "phoneNumbers")
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
package net.hostsharing.hsadminng.hs.office.coopassets;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
@ -9,28 +8,39 @@ import lombok.Setter;
|
||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL;
|
||||
import net.hostsharing.hsadminng.repr.Stringify;
|
||||
import net.hostsharing.hsadminng.repr.Stringifyable;
|
||||
import org.hibernate.annotations.GenericGenerator;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.OneToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.Version;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.UUID;
|
||||
|
||||
import static java.util.Optional.ofNullable;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Column.dependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.ColumnValue.usingDefaultCase;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Nullable.NOT_NULL;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.INSERT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.SELECT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.UPDATE;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.ADMIN;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.AGENT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.directlyFetchedByDependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Column.dependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.ColumnValue.usingDefaultCase;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Nullable.NOT_NULL;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.INSERT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.SELECT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.UPDATE;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.ADMIN;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.AGENT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL.directlyFetchedByDependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.rbacViewFor;
|
||||
import static net.hostsharing.hsadminng.repr.Stringify.stringify;
|
||||
|
||||
@Entity
|
||||
@ -57,8 +67,7 @@ public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable, BaseE
|
||||
.quotedValues(false);
|
||||
|
||||
@Id
|
||||
@GeneratedValue(generator = "UUID")
|
||||
@GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
|
||||
@GeneratedValue
|
||||
private UUID uuid;
|
||||
|
||||
@Version
|
||||
@ -122,15 +131,15 @@ public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable, BaseE
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getTaggedMemberNumber() {
|
||||
return ofNullable(membership).map(HsOfficeMembershipEntity::toShortString).orElse("M-???????");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return stringify.apply(this);
|
||||
}
|
||||
|
||||
public String getTaggedMemberNumber() {
|
||||
return ofNullable(membership).map(HsOfficeMembershipEntity::toShortString).orElse("M-???????");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toShortString() {
|
||||
return "%s:%.3s:%+1.2f".formatted(
|
||||
@ -139,9 +148,9 @@ public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable, BaseE
|
||||
ofNullable(assetValue).orElse(BigDecimal.ZERO));
|
||||
}
|
||||
|
||||
public static RbacView rbac() {
|
||||
public static RbacSpec rbac() {
|
||||
return rbacViewFor("coopAssetsTransaction", HsOfficeCoopAssetsTransactionEntity.class)
|
||||
.withIdentityView(RbacView.SQL.projection("reference"))
|
||||
.withIdentityView(SQL.projection("reference"))
|
||||
.withUpdatableColumns("comment")
|
||||
.importEntityAlias("membership", HsOfficeMembershipEntity.class, usingDefaultCase(),
|
||||
dependsOnColumn("membershipUuid"),
|
||||
|
@ -1,14 +1,13 @@
|
||||
package net.hostsharing.hsadminng.hs.office.coopshares;
|
||||
|
||||
import jakarta.persistence.EntityNotFoundException;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.errors.MultiValidationException;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeCoopSharesApi;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionInsertResource;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionResource;
|
||||
import net.hostsharing.hsadminng.errors.MultiValidationException;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipRepository;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.format.annotation.DateTimeFormat.ISO;
|
||||
@ -25,6 +24,7 @@ import java.util.function.BiConsumer;
|
||||
|
||||
import static net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionTypeResource.CANCELLATION;
|
||||
import static net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionTypeResource.SUBSCRIPTION;
|
||||
import static net.hostsharing.hsadminng.hs.validation.UuidResolver.resolve;
|
||||
|
||||
@RestController
|
||||
public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopSharesApi {
|
||||
@ -33,14 +33,16 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
|
||||
private Context context;
|
||||
|
||||
@Autowired
|
||||
private StandardMapper mapper;
|
||||
private StrictMapper mapper;
|
||||
|
||||
@Autowired
|
||||
private HsOfficeCoopSharesTransactionRepository coopSharesTransactionRepo;
|
||||
|
||||
@Autowired
|
||||
private HsOfficeMembershipRepository membershipRepo;
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
|
||||
@Timed("app.office.coopShares.api.getListOfCoopShares")
|
||||
public ResponseEntity<List<HsOfficeCoopSharesTransactionResource>> getListOfCoopShares(
|
||||
final String currentSubject,
|
||||
@ -55,7 +57,10 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
|
||||
fromValueDate,
|
||||
toValueDate);
|
||||
|
||||
final var resources = mapper.mapList(entities, HsOfficeCoopSharesTransactionResource.class);
|
||||
final var resources = mapper.mapList(
|
||||
entities,
|
||||
HsOfficeCoopSharesTransactionResource.class,
|
||||
ENTITY_TO_RESOURCE_POSTMAPPER);
|
||||
return ResponseEntity.ok(resources);
|
||||
}
|
||||
|
||||
@ -70,7 +75,10 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
|
||||
context.define(currentSubject, assumedRoles);
|
||||
validate(requestBody);
|
||||
|
||||
final var entityToSave = mapper.map(requestBody, HsOfficeCoopSharesTransactionEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER);
|
||||
final var entityToSave = mapper.map(
|
||||
requestBody,
|
||||
HsOfficeCoopSharesTransactionEntity.class,
|
||||
RESOURCE_TO_ENTITY_POSTMAPPER);
|
||||
|
||||
final var saved = coopSharesTransactionRepo.save(entityToSave);
|
||||
|
||||
@ -79,7 +87,7 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
|
||||
.path("/api/hs/office/coopsharestransactions/{id}")
|
||||
.buildAndExpand(saved.getUuid())
|
||||
.toUri();
|
||||
final var mapped = mapper.map(saved, HsOfficeCoopSharesTransactionResource.class);
|
||||
final var mapped = mapper.map(saved, HsOfficeCoopSharesTransactionResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
|
||||
return ResponseEntity.created(uri).body(mapped);
|
||||
}
|
||||
|
||||
@ -95,7 +103,10 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
|
||||
if (result.isEmpty()) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
return ResponseEntity.ok(mapper.map(result.get(), HsOfficeCoopSharesTransactionResource.class));
|
||||
return ResponseEntity.ok(mapper.map(
|
||||
result.get(),
|
||||
HsOfficeCoopSharesTransactionResource.class,
|
||||
ENTITY_TO_RESOURCE_POSTMAPPER));
|
||||
|
||||
}
|
||||
|
||||
@ -137,9 +148,16 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
|
||||
}
|
||||
|
||||
final BiConsumer<HsOfficeCoopSharesTransactionInsertResource, HsOfficeCoopSharesTransactionEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
|
||||
if ( resource.getRevertedShareTxUuid() != null ) {
|
||||
entity.setRevertedShareTx(coopSharesTransactionRepo.findByUuid(resource.getRevertedShareTxUuid())
|
||||
.orElseThrow(() -> new EntityNotFoundException("ERROR: [400] revertedShareTxUuid %s not found".formatted(resource.getRevertedShareTxUuid()))));
|
||||
entity.setMembership(resolve("membership.uuid", resource.getMembershipUuid(), membershipRepo::findByUuid));
|
||||
if (resource.getRevertedShareTxUuid() != null) {
|
||||
entity.setRevertedShareTx(resolve(
|
||||
"revertedShareTx.uuid",
|
||||
resource.getRevertedShareTxUuid(),
|
||||
coopSharesTransactionRepo::findByUuid));
|
||||
}
|
||||
};
|
||||
|
||||
final BiConsumer<HsOfficeCoopSharesTransactionEntity, HsOfficeCoopSharesTransactionResource> ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> {
|
||||
resource.setMembershipUuid(entity.getMembership().getUuid());
|
||||
};
|
||||
}
|
||||
|
@ -7,28 +7,38 @@ import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL;
|
||||
import net.hostsharing.hsadminng.repr.Stringify;
|
||||
import net.hostsharing.hsadminng.repr.Stringifyable;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.OneToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.Version;
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDate;
|
||||
import java.util.UUID;
|
||||
|
||||
import static java.util.Optional.ofNullable;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Column.dependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.ColumnValue.usingDefaultCase;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Nullable.NOT_NULL;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.INSERT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.SELECT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.UPDATE;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.ADMIN;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.AGENT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.directlyFetchedByDependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Column.dependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.ColumnValue.usingDefaultCase;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Nullable.NOT_NULL;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.INSERT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.SELECT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.UPDATE;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.ADMIN;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.AGENT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL.directlyFetchedByDependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.rbacViewFor;
|
||||
import static net.hostsharing.hsadminng.repr.Stringify.stringify;
|
||||
|
||||
@Entity
|
||||
@ -123,7 +133,7 @@ public class HsOfficeCoopSharesTransactionEntity implements Stringifyable, BaseE
|
||||
return "%s:%.3s:%+d".formatted(getMemberNumberTagged(), transactionType, shareCount);
|
||||
}
|
||||
|
||||
public static RbacView rbac() {
|
||||
public static RbacSpec rbac() {
|
||||
return rbacViewFor("coopSharesTransaction", HsOfficeCoopSharesTransactionEntity.class)
|
||||
.withIdentityView(SQL.projection("reference"))
|
||||
.withUpdatableColumns("comment")
|
||||
@ -132,6 +142,7 @@ public class HsOfficeCoopSharesTransactionEntity implements Stringifyable, BaseE
|
||||
directlyFetchedByDependsOnColumn(),
|
||||
NOT_NULL)
|
||||
|
||||
// the membership:ADMIN is not to be confused with the member itself, it's an account manager of the coop
|
||||
.toRole("membership", ADMIN).grantPermission(INSERT)
|
||||
.toRole("membership", ADMIN).grantPermission(UPDATE)
|
||||
.toRole("membership", AGENT).grantPermission(SELECT);
|
||||
|
@ -2,14 +2,16 @@ package net.hostsharing.hsadminng.hs.office.debitor;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountRepository;
|
||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealRepository;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeDebitorsApi;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebitorInsertResource;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebitorPatchResource;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebitorResource;
|
||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealRepository;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealRepository;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.persistence.EntityExistsValidator;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.hibernate.Hibernate;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@ -26,6 +28,7 @@ import java.util.UUID;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.DEBITOR;
|
||||
import static net.hostsharing.hsadminng.hs.validation.UuidResolver.resolve;
|
||||
import static net.hostsharing.hsadminng.repr.TaggedNumber.cropTag;
|
||||
|
||||
@RestController
|
||||
@ -36,16 +39,22 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
|
||||
private Context context;
|
||||
|
||||
@Autowired
|
||||
private StandardMapper mapper;
|
||||
private StrictMapper mapper;
|
||||
|
||||
@Autowired
|
||||
private HsOfficeDebitorRepository debitorRepo;
|
||||
|
||||
@Autowired
|
||||
private HsOfficeRelationRealRepository relrealRepo;
|
||||
private HsOfficeRelationRealRepository realRelRepo;
|
||||
|
||||
@Autowired
|
||||
private EntityExistsValidator entityValidator;
|
||||
private HsOfficePersonRealRepository realPersonRepo;
|
||||
|
||||
@Autowired
|
||||
private HsOfficeContactRealRepository realContactRepo;
|
||||
|
||||
@Autowired
|
||||
private HsOfficeBankAccountRepository bankAccountRepo;
|
||||
|
||||
@PersistenceContext
|
||||
private EntityManager em;
|
||||
@ -81,34 +90,19 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
|
||||
|
||||
context.define(currentSubject, assumedRoles);
|
||||
|
||||
Validate.isTrue(body.getDebitorRel() == null || body.getDebitorRelUuid() == null,
|
||||
Validate.isTrue(
|
||||
body.getDebitorRel() == null || body.getDebitorRelUuid() == null,
|
||||
"ERROR: [400] exactly one of debitorRel and debitorRelUuid must be supplied, but found both");
|
||||
Validate.isTrue(body.getDebitorRel() != null || body.getDebitorRelUuid() != null,
|
||||
Validate.isTrue(
|
||||
body.getDebitorRel() != null || body.getDebitorRelUuid() != null,
|
||||
"ERROR: [400] exactly one of debitorRel and debitorRelUuid must be supplied, but found none");
|
||||
Validate.isTrue(body.getDebitorRel() == null || body.getDebitorRel().getMark() == null,
|
||||
Validate.isTrue(
|
||||
body.getDebitorRel() == null || body.getDebitorRel().getMark() == null,
|
||||
"ERROR: [400] debitorRel.mark must be null");
|
||||
|
||||
final var entityToSave = mapper.map(body, HsOfficeDebitorEntity.class);
|
||||
if (body.getDebitorRel() != null) {
|
||||
final var debitorRel = mapper.map("debitorRel.", body.getDebitorRel(), HsOfficeRelationRealEntity.class);
|
||||
debitorRel.setType(DEBITOR);
|
||||
entityValidator.validateEntityExists("debitorRel.anchorUuid", debitorRel.getAnchor());
|
||||
entityValidator.validateEntityExists("debitorRel.holderUuid", debitorRel.getHolder());
|
||||
entityValidator.validateEntityExists("debitorRel.contactUuid", debitorRel.getContact());
|
||||
entityToSave.setDebitorRel(relrealRepo.save(debitorRel));
|
||||
} else {
|
||||
final var debitorRelOptional = relrealRepo.findByUuid(body.getDebitorRelUuid());
|
||||
debitorRelOptional.ifPresentOrElse(
|
||||
debitorRel -> {entityToSave.setDebitorRel(relrealRepo.save(debitorRel));},
|
||||
() -> {
|
||||
throw new ValidationException(
|
||||
"Unable to find RealRelation by debitorRelUuid: " + body.getDebitorRelUuid());
|
||||
});
|
||||
}
|
||||
final var entityToSave = mapper.map(body, HsOfficeDebitorEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER);
|
||||
|
||||
final var savedEntity = debitorRepo.save(entityToSave);
|
||||
em.flush();
|
||||
em.refresh(savedEntity);
|
||||
final var savedEntity = debitorRepo.save(entityToSave).reload(em);
|
||||
|
||||
final var uri =
|
||||
MvcUriComponentsBuilder.fromController(getClass())
|
||||
@ -181,7 +175,7 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
|
||||
|
||||
context.define(currentSubject, assumedRoles);
|
||||
|
||||
final var current = debitorRepo.findByUuid(debitorUuid).orElseThrow();
|
||||
final var current = debitorRepo.findByUuid(debitorUuid).orElseThrow().reload(em);
|
||||
|
||||
new HsOfficeDebitorEntityPatcher(em, current).apply(body);
|
||||
|
||||
@ -191,7 +185,39 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
|
||||
return ResponseEntity.ok(mapped);
|
||||
}
|
||||
|
||||
final BiConsumer<HsOfficeDebitorInsertResource, HsOfficeDebitorEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
|
||||
if (resource.getDebitorRel() != null) {
|
||||
final var debitorRel = realRelRepo.save(HsOfficeRelationRealEntity.builder()
|
||||
.type(DEBITOR)
|
||||
.anchor(resolve(
|
||||
"debitorRel.anchor.uuid", resource.getDebitorRel().getAnchorUuid(), realPersonRepo::findByUuid))
|
||||
.holder(resolve(
|
||||
"debitorRel.holder.uuid", resource.getDebitorRel().getHolderUuid(), realPersonRepo::findByUuid))
|
||||
.contact(resolve(
|
||||
"debitorRel.contact.uuid", resource.getDebitorRel().getContactUuid(), realContactRepo::findByUuid))
|
||||
.build());
|
||||
entity.setDebitorRel(debitorRel);
|
||||
} else {
|
||||
final var debitorRelOptional = realRelRepo.findByUuid(resource.getDebitorRelUuid());
|
||||
debitorRelOptional.ifPresentOrElse(
|
||||
debitorRel -> {
|
||||
entity.setDebitorRel(realRelRepo.save(debitorRel));
|
||||
},
|
||||
() -> {
|
||||
throw new ValidationException(
|
||||
"Unable to find debitorRel.uuid: " + resource.getDebitorRelUuid());
|
||||
});
|
||||
}
|
||||
|
||||
if (resource.getRefundBankAccountUuid() != null) {
|
||||
entity.setRefundBankAccount(resolve(
|
||||
"refundBankAccount.uuid", resource.getRefundBankAccountUuid(), bankAccountRepo::findByUuid));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
final BiConsumer<HsOfficeDebitorEntity, HsOfficeDebitorResource> ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> {
|
||||
resource.setDebitorNumber(entity.getTaggedDebitorNumber());
|
||||
resource.getPartner().setPartnerNumber(entity.getPartner().getTaggedPartnerNumber());
|
||||
};
|
||||
}
|
||||
|
@ -7,16 +7,16 @@ import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartner;
|
||||
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelation;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRbacEntity;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL;
|
||||
import net.hostsharing.hsadminng.repr.Stringify;
|
||||
import net.hostsharing.hsadminng.repr.Stringifyable;
|
||||
import org.hibernate.annotations.GenericGenerator;
|
||||
import org.hibernate.annotations.JoinFormula;
|
||||
import org.hibernate.annotations.NotFound;
|
||||
import org.hibernate.annotations.NotFoundAction;
|
||||
@ -40,17 +40,17 @@ import static jakarta.persistence.CascadeType.PERSIST;
|
||||
import static jakarta.persistence.CascadeType.REFRESH;
|
||||
import static java.util.Optional.ofNullable;
|
||||
import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.DEBITOR;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Column.dependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.ColumnValue.usingCase;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.ColumnValue.usingDefaultCase;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.GLOBAL;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Nullable.NOT_NULL;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Nullable.NULLABLE;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.directlyFetchedByDependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.fetchedBySql;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Column.dependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.ColumnValue.usingCase;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.ColumnValue.usingDefaultCase;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.GLOBAL;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Nullable.NOT_NULL;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Nullable.NULLABLE;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL.directlyFetchedByDependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL.fetchedBySql;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.rbacViewFor;
|
||||
import static net.hostsharing.hsadminng.repr.Stringify.stringify;
|
||||
|
||||
@Entity
|
||||
@ -75,7 +75,6 @@ public class HsOfficeDebitorEntity implements BaseEntity<HsOfficeDebitorEntity>,
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
@GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
|
||||
private UUID uuid;
|
||||
|
||||
@Version
|
||||
@ -87,16 +86,16 @@ public class HsOfficeDebitorEntity implements BaseEntity<HsOfficeDebitorEntity>,
|
||||
value = """
|
||||
(
|
||||
SELECT DISTINCT partner.uuid
|
||||
FROM hs_office.partner_rv partner
|
||||
JOIN hs_office.relation_rv dRel
|
||||
ON dRel.uuid = debitorreluuid AND dRel.type = 'DEBITOR'
|
||||
JOIN hs_office.relation_rv pRel
|
||||
FROM hs_office.partner partner
|
||||
JOIN hs_office.relation dRel
|
||||
ON dRel.uuid = debitorRelUuid AND dRel.type = 'DEBITOR'
|
||||
JOIN hs_office.relation pRel
|
||||
ON pRel.uuid = partner.partnerRelUuid AND pRel.type = 'PARTNER'
|
||||
WHERE pRel.holderUuid = dRel.anchorUuid
|
||||
)
|
||||
""")
|
||||
@NotFound(action = NotFoundAction.IGNORE) // TODO.impl: map a simplified raw-PartnerEntity, just for the partner-number
|
||||
private HsOfficePartnerEntity partner;
|
||||
@NotFound(action = NotFoundAction.EXCEPTION) // TODO.impl: map a simplified raw-PartnerEntity, just for the partner-number
|
||||
private HsOfficePartnerRealEntity partner;
|
||||
|
||||
@Column(name = "debitornumbersuffix", length = 2)
|
||||
@Pattern(regexp = TWO_DECIMAL_DIGITS)
|
||||
@ -132,9 +131,7 @@ public class HsOfficeDebitorEntity implements BaseEntity<HsOfficeDebitorEntity>,
|
||||
@Override
|
||||
public HsOfficeDebitorEntity load() {
|
||||
BaseEntity.super.load();
|
||||
if (partner != null) {
|
||||
partner.load();
|
||||
}
|
||||
debitorRel.load();
|
||||
if (refundBankAccount != null) {
|
||||
refundBankAccount.load();
|
||||
@ -145,7 +142,7 @@ public class HsOfficeDebitorEntity implements BaseEntity<HsOfficeDebitorEntity>,
|
||||
public String getTaggedDebitorNumber() {
|
||||
return ofNullable(partner)
|
||||
.filter(partner -> debitorNumberSuffix != null)
|
||||
.map(HsOfficePartnerEntity::getPartnerNumber)
|
||||
.map(HsOfficePartner::getPartnerNumber)
|
||||
.map(partnerNumber -> DEBITOR_NUMBER_TAG + partnerNumber + debitorNumberSuffix)
|
||||
.orElse(null);
|
||||
}
|
||||
@ -160,7 +157,7 @@ public class HsOfficeDebitorEntity implements BaseEntity<HsOfficeDebitorEntity>,
|
||||
return getTaggedDebitorNumber();
|
||||
}
|
||||
|
||||
public static RbacView rbac() {
|
||||
public static RbacSpec rbac() {
|
||||
return rbacViewFor("debitor", HsOfficeDebitorEntity.class)
|
||||
.withIdentityView(SQL.query("""
|
||||
SELECT debitor.uuid AS uuid,
|
||||
|
@ -19,7 +19,7 @@ public interface HsOfficeDebitorRepository extends Repository<HsOfficeDebitorEnt
|
||||
|
||||
@Query("""
|
||||
SELECT debitor FROM HsOfficeDebitorEntity debitor
|
||||
JOIN HsOfficePartnerEntity partner
|
||||
JOIN HsOfficePartnerRealEntity partner
|
||||
ON partner.partnerRel.holder = debitor.debitorRel.anchor
|
||||
AND partner.partnerRel.type = 'PARTNER' AND debitor.debitorRel.type = 'DEBITOR'
|
||||
WHERE partner.partnerNumber = :partnerNumber
|
||||
@ -42,7 +42,7 @@ public interface HsOfficeDebitorRepository extends Repository<HsOfficeDebitorEnt
|
||||
|
||||
@Query("""
|
||||
SELECT debitor FROM HsOfficeDebitorEntity debitor
|
||||
JOIN HsOfficePartnerEntity partner
|
||||
JOIN HsOfficePartnerRealEntity partner
|
||||
ON partner.partnerRel.holder = debitor.debitorRel.anchor
|
||||
AND partner.partnerRel.type = 'PARTNER' AND debitor.debitorRel.type = 'DEBITOR'
|
||||
JOIN HsOfficePersonRealEntity person
|
||||
|
@ -6,14 +6,16 @@ import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeMembersh
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipInsertResource;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipPatchResource;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipResource;
|
||||
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRbacEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRealRepository;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
|
||||
|
||||
import jakarta.persistence.EntityNotFoundException;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.function.BiConsumer;
|
||||
@ -28,7 +30,10 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
|
||||
private Context context;
|
||||
|
||||
@Autowired
|
||||
private StandardMapper mapper;
|
||||
private StrictMapper mapper;
|
||||
|
||||
@Autowired
|
||||
private HsOfficePartnerRealRepository partnerRepo;
|
||||
|
||||
@Autowired
|
||||
private HsOfficeMembershipRepository membershipRepo;
|
||||
@ -47,7 +52,7 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
|
||||
|
||||
final var entities = partnerNumber != null
|
||||
? membershipRepo.findMembershipsByPartnerNumber(
|
||||
cropTag(HsOfficePartnerEntity.PARTNER_NUMBER_TAG, partnerNumber))
|
||||
cropTag(HsOfficePartnerRbacEntity.PARTNER_NUMBER_TAG, partnerNumber))
|
||||
: partnerUuid != null
|
||||
? membershipRepo.findMembershipsByPartnerUuid(partnerUuid)
|
||||
: membershipRepo.findAll();
|
||||
@ -68,7 +73,7 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
|
||||
|
||||
context.define(currentSubject, assumedRoles);
|
||||
|
||||
final var entityToSave = mapper.map(body, HsOfficeMembershipEntity.class);
|
||||
final var entityToSave = mapper.map(body, HsOfficeMembershipEntity.class, SEPA_MANDATE_RESOURCE_TO_ENTITY_POSTMAPPER);
|
||||
|
||||
final var saved = membershipRepo.save(entityToSave);
|
||||
|
||||
@ -164,5 +169,12 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
|
||||
if (entity.getValidity().hasUpperBound()) {
|
||||
resource.setValidTo(entity.getValidity().upper().minusDays(1));
|
||||
}
|
||||
resource.getPartner().setPartnerNumber(entity.getPartner().getTaggedPartnerNumber()); // TODO.refa: use partner mapper?
|
||||
};
|
||||
|
||||
final BiConsumer<HsOfficeMembershipInsertResource, HsOfficeMembershipEntity> SEPA_MANDATE_RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
|
||||
entity.setPartner(partnerRepo.findByUuid(resource.getPartnerUuid())
|
||||
.orElseThrow(() -> new EntityNotFoundException(
|
||||
"ERROR: [400] partnerUuid %s not found".formatted(resource.getPartnerUuid()))));
|
||||
};
|
||||
}
|
||||
|
@ -8,11 +8,12 @@ import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRbacEntity;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL;
|
||||
import net.hostsharing.hsadminng.rbac.role.WithRoleId;
|
||||
import net.hostsharing.hsadminng.repr.Stringify;
|
||||
import net.hostsharing.hsadminng.repr.Stringifyable;
|
||||
import org.hibernate.annotations.Type;
|
||||
@ -38,21 +39,21 @@ import static io.hypersistence.utils.hibernate.type.range.Range.emptyRange;
|
||||
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.lowerInclusiveFromPostgresDateRange;
|
||||
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange;
|
||||
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.upperInclusiveFromPostgresDateRange;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Column.dependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.ColumnValue.usingDefaultCase;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.GLOBAL;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Nullable.NOT_NULL;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.DELETE;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.INSERT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.SELECT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.UPDATE;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.RbacSubjectReference.UserRole.CREATOR;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.ADMIN;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.AGENT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.OWNER;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.TENANT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.fetchedBySql;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Column.dependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.ColumnValue.usingDefaultCase;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.GLOBAL;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Nullable.NOT_NULL;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.DELETE;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.INSERT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.SELECT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.UPDATE;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.RbacSubjectReference.UserRole.CREATOR;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.ADMIN;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.AGENT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.OWNER;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.TENANT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL.fetchedBySql;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.rbacViewFor;
|
||||
import static net.hostsharing.hsadminng.repr.Stringify.stringify;
|
||||
|
||||
@Entity
|
||||
@ -63,7 +64,7 @@ import static net.hostsharing.hsadminng.repr.Stringify.stringify;
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@DisplayAs("Membership")
|
||||
public class HsOfficeMembershipEntity implements BaseEntity<HsOfficeMembershipEntity>, Stringifyable {
|
||||
public class HsOfficeMembershipEntity implements BaseEntity<HsOfficeMembershipEntity>, Stringifyable, WithRoleId {
|
||||
|
||||
public static final String MEMBER_NUMBER_TAG = "M-";
|
||||
public static final String TWO_DECIMAL_DIGITS = "^([0-9]{2})$";
|
||||
@ -84,7 +85,7 @@ public class HsOfficeMembershipEntity implements BaseEntity<HsOfficeMembershipEn
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "partneruuid")
|
||||
private HsOfficePartnerEntity partner;
|
||||
private HsOfficePartnerRealEntity partner;
|
||||
|
||||
@Column(name = "membernumbersuffix", length = 2)
|
||||
@Pattern(regexp = TWO_DECIMAL_DIGITS)
|
||||
@ -160,7 +161,7 @@ public class HsOfficeMembershipEntity implements BaseEntity<HsOfficeMembershipEn
|
||||
}
|
||||
}
|
||||
|
||||
public static RbacView rbac() {
|
||||
public static RbacSpec rbac() {
|
||||
return rbacViewFor("membership", HsOfficeMembershipEntity.class)
|
||||
.withIdentityView(SQL.query("""
|
||||
SELECT m.uuid AS uuid,
|
||||
|
@ -2,18 +2,18 @@ package net.hostsharing.hsadminng.hs.office.membership;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipPatchResource;
|
||||
import net.hostsharing.hsadminng.mapper.EntityPatcher;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.mapper.OptionalFromJson;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class HsOfficeMembershipEntityPatcher implements EntityPatcher<HsOfficeMembershipPatchResource> {
|
||||
|
||||
private final StandardMapper mapper;
|
||||
private final StrictMapper mapper;
|
||||
private final HsOfficeMembershipEntity entity;
|
||||
|
||||
public HsOfficeMembershipEntityPatcher(
|
||||
final StandardMapper mapper,
|
||||
final StrictMapper mapper,
|
||||
final HsOfficeMembershipEntity entity) {
|
||||
this.mapper = mapper;
|
||||
this.entity = entity;
|
||||
|
@ -0,0 +1,103 @@
|
||||
package net.hostsharing.hsadminng.hs.office.partner;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContact;
|
||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePerson;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelation;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import net.hostsharing.hsadminng.repr.Stringify;
|
||||
import net.hostsharing.hsadminng.repr.Stringifyable;
|
||||
import org.hibernate.annotations.NotFound;
|
||||
import org.hibernate.annotations.NotFoundAction;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.MappedSuperclass;
|
||||
import jakarta.persistence.Version;
|
||||
import java.util.UUID;
|
||||
|
||||
import static jakarta.persistence.CascadeType.DETACH;
|
||||
import static jakarta.persistence.CascadeType.MERGE;
|
||||
import static jakarta.persistence.CascadeType.PERSIST;
|
||||
import static jakarta.persistence.CascadeType.REFRESH;
|
||||
import static java.util.Optional.ofNullable;
|
||||
import static net.hostsharing.hsadminng.repr.Stringify.stringify;
|
||||
|
||||
@MappedSuperclass
|
||||
@Getter
|
||||
@Setter
|
||||
@SuperBuilder(toBuilder = true)
|
||||
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
@AllArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
@DisplayAs("Partner")
|
||||
public class HsOfficePartner<T extends HsOfficePartner<?>> implements Stringifyable, BaseEntity<T> {
|
||||
|
||||
public static final String PARTNER_NUMBER_TAG = "P-";
|
||||
|
||||
protected static Stringify<HsOfficePartner> stringify = stringify(HsOfficePartner.class, "partner")
|
||||
.withIdProp(HsOfficePartner::toShortString)
|
||||
.withProp(p -> ofNullable(p.getPartnerRel())
|
||||
.map(HsOfficeRelation::getHolder)
|
||||
.map(HsOfficePerson::toShortString)
|
||||
.orElse(null))
|
||||
.withProp(p -> ofNullable(p.getPartnerRel())
|
||||
.map(HsOfficeRelation::getContact)
|
||||
.map(HsOfficeContact::toShortString)
|
||||
.orElse(null))
|
||||
.quotedValues(false);
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private UUID uuid;
|
||||
|
||||
@Version
|
||||
private int version;
|
||||
|
||||
@Column(name = "partnernumber", columnDefinition = "numeric(5) not null")
|
||||
private Integer partnerNumber;
|
||||
|
||||
@ManyToOne(cascade = { PERSIST, MERGE, REFRESH, DETACH }, optional = false, fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "partnerreluuid", nullable = false)
|
||||
private HsOfficeRelationRealEntity partnerRel;
|
||||
|
||||
@ManyToOne(cascade = { PERSIST, MERGE, REFRESH, DETACH }, optional = true, fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "detailsuuid")
|
||||
@NotFound(action = NotFoundAction.IGNORE)
|
||||
private HsOfficePartnerDetailsEntity details;
|
||||
|
||||
@Override
|
||||
public T load() {
|
||||
BaseEntity.super.load();
|
||||
partnerRel.load();
|
||||
if (details != null) {
|
||||
details.load();
|
||||
}
|
||||
//noinspection unchecked
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
public String getTaggedPartnerNumber() {
|
||||
return PARTNER_NUMBER_TAG + partnerNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return stringify.apply(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toShortString() {
|
||||
return getTaggedPartnerNumber();
|
||||
}
|
||||
}
|
@ -13,7 +13,7 @@ import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealRepository;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
@ -26,6 +26,7 @@ import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.PersistenceContext;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.EX_PARTNER;
|
||||
import static net.hostsharing.hsadminng.repr.TaggedNumber.cropTag;
|
||||
@ -38,10 +39,10 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
|
||||
private Context context;
|
||||
|
||||
@Autowired
|
||||
private StandardMapper mapper;
|
||||
private StrictMapper mapper;
|
||||
|
||||
@Autowired
|
||||
private HsOfficePartnerRepository partnerRepo;
|
||||
private HsOfficePartnerRbacRepository partnerRepo;
|
||||
|
||||
@Autowired
|
||||
private HsOfficeRelationRealRepository relationRepo;
|
||||
@ -60,7 +61,7 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
|
||||
|
||||
final var entities = partnerRepo.findPartnerByOptionalNameLike(name);
|
||||
|
||||
final var resources = mapper.mapList(entities, HsOfficePartnerResource.class);
|
||||
final var resources = mapper.mapList(entities, HsOfficePartnerResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
|
||||
return ResponseEntity.ok(resources);
|
||||
}
|
||||
|
||||
@ -83,7 +84,7 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
|
||||
.path("/api/hs/office/partners/{id}")
|
||||
.buildAndExpand(saved.getUuid())
|
||||
.toUri();
|
||||
final var mapped = mapper.map(saved, HsOfficePartnerResource.class);
|
||||
final var mapped = mapper.map(saved, HsOfficePartnerResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
|
||||
return ResponseEntity.created(uri).body(mapped);
|
||||
}
|
||||
|
||||
@ -101,7 +102,8 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
|
||||
if (result.isEmpty()) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
return ResponseEntity.ok(mapper.map(result.get(), HsOfficePartnerResource.class));
|
||||
final var mapped = mapper.map(result.get(), HsOfficePartnerResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
|
||||
return ResponseEntity.ok(mapped);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -118,7 +120,8 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
|
||||
if (result.isEmpty()) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
return ResponseEntity.ok(mapper.map(result.get(), HsOfficePartnerResource.class));
|
||||
final var mapped = mapper.map(result.get(), HsOfficePartnerResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
|
||||
return ResponseEntity.ok(mapped);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -161,20 +164,20 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
|
||||
final var saved = partnerRepo.save(current);
|
||||
optionallyCreateExPartnerRelation(saved, previousPartnerRel);
|
||||
|
||||
final var mapped = mapper.map(saved, HsOfficePartnerResource.class);
|
||||
final var mapped = mapper.map(saved, HsOfficePartnerResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
|
||||
return ResponseEntity.ok(mapped);
|
||||
}
|
||||
|
||||
private void optionallyCreateExPartnerRelation(final HsOfficePartnerEntity saved, final HsOfficeRelationRealEntity previousPartnerRel) {
|
||||
private void optionallyCreateExPartnerRelation(final HsOfficePartnerRbacEntity saved, final HsOfficeRelationRealEntity previousPartnerRel) {
|
||||
if (!saved.getPartnerRel().getUuid().equals(previousPartnerRel.getUuid())) {
|
||||
// TODO.impl: we also need to use the new partner-person as the anchor
|
||||
relationRepo.save(previousPartnerRel.toBuilder().uuid(null).type(EX_PARTNER).build());
|
||||
}
|
||||
}
|
||||
|
||||
private HsOfficePartnerEntity createPartnerEntity(final HsOfficePartnerInsertResource body) {
|
||||
final var entityToSave = new HsOfficePartnerEntity();
|
||||
entityToSave.setPartnerNumber(cropTag(HsOfficePartnerEntity.PARTNER_NUMBER_TAG, body.getPartnerNumber()));
|
||||
private HsOfficePartnerRbacEntity createPartnerEntity(final HsOfficePartnerInsertResource body) {
|
||||
final var entityToSave = new HsOfficePartnerRbacEntity();
|
||||
entityToSave.setPartnerNumber(cropTag(HsOfficePartnerRbacEntity.PARTNER_NUMBER_TAG, body.getPartnerNumber()));
|
||||
entityToSave.setPartnerRel(persistPartnerRel(body.getPartnerRel()));
|
||||
entityToSave.setDetails(mapper.map(body.getDetails(), HsOfficePartnerDetailsEntity.class));
|
||||
return entityToSave;
|
||||
@ -197,4 +200,8 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
|
||||
throw new ReferenceNotFoundException(entityClass, uuid, exc);
|
||||
}
|
||||
}
|
||||
|
||||
final BiConsumer<HsOfficePartnerRbacEntity, HsOfficePartnerResource> ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> {
|
||||
resource.setPartnerNumber(entity.getTaggedPartnerNumber());
|
||||
};
|
||||
}
|
||||
|
@ -3,8 +3,8 @@ package net.hostsharing.hsadminng.hs.office.partner;
|
||||
import lombok.*;
|
||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL;
|
||||
import net.hostsharing.hsadminng.repr.Stringify;
|
||||
import net.hostsharing.hsadminng.repr.Stringifyable;
|
||||
|
||||
@ -13,10 +13,10 @@ import java.io.IOException;
|
||||
import java.time.LocalDate;
|
||||
import java.util.UUID;
|
||||
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.GLOBAL;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.GLOBAL;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.rbacViewFor;
|
||||
import static net.hostsharing.hsadminng.repr.Stringify.stringify;
|
||||
|
||||
@Entity
|
||||
@ -67,7 +67,7 @@ public class HsOfficePartnerDetailsEntity implements BaseEntity<HsOfficePartnerD
|
||||
}
|
||||
|
||||
|
||||
public static RbacView rbac() {
|
||||
public static RbacSpec rbac() {
|
||||
return rbacViewFor("partnerDetails", HsOfficePartnerDetailsEntity.class)
|
||||
.withIdentityView(SQL.query("""
|
||||
SELECT partnerDetails.uuid as uuid, partner_iv.idName as idName
|
||||
|
@ -1,128 +0,0 @@
|
||||
package net.hostsharing.hsadminng.hs.office.partner;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContact;
|
||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePerson;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRbacEntity;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelation;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
|
||||
import net.hostsharing.hsadminng.repr.Stringify;
|
||||
import net.hostsharing.hsadminng.repr.Stringifyable;
|
||||
import org.hibernate.annotations.NotFound;
|
||||
import org.hibernate.annotations.NotFoundAction;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
import static jakarta.persistence.CascadeType.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Column.dependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.ColumnValue.usingDefaultCase;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.GLOBAL;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.SELECT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.directlyFetchedByDependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
||||
import static java.util.Optional.ofNullable;
|
||||
import static net.hostsharing.hsadminng.repr.Stringify.stringify;
|
||||
|
||||
@Entity
|
||||
@Table(schema = "hs_office", name = "partner_rv")
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@DisplayAs("Partner")
|
||||
public class HsOfficePartnerEntity implements Stringifyable, BaseEntity<HsOfficePartnerEntity> {
|
||||
|
||||
public static final String PARTNER_NUMBER_TAG = "P-";
|
||||
|
||||
private static Stringify<HsOfficePartnerEntity> stringify = stringify(HsOfficePartnerEntity.class, "partner")
|
||||
.withIdProp(HsOfficePartnerEntity::toShortString)
|
||||
.withProp(p -> ofNullable(p.getPartnerRel())
|
||||
.map(HsOfficeRelation::getHolder)
|
||||
.map(HsOfficePerson::toShortString)
|
||||
.orElse(null))
|
||||
.withProp(p -> ofNullable(p.getPartnerRel())
|
||||
.map(HsOfficeRelation::getContact)
|
||||
.map(HsOfficeContact::toShortString)
|
||||
.orElse(null))
|
||||
.quotedValues(false);
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private UUID uuid;
|
||||
|
||||
@Version
|
||||
private int version;
|
||||
|
||||
@Column(name = "partnernumber", columnDefinition = "numeric(5) not null")
|
||||
private Integer partnerNumber;
|
||||
|
||||
@ManyToOne(cascade = { PERSIST, MERGE, REFRESH, DETACH }, optional = false, fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "partnerreluuid", nullable = false)
|
||||
private HsOfficeRelationRealEntity partnerRel;
|
||||
|
||||
@ManyToOne(cascade = { PERSIST, MERGE, REFRESH, DETACH }, optional = true, fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "detailsuuid")
|
||||
@NotFound(action = NotFoundAction.IGNORE)
|
||||
private HsOfficePartnerDetailsEntity details;
|
||||
|
||||
@Override
|
||||
public HsOfficePartnerEntity load() {
|
||||
BaseEntity.super.load();
|
||||
partnerRel.load();
|
||||
details.load();
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getTaggedPartnerNumber() {
|
||||
return PARTNER_NUMBER_TAG + partnerNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return stringify.apply(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toShortString() {
|
||||
return getTaggedPartnerNumber();
|
||||
}
|
||||
|
||||
public static RbacView rbac() {
|
||||
return rbacViewFor("partner", HsOfficePartnerEntity.class)
|
||||
.withIdentityView(SQL.projection("'P-' || partnerNumber"))
|
||||
.withUpdatableColumns("partnerRelUuid")
|
||||
.toRole(GLOBAL, ADMIN).grantPermission(INSERT)
|
||||
|
||||
.importRootEntityAliasProxy("partnerRel", HsOfficeRelationRbacEntity.class,
|
||||
usingDefaultCase(),
|
||||
directlyFetchedByDependsOnColumn(),
|
||||
dependsOnColumn("partnerRelUuid"))
|
||||
.createPermission(DELETE).grantedTo("partnerRel", OWNER)
|
||||
.createPermission(UPDATE).grantedTo("partnerRel", ADMIN)
|
||||
.createPermission(SELECT).grantedTo("partnerRel", TENANT)
|
||||
|
||||
.importSubEntityAlias("partnerDetails", HsOfficePartnerDetailsEntity.class,
|
||||
directlyFetchedByDependsOnColumn(),
|
||||
dependsOnColumn("detailsUuid"))
|
||||
.createPermission("partnerDetails", DELETE).grantedTo("partnerRel", OWNER)
|
||||
.createPermission("partnerDetails", UPDATE).grantedTo("partnerRel", AGENT)
|
||||
.createPermission("partnerDetails", SELECT).grantedTo("partnerRel", AGENT); // not TENANT!
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
rbac().generateWithBaseFileName("5-hs-office/504-partner/5043-hs-office-partner-rbac");
|
||||
}
|
||||
}
|
@ -9,10 +9,10 @@ import jakarta.persistence.EntityManager;
|
||||
|
||||
class HsOfficePartnerEntityPatcher implements EntityPatcher<HsOfficePartnerPatchResource> {
|
||||
private final EntityManager em;
|
||||
private final HsOfficePartnerEntity entity;
|
||||
private final HsOfficePartnerRbacEntity entity;
|
||||
HsOfficePartnerEntityPatcher(
|
||||
final EntityManager em,
|
||||
final HsOfficePartnerEntity entity) {
|
||||
final HsOfficePartnerRbacEntity entity) {
|
||||
this.em = em;
|
||||
this.entity = entity;
|
||||
}
|
||||
|
@ -0,0 +1,59 @@
|
||||
package net.hostsharing.hsadminng.hs.office.partner;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRbacEntity;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import java.io.IOException;
|
||||
|
||||
import static jakarta.persistence.CascadeType.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Column.dependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.ColumnValue.usingDefaultCase;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.GLOBAL;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.SELECT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL.directlyFetchedByDependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.rbacViewFor;
|
||||
|
||||
@Entity
|
||||
@Table(schema = "hs_office", name = "partner_rv")
|
||||
@Getter
|
||||
@Setter
|
||||
@SuperBuilder(toBuilder = true)
|
||||
@NoArgsConstructor
|
||||
@DisplayAs("RbacPartner")
|
||||
public class HsOfficePartnerRbacEntity extends HsOfficePartner<HsOfficePartnerRbacEntity> {
|
||||
|
||||
public static RbacSpec rbac() {
|
||||
return rbacViewFor("partner", HsOfficePartnerRbacEntity.class)
|
||||
.withIdentityView(SQL.projection("'P-' || partnerNumber"))
|
||||
.withUpdatableColumns("partnerRelUuid")
|
||||
.toRole(GLOBAL, ADMIN).grantPermission(INSERT)
|
||||
|
||||
.importRootEntityAliasProxy("partnerRel", HsOfficeRelationRbacEntity.class,
|
||||
usingDefaultCase(),
|
||||
directlyFetchedByDependsOnColumn(),
|
||||
dependsOnColumn("partnerRelUuid"))
|
||||
.createPermission(DELETE).grantedTo("partnerRel", OWNER)
|
||||
.createPermission(UPDATE).grantedTo("partnerRel", ADMIN)
|
||||
.createPermission(SELECT).grantedTo("partnerRel", TENANT)
|
||||
|
||||
.importSubEntityAlias("partnerDetails", HsOfficePartnerDetailsEntity.class,
|
||||
directlyFetchedByDependsOnColumn(),
|
||||
dependsOnColumn("detailsUuid"))
|
||||
.createPermission("partnerDetails", DELETE).grantedTo("partnerRel", OWNER)
|
||||
.createPermission("partnerDetails", UPDATE).grantedTo("partnerRel", AGENT)
|
||||
.createPermission("partnerDetails", SELECT).grantedTo("partnerRel", AGENT); // not TENANT!
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
rbac().generateWithBaseFileName("5-hs-office/504-partner/5043-hs-office-partner-rbac");
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package net.hostsharing.hsadminng.hs.office.partner;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface HsOfficePartnerRbacRepository extends Repository<HsOfficePartnerRbacEntity, UUID> {
|
||||
|
||||
@Timed("app.office.partners.repo.findByUuid.rbac")
|
||||
Optional<HsOfficePartnerRbacEntity> findByUuid(UUID id);
|
||||
|
||||
@Timed("app.office.partners.repo.findAll.rbac")
|
||||
List<HsOfficePartnerRbacEntity> findAll(); // TODO.refa: move to a repo in test sources
|
||||
|
||||
@Query(value = """
|
||||
select partner.uuid, partner.detailsuuid, partner.partnernumber, partner.partnerreluuid, partner.version
|
||||
from hs_office.partner_rv partner
|
||||
join hs_office.relation partnerRel on partnerRel.uuid = partner.partnerreluuid
|
||||
join hs_office.contact contact on contact.uuid = partnerRel.contactuuid
|
||||
join hs_office.person partnerPerson on partnerPerson.uuid = partnerRel.holderuuid
|
||||
left join hs_office.partner_details_rv partnerDetails on partnerDetails.uuid = partner.detailsuuid
|
||||
where :name is null
|
||||
or (partnerDetails.uuid is not null and partnerDetails.birthname like (cast(:name as text) || '%') escape '')
|
||||
or contact.caption like (cast(:name as text) || '%') escape ''
|
||||
or partnerPerson.tradename like (cast(:name as text) || '%') escape ''
|
||||
or partnerPerson.givenname like (cast(:name as text) || '%') escape ''
|
||||
or partnerPerson.familyname like (cast(:name as text) || '%') escape ''
|
||||
""", nativeQuery = true)
|
||||
@Timed("app.office.partners.repo.findPartnerByOptionalNameLike.rbac")
|
||||
List<HsOfficePartnerRbacEntity> findPartnerByOptionalNameLike(String name);
|
||||
|
||||
@Timed("app.office.partners.repo.findPartnerByPartnerNumber.rbac")
|
||||
Optional<HsOfficePartnerRbacEntity> findPartnerByPartnerNumber(Integer partnerNumber);
|
||||
|
||||
@Timed("app.office.partners.repo.save.rbac")
|
||||
HsOfficePartnerRbacEntity save(final HsOfficePartnerRbacEntity entity);
|
||||
|
||||
@Timed("app.office.partners.repo.count.rbac")
|
||||
long count();
|
||||
|
||||
@Timed("app.office.partners.repo.deleteByUuid.rbac")
|
||||
int deleteByUuid(UUID uuid);
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package net.hostsharing.hsadminng.hs.office.partner;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
|
||||
@Entity
|
||||
@Table(schema = "hs_office", name = "partner")
|
||||
@Getter
|
||||
@Setter
|
||||
@SuperBuilder(toBuilder = true)
|
||||
@NoArgsConstructor
|
||||
@DisplayAs("RealPartner")
|
||||
public class HsOfficePartnerRealEntity extends HsOfficePartner<HsOfficePartnerRealEntity> {
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package net.hostsharing.hsadminng.hs.office.partner;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface HsOfficePartnerRealRepository extends Repository<HsOfficePartnerRealEntity, UUID> {
|
||||
|
||||
@Timed("app.office.partners.repo.findByUuid.real")
|
||||
Optional<HsOfficePartnerRealEntity> findByUuid(UUID id);
|
||||
|
||||
@Timed("app.office.partners.repo.findAll.real")
|
||||
List<HsOfficePartnerRbacEntity> findAll(); // TODO.refa: move to a repo in test sources
|
||||
|
||||
@Query(value = """
|
||||
select partner.uuid, partner.detailsuuid, partner.partnernumber, partner.partnerreluuid, partner.version
|
||||
from hs_office.partner partner
|
||||
join hs_office.relation partnerRel on partnerRel.uuid = partner.partnerreluuid
|
||||
join hs_office.contact contact on contact.uuid = partnerRel.contactuuid
|
||||
join hs_office.person partnerPerson on partnerPerson.uuid = partnerRel.holderuuid
|
||||
left join hs_office.partner_details_rv partnerDetails on partnerDetails.uuid = partner.detailsuuid
|
||||
where :name is null
|
||||
or (partnerDetails.uuid is not null and partnerDetails.birthname like (cast(:name as text) || '%') escape '')
|
||||
or contact.caption like (cast(:name as text) || '%') escape ''
|
||||
or partnerPerson.tradename like (cast(:name as text) || '%') escape ''
|
||||
or partnerPerson.givenname like (cast(:name as text) || '%') escape ''
|
||||
or partnerPerson.familyname like (cast(:name as text) || '%') escape ''
|
||||
""", nativeQuery = true)
|
||||
@Timed("app.office.partners.repo.findPartnerByOptionalNameLike.real")
|
||||
List<HsOfficePartnerRealEntity> findPartnerByOptionalNameLike(String name);
|
||||
|
||||
@Timed("app.office.partners.repo.findPartnerByPartnerNumber.real")
|
||||
Optional<HsOfficePartnerRealEntity> findPartnerByPartnerNumber(Integer partnerNumber);
|
||||
|
||||
@Timed("app.office.partners.repo.save.real")
|
||||
HsOfficePartnerRealEntity save(final HsOfficePartnerRealEntity entity);
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
package net.hostsharing.hsadminng.hs.office.partner;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface HsOfficePartnerRepository extends Repository<HsOfficePartnerEntity, UUID> {
|
||||
|
||||
@Timed("app.office.partners.repo.findByUuid")
|
||||
Optional<HsOfficePartnerEntity> findByUuid(UUID id);
|
||||
|
||||
@Timed("app.office.partners.repo.findAll")
|
||||
List<HsOfficePartnerEntity> findAll(); // TODO.refa: move to a repo in test sources
|
||||
|
||||
@Query("""
|
||||
SELECT partner FROM HsOfficePartnerEntity partner
|
||||
JOIN HsOfficeRelationRealEntity rel ON rel.uuid = partner.partnerRel.uuid
|
||||
JOIN HsOfficeContactRealEntity contact ON contact.uuid = rel.contact.uuid
|
||||
JOIN HsOfficePersonRealEntity person ON person.uuid = rel.holder.uuid
|
||||
WHERE :name is null
|
||||
OR partner.details.birthName like concat(cast(:name as text), '%')
|
||||
OR contact.caption like concat(cast(:name as text), '%')
|
||||
OR person.tradeName like concat(cast(:name as text), '%')
|
||||
OR person.givenName like concat(cast(:name as text), '%')
|
||||
OR person.familyName like concat(cast(:name as text), '%')
|
||||
""")
|
||||
@Timed("app.office.partners.repo.findPartnerByOptionalNameLike")
|
||||
List<HsOfficePartnerEntity> findPartnerByOptionalNameLike(String name);
|
||||
|
||||
@Timed("app.office.partners.repo.findPartnerByPartnerNumber")
|
||||
Optional<HsOfficePartnerEntity> findPartnerByPartnerNumber(Integer partnerNumber);
|
||||
|
||||
@Timed("app.office.partners.repo.save")
|
||||
HsOfficePartnerEntity save(final HsOfficePartnerEntity entity);
|
||||
|
||||
@Timed("app.office.partners.repo.count")
|
||||
long count();
|
||||
|
||||
@Timed("app.office.partners.repo.deleteByUuid")
|
||||
int deleteByUuid(UUID uuid);
|
||||
}
|
@ -9,6 +9,7 @@ import lombok.experimental.FieldNameConstants;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import net.hostsharing.hsadminng.rbac.role.WithRoleId;
|
||||
import net.hostsharing.hsadminng.repr.Stringify;
|
||||
import net.hostsharing.hsadminng.repr.Stringifyable;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@ -30,7 +31,7 @@ import static net.hostsharing.hsadminng.repr.Stringify.stringify;
|
||||
@SuperBuilder(toBuilder = true)
|
||||
@FieldNameConstants
|
||||
@DisplayAs("Person")
|
||||
public class HsOfficePerson<T extends HsOfficePerson<?> & BaseEntity<?>> implements BaseEntity<T>, Stringifyable {
|
||||
public class HsOfficePerson<T extends HsOfficePerson<?> & BaseEntity<?>> implements BaseEntity<T>, Stringifyable, WithRoleId {
|
||||
|
||||
private static Stringify<HsOfficePerson> toString = stringify(HsOfficePerson.class, "person")
|
||||
.withProp(Fields.personType, HsOfficePerson::getPersonType)
|
||||
|
@ -1,7 +1,7 @@
|
||||
package net.hostsharing.hsadminng.hs.office.person;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficePersonsApi;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePersonInsertResource;
|
||||
@ -24,7 +24,7 @@ public class HsOfficePersonController implements HsOfficePersonsApi {
|
||||
private Context context;
|
||||
|
||||
@Autowired
|
||||
private StandardMapper mapper;
|
||||
private StrictMapper mapper;
|
||||
|
||||
@Autowired
|
||||
private HsOfficePersonRbacRepository personRepo;
|
||||
|
@ -1,20 +1,19 @@
|
||||
package net.hostsharing.hsadminng.hs.office.person;
|
||||
|
||||
import lombok.*;
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import java.io.IOException;
|
||||
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.GLOBAL;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.RbacSubjectReference.UserRole.CREATOR;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.GLOBAL;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.RbacSubjectReference.UserRole.CREATOR;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.rbacViewFor;
|
||||
|
||||
@Entity
|
||||
@Table(schema = "hs_office", name = "person_rv")
|
||||
@ -22,11 +21,10 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@SuperBuilder(toBuilder = true)
|
||||
@FieldNameConstants
|
||||
@DisplayAs("RbacPerson")
|
||||
public class HsOfficePersonRbacEntity extends HsOfficePerson<HsOfficePersonRbacEntity> {
|
||||
|
||||
public static RbacView rbac() {
|
||||
public static RbacSpec rbac() {
|
||||
return rbacViewFor("person", HsOfficePersonRbacEntity.class)
|
||||
.withIdentityView(SQL.projection("concat(tradeName, familyName, givenName)"))
|
||||
.withUpdatableColumns("personType", "title", "salutation", "tradeName", "givenName", "familyName")
|
||||
|
@ -3,7 +3,6 @@ package net.hostsharing.hsadminng.hs.office.person;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||
|
||||
@ -17,7 +16,6 @@ import jakarta.persistence.Table;
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@SuperBuilder(toBuilder = true)
|
||||
@FieldNameConstants
|
||||
@DisplayAs("RealPerson")
|
||||
public class HsOfficePersonRealEntity extends HsOfficePerson<HsOfficePersonRealEntity> {
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ public enum HsOfficePersonType {
|
||||
UNKNOWN_PERSON_TYPE("??"),
|
||||
NATURAL_PERSON("NP"), // a human being
|
||||
LEGAL_PERSON("LP"), // incorporated legal entity like A/S, GmbH, e.K., eG, e.V.
|
||||
ORGANIZATIONAL_UNIT("OU"), // groups of persons within an organization, e.g. "Admin-Team", "Buchhaltung"
|
||||
INCORPORATED_FIRM("IF"), // registered business partnership like OHG, Partnerschaftsgesellschaft
|
||||
UNINCORPORATED_FIRM("UF"), // unregistered partnership, association etc. like GbR, ARGE, community of heirs
|
||||
PUBLIC_INSTITUTION("PI"); // entities under public law like government entities, KdöR, AöR
|
||||
|
@ -6,6 +6,7 @@ import lombok.experimental.SuperBuilder;
|
||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealEntity;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import net.hostsharing.hsadminng.rbac.role.WithRoleId;
|
||||
import net.hostsharing.hsadminng.repr.Stringify;
|
||||
import net.hostsharing.hsadminng.repr.Stringifyable;
|
||||
|
||||
@ -22,7 +23,7 @@ import static net.hostsharing.hsadminng.repr.Stringify.stringify;
|
||||
@Setter
|
||||
@SuperBuilder(toBuilder = true)
|
||||
@FieldNameConstants
|
||||
public class HsOfficeRelation implements BaseEntity<HsOfficeRelation>, Stringifyable {
|
||||
public class HsOfficeRelation implements BaseEntity<HsOfficeRelation>, Stringifyable, WithRoleId {
|
||||
|
||||
private static Stringify<HsOfficeRelation> toString = stringify(HsOfficeRelation.class, "rel")
|
||||
.withProp(Fields.anchor, HsOfficeRelation::getAnchor)
|
||||
|
@ -9,7 +9,7 @@ import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeRelation
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.*;
|
||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealRepository;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@ -32,7 +32,7 @@ public class HsOfficeRelationController implements HsOfficeRelationsApi {
|
||||
private Context context;
|
||||
|
||||
@Autowired
|
||||
private StandardMapper mapper;
|
||||
private StrictMapper mapper;
|
||||
|
||||
@Autowired
|
||||
private HsOfficeRelationRbacRepository rbacRelationRepo;
|
||||
@ -185,6 +185,7 @@ public class HsOfficeRelationController implements HsOfficeRelationsApi {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
final BiConsumer<HsOfficeContactInsertResource, HsOfficeContactRealEntity> CONTACT_RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
|
||||
entity.putPostalAddress(from(resource.getPostalAddress()));
|
||||
entity.putEmailAddresses(from(resource.getEmailAddresses()));
|
||||
entity.putPhoneNumbers(from(resource.getPhoneNumbers()));
|
||||
};
|
||||
|
@ -7,31 +7,31 @@ import lombok.experimental.SuperBuilder;
|
||||
import net.hostsharing.hsadminng.errors.DisplayAs;
|
||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRbacEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRbacEntity;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Table;
|
||||
import java.io.IOException;
|
||||
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.CaseDef.inCaseOf;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.CaseDef.inOtherCases;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Column.dependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.ColumnValue.usingDefaultCase;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.GLOBAL;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Nullable.NOT_NULL;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.DELETE;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.INSERT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.SELECT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.UPDATE;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.RbacSubjectReference.UserRole.CREATOR;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.ADMIN;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.AGENT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.OWNER;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.REFERRER;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.TENANT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.directlyFetchedByDependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.CaseDef.inCaseOf;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.CaseDef.inOtherCases;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Column.dependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.ColumnValue.usingDefaultCase;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.GLOBAL;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Nullable.NOT_NULL;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.DELETE;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.INSERT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.SELECT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.UPDATE;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.RbacSubjectReference.UserRole.CREATOR;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.ADMIN;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.AGENT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.OWNER;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.REFERRER;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.TENANT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL.directlyFetchedByDependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.rbacViewFor;
|
||||
|
||||
@Entity
|
||||
@Table(schema = "hs_office", name = "relation_rv")
|
||||
@ -42,7 +42,7 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
||||
@DisplayAs("RbacRelation")
|
||||
public class HsOfficeRelationRbacEntity extends HsOfficeRelation {
|
||||
|
||||
public static RbacView rbac() {
|
||||
public static RbacSpec rbac() {
|
||||
return rbacViewFor("relation", HsOfficeRelationRbacEntity.class)
|
||||
.withIdentityView(SQL.projection("""
|
||||
(select idName from hs_office.person_iv p where p.uuid = anchorUuid)
|
||||
|
@ -42,22 +42,22 @@ public interface HsOfficeRelationRbacRepository extends Repository<HsOfficeRelat
|
||||
toSqlLikeOperand(mark), toSqlLikeOperand(personData), toSqlLikeOperand(contactData));
|
||||
}
|
||||
|
||||
// TODO: use ELIKE instead of lower(...) LIKE ...? Or use jsonb_path with RegEx like emailAddressRegEx in ContactRepo?
|
||||
// TODO: Or use jsonb_path with RegEx like emailAddressRegEx in ContactRepo?
|
||||
@Query(value = """
|
||||
SELECT rel FROM HsOfficeRelationRbacEntity AS rel
|
||||
WHERE (:relationType IS NULL OR CAST(rel.type AS String) = :relationType)
|
||||
AND ( :personUuid IS NULL
|
||||
OR rel.anchor.uuid = :personUuid OR rel.holder.uuid = :personUuid )
|
||||
AND ( :mark IS NULL OR lower(rel.mark) LIKE :mark )
|
||||
AND ( :mark IS NULL OR rel.mark ILIKE :mark )
|
||||
AND ( :personData IS NULL
|
||||
OR lower(rel.anchor.tradeName) LIKE :personData OR lower(rel.holder.tradeName) LIKE :personData
|
||||
OR lower(rel.anchor.familyName) LIKE :personData OR lower(rel.holder.familyName) LIKE :personData
|
||||
OR lower(rel.anchor.givenName) LIKE :personData OR lower(rel.holder.givenName) LIKE :personData )
|
||||
OR rel.anchor.tradeName ILIKE :personData OR rel.holder.tradeName ILIKE :personData
|
||||
OR rel.anchor.familyName ILIKE :personData OR rel.holder.familyName ILIKE :personData
|
||||
OR rel.anchor.givenName ILIKE :personData OR rel.holder.givenName ILIKE :personData )
|
||||
AND ( :contactData IS NULL
|
||||
OR lower(rel.contact.caption) LIKE :contactData
|
||||
OR lower(CAST(rel.contact.postalAddress AS String)) LIKE :contactData
|
||||
OR lower(CAST(rel.contact.emailAddresses AS String)) LIKE :contactData
|
||||
OR lower(CAST(rel.contact.phoneNumbers AS String)) LIKE :contactData )
|
||||
OR rel.contact.caption ILIKE :contactData
|
||||
OR CAST(rel.contact.postalAddress AS String) ILIKE :contactData
|
||||
OR CAST(rel.contact.emailAddresses AS String) ILIKE :contactData
|
||||
OR CAST(rel.contact.phoneNumbers AS String) ILIKE :contactData )
|
||||
""")
|
||||
@Timed("app.office.relations.repo.findRelationRelatedToPersonUuidRelationByTypeMarkPersonAndContactDataImpl.rbac")
|
||||
List<HsOfficeRelationRbacEntity> findRelationRelatedToPersonUuidRelationByTypeMarkPersonAndContactDataImpl(
|
||||
|
@ -14,10 +14,6 @@ public interface HsOfficeRelationRealRepository extends Repository<HsOfficeRelat
|
||||
@Timed("app.repo.relations.findByUuid.real")
|
||||
Optional<HsOfficeRelationRealEntity> findByUuid(UUID id);
|
||||
|
||||
default List<HsOfficeRelationRealEntity> findRelationRelatedToPersonUuidAndRelationType(@NotNull UUID personUuid, HsOfficeRelationType relationType) {
|
||||
return findRelationRelatedToPersonUuidAndRelationTypeString(personUuid, relationType == null ? null : relationType.toString());
|
||||
}
|
||||
|
||||
@Query(value = """
|
||||
SELECT p.* FROM hs_office.relation AS p
|
||||
WHERE p.anchorUuid = :personUuid OR p.holderUuid = :personUuid
|
||||
@ -25,13 +21,51 @@ public interface HsOfficeRelationRealRepository extends Repository<HsOfficeRelat
|
||||
@Timed("app.repo.relations.findRelationRelatedToPersonUuid.real")
|
||||
List<HsOfficeRelationRealEntity> findRelationRelatedToPersonUuid(@NotNull UUID personUuid);
|
||||
|
||||
/**
|
||||
* Finds relations by a conjunction of optional criteria, including anchorPerson, holderPerson and contact data.
|
||||
* *
|
||||
* @param personUuid the optional UUID of the anchorPerson or holderPerson
|
||||
* @param relationType the type of the relation
|
||||
* @param mark the mark (use '%' for wildcard), case ignored
|
||||
* @param personData a string to match the persons tradeName, familyName or givenName (use '%' for wildcard), case ignored
|
||||
* @param contactData a string to match the contacts caption, postalAddress, emailAddresses or phoneNumbers (use '%' for wildcard), case ignored
|
||||
* @return a list of (accessible) relations which match all given criteria
|
||||
*/
|
||||
default List<HsOfficeRelationRealEntity> findRelationRelatedToPersonUuidRelationTypeMarkPersonAndContactData(
|
||||
final UUID personUuid,
|
||||
final HsOfficeRelationType relationType,
|
||||
final String mark,
|
||||
final String personData,
|
||||
final String contactData) {
|
||||
return findRelationRelatedToPersonUuidRelationByTypeMarkPersonAndContactDataImpl(
|
||||
personUuid, toStringOrNull(relationType),
|
||||
toSqlLikeOperand(mark), toSqlLikeOperand(personData), toSqlLikeOperand(contactData));
|
||||
}
|
||||
|
||||
// TODO: Or use jsonb_path with RegEx like emailAddressRegEx in ContactRepo?
|
||||
@Query(value = """
|
||||
SELECT p.* FROM hs_office.relation AS p
|
||||
WHERE (:relationType IS NULL OR p.type = cast(:relationType AS hs_office.RelationType))
|
||||
AND ( p.anchorUuid = :personUuid OR p.holderUuid = :personUuid)
|
||||
""", nativeQuery = true)
|
||||
@Timed("app.repo.relations.findRelationRelatedToPersonUuidAndRelationTypeString.real")
|
||||
List<HsOfficeRelationRealEntity> findRelationRelatedToPersonUuidAndRelationTypeString(@NotNull UUID personUuid, String relationType);
|
||||
SELECT rel FROM HsOfficeRelationRealEntity AS rel
|
||||
WHERE (:relationType IS NULL OR CAST(rel.type AS String) = :relationType)
|
||||
AND ( :personUuid IS NULL
|
||||
OR rel.anchor.uuid = :personUuid OR rel.holder.uuid = :personUuid )
|
||||
AND ( :mark IS NULL OR rel.mark ILIKE :mark )
|
||||
AND ( :personData IS NULL
|
||||
OR rel.anchor.tradeName ILIKE :personData OR rel.holder.tradeName ILIKE :personData
|
||||
OR rel.anchor.familyName ILIKE :personData OR rel.holder.familyName ILIKE :personData
|
||||
OR rel.anchor.givenName ILIKE :personData OR rel.holder.givenName ILIKE :personData )
|
||||
AND ( :contactData IS NULL
|
||||
OR rel.contact.caption ILIKE :contactData
|
||||
OR CAST(rel.contact.postalAddress AS String) ILIKE :contactData
|
||||
OR CAST(rel.contact.emailAddresses AS String) ILIKE :contactData
|
||||
OR CAST(rel.contact.phoneNumbers AS String) ILIKE :contactData )
|
||||
""")
|
||||
@Timed("app.office.relations.repo.findRelationRelatedToPersonUuidRelationByTypeMarkPersonAndContactDataImpl.real")
|
||||
List<HsOfficeRelationRealEntity> findRelationRelatedToPersonUuidRelationByTypeMarkPersonAndContactDataImpl(
|
||||
final UUID personUuid,
|
||||
final String relationType,
|
||||
final String mark,
|
||||
final String personData,
|
||||
final String contactData);
|
||||
|
||||
@Timed("app.repo.relations.save.real")
|
||||
HsOfficeRelationRealEntity save(final HsOfficeRelationRealEntity entity);
|
||||
@ -41,4 +75,11 @@ public interface HsOfficeRelationRealRepository extends Repository<HsOfficeRelat
|
||||
|
||||
@Timed("app.repo.relations.deleteByUuid.real")
|
||||
int deleteByUuid(UUID uuid);
|
||||
private static String toSqlLikeOperand(final String text) {
|
||||
return text == null ? null : ("%" + text.toLowerCase() + "%");
|
||||
}
|
||||
|
||||
private static String toStringOrNull(final HsOfficeRelationType relationType) {
|
||||
return relationType == null ? null : relationType.name();
|
||||
}
|
||||
}
|
||||
|
@ -2,11 +2,14 @@ package net.hostsharing.hsadminng.hs.office.sepamandate;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountRepository;
|
||||
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorRepository;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeSepaMandatesApi;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebitorResource;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeSepaMandateInsertResource;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeSepaMandatePatchResource;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeSepaMandateResource;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@ -15,6 +18,7 @@ import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBui
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.PersistenceContext;
|
||||
import jakarta.validation.ValidationException;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.function.BiConsumer;
|
||||
@ -29,7 +33,13 @@ public class HsOfficeSepaMandateController implements HsOfficeSepaMandatesApi {
|
||||
private Context context;
|
||||
|
||||
@Autowired
|
||||
private StandardMapper mapper;
|
||||
private StrictMapper mapper;
|
||||
|
||||
@Autowired
|
||||
private HsOfficeDebitorRepository debitorRepo;
|
||||
|
||||
@Autowired
|
||||
private HsOfficeBankAccountRepository bankAccountRepo;
|
||||
|
||||
@Autowired
|
||||
private HsOfficeSepaMandateRepository sepaMandateRepo;
|
||||
@ -137,10 +147,22 @@ public class HsOfficeSepaMandateController implements HsOfficeSepaMandatesApi {
|
||||
if (entity.getValidity().hasUpperBound()) {
|
||||
resource.setValidTo(entity.getValidity().upper().minusDays(1));
|
||||
}
|
||||
resource.setDebitor(mapper.map(entity.getDebitor(), HsOfficeDebitorResource.class));
|
||||
resource.getDebitor().setDebitorNumber(entity.getDebitor().getTaggedDebitorNumber());
|
||||
resource.getDebitor().getPartner().setPartnerNumber(entity.getDebitor().getPartner().getTaggedPartnerNumber());
|
||||
};
|
||||
|
||||
final BiConsumer<HsOfficeSepaMandateInsertResource, HsOfficeSepaMandateEntity> SEPA_MANDATE_RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
|
||||
entity.setValidity(toPostgresDateRange(resource.getValidFrom(), resource.getValidTo()));
|
||||
entity.setDebitor(debitorRepo.findByUuid(resource.getDebitorUuid()).orElseThrow( () ->
|
||||
new ValidationException(
|
||||
"debitor.uuid='" + resource.getDebitorUuid() + "' not found or not accessible"
|
||||
)
|
||||
));
|
||||
entity.setBankAccount(bankAccountRepo.findByUuid(resource.getBankAccountUuid()).orElseThrow( () ->
|
||||
new ValidationException(
|
||||
"bankAccount.uuid='" + resource.getBankAccountUuid() + "' not found or not accessible"
|
||||
)
|
||||
));
|
||||
};
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity
|
||||
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRbacEntity;
|
||||
import net.hostsharing.hsadminng.persistence.BaseEntity;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec;
|
||||
import net.hostsharing.hsadminng.repr.Stringify;
|
||||
import net.hostsharing.hsadminng.repr.Stringifyable;
|
||||
import org.hibernate.annotations.Type;
|
||||
@ -20,16 +20,16 @@ import java.util.UUID;
|
||||
|
||||
import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.DEBITOR;
|
||||
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Column.dependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.ColumnValue.usingCase;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.ColumnValue.usingDefaultCase;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.GLOBAL;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Nullable.NOT_NULL;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.RbacSubjectReference.UserRole.CREATOR;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Column.dependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.ColumnValue.usingCase;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.ColumnValue.usingDefaultCase;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.GLOBAL;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Nullable.NOT_NULL;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.RbacSubjectReference.UserRole.CREATOR;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.rbacViewFor;
|
||||
import static net.hostsharing.hsadminng.repr.Stringify.stringify;
|
||||
|
||||
@Entity
|
||||
@ -100,7 +100,7 @@ public class HsOfficeSepaMandateEntity implements Stringifyable, BaseEntity<HsOf
|
||||
return reference;
|
||||
}
|
||||
|
||||
public static RbacView rbac() {
|
||||
public static RbacSpec rbac() {
|
||||
return rbacViewFor("sepaMandate", HsOfficeSepaMandateEntity.class)
|
||||
.withIdentityView(query("""
|
||||
select sm.uuid as uuid, ba.iban || '-' || sm.validity as idName
|
||||
|
@ -0,0 +1,17 @@
|
||||
package net.hostsharing.hsadminng.hs.validation;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import jakarta.validation.ValidationException;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
|
||||
@UtilityClass
|
||||
public class UuidResolver {
|
||||
|
||||
public static <T> T resolve(final String jsonPath, final UUID uuid, final Function<UUID, Optional<T>> findByUuid) {
|
||||
return findByUuid.apply(uuid)
|
||||
.orElseThrow(() -> new ValidationException("Unable to find " + jsonPath + ": " + uuid));
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
package net.hostsharing.hsadminng.mapper;
|
||||
|
||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* A nicer API for ModelMapper in standard mode.
|
||||
*/
|
||||
@Component
|
||||
public class StandardMapper extends Mapper {
|
||||
|
||||
public StandardMapper(@Autowired final EntityManagerWrapper em) {
|
||||
super(em);
|
||||
getConfiguration().setAmbiguityIgnored(true);
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ package net.hostsharing.hsadminng.persistence;
|
||||
|
||||
import org.hibernate.Hibernate;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface BaseEntity<T extends BaseEntity<?>> {
|
||||
@ -15,4 +16,10 @@ public interface BaseEntity<T extends BaseEntity<?>> {
|
||||
//noinspection unchecked
|
||||
return (T) this;
|
||||
};
|
||||
|
||||
default T reload(final EntityManager em) {
|
||||
em.flush();
|
||||
em.refresh(this);
|
||||
return load();
|
||||
}
|
||||
}
|
||||
|
@ -1,38 +0,0 @@
|
||||
package net.hostsharing.hsadminng.persistence;
|
||||
|
||||
import net.hostsharing.hsadminng.errors.DisplayAs.DisplayName;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.validation.ValidationException;
|
||||
|
||||
@Service
|
||||
public class EntityExistsValidator {
|
||||
|
||||
@Autowired
|
||||
private EntityManagerWrapper em;
|
||||
|
||||
public <T extends BaseEntity<T>> void validateEntityExists(final String property, final T entitySkeleton) {
|
||||
final var foundEntity = em.find(entityClass(entitySkeleton), entitySkeleton.getUuid());
|
||||
if ( foundEntity == null) {
|
||||
throw new ValidationException("Unable to find " + DisplayName.of(entitySkeleton) + " by " + property + ": " + entitySkeleton.getUuid());
|
||||
}
|
||||
}
|
||||
|
||||
private static <T extends BaseEntity<T>> Class<?> entityClass(final T entityOrProxy) {
|
||||
final var entityClass = entityClass(entityOrProxy.getClass());
|
||||
if (entityClass == null) {
|
||||
throw new IllegalArgumentException("@Entity not found in superclass hierarchy of " + entityOrProxy.getClass());
|
||||
}
|
||||
return entityClass;
|
||||
}
|
||||
|
||||
private static Class<?> entityClass(final Class<?> entityOrProxyClass) {
|
||||
return entityOrProxyClass.isAnnotationPresent(Entity.class)
|
||||
? entityOrProxyClass
|
||||
: entityOrProxyClass.getSuperclass() == null
|
||||
? null
|
||||
: entityClass(entityOrProxyClass.getSuperclass());
|
||||
}
|
||||
}
|
@ -1,16 +1,22 @@
|
||||
package net.hostsharing.hsadminng.ping;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
@Controller
|
||||
public class PingController {
|
||||
|
||||
@ResponseBody
|
||||
@RequestMapping(value = "/api/ping", method = RequestMethod.GET)
|
||||
public String ping() {
|
||||
return "pong\n";
|
||||
public String ping(
|
||||
@RequestHeader(name = "current-subject") @NotNull String currentSubject,
|
||||
@RequestHeader(name = "assumed-roles", required = false) String assumedRoles
|
||||
) {
|
||||
return "pong " + currentSubject + "\n";
|
||||
}
|
||||
}
|
||||
|
@ -7,20 +7,20 @@ import java.util.stream.Stream;
|
||||
|
||||
import static java.util.stream.Collectors.joining;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.PostgresTriggerReference.NEW;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.INSERT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.RbacGrantDefinition.GrantType.PERM_TO_ROLE;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.ADMIN;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.GUEST;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.INSERT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.RbacGrantDefinition.GrantType.PERM_TO_ROLE;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.ADMIN;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.GUEST;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.StringWriter.with;
|
||||
import static org.apache.commons.lang3.StringUtils.capitalize;
|
||||
import static org.apache.commons.lang3.StringUtils.uncapitalize;
|
||||
|
||||
public class InsertTriggerGenerator {
|
||||
|
||||
private final RbacView rbacDef;
|
||||
private final RbacSpec rbacDef;
|
||||
private final String liquibaseTagPrefix;
|
||||
|
||||
public InsertTriggerGenerator(final RbacView rbacDef, final String liqibaseTagPrefix) {
|
||||
public InsertTriggerGenerator(final RbacSpec rbacDef, final String liqibaseTagPrefix) {
|
||||
this.rbacDef = rbacDef;
|
||||
this.liquibaseTagPrefix = liqibaseTagPrefix;
|
||||
}
|
||||
@ -203,8 +203,8 @@ public class InsertTriggerGenerator {
|
||||
plPgSql.chopEmptyLines();
|
||||
}
|
||||
|
||||
private void generateInsertPermissionChecksForSingleGrant(final StringWriter plPgSql, final RbacView.RbacGrantDefinition g) {
|
||||
final RbacView.EntityAlias superRoleEntityAlias = g.getSuperRoleDef().getEntityAlias();
|
||||
private void generateInsertPermissionChecksForSingleGrant(final StringWriter plPgSql, final RbacSpec.RbacGrantDefinition g) {
|
||||
final RbacSpec.EntityAlias superRoleEntityAlias = g.getSuperRoleDef().getEntityAlias();
|
||||
|
||||
final var caseCondition = g.isConditional()
|
||||
? ("NEW.type in (" + toStringList(g.getForCases()) + ") and ")
|
||||
@ -275,15 +275,15 @@ public class InsertTriggerGenerator {
|
||||
with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()));
|
||||
}
|
||||
|
||||
private String toStringList(final Set<RbacView.CaseDef> cases) {
|
||||
private String toStringList(final Set<RbacSpec.CaseDef> cases) {
|
||||
return cases.stream().map(c -> "'" + c.value + "'").collect(joining(", "));
|
||||
}
|
||||
|
||||
private boolean isGrantToADifferentTable(final RbacView.RbacGrantDefinition g) {
|
||||
private boolean isGrantToADifferentTable(final RbacSpec.RbacGrantDefinition g) {
|
||||
return !rbacDef.getRootEntityAlias().getRawTableNameWithSchema().equals(g.getSuperRoleDef().getEntityAlias().getRawTableNameWithSchema());
|
||||
}
|
||||
|
||||
private Stream<RbacView.RbacGrantDefinition> getInsertGrants() {
|
||||
private Stream<RbacSpec.RbacGrantDefinition> getInsertGrants() {
|
||||
return rbacDef.getGrantDefs().stream()
|
||||
.filter(g -> g.grantType() == PERM_TO_ROLE)
|
||||
.filter(g -> g.getPermDef().toCreate && g.getPermDef().getPermission() == INSERT);
|
||||
@ -298,14 +298,14 @@ public class InsertTriggerGenerator {
|
||||
g.getSuperRoleDef().getEntityAlias().isGlobal() && g.getSuperRoleDef().getRole() == GUEST);
|
||||
}
|
||||
|
||||
private Optional<RbacView.RbacGrantDefinition> getOptionalInsertGrant() {
|
||||
private Optional<RbacSpec.RbacGrantDefinition> getOptionalInsertGrant() {
|
||||
return getInsertGrants()
|
||||
.reduce(singleton());
|
||||
}
|
||||
|
||||
private Optional<RbacView.RbacRoleDefinition> getOptionalInsertSuperRole() {
|
||||
private Optional<RbacSpec.RbacRoleDefinition> getOptionalInsertSuperRole() {
|
||||
return getInsertGrants()
|
||||
.map(RbacView.RbacGrantDefinition::getSuperRoleDef)
|
||||
.map(RbacSpec.RbacGrantDefinition::getSuperRoleDef)
|
||||
.reduce(singleton());
|
||||
}
|
||||
|
||||
@ -319,12 +319,12 @@ public class InsertTriggerGenerator {
|
||||
};
|
||||
}
|
||||
|
||||
private static String toVar(final RbacView.RbacRoleDefinition roleDef) {
|
||||
private static String toVar(final RbacSpec.RbacRoleDefinition roleDef) {
|
||||
return uncapitalize(roleDef.getEntityAlias().simpleName()) + capitalize(roleDef.getRole().name());
|
||||
}
|
||||
|
||||
|
||||
private String toRoleDescriptor(final RbacView.RbacRoleDefinition roleDef, final String ref) {
|
||||
private String toRoleDescriptor(final RbacSpec.RbacRoleDefinition roleDef, final String ref) {
|
||||
final var functionName = roleDef.descriptorFunctionName();
|
||||
if (roleDef.getEntityAlias().isGlobal()) {
|
||||
return functionName + "()";
|
||||
|
@ -3,12 +3,12 @@ package net.hostsharing.hsadminng.rbac.generator;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.StringWriter.with;
|
||||
|
||||
public class RbacIdentityViewGenerator {
|
||||
private final RbacView rbacDef;
|
||||
private final RbacSpec rbacDef;
|
||||
private final String liquibaseTagPrefix;
|
||||
private final String simpleEntityVarName;
|
||||
private final String rawTableName;
|
||||
|
||||
public RbacIdentityViewGenerator(final RbacView rbacDef, final String liquibaseTagPrefix) {
|
||||
public RbacIdentityViewGenerator(final RbacSpec rbacDef, final String liquibaseTagPrefix) {
|
||||
this.rbacDef = rbacDef;
|
||||
this.liquibaseTagPrefix = liquibaseTagPrefix;
|
||||
this.simpleEntityVarName = rbacDef.getRootEntityAlias().simpleName();
|
||||
|
@ -7,7 +7,7 @@ public class RbacObjectGenerator {
|
||||
private final String liquibaseTagPrefix;
|
||||
private final String rawTableName;
|
||||
|
||||
public RbacObjectGenerator(final RbacView rbacDef, final String liquibaseTagPrefix) {
|
||||
public RbacObjectGenerator(final RbacSpec rbacDef, final String liquibaseTagPrefix) {
|
||||
this.liquibaseTagPrefix = liquibaseTagPrefix;
|
||||
this.rawTableName = rbacDef.getRootEntityAlias().getRawTableNameWithSchema();
|
||||
}
|
||||
|
@ -0,0 +1,80 @@
|
||||
package net.hostsharing.hsadminng.rbac.generator;
|
||||
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.RbacGrantDefinition;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.StringWriter.with;
|
||||
|
||||
class RbacRbacSystemRebuildGenerator {
|
||||
|
||||
private final RbacSpec rbacDef;
|
||||
private final Set<RbacGrantDefinition> rbacGrants = new HashSet<>();
|
||||
private final String liquibaseTagPrefix;
|
||||
private final String rawTableName;
|
||||
|
||||
RbacRbacSystemRebuildGenerator(final RbacSpec rbacDef, final String liquibaseTagPrefix) {
|
||||
this.rbacDef = rbacDef;
|
||||
this.liquibaseTagPrefix = liquibaseTagPrefix;
|
||||
this.rawTableName = rbacDef.getRootEntityAlias().getRawTableNameWithSchema();
|
||||
}
|
||||
|
||||
void generateTo(final StringWriter plPgSql) {
|
||||
plPgSql.writeLn("""
|
||||
-- ============================================================================
|
||||
--changeset RbacRbacSystemRebuildGenerator:${liquibaseTagPrefix}-rbac-rebuild endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
|
||||
-- HOWTO: Rebuild RBAC-system for table ${rawTableName} after changing its RBAC specification.
|
||||
--
|
||||
-- begin transaction;
|
||||
-- call base.defineContext('re-creating RBAC for table ${rawTableName}', null, <<insert executing global admin user here>>);
|
||||
-- call ${rawTableName}_rebuild_rbac_system();
|
||||
-- commit;
|
||||
--
|
||||
-- How it works:
|
||||
-- 1. All grants previously created from the RBAC specification of this table will be deleted.
|
||||
-- These grants are identified by `${rawTableName}.grantedByTriggerOf IS NOT NULL`.
|
||||
-- User-induced grants (`${rawTableName}.grantedByTriggerOf IS NULL`) are NOT deleted.
|
||||
-- 2. New role types will be created, but existing role types which are not specified anymore,
|
||||
-- will NOT be deleted!
|
||||
-- 3. All newly specified grants will be created.
|
||||
--
|
||||
-- IMPORTANT:
|
||||
-- Make sure not to skip any previously defined role-types or you might break indirect grants!
|
||||
-- E.g. If, in an updated version of the RBAC system for a table, you remove the AGENT role type
|
||||
-- and now directly grant the TENANT role to the ADMIN role, all external grants to the AGENT role
|
||||
-- of this table would be in a dead end.
|
||||
|
||||
create or replace procedure ${rawTableName}_rebuild_rbac_system()
|
||||
language plpgsql as $$
|
||||
DECLARE
|
||||
DECLARE
|
||||
row ${rawTableName};
|
||||
grantsAfter numeric;
|
||||
grantsBefore numeric;
|
||||
BEGIN
|
||||
SELECT count(*) INTO grantsBefore FROM rbac.grant;
|
||||
|
||||
FOR row IN SELECT * FROM ${rawTableName} LOOP
|
||||
-- first delete all generated grants for this row from the previously defined RBAC system
|
||||
DELETE FROM rbac.grant g
|
||||
WHERE g.grantedbytriggerof = row.uuid;
|
||||
|
||||
-- then build the grants according to the currently defined RBAC rules
|
||||
CALL ${rawTableName}_build_rbac_system(row);
|
||||
END LOOP;
|
||||
|
||||
select count(*) into grantsAfter from rbac.grant;
|
||||
|
||||
-- print how the total count of grants has changed
|
||||
raise notice 'total grant count before -> after: % -> %', grantsBefore, grantsAfter;
|
||||
END;
|
||||
$$;
|
||||
--//
|
||||
|
||||
""",
|
||||
with("liquibaseTagPrefix", liquibaseTagPrefix),
|
||||
with("rawTableName", rawTableName));
|
||||
}
|
||||
}
|
@ -6,11 +6,11 @@ import static net.hostsharing.hsadminng.rbac.generator.StringWriter.indented;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.StringWriter.with;
|
||||
|
||||
public class RbacRestrictedViewGenerator {
|
||||
private final RbacView rbacDef;
|
||||
private final RbacSpec rbacDef;
|
||||
private final String liquibaseTagPrefix;
|
||||
private final String rawTableName;
|
||||
|
||||
public RbacRestrictedViewGenerator(final RbacView rbacDef, final String liquibaseTagPrefix) {
|
||||
public RbacRestrictedViewGenerator(final RbacSpec rbacDef, final String liquibaseTagPrefix) {
|
||||
this.rbacDef = rbacDef;
|
||||
this.liquibaseTagPrefix = liquibaseTagPrefix;
|
||||
this.rawTableName = rbacDef.getRootEntityAlias().getRawTableNameWithSchema();
|
||||
|
@ -8,7 +8,7 @@ public class RbacRoleDescriptorsGenerator {
|
||||
private final String simpleEntityVarName;
|
||||
private final String rawTableName;
|
||||
|
||||
public RbacRoleDescriptorsGenerator(final RbacView rbacDef, final String liquibaseTagPrefix) {
|
||||
public RbacRoleDescriptorsGenerator(final RbacSpec rbacDef, final String liquibaseTagPrefix) {
|
||||
this.liquibaseTagPrefix = liquibaseTagPrefix;
|
||||
this.simpleEntityVarName = rbacDef.getRootEntityAlias().simpleName();
|
||||
this.rawTableName = rbacDef.getRootEntityAlias().getRawTableNameWithSchema();
|
||||
|
@ -22,19 +22,18 @@ import static java.util.Arrays.asList;
|
||||
import static java.util.Arrays.stream;
|
||||
import static java.util.Collections.max;
|
||||
import static java.util.Optional.ofNullable;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.ColumnValue.usingDefaultCase;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Nullable.NOT_NULL;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.RbacGrantDefinition.GrantType.PERM_TO_ROLE;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.RbacGrantDefinition.GrantType.ROLE_TO_ROLE;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.RbacSubjectReference.UserRole.CREATOR;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.Part.AUTO_FETCH;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.ColumnValue.usingDefaultCase;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Nullable.NOT_NULL;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.RbacGrantDefinition.GrantType.PERM_TO_ROLE;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.RbacGrantDefinition.GrantType.ROLE_TO_ROLE;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.RbacSubjectReference.UserRole.CREATOR;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.SQL.Part.AUTO_FETCH;
|
||||
import static org.apache.commons.collections4.SetUtils.hashSet;
|
||||
import static org.apache.commons.lang3.StringUtils.capitalize;
|
||||
import static org.apache.commons.lang3.StringUtils.uncapitalize;
|
||||
|
||||
@Getter
|
||||
// TODO.refa: rename to RbacDSL
|
||||
public class RbacView {
|
||||
public class RbacSpec {
|
||||
|
||||
public static final String GLOBAL = "rbac.global";
|
||||
public static final String OUTPUT_BASEDIR = "src/main/resources/db/changelog";
|
||||
@ -90,11 +89,11 @@ public class RbacView {
|
||||
* @param <E>
|
||||
* a JPA entity class extending RbacObject
|
||||
*/
|
||||
public static <E extends BaseEntity<?>> RbacView rbacViewFor(final String alias, final Class<E> entityClass) {
|
||||
return new RbacView(alias, entityClass);
|
||||
public static <E extends BaseEntity<?>> RbacSpec rbacViewFor(final String alias, final Class<E> entityClass) {
|
||||
return new RbacSpec(alias, entityClass);
|
||||
}
|
||||
|
||||
RbacView(final String alias, final Class<? extends BaseEntity<?>> entityClass) {
|
||||
RbacSpec(final String alias, final Class<? extends BaseEntity<?>> entityClass) {
|
||||
rootEntityAlias = new EntityAlias(alias, entityClass);
|
||||
entityAliases.put(alias, rootEntityAlias);
|
||||
new RbacSubjectReference(CREATOR);
|
||||
@ -110,7 +109,7 @@ public class RbacView {
|
||||
* @return
|
||||
* the `this` instance itself to allow chained calls.
|
||||
*/
|
||||
public RbacView withUpdatableColumns(final String... columnNames) {
|
||||
public RbacSpec withUpdatableColumns(final String... columnNames) {
|
||||
Collections.addAll(updatableColumns, columnNames);
|
||||
verifyVersionColumnExists();
|
||||
return this;
|
||||
@ -134,7 +133,7 @@ public class RbacView {
|
||||
* @return
|
||||
* the `this` instance itself to allow chained calls.
|
||||
*/
|
||||
public RbacView withIdentityView(final SQL sqlExpression) {
|
||||
public RbacSpec withIdentityView(final SQL sqlExpression) {
|
||||
this.identityViewSqlQuery = sqlExpression;
|
||||
return this;
|
||||
}
|
||||
@ -150,7 +149,7 @@ public class RbacView {
|
||||
* @return
|
||||
* the `this` instance itself to allow chained calls.
|
||||
*/
|
||||
public RbacView withRestrictedViewOrderBy(final SQL orderBySqlExpression) {
|
||||
public RbacSpec withRestrictedViewOrderBy(final SQL orderBySqlExpression) {
|
||||
this.orderBySqlExpression = orderBySqlExpression;
|
||||
return this;
|
||||
}
|
||||
@ -166,7 +165,7 @@ public class RbacView {
|
||||
* @return
|
||||
* the `this` instance itself to allow chained calls.
|
||||
*/
|
||||
public RbacView createRole(final Role role, final Consumer<RbacRoleDefinition> with) {
|
||||
public RbacSpec createRole(final Role role, final Consumer<RbacRoleDefinition> with) {
|
||||
final RbacRoleDefinition newRoleDef = findRbacRole(rootEntityAlias, role).toCreate();
|
||||
with.accept(newRoleDef);
|
||||
previousRoleDef = newRoleDef;
|
||||
@ -182,7 +181,7 @@ public class RbacView {
|
||||
* @return
|
||||
* the `this` instance itself to allow chained calls.
|
||||
*/
|
||||
public RbacView createSubRole(final Role role) {
|
||||
public RbacSpec createSubRole(final Role role) {
|
||||
final RbacRoleDefinition newRoleDef = findRbacRole(rootEntityAlias, role).toCreate();
|
||||
findOrCreateGrantDef(newRoleDef, previousRoleDef).toCreate();
|
||||
previousRoleDef = newRoleDef;
|
||||
@ -202,7 +201,7 @@ public class RbacView {
|
||||
* @return
|
||||
* the `this` instance itself to allow chained calls.
|
||||
*/
|
||||
public RbacView createSubRole(final Role role, final Consumer<RbacRoleDefinition> with) {
|
||||
public RbacSpec createSubRole(final Role role, final Consumer<RbacRoleDefinition> with) {
|
||||
final RbacRoleDefinition newRoleDef = findRbacRole(rootEntityAlias, role).toCreate();
|
||||
findOrCreateGrantDef(newRoleDef, previousRoleDef).toCreate();
|
||||
with.accept(newRoleDef);
|
||||
@ -254,7 +253,7 @@ public class RbacView {
|
||||
.orElseGet(() -> new RbacPermissionDefinition(entityAlias, permission, null, true));
|
||||
}
|
||||
|
||||
public <EC extends BaseEntity> RbacView declarePlaceholderEntityAliases(final String... aliasNames) {
|
||||
public <EC extends BaseEntity> RbacSpec declarePlaceholderEntityAliases(final String... aliasNames) {
|
||||
for (String alias : aliasNames) {
|
||||
entityAliases.put(alias, new EntityAlias(alias));
|
||||
}
|
||||
@ -287,7 +286,7 @@ public class RbacView {
|
||||
* @param <EC>
|
||||
* a JPA entity class extending RbacObject
|
||||
*/
|
||||
public <EC extends BaseEntity<?>> RbacView importRootEntityAliasProxy(
|
||||
public <EC extends BaseEntity<?>> RbacSpec importRootEntityAliasProxy(
|
||||
final String aliasName,
|
||||
final Class<? extends BaseEntity<?>> entityClass,
|
||||
final ColumnValue forCase,
|
||||
@ -312,7 +311,7 @@ public class RbacView {
|
||||
* @param <EC>
|
||||
* a JPA entity class extending RbacObject
|
||||
*/
|
||||
public RbacView importSubEntityAlias(
|
||||
public RbacSpec importSubEntityAlias(
|
||||
final String aliasName, final Class<? extends BaseEntity<?>> entityClass,
|
||||
final SQL fetchSql, final Column dependsOnColum) {
|
||||
importEntityAliasImpl(aliasName, entityClass, usingDefaultCase(), fetchSql, dependsOnColum, true, NOT_NULL);
|
||||
@ -349,7 +348,7 @@ public class RbacView {
|
||||
* @param <EC>
|
||||
* a JPA entity class extending RbacObject
|
||||
*/
|
||||
public RbacView importEntityAlias(
|
||||
public RbacSpec importEntityAlias(
|
||||
final String aliasName, final Class<? extends BaseEntity<?>> entityClass, final ColumnValue usingCase,
|
||||
final Column dependsOnColum, final SQL fetchSql, final Nullable nullable) {
|
||||
importEntityAliasImpl(aliasName, entityClass, usingCase, fetchSql, dependsOnColum, false, nullable);
|
||||
@ -379,12 +378,12 @@ public class RbacView {
|
||||
return entityAlias;
|
||||
}
|
||||
|
||||
private static RbacView rbacDefinition(final Class<? extends BaseEntity> entityClass)
|
||||
private static RbacSpec rbacDefinition(final Class<? extends BaseEntity> entityClass)
|
||||
throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
|
||||
return (RbacView) entityClass.getMethod("rbac").invoke(null);
|
||||
return (RbacSpec) entityClass.getMethod("rbac").invoke(null);
|
||||
}
|
||||
|
||||
private RbacView importAsAlias(final String aliasName, final RbacView importedRbacView, final ColumnValue forCase, final boolean asSubEntity) {
|
||||
private RbacSpec importAsAlias(final String aliasName, final RbacSpec importedRbacView, final ColumnValue forCase, final boolean asSubEntity) {
|
||||
final var mapper = new AliasNameMapper(importedRbacView, aliasName,
|
||||
asSubEntity ? entityAliases.keySet() : null);
|
||||
copyOf(importedRbacView.getEntityAliases().values()).stream()
|
||||
@ -416,7 +415,7 @@ public class RbacView {
|
||||
return this;
|
||||
}
|
||||
|
||||
public RbacView switchOnColumn(final String discriminatorColumName, final CaseDef... caseDefs) {
|
||||
public RbacSpec switchOnColumn(final String discriminatorColumName, final CaseDef... caseDefs) {
|
||||
this.discriminatorColumName = discriminatorColumName;
|
||||
allCases.addAll(stream(caseDefs).toList());
|
||||
|
||||
@ -511,7 +510,7 @@ public class RbacView {
|
||||
new RbacViewPostgresGenerator(this).generateToChangeLog(Path.of(OUTPUT_BASEDIR, baseFileName + ".sql"));
|
||||
}
|
||||
|
||||
public RbacView limitDiagramTo(final String... aliasNames) {
|
||||
public RbacSpec limitDiagramTo(final String... aliasNames) {
|
||||
this.limitDiagramToAliasNames = Set.of(aliasNames);
|
||||
return this;
|
||||
}
|
||||
@ -542,15 +541,15 @@ public class RbacView {
|
||||
this.superRoleDef = findRbacRole(entityAlias, role);
|
||||
}
|
||||
|
||||
public RbacView grantRole(final String entityAlias, final Role role) {
|
||||
public RbacSpec grantRole(final String entityAlias, final Role role) {
|
||||
findOrCreateGrantDef(findRbacRole(entityAlias, role), superRoleDef).toCreate();
|
||||
return RbacView.this;
|
||||
return RbacSpec.this;
|
||||
}
|
||||
|
||||
public RbacView grantPermission(final Permission perm) {
|
||||
public RbacSpec grantPermission(final Permission perm) {
|
||||
final var forTable = rootEntityAlias.getRawTableNameWithSchema();
|
||||
findOrCreateGrantDef(findRbacPerm(rootEntityAlias, perm, forTable), superRoleDef).toCreate();
|
||||
return RbacView.this;
|
||||
return RbacSpec.this;
|
||||
}
|
||||
|
||||
}
|
||||
@ -698,10 +697,10 @@ public class RbacView {
|
||||
this.subRole = role;
|
||||
}
|
||||
|
||||
public RbacView wouldBeGrantedTo(final String entityAlias, final Role role) {
|
||||
public RbacSpec wouldBeGrantedTo(final String entityAlias, final Role role) {
|
||||
this.superRoleEntity = findEntityAlias(entityAlias);
|
||||
this.superRole = role;
|
||||
return RbacView.this;
|
||||
return RbacSpec.this;
|
||||
}
|
||||
}
|
||||
|
||||
@ -733,9 +732,9 @@ public class RbacView {
|
||||
* @return
|
||||
* The RbacView specification to which this permission definition belongs.
|
||||
*/
|
||||
public RbacView grantedTo(final String entityAlias, final Role role) {
|
||||
public RbacSpec grantedTo(final String entityAlias, final Role role) {
|
||||
findOrCreateGrantDef(this, findRbacRole(entityAlias, role)).toCreate();
|
||||
return RbacView.this;
|
||||
return RbacSpec.this;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1186,12 +1185,12 @@ public class RbacView {
|
||||
|
||||
private static class AliasNameMapper {
|
||||
|
||||
private final RbacView importedRbacView;
|
||||
private final RbacSpec importedRbacView;
|
||||
private final String outerAliasName;
|
||||
|
||||
private final Set<String> outerAliasNames;
|
||||
|
||||
AliasNameMapper(final RbacView importedRbacView, final String outerAliasName, final Set<String> outerAliasNames) {
|
||||
AliasNameMapper(final RbacSpec importedRbacView, final String outerAliasName, final Set<String> outerAliasNames) {
|
||||
this.importedRbacView = importedRbacView;
|
||||
this.outerAliasName = outerAliasName;
|
||||
this.outerAliasNames = (outerAliasNames == null) ? Collections.emptySet() : outerAliasNames;
|
||||
@ -1210,19 +1209,19 @@ public class RbacView {
|
||||
|
||||
public static class CaseDef extends ColumnValue {
|
||||
|
||||
final Consumer<RbacView> def;
|
||||
final Consumer<RbacSpec> def;
|
||||
|
||||
private CaseDef(final String discriminatorColumnValue, final Consumer<RbacView> def) {
|
||||
private CaseDef(final String discriminatorColumnValue, final Consumer<RbacSpec> def) {
|
||||
super(discriminatorColumnValue);
|
||||
this.def = def;
|
||||
}
|
||||
|
||||
|
||||
public static CaseDef inCaseOf(final String discriminatorColumnValue, final Consumer<RbacView> def) {
|
||||
public static CaseDef inCaseOf(final String discriminatorColumnValue, final Consumer<RbacSpec> def) {
|
||||
return new CaseDef(discriminatorColumnValue, def);
|
||||
}
|
||||
|
||||
public static CaseDef inOtherCases(final Consumer<RbacView> def) {
|
||||
public static CaseDef inOtherCases(final Consumer<RbacSpec> def) {
|
||||
return new CaseDef(null, def);
|
||||
}
|
||||
|
||||
@ -1281,7 +1280,7 @@ public class RbacView {
|
||||
.filter(c -> stream(c.getDeclaredMethods())
|
||||
.anyMatch(m -> m.getName().equals("rbac") && isStatic(m.getModifiers()))
|
||||
)
|
||||
.map(RbacView::castToSubclassOfBaseEntity)
|
||||
.map(RbacSpec::castToSubclassOfBaseEntity)
|
||||
.collect(Collectors.toSet());
|
||||
return rbacEntityClasses;
|
||||
}
|
||||
@ -1296,6 +1295,6 @@ public class RbacView {
|
||||
*/
|
||||
public static void main(String[] args) throws Exception {
|
||||
findRbacEntityClasses("net.hostsharing.hsadminng")
|
||||
.forEach(RbacView::generateRbacView);
|
||||
.forEach(RbacSpec::generateRbacView);
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package net.hostsharing.hsadminng.rbac.generator;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.CaseDef;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.CaseDef;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.nio.file.*;
|
||||
@ -12,7 +12,7 @@ import java.util.stream.Stream;
|
||||
|
||||
import static java.util.Comparator.comparing;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.RbacGrantDefinition.GrantType.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.RbacGrantDefinition.GrantType.*;
|
||||
|
||||
public class RbacViewMermaidFlowchartGenerator {
|
||||
|
||||
@ -20,14 +20,14 @@ public class RbacViewMermaidFlowchartGenerator {
|
||||
public static final String HOSTSHARING_LIGHT_ORANGE = "#feb28c";
|
||||
public static final String HOSTSHARING_DARK_BLUE = "#274d6e";
|
||||
public static final String HOSTSHARING_LIGHT_BLUE = "#99bcdb";
|
||||
private final RbacView rbacDef;
|
||||
private final RbacSpec rbacDef;
|
||||
|
||||
private final List<RbacView.EntityAlias> usedEntityAliases;
|
||||
private final List<RbacSpec.EntityAlias> usedEntityAliases;
|
||||
|
||||
private final CaseDef forCase;
|
||||
private final StringWriter flowchart = new StringWriter();
|
||||
|
||||
public RbacViewMermaidFlowchartGenerator(final RbacView rbacDef, final CaseDef forCase) {
|
||||
public RbacViewMermaidFlowchartGenerator(final RbacSpec rbacDef, final CaseDef forCase) {
|
||||
this.rbacDef = rbacDef;
|
||||
this.forCase = forCase;
|
||||
|
||||
@ -37,7 +37,7 @@ public class RbacViewMermaidFlowchartGenerator {
|
||||
g.getSubRoleDef() != null ? g.getSubRoleDef().getEntityAlias() : null,
|
||||
g.getPermDef() != null ? g.getPermDef().getEntityAlias() : null))
|
||||
.filter(Objects::nonNull)
|
||||
.sorted(comparing(RbacView.EntityAlias::aliasName))
|
||||
.sorted(comparing(RbacSpec.EntityAlias::aliasName))
|
||||
.distinct()
|
||||
.filter(rbacDef::renderInDiagram)
|
||||
.collect(Collectors.toList());
|
||||
@ -50,7 +50,7 @@ public class RbacViewMermaidFlowchartGenerator {
|
||||
renderGrants();
|
||||
}
|
||||
|
||||
public RbacViewMermaidFlowchartGenerator(final RbacView rbacDef) {
|
||||
public RbacViewMermaidFlowchartGenerator(final RbacSpec rbacDef) {
|
||||
this(rbacDef, null);
|
||||
}
|
||||
private void renderEntitySubgraphs() {
|
||||
@ -61,7 +61,7 @@ public class RbacViewMermaidFlowchartGenerator {
|
||||
.forEach(this::renderEntitySubgraph);
|
||||
}
|
||||
|
||||
private void renderEntitySubgraph(final RbacView.EntityAlias entity) {
|
||||
private void renderEntitySubgraph(final RbacSpec.EntityAlias entity) {
|
||||
if (!rbacDef.renderInDiagram(entity)) {
|
||||
return;
|
||||
}
|
||||
@ -128,7 +128,7 @@ public class RbacViewMermaidFlowchartGenerator {
|
||||
renderGrants(PERM_TO_ROLE, "%% granting permissions to roles");
|
||||
}
|
||||
|
||||
private void renderGrants(final RbacView.RbacGrantDefinition.GrantType grantType, final String comment) {
|
||||
private void renderGrants(final RbacSpec.RbacGrantDefinition.GrantType grantType, final String comment) {
|
||||
final var grantsOfRequestedType = rbacDef.getGrantDefs().stream()
|
||||
.filter(g -> g.grantType() == grantType)
|
||||
.filter(rbacDef::renderInDiagram)
|
||||
@ -141,7 +141,7 @@ public class RbacViewMermaidFlowchartGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isToBeRenderedForThisCase(final RbacView.RbacGrantDefinition g) {
|
||||
private boolean isToBeRenderedForThisCase(final RbacSpec.RbacGrantDefinition g) {
|
||||
if ( g.grantType() == ROLE_TO_USER )
|
||||
return true;
|
||||
if ( forCase == null && !g.isConditional() )
|
||||
@ -150,7 +150,7 @@ public class RbacViewMermaidFlowchartGenerator {
|
||||
return isToBeRenderedInThisGraph;
|
||||
}
|
||||
|
||||
private String grantDef(final RbacView.RbacGrantDefinition grant) {
|
||||
private String grantDef(final RbacSpec.RbacGrantDefinition grant) {
|
||||
final var arrow = (grant.isToCreate() ? " ==>" : " -.->")
|
||||
+ (grant.isAssumed() ? " " : "|XX| ");
|
||||
final var grantDef = switch (grant.grantType()) {
|
||||
@ -164,19 +164,19 @@ public class RbacViewMermaidFlowchartGenerator {
|
||||
return grantDef;
|
||||
}
|
||||
|
||||
private String permDef(final RbacView.RbacPermissionDefinition perm) {
|
||||
private String permDef(final RbacSpec.RbacPermissionDefinition perm) {
|
||||
return permId(perm) + "{{" + perm.getEntityAlias().aliasName() + perm.getPermission() + "}}";
|
||||
}
|
||||
|
||||
private static String permId(final RbacView.RbacPermissionDefinition permDef) {
|
||||
private static String permId(final RbacSpec.RbacPermissionDefinition permDef) {
|
||||
return "perm:" + permDef.getEntityAlias().aliasName() + permDef.getPermission();
|
||||
}
|
||||
|
||||
private String roleDef(final RbacView.RbacRoleDefinition roleDef) {
|
||||
private String roleDef(final RbacSpec.RbacRoleDefinition roleDef) {
|
||||
return roleId(roleDef) + "[[" + roleDef.getEntityAlias().aliasName() + roleDef.getRole() + "]]";
|
||||
}
|
||||
|
||||
private static String roleId(final RbacView.RbacRoleDefinition r) {
|
||||
private static String roleId(final RbacSpec.RbacRoleDefinition r) {
|
||||
return "role:" + r.getEntityAlias().aliasName() + r.getRole();
|
||||
}
|
||||
|
||||
|
@ -11,11 +11,11 @@ import static net.hostsharing.hsadminng.rbac.generator.StringWriter.with;
|
||||
|
||||
public class RbacViewPostgresGenerator {
|
||||
|
||||
private final RbacView rbacDef;
|
||||
private final RbacSpec rbacDef;
|
||||
private final String liqibaseTagPrefix;
|
||||
private final StringWriter plPgSql = new StringWriter();
|
||||
|
||||
public RbacViewPostgresGenerator(final RbacView forRbacDef) {
|
||||
public RbacViewPostgresGenerator(final RbacSpec forRbacDef) {
|
||||
rbacDef = forRbacDef;
|
||||
liqibaseTagPrefix = rbacDef.getRootEntityAlias().getRawTableNameWithSchema().replace("_", "-").replace(".", "-");
|
||||
plPgSql.writeLn("""
|
||||
@ -31,6 +31,7 @@ public class RbacViewPostgresGenerator {
|
||||
new InsertTriggerGenerator(rbacDef, liqibaseTagPrefix).generateTo(plPgSql);
|
||||
new RbacIdentityViewGenerator(rbacDef, liqibaseTagPrefix).generateTo(plPgSql);
|
||||
new RbacRestrictedViewGenerator(rbacDef, liqibaseTagPrefix).generateTo(plPgSql);
|
||||
new RbacRbacSystemRebuildGenerator(rbacDef, liqibaseTagPrefix).generateTo(plPgSql);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,8 +1,8 @@
|
||||
package net.hostsharing.hsadminng.rbac.generator;
|
||||
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.CaseDef;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.RbacGrantDefinition;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.RbacPermissionDefinition;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.CaseDef;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.RbacGrantDefinition;
|
||||
import net.hostsharing.hsadminng.rbac.generator.RbacSpec.RbacPermissionDefinition;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@ -15,22 +15,22 @@ import static java.util.stream.Collectors.joining;
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.PostgresTriggerReference.NEW;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.PostgresTriggerReference.OLD;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.INSERT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.RbacGrantDefinition.GrantType.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Permission.INSERT;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.RbacGrantDefinition.GrantType.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.RbacSpec.Role.*;
|
||||
import static net.hostsharing.hsadminng.rbac.generator.StringWriter.with;
|
||||
import static org.apache.commons.lang3.StringUtils.capitalize;
|
||||
|
||||
class RolesGrantsAndPermissionsGenerator {
|
||||
|
||||
private final RbacView rbacDef;
|
||||
private final RbacSpec rbacDef;
|
||||
private final Set<RbacGrantDefinition> rbacGrants = new HashSet<>();
|
||||
private final String liquibaseTagPrefix;
|
||||
private final String simpleEntityName;
|
||||
private final String simpleEntityVarName;
|
||||
private final String qualifiedRawTableName;
|
||||
|
||||
RolesGrantsAndPermissionsGenerator(final RbacView rbacDef, final String liquibaseTagPrefix) {
|
||||
RolesGrantsAndPermissionsGenerator(final RbacSpec rbacDef, final String liquibaseTagPrefix) {
|
||||
this.rbacDef = rbacDef;
|
||||
this.rbacGrants.addAll(rbacDef.getGrantDefs().stream()
|
||||
.filter(RbacGrantDefinition::isToCreate)
|
||||
@ -95,7 +95,7 @@ class RolesGrantsAndPermissionsGenerator {
|
||||
private void generateSimplifiedUpdateTriggerFunction(final StringWriter plPgSql) {
|
||||
|
||||
final var updateConditions = updatableEntityAliases()
|
||||
.map(RbacView.EntityAlias::dependsOnColumName)
|
||||
.map(RbacSpec.EntityAlias::dependsOnColumName)
|
||||
.distinct()
|
||||
.map(columnName -> "NEW." + columnName + " is distinct from OLD." + columnName)
|
||||
.collect(joining( "\n or "));
|
||||
@ -112,7 +112,7 @@ class RolesGrantsAndPermissionsGenerator {
|
||||
begin
|
||||
|
||||
if ${updateConditions} then
|
||||
delete from rbac.grants g where g.grantedbytriggerof = OLD.uuid;
|
||||
delete from rbac.grant g where g.grantedbytriggerof = OLD.uuid;
|
||||
call ${rawTableQualifiedName}_build_rbac_system(NEW);
|
||||
end if;
|
||||
end; $$;
|
||||
@ -166,7 +166,7 @@ class RolesGrantsAndPermissionsGenerator {
|
||||
|
||||
private boolean hasAnyUpdatableAndNullableEntityAliases() {
|
||||
return updatableEntityAliases()
|
||||
.filter(ea -> ea.nullable() == RbacView.Nullable.NULLABLE)
|
||||
.filter(ea -> ea.nullable() == RbacSpec.Nullable.NULLABLE)
|
||||
.anyMatch(e -> true);
|
||||
}
|
||||
|
||||
@ -210,7 +210,7 @@ class RolesGrantsAndPermissionsGenerator {
|
||||
generateGrants(plPgSql, PERM_TO_ROLE);
|
||||
}
|
||||
|
||||
private Stream<RbacView.EntityAlias> referencedEntityAliases() {
|
||||
private Stream<RbacSpec.EntityAlias> referencedEntityAliases() {
|
||||
return rbacDef.getEntityAliases().values().stream()
|
||||
.filter(ea -> !rbacDef.isRootEntityAlias(ea))
|
||||
.filter(ea -> ea.dependsOnColum() != null)
|
||||
@ -218,7 +218,7 @@ class RolesGrantsAndPermissionsGenerator {
|
||||
.filter(ea -> ea.fetchSql() != null);
|
||||
}
|
||||
|
||||
private Stream<RbacView.EntityAlias> updatableEntityAliases() {
|
||||
private Stream<RbacSpec.EntityAlias> updatableEntityAliases() {
|
||||
return referencedEntityAliases()
|
||||
.filter(ea -> rbacDef.getUpdatableColumns().contains(ea.dependsOnColum().column));
|
||||
}
|
||||
@ -234,7 +234,7 @@ class RolesGrantsAndPermissionsGenerator {
|
||||
});
|
||||
|
||||
updatableEntityAliases()
|
||||
.map(RbacView.EntityAlias::dependsOnColum)
|
||||
.map(RbacSpec.EntityAlias::dependsOnColum)
|
||||
.map(c -> c.column)
|
||||
.sorted()
|
||||
.distinct()
|
||||
@ -250,18 +250,19 @@ class RolesGrantsAndPermissionsGenerator {
|
||||
|
||||
private void generateFetchedVars(
|
||||
final StringWriter plPgSql,
|
||||
final RbacView.EntityAlias ea,
|
||||
final RbacSpec.EntityAlias ea,
|
||||
final PostgresTriggerReference old) {
|
||||
plPgSql.writeLn(
|
||||
ea.fetchSql().sql + " INTO " + entityRefVar(old, ea) + ";",
|
||||
with("columns", ea.aliasName() + ".*"),
|
||||
with("ref", old.name()));
|
||||
if (ea.nullable() == RbacView.Nullable.NOT_NULL) {
|
||||
if (ea.nullable() == RbacSpec.Nullable.NOT_NULL) {
|
||||
plPgSql.writeLn(
|
||||
"assert ${entityRefVar}.uuid is not null, format('${entityRefVar} must not be null for ${REF}.${dependsOnColumn} = %s', ${REF}.${dependsOnColumn});",
|
||||
"assert ${entityRefVar}.uuid is not null, format('${entityRefVar} must not be null for ${REF}.${dependsOnColumn} = %s of ${rawTable}', ${REF}.${dependsOnColumn});",
|
||||
with("entityRefVar", entityRefVar(old, ea)),
|
||||
with("dependsOnColumn", ea.dependsOnColumName()),
|
||||
with("ref", old.name()));
|
||||
with("ref", old.name()),
|
||||
with("rawTable", qualifiedRawTableName));
|
||||
plPgSql.writeLn();
|
||||
}
|
||||
}
|
||||
@ -352,11 +353,11 @@ class RolesGrantsAndPermissionsGenerator {
|
||||
.replace("${perm}", permDef.permission.name());
|
||||
}
|
||||
|
||||
private String refVarName(final PostgresTriggerReference ref, final RbacView.EntityAlias entityAlias) {
|
||||
private String refVarName(final PostgresTriggerReference ref, final RbacSpec.EntityAlias entityAlias) {
|
||||
return ref.name().toLowerCase() + capitalize(entityAlias.aliasName());
|
||||
}
|
||||
|
||||
private String roleRef(final PostgresTriggerReference rootRefVar, final RbacView.RbacRoleDefinition roleDef) {
|
||||
private String roleRef(final PostgresTriggerReference rootRefVar, final RbacSpec.RbacRoleDefinition roleDef) {
|
||||
if (roleDef == null) {
|
||||
System.out.println("null");
|
||||
}
|
||||
@ -369,17 +370,17 @@ class RolesGrantsAndPermissionsGenerator {
|
||||
|
||||
private String entityRefVar(
|
||||
final PostgresTriggerReference rootRefVar,
|
||||
final RbacView.EntityAlias entityAlias) {
|
||||
final RbacSpec.EntityAlias entityAlias) {
|
||||
return rbacDef.isRootEntityAlias(entityAlias)
|
||||
? rootRefVar.name()
|
||||
: rootRefVar.name().toLowerCase() + capitalize(entityAlias.aliasName());
|
||||
}
|
||||
|
||||
private void createRolesWithGrantsSql(final StringWriter plPgSql, final RbacView.Role role) {
|
||||
private void createRolesWithGrantsSql(final StringWriter plPgSql, final RbacSpec.Role role) {
|
||||
|
||||
final var isToCreate = rbacDef.getRoleDefs().stream()
|
||||
.filter(roleDef -> rbacDef.isRootEntityAlias(roleDef.getEntityAlias()) && roleDef.getRole() == role)
|
||||
.findFirst().map(RbacView.RbacRoleDefinition::isToCreate).orElse(false);
|
||||
.findFirst().map(RbacSpec.RbacRoleDefinition::isToCreate).orElse(false);
|
||||
if (!isToCreate) {
|
||||
return;
|
||||
}
|
||||
@ -403,7 +404,7 @@ class RolesGrantsAndPermissionsGenerator {
|
||||
plPgSql.writeLn(");");
|
||||
}
|
||||
|
||||
private void generateUserGrantsForRole(final StringWriter plPgSql, final RbacView.Role role) {
|
||||
private void generateUserGrantsForRole(final StringWriter plPgSql, final RbacSpec.Role role) {
|
||||
final var grantsToUsers = findGrantsToUserForRole(rbacDef.getRootEntityAlias(), role);
|
||||
if (!grantsToUsers.isEmpty()) {
|
||||
final var arrayElements = grantsToUsers.stream()
|
||||
@ -416,13 +417,13 @@ class RolesGrantsAndPermissionsGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
private void generatePermissionsForRole(final StringWriter plPgSql, final RbacView.Role role) {
|
||||
private void generatePermissionsForRole(final StringWriter plPgSql, final RbacSpec.Role role) {
|
||||
final var permissionGrantsForRole = findPermissionsGrantsForRole(rbacDef.getRootEntityAlias(), role);
|
||||
if (!permissionGrantsForRole.isEmpty()) {
|
||||
final var arrayElements = permissionGrantsForRole.stream()
|
||||
.map(RbacGrantDefinition::getPermDef)
|
||||
.map(RbacPermissionDefinition::getPermission)
|
||||
.map(RbacView.Permission::name)
|
||||
.map(RbacSpec.Permission::name)
|
||||
.map(p -> "'" + p + "'")
|
||||
.sorted()
|
||||
.toList();
|
||||
@ -432,7 +433,7 @@ class RolesGrantsAndPermissionsGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
private void generateIncomingSuperRolesForRole(final StringWriter plPgSql, final RbacView.Role role) {
|
||||
private void generateIncomingSuperRolesForRole(final StringWriter plPgSql, final RbacSpec.Role role) {
|
||||
final var unconditionalIncomingGrants = findIncomingSuperRolesForRole(rbacDef.getRootEntityAlias(), role).stream()
|
||||
.filter(g -> !g.isConditional())
|
||||
.toList();
|
||||
@ -446,7 +447,7 @@ class RolesGrantsAndPermissionsGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
private void generateOutgoingSubRolesForRole(final StringWriter plPgSql, final RbacView.Role role) {
|
||||
private void generateOutgoingSubRolesForRole(final StringWriter plPgSql, final RbacSpec.Role role) {
|
||||
final var unconditionalOutgoingGrants = findOutgoingSuperRolesForRole(rbacDef.getRootEntityAlias(), role).stream()
|
||||
.filter(g -> !g.isConditional())
|
||||
.toList();
|
||||
@ -467,8 +468,8 @@ class RolesGrantsAndPermissionsGenerator {
|
||||
}
|
||||
|
||||
private Set<RbacGrantDefinition> findPermissionsGrantsForRole(
|
||||
final RbacView.EntityAlias entityAlias,
|
||||
final RbacView.Role role) {
|
||||
final RbacSpec.EntityAlias entityAlias,
|
||||
final RbacSpec.Role role) {
|
||||
final var roleDef = rbacDef.findRbacRole(entityAlias, role);
|
||||
return rbacGrants.stream()
|
||||
.filter(g -> g.grantType() == PERM_TO_ROLE && g.getSuperRoleDef() == roleDef)
|
||||
@ -476,8 +477,8 @@ class RolesGrantsAndPermissionsGenerator {
|
||||
}
|
||||
|
||||
private Set<RbacGrantDefinition> findGrantsToUserForRole(
|
||||
final RbacView.EntityAlias entityAlias,
|
||||
final RbacView.Role role) {
|
||||
final RbacSpec.EntityAlias entityAlias,
|
||||
final RbacSpec.Role role) {
|
||||
final var roleDef = rbacDef.findRbacRole(entityAlias, role);
|
||||
return rbacGrants.stream()
|
||||
.filter(g -> g.grantType() == ROLE_TO_USER && g.getSubRoleDef() == roleDef)
|
||||
@ -485,8 +486,8 @@ class RolesGrantsAndPermissionsGenerator {
|
||||
}
|
||||
|
||||
private Set<RbacGrantDefinition> findIncomingSuperRolesForRole(
|
||||
final RbacView.EntityAlias entityAlias,
|
||||
final RbacView.Role role) {
|
||||
final RbacSpec.EntityAlias entityAlias,
|
||||
final RbacSpec.Role role) {
|
||||
final var roleDef = rbacDef.findRbacRole(entityAlias, role);
|
||||
return rbacGrants.stream()
|
||||
.filter(g -> g.grantType() == ROLE_TO_ROLE && g.getSubRoleDef() == roleDef)
|
||||
@ -494,8 +495,8 @@ class RolesGrantsAndPermissionsGenerator {
|
||||
}
|
||||
|
||||
private Set<RbacGrantDefinition> findOutgoingSuperRolesForRole(
|
||||
final RbacView.EntityAlias entityAlias,
|
||||
final RbacView.Role role) {
|
||||
final RbacSpec.EntityAlias entityAlias,
|
||||
final RbacSpec.Role role) {
|
||||
final var roleDef = rbacDef.findRbacRole(entityAlias, role);
|
||||
return rbacGrants.stream()
|
||||
.filter(g -> g.grantType() == ROLE_TO_ROLE && g.getSuperRoleDef() == roleDef)
|
||||
@ -579,7 +580,7 @@ class RolesGrantsAndPermissionsGenerator {
|
||||
plPgSql.writeLn();
|
||||
}
|
||||
|
||||
private String toPlPgSqlReference(final RbacView.RbacSubjectReference userRef) {
|
||||
private String toPlPgSqlReference(final RbacSpec.RbacSubjectReference userRef) {
|
||||
return switch (userRef.role) {
|
||||
case CREATOR -> "rbac.currentSubjectUuid()";
|
||||
default -> throw new IllegalArgumentException("unknown user role: " + userRef);
|
||||
@ -588,7 +589,7 @@ class RolesGrantsAndPermissionsGenerator {
|
||||
|
||||
private String toPlPgSqlReference(
|
||||
final PostgresTriggerReference triggerRef,
|
||||
final RbacView.RbacRoleDefinition roleDef,
|
||||
final RbacSpec.RbacRoleDefinition roleDef,
|
||||
final boolean assumed) {
|
||||
final var assumedArg = assumed ? "" : ", rbac.unassumed()";
|
||||
return roleDef.descriptorFunctionName() +
|
||||
@ -599,7 +600,7 @@ class RolesGrantsAndPermissionsGenerator {
|
||||
|
||||
private static String toTriggerReference(
|
||||
final PostgresTriggerReference triggerRef,
|
||||
final RbacView.EntityAlias entityAlias) {
|
||||
final RbacSpec.EntityAlias entityAlias) {
|
||||
return triggerRef.name().toLowerCase() + capitalize(entityAlias.aliasName());
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
@Table(schema = "rbac", name = "grants_ev")
|
||||
@Table(schema = "rbac", name = "grant_ev")
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
|
@ -2,7 +2,7 @@ package net.hostsharing.hsadminng.rbac.grant;
|
||||
|
||||
import io.micrometer.core.annotation.Timed;
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||
import net.hostsharing.hsadminng.rbac.generated.api.v1.api.RbacGrantsApi;
|
||||
import net.hostsharing.hsadminng.rbac.generated.api.v1.model.RbacGrantResource;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@ -23,7 +23,7 @@ public class RbacGrantController implements RbacGrantsApi {
|
||||
private Context context;
|
||||
|
||||
@Autowired
|
||||
private StandardMapper mapper;
|
||||
private StrictMapper mapper;
|
||||
|
||||
@Autowired
|
||||
private RbacGrantRepository rbacGrantRepository;
|
||||
|
@ -8,7 +8,7 @@ import jakarta.persistence.*;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
@Table(schema = "rbac", name = "grants_rv")
|
||||
@Table(schema = "rbac", name = "grant_rv")
|
||||
@IdClass(RbacGrantId.class)
|
||||
@Getter
|
||||
@Setter
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user