Compare commits
279 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1fdd957e9e | ||
|
|
18de1e52f5 | ||
|
|
00ef5f67a1 | ||
|
|
74b289b38c | ||
|
|
100b1c4cc1 | ||
|
|
f96adb2097 | ||
|
|
4e43e4e255 | ||
|
|
7bb15fe456 | ||
|
|
a169fb585c | ||
|
|
af38bb2f29 | ||
|
|
f25253cb3b | ||
|
|
ad7035d7cf | ||
|
|
dfa4009b84 | ||
|
|
4eefde9ff6 | ||
|
|
45f6386104 | ||
|
|
db495d17b3 | ||
|
|
e0a1df09ab | ||
|
|
93e3315a8a | ||
|
|
5e3d632846 | ||
|
|
6412b50ff9 | ||
|
|
4e4a8d7089 | ||
|
|
e2f84d9bbf | ||
|
|
09a86f0a23 | ||
|
|
8adeb37275 | ||
|
|
6968f94e6e | ||
|
|
01731bb7a4 | ||
|
|
47ffe83b8f | ||
|
|
b3757876a7 | ||
|
|
dddf2ba3d1 | ||
|
|
38bbb71965 | ||
|
|
014e82e9b3 | ||
|
|
f5f08b215e | ||
|
|
a540524d52 | ||
|
|
8cba143c76 | ||
|
|
5e0a363aff | ||
|
|
26525d4baa | ||
|
|
c6ea2430bb | ||
|
|
f88dd799b0 | ||
|
|
f4555e1737 | ||
|
|
50e6178060 | ||
|
|
25b2db376b | ||
|
|
0cba921ce2 | ||
|
|
ce8cfff69e | ||
|
|
2c2f3464e9 | ||
|
|
eaa70cffb1 | ||
|
|
6bbe3316ac | ||
|
|
3f94e66eda | ||
|
|
114fbfc62f | ||
|
|
d3e575159f | ||
|
|
5e7dcb1689 | ||
|
|
f9668a3c07 | ||
|
|
aa9fe087f5 | ||
|
|
310339a5d9 | ||
|
|
23c3b6787e | ||
|
|
ae21409577 | ||
|
|
ae04a6fd74 | ||
|
|
d5b2cb2af2 | ||
|
|
e71712450d | ||
|
|
f0b484895a | ||
|
|
c303d46d8e | ||
|
|
8ed4e8660f | ||
|
|
119676e143 | ||
|
|
7b135dc48b | ||
|
|
0692ac8309 | ||
|
|
bacb43c9db | ||
|
|
c2b14d176c | ||
|
|
c7098c98ac | ||
|
|
ed86367ab8 | ||
|
|
8399251f44 | ||
|
|
039b0fad13 | ||
|
|
537dc185a3 | ||
|
|
258bf63cd0 | ||
|
|
1149a0809e | ||
|
|
534ace6a55 | ||
|
|
1c7698d2de | ||
|
|
a2cd372d51 | ||
|
|
628eda60b9 | ||
|
|
534af6d061 | ||
|
|
50bfc103b6 | ||
|
|
b9121b839b | ||
|
|
53e49f083d | ||
|
|
20c0890ad0 | ||
|
|
a689835af6 | ||
|
|
cd159879f5 | ||
|
|
14d9bfcb3b | ||
|
|
9bdd2bb09a | ||
|
|
e3e01c8dbd | ||
|
|
5bda827c21 | ||
|
|
e821371806 | ||
|
|
828c8a840c | ||
|
|
8a7b861ceb | ||
|
|
112d38d7be | ||
|
|
2f49ee0908 | ||
|
|
6f70d1e333 | ||
|
|
6dc5dc6142 | ||
|
|
087431678a | ||
|
|
1107f91691 | ||
|
|
99641a6688 | ||
|
|
12e8c0f53f | ||
|
|
715d33b9e1 | ||
|
|
f98394c136 | ||
|
|
0ae6b2a77b | ||
|
|
b35386684e | ||
|
|
af37030c68 | ||
|
|
6954a5cee3 | ||
|
|
7bec8f98e6 | ||
|
|
5af289479e | ||
|
|
af16b4db01 | ||
|
|
68bc112660 | ||
|
|
b3dd62e3ca | ||
|
|
ac1b86fa41 | ||
|
|
abb11a4893 | ||
|
|
9241a191fa | ||
|
|
1e0fbd8e1d | ||
|
|
5c0679f1fc | ||
|
|
1272d91e74 | ||
|
|
72def10e4a | ||
|
|
2d34c1999d | ||
|
|
f0f888a63d | ||
|
|
7f220bd348 | ||
|
|
86e676cc51 | ||
|
|
a20484e486 | ||
|
|
d151831ad9 | ||
|
|
e2c74b607b | ||
|
|
a54d08bcb0 | ||
|
|
2fc8c9fc65 | ||
|
|
631fe2bc1f | ||
|
|
cb88dca0ea | ||
|
|
c0b98a1bef | ||
|
|
dc2a11ac83 | ||
|
|
b5a76fd483 | ||
|
|
fa71cb9633 | ||
|
|
8f8b6787e9 | ||
|
|
6493c75de4 | ||
|
|
a6c97df229 | ||
|
|
84a0c47ae6 | ||
|
|
d0b6081000 | ||
|
|
f4b874c5a4 | ||
|
|
2648d49c81 | ||
|
|
5583f6613d | ||
|
|
4eb7e1709e | ||
|
|
b43cc69633 | ||
|
|
6712f25890 | ||
|
|
9838dcf10a | ||
|
|
44a404f2da | ||
|
|
2d31712295 | ||
|
|
2feffb9b80 | ||
|
|
4a2203893c | ||
|
|
7f45a73bce | ||
|
|
95778f7926 | ||
|
|
d74d4217f8 | ||
|
|
0722b50584 | ||
|
|
51d1039866 | ||
|
|
819730d970 | ||
|
|
64cecc4a7d | ||
|
|
96d03f756d | ||
|
|
8783dc91e7 | ||
|
|
10a834e7f8 | ||
|
|
08ed0f29c0 | ||
|
|
93a1b1fcda | ||
|
|
6edbd910c4 | ||
|
|
d39ca21c4b | ||
|
|
1f3c862497 | ||
|
|
79a465aa40 | ||
|
|
d655ea958b | ||
|
|
8a41d70701 | ||
|
|
980c59de78 | ||
|
|
7462b38f9f | ||
|
|
755a6f0798 | ||
|
|
0c66bacb49 | ||
|
|
12fd15390d | ||
|
|
d935a0488e | ||
|
|
55f380a602 | ||
|
|
b0bf8359b9 | ||
|
|
b57a12ce79 | ||
|
|
7ae7dc10db | ||
|
|
7dc03d7781 | ||
|
|
6d24d00a67 | ||
|
|
4bd7e8d09b | ||
|
|
d55ef70442 | ||
|
|
d2e52263fb | ||
|
|
921197a665 | ||
|
|
2692a7b1a9 | ||
|
|
9a94879960 | ||
|
|
6242258e09 | ||
|
|
eebbc2d00d | ||
|
|
362a86916a | ||
|
|
5dcf0dbef0 | ||
|
|
f6916dab3e | ||
|
|
442b12edf9 | ||
|
|
f6a0860ac1 | ||
|
|
c458b8e178 | ||
|
|
3bd9b72b3a | ||
|
|
90c159f52d | ||
|
|
b97bd1d9df | ||
|
|
6435f1814b | ||
|
|
0cd586552c | ||
|
|
8ce5b9bc30 | ||
|
|
c68e4033a3 | ||
|
|
59bd73c980 | ||
|
|
18d982f6c2 | ||
|
|
736e4fd874 | ||
|
|
7559ab64ef | ||
|
|
7cf39a0b68 | ||
|
|
381b30198b | ||
|
|
d48263e189 | ||
|
|
7a57283868 | ||
|
|
68ec498bbe | ||
|
|
bbe6307a9b | ||
|
|
2edecc9915 | ||
|
|
78a455db44 | ||
|
|
1508c8fad4 | ||
|
|
6fbfbb17ce | ||
|
|
979e9069fa | ||
|
|
57aa00e096 | ||
|
|
9ace6e25a5 | ||
|
|
5478bd443e | ||
|
|
87afed5aab | ||
|
|
47d8089a36 | ||
|
|
96fbaf7ec5 | ||
|
|
d23fbc54f8 | ||
|
|
518cc6139f | ||
|
|
9a316d1c9f | ||
|
|
55c82c88a6 | ||
|
|
76235935df | ||
|
|
8a47cd9103 | ||
|
|
30a045364d | ||
|
|
acd8f4369b | ||
|
|
02f8689769 | ||
|
|
1c8a8e6302 | ||
|
|
c2bd9a34f4 | ||
|
|
c2b7a69a08 | ||
|
|
e012af658d | ||
|
|
81dbcb98ed | ||
|
|
e88e76771c | ||
|
|
171922c1a4 | ||
|
|
fc025ae440 | ||
|
|
5869719c15 | ||
|
|
3b29517363 | ||
|
|
dda4a94274 | ||
|
|
d40dd2bd57 | ||
|
|
ab44dae367 | ||
|
|
184031e760 | ||
|
|
76aa088152 | ||
|
|
ba0cbae9c4 | ||
|
|
3f7311b272 | ||
|
|
53fec52f86 | ||
|
|
e5c7dae219 | ||
|
|
cf0f623bd6 | ||
|
|
6bb8ee01b7 | ||
|
|
9d9aa6f46d | ||
|
|
400ced9b39 | ||
|
|
cb8b7a27c6 | ||
|
|
1003f4a4a1 | ||
|
|
e6314a220a | ||
|
|
32d12d4155 | ||
|
|
e66082013b | ||
|
|
8bcd42990e | ||
|
|
92ce09c49d | ||
|
|
e8fe27d6df | ||
|
|
60d603e028 | ||
|
|
2c9a7e06c1 | ||
|
|
d91ae1f261 | ||
|
|
d441a8c5a8 | ||
|
|
8c6d55fe6f | ||
|
|
b2bc62694c | ||
|
|
fc889b04a1 | ||
|
|
e6150888ab | ||
|
|
fefcca2ddf | ||
|
|
3318143bb4 | ||
|
|
f377d1a3ab | ||
|
|
d7f0899c3b | ||
|
|
98c6f27190 | ||
|
|
c0b897112a | ||
|
|
d74dab8fef | ||
|
|
791b6b76da | ||
|
|
2d10b2b5c6 | ||
|
|
c904e5947c | ||
|
|
a79165bb09 |
40
.dev/migration/README.md
Normal file
40
.dev/migration/README.md
Normal file
@ -0,0 +1,40 @@
|
||||
### Hot to run locally and test
|
||||
|
||||
1. Start docker compose from this dir
|
||||
2. Start persistence-service with profile: dev
|
||||
* this will create the tenant sync exchange
|
||||
* startup might fail due to tenant-usermanagement not being up. but you need the tenant sync queue first)
|
||||
|
||||
3. Start tenant-user-management with profiles: dev, redaction, migration
|
||||
* this will migrate the master DB and sync KC
|
||||
* it will also send a message to persistence service to run the migration
|
||||
* in case the message doesn't arrive - restart persistence and/or this service - the tenant migration in this service is idempotent
|
||||
|
||||
4. Persistence service should now queue 1869 files to layout-parser
|
||||
5. Start layout-parser with profile: dev
|
||||
6. Start redaction-service with profile: dev
|
||||
7. Debug issues & repeat
|
||||
8. UI is available at htpp://localhost:4200/ui -> login with manageradmin/OsloImWinter!23
|
||||
9. To start clean. docker compose down and up again.
|
||||
|
||||
Useful info:
|
||||
* This docker compose contains a dump from staging from ~2 weeks ago
|
||||
* KC credentials are: admin / secretPasswordForAdmin1234
|
||||
* To use different data you need a tenant-manager database dump from an environment of your choice
|
||||
* this can be obtained via kubectl port forward like so: `kubectl -n <namespace> port-forward services/main-db-postgresql 5432`
|
||||
* followed by a dump: `pg_dumpall -U tenantmanager -h 127.0.0.1 > main.sql`
|
||||
* And a tenant database:
|
||||
* this can be obtained via kubectl port forward like so: `kubectl -n <namespace> port-forward services/tenant-db-postgresql 5432`
|
||||
* followed by a dump: `pg_dumpall -U tenantmanager -h 127.0.0.1 > tenant.sql`
|
||||
* And a KC database:
|
||||
* this can be obtained via kubectl port forward like so: `kubectl -n <namespace> port-forward services/keycloak-postgresql 5432`
|
||||
* followed by a dump: `pg_dumpall -U tenantmanager -h 127.0.0.1 > tenant.sql`
|
||||
* You can now use the migration/utils/database Dockerfile to create a fat DB image with the data ( replace data.sql with your dump and tag accordingly)
|
||||
* For a minio dump you need a similar command ( port fwd sometimes crashes sine clone command uses a lot of connections ):
|
||||
* open port forward `while true; do kubectl -n redaction-staging port-forward statefulset/redaction-staging-minio 9000 --request-timeout=10m; done`
|
||||
* Install mc minio command line utility
|
||||
* create an alias `mc alias set <mirror_name> http://localhost:9000 minioadminuser minioadminpassword`
|
||||
* run: `while true; do mc mirror <mirror_name>/<bucket_name> ./<bucket_name>; done`
|
||||
* use migration/utils/minio/Dockerfile to create an image -> you need to copy it to where the export is.
|
||||
* After you have the images, replace the images in compose with the ones you want
|
||||
|
||||
86
.dev/migration/docker-compose.yaml
Normal file
86
.dev/migration/docker-compose.yaml
Normal file
@ -0,0 +1,86 @@
|
||||
version: '3.9'
|
||||
|
||||
services:
|
||||
tenant-database:
|
||||
pull_policy: always
|
||||
image: nexus.knecon.com:5001/migration/redtenant-db-staging-multi-arch
|
||||
ports:
|
||||
- 15432:5432
|
||||
environment:
|
||||
- POSTGRES_PASSWORD=r3dact3d
|
||||
- POSTGRES_USER=tenant
|
||||
- POSTGRES_DB=red-tenant
|
||||
main-database:
|
||||
image: nexus.knecon.com:5001/migration/tenantmanager-db-staging-multi-arch
|
||||
pull_policy: always
|
||||
ports:
|
||||
- 25432:5432
|
||||
environment:
|
||||
- POSTGRES_PASSWORD=r3dact3d
|
||||
- POSTGRES_USER=tenantmanager
|
||||
- POSTGRES_DB=tenantmanager
|
||||
keycloak-database:
|
||||
image: nexus.knecon.com:5001/migration/keycloak-db-multi-arch
|
||||
pull_policy: always
|
||||
ports:
|
||||
- 35432:5432
|
||||
environment:
|
||||
- POSTGRES_PASSWORD=some-password
|
||||
- POSTGRES_USER=bn_keycloak
|
||||
- POSTGRES_DB=bitnami_keycloak
|
||||
keycloak:
|
||||
command: ['start']
|
||||
volumes:
|
||||
- /tmp/export:/opt/export
|
||||
depends_on:
|
||||
- "keycloak-database"
|
||||
image: quay.io/keycloak/keycloak:20.0.1
|
||||
environment:
|
||||
JAVA_OPTS_APPEND: -Dkeycloak.profile.feature.upload_scripts=enabled
|
||||
KC_HOSTNAME: localhost
|
||||
KC_HTTP_ENABLED: true
|
||||
KC_HOSTNAME_STRICT_HTTPS: false
|
||||
KC_DB: postgres
|
||||
KC_DB_URL: jdbc:postgresql://keycloak-database:5432/bitnami_keycloak
|
||||
KC_DB_USERNAME: bn_keycloak
|
||||
KC_DB_PASSWORD: some-password
|
||||
ports:
|
||||
- "8080:8080"
|
||||
redis:
|
||||
image: redis
|
||||
ports:
|
||||
- "6379:6379"
|
||||
rabbitmq:
|
||||
image: 'rabbitmq:3.9-alpine'
|
||||
environment:
|
||||
- RABBITMQ_DEFAULT_USER=user
|
||||
- RABBITMQ_DEFAULT_PASS=rabbitmq
|
||||
ports:
|
||||
- 5672:5672
|
||||
- 15672:15672
|
||||
minio:
|
||||
pull_policy: always
|
||||
image: nexus.knecon.com:5001/migration/minio-staging-multi-arch
|
||||
ports:
|
||||
- "9001:9001"
|
||||
- "9000:9000"
|
||||
adminer:
|
||||
image: adminer:latest
|
||||
ports:
|
||||
- "58080:8080"
|
||||
ui:
|
||||
pull_policy: always
|
||||
image: nexus.knecon.com:5001/migration/test-ui-csp
|
||||
environment:
|
||||
API_URL: http://localhost:4200
|
||||
APP_NAME: Local
|
||||
FRONTEND_APP_VERSION: 42
|
||||
OAUTH_URL: http://localhost:8080
|
||||
OAUTH_CLIENT_ID: redaction
|
||||
BASE_TRANSLATIONS_DIRECTORY: /assets/i18n/redact/
|
||||
THEME: redact
|
||||
ports:
|
||||
- "4200:8080"
|
||||
# pg_dump bitnami_keycloak -U bn_keycloak -h 127.0.0.1 -p 35432 > data.sql
|
||||
# pg_dump tenantmanager -U tenantmanager -h 127.0.0.1 -p 25432 > data.sql
|
||||
# pg_dump red-tenant -U tenant -h 127.0.0.1 -p 15432 > data.sql
|
||||
2
.dev/migration/utils/database/Dockerfile
Normal file
2
.dev/migration/utils/database/Dockerfile
Normal file
@ -0,0 +1,2 @@
|
||||
FROM postgres:16.0
|
||||
COPY data.sql /docker-entrypoint-initdb.d/
|
||||
0
.dev/migration/utils/database/data.sql
Normal file
0
.dev/migration/utils/database/data.sql
Normal file
16
.dev/migration/utils/minio/Dockerfile
Normal file
16
.dev/migration/utils/minio/Dockerfile
Normal file
@ -0,0 +1,16 @@
|
||||
FROM docker.io/minio/minio:latest
|
||||
|
||||
COPY ./redaction /tmp/redaction
|
||||
COPY --from=docker.io/minio/mc:latest /usr/bin/mc /usr/bin/mc
|
||||
RUN mkdir /buckets
|
||||
RUN minio server /buckets & \
|
||||
server_pid=$!; \
|
||||
until mc alias set local http://localhost:9000 minioadmin minioadmin; do \
|
||||
sleep 1; \
|
||||
done; \
|
||||
mc mb local/redaction; \
|
||||
mc mirror /tmp/redaction local/redaction; \
|
||||
kill $server_pid
|
||||
RUN rm -Rf /tmp/redaction
|
||||
|
||||
CMD ["minio", "server", "/buckets", "--address", ":9000", "--console-address", ":9001"]
|
||||
@ -2,7 +2,7 @@ version: '2'
|
||||
|
||||
services:
|
||||
keycloak:
|
||||
image: quay.io/keycloak/keycloak:20.0
|
||||
image: quay.io/keycloak/keycloak:latest
|
||||
command: start-dev
|
||||
environment:
|
||||
KEYCLOAK_ADMIN: admin
|
||||
@ -24,7 +24,7 @@ services:
|
||||
POSTGRES_PASSWORD: fforesight
|
||||
POSTGRES_DB: master
|
||||
rabbitmq:
|
||||
image: 'rabbitmq:3.9-alpine'
|
||||
image: 'rabbitmq:3.9-management-alpine'
|
||||
mem_limit: 500m
|
||||
environment:
|
||||
- RABBITMQ_DEFAULT_USER=user
|
||||
|
||||
21
README.md
Normal file
21
README.md
Normal file
@ -0,0 +1,21 @@
|
||||
# Tenant User Management Micro-Service: tenant-user-management
|
||||
|
||||
## Introduction
|
||||
The tenant-user-management micro-service profiles with storing and providing everything about the tenant environment. It makes use of tools like Spring Boot 3, Keycloak, Microsoft Azure and AWS S3 to keep your data consistent and secure according to the latest standards.
|
||||
|
||||
### Key features in the Tenant User Management Service
|
||||
|
||||
* **Tenant Management:**
|
||||
This service gives you the option to do CRUD operations to manage your tenants.
|
||||
You can also select between different storage platforms to store your tenants.
|
||||
Some general configurations complements the settings options.
|
||||
|
||||
|
||||
* **User Administration:**
|
||||
Another feature is the handling of your users. You can manage the profiles, activate/deactivate them and give roles as you require.
|
||||
If you wish you can administrate the users for each tenant separately.
|
||||
Or just give a user the needed preferences.
|
||||
|
||||
|
||||
* **SMTP configuration:**
|
||||
Further you can adjust your SMTP settings and set properties as you aimed best for your company.
|
||||
@ -2,15 +2,20 @@ import org.springframework.boot.gradle.tasks.bundling.BootBuildImage
|
||||
|
||||
plugins {
|
||||
java
|
||||
id("org.springframework.boot") version "3.0.6"
|
||||
id("org.springframework.boot") version "3.1.5"
|
||||
id("io.spring.dependency-management") version "1.1.0"
|
||||
id("org.sonarqube") version "4.0.0.2929"
|
||||
id("io.freefair.lombok") version "8.4"
|
||||
pmd
|
||||
`maven-publish`
|
||||
checkstyle
|
||||
jacoco
|
||||
}
|
||||
|
||||
pmd {
|
||||
isConsoleOutput = true
|
||||
}
|
||||
|
||||
configurations {
|
||||
compileOnly {
|
||||
extendsFrom(configurations.annotationProcessor.get())
|
||||
@ -34,8 +39,8 @@ publishing {
|
||||
maven {
|
||||
url = uri("https://nexus.knecon.com/repository/red-platform-releases/")
|
||||
credentials {
|
||||
username = providers.gradleProperty("mavenUser").getOrNull();
|
||||
password = providers.gradleProperty("mavenPassword").getOrNull();
|
||||
username = providers.gradleProperty("mavenUser").getOrNull()
|
||||
password = providers.gradleProperty("mavenPassword").getOrNull()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -47,10 +52,10 @@ repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
maven {
|
||||
url = uri("https://nexus.knecon.com/repository/gindev/");
|
||||
url = uri("https://nexus.knecon.com/repository/gindev/")
|
||||
credentials {
|
||||
username = providers.gradleProperty("mavenUser").getOrNull();
|
||||
password = providers.gradleProperty("mavenPassword").getOrNull();
|
||||
username = providers.gradleProperty("mavenUser").getOrNull()
|
||||
password = providers.gradleProperty("mavenPassword").getOrNull()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -81,13 +86,30 @@ tasks.named<BootBuildImage>("bootBuildImage") {
|
||||
}
|
||||
|
||||
|
||||
configurations {
|
||||
all {
|
||||
exclude(group = "commons-logging", module = "commons-logging")
|
||||
exclude(group = "org.springframework.boot", module = "spring-boot-starter-log4j2")
|
||||
exclude(group = "com.iqser.red.commons", module = "logging-commons")
|
||||
}
|
||||
}
|
||||
|
||||
val persistenceServiceVersion = "2.589.1-RED10196.2"
|
||||
|
||||
|
||||
dependencies {
|
||||
implementation("com.knecon.fforesight:keycloak-commons:0.18.0")
|
||||
implementation("com.knecon.fforesight:swagger-commons:0.5.0")
|
||||
implementation("com.iqser.red.service:persistence-service-internal-api-v1:${persistenceServiceVersion}")
|
||||
implementation("com.knecon.fforesight:database-tenant-commons:0.28.0-RED10196.0")
|
||||
implementation("com.knecon.fforesight:keycloak-commons:0.28.0")
|
||||
implementation("com.knecon.fforesight:swagger-commons:0.7.0")
|
||||
implementation("com.knecon.fforesight:tracing-commons:0.5.0")
|
||||
implementation("com.knecon.fforesight:lifecycle-commons:0.6.0")
|
||||
implementation("net.logstash.logback:logstash-logback-encoder:7.4")
|
||||
implementation("ch.qos.logback:logback-classic")
|
||||
implementation("org.postgresql:postgresql:42.5.4")
|
||||
implementation("com.google.guava:guava:31.1-jre")
|
||||
implementation("org.liquibase:liquibase-core:4.17.2")
|
||||
implementation("org.keycloak:keycloak-admin-client:21.0.1")
|
||||
implementation("com.google.guava:guava:33.0.0-jre")
|
||||
implementation("org.liquibase:liquibase-core:4.20.0")
|
||||
implementation("org.keycloak:keycloak-admin-client:23.0.6")
|
||||
implementation("org.springframework.boot:spring-boot-starter-amqp")
|
||||
implementation("org.springframework.boot:spring-boot-starter-validation")
|
||||
implementation("org.springframework.retry:spring-retry")
|
||||
@ -96,29 +118,27 @@ dependencies {
|
||||
implementation("org.springframework.boot:spring-boot-starter-security")
|
||||
implementation("org.springframework.boot:spring-boot-starter-web")
|
||||
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
|
||||
implementation("org.springframework.boot:spring-boot-starter-data-mongodb")
|
||||
implementation("org.apache.commons:commons-lang3:3.12.0")
|
||||
implementation("commons-validator:commons-validator:1.7")
|
||||
implementation("commons-validator:commons-validator:1.8.0")
|
||||
implementation("org.springframework.boot:spring-boot-configuration-processor")
|
||||
implementation("com.iqser.red.commons:storage-commons:2.22.0")
|
||||
implementation("com.iqser.red.commons:storage-commons:2.45.0")
|
||||
implementation("jakarta.mail:jakarta.mail-api:2.1.2")
|
||||
implementation("org.eclipse.angus:angus-mail:2.0.2")
|
||||
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
||||
testImplementation("org.springframework.cloud:spring-cloud-starter-openfeign")
|
||||
testImplementation("org.projectlombok:lombok")
|
||||
compileOnly("org.projectlombok:lombok")
|
||||
developmentOnly("org.springframework.boot:spring-boot-devtools")
|
||||
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
|
||||
annotationProcessor("org.projectlombok:lombok")
|
||||
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
||||
testImplementation("org.springframework.amqp:spring-rabbit-test")
|
||||
testImplementation("org.testcontainers:postgresql:1.18.3")
|
||||
testImplementation("com.github.dasniko:testcontainers-keycloak:2.5.0")
|
||||
testImplementation("org.testcontainers:testcontainers:1.18.3")
|
||||
testImplementation("org.testcontainers:junit-jupiter:1.18.3")
|
||||
testAnnotationProcessor("org.projectlombok:lombok")
|
||||
|
||||
testImplementation("org.testcontainers:postgresql:1.19.7")
|
||||
testImplementation("org.testcontainers:testcontainers:1.19.7")
|
||||
testImplementation("org.testcontainers:junit-jupiter:1.19.7")
|
||||
testImplementation("com.github.dasniko:testcontainers-keycloak:3.2.0")
|
||||
}
|
||||
|
||||
extra["springCloudVersion"] = "2022.0.2"
|
||||
extra["testcontainersVersion"] = "1.17.6"
|
||||
extra["springCloudVersion"] = "2022.0.4"
|
||||
extra["testcontainersVersion"] = "1.19.7"
|
||||
|
||||
|
||||
group = "com.knecon.fforesight"
|
||||
|
||||
@ -1,15 +1,19 @@
|
||||
<?xml version="1.0"?>
|
||||
<ruleset name="Custom Rules"
|
||||
<ruleset name="Custom ruleset"
|
||||
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
|
||||
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd">
|
||||
|
||||
<description>Knecon main pmd rules</description>
|
||||
<description>
|
||||
Knecon ruleset checks the code for bad stuff
|
||||
</description>
|
||||
|
||||
<rule ref="category/java/errorprone.xml">
|
||||
<exclude name="DataflowAnomalyAnalysis"/>
|
||||
<exclude name="MissingSerialVersionUID"/>
|
||||
<exclude name="AvoidDuplicateLiterals"/>
|
||||
<exclude name="AvoidLiteralsInIfCondition"/>
|
||||
<exclude name="AvoidDuplicateLiterals"/>
|
||||
<exclude name="NullAssignment"/>
|
||||
<exclude name="AssignmentInOperand"/>
|
||||
</rule>
|
||||
|
||||
</ruleset>
|
||||
|
||||
@ -1,16 +1,22 @@
|
||||
<?xml version="1.0"?>
|
||||
<ruleset name="Custom Rules"
|
||||
<ruleset name="Custom ruleset"
|
||||
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
|
||||
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd">
|
||||
|
||||
<description>
|
||||
Knecon test ruleset checks the code for bad stuff
|
||||
</description>
|
||||
|
||||
<description>Knecon test pmd rules</description>
|
||||
|
||||
<rule ref="category/java/errorprone.xml">
|
||||
<exclude name="DataflowAnomalyAnalysis"/>
|
||||
<exclude name="MissingSerialVersionUID"/>
|
||||
<exclude name="BeanMembersShouldSerialize"/>
|
||||
<exclude name="AvoidDuplicateLiterals"/>
|
||||
<exclude name="AvoidLiteralsInIfCondition"/>
|
||||
<exclude name="AvoidDuplicateLiterals"/>
|
||||
<exclude name="NullAssignment"/>
|
||||
<exclude name="AssignmentInOperand"/>
|
||||
<exclude name="TestClassWithoutTestCases"/>
|
||||
<exclude name="BeanMembersShouldSerialize"/>
|
||||
</rule>
|
||||
|
||||
</ruleset>
|
||||
|
||||
8
publish-custom-image.sh
Executable file
8
publish-custom-image.sh
Executable file
@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
dir=${PWD##*/}
|
||||
gradle assemble
|
||||
|
||||
buildNumber=${1:-1}
|
||||
|
||||
gradle bootBuildImage --cleanCache --publishImage -PbuildbootDockerHostNetwork=true -Pversion=$USER-$buildNumber
|
||||
echo "nexus.knecon.com:5001/ff/${dir}:$USER-$buildNumber"
|
||||
@ -4,18 +4,26 @@ import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||
|
||||
import com.knecon.fforesight.keycloakcommons.DefaultKeyCloakCommonsAutoConfiguration;
|
||||
import com.knecon.fforesight.lifecyclecommons.LifecycleAutoconfiguration;
|
||||
import com.knecon.fforesight.swaggercommons.SpringDocAutoConfiguration;
|
||||
import com.knecon.fforesight.tenantcommons.MultiTenancyAutoConfiguration;
|
||||
import com.knecon.fforesight.tenantusermanagement.client.LicenseClient;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@ImportAutoConfiguration({MultiTenancyAutoConfiguration.class, LiquibaseAutoConfiguration.class, DefaultKeyCloakCommonsAutoConfiguration.class, SpringDocAutoConfiguration.class})
|
||||
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class, CassandraAutoConfiguration.class,})
|
||||
@ImportAutoConfiguration({MultiTenancyAutoConfiguration.class, LiquibaseAutoConfiguration.class, DefaultKeyCloakCommonsAutoConfiguration.class, SpringDocAutoConfiguration.class, LifecycleAutoconfiguration.class})
|
||||
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class, CassandraAutoConfiguration.class, MongoAutoConfiguration.class, MongoDataAutoConfiguration.class})
|
||||
@EnableAspectJAutoProxy
|
||||
@EnableFeignClients(basePackageClasses = LicenseClient.class)
|
||||
public class TenantUserManagementServiceApplication {
|
||||
|
||||
/**
|
||||
|
||||
@ -5,7 +5,6 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
||||
@ -23,15 +22,15 @@ public interface GeneralSettingsResource {
|
||||
@ResponseBody
|
||||
@ResponseStatus(value = HttpStatus.OK)
|
||||
@GetMapping(value = API_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Returns the current general Configuration.")
|
||||
@Operation(summary = "Returns the current general configuration")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK.")})
|
||||
GeneralConfigurationModel getGeneralConfigurations();
|
||||
|
||||
|
||||
@ResponseStatus(value = HttpStatus.NO_CONTENT)
|
||||
@PostMapping(value = API_PATH, consumes = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Write General Configurations to KeyCloak")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "General Configuration updated successful."), @ApiResponse(responseCode = "400", description = "General Configuration update failed.")})
|
||||
@Operation(summary = "Write general configuration to KeyCloak")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "General configuration updated successful."), @ApiResponse(responseCode = "400", description = "General configuration update failed.")})
|
||||
void updateGeneralConfigurations(@RequestBody GeneralConfigurationModel generalConfigurationModel);
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,86 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.api.external;
|
||||
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
||||
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderList;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderModel;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderWithDescriptorRequest;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
@Tag(name = "Identity provider configuration endpoints", description = "Provides operations related to the identity providers")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "429", description = "Too many requests."), @ApiResponse(responseCode = "403", description = "Forbidden")})
|
||||
public interface IdentityProviderConfigurationResource {
|
||||
|
||||
String API_PATH = "/configuration/identity-providers";
|
||||
String IDENTITY_PROVIDER_ALIAS_PARAM = "providerAlias";
|
||||
String IDENTITY_PROVIDER_ALIAS_PATH_PARAM = "/{" + IDENTITY_PROVIDER_ALIAS_PARAM + "}";
|
||||
String IMPORT_SUB_PATH = "/import";
|
||||
|
||||
|
||||
@ResponseStatus(value = HttpStatus.OK)
|
||||
@ResponseBody
|
||||
@GetMapping(value = API_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Gets all existing identity providers.", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
|
||||
IdentityProviderList getIdentityProviders();
|
||||
|
||||
|
||||
@ResponseStatus(value = HttpStatus.OK)
|
||||
@ResponseBody
|
||||
@GetMapping(value = API_PATH + IDENTITY_PROVIDER_ALIAS_PATH_PARAM, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Gets an existing identity provider.", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "Not found")})
|
||||
IdentityProviderModel getIdentityProvider(@Parameter(name = IDENTITY_PROVIDER_ALIAS_PARAM, description = "The alias of the identity provider to retrieve.", required = true) @PathVariable(IDENTITY_PROVIDER_ALIAS_PARAM) String identityProviderAlias);
|
||||
|
||||
|
||||
|
||||
@ResponseStatus(value = HttpStatus.CREATED)
|
||||
@ResponseBody
|
||||
@PostMapping(value = API_PATH, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Creates a new identity provider", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "201", description = "Successfully created the identity provider"), @ApiResponse(responseCode = "400", description = "Malformed request parameters or body"), @ApiResponse(responseCode = "409", description = "Duplicate")})
|
||||
ResponseEntity<IdentityProviderModel> createIdentityProvider(@Valid @io.swagger.v3.oas.annotations.parameters.RequestBody @RequestBody IdentityProviderRequest identityProvider);
|
||||
|
||||
|
||||
@ResponseStatus(value = HttpStatus.CREATED)
|
||||
@ResponseBody
|
||||
@PostMapping(value = API_PATH + IMPORT_SUB_PATH, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Creates a new identity provider", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "201", description = "Successfully created the identity provider"), @ApiResponse(responseCode = "400", description = "Malformed request parameters or body"), @ApiResponse(responseCode = "409", description = "Duplicate")})
|
||||
ResponseEntity<IdentityProviderModel> createIdentityProviderFromDescriptor(@Valid @io.swagger.v3.oas.annotations.parameters.RequestBody @RequestBody IdentityProviderWithDescriptorRequest identityProvider);
|
||||
|
||||
|
||||
@ResponseStatus(value = HttpStatus.OK)
|
||||
@ResponseBody
|
||||
@PutMapping(value = API_PATH + IDENTITY_PROVIDER_ALIAS_PATH_PARAM, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Updates an existing identity provider", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Successfully updated the identity provider"), @ApiResponse(responseCode = "404", description = "Not found"), @ApiResponse(responseCode = "400", description = "Malformed request parameters or body")})
|
||||
ResponseEntity<IdentityProviderModel> updateIdentityProvider(@Parameter(name = IDENTITY_PROVIDER_ALIAS_PARAM, description = "The alias of the identity provider to retrieve.", required = true) @PathVariable(IDENTITY_PROVIDER_ALIAS_PARAM) String identityProviderAlias,
|
||||
@Valid @io.swagger.v3.oas.annotations.parameters.RequestBody @RequestBody IdentityProviderRequest identityProvider);
|
||||
|
||||
|
||||
@ResponseStatus(value = HttpStatus.NO_CONTENT)
|
||||
@DeleteMapping(value = API_PATH + IDENTITY_PROVIDER_ALIAS_PATH_PARAM)
|
||||
@Operation(summary = "Deletes an existing identity provider.", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "Successfully deleted the identity provider."), @ApiResponse(responseCode = "404", description = "Not found")})
|
||||
void deleteIdentityProvider(@Parameter(name = IDENTITY_PROVIDER_ALIAS_PARAM, description = "The alias of the identity provider to retrieve.", required = true) @PathVariable(IDENTITY_PROVIDER_ALIAS_PARAM) String identityProviderAlias);
|
||||
|
||||
}
|
||||
@ -6,11 +6,12 @@ import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
||||
import com.knecon.fforesight.tenantusermanagement.model.SMTPConfiguration;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.SMTPResponse;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
@ -22,11 +23,10 @@ public interface SMTPConfigurationResource {
|
||||
|
||||
String TEST_PATH = "/test";
|
||||
|
||||
|
||||
@ResponseBody
|
||||
@ResponseStatus(value = HttpStatus.OK)
|
||||
@GetMapping(value = SMTP_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Returns the current SMTP Configuration.")
|
||||
@Operation(summary = "Returns the current SMTP configuration.")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK."), @ApiResponse(responseCode = "404", description = "SMTP not configured.")})
|
||||
SMTPConfiguration getCurrentSMTPConfiguration();
|
||||
|
||||
@ -34,21 +34,22 @@ public interface SMTPConfigurationResource {
|
||||
@ResponseStatus(value = HttpStatus.NO_CONTENT)
|
||||
@PostMapping(value = SMTP_PATH, consumes = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Write SMTP Settings to KeyCloak")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "SMTP Configuration updated successful."), @ApiResponse(responseCode = "400", description = "SMTP update failed.")})
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "SMTP configuration updated successful."), @ApiResponse(responseCode = "400", description = "SMTP update failed.")})
|
||||
void updateSMTPConfiguration(@RequestBody SMTPConfiguration smtpConfigurationModel);
|
||||
|
||||
|
||||
@ResponseBody
|
||||
@ResponseStatus(value = HttpStatus.OK)
|
||||
@PostMapping(value = SMTP_PATH + TEST_PATH, consumes = MediaType.APPLICATION_JSON_VALUE)
|
||||
@PostMapping(value = SMTP_PATH + TEST_PATH, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Test SMTP Settings to KeyCloak")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "SMTP Configuration is valid."), @ApiResponse(responseCode = "400", description = "SMTP test failed.")})
|
||||
void testSMTPConfiguration(@RequestBody SMTPConfiguration smtpConfigurationModel);
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "SMTP configuration is valid."), @ApiResponse(responseCode = "400", description = "SMTP test failed.")})
|
||||
SMTPResponse testSMTPConfiguration(@RequestBody SMTPConfiguration smtpConfigurationModel);
|
||||
|
||||
|
||||
@ResponseStatus(value = HttpStatus.NO_CONTENT)
|
||||
@DeleteMapping(value = SMTP_PATH)
|
||||
@Operation(summary = "Clear SMTP Settings to KeyCloak")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "SMTP Configuration has been cleared."), @ApiResponse(responseCode = "400", description = "Failed to clear SMTP Configuration.")})
|
||||
@Operation(summary = "Deletes SMTP settings on KeyCloak")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "SMTP configuration has been deleted."), @ApiResponse(responseCode = "400", description = "Failed to delete SMTP configuration.")})
|
||||
void clearSMTPConfiguration();
|
||||
|
||||
}
|
||||
|
||||
@ -4,17 +4,21 @@ import java.util.List;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.knecon.fforesight.tenantcommons.model.TenantResponse;
|
||||
import com.knecon.fforesight.tenantcommons.model.UpdateDetailsRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.DeploymentKeyResponse;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.SimpleTenantResponse;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.TenantRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.CreateTenantRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.UpdateTenantRequest;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
@ -23,36 +27,48 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||
@ResponseStatus(value = HttpStatus.OK)
|
||||
public interface TenantsResource {
|
||||
|
||||
String TENANTS_PATH = "/tenants";
|
||||
String TENANT_ID_PARAM = "tenantId";
|
||||
String TENANT_ID_PATH_PARAM = "/{" + TENANT_ID_PARAM + "}";
|
||||
String TENANTS_TENANT_ID_PATH = TENANTS_PATH + TENANT_ID_PATH_PARAM;
|
||||
|
||||
|
||||
@PostMapping(value = "/tenants", consumes = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Creates a new Tenant", description = "None")
|
||||
@PostMapping(value = TENANTS_PATH, consumes = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Create a new tenant", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
|
||||
void createTenant(@RequestBody TenantRequest tenant);
|
||||
void createTenant(@RequestBody CreateTenantRequest tenant);
|
||||
|
||||
|
||||
@GetMapping(value = "/tenants", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Gets all existing tenant", description = "None")
|
||||
@ResponseBody
|
||||
@ResponseStatus(value = HttpStatus.NO_CONTENT)
|
||||
@Operation(summary = "Deletes given tenant", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "403", description = "Forbidden access, you dont have rights to delete tenants"), @ApiResponse(responseCode = "405", description = "Operation is not allowed."), @ApiResponse(responseCode = "409", description = "Conflict while deleting tenant.")})
|
||||
@DeleteMapping(value = TENANTS_TENANT_ID_PATH)
|
||||
void deleteTenant(@PathVariable("tenantId") String tenantId);
|
||||
|
||||
|
||||
@GetMapping(value = TENANTS_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Gets all existing tenants", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
|
||||
List<TenantResponse> getTenants();
|
||||
|
||||
|
||||
@GetMapping(value = "/tenants/{tenantId}", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Gets all existing tenant", description = "None")
|
||||
@GetMapping(value = TENANTS_TENANT_ID_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Gets an existing tenant", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
|
||||
TenantResponse getTenant(@PathVariable("tenantId") String tenantId);
|
||||
|
||||
@PutMapping(value = "/tenants/{tenantId}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
|
||||
@PutMapping(value = TENANTS_TENANT_ID_PATH, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Update existing tenant", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
|
||||
TenantResponse updateTenant(@PathVariable("tenantId") String tenantId, @RequestBody TenantRequest tenantRequest);
|
||||
TenantResponse updateTenant(@PathVariable("tenantId") String tenantId, @RequestBody UpdateTenantRequest tenantRequest);
|
||||
|
||||
@GetMapping(value = "/tenants/simple", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Gets all existing tenant in a simplified format", description = "None")
|
||||
|
||||
@PostMapping(value = TENANTS_TENANT_ID_PATH + "/details", consumes = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Update details", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
|
||||
List<SimpleTenantResponse> getSimpleTenants();
|
||||
void updateDetails(@PathVariable("tenantId") String tenantId, @RequestBody UpdateDetailsRequest request);
|
||||
|
||||
|
||||
@GetMapping(value = "/deploymentKey" + TENANT_ID_PATH_PARAM, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@ -60,4 +76,10 @@ public interface TenantsResource {
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
|
||||
DeploymentKeyResponse getDeploymentKey(@PathVariable(TENANT_ID_PARAM) String tenantId);
|
||||
|
||||
|
||||
@PostMapping(value = TENANTS_TENANT_ID_PATH + "/sync", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Sync existing tenant", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
|
||||
void syncTenant(@PathVariable("tenantId") String tenantId, @RequestBody JsonNode payload);
|
||||
|
||||
}
|
||||
|
||||
@ -28,14 +28,14 @@ public interface UserPreferenceResource {
|
||||
@ResponseBody
|
||||
@ResponseStatus(value = HttpStatus.OK)
|
||||
@GetMapping(value = PREFERENCES_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Get User Attributes.", description = "None")
|
||||
@Operation(summary = "Gets user attributes", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
|
||||
Map<String, List<String>> getAllUserAttributes();
|
||||
|
||||
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
@PutMapping(value = PREFERENCES_PATH + KEY_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Store User Attribute by key.", description = "None")
|
||||
@Operation(summary = "Store user attribute by key", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK")})
|
||||
void setAttribute(@PathVariable(KEY_PARAMETER_NAME) String key, @RequestBody List<String> values);
|
||||
|
||||
@ -43,7 +43,7 @@ public interface UserPreferenceResource {
|
||||
@ResponseBody
|
||||
@ResponseStatus(value = HttpStatus.NO_CONTENT)
|
||||
@DeleteMapping(value = PREFERENCES_PATH + KEY_PATH_VARIABLE)
|
||||
@Operation(summary = "Delete User Preferences by key.", description = "None")
|
||||
@Operation(summary = "Delete user preferences by key", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK")})
|
||||
void deleteAttribute(@PathVariable(KEY_PARAMETER_NAME) String key);
|
||||
|
||||
|
||||
@ -10,7 +10,6 @@ import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
@ -45,31 +44,31 @@ public interface UserResource {
|
||||
|
||||
|
||||
@ResponseBody
|
||||
@Operation(summary = "Gets the users who contain application-specific roles.", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Invalid " + "offset or limit specified.")})
|
||||
@Operation(summary = "Gets the users holding application-specific roles", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Invalid offset or limit specified.")})
|
||||
@GetMapping(value = RED_USER_REST_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
List<User> getApplicationSpecificUsers(@RequestParam(name = REFRESH_CACHE_PARAM, defaultValue = "false", required = false) boolean bypassCache);
|
||||
|
||||
|
||||
@ResponseBody
|
||||
@Operation(summary = "Gets all the users in realm with information of roles.", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Invalid " + "offset or limit specified.")})
|
||||
@Operation(summary = "Gets all users in realm including role info", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Invalid offset or limit specified.")})
|
||||
@GetMapping(value = USER_REST_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
List<User> getAllUsers(@RequestParam(name = REFRESH_CACHE_PARAM, defaultValue = "false", required = false) boolean bypassCache);
|
||||
|
||||
|
||||
@ResponseBody
|
||||
@ResponseStatus(value = HttpStatus.OK)
|
||||
@Operation(summary = "Update your own user-profile.", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "400", description = "Failed to update profile, e-mail cannot be empty")})
|
||||
@Operation(summary = "Update a user profile", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "400", description = "Failed to update profile, e-mail invalid"), @ApiResponse(responseCode = "404", description = "The userId cannot be found.")})
|
||||
@PostMapping(value = UPDATE_USER_PROFILE_PATH + USER_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
User updateProfile(@PathVariable(USER_ID) String userId, @RequestBody UpdateProfileRequest updateProfileRequest);
|
||||
|
||||
|
||||
@ResponseBody
|
||||
@ResponseStatus(value = HttpStatus.OK)
|
||||
@Operation(summary = "Update your own user-profile.", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "400", description = "Failed to update profile, e-mail cannot be empty")})
|
||||
@Operation(summary = "Update your user profile", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "OK"), @ApiResponse(responseCode = "400", description = "Failed to update profile, e-mail invalid")})
|
||||
@PostMapping(value = UPDATE_MY_USER_PROFILE_PATH, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
User updateMyProfile(@RequestBody UpdateMyProfileRequest updateProfileRequest);
|
||||
|
||||
@ -91,36 +90,36 @@ public interface UserResource {
|
||||
|
||||
|
||||
@ResponseBody
|
||||
@Operation(summary = "Create a new user.", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Invalid Data."), @ApiResponse(responseCode = "409", description = "User already exists.")})
|
||||
@Operation(summary = "Create a new user", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Invalid Data.")})
|
||||
@PostMapping(value = USER_REST_PATH, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
User createUser(@RequestBody CreateUserRequest user);
|
||||
|
||||
|
||||
@ResponseBody
|
||||
@Operation(summary = "Gets the user in realm with information of roles.", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "The " + "userId can not be found."), @ApiResponse(responseCode = "400", description = "The provided user id is empty or " + "null.")})
|
||||
@Operation(summary = "Gets the user in realm including role info", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "The userId cannot be found."), @ApiResponse(responseCode = "400", description = "The provided user id is empty or null.")})
|
||||
@GetMapping(value = USER_REST_PATH + USER_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
User getUserById(@PathVariable(USER_ID) String userId);
|
||||
|
||||
|
||||
@ResponseStatus(value = HttpStatus.OK)
|
||||
@Operation(summary = "Add a role to users", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "No Content"), @ApiResponse(responseCode = "404", description = "The provided userId can not be found."), @ApiResponse(responseCode = "400", description = "One ore more roles are not valid.")})
|
||||
@Operation(summary = "Add roles to a user", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "No Content"), @ApiResponse(responseCode = "404", description = "The provided userId cannot be found."), @ApiResponse(responseCode = "400", description = "One ore more roles are not valid.")})
|
||||
@PostMapping(value = USER_ROLE_REST_PATH, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
User setRoles(@PathVariable(USER_ID) String userId, @RequestBody Set<String> roles);
|
||||
|
||||
|
||||
@ResponseStatus(value = HttpStatus.NO_CONTENT)
|
||||
@Operation(summary = "Reset a user's password", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "No Content"), @ApiResponse(responseCode = "404", description = "The provided userId can not be found.")})
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "204", description = "No Content"), @ApiResponse(responseCode = "404", description = "The provided userId cannot be found.")})
|
||||
@PostMapping(value = RESET_PASSWORD_PATH, consumes = MediaType.APPLICATION_JSON_VALUE)
|
||||
void resetPassword(@PathVariable(USER_ID) String userId, @RequestBody ResetPasswordRequest resetPasswordRequest);
|
||||
|
||||
|
||||
@ResponseBody
|
||||
@Operation(summary = "Activate/ deactivate a user-profile.", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Failed to activate/deactivate profile")})
|
||||
@Operation(summary = "Activate/deactivate a user profile", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Failed to activate/deactivate profile"), @ApiResponse(responseCode = "403", description = "Cannot activate/deactivate users with higher rank roles"), @ApiResponse(responseCode = "404", description = "The userId cannot be found.")})
|
||||
@PostMapping(value = ACTIVATE_USER_PROFILE_PATH + USER_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
User activateProfile(@PathVariable(USER_ID) String userId, @RequestParam(IS_ACTIVE_PARAM) boolean isActive);
|
||||
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.api.external.v2;
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
@RequestMapping("${fforesight.tenant-user-management.base-path-v2:/api}")
|
||||
public interface PublicResourceV2 {
|
||||
|
||||
}
|
||||
38
src/main/java/com/knecon/fforesight/tenantusermanagement/api/external/v2/UserResourceV2.java
vendored
Normal file
38
src/main/java/com/knecon/fforesight/tenantusermanagement/api/external/v2/UserResourceV2.java
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.api.external.v2;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import com.knecon.fforesight.tenantusermanagement.api.external.v2.model.User;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||
|
||||
public interface UserResourceV2 {
|
||||
|
||||
String USER_REST_PATH = "/users";
|
||||
String USER_ID = "userId";
|
||||
String USER_ID_PATH_VARIABLE = "/{" + USER_ID + "}";
|
||||
String USERNAME_PARAM = "username";
|
||||
|
||||
|
||||
@ResponseBody
|
||||
@Operation(summary = "Get a list of users", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "400", description = "Invalid offset or limit specified.")})
|
||||
@GetMapping(value = USER_REST_PATH, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
List<User> getUsers(@RequestParam(name = USERNAME_PARAM, required = false) String username);
|
||||
|
||||
|
||||
@ResponseBody
|
||||
@Operation(summary = "Retrieve a specific user by its identifier.", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK"), @ApiResponse(responseCode = "404", description = "The " + "userId cannot be found."), @ApiResponse(responseCode = "400", description = "The provided user id is empty or " + "null.")})
|
||||
@GetMapping(value = USER_REST_PATH + USER_ID_PATH_VARIABLE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
User getUserById(@PathVariable(USER_ID) String userId);
|
||||
|
||||
}
|
||||
29
src/main/java/com/knecon/fforesight/tenantusermanagement/api/external/v2/model/User.java
vendored
Normal file
29
src/main/java/com/knecon/fforesight/tenantusermanagement/api/external/v2/model/User.java
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.api.external.v2.model;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode(of = "id")
|
||||
public class User {
|
||||
|
||||
private String id;
|
||||
private String username;
|
||||
private String email;
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
private boolean active;
|
||||
|
||||
@Builder.Default
|
||||
private Set<String> roles = new TreeSet<>();
|
||||
|
||||
}
|
||||
18
src/main/java/com/knecon/fforesight/tenantusermanagement/api/external/v2/model/UserList.java
vendored
Normal file
18
src/main/java/com/knecon/fforesight/tenantusermanagement/api/external/v2/model/UserList.java
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.api.external.v2.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class UserList {
|
||||
|
||||
private List<User> users = new ArrayList<>();
|
||||
}
|
||||
@ -11,11 +11,13 @@ import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.knecon.fforesight.tenantcommons.TenantApplicationType;
|
||||
import com.knecon.fforesight.tenantcommons.model.TenantResponse;
|
||||
import com.knecon.fforesight.tenantcommons.model.UpdateDetailsRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.DeploymentKeyResponse;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.SimpleTenantResponse;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.TenantRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.CreateTenantRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.UpdateTenantRequest;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
@ -37,30 +39,25 @@ public interface InternalTenantsResource {
|
||||
@PostMapping(value = "/tenants", consumes = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Creates a new Tenant", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
|
||||
TenantResponse createTenant(@RequestBody TenantRequest tenant);
|
||||
TenantResponse createTenant(@RequestBody CreateTenantRequest tenant);
|
||||
|
||||
|
||||
@GetMapping(value = "/tenants", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Gets all existing tenant", description = "None")
|
||||
@Operation(summary = "Gets all existing tenants", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
|
||||
List<TenantResponse> getTenants();
|
||||
|
||||
|
||||
@GetMapping(value = "/tenants/{tenantId}", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Gets all existing tenant", description = "None")
|
||||
@Operation(summary = "Get the given tenant", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
|
||||
TenantResponse getTenant(@PathVariable("tenantId") String tenantId);
|
||||
|
||||
|
||||
@PutMapping(value = "/tenants/{tenantId}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Update existing tenant", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
|
||||
TenantResponse updateTenant(@PathVariable("tenantId") String tenantId, @RequestBody TenantRequest tenantRequest);
|
||||
|
||||
|
||||
@GetMapping(value = "/tenants/simple", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Gets all existing tenant in a simplified format", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
|
||||
List<SimpleTenantResponse> getSimpleTenants();
|
||||
TenantResponse updateTenant(@PathVariable("tenantId") String tenantId, @RequestBody UpdateTenantRequest tenantRequest);
|
||||
|
||||
|
||||
@GetMapping(value = "/deploymentKey" + TENANT_ID_PATH_PARAM, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@ -68,4 +65,16 @@ public interface InternalTenantsResource {
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
|
||||
DeploymentKeyResponse getDeploymentKey(@PathVariable(TENANT_ID_PARAM) String tenantId);
|
||||
|
||||
|
||||
@PostMapping(value = "/tenants/{tenantId}/sync", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Sync existing tenant", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
|
||||
void syncTenant(@PathVariable("tenantId") String tenantId, @RequestBody JsonNode payload);
|
||||
|
||||
|
||||
@GetMapping(value = {"/tenants/{tenantId}/application-type"}, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Operation(summary = "Gets the application type of the given tenant", description = "None")
|
||||
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
|
||||
TenantApplicationType getTenantApplicationType(@PathVariable("tenantId") String tenantId);
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.client;
|
||||
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
|
||||
import com.iqser.red.service.persistence.service.v1.api.internal.resources.LicenseResource;
|
||||
|
||||
@FeignClient(name = "LicenseResource", url = "${persistence-service.url}")
|
||||
public interface LicenseClient extends LicenseResource {
|
||||
|
||||
}
|
||||
@ -1,25 +1,47 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.controller;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
|
||||
import com.knecon.fforesight.tenantusermanagement.exception.ConflictException;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.ErrorMessage;
|
||||
|
||||
import jakarta.ws.rs.BadRequestException;
|
||||
import jakarta.ws.rs.ForbiddenException;
|
||||
import jakarta.ws.rs.NotFoundException;
|
||||
|
||||
@RestControllerAdvice
|
||||
public class ControllerAdvice {
|
||||
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public ResponseEntity<ErrorMessage> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
|
||||
|
||||
return new ResponseEntity<>(new ErrorMessage(e.getMessage()), HttpStatus.BAD_REQUEST);
|
||||
var errorList = e.getFieldErrors();
|
||||
String errorListAsString = errorList.stream().map(fieldError -> fieldError.getField() + ": " + fieldError.getDefaultMessage()).collect(Collectors.joining(", "));
|
||||
return new ResponseEntity<>(new ErrorMessage(String.format("You have empty/wrong formatted parameters: %s", errorListAsString)), HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
|
||||
@ExceptionHandler(NotFoundException.class)
|
||||
public ResponseEntity<ErrorMessage> handleNotFound(NotFoundException e) {
|
||||
|
||||
return new ResponseEntity<>(new ErrorMessage(e.getMessage()), HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
|
||||
@ExceptionHandler(ForbiddenException.class)
|
||||
public ResponseEntity<ErrorMessage> handleForbiddenAccess(ForbiddenException e) {
|
||||
|
||||
return new ResponseEntity<>(new ErrorMessage(e.getMessage()), HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
|
||||
@ -29,4 +51,42 @@ public class ControllerAdvice {
|
||||
return new ResponseEntity<>(new ErrorMessage(e.getReason()), e.getStatusCode());
|
||||
}
|
||||
|
||||
|
||||
@ExceptionHandler(BadRequestException.class)
|
||||
public ResponseEntity<ErrorMessage> handleBadRequestException(BadRequestException e) {
|
||||
|
||||
return new ResponseEntity<>(new ErrorMessage(e.getMessage()), HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
|
||||
@ExceptionHandler(AccessDeniedException.class)
|
||||
public ResponseEntity<ErrorMessage> handleAccessDeniedException(AccessDeniedException e) {
|
||||
|
||||
return new ResponseEntity<>(new ErrorMessage(e.getMessage()), HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
|
||||
@ExceptionHandler(ConflictException.class)
|
||||
public ResponseEntity<ErrorMessage> handleConflictException(ConflictException e) {
|
||||
|
||||
return new ResponseEntity<>(new ErrorMessage(e.getMessage()), HttpStatus.CONFLICT);
|
||||
}
|
||||
|
||||
@ExceptionHandler({HttpMessageNotReadableException.class})
|
||||
public ResponseEntity<ErrorMessage> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
|
||||
|
||||
String errorMessage = e.getMessage();
|
||||
var cause = e.getCause();
|
||||
if (cause instanceof InvalidFormatException invalidFormatException) {
|
||||
|
||||
errorMessage = cause.getMessage();
|
||||
Class<?> targetType = invalidFormatException.getTargetType();
|
||||
if (targetType != null && targetType.isEnum()) {
|
||||
errorMessage = String.format("Unsupported value for %s", targetType.getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
return new ResponseEntity<>(new ErrorMessage(errorMessage), HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -7,10 +7,12 @@ import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.knecon.fforesight.tenantcommons.TenantContext;
|
||||
import com.knecon.fforesight.tenantusermanagement.api.external.GeneralSettingsResource;
|
||||
import com.knecon.fforesight.tenantusermanagement.api.external.PublicResource;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.GeneralConfigurationModel;
|
||||
import com.knecon.fforesight.tenantusermanagement.service.GeneralConfigurationService;
|
||||
import com.knecon.fforesight.tenantusermanagement.service.TenantManagementService;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -21,13 +23,14 @@ import lombok.extern.slf4j.Slf4j;
|
||||
public class GeneralSettingsController implements GeneralSettingsResource, PublicResource {
|
||||
|
||||
private final GeneralConfigurationService generalConfigurationService;
|
||||
private final TenantManagementService tenantManagementService;
|
||||
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthority('" + READ_GENERAL_CONFIGURATION + "')")
|
||||
public GeneralConfigurationModel getGeneralConfigurations() {
|
||||
|
||||
return generalConfigurationService.getGeneralConfigurations();
|
||||
return generalConfigurationService.getGeneralConfigurations(tenantManagementService.getTenantApplicationType(TenantContext.getTenantId()));
|
||||
}
|
||||
|
||||
|
||||
@ -35,7 +38,7 @@ public class GeneralSettingsController implements GeneralSettingsResource, Publi
|
||||
@PreAuthorize("hasAuthority('" + WRITE_GENERAL_CONFIGURATION + "')")
|
||||
public void updateGeneralConfigurations(@RequestBody GeneralConfigurationModel generalConfigurationModel) {
|
||||
|
||||
generalConfigurationService.updateGeneralConfigurations(generalConfigurationModel);
|
||||
generalConfigurationService.updateGeneralConfigurations(generalConfigurationModel, tenantManagementService.getTenantApplicationType(TenantContext.getTenantId()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,269 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.controller.external;
|
||||
|
||||
import static com.knecon.fforesight.tenantusermanagement.permissions.UserManagementPermissions.READ_IDENTITY_PROVIDER_CONFIGURATION;
|
||||
import static com.knecon.fforesight.tenantusermanagement.permissions.UserManagementPermissions.WRITE_IDENTITY_PROVIDER_CONFIGURATION;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.client.HttpClientErrorException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import com.knecon.fforesight.tenantcommons.TenantContext;
|
||||
import com.knecon.fforesight.tenantusermanagement.api.external.IdentityProviderConfigurationResource;
|
||||
import com.knecon.fforesight.tenantusermanagement.api.external.PublicResource;
|
||||
import com.knecon.fforesight.tenantusermanagement.exception.ConflictException;
|
||||
import com.knecon.fforesight.tenantusermanagement.exception.IdentityProviderExistsAlreadyException;
|
||||
import com.knecon.fforesight.tenantusermanagement.exception.IdentityProviderNotFoundException;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderList;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderModel;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderWithDescriptorRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.properties.TenantUserManagementProperties;
|
||||
import com.knecon.fforesight.tenantusermanagement.service.KeyCloakAdminClientService;
|
||||
import com.knecon.fforesight.tenantusermanagement.service.RealmService;
|
||||
import com.knecon.fforesight.tenantusermanagement.utils.IdentityProviderMappingService;
|
||||
|
||||
import jakarta.ws.rs.BadRequestException;
|
||||
import jakarta.ws.rs.InternalServerErrorException;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
public class IdentityProviderConfigurationController implements IdentityProviderConfigurationResource, PublicResource {
|
||||
|
||||
private final RealmService realmService;
|
||||
private final TenantUserManagementProperties tenantUserManagementProperties;
|
||||
private final KeyCloakAdminClientService keyCloakAdminClientService;
|
||||
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthority('" + READ_IDENTITY_PROVIDER_CONFIGURATION + "')")
|
||||
public IdentityProviderList getIdentityProviders() {
|
||||
|
||||
return new IdentityProviderList(getRealmRepresentation().getIdentityProviders()
|
||||
.stream()
|
||||
.map(IdentityProviderMappingService::toModelFromRepresentation)
|
||||
.toList());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthority('" + READ_IDENTITY_PROVIDER_CONFIGURATION + "')")
|
||||
public IdentityProviderModel getIdentityProvider(String identityProviderAlias) {
|
||||
|
||||
return IdentityProviderMappingService.toModelFromRepresentation(getIdentityProviderRepresentation(identityProviderAlias));
|
||||
}
|
||||
|
||||
|
||||
private IdentityProviderRepresentation getIdentityProviderRepresentation(String identityProviderAlias) {
|
||||
|
||||
return getRealmRepresentation().getIdentityProviders()
|
||||
.stream()
|
||||
.filter(identityProviderRepresentation -> identityProviderRepresentation.getAlias().equals(identityProviderAlias))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IdentityProviderNotFoundException(identityProviderAlias));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthority('" + WRITE_IDENTITY_PROVIDER_CONFIGURATION + "')")
|
||||
public ResponseEntity<IdentityProviderModel> createIdentityProvider(IdentityProviderRequest identityProviderRequest) {
|
||||
|
||||
if (identityProviderRequest.getAlias() == null) {
|
||||
throw new BadRequestException("Alias must not be null.");
|
||||
}
|
||||
|
||||
if (identityProviderRequest.getDisplayName() == null || identityProviderRequest.getDisplayName().isEmpty()) {
|
||||
identityProviderRequest.setDisplayName(identityProviderRequest.getAlias());
|
||||
}
|
||||
checkIfOtherIdpHasSameDisplayNameOnCreate(identityProviderRequest.getDisplayName());
|
||||
|
||||
IdentityProviderModel identityProviderModel = IdentityProviderMappingService.toModelFromRequest(identityProviderRequest);
|
||||
return callKeyCloakIdentityProvidersCreateForModel(identityProviderModel);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthority('" + WRITE_IDENTITY_PROVIDER_CONFIGURATION + "')")
|
||||
public ResponseEntity<IdentityProviderModel> createIdentityProviderFromDescriptor(IdentityProviderWithDescriptorRequest identityProviderWithDescriptorRequest) {
|
||||
|
||||
if (identityProviderWithDescriptorRequest.getDisplayName() == null || identityProviderWithDescriptorRequest.getDisplayName().isEmpty()) {
|
||||
identityProviderWithDescriptorRequest.setDisplayName(identityProviderWithDescriptorRequest.getAlias());
|
||||
}
|
||||
checkIfOtherIdpHasSameDisplayNameOnCreate(identityProviderWithDescriptorRequest.getDisplayName());
|
||||
|
||||
Map<String, Object> requestMap = new HashMap<>();
|
||||
requestMap.put("providerId", identityProviderWithDescriptorRequest.getProviderId());
|
||||
requestMap.put("fromUrl", identityProviderWithDescriptorRequest.getSamlEntityDescriptorURL());
|
||||
|
||||
try {
|
||||
Map<String, String> configurationsMap = realmService.realm(getTenantId()).identityProviders().importFrom(requestMap);
|
||||
|
||||
if (configurationsMap == null || configurationsMap.isEmpty()) {
|
||||
throw new BadRequestException("Could not set config from provided descriptor.");
|
||||
}
|
||||
|
||||
configurationsMap.put("entityId", identityProviderWithDescriptorRequest.getEntityId());
|
||||
IdentityProviderModel identityProviderModel = IdentityProviderMappingService.toModelFromDescriptorRequestAndConfig(identityProviderWithDescriptorRequest,
|
||||
configurationsMap);
|
||||
return callKeyCloakIdentityProvidersCreateForModel(identityProviderModel);
|
||||
|
||||
} catch (InternalServerErrorException internalServerErrorException) {
|
||||
throw new BadRequestException("Provided descriptor is malformed.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private ResponseEntity<IdentityProviderModel> callKeyCloakIdentityProvidersCreateForModel(IdentityProviderModel identityProviderModel) {
|
||||
|
||||
identityProviderModel.setSpecificDefaults();
|
||||
|
||||
try (Response response = realmService.realm(getTenantId()).identityProviders().create(IdentityProviderMappingService.toRepresentationFromModel(identityProviderModel))) {
|
||||
var httpStatus = HttpStatus.valueOf(response.getStatus());
|
||||
switch (httpStatus) {
|
||||
case CREATED -> {
|
||||
IdentityProviderModel createdIdentityProvider = getIdentityProvider(identityProviderModel.getAlias());
|
||||
return new ResponseEntity<>(createdIdentityProvider, HttpStatus.valueOf(response.getStatus()));
|
||||
}
|
||||
case CONFLICT -> throw new IdentityProviderExistsAlreadyException(identityProviderModel.getAlias());
|
||||
default -> throw new ResponseStatusException(httpStatus, extractKeycloakErrorMessageInfos(response.readEntity(String.class)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthority('" + WRITE_IDENTITY_PROVIDER_CONFIGURATION + "')")
|
||||
public ResponseEntity<IdentityProviderModel> updateIdentityProvider(String identityProviderAlias, IdentityProviderRequest identityProviderRequest) {
|
||||
|
||||
identityProviderRequest.setAlias(identityProviderAlias);
|
||||
getIdentityProviderRepresentation(identityProviderAlias);
|
||||
|
||||
checkIfOtherIdpHasSameDisplayNameOnUpdate(identityProviderAlias, identityProviderRequest.getDisplayName());
|
||||
|
||||
IdentityProviderModel identityProviderModel = IdentityProviderMappingService.toModelFromRequest(identityProviderRequest);
|
||||
identityProviderModel.setSpecificDefaults();
|
||||
|
||||
var restTemplate = new RestTemplate();
|
||||
var url = getKeycloakIdentityProviderInstancesUrl() + identityProviderRequest.getAlias();
|
||||
var headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.add("Authorization", "Bearer " + getToken());
|
||||
HttpEntity<IdentityProviderModel> httpEntity = new HttpEntity<>(identityProviderModel, headers);
|
||||
try {
|
||||
ResponseEntity<?> response = restTemplate.exchange(url, HttpMethod.PUT, httpEntity, String.class);
|
||||
var httpStatus = HttpStatus.valueOf(response.getStatusCode().value());
|
||||
if (httpStatus == HttpStatus.NO_CONTENT) {
|
||||
IdentityProviderModel createdIdentityProvider = getIdentityProvider(identityProviderRequest.getAlias());
|
||||
return new ResponseEntity<>(createdIdentityProvider, HttpStatus.OK);
|
||||
} else if (httpStatus.is4xxClientError()) {
|
||||
throw new ResponseStatusException(httpStatus, "Bad request to keycloak API");
|
||||
} else {
|
||||
throw new ResponseStatusException(httpStatus, httpStatus.getReasonPhrase());
|
||||
}
|
||||
} catch (HttpClientErrorException e) {
|
||||
throw new ResponseStatusException(e.getStatusCode(), extractKeycloakErrorMessageInfos(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthority('" + WRITE_IDENTITY_PROVIDER_CONFIGURATION + "')")
|
||||
public void deleteIdentityProvider(String identityProviderAlias) {
|
||||
|
||||
getIdentityProviderRepresentation(identityProviderAlias);
|
||||
|
||||
var restTemplate = new RestTemplate();
|
||||
var url = getKeycloakIdentityProviderInstancesUrl() + identityProviderAlias;
|
||||
var headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.add("Authorization", "Bearer " + getToken());
|
||||
HttpEntity<?> httpEntity = new HttpEntity<>(headers);
|
||||
try {
|
||||
restTemplate.exchange(url, HttpMethod.DELETE, httpEntity, String.class);
|
||||
} catch (HttpClientErrorException e) {
|
||||
throw new ResponseStatusException(e.getStatusCode(), extractKeycloakErrorMessageInfos(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void checkIfOtherIdpHasSameDisplayNameOnUpdate(String alias, String displayName) {
|
||||
|
||||
checkIfOtherIdpHasSameDisplayName(getIdentityProviders().getIdentityProviders()
|
||||
.stream()
|
||||
.filter(identityProviderModel -> !identityProviderModel.getAlias().equals(alias))
|
||||
.toList(), displayName);
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void checkIfOtherIdpHasSameDisplayNameOnCreate(String displayName) {
|
||||
|
||||
checkIfOtherIdpHasSameDisplayName(getIdentityProviders().getIdentityProviders(), displayName);
|
||||
}
|
||||
|
||||
|
||||
private void checkIfOtherIdpHasSameDisplayName(List<IdentityProviderModel> identityProviderModelList, String displayName) {
|
||||
|
||||
if (identityProviderModelList.stream()
|
||||
.anyMatch(idp -> idp.getDisplayName().isEmpty() ? idp.getAlias().equals(displayName) : idp.getDisplayName().equals(displayName))) {
|
||||
throw new ConflictException("Identity provider with this display name already exists.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static String extractKeycloakErrorMessageInfos(String keyCloakErrorMessage) {
|
||||
|
||||
String errorMessageRegex = "\\{\"errorMessage\":\"(.*?)\"}";
|
||||
Pattern pattern = Pattern.compile(errorMessageRegex);
|
||||
Matcher matcher = pattern.matcher(keyCloakErrorMessage);
|
||||
|
||||
if (matcher.find()) {
|
||||
return matcher.group(1);
|
||||
}
|
||||
return keyCloakErrorMessage;
|
||||
}
|
||||
|
||||
|
||||
private String getTenantId() {
|
||||
|
||||
return TenantContext.getTenantId();
|
||||
}
|
||||
|
||||
|
||||
private RealmRepresentation getRealmRepresentation() {
|
||||
|
||||
return realmService.realm(getTenantId()).toRepresentation();
|
||||
}
|
||||
|
||||
|
||||
private String getKeycloakIdentityProviderInstancesUrl() {
|
||||
|
||||
return tenantUserManagementProperties.getServerUrl() + "/admin/realms/" + getTenantId() + "/identity-provider/instances/";
|
||||
}
|
||||
|
||||
|
||||
private String getToken() {
|
||||
|
||||
return keyCloakAdminClientService.getAdminClient().tokenManager().getAccessToken().getToken();
|
||||
}
|
||||
|
||||
}
|
||||
@ -6,20 +6,25 @@ import static com.knecon.fforesight.tenantusermanagement.permissions.UserManagem
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.knecon.fforesight.tenantcommons.EncryptionDecryptionService;
|
||||
import com.knecon.fforesight.tenantcommons.TenantContext;
|
||||
import com.knecon.fforesight.tenantusermanagement.api.external.PublicResource;
|
||||
import com.knecon.fforesight.tenantusermanagement.api.external.SMTPConfigurationResource;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.SMTPConfiguration;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.SMTPResponse;
|
||||
import com.knecon.fforesight.tenantusermanagement.service.EmailService;
|
||||
import com.knecon.fforesight.tenantusermanagement.service.RealmService;
|
||||
import com.knecon.fforesight.tenantusermanagement.service.SMTPService;
|
||||
|
||||
import jakarta.ws.rs.BadRequestException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@ -27,19 +32,16 @@ import lombok.extern.slf4j.Slf4j;
|
||||
@RequiredArgsConstructor
|
||||
public class SMTPConfigurationController implements SMTPConfigurationResource, PublicResource {
|
||||
|
||||
private final static String SMTP_PASSWORD_KEY = "FFORESIGHT_SMTP_PASSWORD";
|
||||
private final RealmService realmService;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final EncryptionDecryptionService encryptionDecryptionService;
|
||||
private final EmailService emailService;
|
||||
private final SMTPService smtpService;
|
||||
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthority('" + READ_SMTP_CONFIGURATION + "')")
|
||||
public SMTPConfiguration getCurrentSMTPConfiguration() {
|
||||
|
||||
var realm = realmService.realm(TenantContext.getTenantId()).toRepresentation();
|
||||
return objectMapper.convertValue(realm.getSmtpServer(), SMTPConfiguration.class);
|
||||
|
||||
return smtpService.getSMTPConfiguration();
|
||||
}
|
||||
|
||||
|
||||
@ -47,51 +49,42 @@ public class SMTPConfigurationController implements SMTPConfigurationResource, P
|
||||
@PreAuthorize("hasAuthority('" + WRITE_SMTP_CONFIGURATION + "')")
|
||||
public void updateSMTPConfiguration(@RequestBody SMTPConfiguration smtpConfigurationModel) {
|
||||
|
||||
var realmRepresentation = realmService.realm(TenantContext.getTenantId()).toRepresentation();
|
||||
var propertiesMap = convertSMTPConfigurationModelToMap(smtpConfigurationModel);
|
||||
realmRepresentation.setSmtpServer(propertiesMap);
|
||||
|
||||
if (!smtpConfigurationModel.getPassword().matches("\\**")) {
|
||||
realmRepresentation.getAttributesOrEmpty().put(SMTP_PASSWORD_KEY, encryptionDecryptionService.encrypt(smtpConfigurationModel.getPassword()));
|
||||
if (!smtpService.canUpdateSMTPConfig()) {
|
||||
throw new BadRequestException("Current license does not allow updating the SMTP configuration!");
|
||||
}
|
||||
|
||||
realmService.realm(TenantContext.getTenantId()).update(realmRepresentation);
|
||||
smtpService.updateSMTPConfiguration(smtpConfigurationModel);
|
||||
}
|
||||
|
||||
|
||||
private Map<String, String> convertSMTPConfigurationModelToMap(SMTPConfiguration smtpConfigurationModel) {
|
||||
|
||||
Map<String, Object> propertiesMap = objectMapper.convertValue(smtpConfigurationModel, Map.class);
|
||||
Map<String, String> stringPropertiesMap = new HashMap<>();
|
||||
propertiesMap.forEach((key, value) -> {
|
||||
if (value != null) {
|
||||
stringPropertiesMap.put(key, value.toString());
|
||||
} else {
|
||||
stringPropertiesMap.put(key, "");
|
||||
}
|
||||
});
|
||||
return stringPropertiesMap;
|
||||
}
|
||||
|
||||
|
||||
@SneakyThrows
|
||||
@Override
|
||||
@PreAuthorize("hasAuthority('" + WRITE_SMTP_CONFIGURATION + "')")
|
||||
public void testSMTPConfiguration(@RequestBody SMTPConfiguration smtpConfigurationModel) {
|
||||
public SMTPResponse testSMTPConfiguration(@RequestBody SMTPConfiguration smtpConfiguration) {
|
||||
|
||||
var propertiesMap = convertSMTPConfigurationModelToMap(smtpConfigurationModel);
|
||||
realmService.realm(TenantContext.getTenantId()).testSMTPConnection(propertiesMap);
|
||||
String targetEmail = realmService.getEmail(realmService.realm(TenantContext.getTenantId()));
|
||||
boolean adminEmail = true;
|
||||
|
||||
if (StringUtils.isEmpty(targetEmail)) {
|
||||
// will send e-mail to self in case targetEmail is not set
|
||||
targetEmail = smtpConfiguration.getFrom();
|
||||
adminEmail = false;
|
||||
}
|
||||
|
||||
SMTPResponse.SMTPResponseBuilder smtpResponseBuilder = emailService.send(smtpService.convertSMTPConfigToMap(smtpConfiguration),
|
||||
targetEmail,
|
||||
"Redaction Test message",
|
||||
"This is a test message");
|
||||
SMTPResponse smtpResponse = smtpResponseBuilder.adminEmail(adminEmail).recipientEmail(targetEmail).build();
|
||||
log.info("Test SMTP Configuration status: {}, reason: {}, recipient: {}", smtpResponse.getStatusCode(), smtpResponse.getReasonPhrase(), smtpResponse.getRecipientEmail());
|
||||
return smtpResponse;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthority('" + WRITE_SMTP_CONFIGURATION + "')")
|
||||
public void clearSMTPConfiguration() {
|
||||
// also update in KC
|
||||
var realmRepresentation = realmService.realm(TenantContext.getTenantId()).toRepresentation();
|
||||
realmRepresentation.setSmtpServer(new HashMap<>());
|
||||
realmRepresentation.getAttributesOrEmpty().remove(SMTP_PASSWORD_KEY);
|
||||
realmService.realm(TenantContext.getTenantId()).update(realmRepresentation);
|
||||
|
||||
smtpService.createDefaultSMTPConfiguration();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.controller.external;
|
||||
|
||||
import static com.knecon.fforesight.tenantusermanagement.permissions.UserManagementPermissions.CREATE_TENANT;
|
||||
import static com.knecon.fforesight.tenantusermanagement.permissions.UserManagementPermissions.DELETE_TENANT;
|
||||
import static com.knecon.fforesight.tenantusermanagement.permissions.UserManagementPermissions.DEPLOYMENT_INFO;
|
||||
import static com.knecon.fforesight.tenantusermanagement.permissions.UserManagementPermissions.GET_TENANTS;
|
||||
import static com.knecon.fforesight.tenantusermanagement.permissions.UserManagementPermissions.UPDATE_TENANT;
|
||||
@ -15,12 +16,14 @@ import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.knecon.fforesight.tenantcommons.model.TenantResponse;
|
||||
import com.knecon.fforesight.tenantcommons.model.UpdateDetailsRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.api.external.PublicResource;
|
||||
import com.knecon.fforesight.tenantusermanagement.api.external.TenantsResource;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.DeploymentKeyResponse;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.SimpleTenantResponse;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.TenantRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.CreateTenantRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.UpdateTenantRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.service.DeploymentKeyService;
|
||||
import com.knecon.fforesight.tenantusermanagement.service.TenantManagementService;
|
||||
|
||||
@ -36,7 +39,7 @@ public class TenantsController implements TenantsResource, PublicResource {
|
||||
|
||||
|
||||
@PreAuthorize("hasAuthority('" + CREATE_TENANT + "')")
|
||||
public void createTenant(@Valid @RequestBody TenantRequest tenantRequest) {
|
||||
public void createTenant(@Valid @RequestBody CreateTenantRequest tenantRequest) {
|
||||
|
||||
try {
|
||||
tenantManagementService.createTenant(tenantRequest);
|
||||
@ -45,9 +48,21 @@ public class TenantsController implements TenantsResource, PublicResource {
|
||||
}
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('" + DELETE_TENANT + "')")
|
||||
public void deleteTenant(String tenantId) {
|
||||
|
||||
try {
|
||||
tenantManagementService.deleteTenant(tenantId);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage(), e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@PreAuthorize("hasAuthority('" + GET_TENANTS + "')")
|
||||
public List<TenantResponse> getTenants() {
|
||||
|
||||
List<TenantResponse> tenants = tenantManagementService.getTenants();
|
||||
return tenants.stream().map(tenantManagementService::removePasswords).collect(Collectors.toList());
|
||||
}
|
||||
@ -61,24 +76,33 @@ public class TenantsController implements TenantsResource, PublicResource {
|
||||
}
|
||||
|
||||
|
||||
public List<SimpleTenantResponse> getSimpleTenants() {
|
||||
|
||||
return tenantManagementService.getTenants().stream().map(t -> new SimpleTenantResponse(t.getTenantId(), t.getDisplayName(), t.getGuid())).toList();
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('" + UPDATE_TENANT + "')")
|
||||
public TenantResponse updateTenant(String tenantId,
|
||||
@RequestBody TenantRequest tenantRequest) {
|
||||
public TenantResponse updateTenant(String tenantId, @RequestBody UpdateTenantRequest tenantRequest) {
|
||||
|
||||
TenantResponse tenantResponse = tenantManagementService.updateTenant(tenantId, tenantRequest);
|
||||
return tenantManagementService.removePasswords(tenantResponse);
|
||||
}
|
||||
|
||||
|
||||
@PreAuthorize("hasAuthority('" + UPDATE_TENANT + "')")
|
||||
public void updateDetails(@PathVariable("tenantId") String tenantId, @RequestBody UpdateDetailsRequest request) {
|
||||
|
||||
tenantManagementService.updateDetails(tenantId, request);
|
||||
}
|
||||
|
||||
|
||||
@PreAuthorize("hasAuthority('" + DEPLOYMENT_INFO + "')")
|
||||
public DeploymentKeyResponse getDeploymentKey(@PathVariable(TENANT_ID_PARAM) String tenantId) {
|
||||
|
||||
return new DeploymentKeyResponse(deploymentKeyService.getDeploymentKey(tenantId));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void syncTenant(@PathVariable(TENANT_ID_PARAM) String tenantId, @RequestBody JsonNode payload) {
|
||||
|
||||
tenantManagementService.syncTenant(tenantId, payload);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.controller.external;
|
||||
|
||||
import static com.knecon.fforesight.tenantusermanagement.permissions.ApplicationRoles.KNECON_ROLE_FILTER;
|
||||
import static com.knecon.fforesight.tenantusermanagement.permissions.UserManagementPermissions.READ_ALL_USERS;
|
||||
import static com.knecon.fforesight.tenantusermanagement.permissions.UserManagementPermissions.READ_USERS;
|
||||
import static com.knecon.fforesight.tenantusermanagement.permissions.UserManagementPermissions.UPDATE_MY_PROFILE;
|
||||
@ -25,7 +26,8 @@ import com.knecon.fforesight.tenantusermanagement.model.ResetPasswordRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.UpdateMyProfileRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.UpdateProfileRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.User;
|
||||
import com.knecon.fforesight.tenantusermanagement.properties.TenantUserManagementProperties;
|
||||
import com.knecon.fforesight.tenantusermanagement.permissions.ApplicationRoles;
|
||||
import com.knecon.fforesight.tenantusermanagement.service.TenantApplicationTypeService;
|
||||
import com.knecon.fforesight.tenantusermanagement.service.UserService;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
@ -37,8 +39,9 @@ import lombok.extern.slf4j.Slf4j;
|
||||
@RequiredArgsConstructor
|
||||
public class UserController implements UserResource, PublicResource {
|
||||
|
||||
|
||||
private final UserService userService;
|
||||
private final TenantUserManagementProperties tenantUserManagementProperties;
|
||||
private final TenantApplicationTypeService tenantApplicationTypeService;
|
||||
|
||||
|
||||
@Override
|
||||
@ -48,9 +51,15 @@ public class UserController implements UserResource, PublicResource {
|
||||
if (bypassCache) {
|
||||
userService.evictUserCache();
|
||||
}
|
||||
var mappedRoles = tenantApplicationTypeService.getCurrentProperties().getKcRoleMapping().getAllRoles();
|
||||
|
||||
var allRoles = tenantUserManagementProperties.getKcRoleMapping().getAllRoles();
|
||||
return userService.getAllUsers().stream().filter(user -> user.getRoles().stream().anyMatch(allRoles::contains)).collect(Collectors.toList());
|
||||
return userService.getAllUsers()
|
||||
.stream()
|
||||
.filter(user -> user.getRoles()
|
||||
.stream()
|
||||
.anyMatch(mappedRoles::contains))
|
||||
.filter(KNECON_ROLE_FILTER)
|
||||
.toList();
|
||||
}
|
||||
|
||||
|
||||
@ -62,7 +71,10 @@ public class UserController implements UserResource, PublicResource {
|
||||
userService.evictUserCache();
|
||||
}
|
||||
|
||||
return userService.getAllUsers();
|
||||
return userService.getAllUsers()
|
||||
.stream()
|
||||
.filter(KNECON_ROLE_FILTER)
|
||||
.toList();
|
||||
}
|
||||
|
||||
|
||||
@ -113,7 +125,20 @@ public class UserController implements UserResource, PublicResource {
|
||||
if (StringUtils.isEmpty(userId)) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "The userId should not be empty.");
|
||||
}
|
||||
return userService.getUserById(userId).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "User not found"));
|
||||
var user = userService.getUserById(userId)
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "User not found"));
|
||||
|
||||
Set<String> filteredRoles = user.getRoles()
|
||||
.stream()
|
||||
.filter(ApplicationRoles::isNoKneconRole)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
if (!user.getRoles().isEmpty() && filteredRoles.isEmpty()) {
|
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "User not found");
|
||||
}
|
||||
|
||||
user.setRoles(filteredRoles);
|
||||
return user;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
@ -14,6 +15,8 @@ import com.knecon.fforesight.tenantusermanagement.api.external.UserPreferenceRes
|
||||
import com.knecon.fforesight.tenantusermanagement.permissions.UserManagementPermissions;
|
||||
import com.knecon.fforesight.tenantusermanagement.service.UserService;
|
||||
|
||||
import jakarta.ws.rs.ForbiddenException;
|
||||
import jakarta.ws.rs.NotFoundException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@RestController
|
||||
|
||||
@ -0,0 +1,57 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.controller.external.v2;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.knecon.fforesight.tenantusermanagement.api.external.v2.PublicResourceV2;
|
||||
import com.knecon.fforesight.tenantusermanagement.api.external.v2.UserResourceV2;
|
||||
import com.knecon.fforesight.tenantusermanagement.api.external.v2.model.User;
|
||||
import com.knecon.fforesight.tenantusermanagement.controller.external.UserController;
|
||||
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@Tag(name = "6. Users endpoints", description = "Operations related to users.")
|
||||
public class UserControllerV2 implements UserResourceV2, PublicResourceV2 {
|
||||
|
||||
private final UserController userController;
|
||||
|
||||
|
||||
public List<User> getUsers(@RequestParam(name = USERNAME_PARAM, required = false) String username) {
|
||||
|
||||
var users = userController.getApplicationSpecificUsers(false)
|
||||
.stream()
|
||||
.map(this::convertUser);
|
||||
|
||||
if (username == null) {
|
||||
return users.toList();
|
||||
}
|
||||
return users.filter(user -> user.getUsername().equals(username)).toList();
|
||||
}
|
||||
|
||||
|
||||
public User getUserById(@PathVariable(USER_ID) String userId) {
|
||||
return convertUser(userController.getUserById(userId));
|
||||
}
|
||||
|
||||
|
||||
private User convertUser(com.knecon.fforesight.tenantusermanagement.model.User user){
|
||||
return User.builder()
|
||||
.id(user.getUserId())
|
||||
.username(user.getUsername())
|
||||
.email(user.getEmail())
|
||||
.firstName(user.getFirstName())
|
||||
.lastName(user.getLastName())
|
||||
.active(user.isActive())
|
||||
.roles(user.getRoles())
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
@ -8,13 +8,15 @@ import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.knecon.fforesight.tenantcommons.TenantApplicationType;
|
||||
import com.knecon.fforesight.tenantcommons.model.TenantResponse;
|
||||
import com.knecon.fforesight.tenantcommons.model.UpdateDetailsRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.api.internal.InternalResource;
|
||||
import com.knecon.fforesight.tenantusermanagement.api.internal.InternalTenantsResource;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.CreateTenantRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.DeploymentKeyResponse;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.SimpleTenantResponse;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.TenantRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.UpdateTenantRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.service.DeploymentKeyService;
|
||||
import com.knecon.fforesight.tenantusermanagement.service.TenantManagementService;
|
||||
|
||||
@ -36,7 +38,7 @@ public class InternalTenantsController implements InternalTenantsResource, Inter
|
||||
}
|
||||
|
||||
|
||||
public TenantResponse createTenant(@Valid @RequestBody TenantRequest tenantRequest) {
|
||||
public TenantResponse createTenant(@Valid @RequestBody CreateTenantRequest tenantRequest) {
|
||||
|
||||
try {
|
||||
return tenantManagementService.createTenant(tenantRequest);
|
||||
@ -59,21 +61,29 @@ public class InternalTenantsController implements InternalTenantsResource, Inter
|
||||
|
||||
|
||||
@Override
|
||||
public TenantResponse updateTenant(String tenantId, TenantRequest tenantRequest) {
|
||||
public TenantResponse updateTenant(String tenantId, UpdateTenantRequest tenantRequest) {
|
||||
|
||||
return tenantManagementService.updateTenant(tenantId, tenantRequest);
|
||||
}
|
||||
|
||||
|
||||
public List<SimpleTenantResponse> getSimpleTenants() {
|
||||
|
||||
return tenantManagementService.getTenants().stream().map(t -> new SimpleTenantResponse(t.getTenantId(), t.getDisplayName(), t.getGuid())).toList();
|
||||
}
|
||||
|
||||
|
||||
public DeploymentKeyResponse getDeploymentKey(@PathVariable(TENANT_ID_PARAM) String tenantId) {
|
||||
|
||||
return new DeploymentKeyResponse(deploymentKeyService.getDeploymentKey(tenantId));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void syncTenant(@PathVariable(TENANT_ID_PARAM) String tenantId, @RequestBody JsonNode payload) {
|
||||
|
||||
tenantManagementService.syncTenant(tenantId, payload);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public TenantApplicationType getTenantApplicationType(@PathVariable(TENANT_ID_PARAM) String tenantId) {
|
||||
|
||||
return tenantManagementService.getTenantApplicationType(tenantId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.knecon.fforesight.tenantusermanagement.model.TenantUser;
|
||||
import com.knecon.fforesight.tenantusermanagement.service.TenantManagementService;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
@ -20,17 +21,15 @@ import lombok.SneakyThrows;
|
||||
public class DevApplicationRunner implements ApplicationRunner {
|
||||
|
||||
private final DevTestTenantService devTestTenantService;
|
||||
private final TenantManagementService tenantManagementService;
|
||||
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void run(ApplicationArguments args) {
|
||||
|
||||
// devTestTenantService.deleteAll();
|
||||
|
||||
List<TenantUser> users = new ArrayList<>();
|
||||
users.add(TenantUser.builder().email("test@taas.com").username("test@taas.com").password("test").roles(Set.of("SUPER_USER")).build());
|
||||
devTestTenantService.createTestTenantIfNotExists("taas", users);
|
||||
devTestTenantService.createTestTenantIfNotExists("redaction", new ArrayList<>());
|
||||
tenantManagementService.syncTenant("redaction", null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.dev;
|
||||
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
|
||||
@Configuration
|
||||
@Profile("dev")
|
||||
@EnableConfigurationProperties(DevTenantProperties.class)
|
||||
public class DevConfiguration {
|
||||
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.dev;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@ConfigurationProperties("dev.tenant")
|
||||
public class DevTenantProperties {
|
||||
|
||||
private boolean recreateTenant;
|
||||
private DevDatabaseProperties db = new DevDatabaseProperties();
|
||||
private DevStorageProperties storage = new DevStorageProperties();
|
||||
|
||||
@Data
|
||||
public static class DevStorageProperties {
|
||||
|
||||
private DevStorageMode mode = DevStorageMode.S3;
|
||||
private DevS3StorageProperties s3 = new DevS3StorageProperties();
|
||||
private DevAzureStorageProperties azure = new DevAzureStorageProperties();
|
||||
|
||||
}
|
||||
|
||||
public enum DevStorageMode {
|
||||
S3,
|
||||
AZURE
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class DevS3StorageProperties {
|
||||
|
||||
private String key;
|
||||
private String secret;
|
||||
private String bucket;
|
||||
|
||||
private String endpoint;
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class DevAzureStorageProperties {
|
||||
|
||||
private String containerName;
|
||||
private String connectionString;
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class DevDatabaseProperties {
|
||||
|
||||
private int port;
|
||||
private String host;
|
||||
private String database;
|
||||
private String schema;
|
||||
private String username;
|
||||
private String password;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -7,7 +7,6 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
@ -16,12 +15,12 @@ import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.StatementCallback;
|
||||
import org.springframework.jdbc.datasource.SingleConnectionDataSource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import com.knecon.fforesight.tenantcommons.model.AzureStorageConnection;
|
||||
import com.knecon.fforesight.tenantcommons.model.DatabaseConnection;
|
||||
import com.knecon.fforesight.tenantcommons.model.S3StorageConnection;
|
||||
import com.knecon.fforesight.tenantcommons.model.SearchConnection;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.TenantRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.SearchConnectionRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.CreateTenantRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.TenantUser;
|
||||
import com.knecon.fforesight.tenantusermanagement.repository.TenantRepository;
|
||||
import com.knecon.fforesight.tenantusermanagement.service.TenantManagementService;
|
||||
@ -42,13 +41,19 @@ public class DevTestTenantService {
|
||||
@Value("${spring.datasource.url:}")
|
||||
private String masterJDBCURL;
|
||||
|
||||
private final DevTenantProperties devTenantProperties;
|
||||
|
||||
|
||||
@SneakyThrows
|
||||
public void createTestTenantIfNotExists(String tenantId, List<TenantUser> users) {
|
||||
|
||||
if (devTenantProperties.isRecreateTenant()) {
|
||||
tenantRepository.deleteByQuery(tenantId);
|
||||
}
|
||||
try {
|
||||
tenantManagementService.getTenant(tenantId);
|
||||
} catch (Exception e) {
|
||||
tenantRepository.deleteByQuery(tenantId);
|
||||
createTestTenant(tenantId, users);
|
||||
}
|
||||
|
||||
@ -67,25 +72,39 @@ public class DevTestTenantService {
|
||||
createDatabase(tenantsDBName, tenantsDBPassword);
|
||||
createSchema(jdbcUrl, tenantId, tenantsDBName, tenantsDBPassword);
|
||||
|
||||
var tenantRequest = TenantRequest.builder()
|
||||
var tenantRequest = CreateTenantRequest.builder()
|
||||
.tenantId(tenantId)
|
||||
.displayName(tenantId)
|
||||
.guid(UUID.randomUUID().toString())
|
||||
.defaultUsers(users != null ? users : new ArrayList<>())
|
||||
.databaseConnection(DatabaseConnection.builder()
|
||||
.driver("postgresql")
|
||||
.host("localhost")
|
||||
.port("5432")
|
||||
.database(tenantsDBName)
|
||||
.schema(tenantId)
|
||||
.username(tenantsDBName)
|
||||
.password(tenantsDBPassword)
|
||||
.host(devTenantProperties.getDb().getHost())
|
||||
.port(devTenantProperties.getDb().getPort() + "")
|
||||
.database(devTenantProperties.getDb().getDatabase())
|
||||
.schema(devTenantProperties.getDb().getSchema())
|
||||
.username(devTenantProperties.getDb().getUsername())
|
||||
.password(devTenantProperties.getDb().getPassword())
|
||||
.build())
|
||||
.searchConnection(SearchConnection.builder().hosts(Set.of("localhost")).port(9200).scheme("http").numberOfShards("1").numberOfReplicas("5").build())
|
||||
.s3StorageConnection(S3StorageConnection.builder().key("minioadmin").secret("minioadmin").bucketName("redaction").endpoint("http://localhost:9000").build())
|
||||
.build();
|
||||
.searchConnection(SearchConnectionRequest.builder()
|
||||
.hosts(Set.of("localhost"))
|
||||
.port(9200)
|
||||
.scheme("http")
|
||||
.numberOfShards("1")
|
||||
.numberOfReplicas("5")
|
||||
.build());
|
||||
|
||||
tenantManagementService.createTenant(tenantRequest);
|
||||
if (devTenantProperties.getStorage().getMode() == DevTenantProperties.DevStorageMode.S3) {
|
||||
tenantRequest.s3StorageConnection(S3StorageConnection.builder().key(devTenantProperties.getStorage().getS3().getKey())
|
||||
.secret(devTenantProperties.getStorage().getS3().getSecret())
|
||||
.region("eu-central-1")
|
||||
.bucketName(devTenantProperties.getStorage().getS3().getBucket()).endpoint(devTenantProperties.getStorage().getS3().getEndpoint()).build());
|
||||
} else {
|
||||
tenantRequest.azureStorageConnection(AzureStorageConnection.builder().connectionString(devTenantProperties.getStorage().getAzure().getConnectionString())
|
||||
.containerName(devTenantProperties.getStorage().getAzure().getContainerName()).build());
|
||||
}
|
||||
|
||||
tenantManagementService.createTenant(tenantRequest.build());
|
||||
|
||||
}
|
||||
|
||||
@ -130,11 +149,4 @@ public class DevTestTenantService {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Transactional
|
||||
public void deleteAll() {
|
||||
|
||||
tenantRepository.deleteAll();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,60 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.entity;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Entity
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Table(name = "global_smtp_configuration")
|
||||
public class GlobalSMTPConfigurationEntity {
|
||||
|
||||
@Id
|
||||
@Column(nullable = false, updatable = false)
|
||||
private String id = "singleton";
|
||||
|
||||
@Column
|
||||
private Boolean auth;
|
||||
|
||||
@Column
|
||||
private String envelopeFrom;
|
||||
|
||||
@Column
|
||||
private String fromEmail;
|
||||
|
||||
@Column
|
||||
private String fromDisplayName;
|
||||
|
||||
@Column
|
||||
private String host;
|
||||
|
||||
@Column
|
||||
private String password;
|
||||
|
||||
@Column
|
||||
private Integer port;
|
||||
|
||||
@Column
|
||||
private String replyTo;
|
||||
|
||||
@Column
|
||||
private String replyToDisplayName;
|
||||
|
||||
@Column
|
||||
private Boolean ssl;
|
||||
|
||||
@Column
|
||||
private Boolean starttls;
|
||||
|
||||
@Column
|
||||
private String userName;
|
||||
}
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.entity;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Embeddable;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Embeddable
|
||||
public class MongoDBConnectionEntity {
|
||||
|
||||
@Column(name = "mongodb_prefix")
|
||||
private String prefix;
|
||||
@Column(name = "mongodb_username")
|
||||
private String username;
|
||||
@Column(name = "mongodb_password")
|
||||
private String password;
|
||||
@Column(name = "mongodb_address")
|
||||
private String address;
|
||||
@Column(name = "mongodb_database")
|
||||
private String database;
|
||||
@Column(name = "mongodb_options")
|
||||
private String options;
|
||||
|
||||
}
|
||||
@ -23,6 +23,7 @@ public class SearchConnectionEntity {
|
||||
@Convert(converter = JSONStringSetConverter.class)
|
||||
private Set<String> hosts;
|
||||
@Column(name = "search_port")
|
||||
@Builder.Default
|
||||
private int port = 9300;
|
||||
@Column(name = "search_scheme")
|
||||
private String scheme;
|
||||
@ -34,5 +35,7 @@ public class SearchConnectionEntity {
|
||||
private String numberOfShards;
|
||||
@Column(name = "search_number_of_replicas")
|
||||
private String numberOfReplicas;
|
||||
@Column(name = "search_index_prefix")
|
||||
private String indexPrefix;
|
||||
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ package com.knecon.fforesight.tenantusermanagement.entity;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.knecon.fforesight.tenantcommons.TenantApplicationType;
|
||||
import com.knecon.fforesight.tenantusermanagement.utils.JSONMapConverter;
|
||||
|
||||
import jakarta.persistence.Basic;
|
||||
@ -10,6 +11,8 @@ import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Convert;
|
||||
import jakarta.persistence.Embedded;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
@ -45,8 +48,17 @@ public class TenantEntity {
|
||||
@Embedded
|
||||
private S3StorageConnectionEntity s3StorageConnection;
|
||||
|
||||
@Embedded
|
||||
private MongoDBConnectionEntity mongoDBConnection;
|
||||
|
||||
@Basic(fetch = FetchType.EAGER)
|
||||
@Column(columnDefinition = "text")
|
||||
@Convert(converter = JSONMapConverter.class)
|
||||
@Builder.Default
|
||||
private Map<String, Object> details = new HashMap<>();
|
||||
|
||||
@Column
|
||||
@Enumerated(EnumType.STRING)
|
||||
private TenantApplicationType applicationType;
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,17 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.events;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class TenantSyncEvent {
|
||||
|
||||
private String tenantId;
|
||||
private JsonNode payload;
|
||||
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.exception;
|
||||
|
||||
public class ConflictException extends RuntimeException {
|
||||
|
||||
public ConflictException(String message) {
|
||||
|
||||
super(message);
|
||||
}
|
||||
|
||||
|
||||
public ConflictException(String message, Throwable t) {
|
||||
|
||||
super(message, t);
|
||||
}
|
||||
|
||||
|
||||
public static ConflictException withObjectName(String objectName) {
|
||||
|
||||
return new ConflictException(String.format("An object of type %s already exists.", objectName));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
||||
@ResponseStatus(code = HttpStatus.CONFLICT)
|
||||
public class IdentityProviderExistsAlreadyException extends ConflictException {
|
||||
|
||||
private static final String IDENTITY_PROVIDER_ALREADY_EXISTS_MESSAGE = "Identity provider with alias %s already exists in keycloak.";
|
||||
|
||||
public IdentityProviderExistsAlreadyException(String identityProviderAlias) {
|
||||
super(String.format(IDENTITY_PROVIDER_ALREADY_EXISTS_MESSAGE, identityProviderAlias));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
||||
import jakarta.ws.rs.NotFoundException;
|
||||
|
||||
@ResponseStatus(code = HttpStatus.NOT_FOUND)
|
||||
public class IdentityProviderNotFoundException extends NotFoundException {
|
||||
|
||||
private static final String IDENTITY_PROVIDER_NOT_FOUND_MESSAGE = "Identity provider with alias %s not found in keycloak.";
|
||||
|
||||
public IdentityProviderNotFoundException(String identityProviderAlias) {
|
||||
super(String.format(IDENTITY_PROVIDER_NOT_FOUND_MESSAGE, identityProviderAlias));
|
||||
}
|
||||
}
|
||||
@ -1,12 +1,17 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.initializer;
|
||||
|
||||
import com.knecon.fforesight.tenantusermanagement.service.TenantManagementService;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.knecon.fforesight.tenantcommons.TenantProvider;
|
||||
import com.knecon.fforesight.tenantusermanagement.service.KeyCloakRoleManagerService;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@ -20,9 +25,17 @@ public class MigrateOnlyHook {
|
||||
|
||||
private final ApplicationContext ctx;
|
||||
|
||||
@SuppressWarnings("PMD")
|
||||
private final TenantManagementService tenantManagementService;
|
||||
|
||||
private final KeyCloakRoleManagerService keyCloakRoleManagerService;
|
||||
|
||||
|
||||
@SuppressWarnings("PMD.DoNotTerminateVM")
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
public void migrate() {
|
||||
|
||||
tenantManagementService.getTenants().forEach(tenant -> keyCloakRoleManagerService.updateRoles(tenant.getTenantId(), tenant.getApplicationType()));
|
||||
|
||||
// This should only run in post upgrade hook
|
||||
if (isMigrateOnly) {
|
||||
System.exit(SpringApplication.exit(ctx, () -> 0));
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.initializer;
|
||||
|
||||
public class TenantInitializer {
|
||||
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.initializer;
|
||||
|
||||
public class TenantInitializerService {
|
||||
|
||||
}
|
||||
@ -20,29 +20,10 @@ public class MessagingConfiguration {
|
||||
return new TopicExchange(tenantExchangeName);
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
TopicExchange userExchange(@Value("${fforesight.user-exchange.name}") String userExchangeName) {
|
||||
|
||||
return new TopicExchange(userExchangeName);
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public RabbitTemplate rabbitTemplate(final ConnectionFactory connectionFactory) {
|
||||
|
||||
final var rabbitTemplate = new RabbitTemplate(connectionFactory);
|
||||
rabbitTemplate.setMessageConverter(producerJackson2MessageConverter());
|
||||
return rabbitTemplate;
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public Jackson2JsonMessageConverter producerJackson2MessageConverter() {
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper().findAndRegisterModules();
|
||||
|
||||
return new Jackson2JsonMessageConverter(mapper);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,55 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.knecon.fforesight.tenantcommons.TenantApplicationType;
|
||||
import com.knecon.fforesight.tenantcommons.model.AzureStorageConnection;
|
||||
import com.knecon.fforesight.tenantcommons.model.DatabaseConnection;
|
||||
import com.knecon.fforesight.tenantcommons.model.S3StorageConnection;
|
||||
import com.knecon.fforesight.tenantcommons.model.MongoDBConnection;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Schema(description = "Object containing the request to create a tenant.")
|
||||
public class CreateTenantRequest {
|
||||
|
||||
@NotBlank
|
||||
@Pattern(regexp = "[A-Za-z0-9_-]*", message = "Tenant Id must match [A-Za-z0-9_-]")
|
||||
@Schema(description = "Parameter containing the ID of the tenant.")
|
||||
private String tenantId;
|
||||
@NotBlank
|
||||
@Schema(description = "Parameter containing the display name of the tenant.")
|
||||
private String displayName;
|
||||
@Schema(description = "Parameter containing the global unique ID of the tenant.")
|
||||
private String guid;
|
||||
|
||||
@Schema(description = "Parameter containing data of the database connection.")
|
||||
private DatabaseConnection databaseConnection;
|
||||
@Schema(description = "Parameter containing data of the search connection.")
|
||||
private SearchConnectionRequest searchConnection;
|
||||
@Schema(description = "Parameter containing data of the Azure storage connection.")
|
||||
private AzureStorageConnection azureStorageConnection;
|
||||
@Schema(description = "Parameter containing data of the S3 storage connection.")
|
||||
private S3StorageConnection s3StorageConnection;
|
||||
@Schema(description = "Parameter containing data of the MongoDB connection.")
|
||||
private MongoDBConnection mongoDBConnection;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Parameter containing a list of users of the tenant.")
|
||||
private List<TenantUser> defaultUsers = new ArrayList<>();
|
||||
|
||||
@Schema(description = "Parameter containing the application type of the tenant.")
|
||||
private TenantApplicationType applicationType;
|
||||
|
||||
}
|
||||
@ -7,6 +7,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(description = "Object containing the request to create a profile.")
|
||||
public class CreateUserRequest {
|
||||
|
||||
@Schema(description = "Email of user.")
|
||||
@ -21,7 +22,10 @@ public class CreateUserRequest {
|
||||
@Schema(description = "Last name of user.")
|
||||
private String lastName;
|
||||
|
||||
@Schema(description = "The list of RED_* roles.")
|
||||
@Schema(description = "Roles to assign to user.")
|
||||
private Set<String> roles = new HashSet<>();
|
||||
|
||||
@Schema(description = "Whether a set password mail should be sent")
|
||||
private boolean sendSetPasswordMail;
|
||||
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.model;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
@ -7,8 +8,10 @@ import lombok.NoArgsConstructor;
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "Object containing data about the deployment key.")
|
||||
public class DeploymentKeyResponse {
|
||||
|
||||
@Schema(description = "Parameter containing the deployment key.")
|
||||
private String value;
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.model;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public interface ExtensibleModel {
|
||||
|
||||
void setNotMappedFields(Map<String, String> notMappedFields);
|
||||
Map<String, String> getNotMappedFields();
|
||||
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.model;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
@ -11,10 +12,14 @@ import lombok.NoArgsConstructor;
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EqualsAndHashCode
|
||||
@Schema(description = "Object containing data about general configurations.")
|
||||
public class GeneralConfigurationModel {
|
||||
|
||||
@Schema(description = "Parameter indicating if the 'ForgotPassword' function is enabled.")
|
||||
private boolean forgotPasswordFunctionEnabled;
|
||||
@Schema(description = "Parameter containing the auxiliary name.")
|
||||
private String auxiliaryName;
|
||||
@Schema(description = "Parameter containing the display name of the application.")
|
||||
private String displayName;
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,128 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.model;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Schema(description = "Object containing information about an identity provider configuration.")
|
||||
public class IdentityProviderConfigModel implements ExtensibleModel {
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Whether the external identity provider is allowed to create a new identifier to represent the principal")
|
||||
private Boolean allowCreate = true;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Display order : It defines the order of the providers in GUI (for example, on the Login page). The lowest number will be applied first.")
|
||||
private Integer guiOrder = 0;
|
||||
|
||||
@Schema(description = "Service provider entity ID : It is used to uniquely identify this SAML Service Provider.")
|
||||
private String entityId;
|
||||
|
||||
@Schema(description = "Identity provider entity ID : It is used to validate the Issuer for received SAML assertions. If empty, no Issuer validation is performed.")
|
||||
private String idpEntityId;
|
||||
|
||||
@Schema(description = "The Url that must be used to send authentication requests (SAML AuthnRequest).")
|
||||
private String singleSignOnServiceUrl;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "The Url that must be used to send logout requests.")
|
||||
private String singleLogoutServiceUrl = "";
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Name of the Attribute Consuming Service profile to advertise in the SP metadata.")
|
||||
private String attributeConsumingServiceName = "";
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Backchannel logout : Does the external IDP support backchannel logout?")
|
||||
private Boolean backchannelSupported = false;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Specifies the URI reference corresponding to a name identifier format.")
|
||||
private IdentityProviderNameIDPolicyFormat nameIDPolicyFormat = IdentityProviderNameIDPolicyFormat.PERSISTENT;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Used to identify and track external users from the assertion. Default is using Subject NameID, alternatively can be set up as identifying attribute.")
|
||||
private IdentityProviderPrincipalType principalType = IdentityProviderPrincipalType.SUBJECT;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "HTTP-POST binding response : Indicates whether to respond to requests using HTTP-POST binding. If false, HTTP-REDIRECT binding will be used.")
|
||||
private Boolean postBindingResponse = false;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "HTTP-POST binding for AuthnRequest : Indicates whether the AuthnRequest must be sent using HTTP-POST binding. If false, HTTP-REDIRECT binding will be used.")
|
||||
private Boolean postBindingAuthnRequest = false;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "HTTP-POST binding logout : Indicates whether to respond to requests using HTTP-POST binding. If false, HTTP-REDIRECT binding will be used.")
|
||||
private Boolean postBindingLogout = false;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Indicates whether the identity provider expects a signed AuthnRequest.")
|
||||
private Boolean wantAuthnRequestsSigned = false;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Indicates whether this service provider expects a signed Assertion.")
|
||||
private Boolean wantAssertionsSigned = false;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Indicates whether this service provider expects an encrypted Assertion.")
|
||||
private Boolean wantAssertionsEncrypted = false;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Force authentication : Indicates whether the identity provider must authenticate the presenter directly rather than rely on a previous security context.")
|
||||
private Boolean forceAuthn = false;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Enable/disable signature validation of external IDP signatures.")
|
||||
private Boolean validateSignature = false;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Sign service provider metadata : Enable/disable signature of the provider SAML metadata.")
|
||||
private Boolean signSpMetadata = false;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Pass subject : During login phase, forward an optional login_hint query parameter to SAML AuthnRequest's Subject.")
|
||||
private Boolean loginHint = false;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Clock skew in seconds that is tolerated when validating identity provider tokens. Default value is zero.")
|
||||
private Integer allowedClockSkew = 0;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Index of the Attribute Consuming Service profile to request during authentication.")
|
||||
private Integer attributeConsumingServiceIndex = 0;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "If hidden, login with this provider is possible only if requested explicitly, for example using the 'kc_idp_hint' parameter.")
|
||||
private Boolean hideOnLoginPage = false;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Default sync mode for all mappers. The sync mode determines when user data will be synced using the mappers. Possible values are: 'legacy' to keep the behaviour before this option was introduced, 'import' to only import the user once during first login of the user with this identity provider, 'force' to always update the user during every login with this identity provider.")
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
private IdentityProviderSyncMode syncMode = IdentityProviderSyncMode.IMPORT;
|
||||
|
||||
@Schema(description = "The signature algorithm to use to sign documents. Note that 'SHA1' based algorithms are deprecated and can be removed in the future. It is recommended to stick to some more secure algorithm instead of '*_SHA1'.")
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
private IdentityProviderSignatureAlgorithm signatureAlgorithm;
|
||||
|
||||
@Schema(description = "SAML signature key name : Signed SAML documents contain identification of signing key in KeyName element. For Keycloak / RH-SSO counter-party, use KEY_ID, for MS AD FS use CERT_SUBJECT, for others check and use NONE if no other option works.")
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
private IdentityProviderSAMLSignatureKeyName xmlSigKeyInfoKeyNameTransformer;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "All not mapped configurations")
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
private Map<String, String> notMappedFields = new HashMap<>();
|
||||
|
||||
}
|
||||
@ -0,0 +1,65 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.model;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Schema(description = "Create request for the identity provider configuration.")
|
||||
public class IdentityProviderConfigRequest {
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Whether the external identity provider is allowed to create a new identifier to represent the principal (default: true)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
private Boolean allowCreate = true;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Display order : It defines the order of the providers in GUI (for example, on the Login page). The lowest number will be applied first. (default: 0)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
private Integer guiOrder = 0;
|
||||
|
||||
@Schema(description = "Service provider entity ID : It is used to uniquely identify this SAML Service Provider.", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull
|
||||
private String entityId;
|
||||
|
||||
@Schema(description = "Identity provider entity ID : It is used to validate the Issuer for received SAML assertions. If empty, no Issuer validation is performed.", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull
|
||||
private String idpEntityId;
|
||||
|
||||
@Schema(description = "The Url that must be used to send authentication requests (SAML AuthnRequest).", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull
|
||||
private String singleSignOnServiceUrl;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Specifies the URI reference corresponding to a name identifier format. (default: PERSISTENT)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
private IdentityProviderNameIDPolicyFormat nameIDPolicyFormat = IdentityProviderNameIDPolicyFormat.PERSISTENT;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Used to identify and track external users from the assertion. Default is using Subject NameID, alternatively can be set up as identifying attribute. (default: SUBJECT)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
private IdentityProviderPrincipalType principalType = IdentityProviderPrincipalType.SUBJECT;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "HTTP-POST binding response : Indicates whether to respond to requests using HTTP-POST binding. If false, HTTP-REDIRECT binding will be used. (default: false)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
private Boolean postBindingResponse = false;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "HTTP-POST binding for AuthnRequest : Indicates whether the AuthnRequest must be sent using HTTP-POST binding. If false, HTTP-REDIRECT binding will be used. (default: false)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
private Boolean postBindingAuthnRequest = false;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Indicates whether the identity provider expects a signed AuthnRequest. (default: false)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
private Boolean wantAuthnRequestsSigned = false;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "The signature algorithm to use to sign documents. Note that 'SHA1' based algorithms are deprecated and can be removed in the future. It is recommended to stick to some more secure algorithm instead of '*_SHA1'. (only used when wantAuthnRequestsSigned is true, default: RSA_SHA256)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
private IdentityProviderSignatureAlgorithm signatureAlgorithm = IdentityProviderSignatureAlgorithm.RSA_SHA256;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "SAML signature key name : Signed SAML documents contain identification of signing key in KeyName element. For Keycloak / RH-SSO counter-party, use KEY_ID, for MS AD FS use CERT_SUBJECT, for others check and use NONE if no other option works. (only used when wantAuthnRequestsSigned is true, default: KEY_ID)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
private IdentityProviderSAMLSignatureKeyName xmlSigKeyInfoKeyNameTransformer = IdentityProviderSAMLSignatureKeyName.KEY_ID;
|
||||
|
||||
}
|
||||
@ -1,5 +1,8 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
@ -7,12 +10,10 @@ import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class SimpleTenantResponse {
|
||||
|
||||
private String tenantId;
|
||||
private String displayName;
|
||||
private String guid;
|
||||
@AllArgsConstructor
|
||||
public class IdentityProviderList {
|
||||
|
||||
@Builder.Default
|
||||
private List<IdentityProviderModel> identityProviders = new ArrayList<>();
|
||||
}
|
||||
@ -0,0 +1,69 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.model;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.Builder;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Schema(description = "Object containing information about an identity provider.")
|
||||
public class IdentityProviderModel implements ExtensibleModel {
|
||||
|
||||
@Schema(description = "Alias of the identity provider used for identification")
|
||||
private String alias;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Configuration of the identity provider")
|
||||
private IdentityProviderConfigModel config = new IdentityProviderConfigModel();
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Display name of the identity provider in keycloak")
|
||||
private String displayName = "";
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "The ID of the SSO technology provider")
|
||||
private String providerId = "saml";
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Enable/disable if tokens must be stored after authenticating users.")
|
||||
private Boolean storeToken = false;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Stored tokens readable : Enable/disable if new users can read any stored tokens. This assigns the broker.read-token role.")
|
||||
private Boolean addReadTokenRoleOnCreate = false;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "If enabled, email provided by this provider is not verified even if verification is enabled for the realm.")
|
||||
private Boolean trustEmail = false;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Account linking only : If true, users cannot log in through this provider. They can only link to this provider. This is useful if you don't want to allow login from the provider, but want to integrate with a provider")
|
||||
private Boolean linkOnly = false;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "First login flow : Alias of authentication flow, which is triggered after first login with this identity provider. Term 'First Login' means that no Keycloak account is currently linked to the authenticated identity provider account.")
|
||||
private String firstBrokerLoginFlowAlias = "first broker login";
|
||||
|
||||
@Builder.Default
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
private Map<String, String> notMappedFields = new HashMap<>();
|
||||
|
||||
|
||||
public void setSpecificDefaults() {
|
||||
|
||||
this.setStoreToken(true);
|
||||
this.setTrustEmail(true);
|
||||
this.getConfig().setSyncMode(IdentityProviderSyncMode.FORCE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,43 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
|
||||
import jakarta.ws.rs.BadRequestException;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public enum IdentityProviderNameIDPolicyFormat {
|
||||
PERSISTENT("urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"),
|
||||
TRANSIENT("urn:oasis:names:tc:SAML:2.0:nameid-format:transient"),
|
||||
EMAIL("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"),
|
||||
KERBEROS("urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos"),
|
||||
X_509_SUBJECT_NAME("urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName"),
|
||||
WINDOWS_DOMAIN_QUALIFIED_NAME("urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName"),
|
||||
UNSPECIFIED("urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified");
|
||||
|
||||
private final String representation;
|
||||
|
||||
|
||||
IdentityProviderNameIDPolicyFormat(String representation) {
|
||||
|
||||
this.representation = representation;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@JsonValue
|
||||
public String toString() {
|
||||
|
||||
return representation;
|
||||
}
|
||||
|
||||
public static IdentityProviderNameIDPolicyFormat fromRepresentation(String value) {
|
||||
|
||||
for (IdentityProviderNameIDPolicyFormat v : values()) {
|
||||
if (v.getRepresentation().equalsIgnoreCase(value)) {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
throw new BadRequestException("Unsupported identity provider name ID policy format");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.model;
|
||||
|
||||
|
||||
import jakarta.ws.rs.BadRequestException;
|
||||
|
||||
public enum IdentityProviderPrincipalType {
|
||||
SUBJECT,
|
||||
ATTRIBUTE,
|
||||
FRIENDLY_ATTRIBUTE;
|
||||
|
||||
|
||||
public static IdentityProviderPrincipalType fromStringValue(String value) {
|
||||
|
||||
for (IdentityProviderPrincipalType v : values()) {
|
||||
if (v.toString().equalsIgnoreCase(value)) {
|
||||
return v;
|
||||
}
|
||||
if (value.equalsIgnoreCase("Subject NameID")) {
|
||||
return SUBJECT;
|
||||
}
|
||||
}
|
||||
throw new BadRequestException("Unsupported identity provider principal type");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.model;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Schema(description = "Create or update request for an identity provider.")
|
||||
public class IdentityProviderRequest {
|
||||
|
||||
@Schema(description = "Alias of the identity provider used for identification", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String alias;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Configuration of the identity provider", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull
|
||||
private @Valid IdentityProviderConfigRequest config = new IdentityProviderConfigRequest();
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Display name of the identity provider in keycloak (optional, fallbacks to alias if empty)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
private String displayName = "";
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "The ID of the SSO technology provider (default: saml)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
private String providerId = "saml";
|
||||
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.model;
|
||||
|
||||
public enum IdentityProviderSAMLSignatureKeyName {
|
||||
NONE,
|
||||
KEY_ID,
|
||||
CERT_SUBJECT
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.model;
|
||||
|
||||
public enum IdentityProviderSignatureAlgorithm {
|
||||
RSA_SHA1,
|
||||
RSA_SHA256,
|
||||
RSA_SHA256_MGF1,
|
||||
RSA_SHA512,
|
||||
RSA_SHA512_MGF1,
|
||||
DSA_SHA1
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.model;
|
||||
|
||||
public enum IdentityProviderSyncMode {
|
||||
INHERIT,
|
||||
IMPORT,
|
||||
LEGACY,
|
||||
FORCE
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.model;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Schema(description = "Create request for an identity provider using a SAML entity descriptor for a faster creation process.")
|
||||
public class IdentityProviderWithDescriptorRequest {
|
||||
|
||||
@Schema(description = "Alias of the identity provider used for identification", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull
|
||||
private String alias;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Display name of the identity provider in keycloak (optional, fallbacks to alias if empty)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
private String displayName = "";
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "The ID of the SSO technology provider (default: saml)", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
private String providerId = "saml";
|
||||
|
||||
@Schema(description = "The SAML entity descriptor as a URL to external IDP metadata, used to fill other fields for a faster identity provider creation process", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull
|
||||
private String samlEntityDescriptorURL;
|
||||
|
||||
@Schema(description = "Service provider entity ID that will be used to uniquely identify this SAML Service Provider", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull
|
||||
private String entityId;
|
||||
}
|
||||
@ -1,11 +1,21 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.model;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@Schema(description = "Object containing the request to reset a password.")
|
||||
public class ResetPasswordRequest {
|
||||
|
||||
@Schema(description = "Parameter containing the new password.")
|
||||
private String password;
|
||||
@Schema(description = "Parameter indicating if the password is temporary.")
|
||||
private boolean temporary;
|
||||
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.model;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
@ -9,20 +10,34 @@ import lombok.NoArgsConstructor;
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Schema(description = "Object containing the SMTP configuration.")
|
||||
public class SMTPConfiguration {
|
||||
|
||||
@Schema(description = "Parameter containing the ID of the SMTP configuration.")
|
||||
private String id;
|
||||
@Schema(description = "Parameter containing the email of the sender.")
|
||||
private String from;
|
||||
@Schema(description = "Parameter containing the display name of the sender.")
|
||||
private String fromDisplayName;
|
||||
@Schema(description = "Parameter containing the email of the sender on the envelope.")
|
||||
private String envelopeFrom;
|
||||
@Schema(description = "Parameter containing the host.")
|
||||
private String host;
|
||||
@Schema(description = "Parameter containing the port.")
|
||||
private Integer port;
|
||||
@Schema(description = "Parameter containing the email to reply.")
|
||||
private String replyTo;
|
||||
@Schema(description = "Parameter containing the display name of the email to reply.")
|
||||
private String replyToDisplayName;
|
||||
@Schema(description = "Parameter indicating the usage of the SSL protocol.")
|
||||
private boolean ssl;
|
||||
@Schema(description = "Parameter indicating the usage of the STARTTLS protocol.")
|
||||
private boolean starttls;
|
||||
@Schema(description = "Parameter indicating the authentication of the client.")
|
||||
private boolean auth;
|
||||
@Schema(description = "Parameter containing the user.")
|
||||
private String user;
|
||||
@Schema(description = "Parameter containing the password.")
|
||||
private String password;
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,26 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.model;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Schema(description = "Object containing a simplified version of the SMTP test connection response.")
|
||||
public class SMTPResponse {
|
||||
|
||||
@Schema(description = "Parameter containing status code of the response.")
|
||||
private int statusCode;
|
||||
@Schema(description = "Parameter containing the reason phrase of the response.")
|
||||
private String reasonPhrase;
|
||||
@Schema(description = "Parameter containing the email of the recipient.")
|
||||
private String recipientEmail;
|
||||
@Schema(description = "Parameter flag that specifies if the admin email was found.")
|
||||
private boolean adminEmail;
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.model;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class SearchConnectionRequest {
|
||||
|
||||
private Set<String> hosts;
|
||||
private int port;
|
||||
private String scheme;
|
||||
private String username;
|
||||
private String password;
|
||||
private String numberOfShards;
|
||||
private String numberOfReplicas;
|
||||
|
||||
}
|
||||
@ -1,40 +0,0 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.knecon.fforesight.tenantcommons.model.AzureStorageConnection;
|
||||
import com.knecon.fforesight.tenantcommons.model.DatabaseConnection;
|
||||
import com.knecon.fforesight.tenantcommons.model.S3StorageConnection;
|
||||
import com.knecon.fforesight.tenantcommons.model.SearchConnection;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class TenantRequest {
|
||||
|
||||
@NotBlank
|
||||
@Pattern(regexp = "[A-Za-z0-9_-]*", message = "Tenant Id must match [A-Za-z0-9_-]")
|
||||
private String tenantId;
|
||||
@NotBlank
|
||||
private String displayName;
|
||||
private String guid;
|
||||
|
||||
private DatabaseConnection databaseConnection;
|
||||
private SearchConnection searchConnection;
|
||||
private AzureStorageConnection azureStorageConnection;
|
||||
private S3StorageConnection s3StorageConnection;
|
||||
|
||||
@Builder.Default
|
||||
private List<TenantUser> defaultUsers = new ArrayList<>();
|
||||
|
||||
}
|
||||
@ -3,6 +3,7 @@ package com.knecon.fforesight.tenantusermanagement.model;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
@ -12,15 +13,22 @@ import lombok.NoArgsConstructor;
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "Object containing the tenant user data.")
|
||||
public class TenantUser {
|
||||
|
||||
@Schema(description = "Parameter containing the username of the tenant user.")
|
||||
private String username;
|
||||
@Schema(description = "Parameter containing the password of the tenant user.")
|
||||
private String password;
|
||||
@Schema(description = "Parameter containing the email of the tenant user.")
|
||||
private String email;
|
||||
@Schema(description = "Parameter containing the first name of the tenant user.")
|
||||
private String firstName;
|
||||
@Schema(description = "Parameter containing the last name of the tenant user.")
|
||||
private String lastName;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Parameter containing the roles assigned to the tenant user.")
|
||||
private Set<String> roles = new HashSet<>();
|
||||
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ import lombok.NoArgsConstructor;
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "Object containing the request to update a profile.")
|
||||
public class UpdateMyProfileRequest {
|
||||
|
||||
@Schema(description = "Email of user.")
|
||||
|
||||
@ -13,6 +13,7 @@ import lombok.NoArgsConstructor;
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@Schema(description = "Object containing the request to update a profile.")
|
||||
public class UpdateProfileRequest {
|
||||
|
||||
@Schema(description = "Email of user.")
|
||||
@ -25,7 +26,7 @@ public class UpdateProfileRequest {
|
||||
private String lastName;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Roles.")
|
||||
@Schema(description = "Roles to assign to the user.")
|
||||
private Set<String> roles = new HashSet<>();
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,43 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.knecon.fforesight.tenantcommons.model.AzureStorageConnection;
|
||||
import com.knecon.fforesight.tenantcommons.model.DatabaseConnection;
|
||||
import com.knecon.fforesight.tenantcommons.model.MongoDBConnection;
|
||||
import com.knecon.fforesight.tenantcommons.model.S3StorageConnection;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Schema(description = "Object containing the request to update a tenant.")
|
||||
public class UpdateTenantRequest {
|
||||
|
||||
@NotBlank
|
||||
@Schema(description = "Parameter containing the display name of the tenant.")
|
||||
private String displayName;
|
||||
@Schema(description = "Parameter containing the global unique ID of the tenant.")
|
||||
private String guid;
|
||||
|
||||
@Schema(description = "Parameter containing data of the database connection.")
|
||||
private DatabaseConnection databaseConnection;
|
||||
@Schema(description = "Parameter containing data of the search connection.")
|
||||
private SearchConnectionRequest searchConnection;
|
||||
@Schema(description = "Parameter containing data of the Azure storage connection.")
|
||||
private AzureStorageConnection azureStorageConnection;
|
||||
@Schema(description = "Parameter containing data of the S3 storage connection.")
|
||||
private S3StorageConnection s3StorageConnection;
|
||||
@Schema(description = "Parameter containing data of the MongoDB connection.")
|
||||
private MongoDBConnection mongoDBConnection;
|
||||
|
||||
}
|
||||
@ -4,6 +4,7 @@ import java.io.Serializable;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
@ -15,21 +16,29 @@ import lombok.NoArgsConstructor;
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode(of = "userId")
|
||||
@Schema(description = "Object containing the user data.")
|
||||
public class User implements Serializable {
|
||||
|
||||
@Schema(description = "Parameter containing the ID of the user.")
|
||||
private String userId;
|
||||
|
||||
@Schema(description = "Parameter containing the username of the user.")
|
||||
private String username;
|
||||
|
||||
@Schema(description = "Parameter containing the email of the user.")
|
||||
private String email;
|
||||
|
||||
@Schema(description = "Parameter containing the first name of the user.")
|
||||
private String firstName;
|
||||
|
||||
@Schema(description = "Parameter containing the last name of the user.")
|
||||
private String lastName;
|
||||
|
||||
@Schema(description = "Parameter indicating if the user is activated.")
|
||||
private boolean isActive;
|
||||
|
||||
@Builder.Default
|
||||
@Schema(description = "Parameter containing the roles of the user.")
|
||||
private Set<String> roles = new TreeSet<>();
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,60 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.permissions;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.knecon.fforesight.tenantusermanagement.model.User;
|
||||
|
||||
public final class ApplicationRoles {
|
||||
|
||||
public static final String KNECON_ADMIN_ROLE = "KNECON_ADMIN";
|
||||
public static final String KNECON_SUPPORT_ROLE = "KNECON_SUPPORT";
|
||||
public static final String RED_USER_ROLE = "RED_USER";
|
||||
public static final String RED_MANAGER_ROLE = "RED_MANAGER";
|
||||
public static final String RED_ADMIN_ROLE = "RED_ADMIN";
|
||||
public static final String RED_USER_ADMIN_ROLE = "RED_USER_ADMIN";
|
||||
|
||||
public static final Set<String> KNECON_ROLES = Set.of(KNECON_ADMIN_ROLE, KNECON_SUPPORT_ROLE);
|
||||
public static final Set<String> RED_ROLES = Set.of(RED_USER_ROLE, RED_MANAGER_ROLE, RED_ADMIN_ROLE, RED_USER_ADMIN_ROLE);
|
||||
|
||||
|
||||
public static final Predicate<User> KNECON_ROLE_FILTER = user -> {
|
||||
Set<String> filteredRoles = user.getRoles()
|
||||
.stream()
|
||||
.filter(ApplicationRoles::isNoKneconRole)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
if (!user.getRoles().isEmpty() && filteredRoles.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
user.setRoles(filteredRoles);
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
public static boolean isNoKneconRole(String role) {
|
||||
|
||||
return !KNECON_ROLES.contains(role);
|
||||
}
|
||||
|
||||
|
||||
public static boolean isKneconRole(String role) {
|
||||
|
||||
return KNECON_ROLES.contains(role);
|
||||
}
|
||||
|
||||
|
||||
public static boolean isNoREDRole(String role) {
|
||||
|
||||
return !RED_ROLES.contains(role);
|
||||
}
|
||||
|
||||
|
||||
public static boolean isREDRole(String role) {
|
||||
|
||||
return RED_ROLES.contains(role);
|
||||
}
|
||||
|
||||
}
|
||||
@ -19,10 +19,16 @@ public class UserManagementPermissions {
|
||||
public static final String CREATE_TENANT = "fforesight-create-tenant";
|
||||
public static final String GET_TENANTS = "fforesight-get-tenants";
|
||||
public static final String UPDATE_TENANT = "fforesight-update-tenant";
|
||||
|
||||
public static final String DELETE_TENANT = "fforesight-delete-tenant";
|
||||
public static final String DEPLOYMENT_INFO = "fforesight-deployment-info";
|
||||
|
||||
// SMTP
|
||||
public static final String READ_SMTP_CONFIGURATION = "fforesight-read-smtp-configuration";
|
||||
public static final String WRITE_SMTP_CONFIGURATION = "fforesight-write-smtp-configuration";
|
||||
|
||||
// Identity provider
|
||||
public static final String READ_IDENTITY_PROVIDER_CONFIGURATION = "fforesight-read-identity-provider-config";
|
||||
public static final String WRITE_IDENTITY_PROVIDER_CONFIGURATION = "fforesight-write-identity-provider-config";
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.properties;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.knecon.fforesight.tenantusermanagement.model.KCRoleMapping;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ApplicationTypeProperties {
|
||||
|
||||
private String applicationClientId;
|
||||
private String applicationName;
|
||||
private Integer tenantAccessTokenLifeSpan = 300;
|
||||
private Integer accessTokenLifeSpan = 1800;
|
||||
private Integer ssoSessionIdleTimeout = 86400;
|
||||
private Integer refreshTokenMaxReuse;
|
||||
private String defaultTheme = "redaction";
|
||||
private List<String> validRedirectUris = new ArrayList<>();
|
||||
private KCRoleMapping kcRoleMapping = new KCRoleMapping();
|
||||
private String loginTheme;
|
||||
private String appPrefix;
|
||||
|
||||
}
|
||||
@ -1,11 +1,13 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.properties;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import com.knecon.fforesight.tenantusermanagement.model.KCRoleMapping;
|
||||
import com.knecon.fforesight.tenantcommons.TenantApplicationType;
|
||||
import com.knecon.fforesight.tenantcommons.TenantContext;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@ -14,20 +16,17 @@ import lombok.Data;
|
||||
public class TenantUserManagementProperties {
|
||||
|
||||
private String serverUrl;
|
||||
private String publicServerUrl;
|
||||
private String realm;
|
||||
private String applicationClientId;
|
||||
private String swaggerClientId ="swagger-ui-client";
|
||||
private String realm = "master";
|
||||
private String swaggerClientId = "swagger-ui-client";
|
||||
private String swaggerClientSecret;
|
||||
private String clientId;
|
||||
private String clientSecret;
|
||||
private String publicServerUrl;
|
||||
private String basePath = "/";
|
||||
private String basePathV2 = "/api";
|
||||
private int connectionPoolSize = 10;
|
||||
private String applicationName;
|
||||
private Integer accessTokenLifeSpan = 1800;
|
||||
private Integer ssoSessionIdleTimeout = 86400;
|
||||
private String defaultTheme = "redaction";
|
||||
private List<String> validRedirectUris = new ArrayList<>();
|
||||
private KCRoleMapping kcRoleMapping = new KCRoleMapping();
|
||||
|
||||
private Map<String, ApplicationTypeProperties> applicationTypes = new HashMap<>();
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,15 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.repository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import com.knecon.fforesight.tenantusermanagement.entity.GlobalSMTPConfigurationEntity;
|
||||
|
||||
public interface GlobalSMTPConfigurationRepository extends JpaRepository<GlobalSMTPConfigurationEntity, String> {
|
||||
|
||||
default Optional<GlobalSMTPConfigurationEntity> findSingleton() {
|
||||
|
||||
return findById("singleton");
|
||||
}
|
||||
}
|
||||
@ -2,15 +2,33 @@ package com.knecon.fforesight.tenantusermanagement.repository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
import com.knecon.fforesight.tenantcommons.TenantApplicationType;
|
||||
import com.knecon.fforesight.tenantusermanagement.entity.TenantEntity;
|
||||
|
||||
import jakarta.transaction.Transactional;
|
||||
|
||||
public interface TenantRepository extends JpaRepository<TenantEntity, String> {
|
||||
|
||||
@Query("select t from TenantEntity t where t.tenantId = :tenantId")
|
||||
Optional<TenantEntity> findByTenantId(@Param("tenantId") String tenantId);
|
||||
|
||||
|
||||
@CacheEvict(value = "tenantApplicationType", key = "#tenantId")
|
||||
@Transactional
|
||||
@Modifying(clearAutomatically = true, flushAutomatically = true)
|
||||
@Query("delete from TenantEntity t where t.id = :tenantId ")
|
||||
void deleteByQuery(String tenantId);
|
||||
|
||||
|
||||
@Cacheable(value = "tenantApplicationType", key = "#tenantId")
|
||||
@Query("select t.applicationType from TenantEntity t where t.tenantId = :tenantId")
|
||||
Optional<TenantApplicationType> findApplicationTypeByTenantId(@Param("tenantId") String tenantId);
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,173 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.service;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.HttpStatusCode;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import com.knecon.fforesight.tenantusermanagement.model.SMTPResponse;
|
||||
|
||||
import jakarta.mail.Address;
|
||||
import jakarta.mail.Message;
|
||||
import jakarta.mail.MessagingException;
|
||||
import jakarta.mail.Multipart;
|
||||
import jakarta.mail.Session;
|
||||
import jakarta.mail.Transport;
|
||||
import jakarta.mail.internet.AddressException;
|
||||
import jakarta.mail.internet.InternetAddress;
|
||||
import jakarta.mail.internet.MimeBodyPart;
|
||||
import jakarta.mail.internet.MimeMessage;
|
||||
import jakarta.mail.internet.MimeMultipart;
|
||||
import jakarta.mail.internet.MimeUtility;
|
||||
import jakarta.ws.rs.BadRequestException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class EmailService {
|
||||
|
||||
private final String AUTHENTICATION_UNSUCCESSFUL = "Authentication unsuccessful";
|
||||
private final String WRONG_USERNAME_PASSWORD = "Username and Password not accepted";
|
||||
|
||||
|
||||
public SMTPResponse.SMTPResponseBuilder send(Map<String, String> config, String address, String subject, String textBody) {
|
||||
|
||||
Transport transport = null;
|
||||
try {
|
||||
|
||||
Properties props = new Properties();
|
||||
|
||||
if (config.containsKey("host")) {
|
||||
props.setProperty("mail.smtp.host", config.get("host"));
|
||||
}
|
||||
|
||||
boolean auth = "true".equals(config.get("auth"));
|
||||
boolean ssl = "true".equals(config.get("ssl"));
|
||||
boolean starttls = "true".equals(config.get("starttls"));
|
||||
|
||||
if (config.containsKey("port") && config.get("port") != null) {
|
||||
props.setProperty("mail.smtp.port", config.get("port"));
|
||||
}
|
||||
|
||||
if (auth) {
|
||||
props.setProperty("mail.smtp.auth", "true");
|
||||
}
|
||||
|
||||
if (ssl) {
|
||||
props.setProperty("mail.smtp.ssl.enable", "true");
|
||||
}
|
||||
|
||||
if (starttls) {
|
||||
props.setProperty("mail.smtp.starttls.enable", "true");
|
||||
}
|
||||
|
||||
if (ssl || starttls) {
|
||||
props.put("mail.smtp.ssl.protocols", getSupportedSslProtocols());
|
||||
}
|
||||
|
||||
props.setProperty("mail.smtp.timeout", "10000");
|
||||
props.setProperty("mail.smtp.connectiontimeout", "10000");
|
||||
|
||||
String from = config.get("from");
|
||||
String fromDisplayName = config.get("fromDisplayName");
|
||||
String replyTo = config.get("replyTo");
|
||||
String replyToDisplayName = config.get("replyToDisplayName");
|
||||
String envelopeFrom = config.get("envelopeFrom");
|
||||
|
||||
Session session = Session.getInstance(props);
|
||||
|
||||
Multipart multipart = new MimeMultipart("alternative");
|
||||
|
||||
MimeBodyPart textPart = new MimeBodyPart();
|
||||
textPart.setText(textBody, "UTF-8");
|
||||
multipart.addBodyPart(textPart);
|
||||
|
||||
Message msg = new MimeMessage(session);
|
||||
msg.setFrom(toInternetAddress(from, fromDisplayName));
|
||||
|
||||
msg.setReplyTo(new Address[]{toInternetAddress(from, fromDisplayName)});
|
||||
|
||||
if (StringUtils.isNotBlank(replyTo)) {
|
||||
msg.setReplyTo(new Address[]{toInternetAddress(replyTo, replyToDisplayName)});
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(envelopeFrom)) {
|
||||
props.setProperty("mail.smtp.from", envelopeFrom);
|
||||
}
|
||||
|
||||
msg.setHeader("To", address);
|
||||
msg.setSubject(MimeUtility.encodeText(subject, StandardCharsets.UTF_8.name(), null));
|
||||
msg.setContent(multipart);
|
||||
msg.saveChanges();
|
||||
msg.setSentDate(new Date());
|
||||
|
||||
transport = session.getTransport("smtp");
|
||||
|
||||
if (auth) {
|
||||
transport.connect(config.get("user"), config.get("password"));
|
||||
} else {
|
||||
transport.connect();
|
||||
}
|
||||
|
||||
transport.sendMessage(msg, new InternetAddress[]{new InternetAddress(address)});
|
||||
|
||||
return SMTPResponse.builder()
|
||||
.statusCode(200);
|
||||
} catch (Exception e) {
|
||||
if (e.getMessage().contains(AUTHENTICATION_UNSUCCESSFUL) || e.getMessage().contains(WRONG_USERNAME_PASSWORD)) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, AUTHENTICATION_UNSUCCESSFUL);
|
||||
} else {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage(), e);
|
||||
}
|
||||
} finally {
|
||||
if (transport != null) {
|
||||
try {
|
||||
transport.close();
|
||||
} catch (MessagingException e) {
|
||||
log.warn("Failed to close transport", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected InternetAddress toInternetAddress(String email, String displayName) throws UnsupportedEncodingException, AddressException, BadRequestException {
|
||||
|
||||
if (StringUtils.isBlank(email)) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Please provide a valid address");
|
||||
}
|
||||
|
||||
if (StringUtils.isBlank(displayName)) {
|
||||
return new InternetAddress(email);
|
||||
}
|
||||
|
||||
return new InternetAddress(email, displayName, "utf-8");
|
||||
}
|
||||
|
||||
|
||||
private String getSupportedSslProtocols() {
|
||||
|
||||
try {
|
||||
String[] protocols = SSLContext.getDefault().getSupportedSSLParameters().getProtocols();
|
||||
if (protocols != null) {
|
||||
return String.join(" ", protocols);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to get list of supported SSL protocols", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.service;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.knecon.fforesight.tenantusermanagement.model.SMTPConfiguration;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class EnvironmentSMTPConfigurationProvider {
|
||||
|
||||
private final Environment environment;
|
||||
|
||||
public static final Integer KEYCLOAK_DEFAULT_PORT = 25;
|
||||
public static final String SMTP_DEFAULT_HOST = "SMTP_DEFAULT_HOST";
|
||||
public static final String SMTP_DEFAULT_PORT = "SMTP_DEFAULT_PORT";
|
||||
public static final String SMTP_DEFAULT_SENDER_EMAIL = "SMTP_DEFAULT_SENDER_EMAIL";
|
||||
public static final String SMTP_DEFAULT_SENDER_NAME = "SMTP_DEFAULT_SENDER_NAME";
|
||||
public static final String SMTP_DEFAULT_REPLY_EMAIL = "SMTP_DEFAULT_REPLY_EMAIL";
|
||||
public static final String SMTP_DEFAULT_REPLY_NAME = "SMTP_DEFAULT_REPLY_NAME";
|
||||
public static final String SMTP_DEFAULT_SENDER_ENVELOPE = "SMTP_DEFAULT_SENDER_ENVELOPE";
|
||||
public static final String SMTP_DEFAULT_SSL = "SMTP_DEFAULT_SSL";
|
||||
public static final String SMTP_DEFAULT_STARTTLS = "SMTP_DEFAULT_STARTTLS";
|
||||
public static final String SMTP_DEFAULT_AUTH = "SMTP_DEFAULT_AUTH";
|
||||
public static final String SMTP_DEFAULT_AUTH_USER = "SMTP_DEFAULT_AUTH_USER";
|
||||
public static final String SMTP_DEFAULT_AUTH_PASSWORD = "SMTP_DEFAULT_AUTH_PASSWORD";
|
||||
|
||||
|
||||
public SMTPConfiguration get() {
|
||||
|
||||
String port = environment.getProperty(SMTP_DEFAULT_PORT, "");
|
||||
return SMTPConfiguration.builder()
|
||||
.id("singleton")
|
||||
.host(environment.getProperty(SMTP_DEFAULT_HOST, ""))
|
||||
.port(StringUtils.isEmpty(port) || !StringUtils.isNumeric(port) ? KEYCLOAK_DEFAULT_PORT : Integer.parseInt(port))
|
||||
.from(environment.getProperty(SMTP_DEFAULT_SENDER_EMAIL, ""))
|
||||
.fromDisplayName(environment.getProperty(SMTP_DEFAULT_SENDER_NAME, ""))
|
||||
.replyTo(environment.getProperty(SMTP_DEFAULT_REPLY_EMAIL, ""))
|
||||
.replyToDisplayName(environment.getProperty(SMTP_DEFAULT_REPLY_NAME, ""))
|
||||
.envelopeFrom(environment.getProperty(SMTP_DEFAULT_SENDER_ENVELOPE, ""))
|
||||
.ssl(Boolean.parseBoolean(environment.getProperty(SMTP_DEFAULT_SSL, "false")))
|
||||
.starttls(Boolean.parseBoolean(environment.getProperty(SMTP_DEFAULT_STARTTLS, "false")))
|
||||
.auth(Boolean.parseBoolean(environment.getProperty(SMTP_DEFAULT_AUTH, "false")))
|
||||
.user(environment.getProperty(SMTP_DEFAULT_AUTH_USER, ""))
|
||||
.password(environment.getProperty(SMTP_DEFAULT_AUTH_PASSWORD, ""))
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
@ -4,8 +4,10 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.knecon.fforesight.tenantcommons.TenantApplicationType;
|
||||
import com.knecon.fforesight.tenantcommons.TenantContext;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.GeneralConfigurationModel;
|
||||
import com.knecon.fforesight.tenantusermanagement.properties.ApplicationTypeProperties;
|
||||
import com.knecon.fforesight.tenantusermanagement.properties.TenantUserManagementProperties;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@ -18,28 +20,30 @@ public class GeneralConfigurationService {
|
||||
|
||||
private final RealmService realmService;
|
||||
private final TenantUserManagementProperties tenantUserManagementProperties;
|
||||
private final TenantApplicationTypeService tenantApplicationTypeService;
|
||||
|
||||
|
||||
public void initGeneralConfiguration(String tenantId) {
|
||||
public void initGeneralConfiguration(String tenantId, TenantApplicationType tenantApplicationType) {
|
||||
|
||||
TenantContext.setTenantId(tenantId);
|
||||
var generalConfiguration = getGeneralConfigurations();
|
||||
log.info("Currently Configured Application Name: {}, default name: {}", generalConfiguration.getDisplayName(), tenantUserManagementProperties.getApplicationName());
|
||||
updateGeneralConfigurations(getGeneralConfigurations());
|
||||
var generalConfiguration = getGeneralConfigurations(tenantApplicationType);
|
||||
log.info("Currently Configured Application Name: {}, default name: {}", generalConfiguration.getDisplayName(), tenantApplicationTypeService.getProperties(tenantApplicationType).getApplicationName());
|
||||
updateGeneralConfigurations(getGeneralConfigurations(tenantApplicationType), tenantApplicationType);
|
||||
TenantContext.clear();
|
||||
}
|
||||
|
||||
|
||||
public GeneralConfigurationModel getGeneralConfigurations() {
|
||||
public GeneralConfigurationModel getGeneralConfigurations(TenantApplicationType tenantApplicationType) {
|
||||
|
||||
var realm = realmService.realm(TenantContext.getTenantId()).toRepresentation();
|
||||
var auxiliaryName = realm.getDisplayNameHtml();
|
||||
|
||||
String computedAuxiliaryName = null;
|
||||
|
||||
if (!tenantUserManagementProperties.getApplicationName().equals(auxiliaryName)) {
|
||||
ApplicationTypeProperties currentAppTypeProperties = tenantApplicationTypeService.getProperties(tenantApplicationType);
|
||||
if (!currentAppTypeProperties.getApplicationName().equals(auxiliaryName)) {
|
||||
|
||||
auxiliaryName = StringUtils.replaceOnce(auxiliaryName, tenantUserManagementProperties.getApplicationName(), "");
|
||||
auxiliaryName = StringUtils.replaceOnce(auxiliaryName, currentAppTypeProperties.getApplicationName(), "");
|
||||
auxiliaryName = StringUtils.replaceOnce(auxiliaryName, " (", "");
|
||||
auxiliaryName = StringUtils.reverse(StringUtils.replaceOnce(StringUtils.reverse(auxiliaryName), ")", ""));
|
||||
|
||||
@ -54,7 +58,7 @@ public class GeneralConfigurationService {
|
||||
}
|
||||
|
||||
|
||||
public void updateGeneralConfigurations(GeneralConfigurationModel generalConfigurationModel) {
|
||||
public void updateGeneralConfigurations(GeneralConfigurationModel generalConfigurationModel, TenantApplicationType tenantApplicationType) {
|
||||
|
||||
var realm = realmService.realm(TenantContext.getTenantId());
|
||||
|
||||
@ -67,10 +71,16 @@ public class GeneralConfigurationService {
|
||||
|
||||
var realmRepresentation = realm.toRepresentation();
|
||||
realmRepresentation.setResetPasswordAllowed(generalConfigurationModel.isForgotPasswordFunctionEnabled());
|
||||
|
||||
realmRepresentation.setRevokeRefreshToken(true);
|
||||
ApplicationTypeProperties applicationTypeProperties = tenantApplicationTypeService.getProperties(tenantApplicationType);
|
||||
realmRepresentation.setRefreshTokenMaxReuse(applicationTypeProperties.getRefreshTokenMaxReuse());
|
||||
realmRepresentation.getAttributes().put("actionTokenGeneratedByUserLifespan.idp-verify-account-via-email", Integer.toString(86400));
|
||||
|
||||
if (!StringUtils.isEmpty(generalConfigurationModel.getAuxiliaryName())) {
|
||||
setDisplayName(realmRepresentation, tenantUserManagementProperties.getApplicationName() + " (" + generalConfigurationModel.getAuxiliaryName() + ")");
|
||||
setDisplayName(realmRepresentation, applicationTypeProperties.getApplicationName() + " (" + generalConfigurationModel.getAuxiliaryName() + ")");
|
||||
} else {
|
||||
setDisplayName(realmRepresentation, tenantUserManagementProperties.getApplicationName());
|
||||
setDisplayName(realmRepresentation, applicationTypeProperties.getApplicationName());
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.service;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import com.knecon.fforesight.tenantusermanagement.entity.GlobalSMTPConfigurationEntity;
|
||||
import com.knecon.fforesight.tenantusermanagement.repository.GlobalSMTPConfigurationRepository;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
|
||||
public class GlobalSMTPConfigurationPersistenceService {
|
||||
|
||||
GlobalSMTPConfigurationRepository smtpConfigurationRepository;
|
||||
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public Optional<GlobalSMTPConfigurationEntity> get() {
|
||||
|
||||
return smtpConfigurationRepository.findSingleton();
|
||||
}
|
||||
|
||||
|
||||
@Transactional
|
||||
public GlobalSMTPConfigurationEntity createUpdate(GlobalSMTPConfigurationEntity smtpConfiguration) {
|
||||
|
||||
smtpConfiguration.setId("singleton");
|
||||
return smtpConfigurationRepository.save(smtpConfiguration);
|
||||
}
|
||||
|
||||
}
|
||||
@ -9,6 +9,8 @@ import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.RoleRepresentation;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.knecon.fforesight.tenantcommons.TenantApplicationType;
|
||||
import com.knecon.fforesight.tenantusermanagement.properties.ApplicationTypeProperties;
|
||||
import com.knecon.fforesight.tenantusermanagement.properties.TenantUserManagementProperties;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@ -21,24 +23,26 @@ public class KeyCloakRoleManagerService {
|
||||
|
||||
private final RealmService realmService;
|
||||
private final TenantUserManagementProperties tenantUserManagementProperties;
|
||||
private final TenantApplicationTypeService tenantApplicationTypeService;
|
||||
|
||||
|
||||
public void updateRoles(String tenantId) {
|
||||
public void updateRoles(String tenantId, TenantApplicationType applicationType) {
|
||||
|
||||
var realm = realmService.realm(tenantId);
|
||||
|
||||
ApplicationTypeProperties applicationTypeProperties = tenantApplicationTypeService.getProperties(applicationType);
|
||||
log.info("Running KeyCloak Role Manager, managing client: {} with system client {}",
|
||||
tenantUserManagementProperties.getApplicationClientId(),
|
||||
tenantUserManagementProperties.getClientId());
|
||||
applicationTypeProperties.getApplicationClientId(),
|
||||
tenantUserManagementProperties.getClientId());
|
||||
var existingRoles = realm.roles().list().stream().map(RoleRepresentation::getName).collect(Collectors.toList());
|
||||
|
||||
log.info("Existing KC roles: {}", existingRoles);
|
||||
|
||||
var redactionClientRepresentation = getRedactionClientRepresentation(tenantId);
|
||||
var redactionClientRepresentation = getRedactionClientRepresentation(tenantId, applicationTypeProperties.getApplicationClientId());
|
||||
var redactionClient = realm.clients().get(redactionClientRepresentation.getId());
|
||||
var clientRoles = redactionClient.roles().list().stream().map(RoleRepresentation::getName).collect(Collectors.toList());
|
||||
|
||||
var allPermissions = tenantUserManagementProperties.getKcRoleMapping().getPermissions();
|
||||
var allPermissions = applicationTypeProperties.getKcRoleMapping().getPermissions();
|
||||
|
||||
log.info("Existing KC client roles: {}", clientRoles);
|
||||
log.info("Current Application KC client roles: {}", allPermissions);
|
||||
@ -65,8 +69,8 @@ public class KeyCloakRoleManagerService {
|
||||
|
||||
var allClientRoles = redactionClient.roles().list();
|
||||
|
||||
var allRoles = tenantUserManagementProperties.getKcRoleMapping().getAllRoles();
|
||||
var rolePermissionMappings = tenantUserManagementProperties.getKcRoleMapping().getRolePermissionMapping();
|
||||
var allRoles = applicationTypeProperties.getKcRoleMapping().getAllRoles();
|
||||
var rolePermissionMappings = applicationTypeProperties.getKcRoleMapping().getRolePermissionMapping();
|
||||
|
||||
// if an application-role doesn't exist, create it
|
||||
for (String applicationRole : allRoles) {
|
||||
@ -93,7 +97,7 @@ public class KeyCloakRoleManagerService {
|
||||
log.info("Finished application role {}", applicationRole);
|
||||
}
|
||||
|
||||
var composites = tenantUserManagementProperties.getKcRoleMapping().getRoleComposites();
|
||||
var composites = applicationTypeProperties.getKcRoleMapping().getRoleComposites();
|
||||
for (var key : composites.keySet()) {
|
||||
var realmRole = realm.roles().get(key).toRepresentation();
|
||||
|
||||
@ -111,9 +115,8 @@ public class KeyCloakRoleManagerService {
|
||||
}
|
||||
|
||||
|
||||
private ClientRepresentation getRedactionClientRepresentation(String tenantId) {
|
||||
private ClientRepresentation getRedactionClientRepresentation(String tenantId, String applicationClientId) {
|
||||
|
||||
String applicationClientId = tenantUserManagementProperties.getApplicationClientId();
|
||||
var clientRepresentationIterator = realmService.realm(tenantId).clients().findByClientId(applicationClientId).iterator();
|
||||
|
||||
if (clientRepresentationIterator.hasNext()) {
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.service;
|
||||
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import com.knecon.fforesight.tenantcommons.model.AuthDetails;
|
||||
import com.knecon.fforesight.tenantusermanagement.properties.TenantUserManagementProperties;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@ -19,12 +21,15 @@ public class RealmService {
|
||||
|
||||
private final TenantUserManagementProperties tenantUserManagementProperties;
|
||||
|
||||
|
||||
public RealmResource realm(String tenantId) {
|
||||
|
||||
return keycloak.getAdminClient().realm(tenantId);
|
||||
}
|
||||
|
||||
public String getEmail(RealmResource resource) {
|
||||
var user = resource.users().list().stream().filter(userRepresentation -> userRepresentation.getUsername().equals("admin")).findFirst();
|
||||
return user.isPresent() ? user.get().getEmail() : "";
|
||||
}
|
||||
|
||||
public AuthDetails getOpenIdConnectDetails(String tenantId) {
|
||||
|
||||
|
||||
@ -0,0 +1,258 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.service;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.iqser.red.service.persistence.service.v1.api.shared.model.license.Feature;
|
||||
import com.iqser.red.service.persistence.service.v1.api.shared.model.license.RedactionLicenseModel;
|
||||
import com.knecon.fforesight.tenantcommons.EncryptionDecryptionService;
|
||||
import com.knecon.fforesight.tenantcommons.TenantContext;
|
||||
import com.knecon.fforesight.tenantusermanagement.client.LicenseClient;
|
||||
import com.knecon.fforesight.tenantusermanagement.entity.GlobalSMTPConfigurationEntity;
|
||||
import com.knecon.fforesight.tenantusermanagement.entity.TenantEntity;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.SMTPConfiguration;
|
||||
import com.knecon.fforesight.tenantusermanagement.repository.TenantRepository;
|
||||
import com.knecon.fforesight.tenantusermanagement.utils.SMTPConfigurationMapper;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class SMTPService {
|
||||
|
||||
private final GlobalSMTPConfigurationPersistenceService smtpConfigurationPersistenceService;
|
||||
private final EnvironmentSMTPConfigurationProvider environmentSMTPConfigurationProvider;
|
||||
private final LicenseClient licenseClient;
|
||||
private final RealmService realmService;
|
||||
private final TenantRepository tenantRepository;
|
||||
|
||||
private final EncryptionDecryptionService encryptionDecryptionService;
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
private final static String SMTP_PASSWORD_KEY = "FFORESIGHT_SMTP_PASSWORD";
|
||||
private final static String DEFAULT_PASSWORD = "**********";
|
||||
public static final String CONFIGURABLE_SMTP_SERVER_FEATURE = "configurableSMTPServer";
|
||||
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
public void synchronizeSMTPConfigurations() {
|
||||
|
||||
log.info("Starting SMTP configuration synchronization...");
|
||||
|
||||
try {
|
||||
List<TenantEntity> tenants = tenantRepository.findAll();
|
||||
log.info("Retrieved {} tenants for SMTP synchronization.", tenants.size());
|
||||
|
||||
Optional<GlobalSMTPConfigurationEntity> optionalLatestGlobalConfigEntity = smtpConfigurationPersistenceService.get();
|
||||
|
||||
if (optionalLatestGlobalConfigEntity.isEmpty()) {
|
||||
log.info("Global SMTP configuration was newly added. Initializing all tenant SMTP configurations.");
|
||||
initializeGlobalSmtpConfig(tenants);
|
||||
|
||||
} else {
|
||||
updateGlobalSmtpConfig(optionalLatestGlobalConfigEntity.get(), tenants);
|
||||
}
|
||||
log.info("SMTP configuration synchronization completed successfully.");
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to synchronize SMTP configurations: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void initializeGlobalSmtpConfig(List<TenantEntity> tenants) {
|
||||
|
||||
SMTPConfiguration currentGlobalConfig = environmentSMTPConfigurationProvider.get();
|
||||
|
||||
initializeTenantsSmtpConfig(tenants, currentGlobalConfig);
|
||||
|
||||
currentGlobalConfig.setPassword(encryptionDecryptionService.encrypt(currentGlobalConfig.getPassword()));
|
||||
GlobalSMTPConfigurationEntity updatedGlobalConfig = SMTPConfigurationMapper.toEntity(currentGlobalConfig);
|
||||
smtpConfigurationPersistenceService.createUpdate(updatedGlobalConfig);
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void updateGlobalSmtpConfig(GlobalSMTPConfigurationEntity latestGlobalConfigEntity, List<TenantEntity> tenants) {
|
||||
|
||||
SMTPConfiguration latestGlobalConfig = SMTPConfigurationMapper.toModel(latestGlobalConfigEntity);
|
||||
latestGlobalConfig.setPassword(encryptionDecryptionService.decrypt(latestGlobalConfig.getPassword()));
|
||||
log.info("Existing global SMTP configuration was loaded.");
|
||||
|
||||
// Generate the latest SMTP config from environment variables and compare it to the last/saved global config
|
||||
SMTPConfiguration currentGlobalConfig = environmentSMTPConfigurationProvider.get();
|
||||
if (!currentGlobalConfig.equals(latestGlobalConfig)) {
|
||||
|
||||
log.info("Environment SMTP configuration has changed. Updating global SMTP configuration.");
|
||||
updateTenantsSmtpConfig(tenants, latestGlobalConfig, currentGlobalConfig);
|
||||
|
||||
currentGlobalConfig.setPassword(encryptionDecryptionService.encrypt(currentGlobalConfig.getPassword()));
|
||||
GlobalSMTPConfigurationEntity updatedGlobalConfig = SMTPConfigurationMapper.toEntity(currentGlobalConfig);
|
||||
smtpConfigurationPersistenceService.createUpdate(updatedGlobalConfig);
|
||||
|
||||
} else {
|
||||
log.info("No changes detected in environment SMTP configuration.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void initializeTenantsSmtpConfig(List<TenantEntity> tenants, SMTPConfiguration currentGlobalConfig) {
|
||||
|
||||
tenants.forEach(tenant -> processTenantSmtpConfig(tenant, currentGlobalConfig, null, true));
|
||||
}
|
||||
|
||||
|
||||
private void updateTenantsSmtpConfig(List<TenantEntity> tenants, SMTPConfiguration latestGlobalConfig, SMTPConfiguration currentGlobalConfig) {
|
||||
|
||||
tenants.forEach(tenant -> processTenantSmtpConfig(tenant, currentGlobalConfig, latestGlobalConfig, false));
|
||||
}
|
||||
|
||||
|
||||
private void processTenantSmtpConfig(TenantEntity tenant, SMTPConfiguration currentGlobalConfig, SMTPConfiguration latestGlobalConfig, boolean isInitialization) {
|
||||
|
||||
String tenantId = tenant.getTenantId();
|
||||
log.info("Processing SMTP configuration for tenant: {}", tenantId);
|
||||
|
||||
try {
|
||||
TenantContext.setTenantId(tenantId);
|
||||
SMTPConfiguration tenantSMTPConfig = getSMTPConfiguration();
|
||||
tenantSMTPConfig.setId("singleton");
|
||||
updatePassword(tenantSMTPConfig);
|
||||
|
||||
if (!StringUtils.isBlank(tenantSMTPConfig.getPassword())) {
|
||||
tenantSMTPConfig.setPassword(encryptionDecryptionService.decrypt(tenantSMTPConfig.getPassword()));
|
||||
}
|
||||
|
||||
if (isInitialization) {
|
||||
if (StringUtils.isBlank(tenantSMTPConfig.getHost())) {
|
||||
log.info("Tenant '{}' SMTP configuration has not been yet set.", tenantId);
|
||||
updateSMTPConfiguration(currentGlobalConfig);
|
||||
log.info("Tenant '{}' SMTP configuration set successfully.", tenantId);
|
||||
} else {
|
||||
log.info("Tenant '{}' SMTP configuration was already set. No action taken.", tenantId);
|
||||
}
|
||||
} else {
|
||||
if (tenantSMTPConfig.equals(latestGlobalConfig)) {
|
||||
log.info("Tenant '{}' SMTP configuration matches global. Updating to latest environment configuration.", tenantId);
|
||||
updateSMTPConfiguration(currentGlobalConfig);
|
||||
log.info("Tenant '{}' SMTP configuration updated successfully.", tenantId);
|
||||
} else {
|
||||
log.info("Tenant '{}' SMTP configuration differs from global. No action taken.", tenantId);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Error processing SMTP configuration for tenant '{}': {}", tenantId, e.getMessage());
|
||||
} finally {
|
||||
TenantContext.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public SMTPConfiguration getSMTPConfiguration() {
|
||||
|
||||
var realm = realmService.realm(TenantContext.getTenantId()).toRepresentation();
|
||||
return objectMapper.convertValue(realm.getSmtpServer(), SMTPConfiguration.class);
|
||||
}
|
||||
|
||||
|
||||
public Map<String, String> convertSMTPConfigToMap(SMTPConfiguration smtpConfiguration) {
|
||||
|
||||
updatePassword(smtpConfiguration);
|
||||
smtpConfiguration.setPassword(encryptionDecryptionService.decrypt(smtpConfiguration.getPassword()));
|
||||
return convertSMTPConfigurationModelToMap(smtpConfiguration);
|
||||
}
|
||||
|
||||
|
||||
public boolean canUpdateSMTPConfig() {
|
||||
|
||||
RedactionLicenseModel licenseModel = licenseClient.getLicense();
|
||||
String activeLicenseId = licenseModel.getActiveLicense();
|
||||
|
||||
return licenseModel.getLicenses()
|
||||
.stream()
|
||||
.filter(license -> license.getId().equals(activeLicenseId))
|
||||
.flatMap(license -> license.getFeatures()
|
||||
.stream())
|
||||
.filter(feature -> CONFIGURABLE_SMTP_SERVER_FEATURE.equals(feature.getName()))
|
||||
.map(Feature::getValue)
|
||||
.filter(Boolean.class::isInstance)
|
||||
.map(Boolean.class::cast)
|
||||
.findFirst()
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
|
||||
public void createDefaultSMTPConfiguration() {
|
||||
|
||||
SMTPConfiguration defaultConfig = environmentSMTPConfigurationProvider.get();
|
||||
updateSMTPConfiguration(defaultConfig);
|
||||
}
|
||||
|
||||
|
||||
public void updateSMTPConfiguration(SMTPConfiguration smtpConfigurationModel) {
|
||||
|
||||
String tenantId = TenantContext.getTenantId();
|
||||
var realmRepresentation = realmService.realm(tenantId).toRepresentation();
|
||||
var propertiesMap = convertSMTPConfigurationModelToMap(smtpConfigurationModel);
|
||||
realmRepresentation.setSmtpServer(propertiesMap);
|
||||
|
||||
if (smtpConfigurationModel.getPassword() != null && !smtpConfigurationModel.getPassword().matches("\\**")) {
|
||||
realmRepresentation.getAttributesOrEmpty().put(SMTP_PASSWORD_KEY, encryptionDecryptionService.encrypt(smtpConfigurationModel.getPassword()));
|
||||
}
|
||||
|
||||
realmService.realm(tenantId).update(realmRepresentation);
|
||||
}
|
||||
|
||||
|
||||
public void clearSMTPConfiguration() {
|
||||
|
||||
var realmRepresentation = realmService.realm(TenantContext.getTenantId()).toRepresentation();
|
||||
realmRepresentation.setSmtpServer(new HashMap<>());
|
||||
realmRepresentation.getAttributesOrEmpty().remove(SMTP_PASSWORD_KEY);
|
||||
realmService.realm(TenantContext.getTenantId()).update(realmRepresentation);
|
||||
}
|
||||
|
||||
|
||||
private Map<String, String> convertSMTPConfigurationModelToMap(SMTPConfiguration smtpConfigurationModel) {
|
||||
|
||||
Map<String, Object> propertiesMap = objectMapper.convertValue(smtpConfigurationModel, new TypeReference<>() {
|
||||
});
|
||||
Map<String, String> stringPropertiesMap = new HashMap<>();
|
||||
propertiesMap.forEach((key, value) -> {
|
||||
if (value != null) {
|
||||
stringPropertiesMap.put(key, value.toString());
|
||||
} else {
|
||||
stringPropertiesMap.put(key, "");
|
||||
}
|
||||
});
|
||||
return stringPropertiesMap;
|
||||
}
|
||||
|
||||
|
||||
private void updatePassword(SMTPConfiguration smtpConfiguration) {
|
||||
|
||||
if (DEFAULT_PASSWORD.equals(smtpConfiguration.getPassword())) {
|
||||
try {
|
||||
var password = realmService.realm(TenantContext.getTenantId()).toRepresentation().getAttributesOrEmpty().getOrDefault(SMTP_PASSWORD_KEY, "");
|
||||
smtpConfiguration.setPassword(password);
|
||||
} catch (Exception e) {
|
||||
log.info("No current SMTP Config exists", e);
|
||||
}
|
||||
} else {
|
||||
smtpConfiguration.setPassword(encryptionDecryptionService.encrypt(smtpConfiguration.getPassword()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.service;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import com.knecon.fforesight.tenantcommons.TenantApplicationType;
|
||||
import com.knecon.fforesight.tenantcommons.TenantContext;
|
||||
import com.knecon.fforesight.tenantusermanagement.properties.ApplicationTypeProperties;
|
||||
import com.knecon.fforesight.tenantusermanagement.properties.TenantUserManagementProperties;
|
||||
import com.knecon.fforesight.tenantusermanagement.repository.TenantRepository;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
|
||||
public class TenantApplicationTypeService {
|
||||
|
||||
TenantUserManagementProperties tenantUserManagementProperties;
|
||||
TenantRepository tenantRepository;
|
||||
|
||||
|
||||
public TenantApplicationType getCurrent() {
|
||||
|
||||
return get(TenantContext.getTenantId());
|
||||
}
|
||||
|
||||
|
||||
public TenantApplicationType get(String tenantId) {
|
||||
|
||||
return tenantRepository.findApplicationTypeByTenantId(tenantId)
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Tenant does not exist"));
|
||||
}
|
||||
|
||||
|
||||
public ApplicationTypeProperties getProperties(TenantApplicationType applicationType) {
|
||||
|
||||
return tenantUserManagementProperties.getApplicationTypes()
|
||||
.get(applicationType.name().toLowerCase(Locale.ROOT));
|
||||
}
|
||||
|
||||
|
||||
public ApplicationTypeProperties getCurrentProperties() {
|
||||
|
||||
TenantApplicationType applicationType = get(TenantContext.getTenantId());
|
||||
return getProperties(applicationType);
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,11 +1,10 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.service;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.knecon.fforesight.tenantcommons.TenantProvider;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@ -23,7 +22,7 @@ public class UserCacheBuilder {
|
||||
|
||||
try {
|
||||
tenantManagementService.getTenants().forEach(tenant -> userService.getAllUsers(tenant.getTenantId()));
|
||||
}catch (Exception e){
|
||||
} catch (Exception e) {
|
||||
log.debug("Cold start");
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,7 +14,6 @@ import org.springframework.retry.support.RetryTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.knecon.fforesight.tenantusermanagement.model.User;
|
||||
import com.knecon.fforesight.tenantusermanagement.properties.TenantUserManagementProperties;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -26,7 +25,7 @@ public class UserListingService {
|
||||
|
||||
private final RealmService realmService;
|
||||
|
||||
private final TenantUserManagementProperties tenantUserManagementProperties;
|
||||
private final TenantApplicationTypeService tenantApplicationTypeService;
|
||||
private final RetryTemplate retryTemplate = RetryTemplate.builder().maxAttempts(3).exponentialBackoff(1000, 2, 5000).build();
|
||||
|
||||
|
||||
@ -41,10 +40,10 @@ public class UserListingService {
|
||||
Map<String, Set<String>> usersByRole = new HashMap<>();
|
||||
if (!allUsers.isEmpty()) {
|
||||
var realmRoles = realm.roles().list().stream().map(r -> r.getName().toUpperCase(Locale.ROOT)).collect(Collectors.toSet());
|
||||
var allRoles = tenantUserManagementProperties.getKcRoleMapping().getAllRoles();
|
||||
var allRoles = tenantApplicationTypeService.getCurrentProperties().getKcRoleMapping().getAllRoles();
|
||||
for (var role : allRoles) {
|
||||
if (realmRoles.contains(role)) {
|
||||
Set<UserRepresentation> users = realm.roles().get(role).getRoleUserMembers(0, 500);
|
||||
List<UserRepresentation> users = realm.roles().get(role).getUserMembers(0, 500);
|
||||
usersByRole.put(role, users.stream().map(UserRepresentation::getId).collect(Collectors.toSet()));
|
||||
}
|
||||
}
|
||||
@ -71,7 +70,7 @@ public class UserListingService {
|
||||
users.add(user);
|
||||
}
|
||||
|
||||
var roleComposites = tenantUserManagementProperties.getKcRoleMapping().getRoleComposites();
|
||||
var roleComposites = tenantApplicationTypeService.getCurrentProperties().getKcRoleMapping().getRoleComposites();
|
||||
users.forEach(user -> {
|
||||
for (var parentRole : roleComposites.keySet()) {
|
||||
if (user.getRoles().contains(parentRole)) {
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.service;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
@ -9,11 +8,6 @@ import java.util.TreeSet;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.ws.rs.ClientErrorException;
|
||||
import javax.ws.rs.NotAuthorizedException;
|
||||
import javax.ws.rs.NotFoundException;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
import org.apache.commons.validator.routines.EmailValidator;
|
||||
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
|
||||
import org.jboss.resteasy.client.jaxrs.internal.ResteasyClientBuilderImpl;
|
||||
@ -45,9 +39,14 @@ import com.knecon.fforesight.tenantusermanagement.model.ResetPasswordRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.UpdateMyProfileRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.UpdateProfileRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.User;
|
||||
import com.knecon.fforesight.tenantusermanagement.permissions.ApplicationRoles;
|
||||
import com.knecon.fforesight.tenantusermanagement.properties.TenantUserManagementProperties;
|
||||
|
||||
import io.micrometer.common.util.StringUtils;
|
||||
import jakarta.ws.rs.ClientErrorException;
|
||||
import jakarta.ws.rs.NotAuthorizedException;
|
||||
import jakarta.ws.rs.NotFoundException;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@ -60,6 +59,7 @@ public class UserService {
|
||||
private final TenantUserManagementProperties tenantUserManagementProperties;
|
||||
private final UserListingService userListingService;
|
||||
private final RabbitTemplate rabbitTemplate;
|
||||
private final TenantApplicationTypeService tenantApplicationTypeService;
|
||||
|
||||
@Value("${fforesight.user-exchange.name}")
|
||||
private String userExchangeName;
|
||||
@ -74,20 +74,20 @@ public class UserService {
|
||||
@CacheEvict(value = "${commons.keycloak.userCache}", allEntries = true, beforeInvocation = true)
|
||||
public User createUser(CreateUserRequest user) {
|
||||
|
||||
if (StringUtils.isEmpty(user.getEmail())) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Email address must be set");
|
||||
}
|
||||
|
||||
String username = StringUtils.isEmpty(user.getUsername()) ? user.getEmail() : user.getUsername();
|
||||
if (!this.getTenantUsersResource().search(username).isEmpty()) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "User with this username already exists");
|
||||
if (!this.getTenantUsersResource().search(username, true).isEmpty() || !this.getTenantUsersResource().searchByEmail(user.getEmail(), true).isEmpty()) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Requested username or email address not available");
|
||||
}
|
||||
|
||||
if (!EmailValidator.getInstance().isValid(user.getEmail())) {
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "Email address format is not valid");
|
||||
}
|
||||
// also search by email in case the username was provided at creation
|
||||
if (!StringUtils.isEmpty(user.getUsername()) && !this.getTenantUsersResource().searchByEmail(user.getEmail(), true).isEmpty()) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "User with this email already exists");
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Email address format is not valid");
|
||||
}
|
||||
|
||||
tenantUserManagementProperties.getKcRoleMapping().validateRoles(user.getRoles());
|
||||
tenantApplicationTypeService.getCurrentProperties().getKcRoleMapping().validateRoles(user.getRoles());
|
||||
|
||||
UserRepresentation userRepresentation = new UserRepresentation();
|
||||
userRepresentation.setUsername(username);
|
||||
@ -96,11 +96,13 @@ public class UserService {
|
||||
userRepresentation.setFirstName(user.getFirstName());
|
||||
userRepresentation.setLastName(user.getLastName());
|
||||
|
||||
checkRankOrderForAssigningRole(user.getRoles(), this.getUserRoles(KeycloakSecurity.getUserId()));
|
||||
|
||||
try (var response = this.getTenantUsersResource().create(userRepresentation)) {
|
||||
|
||||
if (response.getStatusInfo().getFamily() != Response.Status.Family.SUCCESSFUL) {
|
||||
if (response.getStatusInfo().getStatusCode() == 409) {
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, response.getStatusInfo().getReasonPhrase());
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, response.getStatusInfo().getReasonPhrase());
|
||||
}
|
||||
if (response.getStatusInfo().getStatusCode() == 400) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, response.getStatusInfo().getReasonPhrase());
|
||||
@ -110,10 +112,12 @@ public class UserService {
|
||||
|
||||
var createdUser = getUserByUsername(username);
|
||||
|
||||
try {
|
||||
sendResetPasswordEmail(createdUser.getUserId());
|
||||
} catch (Exception e) {
|
||||
log.debug("Activation E-mail could not be sent!", e);
|
||||
if (user.isSendSetPasswordMail()) {
|
||||
try {
|
||||
sendResetPasswordEmail(createdUser.getUserId());
|
||||
} catch (Exception e) {
|
||||
log.debug("Set Password E-mail could not be sent!", e);
|
||||
}
|
||||
}
|
||||
|
||||
this.rabbitTemplate.convertAndSend(userExchangeName, "user.created", new UserCreatedEvent(createdUser, KeycloakSecurity.getUserId()));
|
||||
@ -127,101 +131,186 @@ public class UserService {
|
||||
}
|
||||
|
||||
|
||||
private void sendResetPasswordEmail(String userId) {
|
||||
|
||||
try {
|
||||
this.getTenantUsersResource().get(userId).executeActionsEmail(Collections.singletonList("UPDATE_PASSWORD"), 86400);
|
||||
} catch (Exception e) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Failed to send email", e);
|
||||
}
|
||||
}
|
||||
public void checkRankOrderForAssigningRole(Set<String> newRoles, Set<String> currentUserRoles) {
|
||||
|
||||
|
||||
@CacheEvict(value = "${commons.keycloak.userCache}", allEntries = true, beforeInvocation = true)
|
||||
public User setRoles(String userId, Set<String> newRoles, Set<String> currentUserRoles) {
|
||||
|
||||
var allRoles = tenantUserManagementProperties.getKcRoleMapping().getAllRoles();
|
||||
newRoles.forEach(role -> {
|
||||
if (!allRoles.contains(role)) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid role: " + role);
|
||||
}
|
||||
});
|
||||
|
||||
var userResource = getUserResource(userId);
|
||||
var userRoles = userResource.roles().realmLevel().listEffective().stream().map(RoleRepresentation::getName).collect(Collectors.toSet());
|
||||
|
||||
var roleMapping = tenantUserManagementProperties.getKcRoleMapping();
|
||||
var maxRank = currentUserRoles.stream().map(r -> roleMapping.getRole(r).getRank()).max(Integer::compare).orElse(-1);
|
||||
var newRolesRank = newRoles.stream().map(r -> roleMapping.getRole(r).getRank()).toList();
|
||||
var maxNewRolesRank = newRolesRank.stream().max(Integer::compare).orElse(-1);
|
||||
|
||||
var untouchableRoles = userRoles.stream()
|
||||
.filter(roleMapping::isValidRole)
|
||||
.map(roleMapping::getRole)
|
||||
.filter(r -> r.getRank() > maxRank)
|
||||
.map(KCRole::getName)
|
||||
.collect(Collectors.toSet());
|
||||
var roleMapping = tenantApplicationTypeService.getCurrentProperties().getKcRoleMapping();
|
||||
var maxRank = currentUserRoles.stream()
|
||||
.map(r -> roleMapping.getRole(r).getRank())
|
||||
.max(Integer::compare)
|
||||
.orElse(-1);
|
||||
var newRolesRank = newRoles.stream()
|
||||
.map(r -> roleMapping.getRole(r).getRank())
|
||||
.toList();
|
||||
var maxNewRolesRank = newRolesRank.stream()
|
||||
.max(Integer::compare)
|
||||
.orElse(-1);
|
||||
|
||||
if (maxNewRolesRank > maxRank) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Cannot assign this role to that user. Insufficient rights");
|
||||
}
|
||||
}
|
||||
|
||||
if (!newRoles.containsAll(untouchableRoles)) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Cannot modify some roles for this user. Insufficient rights");
|
||||
}
|
||||
|
||||
if (userId.equals(KeycloakSecurity.getUserId()) && maxRank.equals(roleMapping.getMaxRank()) && !maxNewRolesRank.equals(maxRank)) {
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "Cannot remove highest ranking role from self.");
|
||||
}
|
||||
public Set<String> getUserRoles(String userId) {
|
||||
|
||||
var currentRolesAsRoleRepresentation = allRoles.stream().map(this::getRoleRepresentation).collect(Collectors.toList());
|
||||
var newMappedRoles = newRoles.stream().map(this::getRoleRepresentation).collect(Collectors.toList());
|
||||
|
||||
userResource.roles().realmLevel().remove(currentRolesAsRoleRepresentation);
|
||||
userResource.roles().realmLevel().add(newMappedRoles);
|
||||
|
||||
var userWithNewRoles = getUserByUsername(userResource.toRepresentation().getUsername());
|
||||
this.rabbitTemplate.convertAndSend(userExchangeName, "user.rolesUpdated", (new UserRolesUpdatedEvent(userWithNewRoles, userRoles, newRoles, KeycloakSecurity.getUserId())));
|
||||
|
||||
return userWithNewRoles;
|
||||
var userResource = getUserResource(userId);
|
||||
return userResource.roles().realmLevel().listEffective()
|
||||
.stream()
|
||||
.map(RoleRepresentation::getName)
|
||||
.filter(r -> tenantApplicationTypeService.getCurrentProperties().getKcRoleMapping().isValidRole(r))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
|
||||
@CacheEvict(value = "${commons.keycloak.userCache}", allEntries = true, beforeInvocation = true)
|
||||
public User setRoles(String userId, Set<String> roles) {
|
||||
|
||||
var currentUserResource = getUserResource(KeycloakSecurity.getUserId());
|
||||
var currentUserRoles = currentUserResource.roles().realmLevel().listEffective().stream().map(RoleRepresentation::getName).collect(Collectors.toSet());
|
||||
currentUserRoles = currentUserRoles.stream().filter(r -> tenantUserManagementProperties.getKcRoleMapping().isValidRole(r)).collect(Collectors.toSet());
|
||||
var currentUserRoles = this.getUserRoles(KeycloakSecurity.getUserId());
|
||||
|
||||
var userWithNewRoles = setRoles(userId, roles, currentUserRoles);
|
||||
Set<String> oldRoles = getRoles(userId);
|
||||
if (!userId.equalsIgnoreCase(KeycloakSecurity.getUserId()) && !oldRoles.isEmpty() && oldRoles.stream()
|
||||
.allMatch(ApplicationRoles::isKneconRole)) {
|
||||
throw new NotFoundException("User with id: " + userId + " does not exist");
|
||||
}
|
||||
|
||||
return setRoles(userId, roles, currentUserRoles);
|
||||
}
|
||||
|
||||
|
||||
@CacheEvict(value = "${commons.keycloak.userCache}", allEntries = true, beforeInvocation = true)
|
||||
public User setRoles(String userId, Set<String> newRoles, Set<String> currentUserRoles) {
|
||||
|
||||
var allRoles = tenantApplicationTypeService.getCurrentProperties().getKcRoleMapping().getAllRoles();
|
||||
newRoles.forEach(role -> {
|
||||
|
||||
if (!allRoles.contains(role) || ApplicationRoles.isKneconRole(role)) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid role: " + role);
|
||||
}
|
||||
});
|
||||
|
||||
var userResource = getUserResource(userId);
|
||||
var oldRoles = userResource.roles().realmLevel().listEffective()
|
||||
.stream()
|
||||
.map(RoleRepresentation::getName)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
validateSufficientRoles(userId, oldRoles, newRoles, currentUserRoles);
|
||||
|
||||
var currentRolesAsRoleRepresentation = allRoles.stream()
|
||||
.map(this::getRoleRepresentation)
|
||||
.collect(Collectors.toList());
|
||||
var newMappedRoles = newRoles.stream()
|
||||
.map(this::getRoleRepresentation)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
userResource.roles().realmLevel().remove(currentRolesAsRoleRepresentation);
|
||||
userResource.roles().realmLevel().add(newMappedRoles);
|
||||
|
||||
var userWithNewRoles = getUserByUsername(userResource.toRepresentation().getUsername());
|
||||
this.rabbitTemplate.convertAndSend(userExchangeName, "user.rolesUpdated", (new UserRolesUpdatedEvent(userWithNewRoles, oldRoles, newRoles, KeycloakSecurity.getUserId())));
|
||||
|
||||
return userWithNewRoles;
|
||||
}
|
||||
|
||||
|
||||
private RoleRepresentation getRoleRepresentation(String role) {
|
||||
@CacheEvict(value = "${commons.keycloak.userCache}", allEntries = true, beforeInvocation = true)
|
||||
public void removeRolesForDeletion(String userId, Set<String> roles) {
|
||||
|
||||
RoleRepresentation realmRole;
|
||||
try {
|
||||
realmRole = realmService.realm(TenantContext.getTenantId()).roles().get(role).toRepresentation();
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("The realm role {} is not found.", role);
|
||||
throw new NotFoundException("The realm role " + role + " is not found.", e);
|
||||
var allRoles = tenantApplicationTypeService.getCurrentProperties().getKcRoleMapping().getAllRoles();
|
||||
roles.forEach(role -> {
|
||||
|
||||
if (!allRoles.contains(role)) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid role: " + role);
|
||||
}
|
||||
});
|
||||
|
||||
var userResource = getUserResource(userId);
|
||||
|
||||
var currentRolesAsRoleRepresentation = roles.stream()
|
||||
.map(this::getRoleRepresentation)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
userResource.roles().realmLevel().remove(currentRolesAsRoleRepresentation);
|
||||
}
|
||||
|
||||
|
||||
@CacheEvict(value = "${commons.keycloak.userCache}", allEntries = true, beforeInvocation = true)
|
||||
public void validateSufficientRoles(String userId, Set<String> userRoles, Set<String> newRoles, Set<String> currentUserRoles) {
|
||||
|
||||
var roleMapping = tenantApplicationTypeService.getCurrentProperties().getKcRoleMapping();
|
||||
|
||||
int maxCurrentUserRank = currentUserRoles.stream()
|
||||
.map(r -> roleMapping.getRole(r).getRank())
|
||||
.max(Integer::compare)
|
||||
.orElse(-1);
|
||||
|
||||
Set<String> untouchableRoles = userRoles.stream()
|
||||
.filter(roleMapping::isValidRole)
|
||||
.map(roleMapping::getRole)
|
||||
.filter(r -> r.getRank() > maxCurrentUserRank && !ApplicationRoles.isKneconRole(r.getName()))
|
||||
.map(KCRole::getName)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
Set<String> kneconRoles = userRoles.stream()
|
||||
.filter(roleMapping::isValidRole)
|
||||
.map(roleMapping::getRole)
|
||||
.map(KCRole::getName)
|
||||
.filter(ApplicationRoles::isKneconRole)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
int maxNewRolesRank = newRoles.stream()
|
||||
.map(r -> roleMapping.getRole(r).getRank())
|
||||
.max(Integer::compare)
|
||||
.orElse(-1);
|
||||
|
||||
newRoles.addAll(kneconRoles);
|
||||
|
||||
int maxNewRolesRankIncludingKnecon = newRoles.stream()
|
||||
.map(r -> roleMapping.getRole(r).getRank())
|
||||
.max(Integer::compare)
|
||||
.orElse(-1);
|
||||
|
||||
ensureNoHigherRankAssigned(maxCurrentUserRank, maxNewRolesRank);
|
||||
ensureUntouchableRolesPreserved(untouchableRoles, newRoles);
|
||||
ensureHighestRankNotRemovedFromSelf(userId, maxCurrentUserRank, maxNewRolesRankIncludingKnecon, roleMapping.getMaxRank());
|
||||
}
|
||||
|
||||
|
||||
private void ensureNoHigherRankAssigned(int maxCurrentUserRank, int maxNewRolesRank) {
|
||||
|
||||
if (maxNewRolesRank > maxCurrentUserRank) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Cannot assign this role to that user. Insufficient rights");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void ensureUntouchableRolesPreserved(Set<String> untouchableRoles, Set<String> newRoles) {
|
||||
|
||||
if (!newRoles.containsAll(untouchableRoles)) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Cannot modify some roles for this user. Insufficient rights");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void ensureHighestRankNotRemovedFromSelf(String userId, int maxCurrentUserRank, int maxNewRolesRankIncludingKnecon, int overallMaxRank) {
|
||||
|
||||
boolean isSelf = userId.equalsIgnoreCase(KeycloakSecurity.getUserId());
|
||||
boolean isUserHighestRank = maxCurrentUserRank == overallMaxRank;
|
||||
boolean highestRankRemoved = !Integer.valueOf(maxNewRolesRankIncludingKnecon).equals(maxCurrentUserRank);
|
||||
|
||||
if (isSelf && isUserHighestRank && highestRankRemoved) {
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "Cannot remove highest ranking role from self.");
|
||||
}
|
||||
return realmRole;
|
||||
}
|
||||
|
||||
|
||||
public Optional<User> getUserById(String userId) {
|
||||
|
||||
return userListingService.getAllUsers(TenantContext.getTenantId()).stream().filter(u -> u.getUserId().equalsIgnoreCase(userId)).findAny();
|
||||
}
|
||||
|
||||
|
||||
public List<User> getUsersByIds(Collection<String> userIds) {
|
||||
|
||||
return userListingService.getAllUsers(TenantContext.getTenantId()).stream().filter(u -> userIds.contains(u.getUserId())).collect(Collectors.toList());
|
||||
return userListingService.getAllUsers(TenantContext.getTenantId())
|
||||
.stream()
|
||||
.filter(u -> u.getUserId().equalsIgnoreCase(userId))
|
||||
.findAny();
|
||||
}
|
||||
|
||||
|
||||
@ -249,12 +338,16 @@ public class UserService {
|
||||
user.update(userRepresentation);
|
||||
} catch (ClientErrorException e) {
|
||||
if (e.getResponse().getStatus() == 409) {
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "E-mail already in use");
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "E-mail is not available");
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
var updatedProfile = getUserByUsername(userRepresentation.getUsername());
|
||||
updatedProfile.setRoles(updatedProfile.getRoles()
|
||||
.stream()
|
||||
.filter(ApplicationRoles::isNoKneconRole)
|
||||
.collect(Collectors.toSet()));
|
||||
|
||||
this.rabbitTemplate.convertAndSend(userExchangeName, "user.ownProfileUpdated", (new UserUpdatedOwnProfileEvent(updatedProfile)));
|
||||
|
||||
@ -268,7 +361,8 @@ public class UserService {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "No id provided.");
|
||||
}
|
||||
try {
|
||||
return this.getTenantUsersResource().get(userId);
|
||||
return this.getTenantUsersResource()
|
||||
.get(userId);
|
||||
} catch (NotFoundException e) {
|
||||
throw new NotFoundException("User with id: " + userId + " does not exist", e);
|
||||
}
|
||||
@ -287,19 +381,19 @@ public class UserService {
|
||||
.realm(TenantContext.getTenantId())
|
||||
.username(username)
|
||||
.password(password)
|
||||
.clientId(tenantUserManagementProperties.getApplicationClientId())
|
||||
.clientId(tenantApplicationTypeService.getCurrentProperties().getApplicationClientId())
|
||||
.grantType(OAuth2Constants.PASSWORD)
|
||||
.resteasyClient(new ResteasyClientBuilderImpl().connectionTTL(2, TimeUnit.SECONDS)
|
||||
.hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.ANY)
|
||||
.connectionPoolSize(tenantUserManagementProperties.getConnectionPoolSize())
|
||||
.disableTrustManager()
|
||||
.build())
|
||||
.hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.ANY)
|
||||
.connectionPoolSize(tenantUserManagementProperties.getConnectionPoolSize())
|
||||
.disableTrustManager()
|
||||
.build())
|
||||
.build();
|
||||
|
||||
try {
|
||||
changeEmailClient.tokenManager().getAccessTokenString();
|
||||
} catch (NotAuthorizedException e) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Could not confirm credentials");
|
||||
}
|
||||
|
||||
changeEmailClient.close();
|
||||
@ -317,15 +411,26 @@ public class UserService {
|
||||
|
||||
private User getUserByUsername(String username) {
|
||||
|
||||
var userList = this.getTenantUsersResource().search(username);
|
||||
var userList = this.getTenantUsersResource().search(username, true);
|
||||
if (userList.isEmpty()) {
|
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "User with this username already exists");
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "User with this username already exists");
|
||||
}
|
||||
|
||||
return convert(userList.iterator().next());
|
||||
}
|
||||
|
||||
|
||||
private boolean userExists(String userId) {
|
||||
|
||||
try {
|
||||
getTenantUsersResource().get(userId).toRepresentation();
|
||||
return true;
|
||||
} catch (NotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private UsersResource getTenantUsersResource() {
|
||||
|
||||
return realmService.realm(TenantContext.getTenantId()).users();
|
||||
@ -337,7 +442,7 @@ public class UserService {
|
||||
var user = userListingService.convertBasicUser(userRepresentation);
|
||||
user.setRoles(getRoles(user.getUserId()));
|
||||
|
||||
var roleComposites = tenantUserManagementProperties.getKcRoleMapping().getRoleComposites();
|
||||
var roleComposites = tenantApplicationTypeService.getCurrentProperties().getKcRoleMapping().getRoleComposites();
|
||||
for (var parentRole : roleComposites.keySet()) {
|
||||
if (user.getRoles().contains(parentRole)) {
|
||||
user.getRoles().addAll(roleComposites.get(parentRole));
|
||||
@ -349,13 +454,17 @@ public class UserService {
|
||||
|
||||
private Set<String> getRoles(String id) {
|
||||
|
||||
List<RoleRepresentation> realmMappings = this.getTenantUsersResource().get(id).roles().getAll().getRealmMappings();
|
||||
List<RoleRepresentation> realmMappings = this.getTenantUsersResource()
|
||||
.get(id).roles().getAll().getRealmMappings();
|
||||
if (realmMappings == null) {
|
||||
log.warn("User with id=" + id + " contains null role mappings.");
|
||||
return new TreeSet<>();
|
||||
}
|
||||
var allRoles = tenantUserManagementProperties.getKcRoleMapping().getAllRoles();
|
||||
return realmMappings.stream().map(RoleRepresentation::getName).filter(allRoles::contains).collect(Collectors.toSet());
|
||||
var allRoles = tenantApplicationTypeService.getCurrentProperties().getKcRoleMapping().getAllRoles();
|
||||
return realmMappings.stream()
|
||||
.map(RoleRepresentation::getName)
|
||||
.filter(allRoles::contains)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
|
||||
@ -366,22 +475,29 @@ public class UserService {
|
||||
throw new ResponseStatusException(HttpStatus.CONFLICT, "Cannot delete self");
|
||||
}
|
||||
|
||||
var currentUserResource = getUserResource(KeycloakSecurity.getUserId());
|
||||
var currentRoles = getRoles(currentUserResource.toRepresentation().getId());
|
||||
|
||||
var userResource = getUserResource(userId);
|
||||
var userRoles = getRoles(userId);
|
||||
|
||||
var roleMapping = tenantUserManagementProperties.getKcRoleMapping();
|
||||
var maxRank = currentRoles.stream().map(r -> roleMapping.getRole(r).getRank()).max(Integer::compare).orElse(-1);
|
||||
var toDeleteUserMaxRank = userRoles.stream().map(r -> roleMapping.getRole(r).getRank()).max(Integer::compare).orElse(-1);
|
||||
|
||||
if (toDeleteUserMaxRank > maxRank) {
|
||||
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "It is not allowed to delete a user with higher ranking roles");
|
||||
if (!userExists(userId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var status = validateExecutionForDeletion(KeycloakSecurity.getUserId(), userId);
|
||||
|
||||
if (status.equals(ValidationResult.FORBIDDEN)) {
|
||||
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "It is not allowed to delete a user with higher ranking roles");
|
||||
} else if (status.equals(ValidationResult.INVALID)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var userResource = getUserResource(userId);
|
||||
var userToBeRemoved = getUserByUsername(userResource.toRepresentation().getUsername());
|
||||
userResource.remove();
|
||||
|
||||
if (status.equals(ValidationResult.ROLE_REMOVAL)) {
|
||||
removeRolesForDeletion(userId,
|
||||
getRoles(userId).stream()
|
||||
.filter(ApplicationRoles::isNoKneconRole)
|
||||
.collect(Collectors.toSet()));
|
||||
} else {
|
||||
userResource.remove();
|
||||
}
|
||||
|
||||
this.rabbitTemplate.convertAndSend(userExchangeName, "user.deleted", (new UserRemovedEvent(userToBeRemoved, KeycloakSecurity.getUserId())));
|
||||
|
||||
@ -394,6 +510,12 @@ public class UserService {
|
||||
var user = this.getUserResource(userId);
|
||||
var userRepresentation = user.toRepresentation();
|
||||
|
||||
Set<String> currentRoles = getRoles(userId);
|
||||
if (!userExists(userId) || !currentRoles.isEmpty() && currentRoles.stream()
|
||||
.allMatch(ApplicationRoles::isKneconRole)) {
|
||||
throw new NotFoundException("User with id: " + userId + " does not exist");
|
||||
}
|
||||
|
||||
if (userRepresentation.getFederatedIdentities() != null && !userRepresentation.getFederatedIdentities().isEmpty() && !updateProfileRequest.getEmail()
|
||||
.equals(userRepresentation.getEmail())) {
|
||||
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "It is not allowed to change the email from a federated identity");
|
||||
@ -413,6 +535,10 @@ public class UserService {
|
||||
setRoles(userId, updateProfileRequest.getRoles());
|
||||
|
||||
var updatedUser = getUserByUsername(userRepresentation.getUsername());
|
||||
updatedUser.setRoles(updatedUser.getRoles()
|
||||
.stream()
|
||||
.filter(ApplicationRoles::isNoKneconRole)
|
||||
.collect(Collectors.toSet()));
|
||||
|
||||
this.rabbitTemplate.convertAndSend(userExchangeName, "user.updated", (new UserUpdatedEvent(updatedUser, KeycloakSecurity.getUserId())));
|
||||
|
||||
@ -422,6 +548,16 @@ public class UserService {
|
||||
|
||||
public User activateProfile(String userId, boolean isActive) {
|
||||
|
||||
if (!userId.equalsIgnoreCase(KeycloakSecurity.getUserId())) {
|
||||
var status = validateExecution(KeycloakSecurity.getUserId(), userId);
|
||||
|
||||
if (status.equals(ValidationResult.FORBIDDEN)) {
|
||||
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "It is not allowed to activate/deactivate a user with higher ranking roles");
|
||||
} else if (status.equals(ValidationResult.INVALID)) {
|
||||
throw new NotFoundException("User with id: " + userId + " does not exist");
|
||||
}
|
||||
}
|
||||
|
||||
var user = this.getUserResource(userId);
|
||||
var userRepresentation = user.toRepresentation();
|
||||
|
||||
@ -429,28 +565,45 @@ public class UserService {
|
||||
user.update(userRepresentation);
|
||||
|
||||
var currentRoles = getRoles(userId);
|
||||
|
||||
if (isActive && currentRoles.isEmpty()) { // add RED_USER role
|
||||
setRoles(userId, tenantUserManagementProperties.getKcRoleMapping().getDefaultRoles());
|
||||
setRoles(userId, tenantApplicationTypeService.getCurrentProperties().getKcRoleMapping().getDefaultRoles());
|
||||
}
|
||||
|
||||
var toggledUser = getUserByUsername(userRepresentation.getUsername());
|
||||
toggledUser.setRoles(toggledUser.getRoles()
|
||||
.stream()
|
||||
.filter(ApplicationRoles::isNoKneconRole)
|
||||
.collect(Collectors.toSet()));
|
||||
|
||||
this.rabbitTemplate.convertAndSend(userExchangeName, "user.statusChanged", (new UserStatusToggleEvent(toggledUser, KeycloakSecurity.getUserId())));
|
||||
|
||||
return convert(this.getTenantUsersResource().get(userId).toRepresentation());
|
||||
return toggledUser;
|
||||
}
|
||||
|
||||
|
||||
public void resetPassword(String userId, ResetPasswordRequest resetPasswordRequest) {
|
||||
|
||||
if (!userId.equalsIgnoreCase(KeycloakSecurity.getUserId())) {
|
||||
var status = validateExecution(KeycloakSecurity.getUserId(), userId);
|
||||
|
||||
if (status.equals(ValidationResult.FORBIDDEN)) {
|
||||
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "It is not allowed to reset the password of a user with higher ranking roles");
|
||||
} else if (status.equals(ValidationResult.INVALID)) {
|
||||
throw new NotFoundException("User with id: " + userId + " does not exist");
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
CredentialRepresentation request = new CredentialRepresentation();
|
||||
request.setType("password");
|
||||
request.setType(CredentialRepresentation.PASSWORD);
|
||||
request.setTemporary(resetPasswordRequest.isTemporary());
|
||||
request.setValue(resetPasswordRequest.getPassword());
|
||||
realmService.realm(TenantContext.getTenantId()).users().get(userId).resetPassword(request);
|
||||
realmService.realm(TenantContext.getTenantId()).users()
|
||||
.get(userId).resetPassword(request);
|
||||
log.info("User {} resetted password for user {}", KeycloakSecurity.getUserId(), userId);
|
||||
} catch (Exception e) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Failed to send email", e);
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Could not reset password. It does not match the password policy.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -460,4 +613,92 @@ public class UserService {
|
||||
return userListingService.getAllUsers(TenantContext.getTenantId());
|
||||
}
|
||||
|
||||
|
||||
private RoleRepresentation getRoleRepresentation(String role) {
|
||||
|
||||
RoleRepresentation realmRole;
|
||||
try {
|
||||
realmRole = realmService.realm(TenantContext.getTenantId()).roles()
|
||||
.get(role).toRepresentation();
|
||||
} catch (NotFoundException e) {
|
||||
log.warn("The realm role {} is not found.", role);
|
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "The realm role " + role + " is not found.", e);
|
||||
}
|
||||
return realmRole;
|
||||
}
|
||||
|
||||
|
||||
private void sendResetPasswordEmail(String userId) {
|
||||
|
||||
try {
|
||||
this.getTenantUsersResource()
|
||||
.get(userId).executeActionsEmail(Collections.singletonList("UPDATE_PASSWORD"), 86400);
|
||||
} catch (Exception e) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Failed to send email.", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private enum ValidationResult {
|
||||
ALLOWED,
|
||||
FORBIDDEN,
|
||||
INVALID,
|
||||
ROLE_REMOVAL
|
||||
|
||||
}
|
||||
|
||||
|
||||
private ValidationResult validateExecution(String executingUserId, String targetUserId) {
|
||||
|
||||
var currentUserResource = getUserResource(executingUserId);
|
||||
var currentRoles = getRoles(currentUserResource.toRepresentation().getId());
|
||||
var userRoles = getRoles(targetUserId);
|
||||
|
||||
return validateRoleRanks(currentRoles, userRoles);
|
||||
}
|
||||
|
||||
|
||||
private ValidationResult validateExecutionForDeletion(String executingUserId, String targetUserId) {
|
||||
|
||||
var currentUserResource = getUserResource(executingUserId);
|
||||
var currentRoles = getRoles(currentUserResource.toRepresentation().getId());
|
||||
var userRoles = getRoles(targetUserId);
|
||||
|
||||
ValidationResult validationResult = validateRoleRanks(currentRoles, userRoles);
|
||||
if (validationResult == ValidationResult.ALLOWED) {
|
||||
if (userRoles.stream()
|
||||
.anyMatch(ApplicationRoles::isKneconRole) && userRoles.stream()
|
||||
.anyMatch(ApplicationRoles::isNoKneconRole)) {
|
||||
return ValidationResult.ROLE_REMOVAL;
|
||||
}
|
||||
}
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
|
||||
private ValidationResult validateRoleRanks(Set<String> currentRoles, Set<String> userRoles) {
|
||||
|
||||
if (!userRoles.isEmpty() && userRoles.stream()
|
||||
.allMatch(ApplicationRoles::isKneconRole)) {
|
||||
return ValidationResult.INVALID;
|
||||
}
|
||||
|
||||
var roleMapping = tenantApplicationTypeService.getCurrentProperties().getKcRoleMapping();
|
||||
var maxRank = currentRoles.stream()
|
||||
.map(r -> roleMapping.getRole(r).getRank())
|
||||
.max(Integer::compare)
|
||||
.orElse(-1);
|
||||
var targetRank = userRoles.stream()
|
||||
.filter(ApplicationRoles::isNoKneconRole)
|
||||
.map(r -> roleMapping.getRole(r).getRank())
|
||||
.max(Integer::compare)
|
||||
.orElse(-1);
|
||||
|
||||
if (targetRank <= maxRank) {
|
||||
return ValidationResult.ALLOWED;
|
||||
} else {
|
||||
return ValidationResult.FORBIDDEN;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,179 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.utils;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
||||
|
||||
import com.knecon.fforesight.databasetenantcommons.providers.utils.MagicConverter;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.ExtensibleModel;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderConfigModel;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderModel;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderNameIDPolicyFormat;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderPrincipalType;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderRequest;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.IdentityProviderWithDescriptorRequest;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
public class IdentityProviderMappingService {
|
||||
|
||||
public static IdentityProviderRepresentation toRepresentationFromRequest(IdentityProviderRequest identityProviderRequest) {
|
||||
|
||||
return toRepresentationFromModel(toModelFromRequest(identityProviderRequest));
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static IdentityProviderModel toModelFromRequest(IdentityProviderRequest identityProviderRequest) {
|
||||
|
||||
if (!identityProviderRequest.getConfig().getWantAuthnRequestsSigned()) {
|
||||
identityProviderRequest.getConfig().setXmlSigKeyInfoKeyNameTransformer(null);
|
||||
identityProviderRequest.getConfig().setSignatureAlgorithm(null);
|
||||
}
|
||||
return MagicConverter.convert(identityProviderRequest, IdentityProviderModel.class, getModelFromRequestDeltaMapper());
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static BiConsumer<IdentityProviderRequest, IdentityProviderModel> getModelFromRequestDeltaMapper() {
|
||||
|
||||
return ((identityProviderRequest, identityProviderModel) -> {
|
||||
var identityProviderConfigModel = MagicConverter.convert(identityProviderRequest.getConfig(), IdentityProviderConfigModel.class);
|
||||
identityProviderModel.setConfig(identityProviderConfigModel);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static IdentityProviderRepresentation toRepresentationFromModel(IdentityProviderModel identityProviderModel) {
|
||||
|
||||
return MagicConverter.convert(identityProviderModel, IdentityProviderRepresentation.class, getModelToRepresentationDeltaMapper());
|
||||
}
|
||||
|
||||
|
||||
private static BiConsumer<IdentityProviderModel, IdentityProviderRepresentation> getModelToRepresentationDeltaMapper() {
|
||||
|
||||
return ((identityProviderModel, identityProviderRepresentation) -> identityProviderRepresentation.setConfig(convertObjectToMap(identityProviderModel.getConfig())));
|
||||
}
|
||||
|
||||
|
||||
public static IdentityProviderModel toModelFromDescriptorRequestAndConfig(IdentityProviderWithDescriptorRequest identityProvider, Map<String, String> configurationsMap) {
|
||||
|
||||
IdentityProviderModel identityProviderModel = MagicConverter.convert(identityProvider, IdentityProviderModel.class);
|
||||
identityProviderModel.setConfig(convertMapToObject(configurationsMap, IdentityProviderConfigModel.class));
|
||||
return identityProviderModel;
|
||||
}
|
||||
|
||||
|
||||
@SneakyThrows
|
||||
public static Map<String, String> convertObjectToMap(Object obj) {
|
||||
|
||||
Map<String, String> resultMap = new HashMap<>();
|
||||
Class<?> clazz = obj.getClass();
|
||||
|
||||
Field[] fields = clazz.getDeclaredFields();
|
||||
for (Field field : fields) {
|
||||
PropertyDescriptor pd = new PropertyDescriptor(field.getName(), clazz);
|
||||
Method getter = pd.getReadMethod();
|
||||
Object fieldValue = getter.invoke(obj);
|
||||
if (fieldValue != null) {
|
||||
resultMap.put(field.getName(), fieldValue.toString());
|
||||
}
|
||||
}
|
||||
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
|
||||
public static IdentityProviderModel toModelFromRepresentation(IdentityProviderRepresentation identityProviderRepresentation) {
|
||||
|
||||
return MagicConverter.convert(identityProviderRepresentation, IdentityProviderModel.class, getRepresentationToModelDeltaMapper());
|
||||
}
|
||||
|
||||
|
||||
private static BiConsumer<IdentityProviderRepresentation, IdentityProviderModel> getRepresentationToModelDeltaMapper() {
|
||||
|
||||
return ((identityProviderRepresentation, identityProviderModel) -> identityProviderModel.setConfig(convertMapToObject(identityProviderRepresentation.getConfig(),
|
||||
IdentityProviderConfigModel.class)));
|
||||
}
|
||||
|
||||
|
||||
@SneakyThrows
|
||||
public static <T extends ExtensibleModel> T convertMapToObject(Map<String, String> map, Class<T> clazz) {
|
||||
|
||||
T obj = clazz.getDeclaredConstructor().newInstance();
|
||||
Field[] fields = clazz.getDeclaredFields();
|
||||
sanitizeMapKeys(map);
|
||||
for (Field field : fields) {
|
||||
String fieldName = field.getName();
|
||||
if (map.containsKey(fieldName)) {
|
||||
String fieldValue = map.get(fieldName);
|
||||
setFieldValue(obj, field, fieldValue);
|
||||
map.remove(fieldName);
|
||||
}
|
||||
}
|
||||
|
||||
obj.setNotMappedFields(map);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
private static void sanitizeMapKeys(Map<String, String> inputMap) {
|
||||
|
||||
var keys = new ArrayList<>(inputMap.keySet());
|
||||
for (String key : keys) {
|
||||
String sanitizedKey = sanitize(key);
|
||||
if (!sanitizedKey.equals(key)) {
|
||||
String value = inputMap.remove(key);
|
||||
inputMap.put(sanitizedKey, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static String sanitize(String input) {
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
String[] segments = input.split("\\.");
|
||||
for (String segment : segments) {
|
||||
if (!result.isEmpty()) {
|
||||
result.append(Character.toUpperCase(segment.charAt(0)));
|
||||
result.append(segment.substring(1));
|
||||
} else {
|
||||
result.append(segment);
|
||||
}
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
|
||||
@SneakyThrows
|
||||
private static <T> void setFieldValue(T obj, Field field, String value) {
|
||||
|
||||
Class<?> fieldType = field.getType();
|
||||
PropertyDescriptor pd = new PropertyDescriptor(field.getName(), obj.getClass());
|
||||
Method setter = pd.getWriteMethod();
|
||||
|
||||
if (fieldType == String.class) {
|
||||
setter.invoke(obj, value);
|
||||
} else if (fieldType == int.class || fieldType == Integer.class) {
|
||||
setter.invoke(obj, Integer.parseInt(value));
|
||||
} else if (fieldType == boolean.class || fieldType == Boolean.class) {
|
||||
setter.invoke(obj, Boolean.parseBoolean(value));
|
||||
} else if (fieldType == IdentityProviderNameIDPolicyFormat.class) {
|
||||
setter.invoke(obj, IdentityProviderNameIDPolicyFormat.fromRepresentation(value));
|
||||
} else if (fieldType == IdentityProviderPrincipalType.class) {
|
||||
setter.invoke(obj, IdentityProviderPrincipalType.fromStringValue(value));
|
||||
} else if (fieldType.isEnum()) {
|
||||
Enum<?> enumValue = Enum.valueOf(fieldType.asSubclass(Enum.class), value);
|
||||
setter.invoke(obj, enumValue);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -13,7 +13,7 @@ import lombok.SneakyThrows;
|
||||
@Converter
|
||||
public class JSONMapConverter implements AttributeConverter<Map<String, Object>, String> {
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
|
||||
@SneakyThrows
|
||||
|
||||
@ -13,7 +13,7 @@ import lombok.SneakyThrows;
|
||||
@Converter
|
||||
public class JSONStringSetConverter implements AttributeConverter<Set<String>, String> {
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
private final static ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
|
||||
@SneakyThrows
|
||||
|
||||
@ -0,0 +1,60 @@
|
||||
package com.knecon.fforesight.tenantusermanagement.utils;
|
||||
|
||||
import com.knecon.fforesight.tenantusermanagement.entity.GlobalSMTPConfigurationEntity;
|
||||
import com.knecon.fforesight.tenantusermanagement.model.SMTPConfiguration;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
@UtilityClass
|
||||
public final class SMTPConfigurationMapper {
|
||||
|
||||
public static GlobalSMTPConfigurationEntity toEntity(SMTPConfiguration smtpConfig) {
|
||||
|
||||
if (smtpConfig == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return GlobalSMTPConfigurationEntity.builder()
|
||||
.id("singleton")
|
||||
.auth(smtpConfig.isAuth())
|
||||
.envelopeFrom(smtpConfig.getEnvelopeFrom())
|
||||
.fromEmail(smtpConfig.getFrom())
|
||||
.fromDisplayName(smtpConfig.getFromDisplayName())
|
||||
.host(smtpConfig.getHost())
|
||||
.password(smtpConfig.getPassword())
|
||||
.port(smtpConfig.getPort())
|
||||
.replyTo(smtpConfig.getReplyTo())
|
||||
.replyToDisplayName(smtpConfig.getReplyToDisplayName())
|
||||
.ssl(smtpConfig.isSsl())
|
||||
.starttls(smtpConfig.isStarttls())
|
||||
.userName(smtpConfig.getUser())
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
public static SMTPConfiguration toModel(GlobalSMTPConfigurationEntity entity) {
|
||||
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
SMTPConfiguration smtpConfig = new SMTPConfiguration();
|
||||
smtpConfig.setId(entity.getId());
|
||||
smtpConfig.setAuth(entity.getAuth() != null && entity.getAuth());
|
||||
smtpConfig.setEnvelopeFrom(entity.getEnvelopeFrom());
|
||||
smtpConfig.setFrom(entity.getFromEmail());
|
||||
smtpConfig.setFromDisplayName(entity.getFromDisplayName());
|
||||
smtpConfig.setHost(entity.getHost());
|
||||
smtpConfig.setPassword(entity.getPassword());
|
||||
smtpConfig.setPort(entity.getPort());
|
||||
smtpConfig.setReplyTo(entity.getReplyTo());
|
||||
smtpConfig.setReplyToDisplayName(entity.getReplyToDisplayName());
|
||||
smtpConfig.setSsl(entity.getSsl() != null && entity.getSsl());
|
||||
smtpConfig.setStarttls(entity.getStarttls() != null && entity.getStarttls());
|
||||
smtpConfig.setUser(entity.getUserName());
|
||||
|
||||
return smtpConfig;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,35 +0,0 @@
|
||||
server:
|
||||
port: 8091
|
||||
|
||||
|
||||
fforesight:
|
||||
tenant-user-management:
|
||||
server-url: http://localhost:8080
|
||||
client-secret: oE2DVrV45w0Tr5jBBcoufVxIkFWU69lP
|
||||
client-id: manager
|
||||
realm: master
|
||||
kc-role-mapping:
|
||||
roles:
|
||||
- name: SUPER_USER
|
||||
set-by-default: true
|
||||
rank: 100
|
||||
permissions:
|
||||
- 'fforesight-read-general-configuration'
|
||||
- 'fforesight-write-general-configuration'
|
||||
- 'fforesight-manage-user-preferences'
|
||||
- 'fforesight-read-users'
|
||||
- 'fforesight-read-all-users'
|
||||
- 'fforesight-write-users'
|
||||
- 'fforesight-update-my-profile'
|
||||
- 'fforesight-create-tenant'
|
||||
- 'fforesight-get-tenants'
|
||||
- 'fforesight-update-tenant'
|
||||
- 'fforesight-deployment-info'
|
||||
- 'fforesight-read-smtp-configuration'
|
||||
- 'fforesight-write-smtp-configuration'
|
||||
springdoc:
|
||||
auth-server-url: http://localhost:8080
|
||||
|
||||
|
||||
|
||||
cors.enabled: true
|
||||
93
src/main/resources/application-dev.yml
Normal file
93
src/main/resources/application-dev.yml
Normal file
@ -0,0 +1,93 @@
|
||||
server:
|
||||
port: 8091
|
||||
|
||||
|
||||
fforesight:
|
||||
tenant-user-management:
|
||||
server-url: http://localhost:8080
|
||||
client-secret: p2InUtjQUDSlwsXyEUFuYrSWi1BeZD1P
|
||||
client-id: manager
|
||||
realm: master
|
||||
application-types:
|
||||
redactmanager:
|
||||
kc-role-mapping:
|
||||
roles:
|
||||
- name: SUPER_USER
|
||||
set-by-default: true
|
||||
rank: 100
|
||||
permissions:
|
||||
- 'fforesight-read-general-configuration'
|
||||
- 'fforesight-write-general-configuration'
|
||||
- 'fforesight-manage-user-preferences'
|
||||
- 'fforesight-read-users'
|
||||
- 'fforesight-read-all-users'
|
||||
- 'fforesight-write-users'
|
||||
- 'fforesight-update-my-profile'
|
||||
- 'fforesight-create-tenant'
|
||||
- 'fforesight-get-tenants'
|
||||
- 'fforesight-delete-tenant'
|
||||
- 'fforesight-update-tenant'
|
||||
- 'fforesight-deployment-info'
|
||||
- 'fforesight-read-smtp-configuration'
|
||||
- 'fforesight-write-smtp-configuration'
|
||||
- 'fforesight-read-identity-provider-config'
|
||||
- 'fforesight-write-identity-provider-config'
|
||||
- name: KNECON_ADMIN
|
||||
set-by-default: true
|
||||
rank: 1000
|
||||
permissions:
|
||||
- 'fforesight-read-general-configuration'
|
||||
- 'fforesight-write-general-configuration'
|
||||
- 'fforesight-manage-user-preferences'
|
||||
- 'fforesight-read-users'
|
||||
- 'fforesight-read-all-users'
|
||||
- 'fforesight-write-users'
|
||||
- 'fforesight-update-my-profile'
|
||||
- 'fforesight-create-tenant'
|
||||
- 'fforesight-get-tenants'
|
||||
- 'fforesight-update-tenant'
|
||||
- 'fforesight-deployment-info'
|
||||
- 'fforesight-read-smtp-configuration'
|
||||
- 'fforesight-write-smtp-configuration'
|
||||
- 'fforesight-read-identity-provider-config'
|
||||
- 'fforesight-write-identity-provider-config'
|
||||
- name: KNECON_SUPPORT
|
||||
set-by-default: true
|
||||
rank: 1000
|
||||
permissions:
|
||||
- 'fforesight-read-general-configuration'
|
||||
- 'fforesight-write-general-configuration'
|
||||
- 'fforesight-manage-user-preferences'
|
||||
- 'fforesight-read-users'
|
||||
- 'fforesight-read-all-users'
|
||||
- 'fforesight-write-users'
|
||||
- 'fforesight-update-my-profile'
|
||||
- 'fforesight-create-tenant'
|
||||
- 'fforesight-get-tenants'
|
||||
- 'fforesight-update-tenant'
|
||||
- 'fforesight-deployment-info'
|
||||
- 'fforesight-read-smtp-configuration'
|
||||
- 'fforesight-write-smtp-configuration'
|
||||
- 'fforesight-read-identity-provider-config'
|
||||
- 'fforesight-write-identity-provider-config'
|
||||
application-name: "redaction"
|
||||
springdoc:
|
||||
auth-server-url: http://localhost:8080
|
||||
|
||||
dev.tenant.storage:
|
||||
mode: 'S3'
|
||||
s3:
|
||||
key: minioadmin
|
||||
secret: minioadmin
|
||||
bucket: redaction
|
||||
endpoint: http://localhost:9000
|
||||
|
||||
dev.tenant.db:
|
||||
port: 5432
|
||||
host: localhost
|
||||
database: master
|
||||
schema: public
|
||||
username: fforesight
|
||||
password: fforesight
|
||||
|
||||
cors.enabled: true
|
||||
@ -1,53 +0,0 @@
|
||||
fforesight:
|
||||
tenant-user-management:
|
||||
application-client-id: 'redaction'
|
||||
application-name: 'RedactManager'
|
||||
client-id: 'manager'
|
||||
tenant-access-token-life-span: 300
|
||||
realm: master
|
||||
default-theme: 'scm'
|
||||
valid-redirect-uris: [ '/redaction-gateway-v1/*','/tenant-user-management/*','http://localhost:4200/*','/ui/*' ,'/auth/*' ]
|
||||
kc-role-mapping:
|
||||
unmappedPermissions: [ "red-unarchive-dossier", "red-update-license", "fforesight-create-tenant", "fforesight-update-tenant" ]
|
||||
compositeRoles:
|
||||
- name: RED_MANAGER
|
||||
composites:
|
||||
- name: RED_USER
|
||||
- name: RED_ADMIN
|
||||
composites:
|
||||
- name: RED_USER_ADMIN
|
||||
|
||||
|
||||
roles:
|
||||
- name: RED_USER
|
||||
set-by-default: true
|
||||
rank: 100
|
||||
permissions: [ "red-get-tables", "red-get-rss", "red-add-comment", "red-read-license", "red-read-app-configuration", "red-read-dossier-status", "red-add-dossier-dictionary-entry", "red-add-redaction", "red-add-update-dossier-dictionary-type",
|
||||
"red-delete-comment", "red-delete-dossier-dictionary-entry", "red-delete-dossier-dictionary-type", "red-delete-file", "red-delete-manual-redaction", "red-download-annotated-file",
|
||||
"red-download-original-file", "red-download-redacted-file", "red-download-redaction-preview-file", "red-download-report-template", "red-exclude-include-file",
|
||||
"red-exclude-include-pages", "red-get-report-templates", "fforesight-manage-user-preferences", "red-manage-viewed-pages", "red-process-download", "red-process-manual-redaction-request",
|
||||
"red-read-colors", "red-read-dictionary-types", "red-read-digital-signature", "red-read-dossier", "red-read-dossier-attributes", "red-read-dossier-attributes-config",
|
||||
"red-read-dossier-templates", "red-read-download-status", "red-read-file-attributes-config", "red-read-file-status", "fforesight-read-general-configuration", "red-read-legal-basis",
|
||||
"red-read-manual-redactions", "red-read-notification", "red-read-redaction-log", "red-read-rules", "fforesight-read-users", "red-read-versions", "red-reanalyze-dossier",
|
||||
"red-reanalyze-file", "red-request-redaction", "red-rotate-page", "red-search", "red-search-audit-log", "red-set-reviewer", "red-set-status-approved", "red-set-status-under-approval",
|
||||
"fforesight-update-my-profile", "red-update-notification", "red-upload-file", "red-write-file-attributes", "red-process-texthighlights", "red-get-highlights", "red-convert-highlights", "red-delete-highlights", "red-delete-imported-redactions" ]
|
||||
- name: RED_ADMIN
|
||||
set-by-default: false
|
||||
rank: 800
|
||||
permissions: [ "red-add-dictionary-entry", "red-add-update-dictionary-type", "red-write-dossier-status", "red-read-dossier-status", "red-delete-dictionary-entry", "red-delete-dictionary-type",
|
||||
"red-delete-report-template", "red-download-report-template", "red-get-report-templates", "fforesight-manage-user-preferences", "red-read-colors", "red-read-dictionary-types",
|
||||
"red-read-digital-signature", "red-read-dossier-attributes", "red-read-dossier-attributes-config", "red-read-dossier-templates", "red-read-file-attributes-config",
|
||||
"red-read-legal-basis", "red-read-license-report", "red-read-notification", "red-read-rules", "fforesight-read-smtp-configuration", "red-read-versions", "red-reindex", "red-search-audit-log", "red-update-notification", "red-upload-report-template", "red-write-colors", "red-write-digital-signature", "red-write-dossier-attributes-config",
|
||||
"red-write-dossier-templates", "red-write-file-attributes-config", "fforesight-write-general-configuration", "red-write-legal-basis", "red-write-rules", "fforesight-write-smtp-configuration", "red-write-app-configuration", "red-manage-acl-permissions", "fforesight-create-tenant", "fforesight-get-tenants", "fforesight-update-tenant", "fforesight-deployment-info" ]
|
||||
- name: RED_MANAGER
|
||||
set-by-default: false
|
||||
rank: 200
|
||||
permissions: [ "red-add-update-dossier", "red-archived-dossier", "red-delete-dossier", "red-write-dossier-attributes" ]
|
||||
- name: RED_USER_ADMIN
|
||||
set-by-default: false
|
||||
rank: 400
|
||||
permissions: [ "fforesight-manage-user-preferences", "fforesight-read-all-users", "red-read-dossier", "red-read-app-configuration", "fforesight-read-general-configuration",
|
||||
"red-read-notification", "fforesight-read-users", "fforesight-update-my-profile", "red-update-notification", "fforesight-write-users", "red-read-license" ]
|
||||
|
||||
springdoc:
|
||||
default-tenant: 'redaction'
|
||||
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